Parser based on the vt100.net diagram

Substitute konsole's old vt102 parser with one based on the diagram at
vt100.net.

Williams, Paul Flo. “A parser for DEC’s ANSI-compatible video
terminals.” VT100.net. https://vt100.net/emu/dec_ansi_parser
wilder
Luis Javier Merino Morán 4 years ago committed by Tomaz Canabrava
parent af32e11c5d
commit 497f9afbd5
  1. 872
      src/Vt102Emulation.cpp
  2. 48
      src/Vt102Emulation.h
  3. 6
      src/autotests/Vt102EmulationTest.h

@ -13,6 +13,7 @@
#include <unistd.h>
// Qt
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
#include <QTimer>
@ -253,6 +254,14 @@ constexpr int token_csi_pq(int a)
{
return token_construct(13, a, 0);
}
constexpr int token_osc(int a)
{
return token_construct(14, a, 0);
}
constexpr int token_apc(int a)
{
return token_construct(15, a, 0);
}
const int MAX_ARGUMENT = 40960;
@ -386,253 +395,611 @@ const int DEL = 127;
const int SP = 32;
// process an incoming unicode character
void Vt102Emulation::receiveChars(const QVector<uint> &chars)
//
// Parser based on the vt100.net diagram:
// Williams, Paul Flo. “A parser for DEC’s ANSI-compatible video
// terminals.” VT100.net. https://vt100.net/emu/dec_ansi_parser
void Vt102Emulation::switchState(const ParserStates newState, const uint cc)
{
for (uint cc : chars) {
if (cc == DEL) {
continue; // VT100: ignore.
switch (_state) {
case DcsPassthrough:
unhook();
break;
case OscString:
osc_end(cc);
break;
case SosPmApcString:
apc_end();
break;
default:
break;
}
_state = newState;
}
void Vt102Emulation::esc_dispatch(const uint cc)
{
if (_ignore)
return;
if (_nIntermediate == 0) {
processToken(token_esc(cc), 0, 0);
} else if (_nIntermediate == 1) {
const uint intermediate = _intermediate[0];
if ((charClass[intermediate] & SCS) == SCS) {
processToken(token_esc_cs(intermediate, cc), 0, 0);
} else if (intermediate == '#') {
processToken(token_esc_de(cc), 0, 0);
}
}
}
// early out for displayable characters
if (!getMode(MODE_Sixel) && getMode(MODE_Ansi) && tokenBufferPos == 0 && cc >= 32 && cc != (ESC + 128)) {
_currentScreen->displayCharacter(applyCharset(cc));
continue;
void Vt102Emulation::clear()
{
_nIntermediate = 0;
_ignore = false;
tokenBufferPos = 0;
tokenState = -1;
argc = 0;
argv[0] = 0;
argv[1] = 0;
}
#define MAX_INTERMEDIATES 1
void Vt102Emulation::collect(const uint cc)
{
addToCurrentToken(cc);
if (cc > 0x30)
return;
if (_nIntermediate >= MAX_INTERMEDIATES) {
_ignore = true;
}
_intermediate[_nIntermediate++] = cc;
}
void Vt102Emulation::param(const uint cc)
{
addToCurrentToken(cc);
if ((charClass[cc] & DIG) == DIG) {
addDigit(cc - '0');
} else if (cc == ';') {
addArgument();
} else if (cc == ':') {
_ignore = true;
}
}
void Vt102Emulation::csi_dispatch(const uint cc)
{
if (_ignore)
return;
if ((tokenBufferPos == 0 || (tokenBuffer[0] != '?' && tokenBuffer[0] != '!' && tokenBuffer[0] != '=' && tokenBuffer[0] != '>')) && cc < 256
&& (charClass[cc] & CPN) == CPN && _nIntermediate == 0) {
processToken(token_csi_pn(cc), argv[0], argv[1]);
} else if ((tokenBufferPos == 0 || (tokenBuffer[0] != '?' && tokenBuffer[0] != '!' && tokenBuffer[0] != '=' && tokenBuffer[0] != '>')) && cc < 256
&& (charClass[cc] & CPS) == CPS && _nIntermediate == 0) {
processToken(token_csi_ps(cc, argv[0]), argv[1], argv[2]);
} else if (tokenBufferPos != 0 && tokenBuffer[0] == '!') {
processToken(token_csi_pe(cc), 0, 0);
} else if (_nIntermediate == 1 && _intermediate[0] == ' ') {
if (tokenBufferPos == 1) {
processToken(token_csi_sp(cc), 0, 0);
} else {
processToken(token_csi_psp(cc, argv[0]), 0, 0);
}
} else if (cc == 'y' && _nIntermediate == 1 && _intermediate[0] == '*') {
processChecksumRequest(argc, argv);
} else {
for (int i = 0; i <= argc; i++) {
if (tokenBufferPos != 0 && tokenBuffer[0] == '?') {
processToken(token_csi_pr(cc, argv[i]), i, 0);
} else if (tokenBufferPos != 0 && tokenBuffer[0] == '=') {
processToken(token_csi_pq(cc), 0, 0);
} else if (tokenBufferPos != 0 && tokenBuffer[0] == '>') {
processToken(token_csi_pg(cc), 0, 0);
} else if (cc == 'm' && argc - i >= 4 && (argv[i] == 38 || argv[i] == 48) && argv[i + 1] == 2) {
// ESC[ ... 48;2;<red>;<green>;<blue> ... m -or- ESC[ ... 38;2;<red>;<green>;<blue> ... m
i += 2;
processToken(token_csi_ps(cc, argv[i - 2]), COLOR_SPACE_RGB, (argv[i] << 16) | (argv[i + 1] << 8) | argv[i + 2]);
i += 2;
} else if (cc == 'm' && argc - i >= 2 && (argv[i] == 38 || argv[i] == 48) && argv[i + 1] == 5) {
// ESC[ ... 48;5;<index> ... m -or- ESC[ ... 38;5;<index> ... m
i += 2;
processToken(token_csi_ps(cc, argv[i - 2]), COLOR_SPACE_256, argv[i]);
} else if (_nIntermediate == 0) {
processToken(token_csi_ps(cc, argv[i]), 0, 0);
}
}
}
}
void Vt102Emulation::osc_start()
{
tokenBufferPos = 0;
}
void Vt102Emulation::osc_put(const uint cc)
{
addToCurrentToken(cc);
if (!getMode(MODE_Sixel) && ces(CTL)) {
// ignore control characters in the text part of osc (aka OSC) "ESC]"
// escape sequences; this matches what XTERM docs say
// Allow BEL and ESC here, it will either end the text or be removed later.
if ((osc || apc) && cc != 0x1b && cc != 0x07) {
continue;
// Special case: iterm file protocol is a long escape sequence
if (tokenState == -1) {
tokenStateChange = "1337;File=:";
tokenState = 0;
}
if (tokenState >= 0) {
if ((uint)tokenStateChange[tokenState] == tokenBuffer[tokenBufferPos - 1]) {
tokenState++;
tokenPos = tokenBufferPos;
if ((uint)tokenState == strlen(tokenStateChange)) {
tokenState = -2;
tokenData.clear();
}
return;
}
} else if (tokenState == -2) {
if (tokenBufferPos - tokenPos == 4) {
tokenData.append(QByteArray::fromBase64(QString::fromUcs4(&tokenBuffer[tokenPos], 4).toLocal8Bit()));
tokenBufferPos -= 4;
return;
}
}
}
if (!osc && !apc) {
// DEC HACK ALERT! Control Characters are allowed *within* esc sequences in VT100
// This means, they do neither a resetTokenizer() nor a pushToToken(). Some of them, do
// of course. Guess this originates from a weakly layered handling of the X-on
// X-off protocol, which comes really below this level.
if (cc == CNTL('X') || cc == CNTL('Z') || cc == ESC) {
resetTokenizer(); // VT100: CAN or SUB
}
if (cc != ESC) {
processToken(token_ctl(cc + '@'), 0, 0);
continue;
void Vt102Emulation::osc_end(const uint cc)
{
// This runs two times per link, the first prepares the link to be read,
// the second finalizes it. The escape sequence is in two parts
// start: '\e ] 8 ; <id-path> ; <url-part> \e \\'
// end: '\e ] 8 ; ; \e \\'
// GNU libtextstyle inserts the IDs, for instance; many examples
// do not.
if (tokenBuffer[0] == XTERM_EXTENDED::URL_LINK) {
// printf '\e]8;;https://example.com\e\\This is a link\e]8;;\e\\\n'
emit toggleUrlExtractionRequest();
}
processSessionAttributeRequest(tokenBufferPos, cc);
}
void Vt102Emulation::put(const uint cc)
{
if (m_SixelPictureDefinition && cc >= 0x21) {
addToCurrentToken(cc);
processSixel(cc);
}
}
void Vt102Emulation::hook(const uint cc)
{
if (cc == 'q' && _nIntermediate == 0) {
m_SixelPictureDefinition = true;
resetTokenizer();
}
}
void Vt102Emulation::unhook()
{
m_SixelPictureDefinition = false;
SixelModeDisable();
resetTokenizer();
}
void Vt102Emulation::apc_start()
{
tokenBufferPos = 0;
}
void Vt102Emulation::apc_put(const uint cc)
{
addToCurrentToken(cc);
// <ESC> '_' ... <ESC> '\'
if (tokenBufferPos > 1 && tokenBuffer[0] == 'G') {
if (tokenState == -1) {
tokenStateChange = ";";
tokenState = 0;
} else if (tokenState >= 0) {
if ((uint)tokenStateChange[tokenState] == tokenBuffer[tokenBufferPos - 1]) {
tokenState++;
tokenPos = tokenBufferPos;
if ((uint)tokenState == strlen(tokenStateChange)) {
tokenState = -2;
tokenData.clear();
}
}
} else if (tokenState == -2) {
if (tokenBufferPos - tokenPos == 4) {
tokenData.append(QByteArray::fromBase64(QString::fromUcs4(&tokenBuffer[tokenPos], 4).toLocal8Bit()));
tokenBufferPos -= 4;
}
}
// advance the state
addToCurrentToken(cc);
}
}
uint *s = tokenBuffer;
const int p = tokenBufferPos;
void Vt102Emulation::apc_end()
{
if (tokenBuffer[0] == 'G') {
// Graphics command
processGraphicsToken(tokenBufferPos);
resetTokenizer();
}
}
if (getMode(MODE_Sixel) && processSixel(cc)) {
void Vt102Emulation::receiveChars(const QVector<uint> &chars)
{
for (uint cc : chars) {
// early out for displayable characters
if (_state == Ground && ((cc >= 0x20 && cc <= 0x7E) || cc >= 0xA0)) {
_currentScreen->displayCharacter(applyCharset(cc));
continue;
}
if (getMode(MODE_Ansi)) {
if (lec(1, 0, ESC)) {
continue;
}
if (lec(1, 0, ESC + 128)) {
s[0] = ESC;
receiveChars(QVector<uint>{'['});
continue;
}
if (les(2, 1, GRP)) {
continue;
}
// Operating System Command
if (p > 2 && s[1] == ']') {
// <ESC> ']' ... <ESC> '\'
if (s[p - 2] == ESC && s[p - 1] == '\\') {
// This runs two times per link, the first prepares the link to be read,
// the second finalizes it. The escape sequence is in two parts
// start: '\e ] 8 ; <id-path> ; <url-part> \e \\'
// end: '\e ] 8 ; ; \e \\'
// GNU libtextstyle inserts the IDs, for instance; many examples
// do not.
if (s[2] == XTERM_EXTENDED::URL_LINK) {
// printf '\e]8;;https://example.com\e\\This is a link\e]8;;\e\\\n'
emit toggleUrlExtractionRequest();
// First, process characters that act the same on all states, i.e.
// coming from "anywhere" in the VT100.net diagram.
switch (cc) {
case 0x1B:
switchState(Escape, cc);
clear();
break;
case 0x9B:
switchState(CsiEntry, cc);
clear();
break;
case 0x90:
switchState(DcsEntry, cc);
clear();
break;
case 0x9D:
osc_start();
switchState(OscString, cc);
break;
case 0x18:
case 0x1A:
case 0x80 ... 0x8F:
case 0x91 ... 0x97:
case 0x99 ... 0x9A:
processToken(token_ctl(cc + '@'), 0, 0);
switchState(Ground, cc);
break;
case 0x9C:
// no action
switchState(Ground, cc);
break;
case 0x98:
case 0x9E:
case 0x9F:
apc_start();
switchState(SosPmApcString, cc);
break;
default:
// Now take the current state into account
switch (_state) {
case Ground:
switch (cc) {
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
default:
_currentScreen->displayCharacter(applyCharset(cc));
break;
}
processSessionAttributeRequest(p - 1);
resetTokenizer();
continue;
}
// <ESC> ']' ... <ESC> + one character for reprocessing
if (s[p - 2] == ESC) {
processSessionAttributeRequest(p - 1);
resetTokenizer();
receiveChars(QVector<uint>{cc});
continue;
}
// <ESC> ']' ... <BEL>
if (s[p - 1] == 0x07) {
processSessionAttributeRequest(p);
resetTokenizer();
continue;
}
// Special case: iterm file protocol is a long escape sequence
if (tokenState == -1) {
tokenStateChange = "1337;File=:";
tokenState = 0;
}
if (tokenState >= 0) {
if ((uint)tokenStateChange[tokenState] == s[p - 1]) {
tokenState++;
tokenPos = p;
if ((uint)tokenState == strlen(tokenStateChange)) {
tokenState = -2;
tokenData.clear();
}
continue;
break;
case Escape:
switch (cc) {
case 0x5B:
switchState(CsiEntry, cc);
clear();
break;
case 0x30 ... 0x4F:
case 0x51 ... 0x57:
case 0x59 ... 0x5A:
case 0x5C:
case 0x60 ... 0x7E:
esc_dispatch(cc);
switchState(Ground, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
switchState(EscapeIntermediate, cc);
break;
case 0x5D:
osc_start();
switchState(OscString, cc);
break;
case 0x50:
switchState(DcsEntry, cc);
clear();
break;
case 0x58:
case 0x5E:
case 0x5F:
apc_start();
switchState(SosPmApcString, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
} else if (tokenState == -2) {
if (p - tokenPos == 4) {
tokenData.append(QByteArray::fromBase64(QString::fromUcs4(&tokenBuffer[tokenPos], 4).toLocal8Bit()));
tokenBufferPos -= 4;
continue;
break;
case EscapeIntermediate:
switch (cc) {
case 0x30 ... 0x7E:
esc_dispatch(cc);
switchState(Ground, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
}
}
// Privacy Message
if (p > 2 && s[1] == '^') {
if (s[p - 1] == 0x07 || (s[p - 2] == ESC && s[p - 1] == '\\')) {
resetTokenizer();
continue;
}
}
// Application Program Command
if (p > 2 && s[1] == '_') {
// <ESC> '_' ... <ESC> '\'
if (p > 3 && s[2] == 'G') {
if (tokenState == -1) {
tokenStateChange = ";";
tokenState = 0;
} else if (tokenState >= 0) {
if ((uint)tokenStateChange[tokenState] == s[p - 1]) {
tokenState++;
tokenPos = p;
if ((uint)tokenState == strlen(tokenStateChange)) {
tokenState = -2;
tokenData.clear();
}
continue;
}
} else if (tokenState == -2) {
if (p - tokenPos == 4) {
tokenData.append(QByteArray::fromBase64(QString::fromUcs4(&tokenBuffer[tokenPos], 4).toLocal8Bit()));
tokenBufferPos -= 4;
continue;
}
break;
case CsiEntry:
switch (cc) {
case 0x40 ... 0x7E:
csi_dispatch(cc);
switchState(Ground, cc);
break;
case 0x30 ... 0x3B: // recognize 0x3A as part of params
param(cc);
switchState(CsiParam, cc);
break;
case 0x3C ... 0x3F:
collect(cc);
switchState(CsiParam, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
switchState(CsiIntermediate, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
}
if (s[p - 1] == 0x07 || (s[p - 2] == ESC && s[p - 1] == '\\')) {
if (s[2] == 'G') {
// Graphics command
processGraphicsToken(p);
break;
case CsiParam:
switch (cc) {
case 0x40 ... 0x7E:
csi_dispatch(cc);
switchState(Ground, cc);
break;
case 0x30 ... 0x3B: // recognize 0x3A as part of params
param(cc);
break;
case 0x3C ... 0x3F:
switchState(CsiIgnore, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
switchState(CsiIntermediate, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
resetTokenizer();
continue;
}
}
/* clang-format off */
// <ESC> ']' ...
if (osc ) { continue; }
if (pm ) { continue; }
if (apc ) { continue; }
if (p >= 3 && s[1] == '[') { // parsing a CSI sequence
if (lec(3,2,'?')) { continue; }
if (lec(3,2,'=')) { continue; }
if (lec(3,2,'>')) { continue; }
if (lec(3,2,'!')) { continue; }
if (lec(3,2,SP )) { continue; }
if (lec(4,3,SP )) { continue; }
if (eps( CPN)) { processToken(token_csi_pn(cc), argv[0],argv[1]); resetTokenizer(); continue; }
// resize = \e[8;<row>;<col>t
if (eps(CPS)) {
processToken(token_csi_ps(cc, argv[0]), argv[1], argv[2]);
resetTokenizer();
continue;
}
if (epe( )) { processToken(token_csi_pe(cc), 0, 0); resetTokenizer(); continue; }
if (esp ( )) { processToken(token_csi_sp(cc), 0, 0); resetTokenizer(); continue; }
if (epsp( )) { processToken(token_csi_psp(cc, argv[0]), 0, 0); resetTokenizer(); continue; }
if (ees(DIG)) { addDigit(cc-'0'); continue; }
if (eec(';')) { addArgument(); continue; }
if (ees(INT)) { continue; }
if (p >= 3 && cc == 'y' && s[p - 2] == '*') { processChecksumRequest(argc, argv); resetTokenizer(); continue; }
for (int i = 0; i <= argc; i++) {
if (epp()) {
processToken(token_csi_pr(cc,argv[i]), i, 0);
} else if (eeq()) {
processToken(token_csi_pq(cc), 0, 0); // spec. case for ESC[=0c or ESC[=c
} else if (egt()) {
processToken(token_csi_pg(cc), 0, 0); // spec. case for ESC[>0c or ESC[>c
} else if (cc == 'm' && argc - i >= 4 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 2)
{
// ESC[ ... 48;2;<red>;<green>;<blue> ... m -or- ESC[ ... 38;2;<red>;<green>;<blue> ... m
i += 2;
processToken(token_csi_ps(cc, argv[i-2]), COLOR_SPACE_RGB, (argv[i] << 16) | (argv[i+1] << 8) | argv[i+2]);
i += 2;
} else if (cc == 'm' && argc - i >= 2 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 5) {
// ESC[ ... 48;5;<index> ... m -or- ESC[ ... 38;5;<index> ... m
i += 2;
processToken(token_csi_ps(cc, argv[i-2]), COLOR_SPACE_256, argv[i]);
} else if (p < 2 || (charClass[s[p-2]] & (INT)) != (INT)) {
processToken(token_csi_ps(cc,argv[i]), 0, 0);
}
}
} else {
if (dcs) {
// Check for Sixel DCS q
if (p > 2 && cc == 'q') {
setMode(MODE_Sixel);
// This parameter appears to be ignored
// m_preserveBackground = argv[2] == 1;
resetTokenizer();
break;
case CsiIntermediate:
switch (cc) {
case 0x40 ... 0x7E:
csi_dispatch(cc);
switchState(Ground, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
break;
case 0x30 ... 0x3F:
switchState(CsiIgnore, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
break;
case CsiIgnore:
switch (cc) {
case 0x40 ... 0x7E:
switchState(Ground, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x20 ... 0x3F:
case 0x7F:
// ignore
break;
}
break;
case DcsEntry:
switch (cc) {
case 0x40 ... 0x7E:
hook(cc);
switchState(DcsPassthrough, cc);
break;
case 0x30 ... 0x3B: // recognize 0x3A as part of params
param(cc);
switchState(DcsParam, cc);
break;
case 0x3C ... 0x3F:
collect(cc);
switchState(DcsParam, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
switchState(DcsIntermediate, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
break;
case DcsParam:
switch (cc) {
case 0x40 ... 0x7E:
hook(cc);
switchState(DcsPassthrough, cc);
break;
case 0x30 ... 0x3B: // recognize 0x3A as part of params
param(cc);
break;
case 0x3C ... 0x3F:
switchState(DcsIgnore, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
switchState(DcsIntermediate, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
break;
case DcsIntermediate:
switch (cc) {
case 0x40 ... 0x7E:
hook(cc);
switchState(DcsPassthrough, cc);
break;
case 0x20 ... 0x2F:
collect(cc);
break;
case 0x30 ... 0x3F:
switchState(DcsIgnore, cc);
break;
case 0 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
processToken(token_ctl(cc + '@'), 0, 0);
break;
case 0x7F:
// ignore
break;
}
break;
case DcsPassthrough:
switch (cc) {
case 0 ... 0x7E: // 0x18, 0x1A, 0x1B already taken care of
put(cc);
break;
case 0x9C:
switchState(Ground, cc);
break;
case 0x7F:
// ignore
break;
}
break;
case DcsIgnore:
switch (cc) {
case 0x9C:
switchState(Ground, cc);
break;
case 0 ... 0x7F:
// ignore
break;
}
break;
case OscString:
switch (cc) {
case 0x20 ... 0x7F:
osc_put(cc);
break;
case 0x07: // recognize BEL as OSC terminator
case 0x9C:
switchState(Ground, cc);
break;
case 0 ... 0x06:
case 0x08 ... 0x1F: // 0x18, 0x1A, 0x1B already taken care of.
// ignore
break;
}
break;
case SosPmApcString:
switch (cc) {
case 0 ... 0x7F: // 0x18, 0x1A, 0x1B already taken care of.
apc_put(cc);
// ignore
break;
case 0x9C:
switchState(Ground, cc);
break;
}
break;
}
if (ccc(DIG)) { addDigit(cc-'0');}
if (cc == ';') { addArgument();}
continue;
break;
}
if (lec(2,0,ESC)) { processToken(token_esc(s[1]), 0, 0); resetTokenizer(); continue; }
if (les(3,1,SCS)) { processToken(token_esc_cs(s[1],s[2]), 0, 0); resetTokenizer(); continue; }
if (lec(3,1,'#')) { processToken(token_esc_de(s[2]), 0, 0); resetTokenizer(); continue; }
}
resetTokenizer();
/* clang-format on */
} else {
// VT52 Mode
if (lec(1, 0, ESC)) {
continue;
}
if (les(1, 0, CHR)) {
processToken(token_chr(), s[0], 0);
resetTokenizer();
continue;
}
if (lec(2, 1, 'Y')) {
continue;
}
if (lec(3, 1, 'Y')) {
continue;
}
if (p < 4) {
processToken(token_vt52(s[1]), 0, 0);
resetTokenizer();
continue;
// First, process characters that act the same on all states
switch (cc) {
case 0x18:
case 0x1A:
processToken(token_ctl(cc + '@'), 0, 0);
switchState(Ground, cc);
break;
case 0x1B:
switchState(Vt52Escape, cc);
break;
case 0 ... 0x17:
case 0x19:
case 0x1C ... 0x1F:
processToken(token_ctl(cc + '@'), 0, 0);
break;
default:
// Now take the current state into account
switch (_state) {
case Ground:
_currentScreen->displayCharacter(applyCharset(cc));
break;
case Vt52Escape:
switch (cc) {
case 'Y':
switchState(Vt52CupRow, cc);
break;
case 0x20 ... 'X':
case 'Z' ... 0x7F:
processToken(token_vt52(cc), 0, 0);
switchState(Ground, cc);
break;
}
break;
case Vt52CupRow:
tokenBuffer[0] = cc;
switchState(Vt52CupColumn, cc);
break;
case Vt52CupColumn:
processToken(token_vt52('Y'), tokenBuffer[0], cc);
switchState(Ground, cc);
break;
default:
break;
}
break;
}
processToken(token_vt52(s[1]), s[2], s[3]);
resetTokenizer();
continue;
}
}
}
@ -705,7 +1072,7 @@ void Vt102Emulation::processChecksumRequest([[maybe_unused]] int crargc, int cra
sendString(tmp);
}
void Vt102Emulation::processSessionAttributeRequest(int tokenSize)
void Vt102Emulation::processSessionAttributeRequest(const int tokenSize, const uint terminator)
{
// Describes the window or terminal session attribute to change
// See Session::SessionAttributes for possible values
@ -713,11 +1080,11 @@ void Vt102Emulation::processSessionAttributeRequest(int tokenSize)
int i;
// ignore last character (ESC or BEL)
--tokenSize;
//--tokenSize;
/* clang-format off */
// skip first two characters (ESC, ']')
for (i = 2; i < tokenSize &&
for (i = 0; i < tokenSize &&
tokenBuffer[i] >= '0' &&
tokenBuffer[i] <= '9'; i++)
{
@ -726,7 +1093,7 @@ void Vt102Emulation::processSessionAttributeRequest(int tokenSize)
/* clang-format on */
if (tokenBuffer[i] != ';') {
reportDecodingError();
reportDecodingError(token_osc(terminator));
return;
}
// skip initial ';'
@ -781,7 +1148,7 @@ void Vt102Emulation::processSessionAttributeRequest(int tokenSize)
if (value == QLatin1String("?")) {
// pass terminator type indication here, because OSC response terminator
// should match the terminator of OSC request.
Q_EMIT sessionAttributeRequest(attribute, tokenBuffer[tokenSize]);
Q_EMIT sessionAttributeRequest(attribute, terminator);
return;
}
@ -1328,7 +1695,7 @@ void Vt102Emulation::processToken(int token, int p, int q)
case token_vt52('>' ) : resetMode (MODE_AppKeyPad); break; //VT52
default:
reportDecodingError();
reportDecodingError(token);
break;
}
/* clang-format on */
@ -1341,16 +1708,16 @@ void delete_func(void *p)
void Vt102Emulation::processGraphicsToken(int tokenSize)
{
QString value = QString::fromUcs4(&tokenBuffer[3], tokenSize - 4);
QString value = QString::fromUcs4(&tokenBuffer[1], tokenSize - 1);
QStringList list;
QPixmap pixmap;
int dataPos = value.indexOf(QLatin1Char(';'));
if (dataPos == -1) {
dataPos = value.size() - 1;
dataPos = value.size();
}
if (dataPos > 1024) {
reportDecodingError();
reportDecodingError(token_apc('G'));
return;
}
list = value.mid(0, dataPos).split(QLatin1Char(','));
@ -1383,7 +1750,7 @@ void Vt102Emulation::processGraphicsToken(int tokenSize)
for (int i = 0; i < list.size(); i++) {
if (list.at(i).at(1).toLatin1() != '=') {
reportDecodingError();
reportDecodingError(token_apc('G'));
return;
}
if (list.at(i).at(2).isNumber() || list.at(i).at(2).toLatin1() == '-') {
@ -2221,7 +2588,7 @@ static QString hexdump2(uint *s, int len)
return returnDump;
}
void Vt102Emulation::reportDecodingError()
void Vt102Emulation::reportDecodingError(int token)
{
resetTokenizer();
@ -2229,13 +2596,44 @@ void Vt102Emulation::reportDecodingError()
SixelModeAbort();
}
if (tokenBufferPos == 0 || (tokenBufferPos == 1 && (tokenBuffer[0] & 0xff) >= 32)) {
return;
QString outputError = QStringLiteral("Undecodable sequence: ");
printf("token: %x\n", token);
switch (token & 0xff) {
case 2:
case 3:
case 4:
outputError.append(QStringLiteral("ESC "));
break;
case 5:
case 6:
case 7:
case 9:
case 10:
case 11:
case 12:
case 13:
outputError.append(QStringLiteral("CSI "));
break;
case 14:
outputError.append(QStringLiteral("OSC "));
break;
case 15:
outputError.append(QStringLiteral("APC "));
break;
}
if ((token & 0xff) != 8) { // VT52
outputError.append(hexdump2(tokenBuffer, tokenBufferPos));
} else {
outputError.append(QStringLiteral("(VT52) ESC"));
}
QString outputError = QStringLiteral("Undecodable sequence: ");
outputError.append(hexdump2(tokenBuffer, tokenBufferPos));
qCDebug(KonsoleDebug).noquote() << outputError;
if ((token & 0xff) != 3) { // SCS
outputError.append((token >> 8) & 0xff);
} else {
outputError.append((token >> 16) & 0xff);
}
qDebug() << outputError;
}
void Vt102Emulation::sixelQuery(int q)

@ -135,6 +135,49 @@ private:
int argv[MAXARGS] = {};
int argc;
void initTokenizer();
enum ParserStates {
Ground,
Escape,
EscapeIntermediate,
CsiEntry,
CsiParam,
CsiIntermediate,
CsiIgnore,
DcsEntry,
DcsParam,
DcsIntermediate,
DcsPassthrough,
DcsIgnore,
OscString,
SosPmApcString,
Vt52Escape,
Vt52CupRow,
Vt52CupColumn,
};
ParserStates _state = Ground;
bool _ignore = false;
int _nIntermediate = 0;
unsigned char _intermediate[1];
void switchState(const ParserStates newState, const uint cc);
void esc_dispatch(const uint cc);
void clear();
void collect(const uint cc);
void param(const uint cc);
void csi_dispatch(const uint cc);
void osc_start();
void osc_put(const uint cc);
void osc_end(const uint cc);
void hook(const uint cc);
void unhook();
void put(const uint cc);
void apc_start();
void apc_put(const uint cc);
void apc_end();
// State machine for escape sequences containing large amount of data
int tokenState;
const char *tokenStateChange;
@ -151,10 +194,10 @@ private:
QMap<char, qint64> savedKeys;
protected:
virtual void reportDecodingError();
virtual void reportDecodingError(int token);
virtual void processToken(int code, int p, int q);
virtual void processSessionAttributeRequest(int tokenSize);
virtual void processSessionAttributeRequest(const int tokenSize, const uint terminator);
virtual void processChecksumRequest(int argc, int argv[]);
private:
@ -218,6 +261,7 @@ private:
void SixelColorChangeRGB(const int index, int red, int green, int blue);
void SixelColorChangeHSL(const int index, int hue, int saturation, int value);
void SixelCharacterAdd(uint8_t character, int repeat = 1);
bool m_SixelPictureDefinition = false;
bool m_SixelStarted = false;
QImage m_currentImage;
int m_currentX = 0;

@ -97,7 +97,7 @@ public:
Vt102Emulation::sendString(string);
}
void reportDecodingError() override
void reportDecodingError(int /*token*/) override
{
items.push_back(DecodingError{});
}
@ -110,11 +110,11 @@ public:
}
}
void processSessionAttributeRequest(int tokenSize) override
void processSessionAttributeRequest(const int tokenSize, const uint terminator) override
{
items.push_back(ProcessSessionAttributeRequest{std::vector<uint>(tokenBuffer, tokenBuffer + tokenSize)});
if (!blockFurtherProcessing) {
Vt102Emulation::processSessionAttributeRequest(tokenSize);
Vt102Emulation::processSessionAttributeRequest(tokenSize, terminator);
}
}

Loading…
Cancel
Save