diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 5383cc4713c..049dfcaf443 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -13,6 +13,9 @@ using namespace Microsoft::Console; using namespace Microsoft::Console::VirtualTerminal; +// the console uses 0xffffffff as an "invalid color" value +constexpr COLORREF INVALID_COLOR = 0xffffffff; + // takes ownership of pDispatch OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr pDispatch) : _dispatch(std::move(pDispatch)), @@ -672,36 +675,52 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/, break; } case OscActionCodes::SetForegroundColor: - { - std::vector colors; - success = _GetOscSetColor(string, colors); - if (success && colors.size() > 0) - { - success = _dispatch->SetDefaultForeground(til::at(colors, 0)); - } - TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCFG); - break; - } case OscActionCodes::SetBackgroundColor: - { - std::vector colors; - success = _GetOscSetColor(string, colors); - if (success && colors.size() > 0) - { - success = _dispatch->SetDefaultBackground(til::at(colors, 0)); - } - TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCBG); - break; - } case OscActionCodes::SetCursorColor: { std::vector colors; success = _GetOscSetColor(string, colors); - if (success && colors.size() > 0) + if (success) { - success = _dispatch->SetCursorColor(til::at(colors, 0)); + size_t commandIndex = parameter; + size_t colorIndex = 0; + + if (commandIndex == OscActionCodes::SetForegroundColor && colors.size() > colorIndex) + { + const auto color = til::at(colors, colorIndex); + if (color != INVALID_COLOR) + { + success = success && _dispatch->SetDefaultForeground(color); + } + TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCFG); + commandIndex++; + colorIndex++; + } + + if (commandIndex == OscActionCodes::SetBackgroundColor && colors.size() > colorIndex) + { + const auto color = til::at(colors, colorIndex); + if (color != INVALID_COLOR) + { + success = success && _dispatch->SetDefaultBackground(color); + } + TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCBG); + commandIndex++; + colorIndex++; + } + + if (commandIndex == OscActionCodes::SetCursorColor && colors.size() > colorIndex) + { + const auto color = til::at(colors, colorIndex); + if (color != INVALID_COLOR) + { + success = success && _dispatch->SetCursorColor(color); + } + TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCSCC); + commandIndex++; + colorIndex++; + } } - TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCSCC); break; } case OscActionCodes::SetClipboard: @@ -718,7 +737,7 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/, } case OscActionCodes::ResetCursorColor: { - success = _dispatch->SetCursorColor(0xffffffff); + success = _dispatch->SetCursorColor(INVALID_COLOR); TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCRCC); break; } @@ -947,19 +966,15 @@ bool OutputStateMachineEngine::_ParseHyperlink(const std::wstring_view string, // with accumulated Ps. For example "OSC 10;color1;color2" is effectively an "OSC 10;color1" // and an "OSC 11;color2". // -// However, we do not support the chaining of OSC 10-17 yet. Right now only the first parameter -// will take effect. // Arguments: // - string - the Osc String to parse // - rgbs - receives the colors that we parsed in the format: 0x00BBGGRR // Return Value: -// - True if the first table index and color was parsed successfully. False otherwise. +// - True if at least one color was parsed successfully. False otherwise. bool OutputStateMachineEngine::_GetOscSetColor(const std::wstring_view string, std::vector& rgbs) const noexcept try { - bool success = false; - const auto parts = Utils::SplitString(string, L';'); if (parts.size() < 1) { @@ -973,17 +988,16 @@ try if (colorOptional.has_value()) { newRgbs.push_back(colorOptional.value()); - // Only mark the parsing as a success iff the first color is a success. - if (i == 0) - { - success = true; - } + } + else + { + newRgbs.push_back(INVALID_COLOR); } } rgbs.swap(newRgbs); - return success; + return rgbs.size() > 0; } CATCH_LOG_RETURN_FALSE() diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index fcc438ca529..b13adaf8afb 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -1052,6 +1052,8 @@ class StatefulDispatch final : public TermDispatch _defaultForegroundColor{ RGB(0, 0, 0) }, _setDefaultBackground(false), _defaultBackgroundColor{ RGB(0, 0, 0) }, + _setDefaultCursorColor(false), + _defaultCursorColor{ RGB(0, 0, 0) }, _hyperlinkMode{ false }, _options{ s_cMaxOptions, static_cast(s_uiGraphicsCleared) }, // fill with cleared option _colorTable{}, @@ -1441,6 +1443,13 @@ class StatefulDispatch final : public TermDispatch return true; } + bool SetCursorColor(const DWORD color) noexcept override + { + _setDefaultCursorColor = true; + _defaultCursorColor = color; + return true; + } + bool SetClipboard(std::wstring_view content) noexcept override { _copyContent = { content.begin(), content.end() }; @@ -1527,6 +1536,8 @@ class StatefulDispatch final : public TermDispatch DWORD _defaultForegroundColor; bool _setDefaultBackground; DWORD _defaultBackgroundColor; + bool _setDefaultCursorColor; + DWORD _defaultCursorColor; bool _setColorTableEntry; bool _hyperlinkMode; std::wstring _copyContent; @@ -2846,6 +2857,7 @@ class StateMachineExternalTest final auto engine = std::make_unique(std::move(dispatch)); StateMachine mach(std::move(engine)); + // Single param mach.ProcessString(L"\033]10;rgb:1/1/1\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultForegroundColor); @@ -2876,20 +2888,34 @@ class StateMachineExternalTest final pDispatch->ClearState(); - // Multiple params. + // Multiple params mach.ProcessString(L"\033]10;#111;rgb:2/2/2\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultBackgroundColor); pDispatch->ClearState(); mach.ProcessString(L"\033]10;#111;DarkOrange\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor); pDispatch->ClearState(); - // Partially valid sequences. Only the first color works. + mach.ProcessString(L"\033]10;#111;DarkOrange;rgb:2/2/2\033\\"); + VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); + VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor); + VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor); + VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultCursorColor); + + pDispatch->ClearState(); + + // Partially valid multi-param sequences. mach.ProcessString(L"\033]10;#111;\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); @@ -2899,33 +2925,38 @@ class StateMachineExternalTest final mach.ProcessString(L"\033]10;#111;rgb:\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); + VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); pDispatch->ClearState(); mach.ProcessString(L"\033]10;#111;#2\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); + VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); pDispatch->ClearState(); - // Invalid sequences. - mach.ProcessString(L"\033]10;rgb:1/1/\033\\"); + mach.ProcessString(L"\033]10;;rgb:1/1/1\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor); pDispatch->ClearState(); - mach.ProcessString(L"\033]10;#1\033\\"); + mach.ProcessString(L"\033]10;#1;rgb:1/1/1\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor); pDispatch->ClearState(); - // Invalid multi-param sequences. - mach.ProcessString(L"\033]10;;rgb:1/1/1\033\\"); + // Invalid sequences. + mach.ProcessString(L"\033]10;rgb:1/1/\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); pDispatch->ClearState(); - mach.ProcessString(L"\033]10;#1;rgb:1/1/1\033\\"); + mach.ProcessString(L"\033]10;#1\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); pDispatch->ClearState(); @@ -2944,6 +2975,7 @@ class StateMachineExternalTest final pDispatch->ClearState(); + // Single param mach.ProcessString(L"\033]11;rgb:12/34/56\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor); @@ -2968,7 +3000,30 @@ class StateMachineExternalTest final pDispatch->ClearState(); - // Partially valid sequences. Only the first color works. + // Multiple params + mach.ProcessString(L"\033]11;#111;rgb:2/2/2\033\\"); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); + VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultCursorColor); + + pDispatch->ClearState(); + + mach.ProcessString(L"\033]11;#111;DarkOrange\033\\"); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); + VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultCursorColor); + + pDispatch->ClearState(); + + mach.ProcessString(L"\033]11;#111;DarkOrange;rgb:2/2/2\033\\"); + VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); + VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); + VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultCursorColor); + // The third param is out of range. + + pDispatch->ClearState(); + + // Partially valid multi-param sequences. mach.ProcessString(L"\033]11;#111;\033\\"); VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); @@ -2987,24 +3042,27 @@ class StateMachineExternalTest final pDispatch->ClearState(); - // Invalid sequences. - mach.ProcessString(L"\033]11;rgb:1/1/\033\\"); + mach.ProcessString(L"\033]11;;rgb:1/1/1\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); + VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor); + VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultCursorColor); pDispatch->ClearState(); - mach.ProcessString(L"\033]11;#1\033\\"); + mach.ProcessString(L"\033]11;#1;rgb:1/1/1\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); + VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor); + VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultCursorColor); pDispatch->ClearState(); - // Invalid multi-param sequences. - mach.ProcessString(L"\033]11;;rgb:1/1/1\033\\"); + // Invalid sequences. + mach.ProcessString(L"\033]11;rgb:1/1/\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); pDispatch->ClearState(); - mach.ProcessString(L"\033]11;#1;rgb:1/1/1\033\\"); + mach.ProcessString(L"\033]11;#1\033\\"); VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); pDispatch->ClearState();