Skip to content

Commit

Permalink
Add support for querying Xterm dynamic colors and palette (#17729)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
DHowett authored Aug 21, 2024
1 parent ce92b18 commit 3b4ee83
Show file tree
Hide file tree
Showing 10 changed files with 562 additions and 172 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/patterns/patterns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
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
Expand Down Expand Up @@ -128,7 +129,6 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
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

Expand Down
96 changes: 72 additions & 24 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -3498,28 +3505,69 @@ bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwCo
return true;
}

bool AdaptDispatch::RequestColorTableEntry(const size_t tableIndex)
{
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 foreground 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::SetDefaultForeground(const DWORD dwColor)
bool AdaptDispatch::SetXtermColorResource(const size_t resource, const DWORD color)
{
_renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, TextColor::DEFAULT_FOREGROUND);
return SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, 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:
// - Sets the default background color to a new value
// Arguments:
// - dwColor: The new RGB color value to use, as a COLORREF, format 0x00BBGGRR.
// - Reports the value of one Xterm Color Resource, if it is set.
// Return Value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::SetDefaultBackground(const DWORD dwColor)
bool AdaptDispatch::RequestXtermColorResource(const size_t resource)
{
_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)
{
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:
Expand Down
8 changes: 4 additions & 4 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
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
Expand Down Expand Up @@ -121,7 +122,6 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
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

Expand Down
Loading

0 comments on commit 3b4ee83

Please sign in to comment.