Skip to content
29 changes: 29 additions & 0 deletions src/host/getset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,35 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
return STATUS_SUCCESS;
}

// Routine Description:
// - A private API call for changing the screen mode between normal and reverse.
// When in reverse screen mode, the background and foreground colors are switched.
// Parameters:
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
// Return value:
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate error code.
[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode)
{
try
{
Globals& g = ServiceLocator::LocateGlobals();
CONSOLE_INFORMATION& gci = g.getConsoleInformation();

gci.SetScreenReversed(reverseMode);

if (g.pRender)
Comment thread
j4james marked this conversation as resolved.
Resolved
{
g.pRender->TriggerRedrawAll();
}

return STATUS_SUCCESS;
}
catch (...)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
}

// Routine Description:
// - A private API call for making the cursor visible or not. Does not modify
// blinking state.
Expand Down
2 changes: 2 additions & 0 deletions src/host/getset.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ void DoSrvPrivateSetDefaultAttributes(SCREEN_INFORMATION& screenInfo, const bool
[[nodiscard]] NTSTATUS DoSrvPrivateSetCursorKeysMode(_In_ bool fApplicationMode);
[[nodiscard]] NTSTATUS DoSrvPrivateSetKeypadMode(_In_ bool fApplicationMode);

[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode);

void DoSrvPrivateShowCursor(SCREEN_INFORMATION& screenInfo, const bool show) noexcept;
void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool fEnable);

Expand Down
13 changes: 13 additions & 0 deletions src/host/outputStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,19 @@ bool ConhostInternalGetSet::PrivateSetKeypadMode(const bool fApplicationMode)
return NT_SUCCESS(DoSrvPrivateSetKeypadMode(fApplicationMode));
}

// Routine Description:
// - Connects the PrivateSetScreenMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateSetScreenMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on our public API surface.
// Arguments:
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
// Return Value:
// - true if successful (see DoSrvPrivateSetScreenMode). false otherwise.
bool ConhostInternalGetSet::PrivateSetScreenMode(const bool reverseMode)
{
return NT_SUCCESS(DoSrvPrivateSetScreenMode(reverseMode));
}

// Routine Description:
// - Connects the PrivateShowCursor call directly into our Driver Message servicing call inside Conhost.exe
// PrivateShowCursor is an internal-only "API" call that the vt commands can execute,
Expand Down
2 changes: 2 additions & 0 deletions src/host/outputStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::
bool PrivateSetCursorKeysMode(const bool applicationMode) override;
bool PrivateSetKeypadMode(const bool applicationMode) override;

bool PrivateSetScreenMode(const bool reverseMode) override;

bool PrivateShowCursor(const bool show) noexcept override;
bool PrivateAllowCursorBlinking(const bool enable) override;

Expand Down
28 changes: 26 additions & 2 deletions src/host/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Settings::Settings() :
_fUseWindowSizePixels(false),
_fAutoReturnOnNewline(true), // the historic Windows behavior defaults this to on.
_fRenderGridWorldwide(false), // historically grid lines were only rendered in DBCS codepages, so this is false by default unless otherwise specified.
_fScreenReversed(false),
// window size pixels initialized below
_fInterceptCopyPaste(0),
_DefaultForeground(INVALID_COLOR),
Expand Down Expand Up @@ -394,6 +395,15 @@ void Settings::SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed
}
}

bool Settings::IsScreenReversed() const
{
return _fScreenReversed;
}
void Settings::SetScreenReversed(const bool fScreenReversed)
{
_fScreenReversed = fScreenReversed;
}

bool Settings::GetFilterOnPaste() const
{
return _fFilterOnPaste;
Expand Down Expand Up @@ -942,7 +952,14 @@ COLORREF Settings::CalculateDefaultBackground() const noexcept
COLORREF Settings::LookupForegroundColor(const TextAttribute& attr) const noexcept
{
const auto tableView = std::basic_string_view<COLORREF>(&GetColorTable()[0], GetColorTableSize());
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
if (_fScreenReversed)
{
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
else
{
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
}

// Method Description:
Expand All @@ -955,7 +972,14 @@ COLORREF Settings::LookupForegroundColor(const TextAttribute& attr) const noexce
COLORREF Settings::LookupBackgroundColor(const TextAttribute& attr) const noexcept
{
const auto tableView = std::basic_string_view<COLORREF>(&GetColorTable()[0], GetColorTableSize());
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
if (_fScreenReversed)
{
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
else
{
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
}

bool Settings::GetCopyColor() const noexcept
Expand Down
4 changes: 4 additions & 0 deletions src/host/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class Settings
bool IsGridRenderingAllowedWorldwide() const;
void SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed);

bool IsScreenReversed() const;
void SetScreenReversed(const bool fScreenReversed);

bool GetFilterOnPaste() const;
void SetFilterOnPaste(const bool fFilterOnPaste);

Expand Down Expand Up @@ -231,6 +234,7 @@ class Settings
DWORD _dwVirtTermLevel;
bool _fAutoReturnOnNewline;
bool _fRenderGridWorldwide;
bool _fScreenReversed;
bool _fUseDx;
bool _fCopyColor;

Expand Down
29 changes: 29 additions & 0 deletions src/host/ut_host/ScreenBufferTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class ScreenBufferTests

TEST_METHOD(ScrollLines256Colors);

TEST_METHOD(SetScreenMode);
TEST_METHOD(SetOriginMode);

TEST_METHOD(HardResetBuffer);
Expand Down Expand Up @@ -4409,6 +4410,34 @@ void ScreenBufferTests::ScrollLines256Colors()
}
}

void ScreenBufferTests::SetScreenMode()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& stateMachine = si.GetStateMachine();

const auto rgbForeground = RGB(12, 34, 56);
const auto rgbBackground = RGB(78, 90, 12);
const auto testAttr = TextAttribute{ rgbForeground, rgbBackground };

Log::Comment(L"By default the screen mode is normal.");
VERIFY_IS_FALSE(gci.IsScreenReversed());
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupForegroundColor(testAttr));
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupBackgroundColor(testAttr));

Log::Comment(L"When DECSCNM is set, background and foreground colors are switched.");
stateMachine.ProcessString(L"\x1B[?5h");
VERIFY_IS_TRUE(gci.IsScreenReversed());
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupForegroundColor(testAttr));
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupBackgroundColor(testAttr));

Log::Comment(L"When DECSCNM is reset, the colors are normal again.");
stateMachine.ProcessString(L"\x1B[?5l");
VERIFY_IS_FALSE(gci.IsScreenReversed());
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupForegroundColor(testAttr));
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupBackgroundColor(testAttr));
}

void ScreenBufferTests::SetOriginMode()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/DispatchTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
{
DECCKM_CursorKeysMode = 1,
DECCOLM_SetNumberOfColumns = 3,
DECSCNM_ScreenMode = 5,
DECOM_OriginMode = 6,
ATT610_StartCursorBlink = 12,
DECTCEM_TextCursorEnableMode = 25,
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
virtual bool SetCursorKeysMode(const bool applicationMode) = 0; // DECCKM
virtual bool SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
virtual bool EnableCursorBlinking(const bool enable) = 0; // ATT610
virtual bool SetScreenMode(const bool reverseMode) = 0; //DECSCNM
virtual bool SetOriginMode(const bool relativeMode) = 0; // DECOM
virtual bool SetTopBottomScrollingMargins(const size_t topMargin, const size_t bottomMargin) = 0; // DECSTBM
virtual bool ReverseLineFeed() = 0; // RI
Expand Down
21 changes: 21 additions & 0 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,9 @@ bool AdaptDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModePar
case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns:
success = _DoDECCOLMHelper(enable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns);
break;
case DispatchTypes::PrivateModeParams::DECSCNM_ScreenMode:
success = SetScreenMode(enable);
break;
case DispatchTypes::PrivateModeParams::DECOM_OriginMode:
// The cursor is also moved to the new home position when the origin mode is set or reset.
success = SetOriginMode(enable) && CursorPosition(1, 1);
Expand Down Expand Up @@ -1212,6 +1215,18 @@ bool AdaptDispatch::DeleteLine(const size_t distance)
return _pConApi->DeleteLines(distance);
}

// Routine Description:
// - DECSCNM - Sets the screen mode to either normal or reverse.
// When in reverse screen mode, the background and foreground colors are switched.
// Arguments:
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetScreenMode(const bool reverseMode)
{
return _pConApi->PrivateSetScreenMode(reverseMode);
}

// Routine Description:
// - DECOM - Sets the cursor addressing origin mode to relative or absolute.
// When relative, line numbers start at top margin of the user-defined scrolling region.
Expand Down Expand Up @@ -1562,6 +1577,12 @@ bool AdaptDispatch::HardReset()
success = _EraseScrollback();
}

// Set the DECSCNM screen mode back to normal.
if (success)
{
success = SetScreenMode(false);
}

// Cursor to 1,1 - the Soft Reset guarantees this is absolute
if (success)
{
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ namespace Microsoft::Console::VirtualTerminal
bool SetCursorKeysMode(const bool applicationMode) override; // DECCKM
bool SetKeypadMode(const bool applicationMode) override; // DECKPAM, DECKPNM
bool EnableCursorBlinking(const bool enable) override; // ATT610
bool SetScreenMode(const bool reverseMode) override; //DECSCNM
bool SetOriginMode(const bool relativeMode) noexcept override; // DECOM
bool SetTopBottomScrollingMargins(const size_t topMargin,
const size_t bottomMargin) override; // DECSTBM
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/conGetSet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateSetCursorKeysMode(const bool applicationMode) = 0;
virtual bool PrivateSetKeypadMode(const bool applicationMode) = 0;

virtual bool PrivateSetScreenMode(const bool reverseMode) = 0;

virtual bool PrivateShowCursor(const bool show) = 0;
virtual bool PrivateAllowCursorBlinking(const bool enable) = 0;

Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
bool SetCursorKeysMode(const bool /*applicationMode*/) noexcept override { return false; } // DECCKM
bool SetKeypadMode(const bool /*applicationMode*/) noexcept override { return false; } // DECKPAM, DECKPNM
bool EnableCursorBlinking(const bool /*enable*/) noexcept override { return false; } // ATT610
bool SetScreenMode(const bool /*reverseMode*/) noexcept override { return false; } //DECSCNM
bool SetOriginMode(const bool /*relativeMode*/) noexcept override { return false; }; // DECOM
bool SetTopBottomScrollingMargins(const size_t /*topMargin*/, const size_t /*bottomMargin*/) noexcept override { return false; } // DECSTBM
bool ReverseLineFeed() noexcept override { return false; } // RI
Expand Down
7 changes: 7 additions & 0 deletions src/terminal/adapter/ut_adapter/adapterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,13 @@ class TestGetSet final : public ConGetSet
return _privateSetKeypadModeResult;
}

bool PrivateSetScreenMode(const bool /*reverseMode*/) override
{
Log::Comment(L"PrivateSetScreenMode MOCK called...");

return true;
}

bool PrivateShowCursor(const bool show) override
{
Log::Comment(L"PrivateShowCursor MOCK called...");
Expand Down
30 changes: 30 additions & 0 deletions src/terminal/parser/ut_parser/OutputEngineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ class StatefulDispatch final : public TermDispatch
_isAltBuffer{ false },
_cursorKeysMode{ false },
_cursorBlinking{ true },
_isScreenModeReversed{ false },
_isOriginModeRelative{ false },
_isDECCOLMAllowed{ false },
_windowWidth{ 80 },
Expand Down Expand Up @@ -835,6 +836,9 @@ class StatefulDispatch final : public TermDispatch
case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns:
fSuccess = SetColumns(static_cast<size_t>(fEnable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns));
break;
case DispatchTypes::PrivateModeParams::DECSCNM_ScreenMode:
fSuccess = SetScreenMode(fEnable);
break;
case DispatchTypes::PrivateModeParams::DECOM_OriginMode:
// The cursor is also moved to the new home position when the origin mode is set or reset.
fSuccess = SetOriginMode(fEnable) && CursorPosition(1, 1);
Expand Down Expand Up @@ -898,6 +902,12 @@ class StatefulDispatch final : public TermDispatch
return true;
}

bool SetScreenMode(const bool reverseMode) noexcept override
{
_isScreenModeReversed = reverseMode;
return true;
}

bool SetOriginMode(const bool fRelativeMode) noexcept override
{
_isOriginModeRelative = fRelativeMode;
Expand Down Expand Up @@ -949,6 +959,7 @@ class StatefulDispatch final : public TermDispatch
bool _isAltBuffer;
bool _cursorKeysMode;
bool _cursorBlinking;
bool _isScreenModeReversed;
bool _isOriginModeRelative;
bool _isDECCOLMAllowed;
size_t _windowWidth;
Expand Down Expand Up @@ -1226,6 +1237,25 @@ class StateMachineExternalTest final
pDispatch->ClearState();
}

TEST_METHOD(TestScreenMode)
{
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"\x1b[?5h");
VERIFY_IS_TRUE(pDispatch->_isScreenModeReversed);

pDispatch->ClearState();
pDispatch->_isScreenModeReversed = true;

mach.ProcessString(L"\x1b[?5l");
VERIFY_IS_FALSE(pDispatch->_isScreenModeReversed);

pDispatch->ClearState();
}

TEST_METHOD(TestOriginMode)
{
auto dispatch = std::make_unique<StatefulDispatch>();
Expand Down