From aa256ad5c9656c8aac75b1cf40c0ecae869dd4c0 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Mon, 7 Oct 2024 14:11:38 +0100 Subject: [PATCH] Add support for the S8C1T/S7C1T escape sequences (#17945) This PR adds support for the `S8C1T` and `S7C1T` commands, which enable an application to choose whether the terminal should use C1 controls when sending key sequences and query responses. This also updates the `DOCS` command to set both the input and output code pages. So when switched to ISO2022 mode, the C1 controls will be transmitted as 8-bit, which is what legacy systems would be expecting. ## Detailed Description of the Pull Request / Additional comments While adding the input code page support, I also reworked the way we handle the code page reset in `RIS`. In the original implementation we saved the active code page when the `DOCS` sequence was first used, and that would become the default value for a reset. With this PR I'm now saving the code pages whenever `SetConsoleCP` or `SetConsoleOutputCP` is called, so those APIs now control what the default values will be. This feels more consistent than the previous approach. And this is how WSL sets its initial code page to UTF-8. ## Validation Steps Performed I've added a couple of unit tests that check one of each applicable C1 control in the key sequences and query reports. I also built myself a code page aware telnet client so I could log into WSL in 8-bit mode, and confirmed that the C1 transmissions are working as expected in vttest. Closes #17931 Tests added/passed --- src/cascadia/TerminalCore/Terminal.hpp | 6 +- src/cascadia/TerminalCore/TerminalApi.cpp | 19 +- src/host/getset.cpp | 43 +++-- src/host/getset.h | 1 + src/host/output.cpp | 2 + src/host/outputStream.cpp | 39 +++- src/host/outputStream.hpp | 6 +- src/host/server.h | 3 + src/terminal/adapter/ITermDispatch.hpp | 1 + src/terminal/adapter/ITerminalApi.hpp | 6 +- src/terminal/adapter/InteractDispatch.cpp | 2 +- src/terminal/adapter/adaptDispatch.cpp | 177 ++++++++++-------- src/terminal/adapter/adaptDispatch.hpp | 6 +- src/terminal/adapter/termDispatch.hpp | 1 + .../adapter/ut_adapter/adapterTest.cpp | 71 +++++-- src/terminal/adapter/ut_adapter/inputTest.cpp | 18 ++ src/terminal/input/terminalInput.cpp | 15 +- src/terminal/input/terminalInput.hpp | 13 +- .../parser/OutputStateMachineEngine.cpp | 6 + .../parser/OutputStateMachineEngine.hpp | 2 + src/terminal/parser/stateMachine.cpp | 3 + 21 files changed, 304 insertions(+), 136 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 40a3bc8851..e8accf24e7 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -139,8 +139,10 @@ public: void SetWindowTitle(const std::wstring_view title) override; CursorType GetUserDefaultCursorStyle() const noexcept override; bool ResizeWindow(const til::CoordType width, const til::CoordType height) override; - void SetConsoleOutputCP(const unsigned int codepage) noexcept override; - unsigned int GetConsoleOutputCP() const noexcept override; + void SetCodePage(const unsigned int codepage) noexcept override; + void ResetCodePage() noexcept override; + unsigned int GetOutputCodePage() const noexcept override; + unsigned int GetInputCodePage() const noexcept override; void CopyToClipboard(wil::zwstring_view content) override; void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override; void SetWorkingDirectory(std::wstring_view uri) override; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 40ff599e11..e6edec3ee9 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -116,14 +116,25 @@ bool Terminal::ResizeWindow(const til::CoordType width, const til::CoordType hei return false; } -void Terminal::SetConsoleOutputCP(const unsigned int /*codepage*/) noexcept +void Terminal::SetCodePage(const unsigned int /*codepage*/) noexcept { - // TODO: This will be needed to support 8-bit charsets and DOCS sequences. + // Code pages are dealt with in ConHost, so this isn't needed. } -unsigned int Terminal::GetConsoleOutputCP() const noexcept +void Terminal::ResetCodePage() noexcept { - // TODO: See SetConsoleOutputCP above. + // There is nothing to reset, since the code page never changes. +} + +unsigned int Terminal::GetOutputCodePage() const noexcept +{ + // See above. The code page is always UTF-8. + return CP_UTF8; +} + +unsigned int Terminal::GetInputCodePage() const noexcept +{ + // See above. The code page is always UTF-8. return CP_UTF8; } diff --git a/src/host/getset.cpp b/src/host/getset.cpp index f199362f7a..b8109bca71 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -1140,7 +1140,6 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont { // Set new code page gci.OutputCP = codepage; - SetConsoleCPInfo(TRUE); } @@ -1157,13 +1156,36 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont { try { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - return DoSrvSetConsoleOutputCodePage(codepage); + RETURN_IF_FAILED(DoSrvSetConsoleOutputCodePage(codepage)); + // Setting the code page via the API also updates the default value. + // This is how the initial code page is set to UTF-8 in a WSL shell. + gci.DefaultOutputCP = codepage; + return S_OK; } CATCH_RETURN(); } +[[nodiscard]] HRESULT DoSrvSetConsoleInputCodePage(const unsigned int codepage) +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + + // Return if it's not known as a valid codepage ID. + RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage))); + + // Do nothing if no change. + if (gci.CP != codepage) + { + // Set new code page + gci.CP = codepage; + SetConsoleCPInfo(FALSE); + } + + return S_OK; +} + // Routine Description: // - Sets the codepage used for translating text when calling A versions of functions affecting the input buffer. // Arguments: @@ -1177,19 +1199,10 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - // Return if it's not known as a valid codepage ID. - RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage))); - - // Do nothing if no change. - if (gci.CP != codepage) - { - // Set new code page - gci.CP = codepage; - - SetConsoleCPInfo(FALSE); - } - + RETURN_IF_FAILED(DoSrvSetConsoleInputCodePage(codepage)); + // Setting the code page via the API also updates the default value. + // This is how the initial code page is set to UTF-8 in a WSL shell. + gci.DefaultCP = codepage; return S_OK; } CATCH_RETURN(); diff --git a/src/host/getset.h b/src/host/getset.h index 4c042813c2..f3030eb605 100644 --- a/src/host/getset.h +++ b/src/host/getset.h @@ -19,3 +19,4 @@ Revision History: class SCREEN_INFORMATION; [[nodiscard]] HRESULT DoSrvSetConsoleOutputCodePage(const unsigned int codepage); +[[nodiscard]] HRESULT DoSrvSetConsoleInputCodePage(const unsigned int codepage); diff --git a/src/host/output.cpp b/src/host/output.cpp index e57412f531..729821c841 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -35,6 +35,8 @@ using namespace Microsoft::Console::Interactivity; // codepage by console.cpl or shell32. The default codepage is OEMCP. gci.CP = gci.GetCodePage(); gci.OutputCP = gci.GetCodePage(); + gci.DefaultCP = gci.GetCodePage(); + gci.DefaultOutputCP = gci.GetCodePage(); gci.Flags |= CONSOLE_USE_PRIVATE_FLAGS; diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index d88be99ef3..3fbeb3cafa 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -221,27 +221,52 @@ void ConhostInternalGetSet::ShowWindow(bool showOrHide) } // Routine Description: -// - Connects the SetConsoleOutputCP API call directly into our Driver Message servicing call inside Conhost.exe +// - Set the code page used for translating text when calling A versions of I/O functions. // Arguments: -// - codepage - the new output codepage of the console. +// - codepage - the new code page of the console. // Return Value: // - -void ConhostInternalGetSet::SetConsoleOutputCP(const unsigned int codepage) +void ConhostInternalGetSet::SetCodePage(const unsigned int codepage) { - THROW_IF_FAILED(DoSrvSetConsoleOutputCodePage(codepage)); + LOG_IF_FAILED(DoSrvSetConsoleOutputCodePage(codepage)); + LOG_IF_FAILED(DoSrvSetConsoleInputCodePage(codepage)); } // Routine Description: -// - Gets the codepage used for translating text when calling A versions of functions affecting the output buffer. +// - Reset the code pages to their default values. // Arguments: // - // Return Value: -// - the outputCP of the console. -unsigned int ConhostInternalGetSet::GetConsoleOutputCP() const +// - +void ConhostInternalGetSet::ResetCodePage() +{ + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + LOG_IF_FAILED(DoSrvSetConsoleOutputCodePage(gci.DefaultOutputCP)); + LOG_IF_FAILED(DoSrvSetConsoleInputCodePage(gci.DefaultCP)); +} + +// Routine Description: +// - Gets the code page used for translating text when calling A versions of output functions. +// Arguments: +// - +// Return Value: +// - the output code page of the console. +unsigned int ConhostInternalGetSet::GetOutputCodePage() const { return ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP; } +// Routine Description: +// - Gets the code page used for translating text when calling A versions of input functions. +// Arguments: +// - +// Return Value: +// - the input code page of the console. +unsigned int ConhostInternalGetSet::GetInputCodePage() const +{ + return ServiceLocator::LocateGlobals().getConsoleInformation().CP; +} + // Routine Description: // - Copies the given content to the clipboard. // Arguments: diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 6b564abbf8..7b76e5f3d0 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -53,8 +53,10 @@ public: bool ResizeWindow(const til::CoordType width, const til::CoordType height) override; - void SetConsoleOutputCP(const unsigned int codepage) override; - unsigned int GetConsoleOutputCP() const override; + void SetCodePage(const unsigned int codepage) override; + void ResetCodePage() override; + unsigned int GetOutputCodePage() const override; + unsigned int GetInputCodePage() const override; void CopyToClipboard(const wil::zwstring_view content) override; void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override; diff --git a/src/host/server.h b/src/host/server.h index 1e14a94b73..0a0f905777 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -90,6 +90,9 @@ public: // the following fields are used for ansi-unicode translation UINT CP = 0; UINT OutputCP = 0; + // the VT RIS sequence uses these default values to reset the code pages + UINT DefaultCP = 0; + UINT DefaultOutputCP = 0; ULONG CtrlFlags = 0; // indicates outstanding ctrl requests ULONG LimitingProcessId = 0; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 3df5ef3017..8ec9e33bdc 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -122,6 +122,7 @@ public: virtual void LockingShiftRight(const VTInt gsetNumber) = 0; // LS1R, LS2R, LS3R virtual void SingleShift(const VTInt gsetNumber) = 0; // SS2, SS3 virtual void AcceptC1Controls(const bool enabled) = 0; // DECAC1 + virtual void SendC1Controls(const bool enabled) = 0; // S8C1T, S7C1T virtual void AnnounceCodeStructure(const VTInt ansiLevel) = 0; // ACS virtual void SoftReset() = 0; // DECSTR diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index b874bfb8cd..532133f9a8 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -72,8 +72,10 @@ namespace Microsoft::Console::VirtualTerminal virtual void ShowWindow(bool showOrHide) = 0; - virtual void SetConsoleOutputCP(const unsigned int codepage) = 0; - virtual unsigned int GetConsoleOutputCP() const = 0; + virtual void SetCodePage(const unsigned int codepage) = 0; + virtual void ResetCodePage() = 0; + virtual unsigned int GetOutputCodePage() const = 0; + virtual unsigned int GetInputCodePage() const = 0; virtual void CopyToClipboard(const wil::zwstring_view content) = 0; virtual void SetTaskbarProgress(const DispatchTypes::TaskbarState state, const size_t progress) = 0; diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index fa1a082604..a8fc483aee 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -62,7 +62,7 @@ void InteractDispatch::WriteString(const std::wstring_view string) { if (!string.empty()) { - const auto codepage = _api.GetConsoleOutputCP(); + const auto codepage = _api.GetOutputCodePage(); InputEventQueue keyEvents; for (const auto& wch : string) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 3899c7cd84..9ee93ede8b 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1243,7 +1243,7 @@ void AdaptDispatch::FillRectangularArea(const VTParameter ch, const VTInt top, c const auto charValue = ch.value_or(0) == 0 ? 32 : ch.value(); const auto glChar = (charValue >= 32 && charValue <= 126); const auto grChar = (charValue >= 160 && charValue <= 255); - const auto unicodeChar = (charValue >= 256 && charValue <= 65535 && _api.GetConsoleOutputCP() == CP_UTF8); + const auto unicodeChar = (charValue >= 256 && charValue <= 65535 && _api.GetOutputCodePage() == CP_UTF8); if (glChar || grChar || unicodeChar) { const auto fillChar = _termOutput.TranslateKey(gsl::narrow_cast(charValue)); @@ -1377,8 +1377,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p } } } - const auto response = wil::str_printf(L"\033P%d!~%04X\033\\", id, checksum); - _api.ReturnResponse(response); + _ReturnDcsResponse(wil::str_printf(L"%d!~%04X", id, checksum)); } // Routine Description: @@ -1484,7 +1483,7 @@ void AdaptDispatch::DeviceAttributes() // 32 = Text macros // 42 = ISO Latin-2 character set - _api.ReturnResponse(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"); + _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c"); } // Routine Description: @@ -1496,7 +1495,7 @@ void AdaptDispatch::DeviceAttributes() // - void AdaptDispatch::SecondaryDeviceAttributes() { - _api.ReturnResponse(L"\x1b[>0;10;1c"); + _ReturnCsiResponse(L">0;10;1c"); } // Routine Description: @@ -1506,7 +1505,7 @@ void AdaptDispatch::SecondaryDeviceAttributes() // - void AdaptDispatch::TertiaryDeviceAttributes() { - _api.ReturnResponse(L"\x1bP!|00000000\x1b\\"); + _ReturnDcsResponse(L"!|00000000"); } // Routine Description: @@ -1544,10 +1543,10 @@ void AdaptDispatch::RequestTerminalParameters(const DispatchTypes::ReportingPerm switch (permission) { case DispatchTypes::ReportingPermission::Unsolicited: - _api.ReturnResponse(L"\x1b[2;1;1;128;128;1;0x"); + _ReturnCsiResponse(L"2;1;1;128;128;1;0x"); break; case DispatchTypes::ReportingPermission::Solicited: - _api.ReturnResponse(L"\x1b[3;1;1;128;128;1;0x"); + _ReturnCsiResponse(L"3;1;1;128;128;1;0x"); break; default: break; @@ -1562,7 +1561,7 @@ void AdaptDispatch::RequestTerminalParameters(const DispatchTypes::ReportingPerm // - void AdaptDispatch::_DeviceStatusReport(const wchar_t* parameters) const { - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{}n"), parameters)); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{}n"), parameters)); } // Routine Description: @@ -1598,14 +1597,12 @@ void AdaptDispatch::_CursorPositionReport(const bool extendedReport) { // An extended report also includes the page number. const auto pageNumber = page.Number(); - const auto response = wil::str_printf(L"\x1b[?%d;%d;%dR", cursorPosition.y, cursorPosition.x, pageNumber); - _api.ReturnResponse(response); + _ReturnCsiResponse(wil::str_printf(L"?%d;%d;%dR", cursorPosition.y, cursorPosition.x, pageNumber)); } else { // The standard report only returns the cursor position. - const auto response = wil::str_printf(L"\x1b[%d;%dR", cursorPosition.y, cursorPosition.x); - _api.ReturnResponse(response); + _ReturnCsiResponse(wil::str_printf(L"%d;%dR", cursorPosition.y, cursorPosition.x)); } } @@ -1619,8 +1616,7 @@ void AdaptDispatch::_MacroSpaceReport() const { const auto spaceInBytes = _macroBuffer ? _macroBuffer->GetSpaceAvailable() : MacroBuffer::MAX_SPACE; // The available space is measured in blocks of 16 bytes, so we need to divide by 16. - const auto response = wil::str_printf(L"\x1b[%zu*{", spaceInBytes / 16); - _api.ReturnResponse(response); + _ReturnCsiResponse(wil::str_printf(L"%zu*{", spaceInBytes / 16)); } // Routine Description: @@ -1633,8 +1629,7 @@ void AdaptDispatch::_MacroChecksumReport(const VTParameter id) const { const auto requestId = id.value_or(0); const auto checksum = _macroBuffer ? _macroBuffer->CalculateChecksum() : 0; - const auto response = wil::str_printf(L"\033P%d!~%04X\033\\", requestId, checksum); - _api.ReturnResponse(response); + _ReturnDcsResponse(wil::str_printf(L"%d!~%04X", requestId, checksum)); } // Routine Description: @@ -1732,7 +1727,7 @@ void AdaptDispatch::RequestDisplayedExtent() const auto height = page.Viewport().height(); const auto left = page.XPanOffset() + 1; const auto top = page.YPanOffset() + 1; - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{};{};{};{};{}\"w"), height, width, left, top, page.Number())); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{};{};{};{};{}\"w"), height, width, left, top, page.Number())); } // Routine Description: @@ -2068,7 +2063,7 @@ void AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) prefix = L"?"; } - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\x1b[{}{};{}$y"), prefix, mode, state)); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{}{};{}$y"), prefix, mode, state)); } // - DECKPAM, DECKPNM - Sets the keypad input mode to either Application mode or Numeric mode (true, false respectively) @@ -2785,22 +2780,15 @@ void AdaptDispatch::_InitTabStopsForWidth(const VTInt width) // - codingSystem - The coding system that will be selected. void 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. - if (!_initialCodePage.has_value()) - { - _initialCodePage = _api.GetConsoleOutputCP(); - } - switch (codingSystem) { case DispatchTypes::CodingSystem::ISO2022: - _api.SetConsoleOutputCP(28591); + _api.SetCodePage(28591); AcceptC1Controls(true); _termOutput.EnableGrTranslation(true); break; case DispatchTypes::CodingSystem::UTF8: - _api.SetConsoleOutputCP(CP_UTF8); + _api.SetCodePage(CP_UTF8); AcceptC1Controls(false); _termOutput.EnableGrTranslation(false); break; @@ -2871,6 +2859,24 @@ void AdaptDispatch::AcceptC1Controls(const bool enabled) _api.GetStateMachine().SetParserMode(StateMachine::Mode::AcceptC1, enabled); } +//Routine Description: +// S8C1T/S7C1T - Enable or disable the sending of C1 controls in key sequences +// and query responses. When this is enabled, C1 controls are sent as a single +// codepoint. When disabled, they're sent as a two character escape sequence. +//Arguments: +// - enabled - true to send C1 controls, false to send escape sequences. +void AdaptDispatch::SendC1Controls(const bool enabled) +{ + // If this is an attempt to enable C1 controls, the input code page must be + // one of the DOCS choices (UTF-8 or ISO-8859-1), otherwise there's a risk + // that those controls won't have a valid encoding. + const auto codepage = _api.GetInputCodePage(); + if (enabled == false || codepage == CP_UTF8 || codepage == 28591) + { + _terminalInput.SetInputMode(TerminalInput::Mode::SendC1, enabled); + } +} + //Routine Description: // ACS - Announces the ANSI conformance level for subsequent data exchange. // This requires certain character sets to be mapped into the terminal's @@ -3003,13 +3009,11 @@ void AdaptDispatch::HardReset() // Completely reset the TerminalOutput state. _termOutput = {}; - if (_initialCodePage.has_value()) - { - // Restore initial code page if previously changed by a DOCS sequence. - _api.SetConsoleOutputCP(_initialCodePage.value()); - } - // Disable parsing of C1 control codes. + // Reset the code page to the default value. + _api.ResetCodePage(); + // Disable parsing and sending of C1 control codes. AcceptC1Controls(false); + SendC1Controls(false); // Sets the SGR state to normal - this must be done before EraseInDisplay // to ensure that it clears with the default background color. @@ -3276,7 +3280,7 @@ void AdaptDispatch::RequestColorTableEntry(const size_t tableIndex) { const til::color c{ color }; // Scale values up to match xterm's 16-bit color report format. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033]4;{};rgb:{:04x}/{:04x}/{:04x}\033\\"), tableIndex, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); + _ReturnOscResponse(fmt::format(FMT_COMPILE(L"4;{};rgb:{:04x}/{:04x}/{:04x}"), tableIndex, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); } } @@ -3321,7 +3325,7 @@ void AdaptDispatch::RequestXtermColorResource(const size_t resource) { const til::color c{ color }; // Scale values up to match xterm's 16-bit color report format. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033]{};rgb:{:04x}/{:04x}/{:04x}\033\\"), resource, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); + _ReturnOscResponse(fmt::format(FMT_COMPILE(L"{};rgb:{:04x}/{:04x}/{:04x}"), resource, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); } } } @@ -3377,7 +3381,7 @@ void AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy const auto reportSize = [&](const auto size) { const auto reportType = function - 10; - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{};{};{}t"), reportType, size.height, size.width)); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{};{};{}t"), reportType, size.height, size.width)); }; switch (function) @@ -3844,7 +3848,7 @@ void AdaptDispatch::RequestUserPreferenceCharset() { const auto size = _termOutput.GetUserPreferenceCharsetSize(); const auto id = _termOutput.GetUserPreferenceCharsetId(); - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P{}!u{}\033\\"), (size == 96 ? 1 : 0), id)); + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"{}!u{}"), (size == 96 ? 1 : 0), id)); } // Method Description: @@ -3976,9 +3980,9 @@ void AdaptDispatch::_ReportColorTable(const DispatchTypes::ColorModel colorModel { using namespace std::string_view_literals; - // A valid response always starts with DCS 2 $ s. + // A valid response always starts with 2 $ s. fmt::basic_memory_buffer response; - response.append(L"\033P2$s"sv); + response.append(L"2$s"sv); const auto modelNumber = static_cast(colorModel); for (size_t colorNumber = 0; colorNumber < TextColor::TABLE_SIZE; colorNumber++) @@ -4003,9 +4007,7 @@ void AdaptDispatch::_ReportColorTable(const DispatchTypes::ColorModel colorModel } } - // An ST ends the sequence. - response.append(L"\033\\"sv); - _api.ReturnResponse({ response.data(), response.size() }); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4108,7 +4110,7 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting() _ReportDECACSetting(VTParameter{ parameter }); break; default: - _api.ReturnResponse(L"\033P0$r\033\\"); + _ReturnDcsResponse(L"0$r"); break; } return false; @@ -4147,10 +4149,10 @@ void AdaptDispatch::_ReportSGRSetting() const { using namespace std::string_view_literals; - // A valid response always starts with DCS 1 $ r. + // A valid response always starts with 1 $ r. // Then the '0' parameter is to reset the SGR attributes to the defaults. fmt::basic_memory_buffer response; - response.append(L"\033P1$r0"sv); + response.append(L"1$r0"sv); const auto& attr = _pages.ActivePage().Attributes(); const auto ulStyle = attr.GetUnderlineStyle(); @@ -4202,9 +4204,9 @@ void AdaptDispatch::_ReportSGRSetting() const addColor(40, attr.GetBackground()); addColor(50, attr.GetUnderlineColor()); - // The 'm' indicates this is an SGR response, and ST ends the sequence. - response.append(L"m\033\\"sv); - _api.ReturnResponse({ response.data(), response.size() }); + // The 'm' indicates this is an SGR response. + response.append(L"m"sv); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4217,10 +4219,9 @@ void AdaptDispatch::_ReportDECSTBMSetting() { const auto page = _pages.ActivePage(); const auto [marginTop, marginBottom] = _GetVerticalMargins(page, false); - // A valid response always starts with DCS 1 $ r, the 'r' indicates this - // is a DECSTBM response, and ST ends the sequence. + // A valid response always starts with 1 $ r and the final 'r' indicates this is a DECSTBM response. // VT origin is at 1,1 so we need to add 1 to these margins. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{}r\033\\"), marginTop + 1, marginBottom + 1)); + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"1$r{};{}r"), marginTop + 1, marginBottom + 1)); } // Method Description: @@ -4233,10 +4234,9 @@ void AdaptDispatch::_ReportDECSLRMSetting() { const auto pageWidth = _pages.ActivePage().Width(); const auto [marginLeft, marginRight] = _GetHorizontalMargins(pageWidth); - // A valid response always starts with DCS 1 $ r, the 's' indicates this - // is a DECSLRM response, and ST ends the sequence. + // A valid response always starts with 1 $ r and the 's' indicates this is a DECSLRM response. // VT origin is at 1,1 so we need to add 1 to these margins. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{}s\033\\"), marginLeft + 1, marginRight + 1)); + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"1$r{};{}s"), marginLeft + 1, marginRight + 1)); } // Method Description: @@ -4249,26 +4249,26 @@ void AdaptDispatch::_ReportDECSCUSRSetting() const { const auto& cursor = _pages.ActivePage().Cursor(); const auto blinking = cursor.IsBlinkingAllowed(); - // A valid response always starts with DCS 1 $ r. This is followed by a + // A valid response always starts with 1 $ r. This is followed by a // number from 1 to 6 representing the cursor style. The ' q' indicates - // this is a DECSCUSR response, and ST ends the sequence. + // this is a DECSCUSR response. switch (cursor.GetType()) { case CursorType::FullBox: - _api.ReturnResponse(blinking ? L"\033P1$r1 q\033\\" : L"\033P1$r2 q\033\\"); + _ReturnDcsResponse(blinking ? L"1$r1 q" : L"1$r2 q"); break; case CursorType::Underscore: - _api.ReturnResponse(blinking ? L"\033P1$r3 q\033\\" : L"\033P1$r4 q\033\\"); + _ReturnDcsResponse(blinking ? L"1$r3 q" : L"1$r4 q"); break; case CursorType::VerticalBar: - _api.ReturnResponse(blinking ? L"\033P1$r5 q\033\\" : L"\033P1$r6 q\033\\"); + _ReturnDcsResponse(blinking ? L"1$r5 q" : L"1$r6 q"); break; default: // If we have a non-standard style, this is likely because it's the // user's chosen default style, so we report a default value of 0. // That way, if an application later tries to restore the cursor with // the returned value, it should be reset to its original state. - _api.ReturnResponse(L"\033P1$r0 q\033\\"); + _ReturnDcsResponse(L"1$r0 q"); break; } } @@ -4282,10 +4282,10 @@ void AdaptDispatch::_ReportDECSCUSRSetting() const void AdaptDispatch::_ReportDECSCASetting() const { const auto isProtected = _pages.ActivePage().Attributes().IsProtected(); - // A valid response always starts with DCS 1 $ r. This is followed by '1' if - // the protected attribute is set, or '0' if not. The '"q' indicates this is - // a DECSCA response, and ST ends the sequence. - _api.ReturnResponse(isProtected ? L"\033P1$r1\"q\033\\" : L"\033P1$r0\"q\033\\"); + // A valid response always starts with 1 $ r. This is followed by '1' if the + // protected attribute is set, or '0' if not. The '"q' indicates this is a + // DECSCA response. + _ReturnDcsResponse(isProtected ? L"1$r1\"q" : L"1$r0\"q"); } // Method Description: @@ -4297,10 +4297,10 @@ void AdaptDispatch::_ReportDECSCASetting() const void AdaptDispatch::_ReportDECSACESetting() const { const auto rectangularExtent = _modes.test(Mode::RectangularChangeExtent); - // A valid response always starts with DCS 1 $ r. This is followed by '2' if + // A valid response always starts with 1 $ r. This is followed by '2' if // the extent is rectangular, or '1' if it's a stream. The '*x' indicates - // this is a DECSACE response, and ST ends the sequence. - _api.ReturnResponse(rectangularExtent ? L"\033P1$r2*x\033\\" : L"\033P1$r1*x\033\\"); + // this is a DECSACE response. + _ReturnDcsResponse(rectangularExtent ? L"1$r2*x" : L"1$r1*x"); } // Method Description: @@ -4324,12 +4324,11 @@ void AdaptDispatch::_ReportDECACSetting(const VTInt itemNumber) const bgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameBackground); break; default: - _api.ReturnResponse(L"\033P0$r\033\\"); + _ReturnDcsResponse(L"0$r"); return; } - // A valid response always starts with DCS 1 $ r, the ',|' indicates this - // is a DECAC response, and ST ends the sequence. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{};{},|\033\\"), itemNumber, fgIndex, bgIndex)); + // A valid response always starts with 1 $ r and the ',|' indicates this is a DECAC response. + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"1$r{};{};{},|"), itemNumber, fgIndex, bgIndex)); } // Routine Description: @@ -4442,9 +4441,9 @@ void AdaptDispatch::_ReportCursorInformation() const auto charset2 = _termOutput.GetCharsetId(2); const auto charset3 = _termOutput.GetCharsetId(3); - // A valid response always starts with DCS 1 $ u and ends with ST. + // A valid response always starts with 1 $ u. const auto response = fmt::format( - FMT_COMPILE(L"\033P1$u{};{};{};{};{};{};{};{};{};{}{}{}{}\033\\"), + FMT_COMPILE(L"1$u{};{};{};{};{};{};{};{};{};{}{}{}{}"), cursorPosition.y, cursorPosition.x, page.Number(), @@ -4458,7 +4457,7 @@ void AdaptDispatch::_ReportCursorInformation() charset1, charset2, charset3); - _api.ReturnResponse({ response.data(), response.size() }); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4623,9 +4622,9 @@ void AdaptDispatch::_ReportTabStops() using namespace std::string_view_literals; - // A valid response always starts with DCS 2 $ u. + // A valid response always starts with 2 $ u. fmt::basic_memory_buffer response; - response.append(L"\033P2$u"sv); + response.append(L"2$u"sv); auto need_separator = false; for (auto column = 0; column < width; column++) @@ -4638,9 +4637,7 @@ void AdaptDispatch::_ReportTabStops() } } - // An ST ends the sequence. - response.append(L"\033\\"sv); - _api.ReturnResponse({ response.data(), response.size() }); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4685,6 +4682,26 @@ ITermDispatch::StringHandler AdaptDispatch::_RestoreTabStops() }; } +void AdaptDispatch::_ReturnCsiResponse(const std::wstring_view response) const +{ + const auto csi = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9B" : L"\x1B["; + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"{}{}"), csi, response)); +} + +void AdaptDispatch::_ReturnDcsResponse(const std::wstring_view response) const +{ + const auto dcs = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x90" : L"\x1BP"; + const auto st = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9C" : L"\x1B\\"; + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"{}{}{}"), dcs, response, st)); +} + +void AdaptDispatch::_ReturnOscResponse(const std::wstring_view response) const +{ + const auto osc = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9D" : L"\x1B]"; + const auto st = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9C" : L"\x1B\\"; + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"{}{}{}"), osc, response, st)); +} + // Routine Description: // - DECPS - Plays a sequence of musical notes. // Arguments: diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 4fc85ebc92..161e21d358 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -121,6 +121,7 @@ namespace Microsoft::Console::VirtualTerminal void LockingShiftRight(const VTInt gsetNumber) override; // LS1R, LS2R, LS3R void SingleShift(const VTInt gsetNumber) noexcept override; // SS2, SS3 void AcceptC1Controls(const bool enabled) override; // DECAC1 + void SendC1Controls(const bool enabled) override; // S8C1T, S7C1T void AnnounceCodeStructure(const VTInt ansiLevel) override; // ACS void SoftReset() override; // DECSTR void HardReset() override; // RIS @@ -289,6 +290,10 @@ namespace Microsoft::Console::VirtualTerminal void _ReportTabStops(); StringHandler _RestoreTabStops(); + void _ReturnCsiResponse(const std::wstring_view response) const; + void _ReturnDcsResponse(const std::wstring_view response) const; + void _ReturnOscResponse(const std::wstring_view response) const; + std::vector _tabStopColumns; bool _initDefaultTabStops = true; @@ -302,7 +307,6 @@ namespace Microsoft::Console::VirtualTerminal std::shared_ptr _sixelParser; std::unique_ptr _fontBuffer; std::shared_ptr _macroBuffer; - std::optional _initialCodePage; // We have two instances of the saved cursor state, because we need // one for the main buffer (at index 0), and another for the alt buffer diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index bb12dc3288..bd91d38d7e 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -115,6 +115,7 @@ public: void LockingShiftRight(const VTInt /*gsetNumber*/) override {} // LS1R, LS2R, LS3R void SingleShift(const VTInt /*gsetNumber*/) override {} // SS2, SS3 void AcceptC1Controls(const bool /*enabled*/) override {} // DECAC1 + void SendC1Controls(const bool /*enabled*/) override {} // S8C1T, S7C1T void AnnounceCodeStructure(const VTInt /*ansiLevel*/) override {} // ACS void SoftReset() override {} // DECSTR diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index ab2e08d3b8..5fab90f3d3 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -158,17 +158,28 @@ public: return true; } - void SetConsoleOutputCP(const unsigned int codepage) override + void SetCodePage(const unsigned int codepage) override { - Log::Comment(L"SetConsoleOutputCP MOCK called..."); - THROW_HR_IF(E_FAIL, !_setConsoleOutputCPResult); - VERIFY_ARE_EQUAL(_expectedOutputCP, codepage); + Log::Comment(L"SetCodePage MOCK called..."); + THROW_HR_IF(E_FAIL, !_setCodePageResult); + VERIFY_ARE_EQUAL(_expectedCodePage, codepage); } - unsigned int GetConsoleOutputCP() const override + void ResetCodePage() override { - Log::Comment(L"GetConsoleOutputCP MOCK called..."); - return _expectedOutputCP; + Log::Comment(L"ResetCodePage MOCK called..."); + } + + unsigned int GetOutputCodePage() const override + { + Log::Comment(L"GetOutputCodePage MOCK called..."); + return _expectedCodePage; + } + + unsigned int GetInputCodePage() const override + { + Log::Comment(L"GetInputCodePage MOCK called..."); + return _expectedCodePage; } void CopyToClipboard(const wil::zwstring_view /*content*/) @@ -367,7 +378,7 @@ public: til::point _expectedCursorPos; TextAttribute _expectedAttribute = {}; - unsigned int _expectedOutputCP = 0; + unsigned int _expectedCodePage = 0; bool _isPty = false; bool _returnResponseResult = false; @@ -376,8 +387,7 @@ public: bool _setWindowTitleResult = false; std::wstring_view _expectedWindowTitle{}; - bool _setConsoleOutputCPResult = false; - bool _getConsoleOutputCPResult = false; + bool _setCodePageResult = false; bool _expectedShowWindow = false; std::wstring _expectedMenuJson{}; @@ -3529,15 +3539,15 @@ public: Log::Comment(L"3. Designate ISO-2022 coding system"); // Code page should be set to ISO-8859-1 and C1 parsing enabled - _testGetSet->_setConsoleOutputCPResult = true; - _testGetSet->_expectedOutputCP = 28591; + _testGetSet->_setCodePageResult = true; + _testGetSet->_expectedCodePage = 28591; _pDispatch->DesignateCodingSystem(DispatchTypes::CodingSystem::ISO2022); VERIFY_IS_TRUE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1)); Log::Comment(L"4. Designate UTF-8 coding system"); // Code page should be set to UTF-8 and C1 parsing disabled - _testGetSet->_setConsoleOutputCPResult = true; - _testGetSet->_expectedOutputCP = CP_UTF8; + _testGetSet->_setCodePageResult = true; + _testGetSet->_expectedCodePage = CP_UTF8; _pDispatch->DesignateCodingSystem(DispatchTypes::CodingSystem::UTF8); VERIFY_IS_FALSE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1)); } @@ -3962,6 +3972,39 @@ public: _pDispatch->PagePositionAbsolute(1); } + TEST_METHOD(SendC1ControlTest) + { + const auto S7C1T = L"\033 F"; + const auto S8C1T = L"\033 G"; + + _testGetSet->PrepData(); + _testGetSet->_expectedCodePage = CP_UTF8; + + Log::Comment(L"Generating reports with C1 control sequences"); + _stateMachine->ProcessString(S8C1T); + + _pDispatch->SecondaryDeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x9b>0;10;1c"); + + _pDispatch->TertiaryDeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x90!|00000000\x9c"); + + _pDispatch->RequestColorTableEntry(0); + _testGetSet->ValidateInputEvent(L"\x9d\x34;0;rgb:0c0c/0c0c/0c0c\x9c"); + + Log::Comment(L"Generating reports with 7-bit escape sequence"); + _stateMachine->ProcessString(S7C1T); + + _pDispatch->SecondaryDeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x1b[>0;10;1c"); + + _pDispatch->TertiaryDeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x1bP!|00000000\x1b\\"); + + _pDispatch->RequestColorTableEntry(0); + _testGetSet->ValidateInputEvent(L"\x1b]4;0;rgb:0c0c/0c0c/0c0c\x1b\\"); + } + private: TerminalInput _terminalInput; std::unique_ptr _testGetSet; diff --git a/src/terminal/adapter/ut_adapter/inputTest.cpp b/src/terminal/adapter/ut_adapter/inputTest.cpp index d3c4c3d0d0..ab893be772 100644 --- a/src/terminal/adapter/ut_adapter/inputTest.cpp +++ b/src/terminal/adapter/ut_adapter/inputTest.cpp @@ -36,6 +36,7 @@ public: TEST_METHOD(CtrlNumTest); TEST_METHOD(BackarrowKeyModeTest); TEST_METHOD(AutoRepeatModeTest); + TEST_METHOD(SendC1ControlTest); wchar_t GetModifierChar(const bool fShift, const bool fAlt, const bool fCtrl) { @@ -790,3 +791,20 @@ void InputTest::AutoRepeatModeTest() VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down)); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(up)); } + +void InputTest::SendC1ControlTest() +{ + TerminalInput input; + + Log::Comment(L"Sending keys with C1 control sequences"); + input.SetInputMode(TerminalInput::Mode::SendC1, true); + + TestKey(TerminalInput::MakeOutput(L"\x9bH"), input, 0, VK_HOME); + TestKey(TerminalInput::MakeOutput(L"\x8fP"), input, 0, VK_F1); + + Log::Comment(L"Sending keys with 7-bit escape sequence"); + input.SetInputMode(TerminalInput::Mode::SendC1, false); + + TestKey(TerminalInput::MakeOutput(L"\x1b[H"), input, 0, VK_HOME); + TestKey(TerminalInput::MakeOutput(L"\x1bOP"), input, 0, VK_F1); +} diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index 7c09ef4615..ac0795bdc4 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -53,7 +53,7 @@ void TerminalInput::SetInputMode(const Mode mode, const bool enabled) noexcept // If we've changed one of the modes that alter the VT input sequences, // we'll need to regenerate our keyboard map. - static constexpr auto keyMapModes = til::enumset{ Mode::LineFeed, Mode::Ansi, Mode::Keypad, Mode::CursorKey, Mode::BackarrowKey }; + static constexpr auto keyMapModes = til::enumset{ Mode::LineFeed, Mode::Ansi, Mode::Keypad, Mode::CursorKey, Mode::BackarrowKey, Mode::SendC1 }; if (keyMapModes.test(mode)) { _initKeyboardMap(); @@ -353,6 +353,19 @@ try _keyMap.clear(); + // The CSI and SS3 introducers are C1 control codes, which can either be + // sent as a single codepoint, or as a two character escape sequence. + if (_inputMode.test(Mode::SendC1)) + { + _csi = L"\x9B"; + _ss3 = L"\x8F"; + } + else + { + _csi = L"\x1B["; + _ss3 = L"\x1BO"; + } + // PAUSE doesn't have a VT mapping, but traditionally we've mapped it to ^Z, // regardless of modifiers. defineKeyWithUnusedModifiers(VK_PAUSE, L"\x1A"s); diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp index e8ae53267e..4048826956 100644 --- a/src/terminal/input/terminalInput.hpp +++ b/src/terminal/input/terminalInput.hpp @@ -33,6 +33,7 @@ namespace Microsoft::Console::VirtualTerminal CursorKey, BackarrowKey, Win32, + SendC1, Utf8MouseEncoding, SgrMouseEncoding, @@ -80,10 +81,8 @@ namespace Microsoft::Console::VirtualTerminal til::enumset _inputMode{ Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll }; bool _forceDisableWin32InputMode{ false }; - // In the future, if we add support for "8-bit" input mode, these prefixes - // will sometimes be replaced with equivalent C1 control characters. - static constexpr auto _csi = L"\x1B["; - static constexpr auto _ss3 = L"\x1BO"; + const wchar_t* _csi = L"\x1B["; + const wchar_t* _ss3 = L"\x1BO"; void _initKeyboardMap() noexcept; DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key); @@ -108,9 +107,9 @@ namespace Microsoft::Console::VirtualTerminal #pragma endregion #pragma region MouseInput - [[nodiscard]] static OutputType _GenerateDefaultSequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); - [[nodiscard]] static OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); - [[nodiscard]] static OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isDown, bool isHover, short modifierKeyState, short delta); + [[nodiscard]] OutputType _GenerateDefaultSequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); + [[nodiscard]] OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); + [[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isDown, bool isHover, short modifierKeyState, short delta); [[nodiscard]] OutputType _makeAlternateScrollOutput(short delta) const; diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 5339c29bdf..edb53e100d 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -256,6 +256,12 @@ bool OutputStateMachineEngine::ActionEscDispatch(const VTID id) case EscActionCodes::DECAC1_AcceptC1Controls: _dispatch->AcceptC1Controls(true); break; + case EscActionCodes::S7C1T_Send7bitC1Controls: + _dispatch->SendC1Controls(false); + break; + case EscActionCodes::S8C1T_Send8bitC1Controls: + _dispatch->SendC1Controls(true); + break; case EscActionCodes::ACS_AnsiLevel1: _dispatch->AnnounceCodeStructure(1); break; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index ad594c269c..ab41e066cf 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -77,6 +77,8 @@ namespace Microsoft::Console::VirtualTerminal LS2R_LockingShift = VTID("}"), LS3R_LockingShift = VTID("|"), DECAC1_AcceptC1Controls = VTID(" 7"), + S7C1T_Send7bitC1Controls = VTID(" F"), + S8C1T_Send8bitC1Controls = VTID(" G"), ACS_AnsiLevel1 = VTID(" L"), ACS_AnsiLevel2 = VTID(" M"), ACS_AnsiLevel3 = VTID(" N"), diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 65e7460e9b..12954512bd 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -25,6 +25,9 @@ StateMachine::StateMachine(std::unique_ptr engine, const bo _oscString{}, _cachedSequence{ std::nullopt } { + // The state machine must always accept C1 controls for the input engine, + // otherwise it won't work when the ConPTY terminal has S8C1T enabled. + _parserMode.set(Mode::AcceptC1, _isEngineForInput); _ActionClear(); }