mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-15 02:31:03 +00:00
This PR adds support for the _blink_ graphic rendition attribute. When a
character is output with this attribute set, it "blinks" at a regular
interval, by cycling its color between the normal rendition and a dimmer
shade of that color.
The majority of the blinking mechanism is encapsulated in a new
`BlinkingState` class, which is shared between the Terminal and Conhost
implementations. This class keeps track of the position in the blinking
cycle, which determines whether characters are rendered as normal or
faint.
In Windows Terminal, the state is stored in the `Terminal` class, and in
Conhost it's stored in the `CONSOLE_INFORMATION` class. In both cases,
the `IsBlinkingFaint` method is used to determine the current blinking
rendition, and that is passed on as a parameter to the
`TextAttribute::CalculateRgbColors` method when these classes are
looking up attribute colors.
Prior to calculating the colors, the current attribute is also passed to
the `RecordBlinkingUsage` method, which keeps track of whether there are
actually any blink attributes in use. This is used to determine whether
the screen needs to be refreshed when the blinking cycle toggles between
the normal and faint renditions.
The refresh itself is handled by the `ToggleBlinkingRendition` method,
which is triggered by a timer. In Conhost this is just piggybacking on
the existing cursor blink timer, but in Windows Terminal it needs to
have its own separate timer, since the cursor timer is reset whenever a
key is pressed, which is not something we want for attribute blinking.
Although the `ToggleBlinkingRendition` is called at the same rate as the
cursor blinking, we actually only want the cells to blink at half that
frequency. We thus have a counter that cycles through four phases, and
blinking is rendered as faint for two of those four. Then every two
cycles - when the state changes - a redraw is triggered, but only if
there are actually blinking attributes in use (as previously recorded).
As mentioned earlier, the blinking frequency is based on the cursor
blink rate, so that means it'll automatically be disabled if a user has
set their cursor blink rate to none. It can also be disabled by turning
off the _Show animations in Windows_ option. In Conhost these settings
take effect immediately, but in Windows Terminal they only apply when a
new tab is opened.
This PR also adds partial support for the `SGR 6` _rapid blink_
attribute. This is not used by DEC terminals, but was defined in the
ECMA/ANSI standards. It's not widely supported, but many terminals just
it implement it as an alias for the regular `SGR 5` blink attribute, so
that's what I've done here too.
## Validation Steps Performed
I've checked the _Graphic rendition test pattern_ in Vttest, and
compared our representation of the blink attribute to that of an actual
DEC VT220 terminal as seen on [YouTube]. With the right color scheme
it's a reasonably close match.
[YouTube]: https://www.youtube.com/watch?v=03Pz5AmxbE4&t=1m55s
Closes #7388
(cherry picked from commit d1671a0acd)
401 lines
12 KiB
C++
401 lines
12 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
#include "TextAttribute.hpp"
|
|
#include "../../inc/conattrs.hpp"
|
|
|
|
BYTE TextAttribute::s_legacyDefaultForeground = 7;
|
|
BYTE TextAttribute::s_legacyDefaultBackground = 0;
|
|
|
|
// Routine Description:
|
|
// - Sets the legacy attributes which map to and from the default colors.
|
|
// Parameters:
|
|
// - defaultAttributes: the attribute values to be used for default colors.
|
|
// Return value:
|
|
// - None
|
|
void TextAttribute::SetLegacyDefaultAttributes(const WORD defaultAttributes) noexcept
|
|
{
|
|
s_legacyDefaultForeground = defaultAttributes & FG_ATTRS;
|
|
s_legacyDefaultBackground = (defaultAttributes & BG_ATTRS) >> 4;
|
|
}
|
|
|
|
// Routine Description:
|
|
// Pursuant to GH#6807
|
|
// This routine replaces VT colors from the 16-color set with the "default"
|
|
// flag. It is intended to be used as part of the "VT Quirk" in
|
|
// WriteConsole[AW].
|
|
//
|
|
// There is going to be a very long tail of applications that will
|
|
// explicitly request VT SGR 40/37 when what they really want is to
|
|
// SetConsoleTextAttribute() with a black background/white foreground.
|
|
// Instead of making those applications look bad (and therefore making us
|
|
// look bad, because we're releasing this as an update to something that
|
|
// "looks good" already), we're introducing this compatibility hack. Before
|
|
// the color reckoning in GH#6698 + GH#6506, *every* color was subject to
|
|
// being spontaneously and erroneously turned into the default color. Now,
|
|
// only the 16-color palette value that matches the active console
|
|
// background color will be destroyed when the quirk is enabled.
|
|
//
|
|
// This is not intended to be a long-term solution. This comment will be
|
|
// discovered in forty years(*) time and people will laugh at our hubris.
|
|
//
|
|
// *it doesn't matter when you're reading this, it will always be 40 years
|
|
// from now.
|
|
TextAttribute TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept
|
|
{
|
|
const auto fg{ attribute.GetForeground() };
|
|
const auto bg{ attribute.GetBackground() };
|
|
auto copy{ attribute };
|
|
if (fg.IsIndex16() &&
|
|
attribute.IsBold() == WI_IsFlagSet(s_legacyDefaultForeground, FOREGROUND_INTENSITY) &&
|
|
fg.GetIndex() == (s_legacyDefaultForeground & ~FOREGROUND_INTENSITY))
|
|
{
|
|
// We don't want to turn 1;37m into 39m (or even 1;39m), as this was meant to mimic a legacy color.
|
|
copy.SetDefaultForeground();
|
|
}
|
|
if (bg.IsIndex16() && bg.GetIndex() == s_legacyDefaultBackground)
|
|
{
|
|
copy.SetDefaultBackground();
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Returns a WORD with legacy-style attributes for this textattribute.
|
|
// Parameters:
|
|
// - None
|
|
// Return value:
|
|
// - a WORD with legacy-style attributes for this textattribute.
|
|
WORD TextAttribute::GetLegacyAttributes() const noexcept
|
|
{
|
|
const BYTE fgIndex = _foreground.GetLegacyIndex(s_legacyDefaultForeground);
|
|
const BYTE bgIndex = _background.GetLegacyIndex(s_legacyDefaultBackground);
|
|
const WORD metaAttrs = _wAttrLegacy & META_ATTRS;
|
|
const bool brighten = IsBold() && _foreground.CanBeBrightened();
|
|
return fgIndex | (bgIndex << 4) | metaAttrs | (brighten ? FOREGROUND_INTENSITY : 0);
|
|
}
|
|
|
|
bool TextAttribute::IsLegacy() const noexcept
|
|
{
|
|
return _foreground.IsLegacy() && _background.IsLegacy();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Calculates rgb colors based off of current color table and active modification attributes.
|
|
// Arguments:
|
|
// - colorTable: the current color table rgb values.
|
|
// - 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 bool blinkingIsFaint) const noexcept
|
|
{
|
|
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold());
|
|
auto bg = _background.GetColor(colorTable, defaultBgColor);
|
|
if (IsFaint() || (IsBlinking() && blinkingIsFaint))
|
|
{
|
|
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
|
|
}
|
|
if (IsReverseVideo() ^ reverseScreenMode)
|
|
{
|
|
std::swap(fg, bg);
|
|
}
|
|
if (IsInvisible())
|
|
{
|
|
fg = bg;
|
|
}
|
|
return { fg, bg };
|
|
}
|
|
|
|
// Method description:
|
|
// - Tells us whether the text is a hyperlink or not
|
|
// Return value:
|
|
// - True if it is a hyperlink, false otherwise
|
|
bool TextAttribute::IsHyperlink() const noexcept
|
|
{
|
|
// All non-hyperlink text have a default hyperlinkId of 0 while
|
|
// all hyperlink text have a non-zero hyperlinkId
|
|
return _hyperlinkId != 0;
|
|
}
|
|
|
|
TextColor TextAttribute::GetForeground() const noexcept
|
|
{
|
|
return _foreground;
|
|
}
|
|
|
|
TextColor TextAttribute::GetBackground() const noexcept
|
|
{
|
|
return _background;
|
|
}
|
|
|
|
// Method description:
|
|
// - Retrieves the hyperlink ID of the text
|
|
// Return value:
|
|
// - The hyperlink ID
|
|
uint16_t TextAttribute::GetHyperlinkId() const noexcept
|
|
{
|
|
return _hyperlinkId;
|
|
}
|
|
|
|
void TextAttribute::SetForeground(const TextColor foreground) noexcept
|
|
{
|
|
_foreground = foreground;
|
|
}
|
|
|
|
void TextAttribute::SetBackground(const TextColor background) noexcept
|
|
{
|
|
_background = background;
|
|
}
|
|
|
|
void TextAttribute::SetForeground(const COLORREF rgbForeground) noexcept
|
|
{
|
|
_foreground = TextColor(rgbForeground);
|
|
}
|
|
|
|
void TextAttribute::SetBackground(const COLORREF rgbBackground) noexcept
|
|
{
|
|
_background = TextColor(rgbBackground);
|
|
}
|
|
|
|
void TextAttribute::SetIndexedForeground(const BYTE fgIndex) noexcept
|
|
{
|
|
_foreground = TextColor(fgIndex, false);
|
|
}
|
|
|
|
void TextAttribute::SetIndexedBackground(const BYTE bgIndex) noexcept
|
|
{
|
|
_background = TextColor(bgIndex, false);
|
|
}
|
|
|
|
void TextAttribute::SetIndexedForeground256(const BYTE fgIndex) noexcept
|
|
{
|
|
_foreground = TextColor(fgIndex, true);
|
|
}
|
|
|
|
void TextAttribute::SetIndexedBackground256(const BYTE bgIndex) noexcept
|
|
{
|
|
_background = TextColor(bgIndex, true);
|
|
}
|
|
|
|
void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground) noexcept
|
|
{
|
|
if (fIsForeground)
|
|
{
|
|
SetForeground(rgbColor);
|
|
}
|
|
else
|
|
{
|
|
SetBackground(rgbColor);
|
|
}
|
|
}
|
|
|
|
// Method description:
|
|
// - Sets the hyperlink ID of the text
|
|
// Arguments:
|
|
// - id - the id we wish to set
|
|
void TextAttribute::SetHyperlinkId(uint16_t id) noexcept
|
|
{
|
|
_hyperlinkId = id;
|
|
}
|
|
|
|
bool TextAttribute::IsLeadingByte() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
|
|
}
|
|
|
|
bool TextAttribute::IsTrailingByte() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
|
|
}
|
|
|
|
bool TextAttribute::IsTopHorizontalDisplayed() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL);
|
|
}
|
|
|
|
bool TextAttribute::IsBottomHorizontalDisplayed() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_UNDERSCORE);
|
|
}
|
|
|
|
bool TextAttribute::IsLeftVerticalDisplayed() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_LVERTICAL);
|
|
}
|
|
|
|
bool TextAttribute::IsRightVerticalDisplayed() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_RVERTICAL);
|
|
}
|
|
|
|
void TextAttribute::SetLeftVerticalDisplayed(const bool isDisplayed) noexcept
|
|
{
|
|
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_LVERTICAL, isDisplayed);
|
|
}
|
|
|
|
void TextAttribute::SetRightVerticalDisplayed(const bool isDisplayed) noexcept
|
|
{
|
|
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_RVERTICAL, isDisplayed);
|
|
}
|
|
|
|
bool TextAttribute::IsBold() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Bold);
|
|
}
|
|
|
|
bool TextAttribute::IsFaint() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Faint);
|
|
}
|
|
|
|
bool TextAttribute::IsItalic() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Italics);
|
|
}
|
|
|
|
bool TextAttribute::IsBlinking() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Blinking);
|
|
}
|
|
|
|
bool TextAttribute::IsInvisible() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Invisible);
|
|
}
|
|
|
|
bool TextAttribute::IsCrossedOut() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::CrossedOut);
|
|
}
|
|
|
|
bool TextAttribute::IsUnderlined() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Underlined);
|
|
}
|
|
|
|
bool TextAttribute::IsDoublyUnderlined() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::DoublyUnderlined);
|
|
}
|
|
|
|
bool TextAttribute::IsOverlined() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL);
|
|
}
|
|
|
|
bool TextAttribute::IsReverseVideo() const noexcept
|
|
{
|
|
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
|
}
|
|
|
|
void TextAttribute::SetBold(bool isBold) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Bold, isBold);
|
|
}
|
|
|
|
void TextAttribute::SetFaint(bool isFaint) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Faint, isFaint);
|
|
}
|
|
|
|
void TextAttribute::SetItalic(bool isItalic) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Italics, isItalic);
|
|
}
|
|
|
|
void TextAttribute::SetBlinking(bool isBlinking) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Blinking, isBlinking);
|
|
}
|
|
|
|
void TextAttribute::SetInvisible(bool isInvisible) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Invisible, isInvisible);
|
|
}
|
|
|
|
void TextAttribute::SetCrossedOut(bool isCrossedOut) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::CrossedOut, isCrossedOut);
|
|
}
|
|
|
|
void TextAttribute::SetUnderlined(bool isUnderlined) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Underlined, isUnderlined);
|
|
}
|
|
|
|
void TextAttribute::SetDoublyUnderlined(bool isDoublyUnderlined) noexcept
|
|
{
|
|
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::DoublyUnderlined, isDoublyUnderlined);
|
|
}
|
|
|
|
void TextAttribute::SetOverlined(bool isOverlined) noexcept
|
|
{
|
|
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL, isOverlined);
|
|
}
|
|
|
|
void TextAttribute::SetReverseVideo(bool isReversed) noexcept
|
|
{
|
|
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO, isReversed);
|
|
}
|
|
|
|
ExtendedAttributes TextAttribute::GetExtendedAttributes() const noexcept
|
|
{
|
|
return _extendedAttrs;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - swaps foreground and background color
|
|
void TextAttribute::Invert() noexcept
|
|
{
|
|
WI_ToggleFlag(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
|
}
|
|
|
|
void TextAttribute::SetDefaultForeground() noexcept
|
|
{
|
|
_foreground = TextColor();
|
|
}
|
|
|
|
void TextAttribute::SetDefaultBackground() noexcept
|
|
{
|
|
_background = TextColor();
|
|
}
|
|
|
|
// Method description:
|
|
// - Resets only the meta and extended attributes
|
|
void TextAttribute::SetDefaultMetaAttrs() noexcept
|
|
{
|
|
_extendedAttrs = ExtendedAttributes::Normal;
|
|
_wAttrLegacy = 0;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Returns true if this attribute indicates its background is the "default"
|
|
// background. Its _rgbBackground will contain the actual value of the
|
|
// default background. If the default colors are ever changed, this method
|
|
// should be used to identify attributes with the default bg value, and
|
|
// update them accordingly.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - true iff this attribute indicates it's the "default" background color.
|
|
bool TextAttribute::BackgroundIsDefault() const noexcept
|
|
{
|
|
return _background.IsDefault();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Resets the meta and extended attributes, which is what the VT standard
|
|
// requires for most erasing and filling operations.
|
|
void TextAttribute::SetStandardErase() noexcept
|
|
{
|
|
SetDefaultMetaAttrs();
|
|
_hyperlinkId = 0;
|
|
}
|