From c157f6346aa46f9c95342e32991e8c9cd97a150d Mon Sep 17 00:00:00 2001 From: James Holderness Date: Fri, 3 Jun 2022 02:52:00 +0100 Subject: [PATCH] Add support for restoring a DECCTR color table report (#13139) This PR introduces the framework for the `DECRSTS` sequence which is used to restore terminal state reports. But to start with, I've just implemented the `DECCTR` color table report, which provides a way for applications to alter the terminal's color scheme. ## PR Checklist * [x] Closes #13132 * [x] CLA signed. * [x] Tests added/passed * [ ] Documentation updated. * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Detailed Description of the Pull Request / Additional comments I've added the functions for parsing DEC RGB and HLS color formats into the `Utils` class, where we've got all our other color parsing routines, since this functionality will eventually be needed in other VT protocols like Sixel and ReGIS. Since `DECRSTS` is a `DCS` sequence, this only works in conhost for now, or when using the experimental passthrough mode in Windows Terminal. ## Validation Steps Performed I've added a number of unit tests to check that the `DECCTR` report is being interpreted as expected. This includes various edge cases (e.g. omitted and out-of-range parameters), which I have confirmed to match the color parsing on a real VT240 terminal. --- .github/actions/spelling/expect/expect.txt | 4 + src/host/ut_host/ScreenBufferTests.cpp | 232 ++++++++++++++++++ src/terminal/adapter/DispatchTypes.hpp | 12 + src/terminal/adapter/ITermDispatch.hpp | 2 + src/terminal/adapter/adaptDispatch.cpp | 73 ++++++ src/terminal/adapter/adaptDispatch.hpp | 4 + src/terminal/adapter/termDispatch.hpp | 4 +- .../parser/OutputStateMachineEngine.cpp | 3 + .../parser/OutputStateMachineEngine.hpp | 1 + src/types/inc/utils.hpp | 2 + src/types/utils.cpp | 78 ++++++ 11 files changed, 414 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index eb055cfce85..d9c0dd008a9 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -520,6 +520,7 @@ DECAWM DECCKM DECCOLM DECCRA +DECCTR DECDHL decdld DECDLD @@ -545,7 +546,9 @@ DECREQTPARM DECRLM DECRQM DECRQSS +DECRQTSR DECRST +DECRSTS DECSASD DECSC DECSCA @@ -1006,6 +1009,7 @@ hkey hkl HKLM hlocal +HLS hlsl HMENU HMIDIOUT diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 75bc3eb00c6..09d037545cf 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -132,6 +132,7 @@ class ScreenBufferTests TEST_METHOD(VtNewlineOutsideMargins); TEST_METHOD(VtSetColorTable); + TEST_METHOD(VtRestoreColorTableReport); TEST_METHOD(ResizeTraditionalDoesNotDoubleFreeAttrRows); @@ -1738,6 +1739,237 @@ void ScreenBufferTests::VtSetColorTable() VERIFY_ARE_EQUAL(RGB(9, 9, 9), gci.GetColorTableEntry(5)); } +void ScreenBufferTests::VtRestoreColorTableReport() +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto& stateMachine = si.GetStateMachine(); + + // Set everything to white to start with. + for (auto i = 0; i < 16; i++) + { + gci.SetColorTableEntry(i, RGB(255, 255, 255)); + } + + // The test cases below are copied from the VT340 default color table, but + // note that our HLS conversion algorithm doesn't exactly match the VT340, + // so some of the component values may be off by 1%. + + Log::Comment(L"HLS color definitions"); + + // HLS(0°,0%,0%) -> RGB(0,0,0) + stateMachine.ProcessString(L"\033P2$p0;1;0;0;0\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 0, 0), gci.GetColorTableEntry(0)); + + // HLS(0°,49%,59%) -> RGB(51,51,199) + stateMachine.ProcessString(L"\033P2$p1;1;0;49;59\033\\"); + VERIFY_ARE_EQUAL(RGB(51, 51, 199), gci.GetColorTableEntry(1)); + + // HLS(120°,46%,71%) -> RGB(201,34,34) + stateMachine.ProcessString(L"\033P2$p2;1;120;46;71\033\\"); + VERIFY_ARE_EQUAL(RGB(201, 34, 34), gci.GetColorTableEntry(2)); + + // HLS(240°,49%,59%) -> RGB(51,199,51) + stateMachine.ProcessString(L"\033P2$p3;1;240;49;59\033\\"); + VERIFY_ARE_EQUAL(RGB(51, 199, 51), gci.GetColorTableEntry(3)); + + // HLS(60°,49%,59%) -> RGB(199,51,199) + stateMachine.ProcessString(L"\033P2$p4;1;60;49;59\033\\"); + VERIFY_ARE_EQUAL(RGB(199, 51, 199), gci.GetColorTableEntry(4)); + + // HLS(300°,49%,59%) -> RGB(51,199,199) + stateMachine.ProcessString(L"\033P2$p5;1;300;49;59\033\\"); + VERIFY_ARE_EQUAL(RGB(51, 199, 199), gci.GetColorTableEntry(5)); + + // HLS(180°,49%,59%) -> RGB(199,199,51) + stateMachine.ProcessString(L"\033P2$p6;1;180;49;59\033\\"); + VERIFY_ARE_EQUAL(RGB(199, 199, 51), gci.GetColorTableEntry(6)); + + // HLS(0°,46%,0%) -> RGB(117,117,117) + stateMachine.ProcessString(L"\033P2$p7;1;0;46;0\033\\"); + VERIFY_ARE_EQUAL(RGB(117, 117, 117), gci.GetColorTableEntry(7)); + + // HLS(0°,26%,0%) -> RGB(66,66,66) + stateMachine.ProcessString(L"\033P2$p8;1;0;26;0\033\\"); + VERIFY_ARE_EQUAL(RGB(66, 66, 66), gci.GetColorTableEntry(8)); + + // HLS(0°,46%,28%) -> RGB(84,84,150) + stateMachine.ProcessString(L"\033P2$p9;1;0;46;28\033\\"); + VERIFY_ARE_EQUAL(RGB(84, 84, 150), gci.GetColorTableEntry(9)); + + // HLS(120°,42%,38%) -> RGB(148,66,66) + stateMachine.ProcessString(L"\033P2$p10;1;120;42;38\033\\"); + VERIFY_ARE_EQUAL(RGB(148, 66, 66), gci.GetColorTableEntry(10)); + + // HLS(240°,46%,28%) -> RGB(84,150,84) + stateMachine.ProcessString(L"\033P2$p11;1;240;46;28\033\\"); + VERIFY_ARE_EQUAL(RGB(84, 150, 84), gci.GetColorTableEntry(11)); + + // HLS(60°,46%,28%) -> RGB(150,84,150) + stateMachine.ProcessString(L"\033P2$p12;1;60;46;28\033\\"); + VERIFY_ARE_EQUAL(RGB(150, 84, 150), gci.GetColorTableEntry(12)); + + // HLS(300°,46%,28%) -> RGB(84,150,150) + stateMachine.ProcessString(L"\033P2$p13;1;300;46;28\033\\"); + VERIFY_ARE_EQUAL(RGB(84, 150, 150), gci.GetColorTableEntry(13)); + + // HLS(180°,46%,28%) -> RGB(150,150,84) + stateMachine.ProcessString(L"\033P2$p14;1;180;46;28\033\\"); + VERIFY_ARE_EQUAL(RGB(150, 150, 84), gci.GetColorTableEntry(14)); + + // HLS(0°,79%,0%) -> RGB(201,201,201) + stateMachine.ProcessString(L"\033P2$p15;1;0;79;0\033\\"); + VERIFY_ARE_EQUAL(RGB(201, 201, 201), gci.GetColorTableEntry(15)); + + // Reset everything to white again. + for (auto i = 0; i < 16; i++) + { + gci.SetColorTableEntry(i, RGB(255, 255, 255)); + } + + Log::Comment(L"RGB color definitions"); + + // RGB(0%,0%,0%) -> RGB(0,0,0) + stateMachine.ProcessString(L"\033P2$p0;2;0;0;0\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 0, 0), gci.GetColorTableEntry(0)); + + // RGB(20%,20%,78%) -> RGB(51,51,199) + stateMachine.ProcessString(L"\033P2$p1;2;20;20;78\033\\"); + VERIFY_ARE_EQUAL(RGB(51, 51, 199), gci.GetColorTableEntry(1)); + + // RGB(79%,13%,13%) -> RGB(201,33,33) + stateMachine.ProcessString(L"\033P2$p2;2;79;13;13\033\\"); + VERIFY_ARE_EQUAL(RGB(201, 33, 33), gci.GetColorTableEntry(2)); + + // RGB(20%,78%,20%) -> RGB(51,199,51) + stateMachine.ProcessString(L"\033P2$p3;2;20;78;20\033\\"); + VERIFY_ARE_EQUAL(RGB(51, 199, 51), gci.GetColorTableEntry(3)); + + // RGB(78%,20%,78%) -> RGB(199,51,199) + stateMachine.ProcessString(L"\033P2$p4;2;78;20;78\033\\"); + VERIFY_ARE_EQUAL(RGB(199, 51, 199), gci.GetColorTableEntry(4)); + + // RGB(20%,78%,78%) -> RGB(51,199,199) + stateMachine.ProcessString(L"\033P2$p5;2;20;78;78\033\\"); + VERIFY_ARE_EQUAL(RGB(51, 199, 199), gci.GetColorTableEntry(5)); + + // RGB(78%,78%,20%) -> RGB(199,199,51) + stateMachine.ProcessString(L"\033P2$p6;2;78;78;20\033\\"); + VERIFY_ARE_EQUAL(RGB(199, 199, 51), gci.GetColorTableEntry(6)); + + // RGB(46%,46%,46%) -> RGB(117,117,117) + stateMachine.ProcessString(L"\033P2$p7;2;46;46;46\033\\"); + VERIFY_ARE_EQUAL(RGB(117, 117, 117), gci.GetColorTableEntry(7)); + + // RGB(26%,26%,26%) -> RGB(66,66,66) + stateMachine.ProcessString(L"\033P2$p8;2;26;26;26\033\\"); + VERIFY_ARE_EQUAL(RGB(66, 66, 66), gci.GetColorTableEntry(8)); + + // RGB(33%,33%,59%) -> RGB(84,84,150) + stateMachine.ProcessString(L"\033P2$p9;2;33;33;59\033\\"); + VERIFY_ARE_EQUAL(RGB(84, 84, 150), gci.GetColorTableEntry(9)); + + // RGB(58%,26%,26%) -> RGB(148,66,66) + stateMachine.ProcessString(L"\033P2$p10;2;58;26;26\033\\"); + VERIFY_ARE_EQUAL(RGB(148, 66, 66), gci.GetColorTableEntry(10)); + + // RGB(33%,59%,33%) -> RGB(84,150,84) + stateMachine.ProcessString(L"\033P2$p11;2;33;59;33\033\\"); + VERIFY_ARE_EQUAL(RGB(84, 150, 84), gci.GetColorTableEntry(11)); + + // RGB(59%,33%,59%) -> RGB(150,84,150) + stateMachine.ProcessString(L"\033P2$p12;2;59;33;59\033\\"); + VERIFY_ARE_EQUAL(RGB(150, 84, 150), gci.GetColorTableEntry(12)); + + // RGB(33%,59%,59%) -> RGB(84,150,150) + stateMachine.ProcessString(L"\033P2$p13;2;33;59;59\033\\"); + VERIFY_ARE_EQUAL(RGB(84, 150, 150), gci.GetColorTableEntry(13)); + + // RGB(59%,59%,33%) -> RGB(150,150,84) + stateMachine.ProcessString(L"\033P2$p14;2;59;59;33\033\\"); + VERIFY_ARE_EQUAL(RGB(150, 150, 84), gci.GetColorTableEntry(14)); + + // RGB(79%,79%,79%) -> RGB(201,201,201) + stateMachine.ProcessString(L"\033P2$p15;2;79;79;79\033\\"); + VERIFY_ARE_EQUAL(RGB(201, 201, 201), gci.GetColorTableEntry(15)); + + // Reset everything to white again. + for (auto i = 0; i < 16; i++) + { + gci.SetColorTableEntry(i, RGB(255, 255, 255)); + } + + Log::Comment(L"Multiple color definitions"); + + // Setting colors 0, 2, and 4 to red, green, and blue (HLS). + stateMachine.ProcessString(L"\033P2$p0;1;120;50;100/2;1;240;50;100/4;1;360;50;100\033\\"); + VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(0)); + VERIFY_ARE_EQUAL(RGB(0, 255, 0), gci.GetColorTableEntry(2)); + VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(4)); + + // Setting colors 1, 3, and 5 to red, green, and blue (RGB). + stateMachine.ProcessString(L"\033P2$p1;2;100;0;0/3;2;0;100;0/5;2;0;0;100\033\\"); + VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(1)); + VERIFY_ARE_EQUAL(RGB(0, 255, 0), gci.GetColorTableEntry(3)); + VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(5)); + + // The interpretation of omitted and out of range parameter values is based + // on the VT240 and VT340 sixel implementations. It is assumed that color + // parsing is handled in the same way for other operations. + + Log::Comment(L"Omitted parameter values"); + + // Omitted hue interpreted as 0° (blue) + stateMachine.ProcessString(L"\033P2$p6;1;;50;100\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(6)); + + // Omitted luminosity interpreted as 0% (black) + stateMachine.ProcessString(L"\033P2$p7;1;120;;100\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 0, 0), gci.GetColorTableEntry(7)); + + // Omitted saturation interpreted as 0% (gray) + stateMachine.ProcessString(L"\033P2$p8;1;120;50\033\\"); + VERIFY_ARE_EQUAL(RGB(128, 128, 128), gci.GetColorTableEntry(8)); + + // Omitted red component interpreted as 0% + stateMachine.ProcessString(L"\033P2$p6;2;;50;100\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 128, 255), gci.GetColorTableEntry(6)); + + // Omitted green component interpreted as 0% + stateMachine.ProcessString(L"\033P2$p7;2;50;;100\033\\"); + VERIFY_ARE_EQUAL(RGB(128, 0, 255), gci.GetColorTableEntry(7)); + + // Omitted blue component interpreted as 0% + stateMachine.ProcessString(L"\033P2$p8;2;50;100\033\\"); + VERIFY_ARE_EQUAL(RGB(128, 255, 0), gci.GetColorTableEntry(8)); + + Log::Comment(L"Out of range parameter values"); + + // Hue wraps at 360°, so 480° interpreted as 120° (red) + stateMachine.ProcessString(L"\033P2$p9;1;480;50;100\033\\"); + VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(9)); + + // Luminosity is clamped at 100%, so 150% interpreted as 100% + stateMachine.ProcessString(L"\033P2$p10;1;240;150;100\033\\"); + VERIFY_ARE_EQUAL(RGB(255, 255, 255), gci.GetColorTableEntry(10)); + + // Saturation is clamped at 100%, so 120% interpreted as 100% + stateMachine.ProcessString(L"\033P2$p11;1;0;50;120\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(11)); + + // Red component is clamped at 100%, so 150% interpreted as 100% + stateMachine.ProcessString(L"\033P2$p12;2;150;0;0\033\\"); + VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(12)); + + // Green component is clamped at 100%, so 150% interpreted as 100% + stateMachine.ProcessString(L"\033P2$p13;2;0;150;0\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 255, 0), gci.GetColorTableEntry(13)); + + // Blue component is clamped at 100%, so 150% interpreted as 100% + stateMachine.ProcessString(L"\033P2$p14;2;0;0;150\033\\"); + VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(14)); +} + void ScreenBufferTests::ResizeTraditionalDoesNotDoubleFreeAttrRows() { // there is not much to verify here, this test passes if the console doesn't crash. diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 8a6e14d1864..2ae3b67a6ab 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -245,6 +245,12 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes WindowFrame = 2, }; + enum class ColorModel : VTInt + { + HLS = 1, + RGB = 2, + }; + enum class EraseType : VTInt { ToEnd = 0, @@ -481,6 +487,12 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes Size96 = 1 }; + enum class ReportFormat : VTInt + { + TerminalStateReport = 1, + ColorTableReport = 2 + }; + constexpr VTInt s_sDECCOLMSetColumns = 132; constexpr VTInt s_sDECCOLMResetColumns = 80; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 3a8905737eb..bf6cdbbb934 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -144,6 +144,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch const VTParameter cellHeight, const DispatchTypes::DrcsCharsetSize charsetSize) = 0; // DECDLD + virtual StringHandler RestoreTerminalState(const DispatchTypes::ReportFormat format) = 0; // DECRSTS + virtual StringHandler RequestSetting() = 0; // DECRQSS virtual bool PlaySounds(const VTParameters parameters) = 0; // DECPS diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 4d63b29338a..716aa726e43 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2552,6 +2552,79 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber, }; } +// Method Description: +// - DECRSTS - Restores the terminal state from a stream of data previously +// saved with a DECRQTSR query. +// Arguments: +// - format - the format of the state report being restored. +// Return Value: +// - a function to receive the data or nullptr if the format is unsupported. +ITermDispatch::StringHandler AdaptDispatch::RestoreTerminalState(const DispatchTypes::ReportFormat format) +{ + switch (format) + { + case DispatchTypes::ReportFormat::ColorTableReport: + return _RestoreColorTable(); + default: + return nullptr; + } +} + +// Method Description: +// - DECCTR - This is a parser for the Color Table Report received via DECRSTS. +// The report contains a list of color definitions separated with a slash +// character. Each definition consists of 5 parameters: Pc;Pu;Px;Py;Pz +// - Pc is the color number. +// - Pu is the color model (1 = HLS, 2 = RGB). +// - Px, Py, and Pz are component values in the color model. +// Arguments: +// - +// Return Value: +// - a function to parse the report data. +ITermDispatch::StringHandler AdaptDispatch::_RestoreColorTable() +{ + return [this, parameter = VTInt{}, parameters = std::vector{}](const auto ch) mutable { + if (ch >= L'0' && ch <= L'9') + { + parameter *= 10; + parameter += (ch - L'0'); + parameter = std::min(parameter, MAX_PARAMETER_VALUE); + } + else if (ch == L';') + { + if (parameters.size() < 5) + { + parameters.push_back(parameter); + } + parameter = 0; + } + else if (ch == L'/' || ch == AsciiChars::ESC) + { + parameters.push_back(parameter); + const auto colorParameters = VTParameters{ parameters.data(), parameters.size() }; + const auto colorNumber = colorParameters.at(0).value_or(0); + if (colorNumber < TextColor::TABLE_SIZE) + { + const auto colorModel = DispatchTypes::ColorModel{ colorParameters.at(1) }; + const auto x = colorParameters.at(2).value_or(0); + const auto y = colorParameters.at(3).value_or(0); + const auto z = colorParameters.at(4).value_or(0); + if (colorModel == DispatchTypes::ColorModel::HLS) + { + SetColorTableEntry(colorNumber, Utils::ColorFromHLS(x, y, z)); + } + else if (colorModel == DispatchTypes::ColorModel::RGB) + { + SetColorTableEntry(colorNumber, Utils::ColorFromRGB100(x, y, z)); + } + } + parameters.clear(); + parameter = 0; + } + return (ch != AsciiChars::ESC); + }; +} + // Method Description: // - DECRQSS - Requests the state of a VT setting. The value being queried is // identified by the intermediate and final characters of its control diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 626434e69de..41e91885e25 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -139,6 +139,8 @@ namespace Microsoft::Console::VirtualTerminal const VTParameter cellHeight, const DispatchTypes::DrcsCharsetSize charsetSize) override; // DECDLD + StringHandler RestoreTerminalState(const DispatchTypes::ReportFormat format) override; // DECRSTS + StringHandler RequestSetting() override; // DECRQSS bool PlaySounds(const VTParameters parameters) override; // DECPS @@ -198,6 +200,8 @@ namespace Microsoft::Console::VirtualTerminal void _ResetTabStops() noexcept; void _InitTabStopsForWidth(const VTInt width); + StringHandler _RestoreColorTable(); + void _ReportSGRSetting() const; void _ReportDECSTBMSetting(); diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index a0c62883d38..6da351861f2 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -135,7 +135,9 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons const DispatchTypes::DrcsFontSet /*fontSet*/, const DispatchTypes::DrcsFontUsage /*fontUsage*/, const VTParameter /*cellHeight*/, - const DispatchTypes::DrcsCharsetSize /*charsetSize*/) override { return nullptr; } + const DispatchTypes::DrcsCharsetSize /*charsetSize*/) override { return nullptr; } // DECDLD + + StringHandler RestoreTerminalState(const DispatchTypes::ReportFormat /*format*/) override { return nullptr; }; // DECRSTS StringHandler RequestSetting() override { return nullptr; }; // DECRQSS diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index bf0d38f779f..5e0a9e14f3d 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -667,6 +667,9 @@ IStateMachineEngine::StringHandler OutputStateMachineEngine::ActionDcsDispatch(c parameters.at(6), parameters.at(7)); break; + case DcsActionCodes::DECRSTS_RestoreTerminalState: + handler = _dispatch->RestoreTerminalState(parameters.at(0)); + break; case DcsActionCodes::DECRQSS_RequestSetting: handler = _dispatch->RequestSetting(); break; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 548770f0989..21055d09bc1 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -149,6 +149,7 @@ namespace Microsoft::Console::VirtualTerminal enum DcsActionCodes : uint64_t { DECDLD_DownloadDRCS = VTID("{"), + DECRSTS_RestoreTerminalState = VTID("$p"), DECRQSS_RequestSetting = VTID("$q") }; diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 7a5eca2ff36..cef2620e806 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -47,6 +47,8 @@ namespace Microsoft::Console::Utils til::color ColorFromHexString(const std::string_view wstr); std::optional ColorFromXTermColor(const std::wstring_view wstr) noexcept; std::optional ColorFromXParseColorSpec(const std::wstring_view wstr) noexcept; + til::color ColorFromHLS(const int h, const int l, const int s) noexcept; + til::color ColorFromRGB100(const int r, const int g, const int b) noexcept; bool HexToUint(const wchar_t wch, unsigned int& value) noexcept; bool StringToUint(const std::wstring_view wstr, unsigned int& value); diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 85b226d337f..c3e432204d5 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -318,6 +318,84 @@ catch (...) return std::nullopt; } +// Routine Description: +// - Constructs a til::color value from RGB percentage components. +// Arguments: +// - r - The red component of the color (0-100%). +// - g - The green component of the color (0-100%). +// - b - The blue component of the color (0-100%). +// Return Value: +// - The color defined by the given components. +til::color Utils::ColorFromRGB100(const int r, const int g, const int b) noexcept +{ + // The color class is expecting components in the range 0 to 255, + // so we need to scale our percentage values by 255/100. We can + // optimise this conversion with a pre-created lookup table. + static constexpr auto scale100To255 = [] { + std::array lut{}; + for (size_t i = 0; i < std::size(lut); i++) + { + lut.at(i) = gsl::narrow_cast((i * 255 + 50) / 100); + } + return lut; + }(); + + const auto red = til::at(scale100To255, std::min(r, 100u)); + const auto green = til::at(scale100To255, std::min(g, 100u)); + const auto blue = til::at(scale100To255, std::min(b, 100u)); + return { red, green, blue }; +} + +// Routine Description: +// - Constructs a til::color value from HLS components. +// Arguments: +// - h - The hue component of the color (0-360°). +// - l - The luminosity component of the color (0-100%). +// - s - The saturation component of the color (0-100%). +// Return Value: +// - The color defined by the given components. +til::color Utils::ColorFromHLS(const int h, const int l, const int s) noexcept +{ + const auto hue = h % 360; + const auto lum = gsl::narrow_cast(std::min(l, 100)); + const auto sat = gsl::narrow_cast(std::min(s, 100)); + + // This calculation is based on the HSL to RGB algorithm described in + // Wikipedia: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB + // We start by calculating the chroma value, and the point along the bottom + // faces of the RGB cube with the same hue and chroma as our color (x). + const auto chroma = (50.f - abs(lum - 50.f)) * sat / 50.f; + const auto x = chroma * (60 - abs(hue % 120 - 60)) / 60.f; + + // We'll also need an offset added to each component to match lightness. + const auto lightness = lum - chroma / 2.0f; + + // We use the chroma value for the brightest component, x for the second + // brightest, and 0 for the last. The values are scaled by 255/100 to get + // them in the range 0 to 255, as required by the color class. + constexpr auto scale = 255.f / 100.f; + const auto comp1 = gsl::narrow_cast((chroma + lightness) * scale + 0.5f); + const auto comp2 = gsl::narrow_cast((x + lightness) * scale + 0.5f); + const auto comp3 = gsl::narrow_cast((0 + lightness) * scale + 0.5f); + + // Finally we order the components based on the given hue. But note that the + // DEC terminals used a different mapping for hue than is typical for modern + // color models. Blue is at 0°, red is at 120°, and green is at 240°. + // See DEC STD 070, ReGIS Graphics Extension, § 8.6.2.2.2, Color by Value. + if (hue < 60) + return { comp2, comp3, comp1 }; // blue to magenta + else if (hue < 120) + return { comp1, comp3, comp2 }; // magenta to red + else if (hue < 180) + return { comp1, comp2, comp3 }; // red to yellow + else if (hue < 240) + return { comp2, comp1, comp3 }; // yellow to green + else if (hue < 300) + return { comp3, comp1, comp2 }; // green to cyan + else + return { comp3, comp2, comp1 }; // cyan to blue +} + // Routine Description: // - Converts a hex character to its equivalent integer value. // Arguments: