Add support for querying Xterm dynamic colors and palette (#17729)

This pull request adds support for querying all of the "dynamic
resource" colors (foreground, background, cursor) as well as the entire
color palette using OSC 4, 10, 11 and 12 with the `?` color specifier.

To ease integration and to make it easier to extend later, I have
consolidated `SetDefaultForeground`, `SetDefaultBackground` and
`SetCursorColor` into one function `SetXtermColorResource`, plus its
analog `RequestXtermColorResource`.

Those functions will map xterm resource OSC numbers to color table
entries and optionally color _alias_ entries using a constant table. The
alias mappings are required to support reassigning the default
foreground and background to their indexed entries after a `DECAC`.

While there are only three real entries in the mapping table right now,
I have designs on bringing in selection background (xterm "highlight")
and foreground (xterm "highlightText").

We can also extend this to support resetting via OSC 110-119. However,
at the adapter layer we do not have the requisite information to restore
any of the colors (even the cursor color!) to the user's defaults.

`OSC 10` and `OSC 11` queries report the final values of
`DECAC`-reassigned entries, under the assumption that an application
asking for them wants to make a determination regardless of their
internal meaning to us (that is: they read through the aliased color to
its final destination in the color palette.)

I've tested this with lsix, which detects the background color before
generating sixel previews. It works great!

ConPTY does not currently pass OSC sequences received on the input
handle, so work was required to make it do so.

Closes #3718
This commit is contained in:
Dustin L. Howett 2024-08-21 17:45:14 -05:00 committed by GitHub
parent ce92b18507
commit 3b4ee83ed1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 565 additions and 175 deletions

View File

@ -22,6 +22,7 @@ vcvars\w*
ROY\sG\.\sBIV ROY\sG\.\sBIV
!(?:(?i)ESC)!\[ !(?:(?i)ESC)!\[
!(?:(?i)CSI)!(?:\d+(?:;\d+|)m|[ABCDF]) !(?:(?i)CSI)!(?:\d+(?:;\d+|)m|[ABCDF])
(?i)rgb:[a-z0-9]{2,4}/[a-z0-9]{2,4}/[a-z0-9]{2,4}
# SSE intrinsics like "_mm_subs_epu16" # SSE intrinsics like "_mm_subs_epu16"
\b_mm(?:|256|512)_\w+\b \b_mm(?:|256|512)_\w+\b

View File

@ -76,9 +76,10 @@ public:
virtual bool BackwardsTab(const VTInt numTabs) = 0; // CBT virtual bool BackwardsTab(const VTInt numTabs) = 0; // CBT
virtual bool TabClear(const DispatchTypes::TabClearType clearType) = 0; // TBC virtual bool TabClear(const DispatchTypes::TabClearType clearType) = 0; // TBC
virtual bool TabSet(const VTParameter setType) = 0; // DECST8C virtual bool TabSet(const VTParameter setType) = 0; // DECST8C
virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCColorTable virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCSetColorTable
virtual bool SetDefaultForeground(const DWORD color) = 0; // OSCDefaultForeground virtual bool RequestColorTableEntry(const size_t tableIndex) = 0; // OSCGetColorTable
virtual bool SetDefaultBackground(const DWORD color) = 0; // OSCDefaultBackground virtual bool SetXtermColorResource(const size_t resource, const DWORD color) = 0; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor
virtual bool RequestXtermColorResource(const size_t resource) = 0; // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor
virtual bool AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) = 0; // DECAC virtual bool AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) = 0; // DECAC
virtual bool EraseInDisplay(const DispatchTypes::EraseType eraseType) = 0; // ED virtual bool EraseInDisplay(const DispatchTypes::EraseType eraseType) = 0; // ED
@ -128,7 +129,6 @@ public:
virtual bool ScreenAlignmentPattern() = 0; // DECALN virtual bool ScreenAlignmentPattern() = 0; // DECALN
virtual bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR virtual bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR
virtual bool SetCursorColor(const COLORREF color) = 0; // OSCSetCursorColor, OSCResetCursorColor
virtual bool SetClipboard(wil::zwstring_view content) = 0; // OSCSetClipboard virtual bool SetClipboard(wil::zwstring_view content) = 0; // OSCSetClipboard

View File

@ -18,6 +18,25 @@ using namespace Microsoft::Console::VirtualTerminal;
static constexpr std::wstring_view whitespace{ L" " }; static constexpr std::wstring_view whitespace{ L" " };
struct XtermResourceColorTableEntry
{
int ColorTableIndex;
int AliasIndex;
};
static constexpr std::array<XtermResourceColorTableEntry, 10> XtermResourceColorTableMappings{ {
/* 10 */ { TextColor::DEFAULT_FOREGROUND, static_cast<int>(ColorAlias::DefaultForeground) },
/* 11 */ { TextColor::DEFAULT_BACKGROUND, static_cast<int>(ColorAlias::DefaultBackground) },
/* 12 */ { TextColor::CURSOR_COLOR, -1 },
/* 13 */ { -1, -1 },
/* 14 */ { -1, -1 },
/* 15 */ { -1, -1 },
/* 16 */ { -1, -1 },
/* 17 */ { -1, -1 },
/* 18 */ { -1, -1 },
/* 19 */ { -1, -1 },
} };
AdaptDispatch::AdaptDispatch(ITerminalApi& api, Renderer* renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) noexcept : AdaptDispatch::AdaptDispatch(ITerminalApi& api, Renderer* renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) noexcept :
_api{ api }, _api{ api },
_renderer{ renderer }, _renderer{ renderer },
@ -3446,18 +3465,6 @@ bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
return true; return true;
} }
// Method Description:
// - Sets a single entry of the colortable to a new value
// Arguments:
// - tableIndex: The VT color table index
// - dwColor: The new RGB color value to use.
// Return Value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::SetCursorColor(const COLORREF cursorColor)
{
return SetColorTableEntry(TextColor::CURSOR_COLOR, cursorColor);
}
// Routine Description: // Routine Description:
// - OSC Copy to Clipboard // - OSC Copy to Clipboard
// Arguments: // Arguments:
@ -3498,28 +3505,69 @@ bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwCo
return true; return true;
} }
// Method Description: bool AdaptDispatch::RequestColorTableEntry(const size_t tableIndex)
// - Sets the default foreground color to a new value
// Arguments:
// - dwColor: The new RGB color value to use, as a COLORREF, format 0x00BBGGRR.
// Return Value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::SetDefaultForeground(const DWORD dwColor)
{ {
_renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, TextColor::DEFAULT_FOREGROUND); const auto color = _renderSettings.GetColorTableEntry(tableIndex);
return SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, dwColor); if (color != INVALID_COLOR)
{
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));
}
return true;
} }
// Method Description: // Method Description:
// - Sets the default background color to a new value // - Sets one Xterm Color Resource such as Default Foreground, Background, Cursor
// Arguments:
// - dwColor: The new RGB color value to use, as a COLORREF, format 0x00BBGGRR.
// Return Value: // Return Value:
// True if handled successfully. False otherwise. // True if handled successfully. False otherwise.
bool AdaptDispatch::SetDefaultBackground(const DWORD dwColor) bool AdaptDispatch::SetXtermColorResource(const size_t resource, const DWORD color)
{ {
_renderSettings.SetColorAliasIndex(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND); assert(resource >= 10);
return SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, dwColor); const auto mappingIndex = resource - 10;
const auto& oscMapping = XtermResourceColorTableMappings.at(mappingIndex);
if (oscMapping.ColorTableIndex > 0)
{
if (oscMapping.AliasIndex >= 0) [[unlikely]]
{
// If this color change applies to an aliased color, point the alias at the new color
_renderSettings.SetColorAliasIndex(static_cast<ColorAlias>(oscMapping.AliasIndex), oscMapping.ColorTableIndex);
}
return SetColorTableEntry(oscMapping.ColorTableIndex, color);
}
return true;
}
// Method Description:
// - Reports the value of one Xterm Color Resource, if it is set.
// Return Value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::RequestXtermColorResource(const size_t resource)
{
assert(resource >= 10);
const auto mappingIndex = resource - 10;
const auto& oscMapping = XtermResourceColorTableMappings.at(mappingIndex);
if (oscMapping.ColorTableIndex > 0)
{
size_t finalColorIndex = oscMapping.ColorTableIndex;
if (oscMapping.AliasIndex >= 0) [[unlikely]]
{
finalColorIndex = _renderSettings.GetColorAliasIndex(static_cast<ColorAlias>(oscMapping.AliasIndex));
}
const auto color = _renderSettings.GetColorTableEntry(finalColorIndex);
if (color != INVALID_COLOR)
{
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));
}
}
return true;
} }
// Method Description: // Method Description:

View File

@ -126,14 +126,14 @@ namespace Microsoft::Console::VirtualTerminal
bool HardReset() override; // RIS bool HardReset() override; // RIS
bool ScreenAlignmentPattern() override; // DECALN bool ScreenAlignmentPattern() override; // DECALN
bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) override; // DECSCUSR bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) override; // DECSCUSR
bool SetCursorColor(const COLORREF cursorColor) override;
bool SetClipboard(const wil::zwstring_view content) override; // OSCSetClipboard bool SetClipboard(const wil::zwstring_view content) override; // OSCSetClipboard
bool SetColorTableEntry(const size_t tableIndex, bool SetColorTableEntry(const size_t tableIndex,
const DWORD color) override; // OSCColorTable const DWORD color) override; // OSCSetColorTable
bool SetDefaultForeground(const DWORD color) override; // OSCDefaultForeground bool RequestColorTableEntry(const size_t tableIndex) override; // OSCGetColorTable
bool SetDefaultBackground(const DWORD color) override; // OSCDefaultBackground bool SetXtermColorResource(const size_t resource, const DWORD color) override; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor
bool RequestXtermColorResource(const size_t resource) override; // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor
bool AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) override; // DECAC bool AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) override; // DECAC
bool WindowManipulation(const DispatchTypes::WindowManipulationType function, bool WindowManipulation(const DispatchTypes::WindowManipulationType function,

View File

@ -69,9 +69,10 @@ public:
bool BackwardsTab(const VTInt /*numTabs*/) override { return false; } // CBT bool BackwardsTab(const VTInt /*numTabs*/) override { return false; } // CBT
bool TabClear(const DispatchTypes::TabClearType /*clearType*/) override { return false; } // TBC bool TabClear(const DispatchTypes::TabClearType /*clearType*/) override { return false; } // TBC
bool TabSet(const VTParameter /*setType*/) override { return false; } // DECST8C bool TabSet(const VTParameter /*setType*/) override { return false; } // DECST8C
bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override { return false; } // OSCColorTable bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override { return false; } // OSCSetColorTable
bool SetDefaultForeground(const DWORD /*color*/) override { return false; } // OSCDefaultForeground bool RequestColorTableEntry(const size_t /*tableIndex*/) override { return false; } // OSCGetColorTable
bool SetDefaultBackground(const DWORD /*color*/) override { return false; } // OSCDefaultBackground bool SetXtermColorResource(const size_t /*resource*/, const DWORD /*color*/) override { return false; } // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor
bool RequestXtermColorResource(const size_t /*resource*/) override { return false; } // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor
bool AssignColor(const DispatchTypes::ColorItem /*item*/, const VTInt /*fgIndex*/, const VTInt /*bgIndex*/) override { return false; } // DECAC bool AssignColor(const DispatchTypes::ColorItem /*item*/, const VTInt /*fgIndex*/, const VTInt /*bgIndex*/) override { return false; } // DECAC
bool EraseInDisplay(const DispatchTypes::EraseType /* eraseType*/) override { return false; } // ED bool EraseInDisplay(const DispatchTypes::EraseType /* eraseType*/) override { return false; } // ED
@ -121,7 +122,6 @@ public:
bool ScreenAlignmentPattern() override { return false; } // DECALN bool ScreenAlignmentPattern() override { return false; } // DECALN
bool SetCursorStyle(const DispatchTypes::CursorStyle /*cursorStyle*/) override { return false; } // DECSCUSR bool SetCursorStyle(const DispatchTypes::CursorStyle /*cursorStyle*/) override { return false; } // DECSCUSR
bool SetCursorColor(const COLORREF /*color*/) override { return false; } // OSCSetCursorColor, OSCResetCursorColor
bool SetClipboard(wil::zwstring_view /*content*/) override { return false; } // OscSetClipboard bool SetClipboard(wil::zwstring_view /*content*/) override { return false; } // OscSetClipboard

View File

@ -2398,6 +2398,232 @@ public:
_testGetSet->ValidateInputEvent(expectedResponse.c_str()); _testGetSet->ValidateInputEvent(expectedResponse.c_str());
} }
TEST_METHOD(Osc4ColorPaletteReportTests)
{
_testGetSet->PrepData();
// The colors below use the same VT525 colors as the other color table report tests.
auto& renderSettings = _testGetSet->_renderer._renderSettings;
renderSettings.SetColorTableEntry(0, RGB(0, 0, 0));
renderSettings.SetColorTableEntry(1, RGB(204, 36, 36));
renderSettings.SetColorTableEntry(2, RGB(51, 204, 51));
renderSettings.SetColorTableEntry(3, RGB(204, 204, 51));
renderSettings.SetColorTableEntry(4, RGB(51, 51, 204));
renderSettings.SetColorTableEntry(5, RGB(204, 51, 204));
renderSettings.SetColorTableEntry(6, RGB(51, 204, 204));
renderSettings.SetColorTableEntry(7, RGB(120, 120, 120));
renderSettings.SetColorTableEntry(8, RGB(69, 69, 69));
renderSettings.SetColorTableEntry(9, RGB(255, 0, 0));
renderSettings.SetColorTableEntry(10, RGB(0, 255, 0));
renderSettings.SetColorTableEntry(11, RGB(255, 255, 0));
renderSettings.SetColorTableEntry(12, RGB(0, 0, 255));
renderSettings.SetColorTableEntry(13, RGB(255, 0, 255));
renderSettings.SetColorTableEntry(14, RGB(0, 255, 255));
renderSettings.SetColorTableEntry(15, RGB(255, 255, 255));
for (size_t i = 16; i < TextColor::TABLE_SIZE; i++)
{
renderSettings.SetColorTableEntry(i, RGB(0, 0, 0));
}
// Dynamic color reports begin with an OSC, parameter 4, another parameter for the index, and end with ST.
const auto OSC = L"\033]";
const auto ST = L"\033\\";
_pDispatch->RequestColorTableEntry(0);
std::wstring expectedResponse = OSC;
expectedResponse += L"4;0;rgb:0000/0000/0000";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(1);
expectedResponse = OSC;
expectedResponse += L"4;1;rgb:cccc/2424/2424";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(2);
expectedResponse = OSC;
expectedResponse += L"4;2;rgb:3333/cccc/3333";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(3);
expectedResponse = OSC;
expectedResponse += L"4;3;rgb:cccc/cccc/3333";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(4);
expectedResponse = OSC;
expectedResponse += L"4;4;rgb:3333/3333/cccc";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(5);
expectedResponse = OSC;
expectedResponse += L"4;5;rgb:cccc/3333/cccc";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(6);
expectedResponse = OSC;
expectedResponse += L"4;6;rgb:3333/cccc/cccc";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(7);
expectedResponse = OSC;
expectedResponse += L"4;7;rgb:7878/7878/7878";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(8);
expectedResponse = OSC;
expectedResponse += L"4;8;rgb:4545/4545/4545";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(9);
expectedResponse = OSC;
expectedResponse += L"4;9;rgb:ffff/0000/0000";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(10);
expectedResponse = OSC;
expectedResponse += L"4;10;rgb:0000/ffff/0000";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(11);
expectedResponse = OSC;
expectedResponse += L"4;11;rgb:ffff/ffff/0000";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(12);
expectedResponse = OSC;
expectedResponse += L"4;12;rgb:0000/0000/ffff";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(13);
expectedResponse = OSC;
expectedResponse += L"4;13;rgb:ffff/0000/ffff";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(14);
expectedResponse = OSC;
expectedResponse += L"4;14;rgb:0000/ffff/ffff";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
_pDispatch->RequestColorTableEntry(15);
expectedResponse = OSC;
expectedResponse += L"4;15;rgb:ffff/ffff/ffff";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
}
TEST_METHOD(XtermColorResourceReportTests)
{
_testGetSet->PrepData();
// The colors below use the same VT525 colors as the other color table report tests.
auto& renderSettings = _testGetSet->_renderer._renderSettings;
renderSettings.SetColorTableEntry(0, RGB(0, 0, 0));
renderSettings.SetColorTableEntry(1, RGB(204, 36, 36));
renderSettings.SetColorTableEntry(2, RGB(51, 204, 51));
renderSettings.SetColorTableEntry(3, RGB(204, 204, 51));
renderSettings.SetColorTableEntry(4, RGB(51, 51, 204));
renderSettings.SetColorTableEntry(5, RGB(204, 51, 204));
renderSettings.SetColorTableEntry(6, RGB(51, 204, 204));
renderSettings.SetColorTableEntry(7, RGB(120, 120, 120));
renderSettings.SetColorTableEntry(8, RGB(69, 69, 69));
renderSettings.SetColorTableEntry(9, RGB(255, 0, 0));
renderSettings.SetColorTableEntry(10, RGB(0, 255, 0));
renderSettings.SetColorTableEntry(11, RGB(255, 255, 0));
renderSettings.SetColorTableEntry(12, RGB(0, 0, 255));
renderSettings.SetColorTableEntry(13, RGB(255, 0, 255));
renderSettings.SetColorTableEntry(14, RGB(0, 255, 255));
renderSettings.SetColorTableEntry(15, RGB(255, 255, 255));
renderSettings.SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, RGB(190, 190, 190));
renderSettings.SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, RGB(12, 12, 12));
renderSettings.SetColorTableEntry(TextColor::CURSOR_COLOR, RGB(255, 0, 0));
// Dynamic color reports begin with an OSC, a parameter matching the requested value, and end with ST.
const auto OSC = L"\033]";
const auto ST = L"\033\\";
// Foreground mapped to DARK_WHITE
_pDispatch->RequestXtermColorResource(10);
std::wstring expectedResponse = OSC;
expectedResponse += L"10;rgb:7878/7878/7878";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
// Foreground mapped to independent foreground color
renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, TextColor::DEFAULT_FOREGROUND);
_pDispatch->RequestXtermColorResource(10);
expectedResponse = OSC;
expectedResponse += L"10;rgb:bebe/bebe/bebe";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
// Background mapped to DARK_BLACK
_pDispatch->RequestXtermColorResource(11);
expectedResponse = OSC;
expectedResponse += L"11;rgb:0000/0000/0000";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
// Background mapped to independent background color
renderSettings.SetColorAliasIndex(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND);
_pDispatch->RequestXtermColorResource(11);
expectedResponse = OSC;
expectedResponse += L"11;rgb:0c0c/0c0c/0c0c";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
// Foreground and Background mapped to different indices (e.g. via DECAC)
{
_testGetSet->_response.clear(); // manually clear (since we aren't issuing a call that will empty it)
auto retentionScope = _testGetSet->EnableInputRetentionInScope();
renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, TextColor::DARK_RED);
renderSettings.SetColorAliasIndex(ColorAlias::DefaultBackground, TextColor::BRIGHT_GREEN);
_pDispatch->RequestXtermColorResource(10);
_pDispatch->RequestXtermColorResource(11);
expectedResponse = OSC;
expectedResponse += L"10;rgb:cccc/2424/2424";
expectedResponse += ST;
expectedResponse += OSC;
expectedResponse += L"11;rgb:0000/ffff/0000";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
}
_pDispatch->RequestXtermColorResource(12);
expectedResponse = OSC;
expectedResponse += L"12;rgb:ffff/0000/0000";
expectedResponse += ST;
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
// Resource set to unrepresentable color
_testGetSet->_response.clear(); // manually clear (since we aren't issuing a call that will empty it)
renderSettings.SetColorTableEntry(TextColor::CURSOR_COLOR, INVALID_COLOR);
_pDispatch->RequestXtermColorResource(12);
expectedResponse = L"";
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
// Unsupported resource - no mapped color
_testGetSet->_response.clear();
_pDispatch->RequestXtermColorResource(13);
expectedResponse = L"";
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
}
TEST_METHOD(TabulationStopReportTests) TEST_METHOD(TabulationStopReportTests)
{ {
_testGetSet->PrepData(); _testGetSet->PrepData();

View File

@ -550,8 +550,19 @@ bool InputStateMachineEngine::ActionIgnore() noexcept
// - string - OSC string we've collected. NOT null terminated. // - string - OSC string we've collected. NOT null terminated.
// Return Value: // Return Value:
// - true if we handled the dispatch. // - true if we handled the dispatch.
bool InputStateMachineEngine::ActionOscDispatch(const size_t /*parameter*/, const std::wstring_view /*string*/) noexcept bool InputStateMachineEngine::ActionOscDispatch(const size_t /*parameter*/, const std::wstring_view /*string*/)
{ {
// Unlike ActionCsiDispatch, we are not checking whether the application has requested
// VT input.
// Our documentation states that VT reports generated by application requests will be
// sent regardless of the state of `ENABLE_VIRTUAL_TERMINAL_INPUT`. We can't easily do
// that for CSI reports because we may incidentally pass through non-response VT input;
// however, there should be no OSC on the input stream *except* for responses.
// It should be safe to pass all OSCs from the input stream through to the application.
if (_pfnFlushToInputQueue)
{
return _pfnFlushToInputQueue();
}
return false; return false;
} }

View File

@ -157,7 +157,7 @@ namespace Microsoft::Console::VirtualTerminal
bool ActionIgnore() noexcept override; bool ActionIgnore() noexcept override;
bool ActionOscDispatch(const size_t parameter, const std::wstring_view string) noexcept override; bool ActionOscDispatch(const size_t parameter, const std::wstring_view string) override;
bool ActionSs3Dispatch(const wchar_t wch, const VTParameters parameters) override; bool ActionSs3Dispatch(const wchar_t wch, const VTParameters parameters) override;

View File

@ -14,6 +14,8 @@
using namespace Microsoft::Console; using namespace Microsoft::Console;
using namespace Microsoft::Console::VirtualTerminal; using namespace Microsoft::Console::VirtualTerminal;
constexpr COLORREF COLOR_INQUIRY_COLOR = 0xfeffffff; // It's like INVALID_COLOR but special
// takes ownership of pDispatch // takes ownership of pDispatch
OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr<ITermDispatch> pDispatch) : OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr<ITermDispatch> pDispatch) :
_dispatch(std::move(pDispatch)), _dispatch(std::move(pDispatch)),
@ -788,8 +790,15 @@ bool OutputStateMachineEngine::ActionOscDispatch(const size_t parameter, const s
{ {
const auto tableIndex = til::at(tableIndexes, i); const auto tableIndex = til::at(tableIndexes, i);
const auto rgb = til::at(colors, i); const auto rgb = til::at(colors, i);
if (rgb == COLOR_INQUIRY_COLOR)
{
success = success && _dispatch->RequestColorTableEntry(tableIndex);
}
else
{
success = success && _dispatch->SetColorTableEntry(tableIndex, rgb); success = success && _dispatch->SetColorTableEntry(tableIndex, rgb);
} }
}
break; break;
} }
case OscActionCodes::SetForegroundColor: case OscActionCodes::SetForegroundColor:
@ -800,40 +809,18 @@ bool OutputStateMachineEngine::ActionOscDispatch(const size_t parameter, const s
success = _GetOscSetColor(string, colors); success = _GetOscSetColor(string, colors);
if (success) if (success)
{ {
auto commandIndex = parameter; auto resource = parameter;
size_t colorIndex = 0; for (auto&& color : colors)
if (commandIndex == OscActionCodes::SetForegroundColor && colors.size() > colorIndex)
{ {
const auto color = til::at(colors, colorIndex); if (color == COLOR_INQUIRY_COLOR)
if (color != INVALID_COLOR)
{ {
success = success && _dispatch->SetDefaultForeground(color); success = success && _dispatch->RequestXtermColorResource(resource);
} }
commandIndex++; else if (color != INVALID_COLOR)
colorIndex++;
}
if (commandIndex == OscActionCodes::SetBackgroundColor && colors.size() > colorIndex)
{ {
const auto color = til::at(colors, colorIndex); success = success && _dispatch->SetXtermColorResource(resource, color);
if (color != INVALID_COLOR)
{
success = success && _dispatch->SetDefaultBackground(color);
} }
commandIndex++; resource++;
colorIndex++;
}
if (commandIndex == OscActionCodes::SetCursorColor && colors.size() > colorIndex)
{
const auto color = til::at(colors, colorIndex);
if (color != INVALID_COLOR)
{
success = success && _dispatch->SetCursorColor(color);
}
commandIndex++;
colorIndex++;
} }
} }
break; break;
@ -851,7 +838,8 @@ bool OutputStateMachineEngine::ActionOscDispatch(const size_t parameter, const s
} }
case OscActionCodes::ResetCursorColor: case OscActionCodes::ResetCursorColor:
{ {
success = _dispatch->SetCursorColor(INVALID_COLOR); /* The reset codes for xterm dynamic resources are the set codes + 100 */
success = _dispatch->SetXtermColorResource(parameter - 100u, INVALID_COLOR);
break; break;
} }
case OscActionCodes::Hyperlink: case OscActionCodes::Hyperlink:
@ -938,6 +926,8 @@ bool OutputStateMachineEngine::_GetOscSetColorTable(const std::wstring_view stri
std::vector<size_t>& tableIndexes, std::vector<size_t>& tableIndexes,
std::vector<DWORD>& rgbs) const std::vector<DWORD>& rgbs) const
{ {
using namespace std::string_view_literals;
const auto parts = Utils::SplitString(string, L';'); const auto parts = Utils::SplitString(string, L';');
if (parts.size() < 2) if (parts.size() < 2)
{ {
@ -949,15 +939,25 @@ bool OutputStateMachineEngine::_GetOscSetColorTable(const std::wstring_view stri
for (size_t i = 0, j = 1; j < parts.size(); i += 2, j += 2) for (size_t i = 0, j = 1; j < parts.size(); i += 2, j += 2)
{ {
auto&& index = til::at(parts, i);
auto&& color = til::at(parts, j);
unsigned int tableIndex = 0; unsigned int tableIndex = 0;
const auto indexSuccess = Utils::StringToUint(til::at(parts, i), tableIndex); const auto indexSuccess = Utils::StringToUint(index, tableIndex);
const auto colorOptional = Utils::ColorFromXTermColor(til::at(parts, j));
if (indexSuccess && colorOptional.has_value()) if (indexSuccess)
{
if (color == L"?"sv) [[unlikely]]
{
newTableIndexes.push_back(tableIndex);
newRgbs.push_back(COLOR_INQUIRY_COLOR);
}
else if (const auto colorOptional = Utils::ColorFromXTermColor(color))
{ {
newTableIndexes.push_back(tableIndex); newTableIndexes.push_back(tableIndex);
newRgbs.push_back(colorOptional.value()); newRgbs.push_back(colorOptional.value());
} }
} }
}
tableIndexes.swap(newTableIndexes); tableIndexes.swap(newTableIndexes);
rgbs.swap(newRgbs); rgbs.swap(newRgbs);
@ -1032,6 +1032,8 @@ bool OutputStateMachineEngine::_ParseHyperlink(const std::wstring_view string,
bool OutputStateMachineEngine::_GetOscSetColor(const std::wstring_view string, bool OutputStateMachineEngine::_GetOscSetColor(const std::wstring_view string,
std::vector<DWORD>& rgbs) const std::vector<DWORD>& rgbs) const
{ {
using namespace std::string_view_literals;
const auto parts = Utils::SplitString(string, L';'); const auto parts = Utils::SplitString(string, L';');
if (parts.size() < 1) if (parts.size() < 1)
{ {
@ -1039,10 +1041,14 @@ bool OutputStateMachineEngine::_GetOscSetColor(const std::wstring_view string,
} }
std::vector<DWORD> newRgbs; std::vector<DWORD> newRgbs;
for (size_t i = 0; i < parts.size(); i++) for (auto&& part : parts)
{ {
const auto colorOptional = Utils::ColorFromXTermColor(til::at(parts, i)); if (part == L"?"sv) [[unlikely]]
if (colorOptional.has_value()) {
newRgbs.push_back(COLOR_INQUIRY_COLOR);
continue;
}
else if (const auto colorOptional = Utils::ColorFromXTermColor(part))
{ {
newRgbs.push_back(colorOptional.value()); newRgbs.push_back(colorOptional.value());
} }

View File

@ -1158,12 +1158,8 @@ public:
_numTabs{ 0 }, _numTabs{ 0 },
_tabClear{ false }, _tabClear{ false },
_tabClearTypes{}, _tabClearTypes{},
_setDefaultForeground(false), _xtermResourcesChanged{},
_defaultForegroundColor{ RGB(0, 0, 0) }, _xtermResourceValues{},
_setDefaultBackground(false),
_defaultBackgroundColor{ RGB(0, 0, 0) },
_setDefaultCursorColor(false),
_defaultCursorColor{ RGB(0, 0, 0) },
_hyperlinkMode{ false }, _hyperlinkMode{ false },
_options{ s_cMaxOptions, static_cast<DispatchTypes::GraphicsOptions>(s_uiGraphicsCleared) }, // fill with cleared option _options{ s_cMaxOptions, static_cast<DispatchTypes::GraphicsOptions>(s_uiGraphicsCleared) }, // fill with cleared option
_colorTable{}, _colorTable{},
@ -1432,24 +1428,22 @@ public:
return true; return true;
} }
bool SetDefaultForeground(const DWORD color) noexcept override bool RequestColorTableEntry(const size_t tableIndex) noexcept override
{ {
_setDefaultForeground = true; _colorTableEntriesRequested.push_back(tableIndex);
_defaultForegroundColor = color;
return true; return true;
} }
bool SetDefaultBackground(const DWORD color) noexcept override bool SetXtermColorResource(const size_t resource, const DWORD color) override
{ {
_setDefaultBackground = true; _xtermResourcesChanged.push_back(resource);
_defaultBackgroundColor = color; _xtermResourceValues.push_back(color);
return true; return true;
} }
bool SetCursorColor(const DWORD color) noexcept override bool RequestXtermColorResource(const size_t resource) override
{ {
_setDefaultCursorColor = true; _xtermResourcesRequested.push_back(resource);
_defaultCursorColor = color;
return true; return true;
} }
@ -1529,13 +1523,11 @@ public:
size_t _numTabs; size_t _numTabs;
bool _tabClear; bool _tabClear;
std::vector<DispatchTypes::TabClearType> _tabClearTypes; std::vector<DispatchTypes::TabClearType> _tabClearTypes;
bool _setDefaultForeground; std::vector<size_t> _xtermResourcesChanged;
DWORD _defaultForegroundColor; std::vector<DWORD> _xtermResourceValues;
bool _setDefaultBackground; std::vector<size_t> _xtermResourcesRequested;
DWORD _defaultBackgroundColor;
bool _setDefaultCursorColor;
DWORD _defaultCursorColor;
bool _setColorTableEntry; bool _setColorTableEntry;
std::vector<size_t> _colorTableEntriesRequested;
bool _hyperlinkMode; bool _hyperlinkMode;
std::wstring _copyContent; std::wstring _copyContent;
std::wstring _uri; std::wstring _uri;
@ -2822,105 +2814,114 @@ class StateMachineExternalTest final
// Single param // Single param
mach.ProcessString(L"\033]10;rgb:1/1/1\033\\"); mach.ProcessString(L"\033]10;rgb:1/1/1\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;rgb:12/34/56\033\\"); mach.ProcessString(L"\033]10;rgb:12/34/56\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111\033\\"); mach.ProcessString(L"\033]10;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#123456\033\\"); mach.ProcessString(L"\033]10;#123456\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;DarkOrange\033\\"); mach.ProcessString(L"\033]10;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
// Multiple params // Multiple params
mach.ProcessString(L"\033]10;#111;rgb:2/2/2\033\\"); mach.ProcessString(L"\033]10;#111;rgb:2/2/2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[1]);
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_xtermResourceValues[1]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111;DarkOrange\033\\"); mach.ProcessString(L"\033]10;#111;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[1]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[1]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111;DarkOrange;rgb:2/2/2\033\\"); mach.ProcessString(L"\033]10;#111;DarkOrange;rgb:2/2/2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(3u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[1]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[2]);
VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultCursorColor); VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[1]);
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_xtermResourceValues[2]);
pDispatch->ClearState(); pDispatch->ClearState();
// Partially valid multi-param sequences. // Partially valid multi-param sequences.
mach.ProcessString(L"\033]10;#111;\033\\"); mach.ProcessString(L"\033]10;#111;\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111;rgb:\033\\"); mach.ProcessString(L"\033]10;#111;rgb:\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111;#2\033\\"); mach.ProcessString(L"\033]10;#111;#2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor); VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;;rgb:1/1/1\033\\"); mach.ProcessString(L"\033]10;;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#1;rgb:1/1/1\033\\"); mach.ProcessString(L"\033]10;#1;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
// Invalid sequences. // Invalid sequences.
mach.ProcessString(L"\033]10;rgb:1/1/\033\\"); mach.ProcessString(L"\033]10;rgb:1/1/\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]10;#1\033\\"); mach.ProcessString(L"\033]10;#1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground); VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState(); pDispatch->ClearState();
} }
@ -2933,100 +2934,115 @@ class StateMachineExternalTest final
StateMachine mach(std::move(engine)); StateMachine mach(std::move(engine));
mach.ProcessString(L"\033]11;rgb:1/1/1\033\\"); mach.ProcessString(L"\033]11;rgb:1/1/1\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
// Single param // Single param
mach.ProcessString(L"\033]11;rgb:12/34/56\033\\"); mach.ProcessString(L"\033]11;rgb:12/34/56\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111\033\\"); mach.ProcessString(L"\033]11;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#123456\033\\"); mach.ProcessString(L"\033]11;#123456\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;DarkOrange\033\\"); mach.ProcessString(L"\033]11;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
// Multiple params // Multiple params
mach.ProcessString(L"\033]11;#111;rgb:2/2/2\033\\"); mach.ProcessString(L"\033]11;#111;rgb:2/2/2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultCursorColor); VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[1]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_xtermResourceValues[1]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;DarkOrange\033\\"); mach.ProcessString(L"\033]11;#111;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultCursorColor); VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[1]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[1]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;DarkOrange;rgb:2/2/2\033\\"); mach.ProcessString(L"\033]11;#111;DarkOrange;rgb:2/2/2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(3u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultCursorColor); VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[1]);
// The third param is out of range. VERIFY_ARE_EQUAL(13u, pDispatch->_xtermResourcesChanged[2]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[1]);
// The third param is out of range, but it's technically still in compliance to set it
pDispatch->ClearState(); pDispatch->ClearState();
// Partially valid multi-param sequences. // Partially valid multi-param sequences.
mach.ProcessString(L"\033]11;#111;\033\\"); mach.ProcessString(L"\033]11;#111;\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;rgb:\033\\"); mach.ProcessString(L"\033]11;#111;rgb:\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;#2\033\\"); mach.ProcessString(L"\033]11;#111;#2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor); VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;;rgb:1/1/1\033\\"); mach.ProcessString(L"\033]11;;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor); VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultCursorColor); VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#1;rgb:1/1/1\033\\"); mach.ProcessString(L"\033]11;#1;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor); VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultCursorColor); VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState(); pDispatch->ClearState();
// Invalid sequences. // Invalid sequences.
mach.ProcessString(L"\033]11;rgb:1/1/\033\\"); mach.ProcessString(L"\033]11;rgb:1/1/\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState(); pDispatch->ClearState();
mach.ProcessString(L"\033]11;#1\033\\"); mach.ProcessString(L"\033]11;#1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground); VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState(); pDispatch->ClearState();
} }
@ -3178,6 +3194,88 @@ class StateMachineExternalTest final
pDispatch->ClearState(); pDispatch->ClearState();
} }
TEST_METHOD(TestOscGetColorTableEntry)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\033]4;0;?;1;?;2;;3;?;4;?\033\\"); // Inquire about 0-4 skipping 2
VERIFY_ARE_EQUAL((std::vector<size_t>{ 0u, 1u, 3u, 4u }), pDispatch->_colorTableEntriesRequested);
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0;rgb:00/00/00;1;rgb:00/00/01;2;?;3;rgb:00/00/03;4;rgb:00/00/04\033\\"); // Set 0-4, except 2 (inquire)
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0, 0, 1), pDispatch->_colorTable.at(1));
VERIFY_ARE_EQUAL(RGB(0, 0, 3), pDispatch->_colorTable.at(3));
VERIFY_ARE_EQUAL(RGB(0, 0, 4), pDispatch->_colorTable.at(4));
VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesRequested.size()); // note: (1u, 2u) constructs a 1-length vector containing 2
VERIFY_ARE_EQUAL(2u, pDispatch->_colorTableEntriesRequested[0]);
pDispatch->ClearState();
pDispatch->_colorTable.at(3) = RGB(0xfe, 0xed, 0xfa);
mach.ProcessString(L"\033]4;0;rgb:f0/00/00;1;?;3\033\\"); // Set 0, inquire 1, truncated 3
VERIFY_ARE_EQUAL(RGB(0xf0, 0, 0), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0xfe, 0xed, 0xfa), pDispatch->_colorTable.at(3)); // unchanged from before ProcessString
VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesRequested.size()); // note: (1u, 2u) constructs a 1-length vector containing 2
VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesRequested[0]);
pDispatch->ClearState();
}
TEST_METHOD(TestOscXtermResourceReport)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\033]10;?\033\\");
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesRequested[0]);
pDispatch->ClearState();
// Two params, skip first
mach.ProcessString(L"\033]10;;?\033\\");
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesRequested[0]);
pDispatch->ClearState();
// Two params, set first
mach.ProcessString(L"\033]10;rgb:11/22/33;?\033\\");
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x22, 0x33), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesRequested[0]);
pDispatch->ClearState();
// Two params, set first, starting at 12
mach.ProcessString(L"\033]12;rgb:11/22/33;?\033\\");
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x22, 0x33), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(13u, pDispatch->_xtermResourcesRequested[0]);
pDispatch->ClearState();
// Request all 10
mach.ProcessString(L"\033]10;?;?;?;?;?;?;?;?;?;?\033\\");
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
std::vector<size_t> expected{ 10u, 11u, 12u, 13u, 14u, 15u, 16u, 17u, 18u, 19u };
VERIFY_ARE_EQUAL(expected, pDispatch->_xtermResourcesRequested);
pDispatch->ClearState();
// Request out of range
mach.ProcessString(L"\033]10;;?\033\\");
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesRequested[0]);
pDispatch->ClearState();
}
TEST_METHOD(TestOscSetWindowTitle) TEST_METHOD(TestOscSetWindowTitle)
{ {
BEGIN_TEST_METHOD_PROPERTIES() BEGIN_TEST_METHOD_PROPERTIES()