Skip to content
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

Add support for the "blink" graphic rendition attribute #7490

Merged
9 commits merged into from Sep 21, 2020
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect/expect.txt
Expand Up @@ -857,6 +857,7 @@ GETAUTOHIDEBAREX
GETCARETWIDTH
getch
getchar
GETCLIENTAREAANIMATION
GETCOMMANDHISTORY
GETCOMMANDHISTORYLENGTH
GETCONSOLEINPUT
Expand Down
6 changes: 4 additions & 2 deletions src/buffer/out/TextAttribute.cpp
Expand Up @@ -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<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> 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.
}
Expand Down
3 changes: 2 additions & 1 deletion src/buffer/out/TextAttribute.hpp
Expand Up @@ -69,7 +69,8 @@ class TextAttribute final
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> 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;
Expand Down
45 changes: 45 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Expand Up @@ -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 },
Expand Down Expand Up @@ -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));
j4james marked this conversation as resolved.
Show resolved Hide resolved
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;

Expand Down Expand Up @@ -1777,6 +1796,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_cursorTimer.value().Start();
}

if (_blinkTimer.has_value())
{
_blinkTimer.value().Start();
}

_UpdateSystemParameterSettings();
}

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Expand Up @@ -201,6 +201,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
std::optional<wchar_t> _leadingSurrogate;

std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer;
std::optional<Windows::UI::Xaml::DispatcherTimer> _blinkTimer;

// If this is set, then we assume we are in the middle of panning the
// viewport via touch input.
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Expand Up @@ -1036,3 +1036,8 @@ const std::optional<til::color> Terminal::GetTabColor() const noexcept
{
return _tabColor;
}

BlinkingState& Terminal::GetBlinkingState() const noexcept
{
return _blinkingState;
}
5 changes: 4 additions & 1 deletion src/cascadia/TerminalCore/Terminal.hpp
Expand Up @@ -6,7 +6,7 @@
#include <conattrs.hpp>

#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"

Expand Down Expand Up @@ -187,6 +187,8 @@ class Microsoft::Terminal::Core::Terminal final :

const std::optional<til::color> GetTabColor() const noexcept;

Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept;

#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
enum class SelectionExpansionMode
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp
Expand Up @@ -132,6 +132,7 @@ bool TerminalDispatch::SetGraphicsRendition(const gsl::span<const DispatchTypes:
attr.SetItalic(false);
break;
case BlinkOrXterm256Index:
case RapidBlink: // We just interpret rapid blink as an alias of blink.
attr.SetBlinking(true);
break;
case Steady:
Expand Down
4 changes: 3 additions & 1 deletion src/cascadia/TerminalCore/terminalrenderdata.cpp
Expand Up @@ -52,10 +52,12 @@ const TextAttribute Terminal::GetDefaultBrushColors() noexcept

std::pair<COLORREF, COLORREF> 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.
Expand Down
16 changes: 13 additions & 3 deletions src/host/CursorBlinker.cpp
Expand Up @@ -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()
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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);
}
Expand Down
35 changes: 33 additions & 2 deletions src/host/consoleInformation.cpp
Expand Up @@ -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() :
Expand Down Expand Up @@ -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:
Expand All @@ -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<COLORREF, COLORREF> CONSOLE_INFORMATION::LookupAttributeColors(const TextAttribute& attr) const noexcept
{
_blinkingState.RecordBlinkingUsage(attr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clever clearing it during a render cycle on-blink and using the function that resolves the color to tell whether we should keep blinking.

It took me a little bit to understand what was going on (and why I was worried that we would keep doing a full redraw every other cursor blink for all eternity), but... I like it.

It might want to be documented in a comment (sorry if it already has: I'm reading bottom-up).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've tried to explain this in the doc comment for RecordBlinkingUsage and then there's more detail in the internal comments of ToggleBlinkingRendition as well.

return attr.CalculateRgbColors(Get256ColorTable(),
GetDefaultForeground(),
GetDefaultBackground(),
IsScreenReversed(),
_blinkingState.IsBlinkingFaint());
}

// Method Description:
Expand Down Expand Up @@ -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:
// - <none>
// 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.
Expand Down
4 changes: 4 additions & 0 deletions src/host/server.h
Expand Up @@ -28,6 +28,7 @@ Revision History:
#include "..\server\WaitQueue.h"

#include "..\host\RenderData.hpp"
#include "..\renderer\inc\BlinkingState.hpp"

// clang-format off
// Flags flags
Expand Down Expand Up @@ -125,6 +126,7 @@ class CONSOLE_INFORMATION :

COLORREF GetDefaultForeground() const noexcept;
COLORREF GetDefaultBackground() const noexcept;
std::pair<COLORREF, COLORREF> LookupAttributeColors(const TextAttribute& attr) const noexcept;

void SetTitle(const std::wstring_view newTitle);
void SetTitlePrefix(const std::wstring& newTitlePrefix);
Expand All @@ -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;

Expand All @@ -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)
Expand Down
45 changes: 0 additions & 45 deletions src/host/settings.cpp
Expand Up @@ -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:
// - <none>
// 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:
// - <none>
// 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<COLORREF, COLORREF> Settings::LookupAttributeColors(const TextAttribute& attr) const noexcept
{
return attr.CalculateRgbColors(Get256ColorTable(),
CalculateDefaultForeground(),
CalculateDefaultBackground(),
_fScreenReversed);
}

bool Settings::GetCopyColor() const noexcept
{
return _fCopyColor;
Expand Down