Skip to content

Add support for OSC 104, 110, 111, 112 and 117 (resets) #18767

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 10, 2025
19 changes: 19 additions & 0 deletions src/renderer/base/RenderSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,20 @@ COLORREF RenderSettings::GetColorTableEntry(const size_t tableIndex) const
return _colorTable.at(tableIndex);
}

// Routine Description:
// - Restores all of the xterm-addressable colors to the ones saved in SaveDefaultSettings.
void RenderSettings::RestoreDefaultIndexed256ColorTable()
{
std::copy_n(_defaultColorTable.begin(), 256, _colorTable.begin());
}

// Routine Description:
// - Restores a color table entry to the value saved in SaveDefaultSettings.
void RenderSettings::RestoreDefaultColorTableEntry(const size_t tableIndex)
{
_colorTable.at(tableIndex) = _defaultColorTable.at(tableIndex);
}

// Routine Description:
// - Sets the position in the color table for the given color alias and updates the color.
// Arguments:
Expand Down Expand Up @@ -159,6 +173,11 @@ size_t RenderSettings::GetColorAliasIndex(const ColorAlias alias) const noexcept
return gsl::at(_colorAliasIndices, static_cast<size_t>(alias));
}

void RenderSettings::RestoreDefaultColorAliasIndex(const ColorAlias alias) noexcept
{
gsl::at(_colorAliasIndices, static_cast<size_t>(alias)) = gsl::at(_defaultColorAliasIndices, static_cast<size_t>(alias));
}

// Routine Description:
// - Calculates the RGB colors of a given text attribute, using the current
// color table configuration and active render settings.
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/inc/RenderSettings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ namespace Microsoft::Console::Render
void ResetColorTable() noexcept;
void SetColorTableEntry(const size_t tableIndex, const COLORREF color);
COLORREF GetColorTableEntry(const size_t tableIndex) const;
void RestoreDefaultIndexed256ColorTable();
void RestoreDefaultColorTableEntry(const size_t tableIndex);
void SetColorAlias(const ColorAlias alias, const size_t tableIndex, const COLORREF color);
COLORREF GetColorAlias(const ColorAlias alias) const;
void SetColorAliasIndex(const ColorAlias alias, const size_t tableIndex) noexcept;
size_t GetColorAliasIndex(const ColorAlias alias) const noexcept;
void RestoreDefaultColorAliasIndex(const ColorAlias alias) noexcept;
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept;
std::pair<COLORREF, COLORREF> GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept;
COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept;
Expand Down
5 changes: 4 additions & 1 deletion src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
virtual void TabSet(const VTParameter setType) = 0; // DECST8C
virtual void SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCSetColorTable
virtual void RequestColorTableEntry(const size_t tableIndex) = 0; // OSCGetColorTable
virtual void SetXtermColorResource(const size_t resource, const DWORD color) = 0; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor
virtual void ResetColorTable() = 0; // OSCResetColorTable
virtual void ResetColorTableEntry(const size_t tableIndex) = 0; // OSCResetColorTable
virtual void SetXtermColorResource(const size_t resource, const DWORD color) = 0; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor
virtual void RequestXtermColorResource(const size_t resource) = 0; // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor
virtual void ResetXtermColorResource(const size_t resource) = 0; // OSCResetForegroundColor, OSCResetBackgroundColor, OSCResetCursorColor, OSCResetHighlightColor
virtual void AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) = 0; // DECAC

virtual void EraseInDisplay(const DispatchTypes::EraseType eraseType) = 0; // ED
Expand Down
53 changes: 53 additions & 0 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3292,6 +3292,40 @@ void AdaptDispatch::RequestColorTableEntry(const size_t tableIndex)
}
}

void AdaptDispatch::ResetColorTable()
{
_renderSettings.RestoreDefaultIndexed256ColorTable();
if (_renderer)
{
// This is pessimistic because it's unlikely that the frame or background changed,
// but let's tell the renderer that both changed anyway.
_renderer->TriggerRedrawAll(true, true);
}
}

// Method Description:
// - Restores a single color table entry to its default user-specified value
// Arguments:
// - tableIndex: The VT color table index
void AdaptDispatch::ResetColorTableEntry(const size_t tableIndex)
{
_renderSettings.RestoreDefaultColorTableEntry(tableIndex);

if (_renderer)
{
// If we're updating the background color, we need to let the renderer
// know, since it may want to repaint the window background to match.
const auto backgroundIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultBackground);
const auto backgroundChanged = (tableIndex == backgroundIndex);

// Similarly for the frame color, the tab may need to be repainted.
const auto frameIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameBackground);
const auto frameChanged = (tableIndex == frameIndex);

_renderer->TriggerRedrawAll(backgroundChanged, frameChanged);
}
}

// Method Description:
// - Sets one Xterm Color Resource such as Default Foreground, Background, Cursor
void AdaptDispatch::SetXtermColorResource(const size_t resource, const DWORD color)
Expand Down Expand Up @@ -3338,6 +3372,25 @@ void AdaptDispatch::RequestXtermColorResource(const size_t resource)
}
}

// Method Description:
// - Restores to the original user-provided value one Xterm Color Resource such as Default Foreground, Background, Cursor
void AdaptDispatch::ResetXtermColorResource(const size_t resource)
{
assert(resource >= 10);
const auto mappingIndex = resource - 10;
const auto& oscMapping = XtermResourceColorTableMappings.at(mappingIndex);
if (oscMapping.ColorTableIndex > 0)
{
if (oscMapping.AliasIndex >= 0)
{
// If this color reset applies to an aliased color, point the alias back at the original color
_renderSettings.RestoreDefaultColorAliasIndex(static_cast<ColorAlias>(oscMapping.AliasIndex));
}

ResetColorTableEntry(oscMapping.ColorTableIndex);
}
}

// Method Description:
// DECAC - Assigns the foreground and background color indexes that should be
// used for a given aspect of the user interface.
Expand Down
5 changes: 4 additions & 1 deletion src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@ namespace Microsoft::Console::VirtualTerminal
void SetColorTableEntry(const size_t tableIndex,
const DWORD color) override; // OSCSetColorTable
void RequestColorTableEntry(const size_t tableIndex) override; // OSCGetColorTable
void SetXtermColorResource(const size_t resource, const DWORD color) override; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor
void ResetColorTable() override; // OSCResetColorTable
void ResetColorTableEntry(const size_t tableIndex) override; // OSCResetColorTable
void SetXtermColorResource(const size_t resource, const DWORD color) override; // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor
void RequestXtermColorResource(const size_t resource) override; // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor
void ResetXtermColorResource(const size_t resource) override; // OSCResetForegroundColor, OSCResetBackgroundColor, OSCResetCursorColor, OSCResetHighlightColor
void AssignColor(const DispatchTypes::ColorItem item, const VTInt fgIndex, const VTInt bgIndex) override; // DECAC

void WindowManipulation(const DispatchTypes::WindowManipulationType function,
Expand Down
5 changes: 4 additions & 1 deletion src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
void TabSet(const VTParameter /*setType*/) override {} // DECST8C
void SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override {} // OSCSetColorTable
void RequestColorTableEntry(const size_t /*tableIndex*/) override {} // OSCGetColorTable
void SetXtermColorResource(const size_t /*resource*/, const DWORD /*color*/) override {} // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor, OSCResetCursorColor
void ResetColorTable() override {} // OSCResetColorTable
void ResetColorTableEntry(const size_t /*tableIndex*/) override {} // OSCResetColorTable
void SetXtermColorResource(const size_t /*resource*/, const DWORD /*color*/) override {} // OSCSetDefaultForeground, OSCSetDefaultBackground, OSCSetCursorColor
void RequestXtermColorResource(const size_t /*resource*/) override {} // OSCGetDefaultForeground, OSCGetDefaultBackground, OSCGetCursorColor
void ResetXtermColorResource(const size_t /*resource*/) override {} // OSCResetForegroundColor, OSCResetBackgroundColor, OSCResetCursorColor, OSCResetHighlightColor
void AssignColor(const DispatchTypes::ColorItem /*item*/, const VTInt /*fgIndex*/, const VTInt /*bgIndex*/) override {} // DECAC

void EraseInDisplay(const DispatchTypes::EraseType /* eraseType*/) override {} // ED
Expand Down
34 changes: 32 additions & 2 deletions src/terminal/parser/OutputStateMachineEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -810,10 +810,40 @@ bool OutputStateMachineEngine::ActionOscDispatch(const size_t parameter, const s
}
break;
}
case OscActionCodes::ResetColor:
{
if (string.empty())
{
_dispatch->ResetColorTable();
}
else
{
for (auto&& c : til::split_iterator{ string, L';' })
{
if (const auto index{ til::parse_unsigned<size_t>(c, 10) }; index)
{
_dispatch->ResetColorTableEntry(*index);
}
else
{
// NOTE: xterm stops at the first unparseable index whereas VTE keeps going.
break;
}
}
}
break;
}
case OscActionCodes::ResetForegroundColor:
case OscActionCodes::ResetBackgroundColor:
case OscActionCodes::ResetCursorColor:
case OscActionCodes::ResetHighlightColor:
{
// The reset codes for xterm dynamic resources are the set codes + 100
_dispatch->SetXtermColorResource(parameter - 100u, INVALID_COLOR);
// NOTE: xterm ignores the request if there's any parameters whereas VTE resets the provided index and ignores the rest
if (string.empty())
{
// The reset codes for xterm dynamic resources are the set codes + 100
_dispatch->ResetXtermColorResource(parameter - 100u);
}
break;
}
case OscActionCodes::Hyperlink:
Expand Down
6 changes: 4 additions & 2 deletions src/terminal/parser/OutputStateMachineEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,11 @@ namespace Microsoft::Console::VirtualTerminal
SetHighlightColor = 17,
DECSWT_SetWindowTitle = 21,
SetClipboard = 52,
ResetForegroundColor = 110, // Not implemented
ResetBackgroundColor = 111, // Not implemented
ResetColor = 104,
ResetForegroundColor = 110,
ResetBackgroundColor = 111,
ResetCursorColor = 112,
ResetHighlightColor = 117,
FinalTermAction = 133,
VsCodeAction = 633,
ITerm2Action = 1337,
Expand Down
94 changes: 93 additions & 1 deletion src/terminal/parser/ut_parser/OutputEngineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1163,7 +1163,8 @@ class StatefulDispatch final : public TermDispatch
_hyperlinkMode{ false },
_options{ s_cMaxOptions, static_cast<DispatchTypes::GraphicsOptions>(s_uiGraphicsCleared) }, // fill with cleared option
_colorTable{},
_setColorTableEntry{ false }
_setColorTableEntry{ false },
_resetAllColors{ false }
{
}

Expand Down Expand Up @@ -1390,6 +1391,16 @@ class StatefulDispatch final : public TermDispatch
_colorTableEntriesRequested.push_back(tableIndex);
}

void ResetColorTable() noexcept override
{
_resetAllColors = true;
}

void ResetColorTableEntry(const size_t tableIndex) noexcept override
{
_colorTableEntriesReset.push_back(tableIndex);
}

void SetXtermColorResource(const size_t resource, const DWORD color) override
{
_xtermResourcesChanged.push_back(resource);
Expand All @@ -1401,6 +1412,11 @@ class StatefulDispatch final : public TermDispatch
_xtermResourcesRequested.push_back(resource);
}

void ResetXtermColorResource(const size_t resource) override
{
_xtermResourcesReset.push_back(resource);
}

void SetClipboard(wil::zwstring_view content) noexcept override
{
_copyContent = content;
Expand Down Expand Up @@ -1476,8 +1492,11 @@ class StatefulDispatch final : public TermDispatch
std::vector<size_t> _xtermResourcesChanged;
std::vector<DWORD> _xtermResourceValues;
std::vector<size_t> _xtermResourcesRequested;
std::vector<size_t> _xtermResourcesReset;
bool _setColorTableEntry;
std::vector<size_t> _colorTableEntriesRequested;
bool _resetAllColors;
std::vector<size_t> _colorTableEntriesReset;
bool _hyperlinkMode;
std::wstring _copyContent;
std::wstring _uri;
Expand Down Expand Up @@ -3221,6 +3240,79 @@ class StateMachineExternalTest final
pDispatch->ClearState();
}

TEST_METHOD(TestOscXtermResourceReset)
{
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]110\033\\");
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesReset.size());
VERIFY_ARE_EQUAL(10u, pDispatch->_xtermResourcesReset[0]);
pDispatch->ClearState();

mach.ProcessString(L"\033]111;\033\\"); // dangling ;
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(1u, pDispatch->_xtermResourcesReset.size());
VERIFY_ARE_EQUAL(11u, pDispatch->_xtermResourcesReset[0]);
pDispatch->ClearState();

mach.ProcessString(L"\033]111;110\033\\");
// NOTE: this is xterm behavior - ignore the entire sequence if any params exist
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesChanged.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesRequested.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size());
pDispatch->ClearState();
}

TEST_METHOD(TestOscColorTableReset)
{
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]104\033\\");
VERIFY_IS_TRUE(pDispatch->_resetAllColors);
VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesReset.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size());
pDispatch->ClearState();

mach.ProcessString(L"\033]104;1;3;5;7;9\033\\");
VERIFY_IS_FALSE(pDispatch->_resetAllColors);
VERIFY_ARE_EQUAL(5u, pDispatch->_colorTableEntriesReset.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size());
VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesReset[0]);
VERIFY_ARE_EQUAL(3u, pDispatch->_colorTableEntriesReset[1]);
VERIFY_ARE_EQUAL(5u, pDispatch->_colorTableEntriesReset[2]);
VERIFY_ARE_EQUAL(7u, pDispatch->_colorTableEntriesReset[3]);
VERIFY_ARE_EQUAL(9u, pDispatch->_colorTableEntriesReset[4]);
pDispatch->ClearState();

// NOTE: xterm behavior - stop after first failed parse
mach.ProcessString(L"\033]104;1;a;3\033\\");
VERIFY_IS_FALSE(pDispatch->_resetAllColors);
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesReset.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size());
VERIFY_ARE_EQUAL(1u, pDispatch->_colorTableEntriesReset[0]);
pDispatch->ClearState();

mach.ProcessString(L"\033]104;;;\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesReset.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_colorTableEntriesRequested.size());
VERIFY_ARE_EQUAL(0u, pDispatch->_xtermResourcesReset.size());
pDispatch->ClearState();
}

TEST_METHOD(TestOscSetWindowTitle)
{
BEGIN_TEST_METHOD_PROPERTIES()
Expand Down
Loading