From 70106e40fd3137a8bef69ff024809178578b2200 Mon Sep 17 00:00:00 2001 From: "Dan Thompson (SBS)" Date: Tue, 9 Jul 2019 22:04:03 -0700 Subject: [PATCH] Implement XTPUSHSGR, XTPOPSGR This change adds a new pair of methods to ITermDispatch: PushGraphicsRendition and PopGraphicsRendition, and then plumbs the change through AdaptDispatch, TerminalDispatch, ITerminalApi and TerminalApi. The stack logic is encapsulated in the SgrStack class, to allow it to be reused between the two APIs (AdaptDispatch and TerminalDispatch). Like xterm, only ten levels of nesting are supported. Pushes beyond ten will remain balanced (an equal number of pops will take you back down to zero), up to 100 pushes. Beyond 100 pushes, pushes will become unbalanced (the internal counter will no longer be incremented). This bound gives the terminal a deterministic way to recover from garbage--do 101 pops and you know you've cleared the stack back down to zero. For partial pushes (see the description of XTPUSHSGR in Issue #1796), only attributes that are supported by terminal are saved; others are ignored (this change does not including adding general support for double underlines, for example). A partial push of unsupported parameters results in an "empty" push--the subsequent pop will not change the current text attributes. --- src/buffer/out/TextAttribute.cpp | 6 +- src/buffer/out/TextAttribute.hpp | 6 +- src/buffer/out/sgrStack.hpp | 238 ++++++++++++++++++ src/cascadia/TerminalCore/ITerminalApi.hpp | 3 + src/cascadia/TerminalCore/Terminal.hpp | 5 + src/cascadia/TerminalCore/TerminalApi.cpp | 28 +++ .../TerminalCore/TerminalDispatch.hpp | 4 + .../TerminalCore/TerminalDispatchGraphics.cpp | 11 + src/host/outputStream.cpp | 8 +- src/terminal/adapter/DispatchTypes.hpp | 4 + src/terminal/adapter/ITermDispatch.hpp | 4 + src/terminal/adapter/adaptDispatch.hpp | 8 +- .../adapter/adaptDispatchGraphics.cpp | 46 ++++ src/terminal/adapter/termDispatch.hpp | 4 + .../parser/OutputStateMachineEngine.cpp | 98 +++++++- .../parser/OutputStateMachineEngine.hpp | 18 +- 16 files changed, 465 insertions(+), 26 deletions(-) create mode 100644 src/buffer/out/sgrStack.hpp diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index 6006461aa4b..5f0c2db39d5 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -221,17 +221,17 @@ bool TextAttribute::IsRightVerticalDisplayed() const noexcept return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_RVERTICAL); } -void TextAttribute::SetLeftVerticalDisplayed(bool isDisplayed) noexcept +void TextAttribute::SetLeftVerticalDisplayed(const bool isDisplayed) noexcept { WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_LVERTICAL, isDisplayed); } -void TextAttribute::SetRightVerticalDisplayed(bool isDisplayed) noexcept +void TextAttribute::SetRightVerticalDisplayed(const bool isDisplayed) noexcept { WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_RVERTICAL, isDisplayed); } -void TextAttribute::SetBottomHorizontalDisplayed(bool isDisplayed) noexcept +void TextAttribute::SetBottomHorizontalDisplayed(const bool isDisplayed) noexcept { WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_UNDERSCORE, isDisplayed); } diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 3200e308d8e..d60a15ed619 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -107,9 +107,9 @@ class TextAttribute final bool IsLeftVerticalDisplayed() const noexcept; bool IsRightVerticalDisplayed() const noexcept; - void SetLeftVerticalDisplayed(bool isDisplayed) noexcept; - void SetRightVerticalDisplayed(bool isDisplayed) noexcept; - void SetBottomHorizontalDisplayed(bool isDisplayed) noexcept; + void SetLeftVerticalDisplayed(const bool isDisplayed) noexcept; + void SetRightVerticalDisplayed(const bool isDisplayed) noexcept; + void SetBottomHorizontalDisplayed(const bool isDisplayed) noexcept; void SetFromLegacy(const WORD wLegacy) noexcept; diff --git a/src/buffer/out/sgrStack.hpp b/src/buffer/out/sgrStack.hpp new file mode 100644 index 00000000000..b43f4d9bcdd --- /dev/null +++ b/src/buffer/out/sgrStack.hpp @@ -0,0 +1,238 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- sgrStack.hpp + +Abstract: +- Encapsulates logic for the XTPUSHSGR / XTPOPSGR VT control sequences, which save and + restore text attributes on a stack. + +--*/ + +#pragma once + +#include "TextAttribute.hpp" +#include "..\..\terminal\adapter\DispatchTypes.hpp" + +namespace Microsoft::Console::VirtualTerminal +{ + class SgrStack + { + public: + SgrStack() noexcept : + _numSgrPushes{ 0 }, + _validAttributes{ 0 }, + _storedSgrAttributes{ 0 } + { + } + + // Method Description: + // - Saves the specified text attributes onto an internal stack. + // Arguments: + // - currentAttributes - The attributes to save onto the stack. + // - options - If none supplied, the full attributes are saved. Else only the + // specified parts of currentAttributes are saved. + // Return Value: + // - + void Push(const TextAttribute& currentAttributes, + const gsl::span options) noexcept + { + uint32_t validParts = 0; + + if (options.size() == 0) + { + // We save all current attributes. + validParts = UINT32_MAX; + } + else + { + // Each option is encoded as a bit in validParts. Options that aren't + // supported are ignored. So if you try to save only unsuppported aspects + // of the current text attributes, validParts will be zero, and you'll do + // what is effectively an "empty" push (the subsequent pop will not change + // the current attributes). + for (auto option : options) + { + validParts |= _GraphicsOptionToFlag(option); + } + } + + if (_numSgrPushes < _countof(_storedSgrAttributes)) + { +// Must disable 26482 "Only index into arrays using constant expressions" because we are +// implementing a stack, and that's the whole point. +// We also disable the warning for using gsl::at, because doing that yields another: "No +// array to pointer decay". +#pragma warning(push) +#pragma warning(disable : 26482 26446) + _storedSgrAttributes[_numSgrPushes] = currentAttributes; + _validAttributes[_numSgrPushes] = validParts; +#pragma warning(pop) + } + + if (_numSgrPushes < c_MaxBalancedPushes) + { + _numSgrPushes++; + } + } + + // Method Description: + // - Restores text attributes by removing from the top of the internal stack, + // combining them with the supplied currentAttributes, if appropriate. + // Arguments: + // - currentAttributes - The current text attributes. If only a portion of + // attributes were saved on the internal stack, then those attributes will be + // combined with the currentAttributes passed in to form the return value. + // Return Value: + // - The TextAttribute that has been removed from the top of the stack, possibly + // combined with currentAttributes. + const TextAttribute Pop(const TextAttribute& currentAttributes) noexcept + { + if (_numSgrPushes > 0) + { + _numSgrPushes--; + + if (_numSgrPushes < _countof(_storedSgrAttributes)) + { +// Must disable 26482 "Only index into arrays using constant expressions" because we are +// implementing a stack, and that's the whole point. +// We also disable the warning for using gsl::at, because doing that yields another: "No +// array to pointer decay". +#pragma warning(push) +#pragma warning(disable : 26482 26446) + const uint32_t validParts = _validAttributes[_numSgrPushes]; + + if (validParts == UINT32_MAX) + { + return _storedSgrAttributes[_numSgrPushes]; + } + else + { + return _CombineWithCurrentAttributes(currentAttributes, + _storedSgrAttributes[_numSgrPushes], + validParts); + } +#pragma warning(pop) + } + } + + return currentAttributes; + } + + // Xterm allows the save stack to go ten deep, so we'll follow suit. Pushes after + // ten deep will still remain "balanced"--once you pop back down below ten, you'll + // restore the appropriate text attributes. However, if you get more than a + // hundred pushes deep, we'll stop counting. Why unbalance somebody doing so many + // pushes? Putting a bound on it allows us to provide "reset" functionality: at + // any given point, you can execute 101 pops and know that you've taken the stack + // (push count) to zero. (Then you reset text attributes, and your state is + // clean.) + static constexpr int c_MaxStoredSgrPushes = 10; + static constexpr int c_MaxBalancedPushes = 100; + + private: + static constexpr uint32_t _GraphicsOptionToFlag(DispatchTypes::GraphicsOptions option) + { + int iOption = static_cast(option); + + if (iOption < (sizeof(uint32_t) * 8)) + { + iOption = 1 << iOption; + } + // else it's a bad parameter; we'll just ignore it + + return iOption; + } + + TextAttribute _CombineWithCurrentAttributes(const TextAttribute& currentAttributes, + const TextAttribute& savedAttribute, + uint32_t validParts) noexcept // of savedAttribute + { + TextAttribute result = currentAttributes; + + // From xterm documentation: + // + // CSI # { + // CSI Ps ; Ps # { + // Push video attributes onto stack (XTPUSHSGR), xterm. The + // optional parameters correspond to the SGR encoding for video + // attributes, except for colors (which do not have a unique SGR + // code): + // Ps = 1 -> Bold. + // Ps = 2 -> Faint. + // Ps = 3 -> Italicized. + // Ps = 4 -> Underlined. + // Ps = 5 -> Blink. + // Ps = 7 -> Inverse. + // Ps = 8 -> Invisible. + // Ps = 9 -> Crossed-out characters. + // Ps = 1 0 -> Foreground color. + // Ps = 1 1 -> Background color. + // Ps = 2 1 -> Doubly-underlined. + // + // (some closing braces for people with editors that get thrown off without them: }}) + // + // Attributes that are not currently supported are simply ignored. + + if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::BoldBright) & validParts) + { + if (savedAttribute.IsBold()) + { + result.Embolden(); + } + else + { + result.Debolden(); + } + } + + if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::Underline) & validParts) + { + if (savedAttribute.IsUnderline()) + { + result.EnableUnderline(); + } + else + { + result.DisableUnderline(); + } + } + + if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::Negative) & validParts) + { + if (savedAttribute.IsReverseVideo()) + { + if (!result.IsReverseVideo()) + { + result.Invert(); + } + } + else + { + if (result.IsReverseVideo()) + { + result.Invert(); + } + } + } + + if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::SaveForegroundColor) & validParts) + { + result.SetForegroundFrom(savedAttribute); + } + + if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::SaveBackgroundColor) & validParts) + { + result.SetBackgroundFrom(savedAttribute); + } + + return result; + } + + int _numSgrPushes; // used as an index into the following arrays + TextAttribute _storedSgrAttributes[c_MaxStoredSgrPushes]; + uint32_t _validAttributes[c_MaxStoredSgrPushes]; // flags that indicate which portions of the attributes are valid + }; +} diff --git a/src/cascadia/TerminalCore/ITerminalApi.hpp b/src/cascadia/TerminalCore/ITerminalApi.hpp index 2bf09bf7b31..e0ae44cb735 100644 --- a/src/cascadia/TerminalCore/ITerminalApi.hpp +++ b/src/cascadia/TerminalCore/ITerminalApi.hpp @@ -44,6 +44,9 @@ namespace Microsoft::Terminal::Core virtual bool SetDefaultForeground(const DWORD dwColor) = 0; virtual bool SetDefaultBackground(const DWORD dwColor) = 0; + virtual bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* options, size_t cOptions) = 0; + virtual bool PopGraphicsRendition() = 0; + protected: ITerminalApi() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 518ebde28d4..d21713c6da7 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -6,6 +6,7 @@ #include #include "../../buffer/out/textBuffer.hpp" +#include "../../buffer/out/sgrStack.hpp" #include "../../renderer/inc/IRenderData.hpp" #include "../../terminal/parser/StateMachine.hpp" #include "../../terminal/input/terminalInput.hpp" @@ -82,6 +83,8 @@ class Microsoft::Terminal::Core::Terminal final : bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override; bool SetDefaultForeground(const COLORREF dwColor) override; bool SetDefaultBackground(const COLORREF dwColor) override; + bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* options, size_t cOptions) override; + bool PopGraphicsRendition() override; #pragma endregion #pragma region ITerminalInput @@ -241,4 +244,6 @@ class Microsoft::Terminal::Core::Terminal final : SMALL_RECT _GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const; void _ExpandSelectionRow(SMALL_RECT& selectionRow) const; #pragma endregion + + Microsoft::Console::VirtualTerminal::SgrStack _sgrStack; }; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index b54a22581f5..214fc2f13eb 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -476,3 +476,31 @@ bool Terminal::SetDefaultBackground(const COLORREF dwColor) _buffer->GetRenderTarget().TriggerRedrawAll(); return true; } + +// Method Description: +// - Saves the current text attributes to an internal stack. +// Arguments: +// - options, cOptions: if present, specify which portions of the current text attributes +// should be saved. Only a small subset of GraphicsOptions are actually supported; +// others are ignored. If no options are specified, all attributes are stored. +// Return Value: +// - true +bool Terminal::PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* options, size_t cOptions) +{ + _sgrStack.Push(_buffer->GetCurrentAttributes(), { options, (int)cOptions }); + return true; +} + +// Method Description: +// - Restores text attributes from the internal stack. If only portions of text attributes +// were saved, combines those with the current attributes. +// Arguments: +// - +// Return Value: +// - true +bool Terminal::PopGraphicsRendition() +{ + TextAttribute current = _buffer->GetCurrentAttributes(); + _buffer->SetCurrentAttributes(_sgrStack.Pop(current)); + return true; +} diff --git a/src/cascadia/TerminalCore/TerminalDispatch.hpp b/src/cascadia/TerminalCore/TerminalDispatch.hpp index 5fa9758856e..2988d60f41d 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.hpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.hpp @@ -16,6 +16,10 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc bool SetGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions) override; + bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* const rgOptions, + const size_t cOptions) override; + bool PopGraphicsRendition() override; + virtual bool CursorPosition(const unsigned int uiLine, const unsigned int uiColumn) override; // CUP diff --git a/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp b/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp index b73b9adee1b..27fe12ede96 100644 --- a/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp @@ -326,3 +326,14 @@ bool TerminalDispatch::SetGraphicsRendition(const DispatchTypes::GraphicsOptions } return fSuccess; } + +bool TerminalDispatch::PushGraphicsRendition(const DispatchTypes::GraphicsOptions* const rgOptions, + const size_t cOptions) +{ + return _terminalApi.PushGraphicsRendition(rgOptions, cOptions); +} + +bool TerminalDispatch::PopGraphicsRendition() +{ + return _terminalApi.PopGraphicsRendition(); +} diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index c5400bee2b4..cf45db92d76 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -193,7 +193,7 @@ BOOL ConhostInternalGetSet::PrivateSetDefaultAttributes(const bool fForeground, // - fBackground - The new attributes contain an update to the background attributes // - fMeta - The new attributes contain an update to the meta attributes // Return Value: -// - TRUE if successful (see DoSrvVtSetLegacyAttributes). FALSE otherwise. +// - TRUE. BOOL ConhostInternalGetSet::PrivateSetLegacyAttributes(const WORD wAttr, const bool fForeground, const bool fBackground, @@ -203,6 +203,12 @@ BOOL ConhostInternalGetSet::PrivateSetLegacyAttributes(const WORD wAttr, return TRUE; } +// Routine Description: +// - Similar to PrivateSetLegacyAttributes, but sets the full fidelity TextAttribute. +// Arguments: +// - attributes - new text attributes to apply as default within the console text buffer +// Return Value: +// - TRUE. BOOL ConhostInternalGetSet::PrivateSetAttributes(const TextAttribute& attributes) { DoSrvPrivateSetAttributes(_io.GetActiveOutputBuffer(), attributes); diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 17f26c66d85..fed2bd5721f 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -27,6 +27,10 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes Negative = 7, Invisible = 8, CrossedOut = 9, + // 10 - 19 are actually for selecting fonts, but rarely implemented. 10 and 11 + // have been repurposed by xterm for XTPUSHGR. + SaveForegroundColor = 10, + SaveBackgroundColor = 11, DoublyUnderlined = 21, UnBold = 22, NotItalics = 23, diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index a6532832808..accb845daff 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -77,6 +77,10 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool SetGraphicsRendition(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions) = 0; // SGR + virtual bool PushGraphicsRendition(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, + const size_t cOptions) = 0; // XTPUSHSGR + virtual bool PopGraphicsRendition() = 0; // XTPOPSGR + virtual bool SetPrivateModes(_In_reads_(cParams) const DispatchTypes::PrivateModeParams* const rgParams, const size_t cParams) = 0; // DECSET diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 3465343643e..b430b35ccdb 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -19,6 +19,7 @@ Author(s): #include "conGetSet.hpp" #include "adaptDefaults.hpp" #include "terminalOutput.hpp" +#include "..\..\buffer\out\sgrStack.hpp" #include namespace Microsoft::Console::VirtualTerminal @@ -56,6 +57,9 @@ namespace Microsoft::Console::VirtualTerminal bool DeleteCharacter(_In_ unsigned int const uiCount) override; // DCH bool SetGraphicsRendition(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions) override; // SGR + bool PushGraphicsRendition(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, + const size_t cOptions) override; // XTPUSHSGR + bool PopGraphicsRendition() override; // XTPOPSGR bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR bool DeviceAttributes() override; // DA bool ScrollUp(_In_ unsigned int const uiDistance) override; // SU @@ -164,14 +168,14 @@ namespace Microsoft::Console::VirtualTerminal bool _fIsOriginModeRelative; - bool _fIsSetColumnsEnabled; - bool _fIsDECCOLMAllowed; bool _fChangedForeground; bool _fChangedBackground; bool _fChangedMetaAttrs; + SgrStack _sgrStack; + bool _SetRgbColorsHelper(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions, _Out_ COLORREF* const prgbColor, diff --git a/src/terminal/adapter/adaptDispatchGraphics.cpp b/src/terminal/adapter/adaptDispatchGraphics.cpp index 7315251b8e0..7e77c93f9f0 100644 --- a/src/terminal/adapter/adaptDispatchGraphics.cpp +++ b/src/terminal/adapter/adaptDispatchGraphics.cpp @@ -550,3 +550,49 @@ bool AdaptDispatch::SetGraphicsRendition(_In_reads_(cOptions) const DispatchType return fSuccess; } + +// Method Description: +// - Saves the current text attributes to an internal stack. +// Arguments: +// - rgOptions, cOptions: if present, specify which portions of the current text attributes +// should be saved. Only a small subset of GraphicsOptions are actually supported; +// others are ignored. If no options are specified, all attributes are stored. +// Return Value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::PushGraphicsRendition(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, + const size_t cOptions) +{ + bool fSuccess = true; + TextAttribute currentAttributes; + + fSuccess = _conApi->PrivateGetConsoleScreenBufferAttributes(¤tAttributes); + + if (fSuccess) + { + _sgrStack.Push(currentAttributes, { rgOptions, (int)cOptions }); + } + + return fSuccess; +} + +// Method Description: +// - Restores text attributes from the internal stack. If only portions of text attributes +// were saved, combines those with the current attributes. +// Arguments: +// - +// Return Value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::PopGraphicsRendition() +{ + bool fSuccess = true; + TextAttribute currentAttributes; + + fSuccess = _conApi->PrivateGetConsoleScreenBufferAttributes(¤tAttributes); + + if (fSuccess) + { + fSuccess = _conApi->PrivateSetAttributes(_sgrStack.Pop(currentAttributes)); + } + + return fSuccess; +} diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index a18a5fed9f0..58180cbdace 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -74,6 +74,10 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool SetGraphicsRendition(_In_reads_(_Param_(2)) const DispatchTypes::GraphicsOptions* const /*rgOptions*/, const size_t /*cOptions*/) override { return false; } // SGR + bool PushGraphicsRendition(_In_reads_(_Param_(2)) const DispatchTypes::GraphicsOptions* const /*rgOptions*/, + const size_t /*cOptions*/) override { return false; } // XTPUSHSGR + bool PopGraphicsRendition() override { return false; } // XTPOPSGR + bool SetPrivateModes(_In_reads_(_Param_(2)) const DispatchTypes::PrivateModeParams* const /*rgParams*/, const size_t /*cParams*/) override { return false; } // DECSET diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 52dad4b10fa..1692e730e0f 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -525,6 +525,9 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, case L' ': fSuccess = _IntermediateSpaceDispatch(wch, rgusParams, cParams); break; + case L'#': + fSuccess = _IntermediateHashDispatch(wch, rgusParams, cParams); + break; default: // If no functions to call, overall dispatch was a failure. fSuccess = false; @@ -562,7 +565,7 @@ bool OutputStateMachineEngine::_IntermediateQuestionMarkDispatch(const wchar_t w { case VTActionCodes::DECSET_PrivateModeSet: case VTActionCodes::DECRST_PrivateModeReset: - fSuccess = _GetPrivateModeParams(rgusParams, cParams, rgPrivateModeParams, &cOptions); + fSuccess = _GetTypedParams(rgusParams, cParams, rgPrivateModeParams, &cOptions); break; default: @@ -660,6 +663,71 @@ bool OutputStateMachineEngine::_IntermediateSpaceDispatch(const wchar_t wchActio return fSuccess; } +// Routine Description: +// - Handles actions that have an intermediate '#' (0x23), such as XTPUSHSGR, XTPOPSGR +// Arguments: +// - wch - Character to dispatch. +// Return Value: +// - True if handled successfully. False otherwise. +bool OutputStateMachineEngine::_IntermediateHashDispatch(const wchar_t wchAction, + _In_reads_(cParams) const unsigned short* const rgusParams, + const unsigned short cParams) +{ + bool fSuccess = false; + + DispatchTypes::GraphicsOptions rgPushPopParams[StateMachine::s_cParamsMax]; + size_t cOptions = StateMachine::s_cParamsMax; + // Ensure that there was the right number of params + switch (wchAction) + { + case VTActionCodes::XT_PushSgr: + case VTActionCodes::XT_PushSgrAlias: + fSuccess = _GetTypedParams(rgusParams, cParams, rgPushPopParams, &cOptions); + break; + + case VTActionCodes::XT_PopSgr: + case VTActionCodes::XT_PopSgrAlias: + if (cParams > 0) + { + // Can't supply params for XTPOPSGR. + fSuccess = false; + } + else + { + fSuccess = true; + } + break; + + default: + // If no params to fill, param filling was successful. + fSuccess = true; + break; + } + if (fSuccess) + { + switch (wchAction) + { + case VTActionCodes::XT_PushSgr: + case VTActionCodes::XT_PushSgrAlias: + fSuccess = _dispatch->PushGraphicsRendition(rgPushPopParams, cOptions); + // TODO: telemetry + break; + + case VTActionCodes::XT_PopSgr: + case VTActionCodes::XT_PopSgrAlias: + fSuccess = _dispatch->PopGraphicsRendition(); + // TODO: telemetry + break; + + default: + // If no functions to call, overall dispatch was a failure. + fSuccess = false; + break; + } + } + return fSuccess; +} + // Routine Description: // - Triggers the Clear action to indicate that the state machine should erase // all internal state. @@ -1092,6 +1160,7 @@ _Success_(return ) bool OutputStateMachineEngine::_GetTopBottomMargins(_In_reads } return fSuccess; } + // Routine Description: // - Retrieves the status type parameter for an upcoming device query operation // Arguments: @@ -1124,27 +1193,27 @@ _Success_(return ) bool OutputStateMachineEngine::_GetDeviceStatusOperation(_In_ } // Routine Description: -// - Retrieves the listed private mode params be set/reset by DECSET/DECRST +// - Converts the untyped array of numeric parameters into an array of the specified type. // Arguments: -// - rPrivateModeParams - Pointer to array space (expected 16 max, the max number of params this can generate) that will be filled with valid params from the PrivateModeParams enum -// - pcParams - Pointer to the length of rPrivateModeParams on the way in, and the count of the array used on the way out. +// - rgTypedParams - Pointer to array space (expected 16 max, the max number of params this can generate) that will be filled with valid params from the PrivateModeParams enum +// - pcParams - Pointer to the length of rgTypedParams on the way in, and the count of the array used on the way out. // Return Value: -// - True if we successfully retrieved an array of private mode params from the parameters we've stored. False otherwise. -_Success_(return ) bool OutputStateMachineEngine::_GetPrivateModeParams(_In_reads_(cParams) const unsigned short* const rgusParams, - const unsigned short cParams, - _Out_writes_(*pcParams) DispatchTypes::PrivateModeParams* const rgPrivateModeParams, - _Inout_ size_t* const pcParams) const +// - True if we successfully retrieved an array of strongly-typed params from the parameters we've stored. False otherwise. +template +_Success_(return ) bool OutputStateMachineEngine::_GetTypedParams(_In_reads_(cParams) const unsigned short* const rgusParams, + const unsigned short cParams, + _Out_writes_(*pcParams) TParamType* const rgTypedParams, + _Inout_ size_t* const pcParams) const { bool fSuccess = false; - // Can't just set nothing at all if (cParams > 0) { if (*pcParams >= cParams) { for (size_t i = 0; i < cParams; i++) { - // No memcpy. The parameters are shorts. The graphics options are unsigned ints. - rgPrivateModeParams[i] = (DispatchTypes::PrivateModeParams)rgusParams[i]; + // No memcpy because the parameters are shorts, and the destination type may be a different size. + rgTypedParams[i] = static_cast(rgusParams[i]); } *pcParams = cParams; fSuccess = true; @@ -1154,6 +1223,11 @@ _Success_(return ) bool OutputStateMachineEngine::_GetPrivateModeParams(_In_read fSuccess = false; // not enough space in buffer to hold response. } } + else + { + *pcParams = 0; + fSuccess = true; + } return fSuccess; } diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 79bbadc9b9a..d1668ac26bf 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -81,6 +81,9 @@ namespace Microsoft::Console::VirtualTerminal bool _IntermediateSpaceDispatch(const wchar_t wchAction, _In_reads_(cParams) const unsigned short* const rgusParams, const unsigned short cParams); + bool _IntermediateHashDispatch(const wchar_t wchAction, + _In_reads_(cParams) const unsigned short* const rgusParams, + const unsigned short cParams); enum VTActionCodes : wchar_t { @@ -127,7 +130,11 @@ namespace Microsoft::Console::VirtualTerminal // 'q' is overloaded - no postfix is DECLL, ' ' postfix is DECSCUSR, and '"' is DECSCA DECSCUSR_SetCursorStyle = L'q', // I believe we'll only ever implement DECSCUSR DTTERM_WindowManipulation = L't', - REP_RepeatCharacter = L'b' + REP_RepeatCharacter = L'b', + XT_PushSgr = L'{', + XT_PushSgrAlias = L'p', + XT_PopSgr = L'}', + XT_PopSgrAlias = L'q', }; enum OscActionCodes : unsigned int @@ -195,10 +202,11 @@ namespace Microsoft::Console::VirtualTerminal _Success_(return ) bool _VerifyDeviceAttributesParams(_In_reads_(cParams) const unsigned short* const rgusParams, const unsigned short cParams) const; - _Success_(return ) bool _GetPrivateModeParams(_In_reads_(cParams) const unsigned short* const rgusParams, - const unsigned short cParams, - _Out_writes_(*pcParams) DispatchTypes::PrivateModeParams* const rgPrivateModeParams, - _Inout_ size_t* const pcParams) const; + template + _Success_(return ) bool _GetTypedParams(_In_reads_(cParams) const unsigned short* const rgusParams, + const unsigned short cParams, + _Out_writes_(*pcParams) TParamType* const rgXtPushParams, + _Inout_ size_t* const pcParams) const; static const SHORT s_sDefaultTopMargin = 0; static const SHORT s_sDefaultBottomMargin = 0;