diff --git a/src/Vt102Emulation.cpp b/src/Vt102Emulation.cpp index 4ecb6e3c..238737a0 100644 --- a/src/Vt102Emulation.cpp +++ b/src/Vt102Emulation.cpp @@ -13,6 +13,7 @@ #include // Qt +#include #include #include #include @@ -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 &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;;; ... m -or- ESC[ ... 38;2;;; ... 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; ... m -or- ESC[ ... 38;5; ... 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 ; ; \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); + + // '_' ... '\' + 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 &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{'['}); - continue; - } - if (les(2, 1, GRP)) { - continue; - } - // Operating System Command - if (p > 2 && s[1] == ']') { - // ']' ... '\' - 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 ; ; \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; - } - // ']' ... + one character for reprocessing - if (s[p - 2] == ESC) { - processSessionAttributeRequest(p - 1); - resetTokenizer(); - receiveChars(QVector{cc}); - continue; - } - // ']' ... - 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] == '_') { - // '_' ... '\' - 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 */ - // ']' ... - 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;;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;;; ... m -or- ESC[ ... 38;2;;; ... 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; ... m -or- ESC[ ... 38;5; ... 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) diff --git a/src/Vt102Emulation.h b/src/Vt102Emulation.h index 250d4f89..09b0f867 100644 --- a/src/Vt102Emulation.h +++ b/src/Vt102Emulation.h @@ -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 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; diff --git a/src/autotests/Vt102EmulationTest.h b/src/autotests/Vt102EmulationTest.h index 0730cfac..81eecf8b 100644 --- a/src/autotests/Vt102EmulationTest.h +++ b/src/autotests/Vt102EmulationTest.h @@ -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(tokenBuffer, tokenBuffer + tokenSize)}); if (!blockFurtherProcessing) { - Vt102Emulation::processSessionAttributeRequest(tokenSize); + Vt102Emulation::processSessionAttributeRequest(tokenSize, terminator); } }