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

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
This commit is contained in:
James Holderness 2020-09-22 00:21:33 +01:00 committed by GitHub
parent 206131d83a
commit d1671a0acd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 245 additions and 59 deletions

View file

@ -857,6 +857,7 @@ GETAUTOHIDEBAREX
GETCARETWIDTH
getch
getchar
GETCLIENTAREAANIMATION
GETCOMMANDHISTORY
GETCOMMANDHISTORYLENGTH
GETCONSOLEINPUT

View file

@ -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.
}

View file

@ -69,7 +69,8 @@ public:
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;

View file

@ -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:

View file

@ -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.
@ -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);

View file

@ -1036,3 +1036,8 @@ const std::optional<til::color> Terminal::GetTabColor() const noexcept
{
return _tabColor;
}
BlinkingState& Terminal::GetBlinkingState() const noexcept
{
return _blinkingState;
}

View file

@ -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"
@ -187,6 +187,8 @@ public:
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
@ -224,6 +226,7 @@ private:
COLORREF _defaultBg;
CursorType _defaultCursorShape;
bool _screenReversed;
mutable Microsoft::Console::Render::BlinkingState _blinkingState;
bool _snapOnInput;
bool _altGrAliasing;

View file

@ -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:

View file

@ -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.

View file

@ -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);
}

View file

@ -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<COLORREF, COLORREF> 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:
// - <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.

View file

@ -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 @@ public:
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);
@ -141,6 +143,7 @@ public:
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 @@ private:
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)

View file

@ -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;

View file

@ -185,10 +185,6 @@ public:
bool GetUseDx() const noexcept;
bool GetCopyColor() const noexcept;
COLORREF CalculateDefaultForeground() const noexcept;
COLORREF CalculateDefaultBackground() const noexcept;
std::pair<COLORREF, COLORREF> LookupAttributeColors(const TextAttribute& attr) const noexcept;
private:
DWORD _dwHotKey;
DWORD _dwStartupFlags;

View file

@ -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:
// - <none>
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:
// - <none>
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:
// - <none>
// 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:
// - <none>
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()

View file

@ -10,6 +10,7 @@
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="..\BlinkingState.cpp" />
<ClCompile Include="..\Cluster.cpp" />
<ClCompile Include="..\FontInfo.cpp" />
<ClCompile Include="..\FontInfoBase.cpp" />
@ -22,6 +23,7 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\inc\BlinkingState.hpp" />
<ClInclude Include="..\..\inc\Cluster.hpp" />
<ClInclude Include="..\..\inc\FontInfo.hpp" />
<ClInclude Include="..\..\inc\FontInfoBase.hpp" />

View file

@ -39,6 +39,9 @@
<ClCompile Include="..\RenderEngineBase.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\BlinkingState.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Cluster.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -77,6 +80,9 @@
<ClInclude Include="..\..\inc\RenderEngineBase.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\BlinkingState.hpp">
<Filter>Header Files\inc</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\Cluster.hpp">
<Filter>Header Files\inc</Filter>
</ClInclude>

View file

@ -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;
};
}

View file

@ -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,

View file

@ -141,6 +141,7 @@ bool AdaptDispatch::SetGraphicsRendition(const gsl::span<const DispatchTypes::Gr
attr.SetItalic(false);
break;
case BlinkOrXterm256Index:
case RapidBlink: // We just interpret rapid blink as an alias of blink.
attr.SetBlinking(true);
break;
case Steady: