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
!(?:(?i)ESC)!\[
!(?:(?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"
\b_mm(?:|256|512)_\w+\b

View File

@ -76,9 +76,10 @@ public:
virtual bool BackwardsTab(const VTInt numTabs) = 0; // CBT
virtual bool TabClear(const DispatchTypes::TabClearType clearType) = 0; // TBC
virtual bool TabSet(const VTParameter setType) = 0; // DECST8C
virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCColorTable
virtual bool SetDefaultForeground(const DWORD color) = 0; // OSCDefaultForeground
virtual bool SetDefaultBackground(const DWORD color) = 0; // OSCDefaultBackground
virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCSetColorTable
virtual bool RequestColorTableEntry(const size_t tableIndex) = 0; // OSCGetColorTable
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 EraseInDisplay(const DispatchTypes::EraseType eraseType) = 0; // ED
@ -128,7 +129,6 @@ public:
virtual bool ScreenAlignmentPattern() = 0; // DECALN
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

View File

@ -18,6 +18,25 @@ using namespace Microsoft::Console::VirtualTerminal;
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 :
_api{ api },
_renderer{ renderer },
@ -3446,18 +3465,6 @@ bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
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:
// - OSC Copy to Clipboard
// Arguments:
@ -3498,28 +3505,69 @@ bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwCo
return true;
}
// Method Description:
// - 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)
bool AdaptDispatch::RequestColorTableEntry(const size_t tableIndex)
{
_renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, TextColor::DEFAULT_FOREGROUND);
return SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, dwColor);
const auto color = _renderSettings.GetColorTableEntry(tableIndex);
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:
// - Sets the default background color to a new value
// Arguments:
// - dwColor: The new RGB color value to use, as a COLORREF, format 0x00BBGGRR.
// - Sets one Xterm Color Resource such as Default Foreground, Background, Cursor
// Return Value:
// 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);
return SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, dwColor);
assert(resource >= 10);
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:

View File

@ -126,14 +126,14 @@ namespace Microsoft::Console::VirtualTerminal
bool HardReset() override; // RIS
bool ScreenAlignmentPattern() override; // DECALN
bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) override; // DECSCUSR
bool SetCursorColor(const COLORREF cursorColor) override;
bool SetClipboard(const wil::zwstring_view content) override; // OSCSetClipboard
bool SetColorTableEntry(const size_t tableIndex,
const DWORD color) override; // OSCColorTable
bool SetDefaultForeground(const DWORD color) override; // OSCDefaultForeground
bool SetDefaultBackground(const DWORD color) override; // OSCDefaultBackground
const DWORD color) override; // OSCSetColorTable
bool RequestColorTableEntry(const size_t tableIndex) override; // OSCGetColorTable
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 WindowManipulation(const DispatchTypes::WindowManipulationType function,

View File

@ -69,9 +69,10 @@ public:
bool BackwardsTab(const VTInt /*numTabs*/) override { return false; } // CBT
bool TabClear(const DispatchTypes::TabClearType /*clearType*/) override { return false; } // TBC
bool TabSet(const VTParameter /*setType*/) override { return false; } // DECST8C
bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override { return false; } // OSCColorTable
bool SetDefaultForeground(const DWORD /*color*/) override { return false; } // OSCDefaultForeground
bool SetDefaultBackground(const DWORD /*color*/) override { return false; } // OSCDefaultBackground
bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override { return false; } // OSCSetColorTable
bool RequestColorTableEntry(const size_t /*tableIndex*/) override { return false; } // OSCGetColorTable
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 EraseInDisplay(const DispatchTypes::EraseType /* eraseType*/) override { return false; } // ED
@ -121,7 +122,6 @@ public:
bool ScreenAlignmentPattern() override { return false; } // DECALN
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

View File

@ -2398,6 +2398,232 @@ public:
_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)
{
_testGetSet->PrepData();

View File

@ -550,8 +550,19 @@ bool InputStateMachineEngine::ActionIgnore() noexcept
// - string - OSC string we've collected. NOT null terminated.
// Return Value:
// - 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;
}

View File

@ -157,7 +157,7 @@ namespace Microsoft::Console::VirtualTerminal
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;

View File

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

View File

@ -1158,12 +1158,8 @@ public:
_numTabs{ 0 },
_tabClear{ false },
_tabClearTypes{},
_setDefaultForeground(false),
_defaultForegroundColor{ RGB(0, 0, 0) },
_setDefaultBackground(false),
_defaultBackgroundColor{ RGB(0, 0, 0) },
_setDefaultCursorColor(false),
_defaultCursorColor{ RGB(0, 0, 0) },
_xtermResourcesChanged{},
_xtermResourceValues{},
_hyperlinkMode{ false },
_options{ s_cMaxOptions, static_cast<DispatchTypes::GraphicsOptions>(s_uiGraphicsCleared) }, // fill with cleared option
_colorTable{},
@ -1432,24 +1428,22 @@ public:
return true;
}
bool SetDefaultForeground(const DWORD color) noexcept override
bool RequestColorTableEntry(const size_t tableIndex) noexcept override
{
_setDefaultForeground = true;
_defaultForegroundColor = color;
_colorTableEntriesRequested.push_back(tableIndex);
return true;
}
bool SetDefaultBackground(const DWORD color) noexcept override
bool SetXtermColorResource(const size_t resource, const DWORD color) override
{
_setDefaultBackground = true;
_defaultBackgroundColor = color;
_xtermResourcesChanged.push_back(resource);
_xtermResourceValues.push_back(color);
return true;
}
bool SetCursorColor(const DWORD color) noexcept override
bool RequestXtermColorResource(const size_t resource) override
{
_setDefaultCursorColor = true;
_defaultCursorColor = color;
_xtermResourcesRequested.push_back(resource);
return true;
}
@ -1529,13 +1523,11 @@ public:
size_t _numTabs;
bool _tabClear;
std::vector<DispatchTypes::TabClearType> _tabClearTypes;
bool _setDefaultForeground;
DWORD _defaultForegroundColor;
bool _setDefaultBackground;
DWORD _defaultBackgroundColor;
bool _setDefaultCursorColor;
DWORD _defaultCursorColor;
std::vector<size_t> _xtermResourcesChanged;
std::vector<DWORD> _xtermResourceValues;
std::vector<size_t> _xtermResourcesRequested;
bool _setColorTableEntry;
std::vector<size_t> _colorTableEntriesRequested;
bool _hyperlinkMode;
std::wstring _copyContent;
std::wstring _uri;
@ -2822,105 +2814,114 @@ class StateMachineExternalTest final
// 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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;rgb:12/34/56\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#123456\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultForegroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
// 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);
VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(11u, 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();
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);
VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(11u, 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();
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);
VERIFY_ARE_EQUAL(3u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[1]);
VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[2]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[1]);
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_xtermResourceValues[2]);
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
// Invalid sequences.
mach.ProcessString(L"\033]10;rgb:1/1/\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState();
}
@ -2933,100 +2934,115 @@ class StateMachineExternalTest final
StateMachine mach(std::move(engine));
mach.ProcessString(L"\033]11;rgb:1/1/1\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#123456\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
// 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);
VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
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();
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);
VERIFY_ARE_EQUAL(2u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
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();
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.
VERIFY_ARE_EQUAL(3u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[1]);
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();
// 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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;rgb:\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;#2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
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);
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(12u, pDispatch->_xtermResourcesChanged[0]);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_xtermResourceValues[0]);
pDispatch->ClearState();
// Invalid sequences.
mach.ProcessString(L"\033]11;rgb:1/1/\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
pDispatch->ClearState();
}
@ -3178,6 +3194,88 @@ class StateMachineExternalTest final
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)
{
BEGIN_TEST_METHOD_PROPERTIES()