From 1118f546c5f264d13b3aad5dd9590597016a14d7 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 17 Sep 2024 15:12:56 +0100 Subject: [PATCH] Make the DOCS sequence set both input and output code pages. --- src/cascadia/TerminalCore/Terminal.hpp | 6 ++- src/cascadia/TerminalCore/TerminalApi.cpp | 19 ++++++-- src/host/getset.cpp | 45 ++++++++++--------- src/host/getset.h | 3 +- src/host/output.cpp | 2 + src/host/outputStream.cpp | 42 ++++++++++++++--- src/host/outputStream.hpp | 6 ++- src/host/server.h | 3 ++ src/terminal/adapter/ITerminalApi.hpp | 6 ++- src/terminal/adapter/InteractDispatch.cpp | 2 +- src/terminal/adapter/adaptDispatch.cpp | 29 ++++++------ src/terminal/adapter/adaptDispatch.hpp | 1 - .../adapter/ut_adapter/adapterTest.cpp | 38 ++++++++++------ 13 files changed, 132 insertions(+), 70 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index e9d3f1abd0a..94d2a938a81 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -140,8 +140,10 @@ class Microsoft::Terminal::Core::Terminal final : 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 40ff599e115..e6edec3ee92 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 f199362f7af..332e5111f40 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -1128,23 +1128,16 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont CATCH_RETURN(); } -[[nodiscard]] HRESULT DoSrvSetConsoleOutputCodePage(const unsigned int codepage) +void DoSrvSetConsoleOutputCodePage(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.OutputCP != codepage) { // Set new code page gci.OutputCP = codepage; - SetConsoleCPInfo(TRUE); } - - return S_OK; } // Routine Description: @@ -1157,13 +1150,32 @@ 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 it's not known as a valid codepage ID. + RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage))); + 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(); } +void DoSrvSetConsoleInputCodePage(const unsigned int codepage) +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + // Do nothing if no change. + if (gci.CP != codepage) + { + // Set new code page + gci.CP = codepage; + SetConsoleCPInfo(FALSE); + } +} + // Routine Description: // - Sets the codepage used for translating text when calling A versions of functions affecting the input buffer. // Arguments: @@ -1177,19 +1189,12 @@ 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); - } - + 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 4c042813c27..bb29db1adb6 100644 --- a/src/host/getset.h +++ b/src/host/getset.h @@ -18,4 +18,5 @@ Revision History: #include "../inc/conattrs.hpp" class SCREEN_INFORMATION; -[[nodiscard]] HRESULT DoSrvSetConsoleOutputCodePage(const unsigned int codepage); +void DoSrvSetConsoleOutputCodePage(const unsigned int codepage); +void DoSrvSetConsoleInputCodePage(const unsigned int codepage); diff --git a/src/host/output.cpp b/src/host/output.cpp index e57412f5310..729821c841a 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 d88be99ef33..9938409b2bb 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -221,27 +221,55 @@ 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)); + if (IsValidCodePage(codepage)) + { + DoSrvSetConsoleOutputCodePage(codepage); + DoSrvSetConsoleInputCodePage(codepage); + } +} + +// Routine Description: +// - Reset the code pages to their default values. +// Arguments: +// - +// Return Value: +// - +void ConhostInternalGetSet::ResetCodePage() +{ + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + DoSrvSetConsoleOutputCodePage(gci.DefaultOutputCP); + DoSrvSetConsoleInputCodePage(gci.DefaultCP); } // Routine Description: -// - Gets the codepage used for translating text when calling A versions of functions affecting the output buffer. +// - Gets the code page used for translating text when calling A versions of output functions. // Arguments: // - // Return Value: -// - the outputCP of the console. -unsigned int ConhostInternalGetSet::GetConsoleOutputCP() const +// - 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 6b564abbf8d..7b76e5f3d0a 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -53,8 +53,10 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: 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 1e14a94b731..0a0f905777b 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -90,6 +90,9 @@ class CONSOLE_INFORMATION : // 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/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index b874bfb8cd4..532133f9a84 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 fa1a0826047..a8fc483aee6 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 78658e70c65..9ee93ede8b8 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)); @@ -2780,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; @@ -2874,7 +2867,14 @@ void AdaptDispatch::AcceptC1Controls(const bool enabled) // - enabled - true to send C1 controls, false to send escape sequences. void AdaptDispatch::SendC1Controls(const bool enabled) { - _terminalInput.SetInputMode(TerminalInput::Mode::SendC1, 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: @@ -3009,11 +3009,8 @@ 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()); - } + // Reset the code page to the default value. + _api.ResetCodePage(); // Disable parsing and sending of C1 control codes. AcceptC1Controls(false); SendC1Controls(false); diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 969a49d9f2d..161e21d358d 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -307,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/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index ab2e08d3b85..91c7ee5ce02 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -158,17 +158,28 @@ class TestGetSet final : public ITerminalApi 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 @@ class TestGetSet final : public ITerminalApi til::point _expectedCursorPos; TextAttribute _expectedAttribute = {}; - unsigned int _expectedOutputCP = 0; + unsigned int _expectedCodePage = 0; bool _isPty = false; bool _returnResponseResult = false; @@ -376,8 +387,7 @@ class TestGetSet final : public ITerminalApi 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 @@ class AdapterTest 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)); }