diff --git a/src/host/getset.cpp b/src/host/getset.cpp index ad5af0e9a69..af492bfd514 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -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) + { + 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. diff --git a/src/host/getset.h b/src/host/getset.h index 0199a7660f0..5e30070abc6 100644 --- a/src/host/getset.h +++ b/src/host/getset.h @@ -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); diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index ff713ae4156..b58a77cdadf 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -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, diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index f7f6d720f43..ce06c3aa9de 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -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; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index 437d5a85288..f5aa42672ab 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -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), @@ -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; @@ -942,7 +952,14 @@ COLORREF Settings::CalculateDefaultBackground() const noexcept COLORREF Settings::LookupForegroundColor(const TextAttribute& attr) const noexcept { const auto tableView = std::basic_string_view(&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: @@ -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(&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 diff --git a/src/host/settings.hpp b/src/host/settings.hpp index 8bb1f0c1fde..05503cf979c 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -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); @@ -231,6 +234,7 @@ class Settings DWORD _dwVirtTermLevel; bool _fAutoReturnOnNewline; bool _fRenderGridWorldwide; + bool _fScreenReversed; bool _fUseDx; bool _fCopyColor; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index fa6fd102900..b785039c10e 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -180,6 +180,7 @@ class ScreenBufferTests TEST_METHOD(ScrollLines256Colors); + TEST_METHOD(SetScreenMode); TEST_METHOD(SetOriginMode); TEST_METHOD(HardResetBuffer); @@ -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(); diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 17f26c66d85..1eb6dcafaff 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -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, diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 782c6b14d8e..e416a407493 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -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 diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index a886e272d5a..3dfa62f7824 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -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); @@ -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. @@ -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) { diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 9ff6a98708a..ad97bfc2f20 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -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 diff --git a/src/terminal/adapter/conGetSet.hpp b/src/terminal/adapter/conGetSet.hpp index 6b6f9e0b8e6..759a044649e 100644 --- a/src/terminal/adapter/conGetSet.hpp +++ b/src/terminal/adapter/conGetSet.hpp @@ -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; diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index fe889a1b316..f457b351bbc 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -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 diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 99d8600729b..ed6b88c212e 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -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..."); diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index 79426542806..e705615ade9 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -676,6 +676,7 @@ class StatefulDispatch final : public TermDispatch _isAltBuffer{ false }, _cursorKeysMode{ false }, _cursorBlinking{ true }, + _isScreenModeReversed{ false }, _isOriginModeRelative{ false }, _isDECCOLMAllowed{ false }, _windowWidth{ 80 }, @@ -835,6 +836,9 @@ class StatefulDispatch final : public TermDispatch case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns: fSuccess = SetColumns(static_cast(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); @@ -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; @@ -949,6 +959,7 @@ class StatefulDispatch final : public TermDispatch bool _isAltBuffer; bool _cursorKeysMode; bool _cursorBlinking; + bool _isScreenModeReversed; bool _isOriginModeRelative; bool _isDECCOLMAllowed; size_t _windowWidth; @@ -1226,6 +1237,25 @@ class StateMachineExternalTest final pDispatch->ClearState(); } + TEST_METHOD(TestScreenMode) + { + auto dispatch = std::make_unique(); + auto pDispatch = dispatch.get(); + auto engine = std::make_unique(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();