diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index d755991d8ec..ab9734df856 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -857,6 +857,7 @@ GETAUTOHIDEBAREX GETCARETWIDTH getch getchar +GETCLIENTAREAANIMATION GETCOMMANDHISTORY GETCOMMANDHISTORYLENGTH GETCONSOLEINPUT diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index 8053ece1352..dd6be654efa 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -88,16 +88,18 @@ bool TextAttribute::IsLegacy() const noexcept // - defaultFgColor: the default foreground color rgb value. // - defaultBgColor: the default background color rgb value. // - reverseScreenMode: true if the screen mode is reversed. +// - blinkingIsFaint: true if blinking should be interpreted as faint. // Return Value: // - the foreground and background colors that should be displayed. std::pair TextAttribute::CalculateRgbColors(const gsl::span colorTable, const COLORREF defaultFgColor, const COLORREF defaultBgColor, - const bool reverseScreenMode) const noexcept + const bool reverseScreenMode, + const bool blinkingIsFaint) const noexcept { auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold()); auto bg = _background.GetColor(colorTable, defaultBgColor); - if (IsFaint()) + if (IsFaint() || (IsBlinking() && blinkingIsFaint)) { fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two. } diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index b3d28f443fb..f687f0a434f 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -69,7 +69,8 @@ class TextAttribute final std::pair CalculateRgbColors(const gsl::span colorTable, const COLORREF defaultFgColor, const COLORREF defaultBgColor, - const bool reverseScreenMode = false) const noexcept; + const bool reverseScreenMode = false, + const bool blinkingIsFaint = false) const noexcept; bool IsLeadingByte() const noexcept; bool IsTrailingByte() const noexcept; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index a44ee4c0586..2c9d3e65a08 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -72,6 +72,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }, _touchAnchor{ std::nullopt }, _cursorTimer{}, + _blinkTimer{}, _lastMouseClickTimestamp{}, _lastMouseClickPos{}, _selectionNeedsToBeCopied{ false }, @@ -721,6 +722,24 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _cursorTimer = std::nullopt; } + // Set up blinking attributes + BOOL animationsEnabled = TRUE; + SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0); + if (animationsEnabled && blinkTime != INFINITE) + { + // Create a timer + DispatcherTimer blinkTimer; + blinkTimer.Interval(std::chrono::milliseconds(blinkTime)); + blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick }); + blinkTimer.Start(); + _blinkTimer.emplace(std::move(blinkTimer)); + } + else + { + // The user has disabled blinking + _blinkTimer = std::nullopt; + } + // import value from WinUser (convert from milli-seconds to micro-seconds) _multiClickTimer = GetDoubleClickTime() * 1000; @@ -1777,6 +1796,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _cursorTimer.value().Start(); } + if (_blinkTimer.has_value()) + { + _blinkTimer.value().Start(); + } + _UpdateSystemParameterSettings(); } @@ -1822,6 +1846,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _cursorTimer.value().Stop(); _terminal->SetCursorOn(false); } + + if (_blinkTimer.has_value()) + { + _blinkTimer.value().Stop(); + } } // Method Description: @@ -2035,6 +2064,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _terminal->SetCursorOn(!_terminal->IsCursorOn()); } + // Method Description: + // - Toggle the blinking rendition state when called by the blink timer. + // Arguments: + // - sender: not used + // - e: not used + void TermControl::_BlinkTimerTick(Windows::Foundation::IInspectable const& /* sender */, + Windows::Foundation::IInspectable const& /* e */) + { + if (!_closing) + { + auto& renderTarget = *_renderer; + auto& blinkingState = _terminal->GetBlinkingState(); + blinkingState.ToggleBlinkingRendition(renderTarget); + } + } + // Method Description: // - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging. // Arguments: diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index c2c46d01d66..6fb42cf405d 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -201,6 +201,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation std::optional _leadingSurrogate; std::optional _cursorTimer; + std::optional _blinkTimer; // If this is set, then we assume we are in the middle of panning the // viewport via touch input. @@ -251,6 +252,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void _HyperlinkHandler(const std::wstring_view uri); void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); + void _BlinkTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition); void _SendInputToConnection(const winrt::hstring& wstr); void _SendInputToConnection(std::wstring_view wstr); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index f7a9acab759..d1a8cf43ffa 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1036,3 +1036,8 @@ const std::optional Terminal::GetTabColor() const noexcept { return _tabColor; } + +BlinkingState& Terminal::GetBlinkingState() const noexcept +{ + return _blinkingState; +} diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index accc7a2797d..bdec0a2a319 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -6,7 +6,7 @@ #include #include "../../buffer/out/textBuffer.hpp" -#include "../../renderer/inc/IRenderData.hpp" +#include "../../renderer/inc/BlinkingState.hpp" #include "../../terminal/parser/StateMachine.hpp" #include "../../terminal/input/terminalInput.hpp" @@ -187,6 +187,8 @@ class Microsoft::Terminal::Core::Terminal final : const std::optional GetTabColor() const noexcept; + Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept; + #pragma region TextSelection // These methods are defined in TerminalSelection.cpp enum class SelectionExpansionMode @@ -224,6 +226,7 @@ class Microsoft::Terminal::Core::Terminal final : COLORREF _defaultBg; CursorType _defaultCursorShape; bool _screenReversed; + mutable Microsoft::Console::Render::BlinkingState _blinkingState; bool _snapOnInput; bool _altGrAliasing; diff --git a/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp b/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp index bbc0984296b..a5fe32c7b15 100644 --- a/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp @@ -132,6 +132,7 @@ bool TerminalDispatch::SetGraphicsRendition(const gsl::span Terminal::GetAttributeColors(const TextAttribute& attr) const noexcept { + _blinkingState.RecordBlinkingUsage(attr); auto colors = attr.CalculateRgbColors({ _colorTable.data(), _colorTable.size() }, _defaultFg, _defaultBg, - _screenReversed); + _screenReversed, + _blinkingState.IsBlinkingFaint()); colors.first |= 0xff000000; // We only care about alpha for the default BG (which enables acrylic) // If the bg isn't the default bg color, or reverse video is enabled, make it fully opaque. diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp index 9941b054c7c..bfdad623b7a 100644 --- a/src/host/CursorBlinker.cpp +++ b/src/host/CursorBlinker.cpp @@ -28,6 +28,12 @@ void CursorBlinker::UpdateSystemMetrics() { // This can be -1 in a TS session _uCaretBlinkTime = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime(); + + // If animations are disabled, or the blink rate is infinite, blinking is not allowed. + BOOL animationsEnabled = TRUE; + SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0); + auto& blinkingState = ServiceLocator::LocateGlobals().getConsoleInformation().GetBlinkingState(); + blinkingState.SetBlinkingAllowed(animationsEnabled && _uCaretBlinkTime != INFINITE); } void CursorBlinker::SettingsChanged() @@ -53,7 +59,8 @@ void CursorBlinker::FocusStart() } // Routine Description: -// - This routine is called when the timer in the console with the focus goes off. It blinks the cursor. +// - This routine is called when the timer in the console with the focus goes off. +// It blinks the cursor and also toggles the rendition of any blinking attributes. // Arguments: // - ScreenInfo - reference to screen info structure. // Return Value: @@ -109,7 +116,7 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) if (cursor.GetDelay()) { cursor.SetDelay(false); - goto DoScroll; + goto DoBlinkingRenditionAndScroll; } // Don't blink the cursor for remote sessions. @@ -118,7 +125,7 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) (!cursor.IsBlinkingAllowed())) && cursor.IsOn()) { - goto DoScroll; + goto DoBlinkingRenditionAndScroll; } // Blink only if the cursor isn't turned off via the API @@ -127,6 +134,9 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) cursor.SetIsOn(!cursor.IsOn()); } +DoBlinkingRenditionAndScroll: + gci.GetBlinkingState().ToggleBlinkingRendition(ScreenInfo.GetRenderTarget()); + DoScroll: Scrolling::s_ScrollIfNecessary(ScreenInfo); } diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 4812ec6d544..9cc437a2239 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -12,6 +12,7 @@ #include "..\types\inc\convert.hpp" using Microsoft::Console::Interactivity::ServiceLocator; +using Microsoft::Console::Render::BlinkingState; using Microsoft::Console::VirtualTerminal::VtIo; CONSOLE_INFORMATION::CONSOLE_INFORMATION() : @@ -222,7 +223,8 @@ InputBuffer* const CONSOLE_INFORMATION::GetActiveInputBuffer() const // - the default foreground color of the console. COLORREF CONSOLE_INFORMATION::GetDefaultForeground() const noexcept { - return Settings::CalculateDefaultForeground(); + const auto fg = GetDefaultForegroundColor(); + return fg != INVALID_COLOR ? fg : GetColorTableEntry(LOBYTE(GetFillAttribute()) & FG_ATTRS); } // Method Description: @@ -236,7 +238,25 @@ COLORREF CONSOLE_INFORMATION::GetDefaultForeground() const noexcept // - the default background color of the console. COLORREF CONSOLE_INFORMATION::GetDefaultBackground() const noexcept { - return Settings::CalculateDefaultBackground(); + const auto bg = GetDefaultBackgroundColor(); + return bg != INVALID_COLOR ? bg : GetColorTableEntry((LOBYTE(GetFillAttribute()) & BG_ATTRS) >> 4); +} + +// Method Description: +// - Get the colors of a particular text attribute, using our color table, +// and our configured default attributes. +// Arguments: +// - attr: the TextAttribute to retrieve the foreground color of. +// Return Value: +// - The color values of the attribute's foreground and background. +std::pair CONSOLE_INFORMATION::LookupAttributeColors(const TextAttribute& attr) const noexcept +{ + _blinkingState.RecordBlinkingUsage(attr); + return attr.CalculateRgbColors(Get256ColorTable(), + GetDefaultForeground(), + GetDefaultBackground(), + IsScreenReversed(), + _blinkingState.IsBlinkingFaint()); } // Method Description: @@ -355,6 +375,17 @@ Microsoft::Console::CursorBlinker& CONSOLE_INFORMATION::GetCursorBlinker() noexc return _blinker; } +// Method Description: +// - return a reference to the console's blinking state. +// Arguments: +// - +// Return Value: +// - a reference to the console's blinking state. +BlinkingState& CONSOLE_INFORMATION::GetBlinkingState() const noexcept +{ + return _blinkingState; +} + // Method Description: // - Generates a CHAR_INFO for this output cell, using the TextAttribute // GetLegacyAttributes method to generate the legacy style attributes. diff --git a/src/host/server.h b/src/host/server.h index df96e102912..bc71e371a30 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -28,6 +28,7 @@ Revision History: #include "..\server\WaitQueue.h" #include "..\host\RenderData.hpp" +#include "..\renderer\inc\BlinkingState.hpp" // clang-format off // Flags flags @@ -125,6 +126,7 @@ class CONSOLE_INFORMATION : COLORREF GetDefaultForeground() const noexcept; COLORREF GetDefaultBackground() const noexcept; + std::pair LookupAttributeColors(const TextAttribute& attr) const noexcept; void SetTitle(const std::wstring_view newTitle); void SetTitlePrefix(const std::wstring& newTitlePrefix); @@ -141,6 +143,7 @@ class CONSOLE_INFORMATION : friend class SCREEN_INFORMATION; friend class CommonState; Microsoft::Console::CursorBlinker& GetCursorBlinker() noexcept; + Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept; CHAR_INFO AsCharInfo(const OutputCellView& cell) const noexcept; @@ -157,6 +160,7 @@ class CONSOLE_INFORMATION : Microsoft::Console::VirtualTerminal::VtIo _vtIo; Microsoft::Console::CursorBlinker _blinker; + mutable Microsoft::Console::Render::BlinkingState _blinkingState; }; #define ConsoleLocked() (ServiceLocator::LocateGlobals()->getConsoleInformation()->ConsoleLock.OwningThread == NtCurrentTeb()->ClientId.UniqueThread) diff --git a/src/host/settings.cpp b/src/host/settings.cpp index 13faafa4933..979dce9ec41 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -831,51 +831,6 @@ bool Settings::GetUseDx() const noexcept return _fUseDx; } -// Method Description: -// - Return the default foreground color of the console. If the settings are -// configured to have a default foreground color (separate from the color -// table), this will return that value. Otherwise it will return the value -// from the colortable corresponding to our default legacy attributes. -// Arguments: -// - -// Return Value: -// - the default foreground color of the console. -COLORREF Settings::CalculateDefaultForeground() const noexcept -{ - const auto fg = GetDefaultForegroundColor(); - return fg != INVALID_COLOR ? fg : GetColorTableEntry(LOBYTE(_wFillAttribute) & FG_ATTRS); -} - -// Method Description: -// - Return the default background color of the console. If the settings are -// configured to have a default background color (separate from the color -// table), this will return that value. Otherwise it will return the value -// from the colortable corresponding to our default legacy attributes. -// Arguments: -// - -// Return Value: -// - the default background color of the console. -COLORREF Settings::CalculateDefaultBackground() const noexcept -{ - const auto bg = GetDefaultBackgroundColor(); - return bg != INVALID_COLOR ? bg : GetColorTableEntry((LOBYTE(_wFillAttribute) & BG_ATTRS) >> 4); -} - -// Method Description: -// - Get the colors of a particular text attribute, using our color table, -// and our configured default attributes. -// Arguments: -// - attr: the TextAttribute to retrieve the foreground color of. -// Return Value: -// - The color values of the attribute's foreground and background. -std::pair Settings::LookupAttributeColors(const TextAttribute& attr) const noexcept -{ - return attr.CalculateRgbColors(Get256ColorTable(), - CalculateDefaultForeground(), - CalculateDefaultBackground(), - _fScreenReversed); -} - bool Settings::GetCopyColor() const noexcept { return _fCopyColor; diff --git a/src/host/settings.hpp b/src/host/settings.hpp index decd30d72c1..25eb06dac69 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -185,10 +185,6 @@ class Settings bool GetUseDx() const noexcept; bool GetCopyColor() const noexcept; - COLORREF CalculateDefaultForeground() const noexcept; - COLORREF CalculateDefaultBackground() const noexcept; - std::pair LookupAttributeColors(const TextAttribute& attr) const noexcept; - private: DWORD _dwHotKey; DWORD _dwStartupFlags; diff --git a/src/renderer/base/BlinkingState.cpp b/src/renderer/base/BlinkingState.cpp new file mode 100644 index 00000000000..c3f37bfb99f --- /dev/null +++ b/src/renderer/base/BlinkingState.cpp @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "../inc/BlinkingState.hpp" + +using namespace Microsoft::Console::Render; + +// Method Description: +// - Updates the flag indicating whether cells with the blinking attribute +// can animate on and off. +// Arguments: +// - blinkingAllowed: true if blinking is permitted. +// Return Value: +// - +void BlinkingState::SetBlinkingAllowed(const bool blinkingAllowed) noexcept +{ + _blinkingAllowed = blinkingAllowed; + if (!_blinkingAllowed) + { + _blinkingShouldBeFaint = false; + } +} + +// Method Description: +// - Makes a record of whether the given attribute has blinking enabled or +// not, so we can determine whether the screen will need to be refreshed +// when the blinking rendition state changes. +// Arguments: +// - attr: a text attribute that is expected to be rendered. +// Return Value: +// - +void BlinkingState::RecordBlinkingUsage(const TextAttribute& attr) noexcept +{ + _blinkingIsInUse = _blinkingIsInUse || attr.IsBlinking(); +} + +// Method Description: +// - Determines whether cells with the blinking attribute should be rendered +// as normal or faint, based on the current position in the blinking cycle. +// Arguments: +// - +// Return Value: +// - True if blinking cells should be rendered as faint. +bool BlinkingState::IsBlinkingFaint() const noexcept +{ + return _blinkingShouldBeFaint; +} + +// Method Description: +// - Increments the position in the blinking cycle, toggling the blinking +// rendition state on every second call, potentially triggering a redraw of +// the given render target if there are blinking cells currently in view. +// Arguments: +// - renderTarget: the render target that will be redrawn. +// Return Value: +// - +void BlinkingState::ToggleBlinkingRendition(IRenderTarget& renderTarget) noexcept +try +{ + if (_blinkingAllowed) + { + // This method is called with the frequency of the cursor blink rate, + // but we only want our cells to blink at half that frequency. We thus + // have a blinking cycle that loops through four phases... + _blinkingCycle = (_blinkingCycle + 1) % 4; + // ... and two of those four render the blinking attributes as faint. + _blinkingShouldBeFaint = _blinkingCycle >= 2; + // Every two cycles (when the state changes), we need to trigger a + // redraw, but only if there are actually blinking attributes in use. + if (_blinkingIsInUse && _blinkingCycle % 2 == 0) + { + // We reset the _blinkingIsInUse flag before redrawing, so we can + // get a fresh assessment of the current blinking attribute usage. + _blinkingIsInUse = false; + renderTarget.TriggerRedrawAll(); + } + } +} +CATCH_LOG() diff --git a/src/renderer/base/lib/base.vcxproj b/src/renderer/base/lib/base.vcxproj index bafc76d37d8..434f25d2907 100644 --- a/src/renderer/base/lib/base.vcxproj +++ b/src/renderer/base/lib/base.vcxproj @@ -10,6 +10,7 @@ + @@ -22,6 +23,7 @@ + diff --git a/src/renderer/base/lib/base.vcxproj.filters b/src/renderer/base/lib/base.vcxproj.filters index 300c6dfc5d4..1ec046c4332 100644 --- a/src/renderer/base/lib/base.vcxproj.filters +++ b/src/renderer/base/lib/base.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + Source Files @@ -77,6 +80,9 @@ Header Files + + Header Files\inc + Header Files\inc diff --git a/src/renderer/inc/BlinkingState.hpp b/src/renderer/inc/BlinkingState.hpp new file mode 100644 index 00000000000..17ff2504c35 --- /dev/null +++ b/src/renderer/inc/BlinkingState.hpp @@ -0,0 +1,37 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- BlinkingState.hpp + +Abstract: +- This serves as a structure holding the state of the blinking rendition. + +- It tracks the position in the blinking cycle, which determines whether any + blinking cells should be rendered as on or off/faint. It also records whether + blinking attributes are actually in use or not, so we can decide whether the + screen needs to be refreshed when the blinking cycle changes. +--*/ + +#pragma once + +#include "IRenderTarget.hpp" + +namespace Microsoft::Console::Render +{ + class BlinkingState + { + public: + void SetBlinkingAllowed(const bool blinkingAllowed) noexcept; + void RecordBlinkingUsage(const TextAttribute& attr) noexcept; + bool IsBlinkingFaint() const noexcept; + void ToggleBlinkingRendition(IRenderTarget& renderTarget) noexcept; + + private: + bool _blinkingAllowed = true; + size_t _blinkingCycle = 0; + bool _blinkingIsInUse = false; + bool _blinkingShouldBeFaint = false; + }; +} diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 0eccfff2220..6266ce6784e 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -111,6 +111,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes Italics = 3, Underline = 4, BlinkOrXterm256Index = 5, // 5 is also Blink (appears as Bold). + RapidBlink = 6, Negative = 7, Invisible = 8, CrossedOut = 9, diff --git a/src/terminal/adapter/adaptDispatchGraphics.cpp b/src/terminal/adapter/adaptDispatchGraphics.cpp index 13fc8a17ad6..e5263030163 100644 --- a/src/terminal/adapter/adaptDispatchGraphics.cpp +++ b/src/terminal/adapter/adaptDispatchGraphics.cpp @@ -141,6 +141,7 @@ bool AdaptDispatch::SetGraphicsRendition(const gsl::span