diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index ba5e05b86..ddb33c771 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -548,6 +548,7 @@ DECSCUSR DECSED DECSEL DECSET +DECSLPP DECSLRM DECSMBV DECSMKR @@ -2579,6 +2580,7 @@ vstudio vswhere vtapp VTE +VTID vtio vtmode vtpipeterm diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 9de95ca15..1454ed279 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -3,6 +3,94 @@ #pragma once +namespace Microsoft::Console::VirtualTerminal +{ + class VTID + { + public: + template + constexpr VTID(const char (&s)[Length]) : + _value{ _FromString(s) } + { + } + + constexpr VTID(const uint64_t value) : + _value{ value } + { + } + + constexpr operator uint64_t() const + { + return _value; + } + + constexpr char operator[](const size_t offset) const + { + return SubSequence(offset)._value & 0xFF; + } + + constexpr VTID SubSequence(const size_t offset) const + { + return _value >> (CHAR_BIT * offset); + } + + private: + template + static constexpr uint64_t _FromString(const char (&s)[Length]) + { + static_assert(Length - 1 <= sizeof(_value)); + uint64_t value = 0; + for (auto i = Length - 1; i-- > 0;) + { + value = (value << CHAR_BIT) + gsl::at(s, i); + } + return value; + } + + uint64_t _value; + }; + + class VTIDBuilder + { + public: + void Clear() noexcept + { + _idAccumulator = 0; + _idShift = 0; + } + + void AddIntermediate(const wchar_t intermediateChar) noexcept + { + if (_idShift + CHAR_BIT >= sizeof(_idAccumulator) * CHAR_BIT) + { + // If there is not enough space in the accumulator to add + // the intermediate and still have room left for the final, + // then we reset the accumulator to zero. This will result + // in an id with all zero intermediates, which shouldn't + // match anything. + _idAccumulator = 0; + } + else + { + // Otherwise we shift the intermediate so as to add it to the + // accumulator in the next available space, and then increment + // the shift by 8 bits in preparation for the next character. + _idAccumulator += (static_cast(intermediateChar) << _idShift); + _idShift += CHAR_BIT; + } + } + + VTID Finalize(const wchar_t finalChar) noexcept + { + return _idAccumulator + (static_cast(finalChar) << _idShift); + } + + private: + uint64_t _idAccumulator = 0; + size_t _idShift = 0; + }; +} + namespace Microsoft::Console::VirtualTerminal::DispatchTypes { enum class EraseType : unsigned int @@ -101,16 +189,16 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes W32IM_Win32InputMode = 9001 }; - namespace CharacterSets + enum CharacterSets : uint64_t { - constexpr auto DecSpecialGraphics = std::make_pair(L'0', L'\0'); - constexpr auto ASCII = std::make_pair(L'B', L'\0'); - } + DecSpecialGraphics = VTID("0"), + ASCII = VTID("B") + }; - enum CodingSystem : wchar_t + enum CodingSystem : uint64_t { - ISO2022 = L'@', - UTF8 = L'G' + ISO2022 = VTID("@"), + UTF8 = VTID("G") }; enum TabClearType : unsigned short diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 93f1dffec..b90705ac4 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -98,9 +98,9 @@ public: virtual bool TertiaryDeviceAttributes() = 0; // DA3 virtual bool Vt52DeviceAttributes() = 0; // VT52 Identify - virtual bool DesignateCodingSystem(const wchar_t codingSystem) = 0; // DOCS - virtual bool Designate94Charset(const size_t gsetNumber, const std::pair charset) = 0; // SCS - virtual bool Designate96Charset(const size_t gsetNumber, const std::pair charset) = 0; // SCS + virtual bool DesignateCodingSystem(const VTID codingSystem) = 0; // DOCS + virtual bool Designate94Charset(const size_t gsetNumber, const VTID charset) = 0; // SCS + virtual bool Designate96Charset(const size_t gsetNumber, const VTID charset) = 0; // SCS virtual bool LockingShift(const size_t gsetNumber) = 0; // LS0, LS1, LS2, LS3 virtual bool LockingShiftRight(const size_t gsetNumber) = 0; // LS1R, LS2R, LS3R virtual bool SingleShift(const size_t gsetNumber) = 0; // SS2, SS3 diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 8d656e572..d435498c3 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1670,7 +1670,7 @@ void AdaptDispatch::_InitTabStopsForWidth(const size_t width) // - codingSystem - The coding system that will be selected. // Return value: // True if handled successfully. False otherwise. -bool AdaptDispatch::DesignateCodingSystem(const wchar_t codingSystem) +bool AdaptDispatch::DesignateCodingSystem(const VTID codingSystem) { // If we haven't previously saved the initial code page, do so now. // This will be used to restore the code page in response to a reset. @@ -1712,10 +1712,10 @@ bool AdaptDispatch::DesignateCodingSystem(const wchar_t codingSystem) // If the specified charset is unsupported, we do nothing (remain on the current one) //Arguments: // - gsetNumber - The G-set into which the charset will be selected. -// - charset - The characters indicating the charset that will be used. +// - charset - The identifier indicating the charset that will be used. // Return value: // True if handled successfully. False otherwise. -bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const std::pair charset) +bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const VTID charset) { return _termOutput.Designate94Charset(gsetNumber, charset); } @@ -1727,10 +1727,10 @@ bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const std::pair< // If the specified charset is unsupported, we do nothing (remain on the current one) //Arguments: // - gsetNumber - The G-set into which the charset will be selected. -// - charset - The characters indicating the charset that will be used. +// - charset - The identifier indicating the charset that will be used. // Return value: // True if handled successfully. False otherwise. -bool AdaptDispatch::Designate96Charset(const size_t gsetNumber, const std::pair charset) +bool AdaptDispatch::Designate96Charset(const size_t gsetNumber, const VTID charset) { return _termOutput.Designate96Charset(gsetNumber, charset); } diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 12a0c9021..2e5f27a78 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -89,9 +89,9 @@ namespace Microsoft::Console::VirtualTerminal bool ForwardTab(const size_t numTabs) override; // CHT, HT bool BackwardsTab(const size_t numTabs) override; // CBT bool TabClear(const size_t clearType) override; // TBC - bool DesignateCodingSystem(const wchar_t codingSystem) override; // DOCS - bool Designate94Charset(const size_t gsetNumber, const std::pair charset) override; // SCS - bool Designate96Charset(const size_t gsetNumber, const std::pair charset) override; // SCS + bool DesignateCodingSystem(const VTID codingSystem) override; // DOCS + bool Designate94Charset(const size_t gsetNumber, const VTID charset) override; // SCS + bool Designate96Charset(const size_t gsetNumber, const VTID charset) override; // SCS bool LockingShift(const size_t gsetNumber) override; // LS0, LS1, LS2, LS3 bool LockingShiftRight(const size_t gsetNumber) override; // LS1R, LS2R, LS3R bool SingleShift(const size_t gsetNumber) override; // SS2, SS3 diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index e13579c68..47ecd9907 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -92,9 +92,9 @@ public: bool TertiaryDeviceAttributes() noexcept override { return false; } // DA3 bool Vt52DeviceAttributes() noexcept override { return false; } // VT52 Identify - bool DesignateCodingSystem(const wchar_t /*codingSystem*/) noexcept override { return false; } // DOCS - bool Designate94Charset(const size_t /*gsetNumber*/, const std::pair /*charset*/) noexcept override { return false; } // SCS - bool Designate96Charset(const size_t /*gsetNumber*/, const std::pair /*charset*/) noexcept override { return false; } // SCS + bool DesignateCodingSystem(const VTID /*codingSystem*/) noexcept override { return false; } // DOCS + bool Designate94Charset(const size_t /*gsetNumber*/, const VTID /*charset*/) noexcept override { return false; } // SCS + bool Designate96Charset(const size_t /*gsetNumber*/, const VTID /*charset*/) noexcept override { return false; } // SCS bool LockingShift(const size_t /*gsetNumber*/) noexcept override { return false; } // LS0, LS1, LS2, LS3 bool LockingShiftRight(const size_t /*gsetNumber*/) noexcept override { return false; } // LS1R, LS2R, LS3R bool SingleShift(const size_t /*gsetNumber*/) noexcept override { return false; } // SS2, SS3 diff --git a/src/terminal/adapter/terminalOutput.cpp b/src/terminal/adapter/terminalOutput.cpp index f320839ac..14de84e13 100644 --- a/src/terminal/adapter/terminalOutput.cpp +++ b/src/terminal/adapter/terminalOutput.cpp @@ -17,107 +17,89 @@ TerminalOutput::TerminalOutput() noexcept _gsetTranslationTables.at(3) = Latin1; } -bool TerminalOutput::Designate94Charset(size_t gsetNumber, const std::pair charset) +bool TerminalOutput::Designate94Charset(size_t gsetNumber, const VTID charset) { - switch (charset.first) + switch (charset) { - case L'B': // US ASCII - case L'1': // Alternate Character ROM + case VTID("B"): // US ASCII + case VTID("1"): // Alternate Character ROM return _SetTranslationTable(gsetNumber, Ascii); - case L'0': // DEC Special Graphics - case L'2': // Alternate Character ROM Special Graphics + case VTID("0"): // DEC Special Graphics + case VTID("2"): // Alternate Character ROM Special Graphics return _SetTranslationTable(gsetNumber, DecSpecialGraphics); - case L'<': // DEC Supplemental + case VTID("<"): // DEC Supplemental return _SetTranslationTable(gsetNumber, DecSupplemental); - case L'A': // British NRCS + case VTID("A"): // British NRCS return _SetTranslationTable(gsetNumber, BritishNrcs); - case L'4': // Dutch NRCS + case VTID("4"): // Dutch NRCS return _SetTranslationTable(gsetNumber, DutchNrcs); - case L'5': // Finnish NRCS - case L'C': // (fallback) + case VTID("5"): // Finnish NRCS + case VTID("C"): // (fallback) return _SetTranslationTable(gsetNumber, FinnishNrcs); - case L'R': // French NRCS + case VTID("R"): // French NRCS return _SetTranslationTable(gsetNumber, FrenchNrcs); - case L'f': // French NRCS (ISO update) + case VTID("f"): // French NRCS (ISO update) return _SetTranslationTable(gsetNumber, FrenchNrcsIso); - case L'9': // French Canadian NRCS - case L'Q': // (fallback) + case VTID("9"): // French Canadian NRCS + case VTID("Q"): // (fallback) return _SetTranslationTable(gsetNumber, FrenchCanadianNrcs); - case L'K': // German NRCS + case VTID("K"): // German NRCS return _SetTranslationTable(gsetNumber, GermanNrcs); - case L'Y': // Italian NRCS + case VTID("Y"): // Italian NRCS return _SetTranslationTable(gsetNumber, ItalianNrcs); - case L'6': // Norwegian/Danish NRCS - case L'E': // (fallback) + case VTID("6"): // Norwegian/Danish NRCS + case VTID("E"): // (fallback) return _SetTranslationTable(gsetNumber, NorwegianDanishNrcs); - case L'`': // Norwegian/Danish NRCS (ISO standard) + case VTID("`"): // Norwegian/Danish NRCS (ISO standard) return _SetTranslationTable(gsetNumber, NorwegianDanishNrcsIso); - case L'Z': // Spanish NRCS + case VTID("Z"): // Spanish NRCS return _SetTranslationTable(gsetNumber, SpanishNrcs); - case L'7': // Swedish NRCS - case L'H': // (fallback) + case VTID("7"): // Swedish NRCS + case VTID("H"): // (fallback) return _SetTranslationTable(gsetNumber, SwedishNrcs); - case L'=': // Swiss NRCS + case VTID("="): // Swiss NRCS return _SetTranslationTable(gsetNumber, SwissNrcs); - case L'&': - switch (charset.second) - { - case L'4': // DEC Cyrillic - return _SetTranslationTable(gsetNumber, DecCyrillic); - case L'5': // Russian NRCS - return _SetTranslationTable(gsetNumber, RussianNrcs); - default: - return false; - } - case L'"': - switch (charset.second) - { - case L'?': // DEC Greek - return _SetTranslationTable(gsetNumber, DecGreek); - case L'>': // Greek NRCS - return _SetTranslationTable(gsetNumber, GreekNrcs); - case L'4': // DEC Hebrew - return _SetTranslationTable(gsetNumber, DecHebrew); - default: - return false; - } - case L'%': - switch (charset.second) - { - case L'=': // Hebrew NRCS - return _SetTranslationTable(gsetNumber, HebrewNrcs); - case L'0': // DEC Turkish - return _SetTranslationTable(gsetNumber, DecTurkish); - case L'2': // Turkish NRCS - return _SetTranslationTable(gsetNumber, TurkishNrcs); - case L'5': // DEC Supplemental - return _SetTranslationTable(gsetNumber, DecSupplemental); - case L'6': // Portuguese NRCS - return _SetTranslationTable(gsetNumber, PortugueseNrcs); - default: - return false; - } + case VTID("&4"): // DEC Cyrillic + return _SetTranslationTable(gsetNumber, DecCyrillic); + case VTID("&5"): // Russian NRCS + return _SetTranslationTable(gsetNumber, RussianNrcs); + case VTID("\"?"): // DEC Greek + return _SetTranslationTable(gsetNumber, DecGreek); + case VTID("\">"): // Greek NRCS + return _SetTranslationTable(gsetNumber, GreekNrcs); + case VTID("\"4"): // DEC Hebrew + return _SetTranslationTable(gsetNumber, DecHebrew); + case VTID("%="): // Hebrew NRCS + return _SetTranslationTable(gsetNumber, HebrewNrcs); + case VTID("%0"): // DEC Turkish + return _SetTranslationTable(gsetNumber, DecTurkish); + case VTID("%2"): // Turkish NRCS + return _SetTranslationTable(gsetNumber, TurkishNrcs); + case VTID("%5"): // DEC Supplemental + return _SetTranslationTable(gsetNumber, DecSupplemental); + case VTID("%6"): // Portuguese NRCS + return _SetTranslationTable(gsetNumber, PortugueseNrcs); default: return false; } } -bool TerminalOutput::Designate96Charset(size_t gsetNumber, const std::pair charset) +bool TerminalOutput::Designate96Charset(size_t gsetNumber, const VTID charset) { - switch (charset.first) + switch (charset) { - case L'A': // ISO Latin-1 Supplemental - case L'<': // (UPSS when assigned to Latin-1) + case VTID("A"): // ISO Latin-1 Supplemental + case VTID("<"): // (UPSS when assigned to Latin-1) return _SetTranslationTable(gsetNumber, Latin1); - case L'B': // ISO Latin-2 Supplemental + case VTID("B"): // ISO Latin-2 Supplemental return _SetTranslationTable(gsetNumber, Latin2); - case L'L': // ISO Latin-Cyrillic Supplemental + case VTID("L"): // ISO Latin-Cyrillic Supplemental return _SetTranslationTable(gsetNumber, LatinCyrillic); - case L'F': // ISO Latin-Greek Supplemental + case VTID("F"): // ISO Latin-Greek Supplemental return _SetTranslationTable(gsetNumber, LatinGreek); - case L'H': // ISO Latin-Hebrew Supplemental + case VTID("H"): // ISO Latin-Hebrew Supplemental return _SetTranslationTable(gsetNumber, LatinHebrew); - case L'M': // ISO Latin-5 Supplemental + case VTID("M"): // ISO Latin-5 Supplemental return _SetTranslationTable(gsetNumber, Latin5); default: return false; diff --git a/src/terminal/adapter/terminalOutput.hpp b/src/terminal/adapter/terminalOutput.hpp index a26f6d85a..e82e067a7 100644 --- a/src/terminal/adapter/terminalOutput.hpp +++ b/src/terminal/adapter/terminalOutput.hpp @@ -26,8 +26,8 @@ namespace Microsoft::Console::VirtualTerminal TerminalOutput() noexcept; wchar_t TranslateKey(const wchar_t wch) const noexcept; - bool Designate94Charset(const size_t gsetNumber, const std::pair charset); - bool Designate96Charset(const size_t gsetNumber, const std::pair charset); + bool Designate94Charset(const size_t gsetNumber, const VTID charset); + bool Designate96Charset(const size_t gsetNumber, const VTID charset); bool LockingShift(const size_t gsetNumber); bool LockingShiftRight(const size_t gsetNumber); bool SingleShift(const size_t gsetNumber); diff --git a/src/terminal/parser/IStateMachineEngine.hpp b/src/terminal/parser/IStateMachineEngine.hpp index c1a668fb7..169cf8ac0 100644 --- a/src/terminal/parser/IStateMachineEngine.hpp +++ b/src/terminal/parser/IStateMachineEngine.hpp @@ -12,6 +12,9 @@ Abstract: the existing VT parsing. */ #pragma once + +#include "../adapter/DispatchTypes.hpp" + namespace Microsoft::Console::VirtualTerminal { class IStateMachineEngine @@ -30,14 +33,9 @@ namespace Microsoft::Console::VirtualTerminal virtual bool ActionPassThroughString(const std::wstring_view string) = 0; - virtual bool ActionEscDispatch(const wchar_t wch, - const gsl::span intermediates) = 0; - virtual bool ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) = 0; - virtual bool ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) = 0; + virtual bool ActionEscDispatch(const VTID id) = 0; + virtual bool ActionVt52EscDispatch(const VTID id, const gsl::span parameters) = 0; + virtual bool ActionCsiDispatch(const VTID id, const gsl::span parameters) = 0; virtual bool ActionClear() = 0; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index a4d903f56..c795243bf 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -34,9 +34,9 @@ static constexpr std::array s_csiMap = { CsiToVkey{ CsiActionCodes::CSI_F4, VK_F4 } }; -static bool operator==(const CsiToVkey& pair, const CsiActionCodes code) noexcept +static bool operator==(const CsiToVkey& pair, const VTID id) noexcept { - return pair.action == code; + return pair.action == id; } struct GenericToVkey @@ -298,12 +298,10 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st // a simple escape sequence. These sequences traditionally start with ESC // and a simple letter. No complicated parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the escape sequence to dispatch. // Return Value: // - true iff we successfully dispatched the sequence. -bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch, - const gsl::span /*intermediates*/) +bool InputStateMachineEngine::ActionEscDispatch(const VTID id) { if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue) { @@ -312,6 +310,9 @@ bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch, bool success = false; + // There are no intermediates, so the id is effectively the final char. + const wchar_t wch = gsl::narrow_cast(id); + // 0x7f is DEL, which we treat effectively the same as a ctrl character. if (wch == 0x7f) { @@ -339,14 +340,11 @@ bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch, // a VT52 escape sequence. These sequences start with ESC and a single letter, // sometimes followed by parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence. +// - id - Identifier of the VT52 sequence to dispatch. // - parameters - Set of parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool InputStateMachineEngine::ActionVt52EscDispatch(const wchar_t /*wch*/, - const gsl::span /*intermediates*/, - const gsl::span /*parameters*/) noexcept +bool InputStateMachineEngine::ActionVt52EscDispatch(const VTID /*id*/, const gsl::span /*parameters*/) noexcept { // VT52 escape sequences are not used in the input state machine. return false; @@ -357,17 +355,12 @@ bool InputStateMachineEngine::ActionVt52EscDispatch(const wchar_t /*wch*/, // a control sequence. These sequences perform various API-type commands // that can include many parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the control sequence to dispatch. // - parameters - set of numeric parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) +bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span parameters) { - const auto actionCode = static_cast(wch); - // GH#4999 - If the client was in VT input mode, but we received a // win32-input-mode sequence, then _don't_ passthrough the sequence to the // client. It's impossibly unlikely that the client actually wanted @@ -376,7 +369,7 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, // client reads it. if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue && - actionCode != CsiActionCodes::Win32KeyboardInput) + id != CsiActionCodes::Win32KeyboardInput) { return _pfnFlushToInputQueue(); } @@ -392,32 +385,22 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, const auto remainingArgs = parameters.size() > 1 ? parameters.subspan(1) : gsl::span{}; bool success = false; - // Handle intermediate characters, if any - if (!intermediates.empty()) + switch (id) { - switch (static_cast(til::at(intermediates, 0))) - { - case CsiIntermediateCodes::MOUSE_SGR: - { - DWORD buttonState = 0; - DWORD eventFlags = 0; - modifierState = _GetSGRMouseModifierState(parameters); - success = _GetSGRXYPosition(parameters, row, col); + case CsiActionCodes::MouseDown: + case CsiActionCodes::MouseUp: + { + DWORD buttonState = 0; + DWORD eventFlags = 0; + modifierState = _GetSGRMouseModifierState(parameters); + success = _GetSGRXYPosition(parameters, row, col); - // we need _UpdateSGRMouseButtonState() on the left side here because we _always_ should be updating our state - // even if we failed to parse a portion of this sequence. - success = _UpdateSGRMouseButtonState(wch, parameters, buttonState, eventFlags) && success; - success = success && _WriteMouseEvent(col, row, buttonState, modifierState, eventFlags); - break; - } - default: - success = false; - break; - } + // we need _UpdateSGRMouseButtonState() on the left side here because we _always_ should be updating our state + // even if we failed to parse a portion of this sequence. + success = _UpdateSGRMouseButtonState(id, parameters, buttonState, eventFlags) && success; + success = success && _WriteMouseEvent(col, row, buttonState, modifierState, eventFlags); return success; } - switch (actionCode) - { case CsiActionCodes::Generic: modifierState = _GetGenericKeysModifierState(parameters); success = _GetGenericVkey(parameters, vkey); @@ -443,8 +426,8 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, case CsiActionCodes::CSI_F1: case CsiActionCodes::CSI_F2: case CsiActionCodes::CSI_F4: - success = _GetCursorKeysVkey(wch, vkey); - modifierState = _GetCursorKeysModifierState(parameters, static_cast(wch)); + success = _GetCursorKeysVkey(id, vkey); + modifierState = _GetCursorKeysModifierState(parameters, id); break; case CsiActionCodes::CursorBackTab: modifierState = SHIFT_PRESSED; @@ -464,7 +447,7 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, if (success) { - switch (static_cast(wch)) + switch (id) { // case CsiActionCodes::DSR_DeviceStatusReportResponse: case CsiActionCodes::CSI_F3: @@ -799,10 +782,10 @@ bool InputStateMachineEngine::_WriteMouseEvent(const size_t column, const size_t // sequence. This is for Arrow keys, Home, End, etc. // Arguments: // - parameters - the set of parameters to get the modifier state from. -// - actionCode - the actionCode for the sequence we're operating on. +// - id - the identifier for the sequence we're operating on. // Return Value: // - the INPUT_RECORD compatible modifier state. -DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span parameters, const CsiActionCodes actionCode) noexcept +DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span parameters, const VTID id) noexcept { DWORD modifiers = 0; if (_IsModified(parameters.size()) && parameters.size() >= 2) @@ -816,7 +799,7 @@ DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span CsiActionCodes::CSI_F4) + if (id < CsiActionCodes::CSI_F1 || id > CsiActionCodes::CSI_F4) { WI_SetFlag(modifiers, ENHANCED_KEY); } @@ -917,13 +900,13 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept // - Here, we refer to and maintain the global state of our mouse. // - Mouse wheel events are added at the end to keep them out of the global state // Arguments: -// - wch: the wchar_t representing whether the button was pressed or released +// - id: the sequence identifier representing whether the button was pressed or released // - parameters: the wchar_t to get the mapped vkey of. Represents the direction of the button (down vs up) // - buttonState: Receives the button state for the record // - eventFlags: Receives the special mouse events for the record // Return Value: // true iff we were able to synthesize buttonState -bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const wchar_t wch, +bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id, const gsl::span parameters, DWORD& buttonState, DWORD& eventFlags) noexcept @@ -987,7 +970,7 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const wchar_t wch, // Step 2: Decide whether to set or clear that button's bit // NOTE: WI_SetFlag/WI_ClearFlag can't be used here because buttonFlag would have to be a compile-time constant - switch (static_cast(wch)) + switch (id) { case CsiActionCodes::MouseDown: // set flag @@ -1049,15 +1032,15 @@ bool InputStateMachineEngine::_GetGenericVkey(const gsl::span para // Method Description: // - Gets the Vkey from the CSI codes table associated with a particular character. // Arguments: -// - wch: the wchar_t to get the mapped vkey of. +// - id: the sequence identifier to get the mapped vkey of. // - vkey: Receives the vkey // Return Value: // true iff we found the key -bool InputStateMachineEngine::_GetCursorKeysVkey(const wchar_t wch, short& vkey) const +bool InputStateMachineEngine::_GetCursorKeysVkey(const VTID id, short& vkey) const { vkey = 0; - const auto mapping = std::find(s_csiMap.cbegin(), s_csiMap.cend(), (CsiActionCodes)wch); + const auto mapping = std::find(s_csiMap.cbegin(), s_csiMap.cend(), id); if (mapping != s_csiMap.end()) { vkey = mapping->vkey; diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 14f6cedc1..a14aa87f6 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -50,30 +50,25 @@ namespace Microsoft::Console::VirtualTerminal // CAPSLOCK_ON 0x0080 // ENHANCED_KEY 0x0100 - enum CsiIntermediateCodes : wchar_t + enum CsiActionCodes : uint64_t { - MOUSE_SGR = L'<', - }; - - enum class CsiActionCodes : wchar_t - { - ArrowUp = L'A', - ArrowDown = L'B', - ArrowRight = L'C', - ArrowLeft = L'D', - Home = L'H', - End = L'F', - MouseDown = L'M', - MouseUp = L'm', - Generic = L'~', // Used for a whole bunch of possible keys - CSI_F1 = L'P', - CSI_F2 = L'Q', - CSI_F3 = L'R', // Both F3 and DSR are on R. - // DSR_DeviceStatusReportResponse = L'R', - CSI_F4 = L'S', - DTTERM_WindowManipulation = L't', - CursorBackTab = L'Z', - Win32KeyboardInput = L'_' + ArrowUp = VTID("A"), + ArrowDown = VTID("B"), + ArrowRight = VTID("C"), + ArrowLeft = VTID("D"), + Home = VTID("H"), + End = VTID("F"), + MouseDown = VTID(" intermediates) override; + bool ActionEscDispatch(const VTID id) override; - bool ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) noexcept override; + bool ActionVt52EscDispatch(const VTID id, const gsl::span parameters) noexcept override; - bool ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) override; + bool ActionCsiDispatch(const VTID id, const gsl::span parameters) override; bool ActionClear() noexcept override; @@ -180,7 +170,7 @@ namespace Microsoft::Console::VirtualTerminal bool _lookingForDSR; DWORD _mouseButtonState = 0; - DWORD _GetCursorKeysModifierState(const gsl::span parameters, const CsiActionCodes actionCode) noexcept; + DWORD _GetCursorKeysModifierState(const gsl::span parameters, const VTID id) noexcept; DWORD _GetGenericKeysModifierState(const gsl::span parameters) noexcept; DWORD _GetSGRMouseModifierState(const gsl::span parameters) noexcept; bool _GenerateKeyFromChar(const wchar_t wch, short& vkey, DWORD& modifierState) noexcept; @@ -188,13 +178,13 @@ namespace Microsoft::Console::VirtualTerminal bool _IsModified(const size_t paramCount) noexcept; DWORD _GetModifier(const size_t parameter) noexcept; - bool _UpdateSGRMouseButtonState(const wchar_t wch, + bool _UpdateSGRMouseButtonState(const VTID id, const gsl::span parameters, DWORD& buttonState, DWORD& eventFlags) noexcept; bool _GetGenericVkey(const gsl::span parameters, short& vkey) const; - bool _GetCursorKeysVkey(const wchar_t wch, short& vkey) const; + bool _GetCursorKeysVkey(const VTID id, short& vkey) const; bool _GetSs3KeysVkey(const wchar_t wch, short& vkey) const; bool _WriteSingleKey(const short vkey, const DWORD modifierState); diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 86a2023af..cdecc28bb 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -179,89 +179,123 @@ bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view s // a simple escape sequence. These sequences traditionally start with ESC // and a simple letter. No complicated parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the escape sequence to dispatch. // Return Value: // - true iff we successfully dispatched the sequence. -bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, - const gsl::span intermediates) +bool OutputStateMachineEngine::ActionEscDispatch(const VTID id) { - if (wch == L'\\' && intermediates.empty()) - { - // This is presumably the 7-bit string terminator, which is essentially a no-op. - return true; - } - bool success = false; - // no intermediates. - if (intermediates.empty()) + switch (id) { - switch (wch) + case EscActionCodes::ST_StringTerminator: + // This is the 7-bit string terminator, which is essentially a no-op. + success = true; + break; + case EscActionCodes::DECSC_CursorSave: + success = _dispatch->CursorSaveState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSC); + break; + case EscActionCodes::DECRC_CursorRestore: + success = _dispatch->CursorRestoreState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRC); + break; + case EscActionCodes::DECKPAM_KeypadApplicationMode: + success = _dispatch->SetKeypadMode(true); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPAM); + break; + case EscActionCodes::DECKPNM_KeypadNumericMode: + success = _dispatch->SetKeypadMode(false); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPNM); + break; + case EscActionCodes::NEL_NextLine: + success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithReturn); + TermTelemetry::Instance().Log(TermTelemetry::Codes::NEL); + break; + case EscActionCodes::IND_Index: + success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithoutReturn); + TermTelemetry::Instance().Log(TermTelemetry::Codes::IND); + break; + case EscActionCodes::RI_ReverseLineFeed: + success = _dispatch->ReverseLineFeed(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::RI); + break; + case EscActionCodes::HTS_HorizontalTabSet: + success = _dispatch->HorizontalTabSet(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::HTS); + break; + case EscActionCodes::RIS_ResetToInitialState: + success = _dispatch->HardReset(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::RIS); + break; + case EscActionCodes::SS2_SingleShift: + success = _dispatch->SingleShift(2); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SS2); + break; + case EscActionCodes::SS3_SingleShift: + success = _dispatch->SingleShift(3); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SS3); + break; + case EscActionCodes::LS2_LockingShift: + success = _dispatch->LockingShift(2); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2); + break; + case EscActionCodes::LS3_LockingShift: + success = _dispatch->LockingShift(3); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3); + break; + case EscActionCodes::LS1R_LockingShift: + success = _dispatch->LockingShiftRight(1); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS1R); + break; + case EscActionCodes::LS2R_LockingShift: + success = _dispatch->LockingShiftRight(2); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2R); + break; + case EscActionCodes::LS3R_LockingShift: + success = _dispatch->LockingShiftRight(3); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3R); + break; + case EscActionCodes::DECALN_ScreenAlignmentPattern: + success = _dispatch->ScreenAlignmentPattern(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN); + break; + default: + const auto commandChar = id[0]; + const auto commandParameter = id.SubSequence(1); + switch (commandChar) { - case VTActionCodes::DECSC_CursorSave: - success = _dispatch->CursorSaveState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSC); + case '%': + success = _dispatch->DesignateCodingSystem(commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DOCS); break; - case VTActionCodes::DECRC_CursorRestore: - success = _dispatch->CursorRestoreState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRC); + case '(': + success = _dispatch->Designate94Charset(0, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG0); break; - case VTActionCodes::DECKPAM_KeypadApplicationMode: - success = _dispatch->SetKeypadMode(true); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPAM); + case ')': + success = _dispatch->Designate94Charset(1, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); break; - case VTActionCodes::DECKPNM_KeypadNumericMode: - success = _dispatch->SetKeypadMode(false); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPNM); + case '*': + success = _dispatch->Designate94Charset(2, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); break; - case VTActionCodes::NEL_NextLine: - success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithReturn); - TermTelemetry::Instance().Log(TermTelemetry::Codes::NEL); + case '+': + success = _dispatch->Designate94Charset(3, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); break; - case VTActionCodes::IND_Index: - success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithoutReturn); - TermTelemetry::Instance().Log(TermTelemetry::Codes::IND); + case '-': + success = _dispatch->Designate96Charset(1, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); break; - case VTActionCodes::RI_ReverseLineFeed: - success = _dispatch->ReverseLineFeed(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::RI); + case '.': + success = _dispatch->Designate96Charset(2, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); break; - case VTActionCodes::HTS_HorizontalTabSet: - success = _dispatch->HorizontalTabSet(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::HTS); - break; - case VTActionCodes::RIS_ResetToInitialState: - success = _dispatch->HardReset(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::RIS); - break; - case VTActionCodes::SS2_SingleShift: - success = _dispatch->SingleShift(2); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SS2); - break; - case VTActionCodes::SS3_SingleShift: - success = _dispatch->SingleShift(3); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SS3); - break; - case VTActionCodes::LS2_LockingShift: - success = _dispatch->LockingShift(2); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2); - break; - case VTActionCodes::LS3_LockingShift: - success = _dispatch->LockingShift(3); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3); - break; - case VTActionCodes::LS1R_LockingShift: - success = _dispatch->LockingShiftRight(1); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS1R); - break; - case VTActionCodes::LS2R_LockingShift: - success = _dispatch->LockingShiftRight(2); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2R); - break; - case VTActionCodes::LS3R_LockingShift: - success = _dispatch->LockingShiftRight(3); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3R); + case '/': + success = _dispatch->Designate96Charset(3, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); break; default: // If no functions to call, overall dispatch was a failure. @@ -269,36 +303,6 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, break; } } - else if (intermediates.size() == 1) - { - switch (til::at(intermediates, 0)) - { - case L'%': - success = _dispatch->DesignateCodingSystem(wch); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DOCS); - break; - case L'#': - switch (wch) - { - case VTActionCodes::DECALN_ScreenAlignmentPattern: - success = _dispatch->ScreenAlignmentPattern(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - break; - default: - success = _IntermediateScsDispatch(wch, intermediates); - break; - } - } - else if (intermediates.size() == 2) - { - success = _IntermediateScsDispatch(wch, intermediates); - } // If we were unable to process the string, and there's a TTY attached to us, // trigger the state machine to flush the string to the terminal. @@ -317,135 +321,74 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, // a VT52 escape sequence. These sequences start with ESC and a single letter, // sometimes followed by parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence. +// - id - Identifier of the VT52 sequence to dispatch. // - parameters - Set of parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool OutputStateMachineEngine::ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) +bool OutputStateMachineEngine::ActionVt52EscDispatch(const VTID id, const gsl::span parameters) { bool success = false; - // no intermediates. - if (intermediates.empty()) + switch (id) { - switch (wch) - { - case Vt52ActionCodes::CursorUp: - success = _dispatch->CursorUp(1); - break; - case Vt52ActionCodes::CursorDown: - success = _dispatch->CursorDown(1); - break; - case Vt52ActionCodes::CursorRight: - success = _dispatch->CursorForward(1); - break; - case Vt52ActionCodes::CursorLeft: - success = _dispatch->CursorBackward(1); - break; - case Vt52ActionCodes::EnterGraphicsMode: - success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::DecSpecialGraphics); - break; - case Vt52ActionCodes::ExitGraphicsMode: - success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::ASCII); - break; - case Vt52ActionCodes::CursorToHome: - success = _dispatch->CursorPosition(1, 1); - break; - case Vt52ActionCodes::ReverseLineFeed: - success = _dispatch->ReverseLineFeed(); - break; - case Vt52ActionCodes::EraseToEndOfScreen: - success = _dispatch->EraseInDisplay(DispatchTypes::EraseType::ToEnd); - break; - case Vt52ActionCodes::EraseToEndOfLine: - success = _dispatch->EraseInLine(DispatchTypes::EraseType::ToEnd); - break; - case Vt52ActionCodes::DirectCursorAddress: - // VT52 cursor addresses are provided as ASCII characters, with - // the lowest value being a space, representing an address of 1. - success = _dispatch->CursorPosition(gsl::at(parameters, 0) - ' ' + 1, gsl::at(parameters, 1) - ' ' + 1); - break; - case Vt52ActionCodes::Identify: - success = _dispatch->Vt52DeviceAttributes(); - break; - case Vt52ActionCodes::EnterAlternateKeypadMode: - success = _dispatch->SetKeypadMode(true); - break; - case Vt52ActionCodes::ExitAlternateKeypadMode: - success = _dispatch->SetKeypadMode(false); - break; - case Vt52ActionCodes::ExitVt52Mode: - { - const DispatchTypes::PrivateModeParams mode[] = { DispatchTypes::PrivateModeParams::DECANM_AnsiMode }; - success = _dispatch->SetPrivateModes(mode); - break; - } - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } + case Vt52ActionCodes::CursorUp: + success = _dispatch->CursorUp(1); + break; + case Vt52ActionCodes::CursorDown: + success = _dispatch->CursorDown(1); + break; + case Vt52ActionCodes::CursorRight: + success = _dispatch->CursorForward(1); + break; + case Vt52ActionCodes::CursorLeft: + success = _dispatch->CursorBackward(1); + break; + case Vt52ActionCodes::EnterGraphicsMode: + success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::DecSpecialGraphics); + break; + case Vt52ActionCodes::ExitGraphicsMode: + success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::ASCII); + break; + case Vt52ActionCodes::CursorToHome: + success = _dispatch->CursorPosition(1, 1); + break; + case Vt52ActionCodes::ReverseLineFeed: + success = _dispatch->ReverseLineFeed(); + break; + case Vt52ActionCodes::EraseToEndOfScreen: + success = _dispatch->EraseInDisplay(DispatchTypes::EraseType::ToEnd); + break; + case Vt52ActionCodes::EraseToEndOfLine: + success = _dispatch->EraseInLine(DispatchTypes::EraseType::ToEnd); + break; + case Vt52ActionCodes::DirectCursorAddress: + // VT52 cursor addresses are provided as ASCII characters, with + // the lowest value being a space, representing an address of 1. + success = _dispatch->CursorPosition(gsl::at(parameters, 0) - ' ' + 1, gsl::at(parameters, 1) - ' ' + 1); + break; + case Vt52ActionCodes::Identify: + success = _dispatch->Vt52DeviceAttributes(); + break; + case Vt52ActionCodes::EnterAlternateKeypadMode: + success = _dispatch->SetKeypadMode(true); + break; + case Vt52ActionCodes::ExitAlternateKeypadMode: + success = _dispatch->SetKeypadMode(false); + break; + case Vt52ActionCodes::ExitVt52Mode: + { + const DispatchTypes::PrivateModeParams mode[] = { DispatchTypes::PrivateModeParams::DECANM_AnsiMode }; + success = _dispatch->SetPrivateModes(mode); + break; } - - _ClearLastChar(); - - return success; -} - -// Routine Description: -// - Handles SCS charset designation actions that can have one or two possible intermediates. -// Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateScsDispatch(const wchar_t wch, - const gsl::span intermediates) -{ - bool success = false; - - // If we have more than one intermediate, the second intermediate forms part of - // the charset identifier. Otherwise it's identified by just the final character. - const auto charset = intermediates.size() > 1 ? std::make_pair(til::at(intermediates, 1), wch) : std::make_pair(wch, L'\0'); - - switch (til::at(intermediates, 0)) - { - case L'(': - success = _dispatch->Designate94Charset(0, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG0); - break; - case L')': - success = _dispatch->Designate94Charset(1, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); - break; - case L'*': - success = _dispatch->Designate94Charset(2, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); - break; - case L'+': - success = _dispatch->Designate94Charset(3, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); - break; - case L'-': - success = _dispatch->Designate96Charset(1, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); - break; - case L'.': - success = _dispatch->Designate96Charset(2, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); - break; - case L'/': - success = _dispatch->Designate96Charset(3, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); - break; default: + // If no functions to call, overall dispatch was a failure. success = false; break; } + _ClearLastChar(); + return success; } @@ -454,14 +397,11 @@ bool OutputStateMachineEngine::_IntermediateScsDispatch(const wchar_t wch, // a control sequence. These sequences perform various API-type commands // that can include many parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the control sequence to dispatch. // - parameters - set of numeric parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - gsl::span parameters) +bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, gsl::span parameters) { bool success = false; size_t distance = 0; @@ -473,252 +413,261 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, size_t clearType = 0; unsigned int function = 0; DispatchTypes::EraseType eraseType = DispatchTypes::EraseType::ToEnd; + std::vector privateModeParams; // We hold the vector in the class because client applications that do a lot of color work // would spend a lot of time reallocating/resizing the vector. _graphicsOptions.clear(); DispatchTypes::AnsiStatusType deviceStatusType = static_cast(0); // there is no default status type. size_t repeatCount = 0; + DispatchTypes::CursorStyle cursorStyle = DefaultCursorStyle; // This is all the args after the first arg, and the count of args not including the first one. const auto remainingParams = parameters.size() > 1 ? parameters.subspan(1) : gsl::span{}; - if (intermediates.empty()) + // fill params + switch (id) { - // fill params - switch (wch) - { - case VTActionCodes::CUU_CursorUp: - case VTActionCodes::CUD_CursorDown: - case VTActionCodes::CUF_CursorForward: - case VTActionCodes::CUB_CursorBackward: - case VTActionCodes::CNL_CursorNextLine: - case VTActionCodes::CPL_CursorPrevLine: - case VTActionCodes::CHA_CursorHorizontalAbsolute: - case VTActionCodes::HPA_HorizontalPositionAbsolute: - case VTActionCodes::VPA_VerticalLinePositionAbsolute: - case VTActionCodes::HPR_HorizontalPositionRelative: - case VTActionCodes::VPR_VerticalPositionRelative: - case VTActionCodes::ICH_InsertCharacter: - case VTActionCodes::DCH_DeleteCharacter: - case VTActionCodes::ECH_EraseCharacters: - success = _GetCursorDistance(parameters, distance); - break; - case VTActionCodes::HVP_HorizontalVerticalPosition: - case VTActionCodes::CUP_CursorPosition: - success = _GetXYPosition(parameters, line, column); - break; - case VTActionCodes::DECSTBM_SetScrollingRegion: - success = _GetTopBottomMargins(parameters, topMargin, bottomMargin); - break; - case VTActionCodes::ED_EraseDisplay: - case VTActionCodes::EL_EraseLine: - success = _GetEraseOperation(parameters, eraseType); - break; - case VTActionCodes::SGR_SetGraphicsRendition: - success = _GetGraphicsOptions(parameters, _graphicsOptions); - break; - case VTActionCodes::DSR_DeviceStatusReport: - success = _GetDeviceStatusOperation(parameters, deviceStatusType); - break; - case VTActionCodes::DA_DeviceAttributes: - success = _VerifyDeviceAttributesParams(parameters); - break; - case VTActionCodes::SU_ScrollUp: - case VTActionCodes::SD_ScrollDown: - success = _GetScrollDistance(parameters, distance); - break; - case VTActionCodes::ANSISYSSC_CursorSave: - case VTActionCodes::ANSISYSRC_CursorRestore: - success = _VerifyHasNoParameters(parameters); - break; - case VTActionCodes::IL_InsertLine: - case VTActionCodes::DL_DeleteLine: - success = _GetScrollDistance(parameters, distance); - break; - case VTActionCodes::CHT_CursorForwardTab: - case VTActionCodes::CBT_CursorBackTab: - success = _GetTabDistance(parameters, numTabs); - break; - case VTActionCodes::TBC_TabClear: - success = _GetTabClearType(parameters, clearType); - break; - case VTActionCodes::DTTERM_WindowManipulation: - success = _GetWindowManipulationType(parameters, function); - break; - case VTActionCodes::REP_RepeatCharacter: - success = _GetRepeatCount(parameters, repeatCount); - break; - default: - // If no params to fill, param filling was successful. - success = true; - break; - } - - // if param filling successful, try to dispatch - if (success) - { - switch (wch) - { - case VTActionCodes::CUU_CursorUp: - success = _dispatch->CursorUp(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUU); - break; - case VTActionCodes::CUD_CursorDown: - success = _dispatch->CursorDown(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUD); - break; - case VTActionCodes::CUF_CursorForward: - success = _dispatch->CursorForward(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUF); - break; - case VTActionCodes::CUB_CursorBackward: - success = _dispatch->CursorBackward(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUB); - break; - case VTActionCodes::CNL_CursorNextLine: - success = _dispatch->CursorNextLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CNL); - break; - case VTActionCodes::CPL_CursorPrevLine: - success = _dispatch->CursorPrevLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CPL); - break; - case VTActionCodes::CHA_CursorHorizontalAbsolute: - case VTActionCodes::HPA_HorizontalPositionAbsolute: - success = _dispatch->CursorHorizontalPositionAbsolute(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CHA); - break; - case VTActionCodes::VPA_VerticalLinePositionAbsolute: - success = _dispatch->VerticalLinePositionAbsolute(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::VPA); - break; - case VTActionCodes::HPR_HorizontalPositionRelative: - success = _dispatch->HorizontalPositionRelative(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::HPR); - break; - case VTActionCodes::VPR_VerticalPositionRelative: - success = _dispatch->VerticalPositionRelative(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::VPR); - break; - case VTActionCodes::CUP_CursorPosition: - case VTActionCodes::HVP_HorizontalVerticalPosition: - success = _dispatch->CursorPosition(line, column); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUP); - break; - case VTActionCodes::DECSTBM_SetScrollingRegion: - success = _dispatch->SetTopBottomScrollingMargins(topMargin, bottomMargin); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM); - break; - case VTActionCodes::ICH_InsertCharacter: - success = _dispatch->InsertCharacter(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ICH); - break; - case VTActionCodes::DCH_DeleteCharacter: - success = _dispatch->DeleteCharacter(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DCH); - break; - case VTActionCodes::ED_EraseDisplay: - success = _dispatch->EraseInDisplay(eraseType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ED); - break; - case VTActionCodes::EL_EraseLine: - success = _dispatch->EraseInLine(eraseType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::EL); - break; - case VTActionCodes::SGR_SetGraphicsRendition: - success = _dispatch->SetGraphicsRendition({ _graphicsOptions.data(), _graphicsOptions.size() }); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SGR); - break; - case VTActionCodes::DSR_DeviceStatusReport: - success = _dispatch->DeviceStatusReport(deviceStatusType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DSR); - break; - case VTActionCodes::DA_DeviceAttributes: - success = _dispatch->DeviceAttributes(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DA); - break; - case VTActionCodes::SU_ScrollUp: - success = _dispatch->ScrollUp(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SU); - break; - case VTActionCodes::SD_ScrollDown: - success = _dispatch->ScrollDown(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SD); - break; - case VTActionCodes::ANSISYSSC_CursorSave: - success = _dispatch->CursorSaveState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSSC); - break; - case VTActionCodes::ANSISYSRC_CursorRestore: - success = _dispatch->CursorRestoreState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSRC); - break; - case VTActionCodes::IL_InsertLine: - success = _dispatch->InsertLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::IL); - break; - case VTActionCodes::DL_DeleteLine: - success = _dispatch->DeleteLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DL); - break; - case VTActionCodes::CHT_CursorForwardTab: - success = _dispatch->ForwardTab(numTabs); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CHT); - break; - case VTActionCodes::CBT_CursorBackTab: - success = _dispatch->BackwardsTab(numTabs); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CBT); - break; - case VTActionCodes::TBC_TabClear: - success = _dispatch->TabClear(clearType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::TBC); - break; - case VTActionCodes::ECH_EraseCharacters: - success = _dispatch->EraseCharacters(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ECH); - break; - case VTActionCodes::DTTERM_WindowManipulation: - success = _dispatch->WindowManipulation(static_cast(function), - remainingParams); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DTTERM_WM); - break; - case VTActionCodes::REP_RepeatCharacter: - // Handled w/o the dispatch. This function is unique in that way - // If this were in the ITerminalDispatch, then each - // implementation would effectively be the same, calling only - // functions that are already part of the interface. - // Print the last graphical character a number of times. - if (_lastPrintedChar != AsciiChars::NUL) - { - std::wstring wstr(repeatCount, _lastPrintedChar); - _dispatch->PrintString(wstr); - } - success = true; - TermTelemetry::Instance().Log(TermTelemetry::Codes::REP); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - } + case CsiActionCodes::CUU_CursorUp: + case CsiActionCodes::CUD_CursorDown: + case CsiActionCodes::CUF_CursorForward: + case CsiActionCodes::CUB_CursorBackward: + case CsiActionCodes::CNL_CursorNextLine: + case CsiActionCodes::CPL_CursorPrevLine: + case CsiActionCodes::CHA_CursorHorizontalAbsolute: + case CsiActionCodes::HPA_HorizontalPositionAbsolute: + case CsiActionCodes::VPA_VerticalLinePositionAbsolute: + case CsiActionCodes::HPR_HorizontalPositionRelative: + case CsiActionCodes::VPR_VerticalPositionRelative: + case CsiActionCodes::ICH_InsertCharacter: + case CsiActionCodes::DCH_DeleteCharacter: + case CsiActionCodes::ECH_EraseCharacters: + success = _GetCursorDistance(parameters, distance); + break; + case CsiActionCodes::HVP_HorizontalVerticalPosition: + case CsiActionCodes::CUP_CursorPosition: + success = _GetXYPosition(parameters, line, column); + break; + case CsiActionCodes::DECSTBM_SetScrollingRegion: + success = _GetTopBottomMargins(parameters, topMargin, bottomMargin); + break; + case CsiActionCodes::ED_EraseDisplay: + case CsiActionCodes::EL_EraseLine: + success = _GetEraseOperation(parameters, eraseType); + break; + case CsiActionCodes::DECSET_PrivateModeSet: + case CsiActionCodes::DECRST_PrivateModeReset: + success = _GetPrivateModeParams(parameters, privateModeParams); + break; + case CsiActionCodes::SGR_SetGraphicsRendition: + success = _GetGraphicsOptions(parameters, _graphicsOptions); + break; + case CsiActionCodes::DSR_DeviceStatusReport: + success = _GetDeviceStatusOperation(parameters, deviceStatusType); + break; + case CsiActionCodes::DA_DeviceAttributes: + case CsiActionCodes::DA2_SecondaryDeviceAttributes: + case CsiActionCodes::DA3_TertiaryDeviceAttributes: + success = _VerifyDeviceAttributesParams(parameters); + break; + case CsiActionCodes::SU_ScrollUp: + case CsiActionCodes::SD_ScrollDown: + success = _GetScrollDistance(parameters, distance); + break; + case CsiActionCodes::ANSISYSSC_CursorSave: + case CsiActionCodes::ANSISYSRC_CursorRestore: + success = _VerifyHasNoParameters(parameters); + break; + case CsiActionCodes::IL_InsertLine: + case CsiActionCodes::DL_DeleteLine: + success = _GetScrollDistance(parameters, distance); + break; + case CsiActionCodes::CHT_CursorForwardTab: + case CsiActionCodes::CBT_CursorBackTab: + success = _GetTabDistance(parameters, numTabs); + break; + case CsiActionCodes::TBC_TabClear: + success = _GetTabClearType(parameters, clearType); + break; + case CsiActionCodes::DTTERM_WindowManipulation: + success = _GetWindowManipulationType(parameters, function); + break; + case CsiActionCodes::REP_RepeatCharacter: + success = _GetRepeatCount(parameters, repeatCount); + break; + case CsiActionCodes::DECSCUSR_SetCursorStyle: + success = _GetCursorStyle(parameters, cursorStyle); + break; + default: + // If no params to fill, param filling was successful. + success = true; + break; } - else if (intermediates.size() == 1) + + // if param filling successful, try to dispatch + if (success) { - const auto value = til::at(intermediates, 0); - switch (value) + switch (id) { - case L'?': - success = _IntermediateQuestionMarkDispatch(wch, parameters); + case CsiActionCodes::CUU_CursorUp: + success = _dispatch->CursorUp(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUU); break; - case L'>': - case L'=': - success = _IntermediateGreaterThanOrEqualDispatch(wch, value, parameters); + case CsiActionCodes::CUD_CursorDown: + success = _dispatch->CursorDown(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUD); break; - case L'!': - success = _IntermediateExclamationDispatch(wch); + case CsiActionCodes::CUF_CursorForward: + success = _dispatch->CursorForward(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUF); break; - case L' ': - success = _IntermediateSpaceDispatch(wch, parameters); + case CsiActionCodes::CUB_CursorBackward: + success = _dispatch->CursorBackward(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUB); + break; + case CsiActionCodes::CNL_CursorNextLine: + success = _dispatch->CursorNextLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CNL); + break; + case CsiActionCodes::CPL_CursorPrevLine: + success = _dispatch->CursorPrevLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CPL); + break; + case CsiActionCodes::CHA_CursorHorizontalAbsolute: + case CsiActionCodes::HPA_HorizontalPositionAbsolute: + success = _dispatch->CursorHorizontalPositionAbsolute(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CHA); + break; + case CsiActionCodes::VPA_VerticalLinePositionAbsolute: + success = _dispatch->VerticalLinePositionAbsolute(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::VPA); + break; + case CsiActionCodes::HPR_HorizontalPositionRelative: + success = _dispatch->HorizontalPositionRelative(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::HPR); + break; + case CsiActionCodes::VPR_VerticalPositionRelative: + success = _dispatch->VerticalPositionRelative(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::VPR); + break; + case CsiActionCodes::CUP_CursorPosition: + case CsiActionCodes::HVP_HorizontalVerticalPosition: + success = _dispatch->CursorPosition(line, column); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUP); + break; + case CsiActionCodes::DECSTBM_SetScrollingRegion: + success = _dispatch->SetTopBottomScrollingMargins(topMargin, bottomMargin); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM); + break; + case CsiActionCodes::ICH_InsertCharacter: + success = _dispatch->InsertCharacter(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ICH); + break; + case CsiActionCodes::DCH_DeleteCharacter: + success = _dispatch->DeleteCharacter(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DCH); + break; + case CsiActionCodes::ED_EraseDisplay: + success = _dispatch->EraseInDisplay(eraseType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ED); + break; + case CsiActionCodes::EL_EraseLine: + success = _dispatch->EraseInLine(eraseType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::EL); + break; + case CsiActionCodes::DECSET_PrivateModeSet: + success = _dispatch->SetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); + //TODO: MSFT:6367459 Add specific logging for each of the DECSET/DECRST codes + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSET); + break; + case CsiActionCodes::DECRST_PrivateModeReset: + success = _dispatch->ResetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRST); + break; + case CsiActionCodes::SGR_SetGraphicsRendition: + success = _dispatch->SetGraphicsRendition({ _graphicsOptions.data(), _graphicsOptions.size() }); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SGR); + break; + case CsiActionCodes::DSR_DeviceStatusReport: + success = _dispatch->DeviceStatusReport(deviceStatusType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DSR); + break; + case CsiActionCodes::DA_DeviceAttributes: + success = _dispatch->DeviceAttributes(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DA); + break; + case CsiActionCodes::DA2_SecondaryDeviceAttributes: + success = _dispatch->SecondaryDeviceAttributes(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DA2); + break; + case CsiActionCodes::DA3_TertiaryDeviceAttributes: + success = _dispatch->TertiaryDeviceAttributes(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DA3); + break; + case CsiActionCodes::SU_ScrollUp: + success = _dispatch->ScrollUp(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SU); + break; + case CsiActionCodes::SD_ScrollDown: + success = _dispatch->ScrollDown(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SD); + break; + case CsiActionCodes::ANSISYSSC_CursorSave: + success = _dispatch->CursorSaveState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSSC); + break; + case CsiActionCodes::ANSISYSRC_CursorRestore: + success = _dispatch->CursorRestoreState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSRC); + break; + case CsiActionCodes::IL_InsertLine: + success = _dispatch->InsertLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::IL); + break; + case CsiActionCodes::DL_DeleteLine: + success = _dispatch->DeleteLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DL); + break; + case CsiActionCodes::CHT_CursorForwardTab: + success = _dispatch->ForwardTab(numTabs); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CHT); + break; + case CsiActionCodes::CBT_CursorBackTab: + success = _dispatch->BackwardsTab(numTabs); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CBT); + break; + case CsiActionCodes::TBC_TabClear: + success = _dispatch->TabClear(clearType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::TBC); + break; + case CsiActionCodes::ECH_EraseCharacters: + success = _dispatch->EraseCharacters(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ECH); + break; + case CsiActionCodes::DTTERM_WindowManipulation: + success = _dispatch->WindowManipulation(static_cast(function), + remainingParams); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DTTERM_WM); + break; + case CsiActionCodes::REP_RepeatCharacter: + // Handled w/o the dispatch. This function is unique in that way + // If this were in the ITerminalDispatch, then each + // implementation would effectively be the same, calling only + // functions that are already part of the interface. + // Print the last graphical character a number of times. + if (_lastPrintedChar != AsciiChars::NUL) + { + std::wstring wstr(repeatCount, _lastPrintedChar); + _dispatch->PrintString(wstr); + } + success = true; + TermTelemetry::Instance().Log(TermTelemetry::Codes::REP); + break; + case CsiActionCodes::DECSCUSR_SetCursorStyle: + success = _dispatch->SetCursorStyle(cursorStyle); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSCUSR); + break; + case CsiActionCodes::DECSTR_SoftReset: + success = _dispatch->SoftReset(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTR); break; default: // If no functions to call, overall dispatch was a failure. @@ -726,6 +675,7 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, break; } } + // If we were unable to process the string, and there's a TTY attached to us, // trigger the state machine to flush the string to the terminal. if (_pfnFlushToTerminal != nullptr && !success) @@ -738,165 +688,6 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, return success; } -// Routine Description: -// - Handles actions that have postfix params on an intermediate '?', such as DECTCEM, DECCOLM, ATT610 -// Arguments: -// - wch - Character to dispatch. -// - parameters - set of numeric parameters collected while parsing the sequence. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateQuestionMarkDispatch(const wchar_t wchAction, - const gsl::span parameters) -{ - bool success = false; - - std::vector privateModeParams; - // Ensure that there was the right number of params - switch (wchAction) - { - case VTActionCodes::DECSET_PrivateModeSet: - case VTActionCodes::DECRST_PrivateModeReset: - success = _GetPrivateModeParams(parameters, privateModeParams); - break; - - default: - // If no params to fill, param filling was successful. - success = true; - break; - } - if (success) - { - switch (wchAction) - { - case VTActionCodes::DECSET_PrivateModeSet: - success = _dispatch->SetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); - //TODO: MSFT:6367459 Add specific logging for each of the DECSET/DECRST codes - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSET); - break; - case VTActionCodes::DECRST_PrivateModeReset: - success = _dispatch->ResetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRST); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - } - return success; -} - -// Routine Description: -// - Handles actions that have postfix params on an intermediate '>' or '='. -// Arguments: -// - wch - Character to dispatch. -// - intermediate - The intermediate character. -// - parameters - Set of numeric parameters collected while parsing the sequence. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateGreaterThanOrEqualDispatch(const wchar_t wch, - const wchar_t intermediate, - const gsl::span parameters) -{ - bool success = false; - - switch (wch) - { - case VTActionCodes::DA_DeviceAttributes: - if (_VerifyDeviceAttributesParams(parameters)) - { - switch (intermediate) - { - case L'>': - success = _dispatch->SecondaryDeviceAttributes(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DA2); - break; - case L'=': - success = _dispatch->TertiaryDeviceAttributes(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DA3); - break; - default: - success = false; - break; - } - } - break; - default: - success = false; - break; - } - - return success; -} - -// Routine Description: -// - Handles actions that have an intermediate '!', such as DECSTR -// Arguments: -// - wch - Character to dispatch. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateExclamationDispatch(const wchar_t wchAction) -{ - bool success = false; - - switch (wchAction) - { - case VTActionCodes::DECSTR_SoftReset: - success = _dispatch->SoftReset(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTR); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - return success; -} - -// Routine Description: -// - Handles actions that have an intermediate ' ' (0x20), such as DECSCUSR -// Arguments: -// - wch - Character to dispatch. -// - parameters - set of numeric parameters collected while parsing the sequence. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateSpaceDispatch(const wchar_t wchAction, - const gsl::span parameters) -{ - bool success = false; - DispatchTypes::CursorStyle cursorStyle = DefaultCursorStyle; - - // Parse params - switch (wchAction) - { - case VTActionCodes::DECSCUSR_SetCursorStyle: - success = _GetCursorStyle(parameters, cursorStyle); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - - // if param filling successful, try to dispatch - if (success) - { - switch (wchAction) - { - case VTActionCodes::DECSCUSR_SetCursorStyle: - success = _dispatch->SetCursorStyle(cursorStyle); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSCUSR); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - } - - return success; -} - // Routine Description: // - Triggers the Clear action to indicate that the state machine should erase // all internal state. diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index bce98255e..e6332a076 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -33,16 +33,11 @@ namespace Microsoft::Console::VirtualTerminal bool ActionPassThroughString(const std::wstring_view string) override; - bool ActionEscDispatch(const wchar_t wch, - const gsl::span intermediates) override; + bool ActionEscDispatch(const VTID id) override; - bool ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) override; + bool ActionVt52EscDispatch(const VTID id, const gsl::span parameters) override; - bool ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) override; + bool ActionCsiDispatch(const VTID id, const gsl::span parameters) override; bool ActionClear() noexcept override; @@ -73,94 +68,89 @@ namespace Microsoft::Console::VirtualTerminal wchar_t _lastPrintedChar; std::vector _graphicsOptions; - bool _IntermediateScsDispatch(const wchar_t wch, - const gsl::span intermediates); - bool _IntermediateQuestionMarkDispatch(const wchar_t wchAction, - const gsl::span parameters); - bool _IntermediateGreaterThanOrEqualDispatch(const wchar_t wch, - const wchar_t intermediate, - const gsl::span parameters); - bool _IntermediateExclamationDispatch(const wchar_t wch); - bool _IntermediateSpaceDispatch(const wchar_t wchAction, - const gsl::span parameters); - - enum VTActionCodes : wchar_t + enum EscActionCodes : uint64_t { - CUU_CursorUp = L'A', - CUD_CursorDown = L'B', - CUF_CursorForward = L'C', - CUB_CursorBackward = L'D', - CNL_CursorNextLine = L'E', - CPL_CursorPrevLine = L'F', - CHA_CursorHorizontalAbsolute = L'G', - CUP_CursorPosition = L'H', - ED_EraseDisplay = L'J', - EL_EraseLine = L'K', - SU_ScrollUp = L'S', - SD_ScrollDown = L'T', - ICH_InsertCharacter = L'@', - DCH_DeleteCharacter = L'P', - SGR_SetGraphicsRendition = L'm', - DECSC_CursorSave = L'7', - DECRC_CursorRestore = L'8', - DECSET_PrivateModeSet = L'h', - DECRST_PrivateModeReset = L'l', - ANSISYSSC_CursorSave = L's', // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented. - ANSISYSRC_CursorRestore = L'u', // NOTE: Overlaps with DECSMBV. Fix when/if implemented. - DECKPAM_KeypadApplicationMode = L'=', - DECKPNM_KeypadNumericMode = L'>', - DSR_DeviceStatusReport = L'n', - DA_DeviceAttributes = L'c', - DECSCPP_SetColumnsPerPage = L'|', - IL_InsertLine = L'L', - DL_DeleteLine = L'M', // Yes, this is the same as RI, however, RI is not preceded by a CSI, and DL is. - HPA_HorizontalPositionAbsolute = L'`', - VPA_VerticalLinePositionAbsolute = L'd', - HPR_HorizontalPositionRelative = L'a', - VPR_VerticalPositionRelative = L'e', - DECSTBM_SetScrollingRegion = L'r', - NEL_NextLine = L'E', // Not a CSI, so doesn't overlap with CNL - IND_Index = L'D', // Not a CSI, so doesn't overlap with CUB - RI_ReverseLineFeed = L'M', - HTS_HorizontalTabSet = L'H', // Not a CSI, so doesn't overlap with CUP - CHT_CursorForwardTab = L'I', - CBT_CursorBackTab = L'Z', - TBC_TabClear = L'g', - ECH_EraseCharacters = L'X', - HVP_HorizontalVerticalPosition = L'f', - DECSTR_SoftReset = L'p', - RIS_ResetToInitialState = L'c', // DA is prefaced by CSI, RIS by ESC - // 'q' is overloaded - no postfix is DECLL, ' ' postfix is DECSCUSR, and '"' is DECSCA - DECSCUSR_SetCursorStyle = L'q', // I believe we'll only ever implement DECSCUSR - DTTERM_WindowManipulation = L't', - REP_RepeatCharacter = L'b', - SS2_SingleShift = L'N', - SS3_SingleShift = L'O', - LS2_LockingShift = L'n', - LS3_LockingShift = L'o', - LS1R_LockingShift = L'~', - LS2R_LockingShift = L'}', - LS3R_LockingShift = L'|', - DECALN_ScreenAlignmentPattern = L'8' + DECSC_CursorSave = VTID("7"), + DECRC_CursorRestore = VTID("8"), + DECKPAM_KeypadApplicationMode = VTID("="), + DECKPNM_KeypadNumericMode = VTID(">"), + IND_Index = VTID("D"), + NEL_NextLine = VTID("E"), + HTS_HorizontalTabSet = VTID("H"), + RI_ReverseLineFeed = VTID("M"), + SS2_SingleShift = VTID("N"), + SS3_SingleShift = VTID("O"), + ST_StringTerminator = VTID("\\"), + RIS_ResetToInitialState = VTID("c"), + LS2_LockingShift = VTID("n"), + LS3_LockingShift = VTID("o"), + LS1R_LockingShift = VTID("~"), + LS2R_LockingShift = VTID("}"), + LS3R_LockingShift = VTID("|"), + DECALN_ScreenAlignmentPattern = VTID("#8") }; - enum Vt52ActionCodes : wchar_t + enum CsiActionCodes : uint64_t { - CursorUp = L'A', - CursorDown = L'B', - CursorRight = L'C', - CursorLeft = L'D', - EnterGraphicsMode = L'F', - ExitGraphicsMode = L'G', - CursorToHome = L'H', - ReverseLineFeed = L'I', - EraseToEndOfScreen = L'J', - EraseToEndOfLine = L'K', - DirectCursorAddress = L'Y', - Identify = L'Z', - EnterAlternateKeypadMode = L'=', - ExitAlternateKeypadMode = L'>', - ExitVt52Mode = L'<' + ICH_InsertCharacter = VTID("@"), + CUU_CursorUp = VTID("A"), + CUD_CursorDown = VTID("B"), + CUF_CursorForward = VTID("C"), + CUB_CursorBackward = VTID("D"), + CNL_CursorNextLine = VTID("E"), + CPL_CursorPrevLine = VTID("F"), + CHA_CursorHorizontalAbsolute = VTID("G"), + CUP_CursorPosition = VTID("H"), + CHT_CursorForwardTab = VTID("I"), + ED_EraseDisplay = VTID("J"), + EL_EraseLine = VTID("K"), + IL_InsertLine = VTID("L"), + DL_DeleteLine = VTID("M"), + DCH_DeleteCharacter = VTID("P"), + SU_ScrollUp = VTID("S"), + SD_ScrollDown = VTID("T"), + ECH_EraseCharacters = VTID("X"), + CBT_CursorBackTab = VTID("Z"), + HPA_HorizontalPositionAbsolute = VTID("`"), + HPR_HorizontalPositionRelative = VTID("a"), + REP_RepeatCharacter = VTID("b"), + DA_DeviceAttributes = VTID("c"), + DA2_SecondaryDeviceAttributes = VTID(">c"), + DA3_TertiaryDeviceAttributes = VTID("=c"), + VPA_VerticalLinePositionAbsolute = VTID("d"), + VPR_VerticalPositionRelative = VTID("e"), + HVP_HorizontalVerticalPosition = VTID("f"), + TBC_TabClear = VTID("g"), + DECSET_PrivateModeSet = VTID("?h"), + DECRST_PrivateModeReset = VTID("?l"), + SGR_SetGraphicsRendition = VTID("m"), + DSR_DeviceStatusReport = VTID("n"), + DECSTBM_SetScrollingRegion = VTID("r"), + ANSISYSSC_CursorSave = VTID("s"), // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented. + DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented. + ANSISYSRC_CursorRestore = VTID("u"), + DECSCUSR_SetCursorStyle = VTID(" q"), + DECSTR_SoftReset = VTID("!p"), + DECSCPP_SetColumnsPerPage = VTID("$|") + }; + + enum Vt52ActionCodes : uint64_t + { + CursorUp = VTID("A"), + CursorDown = VTID("B"), + CursorRight = VTID("C"), + CursorLeft = VTID("D"), + EnterGraphicsMode = VTID("F"), + ExitGraphicsMode = VTID("G"), + CursorToHome = VTID("H"), + ReverseLineFeed = VTID("I"), + EraseToEndOfScreen = VTID("J"), + EraseToEndOfLine = VTID("K"), + DirectCursorAddress = VTID("Y"), + Identify = VTID("Z"), + EnterAlternateKeypadMode = VTID("="), + ExitAlternateKeypadMode = VTID(">"), + ExitVt52Mode = VTID("<") }; enum OscActionCodes : unsigned int diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 7e60032c5..536bc591c 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -15,7 +15,6 @@ StateMachine::StateMachine(std::unique_ptr engine) : _state(VTStates::Ground), _trace(Microsoft::Console::VirtualTerminal::ParserTracing()), _isInAnsiMode(true), - _intermediates{}, _parameters{}, _oscString{}, _cachedSequence{ std::nullopt }, @@ -422,7 +421,7 @@ void StateMachine::_ActionEscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"EscDispatch"); - const bool success = _engine->ActionEscDispatch(wch, { _intermediates.data(), _intermediates.size() }); + const bool success = _engine->ActionEscDispatch(_identifier.Finalize(wch)); // Trace the result. _trace.DispatchSequenceTrace(success); @@ -445,8 +444,7 @@ void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"Vt52EscDispatch"); - const bool success = _engine->ActionVt52EscDispatch(wch, - { _intermediates.data(), _intermediates.size() }, + const bool success = _engine->ActionVt52EscDispatch(_identifier.Finalize(wch), { _parameters.data(), _parameters.size() }); // Trace the result. @@ -470,8 +468,7 @@ void StateMachine::_ActionCsiDispatch(const wchar_t wch) { _trace.TraceOnAction(L"CsiDispatch"); - const bool success = _engine->ActionCsiDispatch(wch, - { _intermediates.data(), _intermediates.size() }, + const bool success = _engine->ActionCsiDispatch(_identifier.Finalize(wch), { _parameters.data(), _parameters.size() }); // Trace the result. @@ -490,12 +487,12 @@ void StateMachine::_ActionCsiDispatch(const wchar_t wch) // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionCollect(const wchar_t wch) +void StateMachine::_ActionCollect(const wchar_t wch) noexcept { _trace.TraceOnAction(L"Collect"); // store collect data - _intermediates.push_back(wch); + _identifier.AddIntermediate(wch); } // Routine Description: @@ -541,7 +538,7 @@ void StateMachine::_ActionClear() _trace.TraceOnAction(L"Clear"); // clear all internal stored state. - _intermediates.clear(); + _identifier.Clear(); _parameters.clear(); diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index bcbf3f95b..03d134dfc 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -55,7 +55,7 @@ namespace Microsoft::Console::VirtualTerminal void _ActionPrint(const wchar_t wch); void _ActionEscDispatch(const wchar_t wch); void _ActionVt52EscDispatch(const wchar_t wch); - void _ActionCollect(const wchar_t wch); + void _ActionCollect(const wchar_t wch) noexcept; void _ActionParam(const wchar_t wch); void _ActionCsiDispatch(const wchar_t wch); void _ActionOscParam(const wchar_t wch) noexcept; @@ -142,7 +142,7 @@ namespace Microsoft::Console::VirtualTerminal std::wstring_view _run; - std::vector _intermediates; + VTIDBuilder _identifier; std::vector _parameters; std::wstring _oscString; diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index b97fa263c..c9d97b868 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -222,7 +222,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest std::wstring GenerateSgrMouseSequence(const CsiMouseButtonCodes button, const unsigned short modifiers, const COORD position, - const CsiActionCodes direction); + const VTID direction); // SGR_PARAMS serves as test input // - the state of the buttons (constructed via InputStateMachineEngine::CsiActionMouseCodes) @@ -1089,7 +1089,7 @@ void InputEngineTest::AltBackspaceEnterTest() std::wstring InputEngineTest::GenerateSgrMouseSequence(const CsiMouseButtonCodes button, const unsigned short modifiers, const COORD position, - const CsiActionCodes direction) + const VTID direction) { // we first need to convert "button" and "modifiers" into an 8 bit sequence unsigned int actionCode = 0; @@ -1102,7 +1102,11 @@ std::wstring InputEngineTest::GenerateSgrMouseSequence(const CsiMouseButtonCodes // modifiers represents the middle 4 bits actionCode |= modifiers; - return wil::str_printf_failfast(L"\x1b[<%d;%d;%d%c", static_cast(actionCode), position.X, position.Y, direction); + // mouse sequence identifiers consist of a private parameter prefix and a final character + const wchar_t prefixChar = direction[0]; + const wchar_t finalChar = direction[1]; + + return wil::str_printf_failfast(L"\x1b[%c%d;%d;%d%c", prefixChar, static_cast(actionCode), position.X, position.Y, finalChar); } void InputEngineTest::VerifySGRMouseData(const std::vector> testData) diff --git a/src/terminal/parser/ut_parser/StateMachineTest.cpp b/src/terminal/parser/ut_parser/StateMachineTest.cpp index 74e29ea72..1b4e2f057 100644 --- a/src/terminal/parser/ut_parser/StateMachineTest.cpp +++ b/src/terminal/parser/ut_parser/StateMachineTest.cpp @@ -50,12 +50,9 @@ public: return true; }; - bool ActionEscDispatch(const wchar_t /* wch */, - const gsl::span /* intermediates */) override { return true; }; + bool ActionEscDispatch(const VTID /* id */) override { return true; }; - bool ActionVt52EscDispatch(const wchar_t /*wch*/, - const gsl::span /*intermediates*/, - const gsl::span /*parameters*/) override { return true; }; + bool ActionVt52EscDispatch(const VTID /*id*/, const gsl::span /*parameters*/) override { return true; }; bool ActionClear() override { return true; }; @@ -82,9 +79,7 @@ public: bool DispatchIntermediatesFromEscape() const override { return false; }; // ActionCsiDispatch is the only method that's actually implemented. - bool ActionCsiDispatch(const wchar_t /*wch*/, - const gsl::span /*intermediates*/, - const gsl::span parameters) override + bool ActionCsiDispatch(const VTID /*id*/, const gsl::span parameters) override { // If flush to terminal is registered for a test, then use it. if (pfnFlushToTerminal)