Move all blink handling into Renderer (#19330)

This PR moves the cursor blinker and VT blink rendition timer into
`Renderer`. To do so, this PR introduces a generic timer system with
which you can schedule arbitrary timer jobs. Thanks to this, this PR
removes a crapton of code, particularly throughout conhost.

## Validation Steps Performed
* Focus/unfocus starts/stops blinking 
* OS-wide blink settings apply on focus 
This commit is contained in:
Leonard Hecker 2025-11-11 15:29:48 +01:00 committed by GitHub
parent 47018442cd
commit 2e78665ee0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 1189 additions and 1797 deletions

View File

@ -1759,6 +1759,7 @@ UINTs
uld uld
uldash uldash
uldb uldb
ULONGLONG
ulwave ulwave
Unadvise Unadvise
unattend unattend

View File

@ -13,40 +13,28 @@
// - ulSize - The height of the cursor within this buffer // - ulSize - The height of the cursor within this buffer
Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept : Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept :
_parentBuffer{ parentBuffer }, _parentBuffer{ parentBuffer },
_fIsVisible(true), _ulSize(ulSize)
_fIsOn(true),
_fIsDouble(false),
_fBlinkingAllowed(true),
_fDelay(false),
_fIsConversionArea(false),
_fDelayedEolWrap(false),
_fDeferCursorRedraw(false),
_fHaveDeferredCursorRedraw(false),
_ulSize(ulSize),
_cursorType(CursorType::Legacy)
{ {
} }
Cursor::~Cursor() = default;
til::point Cursor::GetPosition() const noexcept til::point Cursor::GetPosition() const noexcept
{ {
return _cPosition; return _cPosition;
} }
uint64_t Cursor::GetLastMutationId() const noexcept
{
return _mutationId;
}
bool Cursor::IsVisible() const noexcept bool Cursor::IsVisible() const noexcept
{ {
return _fIsVisible; return _isVisible;
} }
bool Cursor::IsOn() const noexcept bool Cursor::IsBlinking() const noexcept
{ {
return _fIsOn; return _isBlinking;
}
bool Cursor::IsBlinkingAllowed() const noexcept
{
return _fBlinkingAllowed;
} }
bool Cursor::IsDouble() const noexcept bool Cursor::IsDouble() const noexcept
@ -54,173 +42,128 @@ bool Cursor::IsDouble() const noexcept
return _fIsDouble; return _fIsDouble;
} }
bool Cursor::IsConversionArea() const noexcept
{
return _fIsConversionArea;
}
bool Cursor::GetDelay() const noexcept
{
return _fDelay;
}
ULONG Cursor::GetSize() const noexcept ULONG Cursor::GetSize() const noexcept
{ {
return _ulSize; return _ulSize;
} }
void Cursor::SetIsVisible(const bool fIsVisible) noexcept void Cursor::SetIsVisible(bool enable) noexcept
{ {
_fIsVisible = fIsVisible; if (_isVisible != enable)
_RedrawCursor(); {
_isVisible = enable;
_redrawIfVisible();
}
} }
void Cursor::SetIsOn(const bool fIsOn) noexcept void Cursor::SetIsBlinking(bool enable) noexcept
{ {
_fIsOn = fIsOn; if (_isBlinking != enable)
_RedrawCursorAlways(); {
} _isBlinking = enable;
_redrawIfVisible();
void Cursor::SetBlinkingAllowed(const bool fBlinkingAllowed) noexcept }
{
_fBlinkingAllowed = fBlinkingAllowed;
// GH#2642 - From what we've gathered from other terminals, when blinking is
// disabled, the cursor should remain On always, and have the visibility
// controlled by the IsVisible property. So when you do a printf "\e[?12l"
// to disable blinking, the cursor stays stuck On. At this point, only the
// cursor visibility property controls whether the user can see it or not.
// (Yes, the cursor can be On and NOT Visible)
_fIsOn = true;
_RedrawCursorAlways();
} }
void Cursor::SetIsDouble(const bool fIsDouble) noexcept void Cursor::SetIsDouble(const bool fIsDouble) noexcept
{ {
if (_fIsDouble != fIsDouble)
{
_fIsDouble = fIsDouble; _fIsDouble = fIsDouble;
_RedrawCursor(); _redrawIfVisible();
} }
void Cursor::SetIsConversionArea(const bool fIsConversionArea) noexcept
{
// Functionally the same as "Hide cursor"
// Never called with TRUE, it's only used in the creation of a
// ConversionAreaInfo, and never changed after that.
_fIsConversionArea = fIsConversionArea;
_RedrawCursorAlways();
}
void Cursor::SetDelay(const bool fDelay) noexcept
{
_fDelay = fDelay;
} }
void Cursor::SetSize(const ULONG ulSize) noexcept void Cursor::SetSize(const ULONG ulSize) noexcept
{ {
if (_ulSize != ulSize)
{
_ulSize = ulSize; _ulSize = ulSize;
_RedrawCursor(); _redrawIfVisible();
}
} }
void Cursor::SetStyle(const ULONG ulSize, const CursorType type) noexcept void Cursor::SetStyle(const ULONG ulSize, const CursorType type) noexcept
{ {
if (_ulSize != ulSize || _cursorType != type)
{
_ulSize = ulSize; _ulSize = ulSize;
_cursorType = type; _cursorType = type;
_redrawIfVisible();
_RedrawCursor();
}
// Routine Description:
// - Sends a redraw message to the renderer only if the cursor is currently on.
// - NOTE: For use with most methods in this class.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Cursor::_RedrawCursor() noexcept
{
// Only trigger the redraw if we're on.
// Don't draw the cursor if this was triggered from a conversion area.
// (Conversion areas have cursors to mark the insertion point internally, but the user's actual cursor is the one on the primary screen buffer.)
if (IsOn() && !IsConversionArea())
{
if (_fDeferCursorRedraw)
{
_fHaveDeferredCursorRedraw = true;
} }
else
{
_RedrawCursorAlways();
}
}
}
// Routine Description:
// - Sends a redraw message to the renderer no matter what.
// - NOTE: For use with the method that turns the cursor on and off to force a refresh
// and clear the ON cursor from the screen. Not for use with other methods.
// They should use the other method so refreshes are suppressed while the cursor is off.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Cursor::_RedrawCursorAlways() noexcept
{
_parentBuffer.NotifyPaintFrame();
} }
void Cursor::SetPosition(const til::point cPosition) noexcept void Cursor::SetPosition(const til::point cPosition) noexcept
{ {
_RedrawCursor(); // The VT code assumes that moving the cursor implicitly resets the delayed EOL wrap,
_cPosition = cPosition; // so we call ResetDelayEOLWrap() independent of _cPosition != cPosition.
_RedrawCursor(); // You can see the effect of this with "`e[1;9999Ha`e[1;9999Hb", which should print just "b".
ResetDelayEOLWrap(); ResetDelayEOLWrap();
if (_cPosition != cPosition)
{
_cPosition = cPosition;
_redrawIfVisible();
}
} }
void Cursor::SetXPosition(const til::CoordType NewX) noexcept void Cursor::SetXPosition(const til::CoordType NewX) noexcept
{ {
_RedrawCursor();
_cPosition.x = NewX;
_RedrawCursor();
ResetDelayEOLWrap(); ResetDelayEOLWrap();
if (_cPosition.x != NewX)
{
_cPosition.x = NewX;
_redrawIfVisible();
}
} }
void Cursor::SetYPosition(const til::CoordType NewY) noexcept void Cursor::SetYPosition(const til::CoordType NewY) noexcept
{ {
_RedrawCursor();
_cPosition.y = NewY;
_RedrawCursor();
ResetDelayEOLWrap(); ResetDelayEOLWrap();
if (_cPosition.y != NewY)
{
_cPosition.y = NewY;
_redrawIfVisible();
}
} }
void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
{ {
_RedrawCursor();
_cPosition.x += DeltaX;
_RedrawCursor();
ResetDelayEOLWrap(); ResetDelayEOLWrap();
if (DeltaX != 0)
{
_cPosition.x = _cPosition.x + DeltaX;
_redrawIfVisible();
}
} }
void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
{ {
_RedrawCursor();
_cPosition.y += DeltaY;
_RedrawCursor();
ResetDelayEOLWrap(); ResetDelayEOLWrap();
if (DeltaY != 0)
{
_cPosition.y = _cPosition.y + DeltaY;
_redrawIfVisible();
}
} }
void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
{ {
_RedrawCursor();
_cPosition.x -= DeltaX;
_RedrawCursor();
ResetDelayEOLWrap(); ResetDelayEOLWrap();
if (DeltaX != 0)
{
_cPosition.x = _cPosition.x - DeltaX;
_redrawIfVisible();
}
} }
void Cursor::DecrementYPosition(const til::CoordType DeltaY) noexcept void Cursor::DecrementYPosition(const til::CoordType DeltaY) noexcept
{ {
_RedrawCursor();
_cPosition.y -= DeltaY;
_RedrawCursor();
ResetDelayEOLWrap(); ResetDelayEOLWrap();
if (DeltaY != 0)
{
_cPosition.y = _cPosition.y - DeltaY;
_redrawIfVisible();
}
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -233,73 +176,33 @@ void Cursor::DecrementYPosition(const til::CoordType DeltaY) noexcept
// - OtherCursor - The cursor to copy properties from // - OtherCursor - The cursor to copy properties from
// Return Value: // Return Value:
// - <none> // - <none>
void Cursor::CopyProperties(const Cursor& OtherCursor) noexcept void Cursor::CopyProperties(const Cursor& other) noexcept
{ {
// We shouldn't copy the position as it will be already rearranged by the resize operation. _cPosition = other._cPosition;
//_cPosition = pOtherCursor->_cPosition; _coordDelayedAt = other._coordDelayedAt;
_ulSize = other._ulSize;
_fIsVisible = OtherCursor._fIsVisible; _cursorType = other._cursorType;
_fIsOn = OtherCursor._fIsOn; _isVisible = other._isVisible;
_fIsDouble = OtherCursor._fIsDouble; _isBlinking = other._isBlinking;
_fBlinkingAllowed = OtherCursor._fBlinkingAllowed; _fIsDouble = other._fIsDouble;
_fDelay = OtherCursor._fDelay;
_fIsConversionArea = OtherCursor._fIsConversionArea;
// A resize operation should invalidate the delayed end of line status, so do not copy.
//_fDelayedEolWrap = OtherCursor._fDelayedEolWrap;
//_coordDelayedAt = OtherCursor._coordDelayedAt;
_fDeferCursorRedraw = OtherCursor._fDeferCursorRedraw;
_fHaveDeferredCursorRedraw = OtherCursor._fHaveDeferredCursorRedraw;
// Size will be handled separately in the resize operation.
//_ulSize = OtherCursor._ulSize;
_cursorType = OtherCursor._cursorType;
} }
void Cursor::DelayEOLWrap() noexcept void Cursor::DelayEOLWrap() noexcept
{ {
_coordDelayedAt = _cPosition; _coordDelayedAt = _cPosition;
_fDelayedEolWrap = true;
} }
void Cursor::ResetDelayEOLWrap() noexcept void Cursor::ResetDelayEOLWrap() noexcept
{ {
_coordDelayedAt = {}; _coordDelayedAt.reset();
_fDelayedEolWrap = false;
} }
til::point Cursor::GetDelayedAtPosition() const noexcept const std::optional<til::point>& Cursor::GetDelayEOLWrap() const noexcept
{ {
return _coordDelayedAt; return _coordDelayedAt;
} }
bool Cursor::IsDelayedEOLWrap() const noexcept CursorType Cursor::GetType() const noexcept
{
return _fDelayedEolWrap;
}
void Cursor::StartDeferDrawing() noexcept
{
_fDeferCursorRedraw = true;
}
bool Cursor::IsDeferDrawing() noexcept
{
return _fDeferCursorRedraw;
}
void Cursor::EndDeferDrawing() noexcept
{
if (_fHaveDeferredCursorRedraw)
{
_RedrawCursorAlways();
}
_fDeferCursorRedraw = FALSE;
}
const CursorType Cursor::GetType() const noexcept
{ {
return _cursorType; return _cursorType;
} }
@ -308,3 +211,18 @@ void Cursor::SetType(const CursorType type) noexcept
{ {
_cursorType = type; _cursorType = type;
} }
void Cursor::_redrawIfVisible() noexcept
{
_mutationId++;
if (_isVisible)
{
_parentBuffer.NotifyPaintFrame();
}
}
void Cursor::_redraw() noexcept
{
_mutationId++;
_parentBuffer.NotifyPaintFrame();
}

View File

@ -29,8 +29,6 @@ public:
Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept; Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept;
~Cursor();
// No Copy. It will copy the timer handle. Bad news. // No Copy. It will copy the timer handle. Bad news.
Cursor(const Cursor&) = delete; Cursor(const Cursor&) = delete;
Cursor& operator=(const Cursor&) & = delete; Cursor& operator=(const Cursor&) & = delete;
@ -38,27 +36,17 @@ public:
Cursor(Cursor&&) = default; Cursor(Cursor&&) = default;
Cursor& operator=(Cursor&&) & = delete; Cursor& operator=(Cursor&&) & = delete;
uint64_t GetLastMutationId() const noexcept;
bool IsVisible() const noexcept; bool IsVisible() const noexcept;
bool IsOn() const noexcept; bool IsBlinking() const noexcept;
bool IsBlinkingAllowed() const noexcept;
bool IsDouble() const noexcept; bool IsDouble() const noexcept;
bool IsConversionArea() const noexcept;
bool GetDelay() const noexcept;
ULONG GetSize() const noexcept; ULONG GetSize() const noexcept;
til::point GetPosition() const noexcept; til::point GetPosition() const noexcept;
CursorType GetType() const noexcept;
const CursorType GetType() const noexcept; void SetIsVisible(bool enable) noexcept;
void SetIsBlinking(bool enable) noexcept;
void StartDeferDrawing() noexcept;
bool IsDeferDrawing() noexcept;
void EndDeferDrawing() noexcept;
void SetIsVisible(const bool fIsVisible) noexcept;
void SetIsOn(const bool fIsOn) noexcept;
void SetBlinkingAllowed(const bool fIsOn) noexcept;
void SetIsDouble(const bool fIsDouble) noexcept; void SetIsDouble(const bool fIsDouble) noexcept;
void SetIsConversionArea(const bool fIsConversionArea) noexcept;
void SetDelay(const bool fDelay) noexcept;
void SetSize(const ULONG ulSize) noexcept; void SetSize(const ULONG ulSize) noexcept;
void SetStyle(const ULONG ulSize, const CursorType type) noexcept; void SetStyle(const ULONG ulSize, const CursorType type) noexcept;
@ -70,41 +58,30 @@ public:
void DecrementXPosition(const til::CoordType DeltaX) noexcept; void DecrementXPosition(const til::CoordType DeltaX) noexcept;
void DecrementYPosition(const til::CoordType DeltaY) noexcept; void DecrementYPosition(const til::CoordType DeltaY) noexcept;
void CopyProperties(const Cursor& OtherCursor) noexcept; void CopyProperties(const Cursor& other) noexcept;
void DelayEOLWrap() noexcept; void DelayEOLWrap() noexcept;
void ResetDelayEOLWrap() noexcept; void ResetDelayEOLWrap() noexcept;
til::point GetDelayedAtPosition() const noexcept; const std::optional<til::point>& GetDelayEOLWrap() const noexcept;
bool IsDelayedEOLWrap() const noexcept;
void SetType(const CursorType type) noexcept; void SetType(const CursorType type) noexcept;
private: private:
void _redrawIfVisible() noexcept;
void _redraw() noexcept;
TextBuffer& _parentBuffer; TextBuffer& _parentBuffer;
//TODO: separate the rendering and text placement //TODO: separate the rendering and text placement
// NOTE: If you are adding a property here, go add it to CopyProperties. // NOTE: If you are adding a property here, go add it to CopyProperties.
uint64_t _mutationId = 0;
til::point _cPosition; // current position on screen (in screen buffer coords). til::point _cPosition; // current position on screen (in screen buffer coords).
std::optional<til::point> _coordDelayedAt; // coordinate the EOL wrap was delayed at.
bool _fIsVisible; // whether cursor is visible (set only through the API)
bool _fIsOn; // whether blinking cursor is on or not
bool _fIsDouble; // whether the cursor size should be doubled
bool _fBlinkingAllowed; //Whether or not the cursor is allowed to blink at all. only set through VT (^[[?12h/l)
bool _fDelay; // don't blink scursor on next timer message
bool _fIsConversionArea; // is attached to a conversion area so it doesn't actually need to display the cursor.
bool _fDelayedEolWrap; // don't wrap at EOL till the next char comes in.
til::point _coordDelayedAt; // coordinate the EOL wrap was delayed at.
bool _fDeferCursorRedraw; // whether we should defer redrawing the cursor or not
bool _fHaveDeferredCursorRedraw; // have we been asked to redraw the cursor while it was being deferred?
ULONG _ulSize; ULONG _ulSize;
CursorType _cursorType = CursorType::Legacy;
void _RedrawCursor() noexcept; bool _isVisible = true;
void _RedrawCursorAlways() noexcept; bool _isBlinking = true;
bool _fIsDouble = false; // whether the cursor size should be doubled
CursorType _cursorType;
}; };

View File

@ -1883,7 +1883,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (read < sizeof(buffer)) if (read < sizeof(buffer))
{ {
// Normally the cursor should already be at the start of the line, but let's be absolutely sure it is. // Normally the cursor should already be at the start of the line, but let's be absolutely sure it is.
if (_terminal->GetCursorPosition().x != 0) if (_terminal->GetTextBuffer().GetCursor().GetPosition().x != 0)
{ {
_terminal->Write(L"\r\n"); _terminal->Write(L"\r\n");
} }
@ -1938,31 +1938,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TabColorChanged.raise(*this, nullptr); TabColorChanged.raise(*this, nullptr);
} }
void ControlCore::BlinkAttributeTick()
{
const auto lock = _terminal->LockForWriting();
auto& renderSettings = _terminal->GetRenderSettings();
renderSettings.ToggleBlinkRendition(_renderer.get());
}
void ControlCore::BlinkCursor()
{
const auto lock = _terminal->LockForWriting();
_terminal->BlinkCursor();
}
bool ControlCore::CursorOn() const
{
return _terminal->IsCursorOn();
}
void ControlCore::CursorOn(const bool isCursorOn)
{
const auto lock = _terminal->LockForWriting();
_terminal->SetCursorOn(isCursorOn);
}
void ControlCore::ResumeRendering() void ControlCore::ResumeRendering()
{ {
// The lock must be held, because it calls into IRenderData which is shared state. // The lock must be held, because it calls into IRenderData which is shared state.
@ -1994,6 +1969,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _terminal->GetViewportRelativeCursorPosition().to_core_point(); return _terminal->GetViewportRelativeCursorPosition().to_core_point();
} }
bool ControlCore::ForceCursorVisible() const noexcept
{
return _forceCursorVisible;
}
void ControlCore::ForceCursorVisible(bool force)
{
const auto lock = _terminal->LockForWriting();
_renderer->AllowCursorVisibility(Render::InhibitionSource::Host, _terminal->IsFocused() || force);
_forceCursorVisible = force;
}
// This one's really pushing the boundary of what counts as "encapsulation". // This one's really pushing the boundary of what counts as "encapsulation".
// It really belongs in the "Interactivity" layer, which doesn't yet exist. // It really belongs in the "Interactivity" layer, which doesn't yet exist.
// There's so many accesses to the selection in the Core though, that I just // There's so many accesses to the selection in the Core though, that I just
@ -2073,7 +2060,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// //
// As noted in GH #8573, there's plenty of edge cases with this // As noted in GH #8573, there's plenty of edge cases with this
// approach, but it's good enough to bring value to 90% of use cases. // approach, but it's good enough to bring value to 90% of use cases.
const auto cursorPos{ _terminal->GetCursorPosition() };
// Does the current buffer line have a mark on it? // Does the current buffer line have a mark on it?
const auto& marks{ _terminal->GetMarkExtents() }; const auto& marks{ _terminal->GetMarkExtents() };
@ -2081,8 +2067,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ {
const auto& last{ marks.back() }; const auto& last{ marks.back() };
const auto [start, end] = last.GetExtent(); const auto [start, end] = last.GetExtent();
const auto bufferSize = _terminal->GetTextBuffer().GetSize(); const auto& buffer = _terminal->GetTextBuffer();
auto lastNonSpace = _terminal->GetTextBuffer().GetLastNonSpaceCharacter(); const auto cursorPos = buffer.GetCursor().GetPosition();
const auto bufferSize = buffer.GetSize();
auto lastNonSpace = buffer.GetLastNonSpaceCharacter();
bufferSize.IncrementInBounds(lastNonSpace, true); bufferSize.IncrementInBounds(lastNonSpace, true);
// If the user clicked off to the right side of the prompt, we // If the user clicked off to the right side of the prompt, we
@ -2460,7 +2448,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ {
TerminalInput::OutputType out; TerminalInput::OutputType out;
{ {
const auto lock = _terminal->LockForReading(); const auto lock = _terminal->LockForWriting();
_renderer->AllowCursorVisibility(Render::InhibitionSource::Host, focused || _forceCursorVisible);
out = _terminal->FocusChanged(focused); out = _terminal->FocusChanged(focused);
} }
if (out && !out->empty()) if (out && !out->empty())

View File

@ -214,14 +214,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
#pragma endregion #pragma endregion
void BlinkAttributeTick();
void BlinkCursor();
bool CursorOn() const;
void CursorOn(const bool isCursorOn);
bool IsVtMouseModeEnabled() const; bool IsVtMouseModeEnabled() const;
bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const; bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const;
Core::Point CursorPosition() const; Core::Point CursorPosition() const;
bool ForceCursorVisible() const noexcept;
void ForceCursorVisible(bool force);
bool CopyOnSelect() const; bool CopyOnSelect() const;
Control::SelectionData SelectionInfo() const; Control::SelectionData SelectionInfo() const;
@ -401,6 +398,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
float _panelWidth{ 0 }; float _panelWidth{ 0 };
float _panelHeight{ 0 }; float _panelHeight{ 0 };
float _compositionScale{ 0 }; float _compositionScale{ 0 };
bool _forceCursorVisible = false;
// Audio stuff. // Audio stuff.
MidiAudio _midiAudio; MidiAudio _midiAudio;

View File

@ -150,8 +150,8 @@ namespace Microsoft.Terminal.Control
void SetReadOnlyMode(Boolean readOnlyState); void SetReadOnlyMode(Boolean readOnlyState);
Microsoft.Terminal.Core.Point CursorPosition { get; }; Microsoft.Terminal.Core.Point CursorPosition { get; };
Boolean ForceCursorVisible;
void ResumeRendering(); void ResumeRendering();
void BlinkAttributeTick();
SearchResults Search(SearchRequest request); SearchResults Search(SearchRequest request);
void ClearSearch(); void ClearSearch();
@ -165,9 +165,7 @@ namespace Microsoft.Terminal.Control
String HoveredUriText { get; }; String HoveredUriText { get; };
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; }; Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
void BlinkCursor();
Boolean IsInReadOnlyMode { get; }; Boolean IsInReadOnlyMode { get; };
Boolean CursorOn;
void EnablePainting(); void EnablePainting();
String ReadEntireBuffer(); String ReadEntireBuffer();

View File

@ -63,7 +63,7 @@ RECT HwndTerminal::TsfDataProvider::GetCursorPosition()
til::size fontSize; til::size fontSize;
{ {
const auto lock = _terminal->_terminal->LockForReading(); const auto lock = _terminal->_terminal->LockForReading();
cursorPos = _terminal->_terminal->GetCursorPosition(); // measured in terminal cells cursorPos = _terminal->_terminal->GetTextBuffer().GetCursor().GetPosition(); // measured in terminal cells
fontSize = _terminal->_actualFont.GetSize(); // measured in pixels, not DIP fontSize = _terminal->_actualFont.GetSize(); // measured in pixels, not DIP
} }
POINT ptSuggestion = { POINT ptSuggestion = {
@ -706,15 +706,6 @@ void HwndTerminal::_ClearSelection()
_renderer->TriggerSelection(); _renderer->TriggerSelection();
} }
void _stdcall TerminalClearSelection(void* terminal)
try
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
const auto lock = publicTerminal->_terminal->LockForWriting();
publicTerminal->_ClearSelection();
}
CATCH_LOG()
bool _stdcall TerminalIsSelectionActive(void* terminal) bool _stdcall TerminalIsSelectionActive(void* terminal)
try try
{ {
@ -986,61 +977,53 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->Refresh(windowSize, &dimensions); publicTerminal->Refresh(windowSize, &dimensions);
} }
void _stdcall TerminalBlinkCursor(void* terminal) void __stdcall TerminalSetFocused(void* terminal, bool focused)
try
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return;
}
const auto lock = publicTerminal->_terminal->LockForWriting();
publicTerminal->_terminal->BlinkCursor();
}
CATCH_LOG()
void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible)
try
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return;
}
const auto lock = publicTerminal->_terminal->LockForWriting();
publicTerminal->_terminal->SetCursorOn(visible);
}
CATCH_LOG()
void __stdcall TerminalSetFocus(void* terminal)
{ {
const auto publicTerminal = static_cast<HwndTerminal*>(terminal); const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_focused = true; publicTerminal->_setFocused(focused);
if (auto uiaEngine = publicTerminal->_uiaEngine.get())
{
LOG_IF_FAILED(uiaEngine->Enable());
}
publicTerminal->_FocusTSF();
} }
void HwndTerminal::_FocusTSF() noexcept void HwndTerminal::_setFocused(bool focused) noexcept
{ {
if (_focused == focused)
{
return;
}
TerminalInput::OutputType out;
{
const auto lock = _terminal->LockForWriting();
_focused = focused;
if (focused)
{
if (!_tsfHandle) if (!_tsfHandle)
{ {
_tsfHandle = Microsoft::Console::TSF::Handle::Create(); _tsfHandle = Microsoft::Console::TSF::Handle::Create();
_tsfHandle.AssociateFocus(&_tsfDataProvider); _tsfHandle.AssociateFocus(&_tsfDataProvider);
} }
}
void __stdcall TerminalKillFocus(void* terminal) if (const auto uiaEngine = _uiaEngine.get())
{ {
const auto publicTerminal = static_cast<HwndTerminal*>(terminal); LOG_IF_FAILED(uiaEngine->Enable());
publicTerminal->_focused = false; }
if (auto uiaEngine = publicTerminal->_uiaEngine.get()) }
else
{
if (const auto uiaEngine = _uiaEngine.get())
{ {
LOG_IF_FAILED(uiaEngine->Disable()); LOG_IF_FAILED(uiaEngine->Disable());
} }
}
_renderer->AllowCursorVisibility(Microsoft::Console::Render::InhibitionSource::Host, focused);
out = _terminal->FocusChanged(focused);
}
if (out)
{
_WriteTextToConnection(*out);
}
} }
// Routine Description: // Routine Description:

View File

@ -49,7 +49,6 @@ __declspec(dllexport) HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ v
__declspec(dllexport) HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ til::CoordType width, _In_ til::CoordType height, _Out_ til::size* dimensions); __declspec(dllexport) HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ til::CoordType width, _In_ til::CoordType height, _Out_ til::size* dimensions);
__declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi); __declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
__declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop); __declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop);
__declspec(dllexport) void _stdcall TerminalClearSelection(void* terminal);
__declspec(dllexport) const wchar_t* _stdcall TerminalGetSelection(void* terminal); __declspec(dllexport) const wchar_t* _stdcall TerminalGetSelection(void* terminal);
__declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal); __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal); __declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
@ -57,10 +56,7 @@ __declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalThe
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*)); __declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown); __declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD flags, WORD scanCode); __declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD flags, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal); __declspec(dllexport) void _stdcall TerminalSetFocused(void* terminal, bool focused);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
__declspec(dllexport) void _stdcall TerminalSetFocus(void* terminal);
__declspec(dllexport) void _stdcall TerminalKillFocus(void* terminal);
}; };
struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
@ -91,9 +87,9 @@ private:
TsfDataProvider(HwndTerminal* t) : TsfDataProvider(HwndTerminal* t) :
_terminal(t) {} _terminal(t) {}
virtual ~TsfDataProvider() = default; virtual ~TsfDataProvider() = default;
STDMETHODIMP TsfDataProvider::QueryInterface(REFIID, void**) noexcept override; STDMETHODIMP QueryInterface(REFIID, void**) noexcept override;
ULONG STDMETHODCALLTYPE TsfDataProvider::AddRef() noexcept override; ULONG STDMETHODCALLTYPE AddRef() noexcept override;
ULONG STDMETHODCALLTYPE TsfDataProvider::Release() noexcept override; ULONG STDMETHODCALLTYPE Release() noexcept override;
HWND GetHwnd() override; HWND GetHwnd() override;
RECT GetViewport() override; RECT GetViewport() override;
RECT GetCursorPosition() override; RECT GetCursorPosition() override;
@ -132,16 +128,12 @@ private:
friend HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ til::CoordType width, _In_ til::CoordType height, _Out_ til::size* dimensions); friend HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ til::CoordType width, _In_ til::CoordType height, _Out_ til::size* dimensions);
friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi); friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
friend void _stdcall TerminalUserScroll(void* terminal, int viewTop); friend void _stdcall TerminalUserScroll(void* terminal, int viewTop);
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal); friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
friend bool _stdcall TerminalIsSelectionActive(void* terminal); friend bool _stdcall TerminalIsSelectionActive(void* terminal);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown); friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags); friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags);
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, til::CoordType fontSize, int newDpi); friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, til::CoordType fontSize, int newDpi);
friend void _stdcall TerminalBlinkCursor(void* terminal); friend void _stdcall TerminalSetFocused(void* terminal, bool focused);
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
friend void _stdcall TerminalSetFocus(void* terminal);
friend void _stdcall TerminalKillFocus(void* terminal);
void _UpdateFont(int newDpi); void _UpdateFont(int newDpi);
void _WriteTextToConnection(const std::wstring_view text) noexcept; void _WriteTextToConnection(const std::wstring_view text) noexcept;
@ -149,7 +141,7 @@ private:
HRESULT _CopyToSystemClipboard(wil::zstring_view stringToCopy, LPCWSTR lpszFormat) const; HRESULT _CopyToSystemClipboard(wil::zstring_view stringToCopy, LPCWSTR lpszFormat) const;
void _PasteTextFromClipboard() noexcept; void _PasteTextFromClipboard() noexcept;
void _FocusTSF() noexcept; void _setFocused(bool focused) noexcept;
const unsigned int _NumberOfClicks(til::point clickPos, std::chrono::steady_clock::time_point clickTime) noexcept; const unsigned int _NumberOfClicks(til::point clickPos, std::chrono::steady_clock::time_point clickTime) noexcept;
HRESULT _StartSelection(LPARAM lParam) noexcept; HRESULT _StartSelection(LPARAM lParam) noexcept;

View File

@ -1401,51 +1401,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
ScrollBar().ViewportSize(bufferHeight); ScrollBar().ViewportSize(bufferHeight);
ScrollBar().LargeChange(bufferHeight); // scroll one "screenful" at a time when the scroll bar is clicked ScrollBar().LargeChange(bufferHeight); // scroll one "screenful" at a time when the scroll bar is clicked
// Set up blinking cursor
int blinkTime = GetCaretBlinkTime();
if (blinkTime != INFINITE)
{
// Create a timer
_cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
_cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
// As of GH#6586, don't start the cursor timer immediately, and
// don't show the cursor initially. We'll show the cursor and start
// the timer when the control is first focused.
//
// As of GH#11411, turn on the cursor if we've already been marked
// as focused. We suspect that it's possible for the Focused event
// to fire before the LayoutUpdated. In that case, the
// _GotFocusHandler would mark us _focused, but find that a
// _cursorTimer doesn't exist, and it would never turn on the
// cursor. To mitigate, we'll initialize the cursor's 'on' state
// with `_focused` here.
_core.CursorOn(_focused || _displayCursorWhileBlurred());
if (_displayCursorWhileBlurred())
{
_cursorTimer.Start();
}
}
else
{
_cursorTimer.Destroy();
}
// Set up blinking attributes
auto animationsEnabled = TRUE;
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
if (animationsEnabled && blinkTime != INFINITE)
{
// Create a timer
_blinkTimer.Interval(std::chrono::milliseconds(blinkTime));
_blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick });
_blinkTimer.Start();
}
else
{
// The user has disabled blinking
_blinkTimer.Destroy();
}
// Now that the renderer is set up, update the appearance for initialization // Now that the renderer is set up, update the appearance for initialization
_UpdateAppearanceFromUIThread(_core.FocusedAppearance()); _UpdateAppearanceFromUIThread(_core.FocusedAppearance());
@ -1938,14 +1893,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
get_self<TermControlAutomationPeer>(_automationPeer)->RecordKeyEvent(vkey); get_self<TermControlAutomationPeer>(_automationPeer)->RecordKeyEvent(vkey);
} }
if (_cursorTimer)
{
// Manually show the cursor when a key is pressed. Restarting
// the timer prevents flickering.
_core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark);
_cursorTimer.Start();
}
return handled; return handled;
} }
@ -2403,17 +2350,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ {
return; return;
} }
if (_cursorTimer)
{
// When the terminal focuses, show the cursor immediately
_core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark);
_cursorTimer.Start();
}
if (_blinkTimer)
{
_blinkTimer.Start();
}
// Only update the appearance here if an unfocused config exists - if an // Only update the appearance here if an unfocused config exists - if an
// unfocused config does not exist then we never would have switched // unfocused config does not exist then we never would have switched
@ -2450,17 +2386,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_interactivity.LostFocus(); _interactivity.LostFocus();
} }
if (_cursorTimer && !_displayCursorWhileBlurred())
{
_cursorTimer.Stop();
_core.CursorOn(false);
}
if (_blinkTimer)
{
_blinkTimer.Stop();
}
// Check if there is an unfocused config we should set the appearance to // Check if there is an unfocused config we should set the appearance to
// upon losing focus // upon losing focus
if (_core.HasUnfocusedAppearance()) if (_core.HasUnfocusedAppearance())
@ -2528,34 +2453,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.ScaleChanged(scaleX); _core.ScaleChanged(scaleX);
} }
// Method Description:
// - Toggle the cursor on and off when called by the cursor blink timer.
// Arguments:
// - sender: not used
// - e: not used
void TermControl::_CursorTimerTick(const Windows::Foundation::IInspectable& /* sender */,
const Windows::Foundation::IInspectable& /* e */)
{
if (!_IsClosing())
{
_core.BlinkCursor();
}
}
// Method Description:
// - Toggle the blinking rendition state when called by the blink timer.
// Arguments:
// - sender: not used
// - e: not used
void TermControl::_BlinkTimerTick(const Windows::Foundation::IInspectable& /* sender */,
const Windows::Foundation::IInspectable& /* e */)
{
if (!_IsClosing())
{
_core.BlinkAttributeTick();
}
}
// Method Description: // Method Description:
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging. // - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
// Arguments: // Arguments:
@ -2724,8 +2621,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// while the thread is supposed to be idle. Stop these timers avoids this. // while the thread is supposed to be idle. Stop these timers avoids this.
_autoScrollTimer.Stop(); _autoScrollTimer.Stop();
_bellLightTimer.Stop(); _bellLightTimer.Stop();
_cursorTimer.Stop();
_blinkTimer.Stop();
// This is absolutely crucial, as the TSF code tries to hold a strong reference to _tsfDataProvider, // This is absolutely crucial, as the TSF code tries to hold a strong reference to _tsfDataProvider,
// but right now _tsfDataProvider implements IUnknown as a no-op. This ensures that TSF stops referencing us. // but right now _tsfDataProvider implements IUnknown as a no-op. This ensures that TSF stops referencing us.
@ -4168,44 +4063,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.ContextMenuSelectOutput(); _core.ContextMenuSelectOutput();
} }
// Should the text cursor be displayed, even when the control isn't focused?
// n.b. "blur" is the opposite of "focus".
bool TermControl::_displayCursorWhileBlurred() const noexcept
{
return CursorVisibility() == Control::CursorDisplayState::Shown;
}
Control::CursorDisplayState TermControl::CursorVisibility() const noexcept Control::CursorDisplayState TermControl::CursorVisibility() const noexcept
{ {
return _cursorVisibility; return _cursorVisibility;
} }
void TermControl::CursorVisibility(Control::CursorDisplayState cursorVisibility) void TermControl::CursorVisibility(Control::CursorDisplayState cursorVisibility)
{ {
_cursorVisibility = cursorVisibility; _cursorVisibility = cursorVisibility;
if (!_initializedTerminal)
{
return;
}
if (_displayCursorWhileBlurred()) // NOTE: This code is specific to broadcast input. It's never been well integrated.
// Ideally TermControl should not tie focus to XAML in the first place,
// allowing us to truly say "yeah these two controls both have focus".
if (_core)
{ {
// If we should be ALWAYS displaying the cursor, turn it on and start blinking. _core.ForceCursorVisible(cursorVisibility == CursorDisplayState::Shown);
_core.CursorOn(true);
if (_cursorTimer)
{
_cursorTimer.Start();
}
}
else
{
// Otherwise, if we're unfocused, then turn the cursor off and stop
// blinking. (if we're focused, then we're already doing the right
// thing)
const auto focused = FocusState() != FocusState::Unfocused;
if (!focused && _cursorTimer)
{
_cursorTimer.Stop();
}
_core.CursorOn(focused);
} }
} }
} }

View File

@ -313,9 +313,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellDarkAnimation{ nullptr }; winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellDarkAnimation{ nullptr };
SafeDispatcherTimer _bellLightTimer; SafeDispatcherTimer _bellLightTimer;
SafeDispatcherTimer _cursorTimer;
SafeDispatcherTimer _blinkTimer;
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
winrt::hstring _restorePath; winrt::hstring _restorePath;
bool _showMarksInScrollbar{ false }; bool _showMarksInScrollbar{ false };
@ -387,8 +384,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
safe_void_coroutine _HyperlinkHandler(Windows::Foundation::IInspectable sender, Control::OpenHyperlinkEventArgs e); safe_void_coroutine _HyperlinkHandler(Windows::Foundation::IInspectable sender, Control::OpenHyperlinkEventArgs e);
void _CursorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e);
void _BlinkTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e);
void _BellLightOff(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); void _BellLightOff(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e);
void _SetEndSelectionPointAtCursor(const Windows::Foundation::Point& cursorPosition); void _SetEndSelectionPointAtCursor(const Windows::Foundation::Point& cursorPosition);
@ -450,7 +445,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _SelectCommandHandler(const IInspectable& sender, const IInspectable& args); void _SelectCommandHandler(const IInspectable& sender, const IInspectable& args);
void _SelectOutputHandler(const IInspectable& sender, const IInspectable& args); void _SelectOutputHandler(const IInspectable& sender, const IInspectable& args);
bool _displayCursorWhileBlurred() const noexcept;
struct Revokers struct Revokers
{ {

View File

@ -6,20 +6,16 @@ EXPORTS
; Flat C ABI ; Flat C ABI
CreateTerminal CreateTerminal
DestroyTerminal DestroyTerminal
TerminalBlinkCursor
TerminalCalculateResize TerminalCalculateResize
TerminalClearSelection
TerminalDpiChanged TerminalDpiChanged
TerminalGetSelection TerminalGetSelection
TerminalIsSelectionActive TerminalIsSelectionActive
TerminalKillFocus
TerminalRegisterScrollCallback TerminalRegisterScrollCallback
TerminalRegisterWriteCallback TerminalRegisterWriteCallback
TerminalSendCharEvent TerminalSendCharEvent
TerminalSendKeyEvent TerminalSendKeyEvent
TerminalSendOutput TerminalSendOutput
TerminalSetCursorVisible TerminalSetFocused
TerminalSetFocus
TerminalSetTheme TerminalSetTheme
TerminalTriggerResize TerminalTriggerResize
TerminalTriggerResizeWithDimension TerminalTriggerResizeWithDimension

View File

@ -784,6 +784,19 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s
// - none // - none
TerminalInput::OutputType Terminal::FocusChanged(const bool focused) TerminalInput::OutputType Terminal::FocusChanged(const bool focused)
{ {
if (_focused == focused)
{
return {};
}
_focused = focused;
// Recalculate the IRenderData::GetBlinkInterval() on the next call.
if (focused)
{
_cursorBlinkInterval.reset();
}
return _getTerminalInput().HandleFocus(focused); return _getTerminalInput().HandleFocus(focused);
} }
@ -1021,6 +1034,11 @@ int Terminal::ViewEndIndex() const noexcept
return _inAltBuffer() ? _altBufferSize.height - 1 : _mutableViewport.BottomInclusive(); return _inAltBuffer() ? _altBufferSize.height - 1 : _mutableViewport.BottomInclusive();
} }
bool Terminal::IsFocused() const noexcept
{
return _focused;
}
RenderSettings& Terminal::GetRenderSettings() noexcept RenderSettings& Terminal::GetRenderSettings() noexcept
{ {
_assertLocked(); _assertLocked();
@ -1182,31 +1200,6 @@ void Terminal::SetPlayMidiNoteCallback(std::function<void(const int, const int,
_pfnPlayMidiNote.swap(pfn); _pfnPlayMidiNote.swap(pfn);
} }
void Terminal::BlinkCursor() noexcept
{
if (_selectionMode != SelectionInteractionMode::Mark)
{
auto& cursor = _activeBuffer().GetCursor();
if (cursor.IsBlinkingAllowed() && cursor.IsVisible())
{
cursor.SetIsOn(!cursor.IsOn());
}
}
}
// Method Description:
// - Sets the cursor to be currently on. On/Off is tracked independently of
// cursor visibility (hidden/visible). On/off is controlled by the cursor
// blinker. Visibility is usually controlled by the client application. If the
// cursor is hidden, then the cursor will remain hidden. If the cursor is
// Visible, then it will immediately become visible.
// Arguments:
// - isVisible: whether the cursor should be visible
void Terminal::SetCursorOn(const bool isOn) noexcept
{
_activeBuffer().GetCursor().SetIsOn(isOn);
}
// Method Description: // Method Description:
// - Update our internal knowledge about where regex patterns are on the screen // - Update our internal knowledge about where regex patterns are on the screen
// - This is called by TerminalControl (through a throttled function) when the visible // - This is called by TerminalControl (through a throttled function) when the visible
@ -1580,7 +1573,7 @@ void Terminal::ColorSelection(const TextAttribute& attr, winrt::Microsoft::Termi
// - Returns the position of the cursor relative to the visible viewport // - Returns the position of the cursor relative to the visible viewport
til::point Terminal::GetViewportRelativeCursorPosition() const noexcept til::point Terminal::GetViewportRelativeCursorPosition() const noexcept
{ {
const auto absoluteCursorPosition{ GetCursorPosition() }; const auto absoluteCursorPosition{ _activeBuffer().GetCursor().GetPosition() };
const auto mutableViewport{ _GetMutableViewport() }; const auto mutableViewport{ _GetMutableViewport() };
const auto relativeCursorPos = absoluteCursorPosition - mutableViewport.Origin(); const auto relativeCursorPos = absoluteCursorPosition - mutableViewport.Origin();
return { relativeCursorPos.x, relativeCursorPos.y + _scrollOffset }; return { relativeCursorPos.x, relativeCursorPos.y + _scrollOffset };

View File

@ -114,6 +114,7 @@ public:
int ViewStartIndex() const noexcept; int ViewStartIndex() const noexcept;
int ViewEndIndex() const noexcept; int ViewEndIndex() const noexcept;
bool IsFocused() const noexcept;
RenderSettings& GetRenderSettings() noexcept; RenderSettings& GetRenderSettings() noexcept;
const RenderSettings& GetRenderSettings() const noexcept; const RenderSettings& GetRenderSettings() const noexcept;
@ -198,30 +199,25 @@ public:
void UnlockConsole() noexcept override; void UnlockConsole() noexcept override;
// These methods are defined in TerminalRenderData.cpp // These methods are defined in TerminalRenderData.cpp
til::point GetCursorPosition() const noexcept override; Microsoft::Console::Render::TimerDuration GetBlinkInterval() noexcept override;
bool IsCursorVisible() const noexcept override;
bool IsCursorOn() const noexcept override;
ULONG GetCursorHeight() const noexcept override;
ULONG GetCursorPixelWidth() const noexcept override; ULONG GetCursorPixelWidth() const noexcept override;
CursorType GetCursorStyle() const noexcept override; bool IsGridLineDrawingAllowed() noexcept override;
bool IsCursorDoubleWidth() const override; std::wstring GetHyperlinkUri(uint16_t id) const override;
const bool IsGridLineDrawingAllowed() noexcept override; std::wstring GetHyperlinkCustomId(uint16_t id) const override;
const std::wstring GetHyperlinkUri(uint16_t id) const override; std::vector<size_t> GetPatternId(const til::point location) const override;
const std::wstring GetHyperlinkCustomId(uint16_t id) const override;
const std::vector<size_t> GetPatternId(const til::point location) const override;
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override; std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override;
std::span<const til::point_span> GetSelectionSpans() const noexcept override; std::span<const til::point_span> GetSelectionSpans() const noexcept override;
std::span<const til::point_span> GetSearchHighlights() const noexcept override; std::span<const til::point_span> GetSearchHighlights() const noexcept override;
const til::point_span* GetSearchHighlightFocused() const noexcept override; const til::point_span* GetSearchHighlightFocused() const noexcept override;
const bool IsSelectionActive() const noexcept override; bool IsSelectionActive() const noexcept override;
const bool IsBlockSelection() const noexcept override; bool IsBlockSelection() const noexcept override;
void ClearSelection() override; void ClearSelection() override;
void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override; void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override;
const til::point GetSelectionAnchor() const noexcept override; til::point GetSelectionAnchor() const noexcept override;
const til::point GetSelectionEnd() const noexcept override; til::point GetSelectionEnd() const noexcept override;
const std::wstring_view GetConsoleTitle() const noexcept override; std::wstring_view GetConsoleTitle() const noexcept override;
const bool IsUiaDataInitialized() const noexcept override; bool IsUiaDataInitialized() const noexcept override;
#pragma endregion #pragma endregion
void SetWriteInputCallback(std::function<void(std::wstring_view)> pfn) noexcept; void SetWriteInputCallback(std::function<void(std::wstring_view)> pfn) noexcept;
@ -240,9 +236,6 @@ public:
void SetSearchHighlightFocused(size_t focusedIdx) noexcept; void SetSearchHighlightFocused(size_t focusedIdx) noexcept;
void ScrollToSearchHighlight(til::CoordType searchScrollOffset); void ScrollToSearchHighlight(til::CoordType searchScrollOffset);
void BlinkCursor() noexcept;
void SetCursorOn(const bool isOn) noexcept;
void UpdatePatternsUnderLock(); void UpdatePatternsUnderLock();
const std::optional<til::color> GetTabColor() const; const std::optional<til::color> GetTabColor() const;
@ -316,7 +309,7 @@ public:
UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const noexcept; UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const noexcept;
til::point SelectionStartForRendering() const; til::point SelectionStartForRendering() const;
til::point SelectionEndForRendering() const; til::point SelectionEndForRendering() const;
const SelectionEndpoint SelectionEndpointTarget() const noexcept; SelectionEndpoint SelectionEndpointTarget() const noexcept;
TextCopyData RetrieveSelectedTextFromBuffer(const bool singleLine, const bool withControlSequences = false, const bool html = false, const bool rtf = false) const; TextCopyData RetrieveSelectedTextFromBuffer(const bool singleLine, const bool withControlSequences = false, const bool html = false, const bool rtf = false) const;
#pragma endregion #pragma endregion
@ -363,9 +356,11 @@ private:
mutable til::generation_t _lastSelectionGeneration{}; mutable til::generation_t _lastSelectionGeneration{};
CursorType _defaultCursorShape = CursorType::Legacy; CursorType _defaultCursorShape = CursorType::Legacy;
std::optional<Microsoft::Console::Render::TimerDuration> _cursorBlinkInterval;
til::enumset<Mode> _systemMode{ Mode::AutoWrap }; til::enumset<Mode> _systemMode{ Mode::AutoWrap };
bool _focused = false;
bool _snapOnInput = true; bool _snapOnInput = true;
bool _altGrAliasing = true; bool _altGrAliasing = true;
bool _suppressApplicationTitle = false; bool _suppressApplicationTitle = false;

View File

@ -249,7 +249,7 @@ void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs)
auto& tgtCursor = _altBuffer->GetCursor(); auto& tgtCursor = _altBuffer->GetCursor();
tgtCursor.SetStyle(myCursor.GetSize(), myCursor.GetType()); tgtCursor.SetStyle(myCursor.GetSize(), myCursor.GetType());
tgtCursor.SetIsVisible(myCursor.IsVisible()); tgtCursor.SetIsVisible(myCursor.IsVisible());
tgtCursor.SetBlinkingAllowed(myCursor.IsBlinkingAllowed()); tgtCursor.SetIsBlinking(myCursor.IsBlinking());
// The new position should match the viewport-relative position of the main buffer. // The new position should match the viewport-relative position of the main buffer.
auto tgtCursorPos = myCursor.GetPosition(); auto tgtCursorPos = myCursor.GetPosition();
@ -307,7 +307,7 @@ void Terminal::UseMainScreenBuffer()
mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType()); mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType());
mainCursor.SetIsVisible(altCursor.IsVisible()); mainCursor.SetIsVisible(altCursor.IsVisible());
mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed()); mainCursor.SetIsBlinking(altCursor.IsBlinking());
auto tgtCursorPos = altCursor.GetPosition(); auto tgtCursorPos = altCursor.GetPosition();
tgtCursorPos.y += _mutableViewport.Top(); tgtCursorPos.y += _mutableViewport.Top();
@ -359,7 +359,7 @@ void Terminal::SearchMissingCommand(const std::wstring_view command)
{ {
if (_pfnSearchMissingCommand) if (_pfnSearchMissingCommand)
{ {
const auto bufferRow = GetCursorPosition().y; const auto bufferRow = _activeBuffer().GetCursor().GetPosition().y;
_pfnSearchMissingCommand(command, bufferRow); _pfnSearchMissingCommand(command, bufferRow);
} }
} }

View File

@ -69,7 +69,7 @@ std::vector<til::point_span> Terminal::_GetSelectionSpans() const noexcept
// - None // - None
// Return Value: // Return Value:
// - None // - None
const til::point Terminal::GetSelectionAnchor() const noexcept til::point Terminal::GetSelectionAnchor() const noexcept
{ {
_assertLocked(); _assertLocked();
return _selection->start; return _selection->start;
@ -81,7 +81,7 @@ const til::point Terminal::GetSelectionAnchor() const noexcept
// - None // - None
// Return Value: // Return Value:
// - None // - None
const til::point Terminal::GetSelectionEnd() const noexcept til::point Terminal::GetSelectionEnd() const noexcept
{ {
_assertLocked(); _assertLocked();
return _selection->end; return _selection->end;
@ -139,7 +139,7 @@ til::point Terminal::SelectionEndForRendering() const
return til::point{ pos }; return til::point{ pos };
} }
const Terminal::SelectionEndpoint Terminal::SelectionEndpointTarget() const noexcept Terminal::SelectionEndpoint Terminal::SelectionEndpointTarget() const noexcept
{ {
return _selectionEndpoint; return _selectionEndpoint;
} }
@ -148,13 +148,13 @@ const Terminal::SelectionEndpoint Terminal::SelectionEndpointTarget() const noex
// - Checks if selection is active // - Checks if selection is active
// Return Value: // Return Value:
// - bool representing if selection is active. Used to decide copy/paste on right click // - bool representing if selection is active. Used to decide copy/paste on right click
const bool Terminal::IsSelectionActive() const noexcept bool Terminal::IsSelectionActive() const noexcept
{ {
_assertLocked(); _assertLocked();
return _selection->active; return _selection->active;
} }
const bool Terminal::IsBlockSelection() const noexcept bool Terminal::IsBlockSelection() const noexcept
{ {
_assertLocked(); _assertLocked();
return _selection->blockSelection; return _selection->blockSelection;
@ -362,7 +362,6 @@ void Terminal::ToggleMarkMode()
{ {
// Enter Mark Mode // Enter Mark Mode
// NOTE: directly set cursor state. We already should have locked before calling this function. // NOTE: directly set cursor state. We already should have locked before calling this function.
_activeBuffer().GetCursor().SetIsOn(false);
if (!IsSelectionActive()) if (!IsSelectionActive())
{ {
// No selection --> start one at the cursor // No selection --> start one at the cursor

View File

@ -3,7 +3,6 @@
#include "pch.h" #include "pch.h"
#include "Terminal.hpp" #include "Terminal.hpp"
#include <DefaultSettings.h>
using namespace Microsoft::Terminal::Core; using namespace Microsoft::Terminal::Core;
using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Types;
@ -39,22 +38,17 @@ void Terminal::SetFontInfo(const FontInfo& fontInfo)
_fontInfo = fontInfo; _fontInfo = fontInfo;
} }
til::point Terminal::GetCursorPosition() const noexcept TimerDuration Terminal::GetBlinkInterval() noexcept
{ {
const auto& cursor = _activeBuffer().GetCursor(); if (!_cursorBlinkInterval)
return cursor.GetPosition(); {
} const auto enabled = GetSystemMetrics(SM_CARETBLINKINGENABLED);
const auto interval = GetCaretBlinkTime();
bool Terminal::IsCursorVisible() const noexcept // >10s --> no blinking. The limit is arbitrary, because technically the valid range
{ // on Windows is 200-1200ms. GetCaretBlinkTime() returns INFINITE for no blinking, 0 for errors.
const auto& cursor = _activeBuffer().GetCursor(); _cursorBlinkInterval = enabled && interval <= 10000 ? std ::chrono::milliseconds(interval) : TimerDuration::max();
return cursor.IsVisible(); }
} return *_cursorBlinkInterval;
bool Terminal::IsCursorOn() const noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return cursor.IsOn();
} }
ULONG Terminal::GetCursorPixelWidth() const noexcept ULONG Terminal::GetCursorPixelWidth() const noexcept
@ -62,34 +56,17 @@ ULONG Terminal::GetCursorPixelWidth() const noexcept
return 1; return 1;
} }
ULONG Terminal::GetCursorHeight() const noexcept bool Terminal::IsGridLineDrawingAllowed() noexcept
{
return _activeBuffer().GetCursor().GetSize();
}
CursorType Terminal::GetCursorStyle() const noexcept
{
return _activeBuffer().GetCursor().GetType();
}
bool Terminal::IsCursorDoubleWidth() const
{
const auto& buffer = _activeBuffer();
const auto position = buffer.GetCursor().GetPosition();
return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single;
}
const bool Terminal::IsGridLineDrawingAllowed() noexcept
{ {
return true; return true;
} }
const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkUri(uint16_t id) const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkUri(uint16_t id) const
{ {
return _activeBuffer().GetHyperlinkUriFromId(id); return _activeBuffer().GetHyperlinkUriFromId(id);
} }
const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uint16_t id) const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uint16_t id) const
{ {
return _activeBuffer().GetCustomIdFromId(id); return _activeBuffer().GetCustomIdFromId(id);
} }
@ -100,7 +77,7 @@ const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uin
// - The location // - The location
// Return value: // Return value:
// - The pattern IDs of the location // - The pattern IDs of the location
const std::vector<size_t> Terminal::GetPatternId(const til::point location) const std::vector<size_t> Terminal::GetPatternId(const til::point location) const
{ {
_assertLocked(); _assertLocked();
@ -218,7 +195,7 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo
_activeBuffer().TriggerSelection(); _activeBuffer().TriggerSelection();
} }
const std::wstring_view Terminal::GetConsoleTitle() const noexcept std::wstring_view Terminal::GetConsoleTitle() const noexcept
{ {
_assertLocked(); _assertLocked();
if (_title.has_value()) if (_title.has_value())
@ -246,7 +223,7 @@ void Terminal::UnlockConsole() noexcept
_readWriteLock.unlock(); _readWriteLock.unlock();
} }
const bool Terminal::IsUiaDataInitialized() const noexcept bool Terminal::IsUiaDataInitialized() const noexcept
{ {
// GH#11135: Windows Terminal needs to create and return an automation peer // GH#11135: Windows Terminal needs to create and return an automation peer
// when a screen reader requests it. However, the terminal might not be fully // when a screen reader requests it. However, the terminal might not be fully

View File

@ -33,7 +33,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_previewControl = Control::TermControl(settings, settings, *_previewConnection); _previewControl = Control::TermControl(settings, settings, *_previewConnection);
_previewControl.IsEnabled(false); _previewControl.IsEnabled(false);
_previewControl.AllowFocusWhenDisabled(false); _previewControl.AllowFocusWhenDisabled(false);
_previewControl.CursorVisibility(Microsoft::Terminal::Control::CursorDisplayState::Shown);
ControlPreview().Child(_previewControl); ControlPreview().Child(_previewControl);
} }

View File

@ -26,14 +26,12 @@ namespace TerminalCoreUnitTests
TEST_METHOD(SetColorTableEntry); TEST_METHOD(SetColorTableEntry);
TEST_METHOD(CursorVisibility);
TEST_METHOD(CursorVisibilityViaStateMachine); TEST_METHOD(CursorVisibilityViaStateMachine);
// Terminal::_WriteBuffer used to enter infinite loops under certain conditions. // Terminal::_WriteBuffer used to enter infinite loops under certain conditions.
// This test ensures that Terminal::_WriteBuffer doesn't get stuck when // This test ensures that Terminal::_WriteBuffer doesn't get stuck when
// PrintString() is called with more code units than the buffer width. // PrintString() is called with more code units than the buffer width.
TEST_METHOD(PrintStringOfSurrogatePairs); TEST_METHOD(PrintStringOfSurrogatePairs);
TEST_METHOD(CheckDoubleWidthCursor);
TEST_METHOD(AddHyperlink); TEST_METHOD(AddHyperlink);
TEST_METHOD(AddHyperlinkCustomId); TEST_METHOD(AddHyperlinkCustomId);
@ -131,39 +129,6 @@ void TerminalApiTest::PrintStringOfSurrogatePairs()
return; return;
} }
void TerminalApiTest::CursorVisibility()
{
// GH#3093 - Cursor Visibility and On states shouldn't affect each other
Terminal term{ Terminal::TestDummyMarker{} };
DummyRenderer renderer{ &term };
term.Create({ 100, 100 }, 0, renderer);
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsVisible());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed());
term.SetCursorOn(false);
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsVisible());
VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed());
term.SetCursorOn(true);
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsVisible());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed());
auto& textBuffer = term.GetBufferAndViewport().buffer;
textBuffer.GetCursor().SetIsVisible(false);
VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsVisible());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed());
term.SetCursorOn(false);
VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsVisible());
VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed());
}
void TerminalApiTest::CursorVisibilityViaStateMachine() void TerminalApiTest::CursorVisibilityViaStateMachine()
{ {
// This is a nearly literal copy-paste of ScreenBufferTests::TestCursorIsOn, adapted for the Terminal // This is a nearly literal copy-paste of ScreenBufferTests::TestCursorIsOn, adapted for the Terminal
@ -176,89 +141,38 @@ void TerminalApiTest::CursorVisibilityViaStateMachine()
auto& cursor = tbi.GetCursor(); auto& cursor = tbi.GetCursor();
stateMachine.ProcessString(L"Hello World"); stateMachine.ProcessString(L"Hello World");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12l"); stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h"); stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
cursor.SetIsOn(false);
stateMachine.ProcessString(L"\x1b[?12l"); stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h"); stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25l"); stateMachine.ProcessString(L"\x1b[?25l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsVisible()); VERIFY_IS_FALSE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25h"); stateMachine.ProcessString(L"\x1b[?25h");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12;25l"); stateMachine.ProcessString(L"\x1b[?12;25l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsVisible()); VERIFY_IS_FALSE(cursor.IsVisible());
} }
void TerminalApiTest::CheckDoubleWidthCursor()
{
Terminal term{ Terminal::TestDummyMarker{} };
DummyRenderer renderer{ &term };
term.Create({ 100, 100 }, 0, renderer);
auto& tbi = *(term._mainBuffer);
auto& stateMachine = *(term._stateMachine);
auto& cursor = tbi.GetCursor();
// Lets stuff the buffer with single width characters,
// but leave the last two columns empty for double width.
std::wstring singleWidthText;
singleWidthText.reserve(98);
for (size_t i = 0; i < 98; ++i)
{
singleWidthText.append(L"A");
}
stateMachine.ProcessString(singleWidthText);
VERIFY_IS_TRUE(cursor.GetPosition().x == 98);
// Stuff two double width characters.
std::wstring doubleWidthText{ L"我愛" };
stateMachine.ProcessString(doubleWidthText);
// The last 'A'
cursor.SetPosition({ 97, 0 });
VERIFY_IS_FALSE(term.IsCursorDoubleWidth());
// This and the next CursorPos are taken up by '我‘
cursor.SetPosition({ 98, 0 });
VERIFY_IS_TRUE(term.IsCursorDoubleWidth());
cursor.SetPosition({ 99, 0 });
VERIFY_IS_TRUE(term.IsCursorDoubleWidth());
// This and the next CursorPos are taken up by ’愛‘
cursor.SetPosition({ 0, 1 });
VERIFY_IS_TRUE(term.IsCursorDoubleWidth());
cursor.SetPosition({ 1, 1 });
VERIFY_IS_TRUE(term.IsCursorDoubleWidth());
}
void TerminalCoreUnitTests::TerminalApiTest::AddHyperlink() void TerminalCoreUnitTests::TerminalApiTest::AddHyperlink()
{ {
// This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlink, adapted for the Terminal // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlink, adapted for the Terminal

View File

@ -92,14 +92,6 @@ namespace Microsoft.Terminal.Wpf
WM_MOUSEWHEEL = 0x020A, WM_MOUSEWHEEL = 0x020A,
} }
public enum VirtualKey : ushort
{
/// <summary>
/// ALT key
/// </summary>
VK_MENU = 0x12,
}
[Flags] [Flags]
public enum SetWindowPosFlags : uint public enum SetWindowPosFlags : uint
{ {
@ -206,9 +198,6 @@ namespace Microsoft.Terminal.Wpf
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)] [DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalUserScroll(IntPtr terminal, int viewTop); public static extern void TerminalUserScroll(IntPtr terminal, int viewTop);
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalClearSelection(IntPtr terminal);
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)] [DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
[return: MarshalAs(UnmanagedType.LPWStr)] [return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string TerminalGetSelection(IntPtr terminal); public static extern string TerminalGetSelection(IntPtr terminal);
@ -229,30 +218,12 @@ namespace Microsoft.Terminal.Wpf
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)] [DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalSetTheme(IntPtr terminal, [MarshalAs(UnmanagedType.Struct)] TerminalTheme theme, string fontFamily, short fontSize, int newDpi); public static extern void TerminalSetTheme(IntPtr terminal, [MarshalAs(UnmanagedType.Struct)] TerminalTheme theme, string fontFamily, short fontSize, int newDpi);
[DllImport("Microsoft.Terminal.Control.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalBlinkCursor(IntPtr terminal);
[DllImport("Microsoft.Terminal.Control.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalSetCursorVisible(IntPtr terminal, bool visible);
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)] [DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalSetFocus(IntPtr terminal); public static extern void TerminalSetFocused(IntPtr terminal, bool focused);
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalKillFocus(IntPtr terminal);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetFocus(IntPtr hWnd); public static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetFocus();
[DllImport("user32.dll", SetLastError = true)]
public static extern short GetKeyState(int keyCode);
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetCaretBlinkTime();
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS public struct WINDOWPOS
{ {

View File

@ -24,7 +24,6 @@ namespace Microsoft.Terminal.Wpf
private ITerminalConnection connection; private ITerminalConnection connection;
private IntPtr hwnd; private IntPtr hwnd;
private IntPtr terminal; private IntPtr terminal;
private DispatcherTimer blinkTimer;
private NativeMethods.ScrollCallback scrollCallback; private NativeMethods.ScrollCallback scrollCallback;
private NativeMethods.WriteCallback writeCallback; private NativeMethods.WriteCallback writeCallback;
@ -36,23 +35,6 @@ namespace Microsoft.Terminal.Wpf
this.MessageHook += this.TerminalContainer_MessageHook; this.MessageHook += this.TerminalContainer_MessageHook;
this.GotFocus += this.TerminalContainer_GotFocus; this.GotFocus += this.TerminalContainer_GotFocus;
this.Focusable = true; this.Focusable = true;
var blinkTime = NativeMethods.GetCaretBlinkTime();
if (blinkTime == uint.MaxValue)
{
return;
}
this.blinkTimer = new DispatcherTimer();
this.blinkTimer.Interval = TimeSpan.FromMilliseconds(blinkTime);
this.blinkTimer.Tick += (_, __) =>
{
if (this.terminal != IntPtr.Zero)
{
NativeMethods.TerminalBlinkCursor(this.terminal);
}
};
} }
/// <summary> /// <summary>
@ -314,15 +296,6 @@ namespace Microsoft.Terminal.Wpf
NativeMethods.TerminalDpiChanged(this.terminal, (int)dpiScale.PixelsPerInchX); NativeMethods.TerminalDpiChanged(this.terminal, (int)dpiScale.PixelsPerInchX);
} }
if (NativeMethods.GetFocus() == this.hwnd)
{
this.blinkTimer?.Start();
}
else
{
NativeMethods.TerminalSetCursorVisible(this.terminal, false);
}
return new HandleRef(this, this.hwnd); return new HandleRef(this, this.hwnd);
} }
@ -360,13 +333,10 @@ namespace Microsoft.Terminal.Wpf
switch ((NativeMethods.WindowMessage)msg) switch ((NativeMethods.WindowMessage)msg)
{ {
case NativeMethods.WindowMessage.WM_SETFOCUS: case NativeMethods.WindowMessage.WM_SETFOCUS:
NativeMethods.TerminalSetFocus(this.terminal); NativeMethods.TerminalSetFocused(this.terminal, true);
this.blinkTimer?.Start();
break; break;
case NativeMethods.WindowMessage.WM_KILLFOCUS: case NativeMethods.WindowMessage.WM_KILLFOCUS:
NativeMethods.TerminalKillFocus(this.terminal); NativeMethods.TerminalSetFocused(this.terminal, false);
this.blinkTimer?.Stop();
NativeMethods.TerminalSetCursorVisible(this.terminal, false);
break; break;
case NativeMethods.WindowMessage.WM_MOUSEACTIVATE: case NativeMethods.WindowMessage.WM_MOUSEACTIVATE:
this.Focus(); this.Focus();
@ -375,12 +345,8 @@ namespace Microsoft.Terminal.Wpf
case NativeMethods.WindowMessage.WM_SYSKEYDOWN: // fallthrough case NativeMethods.WindowMessage.WM_SYSKEYDOWN: // fallthrough
case NativeMethods.WindowMessage.WM_KEYDOWN: case NativeMethods.WindowMessage.WM_KEYDOWN:
{ {
// WM_KEYDOWN lParam layout documentation: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
NativeMethods.TerminalSetCursorVisible(this.terminal, true);
UnpackKeyMessage(wParam, lParam, out ushort vkey, out ushort scanCode, out ushort flags); UnpackKeyMessage(wParam, lParam, out ushort vkey, out ushort scanCode, out ushort flags);
NativeMethods.TerminalSendKeyEvent(this.terminal, vkey, scanCode, flags, true); NativeMethods.TerminalSendKeyEvent(this.terminal, vkey, scanCode, flags, true);
this.blinkTimer?.Start();
break; break;
} }

View File

@ -4,6 +4,7 @@
#include "precomp.h" #include "precomp.h"
#include "AccessibilityNotifier.h" #include "AccessibilityNotifier.h"
#include "../types/inc/convert.hpp"
#include "../interactivity/inc/ServiceLocator.hpp" #include "../interactivity/inc/ServiceLocator.hpp"
using namespace Microsoft::Console; using namespace Microsoft::Console;
@ -70,17 +71,19 @@ void AccessibilityNotifier::Initialize(HWND hwnd, DWORD msaaDelay, DWORD uiaDela
void AccessibilityNotifier::SetUIAProvider(IRawElementProviderSimple* provider) noexcept void AccessibilityNotifier::SetUIAProvider(IRawElementProviderSimple* provider) noexcept
{ {
// NOTE: The assumption is that you're holding the console lock when calling any of the member functions.
// This is why we can safely update these members (no worker thread is running nor can be scheduled).
assert(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked());
// If UIA events are disabled, don't set _uiaProvider either. // If UIA events are disabled, don't set _uiaProvider either.
// It would trigger unnecessary work. // It would trigger unnecessary work.
//
// NOTE: We check this before the assert() below so that unit tests don't trigger the assert.
if (!_uiaEnabled) if (!_uiaEnabled)
{ {
return; return;
} }
// NOTE: The assumption is that you're holding the console lock when calling any of the member functions.
// This is why we can safely update these members (no worker thread is running nor can be scheduled).
assert(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked());
// Of course we must ensure our precious provider object doesn't go away. // Of course we must ensure our precious provider object doesn't go away.
if (provider) if (provider)
{ {
@ -90,7 +93,10 @@ void AccessibilityNotifier::SetUIAProvider(IRawElementProviderSimple* provider)
const auto old = _uiaProvider.exchange(provider, std::memory_order_relaxed); const auto old = _uiaProvider.exchange(provider, std::memory_order_relaxed);
// Before we can release the old object, we must ensure it's not in use by a worker thread. // Before we can release the old object, we must ensure it's not in use by a worker thread.
if (_timer)
{
WaitForThreadpoolTimerCallbacks(_timer.get(), TRUE); WaitForThreadpoolTimerCallbacks(_timer.get(), TRUE);
}
if (old) if (old)
{ {
@ -140,16 +146,26 @@ void AccessibilityNotifier::SetUIAProvider(IRawElementProviderSimple* provider)
// Unfortunately there's no way to know whether anyone even needs this information so we always raise this. // Unfortunately there's no way to know whether anyone even needs this information so we always raise this.
void AccessibilityNotifier::CursorChanged(til::point position, bool activeSelection) noexcept void AccessibilityNotifier::CursorChanged(til::point position, bool activeSelection) noexcept
{ {
const auto uiaEnabled = _uiaProvider.load(std::memory_order_relaxed) != nullptr;
// Can't check for IsWinEventHookInstalled(EVENT_CONSOLE_CARET), // Can't check for IsWinEventHookInstalled(EVENT_CONSOLE_CARET),
// because we need to emit a ConsoleControl() call regardless. // because we need to emit a ConsoleControl() call regardless.
if (_msaaEnabled) if (_msaaEnabled || uiaEnabled)
{ {
const auto guard = _lock.lock_exclusive(); const auto guard = _lock.lock_exclusive();
if (_msaaEnabled)
{
_state.eventConsoleCaretPositionX = position.x; _state.eventConsoleCaretPositionX = position.x;
_state.eventConsoleCaretPositionY = position.y; _state.eventConsoleCaretPositionY = position.y;
_state.eventConsoleCaretSelecting = activeSelection; _state.eventConsoleCaretSelecting = activeSelection;
_state.eventConsoleCaretPrimed = true; _state.eventConsoleCaretPrimed = true;
}
if (uiaEnabled)
{
_state.textSelectionChanged = true;
}
_timerSet(); _timerSet();
} }
@ -167,10 +183,18 @@ void AccessibilityNotifier::SelectionChanged() noexcept
} }
} }
bool AccessibilityNotifier::WantsRegionChangedEvents() const noexcept
{
// See RegionChanged().
return (_msaaEnabled && IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_REGION)) ||
(_uiaProvider.load(std::memory_order_relaxed) != nullptr);
}
// Emits EVENT_CONSOLE_UPDATE_REGION, the region of the console that changed. // Emits EVENT_CONSOLE_UPDATE_REGION, the region of the console that changed.
// `end` is expected to be an inclusive coordinate.
void AccessibilityNotifier::RegionChanged(til::point begin, til::point end) noexcept void AccessibilityNotifier::RegionChanged(til::point begin, til::point end) noexcept
{ {
if (begin >= end) if (begin > end)
{ {
return; return;
} }
@ -306,7 +330,7 @@ void AccessibilityNotifier::_timerSet() noexcept
{ {
if (!_delay) if (!_delay)
{ {
_emitMSAA(_state); _emitEvents(_state);
} }
else if (!_state.timerScheduled) else if (!_state.timerScheduled)
{ {
@ -341,40 +365,79 @@ void NTAPI AccessibilityNotifier::_timerEmitMSAA(PTP_CALLBACK_INSTANCE, PVOID co
memset(&self->_state, 0, sizeof(State)); memset(&self->_state, 0, sizeof(State));
} }
self->_emitMSAA(state); self->_emitEvents(state);
} }
void AccessibilityNotifier::_emitMSAA(State& state) const noexcept void AccessibilityNotifier::_emitEvents(State& state) const noexcept
{ {
const auto cc = ServiceLocator::LocateConsoleControl<IConsoleControl>(); const auto cc = ServiceLocator::LocateConsoleControl<IConsoleControl>();
const auto provider = _uiaProvider.load(std::memory_order_relaxed); const auto provider = _uiaProvider.load(std::memory_order_relaxed);
LONG updateRegionBeg = 0;
LONG updateRegionEnd = 0;
LONG updateSimpleCharAndAttr = 0;
LONG caretPosition = 0;
std::optional<CONSOLE_CARET_INFO> caretInfo;
// vvv Prepare any information we need vvv
//
// Because NotifyWinEvent and UiaRaiseAutomationEvent are _very_ slow,
// and the following needs the console lock, we do it separately first.
if (state.eventConsoleUpdateRegionPrimed || state.eventConsoleCaretPrimed)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
if (state.eventConsoleUpdateRegionPrimed)
{
const auto regionBegX = castSaturated<SHORT>(state.eventConsoleUpdateRegionBeginX);
const auto regionBegY = castSaturated<SHORT>(state.eventConsoleUpdateRegionBeginY);
const auto regionEndX = castSaturated<SHORT>(state.eventConsoleUpdateRegionEndX);
const auto regionEndY = castSaturated<SHORT>(state.eventConsoleUpdateRegionEndY);
updateRegionBeg = MAKELONG(regionBegX, regionBegY);
updateRegionEnd = MAKELONG(regionEndX, regionEndY);
// Historically we'd emit an EVENT_CONSOLE_UPDATE_SIMPLE event for single-char updates,
// but in the 30 years since, the way fast software is written has changed:
// We now have plenty CPU power but the speed of light is still the same.
// It's much more important to batch events to avoid NotifyWinEvent's latency problems.
// EVENT_CONSOLE_UPDATE_SIMPLE is not trivially batch-able, so we should avoid it.
//
// That said, NVDA is currently a very popular screen reader for Windows.
// IF you set its "Windows Console support" to "Legacy" AND disable
// "Use enhanced typed character support in legacy Windows Console when available"
// then it will purely rely on these WinEvents for accessibility.
//
// In this case it assumes that EVENT_CONSOLE_UPDATE_REGION is regular output
// and that EVENT_CONSOLE_UPDATE_SIMPLE is keyboard input (FYI: don't do this).
// The problem now is that it doesn't announce any EVENT_CONSOLE_UPDATE_REGION
// events where beg == end (i.e. a single character change).
//
// Unfortunately, the same is partially true for Microsoft's own Narrator.
if (gci.HasActiveOutputBuffer() && updateRegionBeg == updateRegionEnd)
{
auto& screenInfo = gci.GetActiveOutputBuffer();
auto& buffer = screenInfo.GetTextBuffer();
const auto& row = buffer.GetRowByOffset(regionBegY);
const auto glyph = row.GlyphAt(regionBegX);
const auto attr = row.GetAttrByColumn(regionBegX);
updateSimpleCharAndAttr = MAKELONG(Utf16ToUcs2(glyph), attr.GetLegacyAttributes());
}
}
if (state.eventConsoleCaretPrimed) if (state.eventConsoleCaretPrimed)
{ {
const auto x = castSaturated<SHORT>(state.eventConsoleCaretPositionX); const auto caretX = castSaturated<SHORT>(state.eventConsoleCaretPositionX);
const auto y = castSaturated<SHORT>(state.eventConsoleCaretPositionY); const auto caretY = castSaturated<SHORT>(state.eventConsoleCaretPositionY);
// Technically, CONSOLE_CARET_SELECTION and CONSOLE_CARET_VISIBLE are bitflags, caretPosition = MAKELONG(caretX, caretY);
// however Microsoft's _own_ example code for these assumes that they're an
// enumeration and also assumes that a value of 0 (= invisible cursor) is invalid.
// So, we just pretend as if the cursor is always visible.
const auto flags = state.eventConsoleCaretSelecting ? CONSOLE_CARET_SELECTION : CONSOLE_CARET_VISIBLE;
// There's no need to check for IsWinEventHookInstalled,
// because NotifyWinEvent is very fast if no event is installed.
cc->NotifyWinEvent(EVENT_CONSOLE_CARET, _hwnd, flags, MAKELONG(x, y));
{
std::optional<CONSOLE_CARET_INFO> caretInfo;
// Convert the buffer position to the equivalent screen coordinates // Convert the buffer position to the equivalent screen coordinates
// required by CONSOLE_CARET_INFO, taking line rendition into account. // required by CONSOLE_CARET_INFO, taking line rendition into account.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
if (gci.HasActiveOutputBuffer()) if (gci.HasActiveOutputBuffer())
{ {
auto& screenInfo = gci.GetActiveOutputBuffer(); auto& screenInfo = gci.GetActiveOutputBuffer();
auto& buffer = screenInfo.GetTextBuffer(); auto& buffer = screenInfo.GetTextBuffer();
const auto position = buffer.BufferToScreenPosition({ x, y }); const auto position = buffer.BufferToScreenPosition({ caretX, caretY });
const auto viewport = screenInfo.GetViewport(); const auto viewport = screenInfo.GetViewport();
const auto fontSize = screenInfo.GetScreenFontSize(); const auto fontSize = screenInfo.GetScreenFontSize();
const auto left = (position.x - viewport.Left()) * fontSize.width; const auto left = (position.x - viewport.Left()) * fontSize.width;
@ -389,13 +452,64 @@ void AccessibilityNotifier::_emitMSAA(State& state) const noexcept
}, },
}); });
} }
gci.UnlockConsole(); }
gci.UnlockConsole();
}
// vvv Raise events now vvv
//
// NOTE: When typing in a cooked read prompt (e.g. cmd.exe), the following events
// are historically raised synchronously/immediately in the listed order:
// * NotifyWinEvent(EVENT_CONSOLE_UPDATE_SIMPLE)
// * UiaRaiseAutomationEvent(UIA_Text_TextChangedEventId)
//
// Then, between 0-530ms later, via the now removed blink timer routine,
// the following was raised asynchronously:
// * ConsoleControl(ConsoleSetCaretInfo)
// * NotifyWinEvent(EVENT_CONSOLE_CARET)
// * UiaRaiseAutomationEvent(UIA_Text_TextSelectionChangedEventId)
if (state.eventConsoleUpdateRegionPrimed)
{
if (updateSimpleCharAndAttr)
{
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_SIMPLE, _hwnd, updateRegionBeg, updateSimpleCharAndAttr);
}
else
{
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_REGION, _hwnd, updateRegionBeg, updateRegionEnd);
}
state.eventConsoleUpdateRegionBeginX = 0;
state.eventConsoleUpdateRegionBeginY = 0;
state.eventConsoleUpdateRegionEndX = 0;
state.eventConsoleUpdateRegionEndY = 0;
state.eventConsoleUpdateRegionPrimed = false;
}
if (state.textChanged)
{
_emitUIAEvent(provider, UIA_Text_TextChangedEventId);
state.textChanged = false;
}
if (state.eventConsoleCaretPrimed)
{
if (caretInfo) if (caretInfo)
{ {
cc->Control(ControlType::ConsoleSetCaretInfo, &*caretInfo, sizeof(*caretInfo)); cc->Control(ControlType::ConsoleSetCaretInfo, &*caretInfo, sizeof(*caretInfo));
} }
}
// There's no need to check for IsWinEventHookInstalled,
// because NotifyWinEvent is very fast if no event is installed.
//
// Technically, CONSOLE_CARET_SELECTION and CONSOLE_CARET_VISIBLE are bitflags,
// however Microsoft's _own_ example code for these assumes that they're an
// enumeration and also assumes that a value of 0 (= invisible cursor) is invalid.
// So, we just pretend as if the cursor is always visible.
const auto flags = state.eventConsoleCaretSelecting ? CONSOLE_CARET_SELECTION : CONSOLE_CARET_VISIBLE;
cc->NotifyWinEvent(EVENT_CONSOLE_CARET, _hwnd, flags, caretPosition);
state.eventConsoleCaretPositionX = 0; state.eventConsoleCaretPositionX = 0;
state.eventConsoleCaretPositionY = 0; state.eventConsoleCaretPositionY = 0;
@ -403,72 +517,10 @@ void AccessibilityNotifier::_emitMSAA(State& state) const noexcept
state.eventConsoleCaretPrimed = false; state.eventConsoleCaretPrimed = false;
} }
if (state.eventConsoleUpdateRegionPrimed) if (state.textSelectionChanged)
{ {
const auto begX = castSaturated<SHORT>(state.eventConsoleUpdateRegionBeginX); _emitUIAEvent(provider, UIA_Text_TextSelectionChangedEventId);
const auto begY = castSaturated<SHORT>(state.eventConsoleUpdateRegionBeginY); state.textSelectionChanged = false;
const auto endX = castSaturated<SHORT>(state.eventConsoleUpdateRegionEndX);
const auto endY = castSaturated<SHORT>(state.eventConsoleUpdateRegionEndY);
const auto beg = MAKELONG(begX, begY);
const auto end = MAKELONG(endX, endY);
// Previously, we'd also emit a EVENT_CONSOLE_UPDATE_SIMPLE event for single-char updates,
// but in the 30 years since, the way fast software is written has changed:
// We now have plenty CPU power but the speed of light is still the same.
// It's much more important to batch events to avoid NotifyWinEvent's latency problems.
// EVENT_CONSOLE_UPDATE_SIMPLE is not trivially batch-able and so it got removed.
//
// That said, NVDA is currently a very popular screen reader for Windows.
// IF you set its "Windows Console support" to "Legacy" AND disable
// "Use enhanced typed character support in legacy Windows Console when available"
// then it will purely rely on these WinEvents for accessibility.
//
// In this case it assumes that EVENT_CONSOLE_UPDATE_REGION is regular output
// and that EVENT_CONSOLE_UPDATE_SIMPLE is keyboard input (FYI: don't do this).
// The problem now is that it doesn't announce any EVENT_CONSOLE_UPDATE_REGION
// events where beg == end (i.e. a single character change).
//
// The good news is that if you set these two options in NVDA, it crashes whenever
// any conhost instance exits, so... maybe we don't need to work around this? :D
//
// I'll leave this code here, in case we ever need to shim EVENT_CONSOLE_UPDATE_SIMPLE.
#if 0
LONG charAndAttr = 0;
if (beg == end)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
if (gci.HasActiveOutputBuffer())
{
auto& tb = gci.GetActiveOutputBuffer().GetTextBuffer();
const auto& row = tb.GetRowByOffset(begY);
const auto glyph = row.GlyphAt(begX);
const auto attr = row.GetAttrByColumn(begX);
charAndAttr = MAKELONG(Utf16ToUcs2(glyph), attr.GetLegacyAttributes());
}
gci.UnlockConsole();
}
if (charAndAttr)
{
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_SIMPLE, _hwnd, beg, charAndAttr);
}
else
{
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_REGION, _hwnd, beg, end);
}
#else
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_REGION, _hwnd, beg, end);
#endif
state.eventConsoleUpdateRegionBeginX = 0;
state.eventConsoleUpdateRegionBeginY = 0;
state.eventConsoleUpdateRegionEndX = 0;
state.eventConsoleUpdateRegionEndY = 0;
state.eventConsoleUpdateRegionPrimed = false;
} }
if (state.eventConsoleUpdateScrollPrimed) if (state.eventConsoleUpdateScrollPrimed)
@ -488,18 +540,6 @@ void AccessibilityNotifier::_emitMSAA(State& state) const noexcept
cc->NotifyWinEvent(EVENT_CONSOLE_LAYOUT, _hwnd, 0, 0); cc->NotifyWinEvent(EVENT_CONSOLE_LAYOUT, _hwnd, 0, 0);
state.eventConsoleLayoutPrimed = false; state.eventConsoleLayoutPrimed = false;
} }
if (state.textSelectionChanged)
{
_emitUIAEvent(provider, UIA_Text_TextSelectionChangedEventId);
state.textSelectionChanged = false;
}
if (state.textChanged)
{
_emitUIAEvent(provider, UIA_Text_TextChangedEventId);
state.textChanged = false;
}
} }
void AccessibilityNotifier::_emitUIAEvent(IRawElementProviderSimple* provider, EVENTID id) noexcept void AccessibilityNotifier::_emitUIAEvent(IRawElementProviderSimple* provider, EVENTID id) noexcept

View File

@ -26,6 +26,7 @@ namespace Microsoft::Console
void CursorChanged(til::point position, bool activeSelection) noexcept; void CursorChanged(til::point position, bool activeSelection) noexcept;
void SelectionChanged() noexcept; void SelectionChanged() noexcept;
bool WantsRegionChangedEvents() const noexcept;
void RegionChanged(til::point begin, til::point end) noexcept; void RegionChanged(til::point begin, til::point end) noexcept;
void ScrollBuffer(til::CoordType delta) noexcept; void ScrollBuffer(til::CoordType delta) noexcept;
void ScrollViewport(til::point delta) noexcept; void ScrollViewport(til::point delta) noexcept;
@ -70,7 +71,7 @@ namespace Microsoft::Console
PTP_TIMER _createTimer(PTP_TIMER_CALLBACK callback) noexcept; PTP_TIMER _createTimer(PTP_TIMER_CALLBACK callback) noexcept;
void _timerSet() noexcept; void _timerSet() noexcept;
static void NTAPI _timerEmitMSAA(PTP_CALLBACK_INSTANCE instance, PVOID context, PTP_TIMER timer) noexcept; static void NTAPI _timerEmitMSAA(PTP_CALLBACK_INSTANCE instance, PVOID context, PTP_TIMER timer) noexcept;
void _emitMSAA(State& msaa) const noexcept; void _emitEvents(State& msaa) const noexcept;
static void _emitUIAEvent(IRawElementProviderSimple* provider, EVENTID id) noexcept; static void _emitUIAEvent(IRawElementProviderSimple* provider, EVENTID id) noexcept;
// The main window, used for NotifyWinEvent / ConsoleControl(ConsoleSetCaretInfo) calls. // The main window, used for NotifyWinEvent / ConsoleControl(ConsoleSetCaretInfo) calls.

View File

@ -1,146 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "../host/scrolling.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop
using namespace Microsoft::Console;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::Render;
static void CALLBACK CursorTimerRoutineWrapper(_Inout_ PTP_CALLBACK_INSTANCE /*Instance*/, _Inout_opt_ PVOID /*Context*/, _Inout_ PTP_TIMER /*Timer*/)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// There's a slight race condition here.
// CreateThreadpoolTimer callbacks may be scheduled even after they were canceled.
// But I'm not too concerned that this will lead to issues at the time of writing,
// as CursorBlinker is allocated as a static variable through the Globals class.
// It'd be nice to fix this, but realistically it'll likely not lead to issues.
gci.LockConsole();
gci.GetCursorBlinker().TimerRoutine(gci.GetActiveOutputBuffer());
gci.UnlockConsole();
}
CursorBlinker::CursorBlinker() :
_timer(THROW_LAST_ERROR_IF_NULL(CreateThreadpoolTimer(&CursorTimerRoutineWrapper, nullptr, nullptr))),
_uCaretBlinkTime(INFINITE) // default to no blink
{
}
CursorBlinker::~CursorBlinker()
{
KillCaretTimer();
}
void CursorBlinker::UpdateSystemMetrics() noexcept
{
// 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.
auto animationsEnabled = TRUE;
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
auto& renderSettings = ServiceLocator::LocateGlobals().getConsoleInformation().GetRenderSettings();
renderSettings.SetRenderMode(RenderSettings::Mode::BlinkAllowed, animationsEnabled && _uCaretBlinkTime != INFINITE);
}
void CursorBlinker::SettingsChanged() noexcept
{
const auto dwCaretBlinkTime = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime();
if (dwCaretBlinkTime != _uCaretBlinkTime)
{
KillCaretTimer();
_uCaretBlinkTime = dwCaretBlinkTime;
SetCaretTimer();
}
}
void CursorBlinker::FocusEnd() const noexcept
{
KillCaretTimer();
}
void CursorBlinker::FocusStart() const noexcept
{
SetCaretTimer();
}
// Routine Description:
// - 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:
// - <none>
void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept
{
auto& buffer = ScreenInfo.GetTextBuffer();
auto& cursor = buffer.GetCursor();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS))
{
goto DoScroll;
}
// If the DelayCursor flag has been set, wait one more tick before toggle.
// This is used to guarantee the cursor is on for a finite period of time
// after a move and off for a finite period of time after a WriteString.
if (cursor.GetDelay())
{
cursor.SetDelay(false);
goto DoBlinkingRenditionAndScroll;
}
// Don't blink the cursor for remote sessions.
if ((!ServiceLocator::LocateSystemConfigurationProvider()->IsCaretBlinkingEnabled() ||
_uCaretBlinkTime == -1 ||
(!cursor.IsBlinkingAllowed())) &&
cursor.IsOn())
{
goto DoBlinkingRenditionAndScroll;
}
// Blink only if the cursor isn't turned off via the API
if (cursor.IsVisible())
{
cursor.SetIsOn(!cursor.IsOn());
}
DoBlinkingRenditionAndScroll:
gci.GetRenderSettings().ToggleBlinkRendition(buffer.GetRenderer());
DoScroll:
Scrolling::s_ScrollIfNecessary(ScreenInfo);
}
// Routine Description:
// - If guCaretBlinkTime is -1, we don't want to blink the caret. However, we
// need to make sure it gets drawn, so we'll set a short timer. When that
// goes off, we'll hit CursorTimerRoutine, and it'll do the right thing if
// guCaretBlinkTime is -1.
void CursorBlinker::SetCaretTimer() const noexcept
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsInVtIoMode())
{
return;
}
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
static constexpr DWORD dwDefTimeout = 0x212;
const auto periodInMS = _uCaretBlinkTime == -1 ? dwDefTimeout : _uCaretBlinkTime;
// The FILETIME struct measures time in 100ns steps. 10000 thus equals 1ms.
auto periodInFiletime = -static_cast<int64_t>(periodInMS) * 10000;
SetThreadpoolTimer(_timer.get(), reinterpret_cast<FILETIME*>(&periodInFiletime), periodInMS, 0);
}
void CursorBlinker::KillCaretTimer() const noexcept
{
SetThreadpoolTimer(_timer.get(), nullptr, 0, 0);
}

View File

@ -1,37 +0,0 @@
/*
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- CursorBlinker.hpp
Abstract:
- Encapsulates all of the behavior needed to blink the cursor, and update the
blink rate to account for different system settings.
Author(s):
- Mike Griese (migrie) Nov 2018
*/
namespace Microsoft::Console
{
class CursorBlinker final
{
public:
CursorBlinker();
~CursorBlinker();
void FocusStart() const noexcept;
void FocusEnd() const noexcept;
void UpdateSystemMetrics() noexcept;
void SettingsChanged() noexcept;
void TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept;
private:
void SetCaretTimer() const noexcept;
void KillCaretTimer() const noexcept;
wil::unique_threadpool_timer_nowait _timer;
UINT _uCaretBlinkTime;
};
}

View File

@ -253,10 +253,13 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
ImageSlice::EraseCells(screenInfo.GetTextBuffer(), startingCoordinate, result.cellsModified); ImageSlice::EraseCells(screenInfo.GetTextBuffer(), startingCoordinate, result.cellsModified);
} }
if (result.cellsModified > 0)
{ {
// Notify accessibility
auto endingCoordinate = startingCoordinate; auto endingCoordinate = startingCoordinate;
bufferSize.WalkInBounds(endingCoordinate, result.cellsModified); if (result.cellsModified > 1)
{
bufferSize.WalkInBounds(endingCoordinate, result.cellsModified - 1);
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier; auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.RegionChanged(startingCoordinate, endingCoordinate); an.RegionChanged(startingCoordinate, endingCoordinate);

View File

@ -17,7 +17,6 @@
#include "VtIo.hpp" #include "VtIo.hpp"
#include "../types/inc/convert.hpp" #include "../types/inc/convert.hpp"
#include "../types/inc/GlyphWidth.hpp"
#include "../types/inc/Viewport.hpp" #include "../types/inc/Viewport.hpp"
#include "../interactivity/inc/ServiceLocator.hpp" #include "../interactivity/inc/ServiceLocator.hpp"
@ -34,29 +33,46 @@ constexpr bool controlCharPredicate(wchar_t wch)
static auto raiseAccessibilityEventsOnExit(SCREEN_INFORMATION& screenInfo) static auto raiseAccessibilityEventsOnExit(SCREEN_INFORMATION& screenInfo)
{ {
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& bufferBefore = gci.GetActiveOutputBuffer(); const auto bufferBefore = &gci.GetActiveOutputBuffer();
const auto cursorBefore = bufferBefore.GetTextBuffer().GetCursor().GetPosition(); const auto cursorBefore = bufferBefore->GetTextBuffer().GetCursor().GetPosition();
auto raise = wil::scope_exit([&bufferBefore, cursorBefore] { auto raise = wil::scope_exit([bufferBefore, cursorBefore] {
// !!! NOTE !!! `bufferBefore` may now be a stale pointer, because VT // !!! NOTE !!! `bufferBefore` may now be a stale pointer, because VT
// sequences can switch between the main and alternative screen buffer. // sequences can switch between the main and alternative screen buffer.
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier; auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& bufferAfter = gci.GetActiveOutputBuffer(); const auto& bufferAfter = &gci.GetActiveOutputBuffer();
const auto cursorAfter = bufferAfter.GetTextBuffer().GetCursor().GetPosition(); const auto cursorAfter = bufferAfter->GetTextBuffer().GetCursor().GetPosition();
if (&bufferBefore == &bufferAfter)
{
an.RegionChanged(cursorBefore, cursorAfter);
}
if (cursorBefore != cursorAfter) if (cursorBefore != cursorAfter)
{ {
if (bufferBefore == bufferAfter && an.WantsRegionChangedEvents())
{
// Make the range ordered...
auto beg = cursorBefore;
auto end = cursorAfter;
if (beg > end)
{
std::swap(beg, end);
}
// ...and make it inclusive.
end.x--;
if (end.x < 0)
{
end.y--;
end.x = bufferAfter->GetBufferSize().Width() - 1;
}
an.RegionChanged(beg, end);
}
an.CursorChanged(cursorAfter, false); an.CursorChanged(cursorAfter, false);
} }
}); });
// Don't raise any events for inactive buffers. // Don't raise any events for inactive buffers.
if (&bufferBefore != &screenInfo) if (bufferBefore != &screenInfo)
{ {
raise.release(); raise.release();
} }
@ -131,7 +147,7 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point
coordCursor.y = bufferSize.height - 1; coordCursor.y = bufferSize.height - 1;
} }
LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, false)); LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor));
} }
// As the name implies, this writes text without processing its control characters. // As the name implies, this writes text without processing its control characters.
@ -191,10 +207,9 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
// If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode. // If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode.
// Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back // Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back
// to a position that the Console APIs expect (= not delayed). // to a position that the Console APIs expect (= not delayed).
if (cursor.IsDelayedEOLWrap() && wrapAtEOL) if (const auto delayed = cursor.GetDelayEOLWrap(); delayed && wrapAtEOL)
{ {
auto pos = cursor.GetPosition(); auto pos = cursor.GetPosition();
const auto delayed = cursor.GetDelayedAtPosition();
cursor.ResetDelayEOLWrap(); cursor.ResetDelayEOLWrap();
if (delayed == pos) if (delayed == pos)
{ {

View File

@ -355,17 +355,6 @@ const std::wstring_view CONSOLE_INFORMATION::GetLinkTitle() const noexcept
return _LinkTitle; return _LinkTitle;
} }
// Method Description:
// - return a reference to the console's cursor blinker.
// Arguments:
// - <none>
// Return Value:
// - a reference to the console's cursor blinker.
Microsoft::Console::CursorBlinker& CONSOLE_INFORMATION::GetCursorBlinker() noexcept
{
return _blinker;
}
// Method Description: // Method Description:
// - Returns the MIDI audio instance. // - Returns the MIDI audio instance.
// Arguments: // Arguments:

View File

@ -94,7 +94,7 @@ void InputTests::TestGetMouseButtonsValid()
} }
else else
{ {
dwButtonsExpected = Microsoft::Console::Interactivity::OneCore::SystemConfigurationProvider::s_DefaultNumberOfMouseButtons; dwButtonsExpected = 3;
} }
VERIFY_ARE_EQUAL(dwButtonsExpected, nMouseButtons); VERIFY_ARE_EQUAL(dwButtonsExpected, nMouseButtons);

View File

@ -749,7 +749,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
writer.Submit(); writer.Submit();
} }
RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true)); RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position));
// Attempt to "snap" the viewport to the cursor position. If the cursor // Attempt to "snap" the viewport to the cursor position. If the cursor
// is not in the current viewport, we'll try and move the viewport so // is not in the current viewport, we'll try and move the viewport so

View File

@ -4,7 +4,6 @@
<ClCompile Include="..\alias.cpp" /> <ClCompile Include="..\alias.cpp" />
<ClCompile Include="..\cmdline.cpp" /> <ClCompile Include="..\cmdline.cpp" />
<ClCompile Include="..\ConsoleArguments.cpp" /> <ClCompile Include="..\ConsoleArguments.cpp" />
<ClCompile Include="..\CursorBlinker.cpp" />
<ClCompile Include="..\readDataCooked.cpp" /> <ClCompile Include="..\readDataCooked.cpp" />
<ClCompile Include="..\consoleInformation.cpp" /> <ClCompile Include="..\consoleInformation.cpp" />
<ClCompile Include="..\dbcs.cpp" /> <ClCompile Include="..\dbcs.cpp" />
@ -59,7 +58,6 @@
<ClInclude Include="..\ConsoleArguments.hpp" /> <ClInclude Include="..\ConsoleArguments.hpp" />
<ClInclude Include="..\conserv.h" /> <ClInclude Include="..\conserv.h" />
<ClInclude Include="..\conwinuserrefs.h" /> <ClInclude Include="..\conwinuserrefs.h" />
<ClInclude Include="..\CursorBlinker.hpp" />
<ClInclude Include="..\dbcs.h" /> <ClInclude Include="..\dbcs.h" />
<ClInclude Include="..\directio.h" /> <ClInclude Include="..\directio.h" />
<ClInclude Include="..\getset.h" /> <ClInclude Include="..\getset.h" />

View File

@ -144,9 +144,6 @@
<ClCompile Include="..\PtySignalInputThread.cpp"> <ClCompile Include="..\PtySignalInputThread.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\CursorBlinker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\AccessibilityNotifier.cpp"> <ClCompile Include="..\AccessibilityNotifier.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
@ -284,9 +281,6 @@
<ClInclude Include="..\history.h"> <ClInclude Include="..\history.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\CursorBlinker.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\IIoProvider.hpp"> <ClInclude Include="..\IIoProvider.hpp">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>

View File

@ -435,21 +435,6 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.SetActiveOutputBuffer(screenInfo); gci.SetActiveOutputBuffer(screenInfo);
// initialize cursor GH#4102 - Typically, the cursor is set to on by the
// cursor blinker. Unfortunately, in conpty mode, there is no cursor
// blinker. So, in conpty mode, we need to leave the cursor on always. The
// cursor can still be set to hidden, and whether the cursor should be
// blinking will still be passed through to the terminal, but internally,
// the cursor should always be on.
//
// In particular, some applications make use of a calling
// `SetConsoleScreenBuffer` and `SetCursorPosition` without printing any
// text in between these calls. If we initialize the cursor to Off in conpty
// mode, then the cursor will remain off until they print text. This can
// lead to alignment problems in the terminal, because we won't move the
// terminal's cursor in this _exact_ scenario.
screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsInVtIoMode());
// set font // set font
screenInfo.RefreshFontWithRenderer(); screenInfo.RefreshFontWithRenderer();

View File

@ -6,15 +6,20 @@
#include "renderData.hpp" #include "renderData.hpp"
#include "dbcs.h" #include "dbcs.h"
#include "handle.h"
#include "../interactivity/inc/ServiceLocator.hpp" #include "../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop #pragma hdrstop
using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity; using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::Render;
using Microsoft::Console::Interactivity::ServiceLocator; using Microsoft::Console::Interactivity::ServiceLocator;
void RenderData::UpdateSystemMetrics()
{
_cursorBlinkInterval.reset();
}
// Routine Description: // Routine Description:
// - Retrieves the viewport that applies over the data available in the GetTextBuffer() call // - Retrieves the viewport that applies over the data available in the GetTextBuffer() call
// Return Value: // Return Value:
@ -96,93 +101,17 @@ void RenderData::UnlockConsole() noexcept
gci.UnlockConsole(); gci.UnlockConsole();
} }
// Method Description: TimerDuration RenderData::GetBlinkInterval() noexcept
// - Gets the cursor's position in the buffer, relative to the buffer origin.
// Arguments:
// - <none>
// Return Value:
// - the cursor's position in the buffer relative to the buffer origin.
til::point RenderData::GetCursorPosition() const noexcept
{ {
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (!_cursorBlinkInterval)
const auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
return cursor.GetPosition();
}
// Method Description:
// - Returns whether the cursor is currently visible or not. If the cursor is
// visible and blinking, this is true, even if the cursor has currently
// blinked to the "off" state.
// Arguments:
// - <none>
// Return Value:
// - true if the cursor is set to the visible state, regardless of blink state
bool RenderData::IsCursorVisible() const noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
return cursor.IsVisible();
}
// Method Description:
// - Returns whether the cursor is currently visually visible or not. If the
// cursor is visible, and blinking, this will alternate between true and
// false as the cursor blinks.
// Arguments:
// - <none>
// Return Value:
// - true if the cursor is currently visually visible, depending upon blink state
bool RenderData::IsCursorOn() const noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
return cursor.IsVisible() && cursor.IsOn();
}
// Method Description:
// - The height of the cursor, out of 100, where 100 indicates the cursor should
// be the full height of the cell.
// Arguments:
// - <none>
// Return Value:
// - height of the cursor, out of 100
ULONG RenderData::GetCursorHeight() const noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
// Determine cursor height
auto ulHeight = cursor.GetSize();
// Now adjust the height for the overwrite/insert mode. If we're in overwrite mode, IsDouble will be set.
// When IsDouble is set, we either need to double the height of the cursor, or if it's already too big,
// then we need to shrink it by half.
if (cursor.IsDouble())
{ {
if (ulHeight > 50) // 50 because 50 percent is half of 100 percent which is the max size. const auto enabled = ServiceLocator::LocateSystemConfigurationProvider()->IsCaretBlinkingEnabled();
{ const auto interval = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime();
ulHeight >>= 1; // >10s --> no blinking. The limit is arbitrary, because technically the valid range
// on Windows is 200-1200ms. GetCaretBlinkTime() returns INFINITE for no blinking, 0 for errors.
_cursorBlinkInterval = enabled && interval <= 10000 ? std ::chrono::milliseconds(interval) : TimerDuration::max();
} }
else return *_cursorBlinkInterval;
{
ulHeight <<= 1;
}
}
return ulHeight;
}
// Method Description:
// - The CursorType of the cursor. The CursorType is used to determine what
// shape the cursor should be.
// Arguments:
// - <none>
// Return Value:
// - the CursorType of the cursor.
CursorType RenderData::GetCursorStyle() const noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& cursor = gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor();
return cursor.GetType();
} }
// Method Description: // Method Description:
@ -197,26 +126,13 @@ ULONG RenderData::GetCursorPixelWidth() const noexcept
return ServiceLocator::LocateGlobals().cursorPixelWidth; return ServiceLocator::LocateGlobals().cursorPixelWidth;
} }
// Method Description:
// - Returns true if the cursor should be drawn twice as wide as usual because
// the cursor is currently over a cell with a double-wide character in it.
// Arguments:
// - <none>
// Return Value:
// - true if the cursor should be drawn twice as wide as usual
bool RenderData::IsCursorDoubleWidth() const
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.GetActiveOutputBuffer().CursorIsDoubleWidth();
}
// Routine Description: // Routine Description:
// - Checks the user preference as to whether grid line drawing is allowed around the edges of each cell. // - Checks the user preference as to whether grid line drawing is allowed around the edges of each cell.
// - This is for backwards compatibility with old behaviors in the legacy console. // - This is for backwards compatibility with old behaviors in the legacy console.
// Return Value: // Return Value:
// - If true, line drawing information retrieved from the text buffer can/should be displayed. // - If true, line drawing information retrieved from the text buffer can/should be displayed.
// - If false, it should be ignored and never drawn // - If false, it should be ignored and never drawn
const bool RenderData::IsGridLineDrawingAllowed() noexcept bool RenderData::IsGridLineDrawingAllowed() noexcept
{ {
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto outputMode = gci.GetActiveOutputBuffer().OutputMode; const auto outputMode = gci.GetActiveOutputBuffer().OutputMode;
@ -240,7 +156,7 @@ const bool RenderData::IsGridLineDrawingAllowed() noexcept
// - Retrieves the title information to be displayed in the frame/edge of the window // - Retrieves the title information to be displayed in the frame/edge of the window
// Return Value: // Return Value:
// - String with title information // - String with title information
const std::wstring_view RenderData::GetConsoleTitle() const noexcept std::wstring_view RenderData::GetConsoleTitle() const noexcept
{ {
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.GetTitleAndPrefix(); return gci.GetTitleAndPrefix();
@ -252,7 +168,7 @@ const std::wstring_view RenderData::GetConsoleTitle() const noexcept
// - The hyperlink ID // - The hyperlink ID
// Return Value: // Return Value:
// - The URI // - The URI
const std::wstring RenderData::GetHyperlinkUri(uint16_t id) const std::wstring RenderData::GetHyperlinkUri(uint16_t id) const
{ {
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.GetActiveOutputBuffer().GetTextBuffer().GetHyperlinkUriFromId(id); return gci.GetActiveOutputBuffer().GetTextBuffer().GetHyperlinkUriFromId(id);
@ -264,14 +180,14 @@ const std::wstring RenderData::GetHyperlinkUri(uint16_t id) const
// - The hyperlink ID // - The hyperlink ID
// Return Value: // Return Value:
// - The custom ID if there was one, empty string otherwise // - The custom ID if there was one, empty string otherwise
const std::wstring RenderData::GetHyperlinkCustomId(uint16_t id) const std::wstring RenderData::GetHyperlinkCustomId(uint16_t id) const
{ {
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.GetActiveOutputBuffer().GetTextBuffer().GetCustomIdFromId(id); return gci.GetActiveOutputBuffer().GetTextBuffer().GetCustomIdFromId(id);
} }
// For now, we ignore regex patterns in conhost // For now, we ignore regex patterns in conhost
const std::vector<size_t> RenderData::GetPatternId(const til::point /*location*/) const std::vector<size_t> RenderData::GetPatternId(const til::point /*location*/) const
{ {
return {}; return {};
} }
@ -293,12 +209,12 @@ std::pair<COLORREF, COLORREF> RenderData::GetAttributeColors(const TextAttribute
// - <none> // - <none>
// Return Value: // Return Value:
// - True if the selection variables contain valid selection data. False otherwise. // - True if the selection variables contain valid selection data. False otherwise.
const bool RenderData::IsSelectionActive() const bool RenderData::IsSelectionActive() const
{ {
return Selection::Instance().IsAreaSelected(); return Selection::Instance().IsAreaSelected();
} }
const bool RenderData::IsBlockSelection() const noexcept bool RenderData::IsBlockSelection() const
{ {
return !Selection::Instance().IsLineSelection(); return !Selection::Instance().IsLineSelection();
} }
@ -338,7 +254,7 @@ const til::point_span* RenderData::GetSearchHighlightFocused() const noexcept
// - none // - none
// Return Value: // Return Value:
// - current selection anchor // - current selection anchor
const til::point RenderData::GetSelectionAnchor() const noexcept til::point RenderData::GetSelectionAnchor() const noexcept
{ {
return Selection::Instance().GetSelectionAnchor(); return Selection::Instance().GetSelectionAnchor();
} }
@ -349,7 +265,7 @@ const til::point RenderData::GetSelectionAnchor() const noexcept
// - none // - none
// Return Value: // Return Value:
// - current selection anchor // - current selection anchor
const til::point RenderData::GetSelectionEnd() const noexcept til::point RenderData::GetSelectionEnd() const noexcept
{ {
// The selection area in ConHost is encoded as two things... // The selection area in ConHost is encoded as two things...
// - SelectionAnchor: the initial position where the selection was started // - SelectionAnchor: the initial position where the selection was started
@ -381,3 +297,8 @@ const til::point RenderData::GetSelectionEnd() const noexcept
return { x_pos, y_pos }; return { x_pos, y_pos };
} }
bool RenderData::IsUiaDataInitialized() const noexcept
{
return true;
}

View File

@ -1,16 +1,5 @@
/*++ // Copyright (c) Microsoft Corporation.
Copyright (c) Microsoft Corporation // Licensed under the MIT license.
Licensed under the MIT license.
Module Name:
- renderData.hpp
Abstract:
- This method provides an interface for rendering the final display based on the current console state
Author(s):
- Michael Niksa (miniksa) Nov 2015
--*/
#pragma once #pragma once
@ -20,41 +9,43 @@ class RenderData final :
public Microsoft::Console::Render::IRenderData public Microsoft::Console::Render::IRenderData
{ {
public: public:
void UpdateSystemMetrics();
//
// BEGIN IRenderData
//
Microsoft::Console::Types::Viewport GetViewport() noexcept override; Microsoft::Console::Types::Viewport GetViewport() noexcept override;
til::point GetTextBufferEndPosition() const noexcept override; til::point GetTextBufferEndPosition() const noexcept override;
TextBuffer& GetTextBuffer() const noexcept override; TextBuffer& GetTextBuffer() const noexcept override;
const FontInfo& GetFontInfo() const noexcept override; const FontInfo& GetFontInfo() const noexcept override;
std::span<const til::point_span> GetSearchHighlights() const noexcept override;
const til::point_span* GetSearchHighlightFocused() const noexcept override;
std::span<const til::point_span> GetSelectionSpans() const noexcept override; std::span<const til::point_span> GetSelectionSpans() const noexcept override;
void LockConsole() noexcept override; void LockConsole() noexcept override;
void UnlockConsole() noexcept override; void UnlockConsole() noexcept override;
til::point GetCursorPosition() const noexcept override; Microsoft::Console::Render::TimerDuration GetBlinkInterval() noexcept override;
bool IsCursorVisible() const noexcept override;
bool IsCursorOn() const noexcept override;
ULONG GetCursorHeight() const noexcept override;
CursorType GetCursorStyle() const noexcept override;
ULONG GetCursorPixelWidth() const noexcept override; ULONG GetCursorPixelWidth() const noexcept override;
bool IsCursorDoubleWidth() const override; bool IsGridLineDrawingAllowed() noexcept override;
std::wstring_view GetConsoleTitle() const noexcept override;
const bool IsGridLineDrawingAllowed() noexcept override; std::wstring GetHyperlinkUri(uint16_t id) const override;
std::wstring GetHyperlinkCustomId(uint16_t id) const override;
const std::wstring_view GetConsoleTitle() const noexcept override; std::vector<size_t> GetPatternId(const til::point location) const override;
const std::wstring GetHyperlinkUri(uint16_t id) const override;
const std::wstring GetHyperlinkCustomId(uint16_t id) const override;
const std::vector<size_t> GetPatternId(const til::point location) const override;
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override; std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override;
const bool IsSelectionActive() const override; bool IsSelectionActive() const override;
const bool IsBlockSelection() const noexcept override; bool IsBlockSelection() const override;
void ClearSelection() override; void ClearSelection() override;
void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override; void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override;
std::span<const til::point_span> GetSearchHighlights() const noexcept override; til::point GetSelectionAnchor() const noexcept override;
const til::point_span* GetSearchHighlightFocused() const noexcept override; til::point GetSelectionEnd() const noexcept override;
const til::point GetSelectionAnchor() const noexcept override; bool IsUiaDataInitialized() const noexcept override;
const til::point GetSelectionEnd() const noexcept override;
const bool IsUiaDataInitialized() const noexcept override { return true; } //
// END IRenderData
//
private:
std::optional<Microsoft::Console::Render::TimerDuration> _cursorBlinkInterval;
}; };

View File

@ -1311,12 +1311,6 @@ try
// Also save the distance to the virtual bottom so it can be restored after the resize // Also save the distance to the virtual bottom so it can be restored after the resize
const auto cursorDistanceFromBottom = _virtualBottom - _textBuffer->GetCursor().GetPosition().y; const auto cursorDistanceFromBottom = _virtualBottom - _textBuffer->GetCursor().GetPosition().y;
// skip any drawing updates that might occur until we swap _textBuffer with the new buffer or we exit early.
newTextBuffer->GetCursor().StartDeferDrawing();
_textBuffer->GetCursor().StartDeferDrawing();
// we're capturing _textBuffer by reference here because when we exit, we want to EndDefer on the current active buffer.
auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); });
TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get()); TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get());
// Since the reflow doesn't preserve the virtual bottom, we try and // Since the reflow doesn't preserve the virtual bottom, we try and
@ -1357,8 +1351,6 @@ NT_CATCH_RETURN()
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeTraditional(const til::size coordNewScreenSize) [[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeTraditional(const til::size coordNewScreenSize)
try try
{ {
_textBuffer->GetCursor().StartDeferDrawing();
auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); });
_textBuffer->ResizeTraditional(coordNewScreenSize); _textBuffer->ResizeTraditional(coordNewScreenSize);
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
@ -1545,9 +1537,8 @@ void SCREEN_INFORMATION::SetCursorDBMode(const bool DoubleCursor)
// - TurnOn - true if cursor should be left on, false if should be left off // - TurnOn - true if cursor should be left on, false if should be left off
// Return Value: // Return Value:
// - Status // - Status
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::SetCursorPosition(const til::point Position, const bool TurnOn) [[nodiscard]] NTSTATUS SCREEN_INFORMATION::SetCursorPosition(const til::point Position)
{ {
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& cursor = _textBuffer->GetCursor(); auto& cursor = _textBuffer->GetCursor();
// //
@ -1581,20 +1572,6 @@ void SCREEN_INFORMATION::SetCursorDBMode(const bool DoubleCursor)
_virtualBottom = Position.y; _virtualBottom = Position.y;
} }
// if we have the focus, adjust the cursor state
if (gci.Flags & CONSOLE_HAS_FOCUS)
{
if (TurnOn)
{
cursor.SetDelay(false);
cursor.SetIsOn(true);
}
else
{
cursor.SetDelay(true);
}
}
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
@ -1746,7 +1723,7 @@ const SCREEN_INFORMATION* SCREEN_INFORMATION::GetAltBuffer() const noexcept
auto& altCursor = createdBuffer->GetTextBuffer().GetCursor(); auto& altCursor = createdBuffer->GetTextBuffer().GetCursor();
altCursor.SetStyle(myCursor.GetSize(), myCursor.GetType()); altCursor.SetStyle(myCursor.GetSize(), myCursor.GetType());
altCursor.SetIsVisible(myCursor.IsVisible()); altCursor.SetIsVisible(myCursor.IsVisible());
altCursor.SetBlinkingAllowed(myCursor.IsBlinkingAllowed()); altCursor.SetIsBlinking(myCursor.IsBlinking());
// The new position should match the viewport-relative position of the main buffer. // The new position should match the viewport-relative position of the main buffer.
auto altCursorPos = myCursor.GetPosition(); auto altCursorPos = myCursor.GetPosition();
altCursorPos.y -= GetVirtualViewport().Top(); altCursorPos.y -= GetVirtualViewport().Top();
@ -1895,7 +1872,7 @@ void SCREEN_INFORMATION::UseMainScreenBuffer()
auto& mainCursor = psiMain->GetTextBuffer().GetCursor(); auto& mainCursor = psiMain->GetTextBuffer().GetCursor();
mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType()); mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType());
mainCursor.SetIsVisible(altCursor.IsVisible()); mainCursor.SetIsVisible(altCursor.IsVisible());
mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed()); mainCursor.SetIsBlinking(altCursor.IsBlinking());
// Copy the alt buffer's output mode back to the main buffer. // Copy the alt buffer's output mode back to the main buffer.
psiMain->OutputMode = psiAlt->OutputMode; psiMain->OutputMode = psiAlt->OutputMode;
@ -2336,19 +2313,6 @@ Viewport SCREEN_INFORMATION::GetVtPageArea() const noexcept
return Viewport::FromExclusive({ 0, top, bufferWidth, top + viewportHeight }); return Viewport::FromExclusive({ 0, top, bufferWidth, top + viewportHeight });
} }
// Method Description:
// - Returns true if the character at the cursor's current position is wide.
// Arguments:
// - <none>
// Return Value:
// - true if the character at the cursor's current position is wide
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const
{
const auto& buffer = GetTextBuffer();
const auto position = buffer.GetCursor().GetPosition();
return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single;
}
// Method Description: // Method Description:
// - Gets the current font of the screen buffer. // - Gets the current font of the screen buffer.
// Arguments: // Arguments:

View File

@ -70,7 +70,7 @@ public:
const UINT uiCursorSize, const UINT uiCursorSize,
_Outptr_ SCREEN_INFORMATION** const ppScreen); _Outptr_ SCREEN_INFORMATION** const ppScreen);
~SCREEN_INFORMATION(); ~SCREEN_INFORMATION() override;
void GetScreenBufferInformation(_Out_ til::size* pcoordSize, void GetScreenBufferInformation(_Out_ til::size* pcoordSize,
_Out_ til::point* pcoordCursorPosition, _Out_ til::point* pcoordCursorPosition,
@ -166,8 +166,6 @@ public:
InputBuffer* const GetActiveInputBuffer() const override; InputBuffer* const GetActiveInputBuffer() const override;
#pragma endregion #pragma endregion
bool CursorIsDoubleWidth() const;
DWORD OutputMode; DWORD OutputMode;
short WheelDelta; short WheelDelta;
@ -194,7 +192,7 @@ public:
void SetCursorType(const CursorType Type, const bool setMain = false) noexcept; void SetCursorType(const CursorType Type, const bool setMain = false) noexcept;
void SetCursorDBMode(const bool DoubleCursor); void SetCursorDBMode(const bool DoubleCursor);
[[nodiscard]] NTSTATUS SetCursorPosition(const til::point Position, const bool TurnOn); [[nodiscard]] NTSTATUS SetCursorPosition(til::point Position);
[[nodiscard]] NTSTATUS UseAlternateScreenBuffer(const TextAttribute& initAttributes); [[nodiscard]] NTSTATUS UseAlternateScreenBuffer(const TextAttribute& initAttributes);
void UseMainScreenBuffer(); void UseMainScreenBuffer();

View File

@ -177,9 +177,6 @@ void Selection::InitializeMouseSelection(const til::point coordBufferPos)
if (pWindow != nullptr) if (pWindow != nullptr)
{ {
pWindow->UpdateWindowText(); pWindow->UpdateWindowText();
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.SelectionChanged();
} }
// Fire off an event to let accessibility apps know the selection has changed. // Fire off an event to let accessibility apps know the selection has changed.
@ -302,7 +299,6 @@ void Selection::_ExtendSelection(Selection::SelectionData* d, _In_ til::point co
// Fire off an event to let accessibility apps know the selection has changed. // Fire off an event to let accessibility apps know the selection has changed.
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier; auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(coordBufferPos, true); an.CursorChanged(coordBufferPos, true);
an.SelectionChanged();
} }
// Routine Description: // Routine Description:
@ -313,9 +309,6 @@ void Selection::_ExtendSelection(Selection::SelectionData* d, _In_ til::point co
// - <none> // - <none>
void Selection::_CancelMouseSelection() void Selection::_CancelMouseSelection()
{ {
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& ScreenInfo = gci.GetActiveOutputBuffer();
// invert old select rect. if we're selecting by mouse, we // invert old select rect. if we're selecting by mouse, we
// always have a selection rect. // always have a selection rect.
HideSelection(); HideSelection();
@ -331,6 +324,8 @@ void Selection::_CancelMouseSelection()
// Mark the cursor position as changed so we'll fire off a win event. // Mark the cursor position as changed so we'll fire off a win event.
// NOTE(lhecker): Why is this the only cancel function that would raise a WinEvent? Makes no sense to me. // NOTE(lhecker): Why is this the only cancel function that would raise a WinEvent? Makes no sense to me.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& ScreenInfo = gci.GetActiveOutputBuffer();
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier; auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(ScreenInfo.GetTextBuffer().GetCursor().GetPosition(), false); an.CursorChanged(ScreenInfo.GetTextBuffer().GetCursor().GetPosition(), false);
} }
@ -504,7 +499,7 @@ void Selection::InitializeMarkSelection()
screenInfo.SetCursorInformation(100, TRUE); screenInfo.SetCursorInformation(100, TRUE);
const auto coordPosition = cursor.GetPosition(); const auto coordPosition = cursor.GetPosition();
LOG_IF_FAILED(screenInfo.SetCursorPosition(coordPosition, true)); LOG_IF_FAILED(screenInfo.SetCursorPosition(coordPosition));
// set the cursor position as the anchor position // set the cursor position as the anchor position
// it will get updated as the cursor moves for mark mode, // it will get updated as the cursor moves for mark mode,

View File

@ -182,7 +182,6 @@ void Selection::_RestoreDataToCursor(Cursor& cursor) noexcept
cursor.SetSize(_d->ulSavedCursorSize); cursor.SetSize(_d->ulSavedCursorSize);
cursor.SetIsVisible(_d->fSavedCursorVisible); cursor.SetIsVisible(_d->fSavedCursorVisible);
cursor.SetType(_d->savedCursorType); cursor.SetType(_d->savedCursorType);
cursor.SetIsOn(true);
cursor.SetPosition(_d->coordSavedCursorPosition); cursor.SetPosition(_d->coordSavedCursorPosition);
} }

View File

@ -16,7 +16,6 @@ Revision History:
#pragma once #pragma once
#include "CursorBlinker.hpp"
#include "IIoProvider.hpp" #include "IIoProvider.hpp"
#include "readDataCooked.hpp" #include "readDataCooked.hpp"
#include "settings.hpp" #include "settings.hpp"
@ -143,7 +142,6 @@ public:
friend void SetActiveScreenBuffer(_Inout_ SCREEN_INFORMATION& screenInfo); friend void SetActiveScreenBuffer(_Inout_ SCREEN_INFORMATION& screenInfo);
friend class SCREEN_INFORMATION; friend class SCREEN_INFORMATION;
friend class CommonState; friend class CommonState;
Microsoft::Console::CursorBlinker& GetCursorBlinker() noexcept;
MidiAudio& GetMidiAudio(); MidiAudio& GetMidiAudio();
@ -165,7 +163,6 @@ private:
std::optional<std::wstring> _pendingClipboardText; std::optional<std::wstring> _pendingClipboardText;
Microsoft::Console::VirtualTerminal::VtIo _vtIo; Microsoft::Console::VirtualTerminal::VtIo _vtIo;
Microsoft::Console::CursorBlinker _blinker;
MidiAudio _midiAudio; MidiAudio _midiAudio;
}; };

View File

@ -45,7 +45,6 @@ SOURCES = \
..\selectionState.cpp \ ..\selectionState.cpp \
..\scrolling.cpp \ ..\scrolling.cpp \
..\cmdline.cpp \ ..\cmdline.cpp \
..\CursorBlinker.cpp \
..\alias.cpp \ ..\alias.cpp \
..\history.cpp \ ..\history.cpp \
..\VtIo.cpp \ ..\VtIo.cpp \

View File

@ -408,7 +408,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
mainCursor.SetPosition(mainCursorPos); mainCursor.SetPosition(mainCursorPos);
mainCursor.SetIsVisible(mainCursorVisible); mainCursor.SetIsVisible(mainCursorVisible);
mainCursor.SetStyle(mainCursorSize, mainCursorType); mainCursor.SetStyle(mainCursorSize, mainCursorType);
mainCursor.SetBlinkingAllowed(mainCursorBlinking); mainCursor.SetIsBlinking(mainCursorBlinking);
Log::Comment(L"Switch to the alternate buffer."); Log::Comment(L"Switch to the alternate buffer.");
VERIFY_SUCCEEDED(mainBuffer.UseAlternateScreenBuffer({})); VERIFY_SUCCEEDED(mainBuffer.UseAlternateScreenBuffer({}));
@ -423,7 +423,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
Log::Comment(L"Confirm the cursor style is inherited from the main buffer."); Log::Comment(L"Confirm the cursor style is inherited from the main buffer.");
VERIFY_ARE_EQUAL(mainCursorSize, altCursor.GetSize()); VERIFY_ARE_EQUAL(mainCursorSize, altCursor.GetSize());
VERIFY_ARE_EQUAL(mainCursorType, altCursor.GetType()); VERIFY_ARE_EQUAL(mainCursorType, altCursor.GetType());
VERIFY_ARE_EQUAL(mainCursorBlinking, altCursor.IsBlinkingAllowed()); VERIFY_ARE_EQUAL(mainCursorBlinking, altCursor.IsBlinking());
Log::Comment(L"Set the cursor attributes in the alt buffer."); Log::Comment(L"Set the cursor attributes in the alt buffer.");
auto altCursorPos = til::point{ 5, 3 }; auto altCursorPos = til::point{ 5, 3 };
@ -434,7 +434,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
altCursor.SetPosition(altCursorPos); altCursor.SetPosition(altCursorPos);
altCursor.SetIsVisible(altCursorVisible); altCursor.SetIsVisible(altCursorVisible);
altCursor.SetStyle(altCursorSize, altCursorType); altCursor.SetStyle(altCursorSize, altCursorType);
altCursor.SetBlinkingAllowed(altCursorBlinking); altCursor.SetIsBlinking(altCursorBlinking);
Log::Comment(L"Switch back to the main buffer."); Log::Comment(L"Switch back to the main buffer.");
useMain.release(); useMain.release();
@ -448,7 +448,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
Log::Comment(L"Confirm the cursor style is inherited from the alt buffer."); Log::Comment(L"Confirm the cursor style is inherited from the alt buffer.");
VERIFY_ARE_EQUAL(altCursorSize, mainCursor.GetSize()); VERIFY_ARE_EQUAL(altCursorSize, mainCursor.GetSize());
VERIFY_ARE_EQUAL(altCursorType, mainCursor.GetType()); VERIFY_ARE_EQUAL(altCursorType, mainCursor.GetType());
VERIFY_ARE_EQUAL(altCursorBlinking, mainCursor.IsBlinkingAllowed()); VERIFY_ARE_EQUAL(altCursorBlinking, mainCursor.IsBlinking());
} }
void ScreenBufferTests::TestReverseLineFeed() void ScreenBufferTests::TestReverseLineFeed()
@ -3829,7 +3829,7 @@ void ScreenBufferTests::ScrollOperations()
} }
Log::Comment(L"Set the cursor position and perform the operation."); Log::Comment(L"Set the cursor position and perform the operation.");
VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos, true)); VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos));
stateMachine.ProcessString(escapeSequence.str()); stateMachine.ProcessString(escapeSequence.str());
// The cursor shouldn't move. // The cursor shouldn't move.
@ -3911,7 +3911,7 @@ void ScreenBufferTests::InsertReplaceMode()
Log::Comment(L"Write additional content into a line of text with IRM mode enabled."); Log::Comment(L"Write additional content into a line of text with IRM mode enabled.");
// Set the cursor position partway through the target row. // Set the cursor position partway through the target row.
VERIFY_SUCCEEDED(si.SetCursorPosition({ targetCol, targetRow }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ targetCol, targetRow }));
// Enable Insert/Replace mode. // Enable Insert/Replace mode.
stateMachine.ProcessString(L"\033[4h"); stateMachine.ProcessString(L"\033[4h");
// Write out some new content. // Write out some new content.
@ -3936,7 +3936,7 @@ void ScreenBufferTests::InsertReplaceMode()
Log::Comment(L"Write additional content into a line of text with IRM mode disabled."); Log::Comment(L"Write additional content into a line of text with IRM mode disabled.");
// Set the cursor position partway through the target row. // Set the cursor position partway through the target row.
VERIFY_SUCCEEDED(si.SetCursorPosition({ targetCol, targetRow }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ targetCol, targetRow }));
// Disable Insert/Replace mode. // Disable Insert/Replace mode.
stateMachine.ProcessString(L"\033[4l"); stateMachine.ProcessString(L"\033[4l");
// Write out some new content. // Write out some new content.
@ -3999,7 +3999,7 @@ void ScreenBufferTests::InsertChars()
auto insertPos = til::CoordType{ 20 }; auto insertPos = til::CoordType{ 20 };
// Place the cursor in the center of the line. // Place the cursor in the center of the line.
VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }));
// Save the cursor position. It shouldn't move for the rest of the test. // Save the cursor position. It shouldn't move for the rest of the test.
const auto& cursor = si.GetTextBuffer().GetCursor(); const auto& cursor = si.GetTextBuffer().GetCursor();
@ -4067,7 +4067,7 @@ void ScreenBufferTests::InsertChars()
// Move cursor to right edge. // Move cursor to right edge.
insertPos = horizontalMarginsActive ? viewportEnd - 1 : bufferWidth - 1; insertPos = horizontalMarginsActive ? viewportEnd - 1 : bufferWidth - 1;
VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }));
expectedCursor = cursor.GetPosition(); expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green. // Fill the entire line with Qs. Blue on Green.
@ -4116,7 +4116,7 @@ void ScreenBufferTests::InsertChars()
// Move cursor to left edge. // Move cursor to left edge.
insertPos = horizontalMarginsActive ? viewportStart : 0; insertPos = horizontalMarginsActive ? viewportStart : 0;
VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }));
expectedCursor = cursor.GetPosition(); expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green. // Fill the entire line with Qs. Blue on Green.
@ -4199,7 +4199,7 @@ void ScreenBufferTests::DeleteChars()
auto deletePos = til::CoordType{ 20 }; auto deletePos = til::CoordType{ 20 };
// Place the cursor in the center of the line. // Place the cursor in the center of the line.
VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }));
// Save the cursor position. It shouldn't move for the rest of the test. // Save the cursor position. It shouldn't move for the rest of the test.
const auto& cursor = si.GetTextBuffer().GetCursor(); const auto& cursor = si.GetTextBuffer().GetCursor();
@ -4267,7 +4267,7 @@ void ScreenBufferTests::DeleteChars()
// Move cursor to right edge. // Move cursor to right edge.
deletePos = horizontalMarginsActive ? viewportEnd - 1 : bufferWidth - 1; deletePos = horizontalMarginsActive ? viewportEnd - 1 : bufferWidth - 1;
VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }));
expectedCursor = cursor.GetPosition(); expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green. // Fill the entire line with Qs. Blue on Green.
@ -4316,7 +4316,7 @@ void ScreenBufferTests::DeleteChars()
// Move cursor to left edge. // Move cursor to left edge.
deletePos = horizontalMarginsActive ? viewportStart : 0; deletePos = horizontalMarginsActive ? viewportStart : 0;
VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }));
expectedCursor = cursor.GetPosition(); expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green. // Fill the entire line with Qs. Blue on Green.
@ -4484,7 +4484,7 @@ void ScreenBufferTests::ScrollingWideCharsHorizontally()
_FillLine(testRow, testChars, testAttr); _FillLine(testRow, testChars, testAttr);
Log::Comment(L"Position the cursor at the start of the test row"); Log::Comment(L"Position the cursor at the start of the test row");
VERIFY_SUCCEEDED(si.SetCursorPosition({ 0, testRow }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ 0, testRow }));
Log::Comment(L"Insert 1 cell at the start of the test row"); Log::Comment(L"Insert 1 cell at the start of the test row");
stateMachine.ProcessString(L"\033[@"); stateMachine.ProcessString(L"\033[@");
@ -4552,7 +4552,7 @@ void ScreenBufferTests::EraseScrollbackTests()
const auto cursorPos = til::point{ centerX, centerY }; const auto cursorPos = til::point{ centerX, centerY };
Log::Comment(L"Set the cursor position and erase the scrollback."); Log::Comment(L"Set the cursor position and erase the scrollback.");
VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos, true)); VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos));
stateMachine.ProcessString(L"\x1b[3J"); stateMachine.ProcessString(L"\x1b[3J");
// The viewport should move to the top of the buffer, while the cursor // The viewport should move to the top of the buffer, while the cursor
@ -4682,7 +4682,7 @@ void ScreenBufferTests::EraseTests()
const auto centerY = (viewport.Top() + viewport.BottomExclusive()) / 2; const auto centerY = (viewport.Top() + viewport.BottomExclusive()) / 2;
Log::Comment(L"Set the cursor position and perform the operation."); Log::Comment(L"Set the cursor position and perform the operation.");
VERIFY_SUCCEEDED(si.SetCursorPosition({ centerX, centerY }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ centerX, centerY }));
stateMachine.ProcessString(escapeSequence.str()); stateMachine.ProcessString(escapeSequence.str());
// Get cursor position and viewport range. // Get cursor position and viewport range.
@ -5756,7 +5756,7 @@ void ScreenBufferTests::HardResetBuffer()
si.SetAttributes(TextAttribute()); si.SetAttributes(TextAttribute());
si.ClearTextData(); si.ClearTextData();
VERIFY_SUCCEEDED(si.SetViewportOrigin(true, { 0, 0 }, true)); VERIFY_SUCCEEDED(si.SetViewportOrigin(true, { 0, 0 }, true));
VERIFY_SUCCEEDED(si.SetCursorPosition({ 0, 0 }, true)); VERIFY_SUCCEEDED(si.SetCursorPosition({ 0, 0 }));
VERIFY_IS_TRUE(isBufferClear()); VERIFY_IS_TRUE(isBufferClear());
Log::Comment(L"Write a single line of text to the buffer"); Log::Comment(L"Write a single line of text to the buffer");
@ -5983,7 +5983,7 @@ void ScreenBufferTests::ClearAlternateBuffer()
auto useMain = wil::scope_exit([&] { altBuffer.UseMainScreenBuffer(); }); auto useMain = wil::scope_exit([&] { altBuffer.UseMainScreenBuffer(); });
// Set the position to home; otherwise, it's inherited from the main buffer. // Set the position to home; otherwise, it's inherited from the main buffer.
VERIFY_SUCCEEDED(altBuffer.SetCursorPosition({ 0, 0 }, true)); VERIFY_SUCCEEDED(altBuffer.SetCursorPosition({ 0, 0 }));
WriteText(altBuffer.GetTextBuffer()); WriteText(altBuffer.GetTextBuffer());
VerifyText(altBuffer.GetTextBuffer()); VerifyText(altBuffer.GetTextBuffer());
@ -6890,7 +6890,7 @@ void ScreenBufferTests::CursorSaveRestore()
stateMachine.ProcessString(restoreCursor); stateMachine.ProcessString(restoreCursor);
// Verify initial position, delayed wrap, colors, and graphic character set. // Verify initial position, delayed wrap, colors, and graphic character set.
VERIFY_ARE_EQUAL(til::point(20, 10), cursor.GetPosition()); VERIFY_ARE_EQUAL(til::point(20, 10), cursor.GetPosition());
VERIFY_IS_TRUE(cursor.IsDelayedEOLWrap()); VERIFY_IS_TRUE(cursor.GetDelayEOLWrap().has_value());
cursor.ResetDelayEOLWrap(); cursor.ResetDelayEOLWrap();
VERIFY_ARE_EQUAL(colorAttrs, si.GetAttributes()); VERIFY_ARE_EQUAL(colorAttrs, si.GetAttributes());
stateMachine.ProcessString(asciiText); stateMachine.ProcessString(asciiText);
@ -6905,7 +6905,7 @@ void ScreenBufferTests::CursorSaveRestore()
stateMachine.ProcessString(restoreCursor); stateMachine.ProcessString(restoreCursor);
// Verify initial saved position, delayed wrap, colors, and graphic character set. // Verify initial saved position, delayed wrap, colors, and graphic character set.
VERIFY_ARE_EQUAL(til::point(20, 10), cursor.GetPosition()); VERIFY_ARE_EQUAL(til::point(20, 10), cursor.GetPosition());
VERIFY_IS_TRUE(cursor.IsDelayedEOLWrap()); VERIFY_IS_TRUE(cursor.GetDelayEOLWrap().has_value());
cursor.ResetDelayEOLWrap(); cursor.ResetDelayEOLWrap();
VERIFY_ARE_EQUAL(colorAttrs, si.GetAttributes()); VERIFY_ARE_EQUAL(colorAttrs, si.GetAttributes());
stateMachine.ProcessString(asciiText); stateMachine.ProcessString(asciiText);
@ -6923,7 +6923,7 @@ void ScreenBufferTests::CursorSaveRestore()
stateMachine.ProcessString(restoreCursor); stateMachine.ProcessString(restoreCursor);
// Verify home position, no delayed wrap, default attributes, and ascii character set. // Verify home position, no delayed wrap, default attributes, and ascii character set.
VERIFY_ARE_EQUAL(til::point(0, 0), cursor.GetPosition()); VERIFY_ARE_EQUAL(til::point(0, 0), cursor.GetPosition());
VERIFY_IS_FALSE(cursor.IsDelayedEOLWrap()); VERIFY_IS_FALSE(cursor.GetDelayEOLWrap().has_value());
VERIFY_ARE_EQUAL(defaultAttrs, si.GetAttributes()); VERIFY_ARE_EQUAL(defaultAttrs, si.GetAttributes());
stateMachine.ProcessString(asciiText); stateMachine.ProcessString(asciiText);
VERIFY_IS_TRUE(_ValidateLineContains(til::point(0, 0), asciiText, defaultAttrs)); VERIFY_IS_TRUE(_ValidateLineContains(til::point(0, 0), asciiText, defaultAttrs));
@ -7023,7 +7023,7 @@ void ScreenBufferTests::ScreenAlignmentPattern()
// Place the cursor in the center. // Place the cursor in the center.
auto cursorPos = til::point{ bufferWidth / 2, (viewportStart + viewportEnd) / 2 }; auto cursorPos = til::point{ bufferWidth / 2, (viewportStart + viewportEnd) / 2 };
VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos, true)); VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos));
Log::Comment(L"Execute the DECALN escape sequence."); Log::Comment(L"Execute the DECALN escape sequence.");
stateMachine.ProcessString(L"\x1b#8"); stateMachine.ProcessString(L"\x1b#8");
@ -7059,44 +7059,35 @@ void ScreenBufferTests::TestCursorIsOn()
auto& cursor = tbi.GetCursor(); auto& cursor = tbi.GetCursor();
stateMachine.ProcessString(L"Hello World"); stateMachine.ProcessString(L"Hello World");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12l"); stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h"); stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
cursor.SetIsOn(false);
stateMachine.ProcessString(L"\x1b[?12l"); stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h"); stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25l"); stateMachine.ProcessString(L"\x1b[?25l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsVisible()); VERIFY_IS_FALSE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25h"); stateMachine.ProcessString(L"\x1b[?25h");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsVisible()); VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12;25l"); stateMachine.ProcessString(L"\x1b[?12;25l");
VERIFY_IS_TRUE(cursor.IsOn()); VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsVisible()); VERIFY_IS_FALSE(cursor.IsVisible());
} }
@ -8158,7 +8149,7 @@ void ScreenBufferTests::DelayedWrapReset()
stateMachine.ProcessCharacter(L'X'); stateMachine.ProcessCharacter(L'X');
{ {
auto& cursor = si.GetTextBuffer().GetCursor(); auto& cursor = si.GetTextBuffer().GetCursor();
VERIFY_IS_TRUE(cursor.IsDelayedEOLWrap()); VERIFY_IS_TRUE(cursor.GetDelayEOLWrap().has_value());
VERIFY_ARE_EQUAL(startPos, cursor.GetPosition()); VERIFY_ARE_EQUAL(startPos, cursor.GetPosition());
} }
@ -8170,7 +8161,7 @@ void ScreenBufferTests::DelayedWrapReset()
{ {
auto& cursor = si.GetTextBuffer().GetCursor(); auto& cursor = si.GetTextBuffer().GetCursor();
const auto actualPos = cursor.GetPosition() - si.GetViewport().Origin(); const auto actualPos = cursor.GetPosition() - si.GetViewport().Origin();
VERIFY_IS_FALSE(cursor.IsDelayedEOLWrap()); VERIFY_IS_FALSE(cursor.GetDelayEOLWrap().has_value());
VERIFY_ARE_EQUAL(expectedPos, actualPos); VERIFY_ARE_EQUAL(expectedPos, actualPos);
} }
} }

View File

@ -366,23 +366,15 @@ void TextBufferTests::TestCopyProperties()
testTextBuffer->GetCursor().SetIsVisible(false); testTextBuffer->GetCursor().SetIsVisible(false);
otherTbi.GetCursor().SetIsVisible(true); otherTbi.GetCursor().SetIsVisible(true);
testTextBuffer->GetCursor().SetIsOn(false);
otherTbi.GetCursor().SetIsOn(true);
testTextBuffer->GetCursor().SetIsDouble(false); testTextBuffer->GetCursor().SetIsDouble(false);
otherTbi.GetCursor().SetIsDouble(true); otherTbi.GetCursor().SetIsDouble(true);
testTextBuffer->GetCursor().SetDelay(false);
otherTbi.GetCursor().SetDelay(true);
// run copy // run copy
testTextBuffer->CopyProperties(otherTbi); testTextBuffer->CopyProperties(otherTbi);
// test that new now contains values from other // test that new now contains values from other
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsVisible()); VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsVisible());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsDouble()); VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsDouble());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().GetDelay());
} }
void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY) void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY)

View File

@ -938,4 +938,15 @@ namespace til
}; };
} }
namespace std
{
template<typename T, size_t N, typename F>
void erase_if(til::small_vector<T, N>& vec, F&& pred)
{
const auto beg = vec.begin();
const auto end = vec.end();
vec.erase(std::remove_if(beg, end, std::forward<F>(pred)), end);
}
}
#pragma warning(pop) #pragma warning(pop)

View File

@ -9,12 +9,12 @@ using namespace Microsoft::Console::Interactivity::OneCore;
UINT SystemConfigurationProvider::GetCaretBlinkTime() noexcept UINT SystemConfigurationProvider::GetCaretBlinkTime() noexcept
{ {
return s_DefaultCaretBlinkTime; return 530; // milliseconds
} }
bool SystemConfigurationProvider::IsCaretBlinkingEnabled() noexcept bool SystemConfigurationProvider::IsCaretBlinkingEnabled() noexcept
{ {
return s_DefaultIsCaretBlinkingEnabled; return true;
} }
int SystemConfigurationProvider::GetNumberOfMouseButtons() noexcept int SystemConfigurationProvider::GetNumberOfMouseButtons() noexcept
@ -25,23 +25,23 @@ int SystemConfigurationProvider::GetNumberOfMouseButtons() noexcept
} }
else else
{ {
return s_DefaultNumberOfMouseButtons; return 3;
} }
} }
ULONG SystemConfigurationProvider::GetCursorWidth() noexcept ULONG SystemConfigurationProvider::GetCursorWidth() noexcept
{ {
return s_DefaultCursorWidth; return 1;
} }
ULONG SystemConfigurationProvider::GetNumberOfWheelScrollLines() noexcept ULONG SystemConfigurationProvider::GetNumberOfWheelScrollLines() noexcept
{ {
return s_DefaultNumberOfWheelScrollLines; return 3;
} }
ULONG SystemConfigurationProvider::GetNumberOfWheelScrollCharacters() noexcept ULONG SystemConfigurationProvider::GetNumberOfWheelScrollCharacters() noexcept
{ {
return s_DefaultNumberOfWheelScrollCharacters; return 3;
} }
void SystemConfigurationProvider::GetSettingsFromLink( void SystemConfigurationProvider::GetSettingsFromLink(

View File

@ -41,13 +41,6 @@ namespace Microsoft::Console::Interactivity::OneCore
_Inout_opt_ IconInfo* iconInfo) override; _Inout_opt_ IconInfo* iconInfo) override;
private: private:
static constexpr UINT s_DefaultCaretBlinkTime = 530; // milliseconds
static constexpr bool s_DefaultIsCaretBlinkingEnabled = true;
static constexpr int s_DefaultNumberOfMouseButtons = 3;
static constexpr ULONG s_DefaultCursorWidth = 1;
static constexpr ULONG s_DefaultNumberOfWheelScrollLines = 3;
static constexpr ULONG s_DefaultNumberOfWheelScrollCharacters = 3;
friend class ::InputTests; friend class ::InputTests;
}; };
} }

View File

@ -35,7 +35,7 @@ ULONG SystemConfigurationProvider::GetCursorWidth()
else else
{ {
LOG_LAST_ERROR(); LOG_LAST_ERROR();
return s_DefaultCursorWidth; return 1;
} }
} }

View File

@ -39,8 +39,5 @@ namespace Microsoft::Console::Interactivity::Win32
_In_ PCWSTR pwszCurrDir, _In_ PCWSTR pwszCurrDir,
_In_ PCWSTR pwszAppName, _In_ PCWSTR pwszAppName,
_Inout_opt_ IconInfo* iconInfo); _Inout_opt_ IconInfo* iconInfo);
private:
static const ULONG s_DefaultCursorWidth = 1;
}; };
} }

View File

@ -167,15 +167,12 @@ void Window::_UpdateSystemMetrics() const
{ {
const auto dpiApi = ServiceLocator::LocateHighDpiApi<WindowDpiApi>(); const auto dpiApi = ServiceLocator::LocateHighDpiApi<WindowDpiApi>();
auto& g = ServiceLocator::LocateGlobals(); auto& g = ServiceLocator::LocateGlobals();
auto& gci = g.getConsoleInformation();
Scrolling::s_UpdateSystemMetrics(); Scrolling::s_UpdateSystemMetrics();
g.sVerticalScrollSize = dpiApi->GetSystemMetricsForDpi(SM_CXVSCROLL, g.dpi); g.sVerticalScrollSize = dpiApi->GetSystemMetricsForDpi(SM_CXVSCROLL, g.dpi);
g.sHorizontalScrollSize = dpiApi->GetSystemMetricsForDpi(SM_CYHSCROLL, g.dpi); g.sHorizontalScrollSize = dpiApi->GetSystemMetricsForDpi(SM_CYHSCROLL, g.dpi);
gci.GetCursorBlinker().UpdateSystemMetrics();
const auto sysConfig = ServiceLocator::LocateSystemConfigurationProvider(); const auto sysConfig = ServiceLocator::LocateSystemConfigurationProvider();
g.cursorPixelWidth = sysConfig->GetCursorWidth(); g.cursorPixelWidth = sysConfig->GetCursorWidth();

View File

@ -27,17 +27,17 @@ struct TsfDataProvider : Microsoft::Console::TSF::IDataProvider
{ {
virtual ~TsfDataProvider() = default; virtual ~TsfDataProvider() = default;
STDMETHODIMP TsfDataProvider::QueryInterface(REFIID, void**) noexcept override STDMETHODIMP QueryInterface(REFIID, void**) noexcept override
{ {
return E_NOTIMPL; return E_NOTIMPL;
} }
ULONG STDMETHODCALLTYPE TsfDataProvider::AddRef() noexcept override ULONG STDMETHODCALLTYPE AddRef() noexcept override
{ {
return 1; return 1;
} }
ULONG STDMETHODCALLTYPE TsfDataProvider::Release() noexcept override ULONG STDMETHODCALLTYPE Release() noexcept override
{ {
return 1; return 1;
} }
@ -287,12 +287,12 @@ static constexpr TsfDataProvider s_tsfDataProvider;
case WM_SETFOCUS: case WM_SETFOCUS:
{ {
gci.ProcessHandleList.ModifyConsoleProcessFocus(TRUE); gci.ProcessHandleList.ModifyConsoleProcessFocus(TRUE);
gci.Flags |= CONSOLE_HAS_FOCUS; gci.Flags |= CONSOLE_HAS_FOCUS;
gci.GetCursorBlinker().FocusStart(); if (const auto renderer = ServiceLocator::LocateGlobals().pRender)
{
HandleFocusEvent(TRUE); renderer->AllowCursorVisibility(Render::InhibitionSource::Host, true);
}
if (!g.tsf) if (!g.tsf)
{ {
@ -306,21 +306,21 @@ static constexpr TsfDataProvider s_tsfDataProvider;
LOG_IF_FAILED(_pUiaProvider->SetTextAreaFocus()); LOG_IF_FAILED(_pUiaProvider->SetTextAreaFocus());
} }
HandleFocusEvent(TRUE);
break; break;
} }
case WM_KILLFOCUS: case WM_KILLFOCUS:
{ {
gci.ProcessHandleList.ModifyConsoleProcessFocus(FALSE); gci.ProcessHandleList.ModifyConsoleProcessFocus(FALSE);
gci.Flags &= ~CONSOLE_HAS_FOCUS; gci.Flags &= ~CONSOLE_HAS_FOCUS;
// turn it off when we lose focus. if (const auto renderer = ServiceLocator::LocateGlobals().pRender)
gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().SetIsOn(false); {
gci.GetCursorBlinker().FocusEnd(); renderer->AllowCursorVisibility(Render::InhibitionSource::Host, false);
}
HandleFocusEvent(FALSE); HandleFocusEvent(FALSE);
break; break;
} }
@ -364,9 +364,9 @@ static constexpr TsfDataProvider s_tsfDataProvider;
case WM_SETTINGCHANGE: case WM_SETTINGCHANGE:
{ {
LOG_IF_FAILED(Microsoft::Console::Internal::Theming::TrySetDarkMode(hWnd)); LOG_IF_FAILED(Microsoft::Console::Internal::Theming::TrySetDarkMode(hWnd));
gci.GetCursorBlinker().SettingsChanged(); gci.renderData.UpdateSystemMetrics();
}
__fallthrough; __fallthrough;
}
case WM_DISPLAYCHANGE: case WM_DISPLAYCHANGE:
{ {

View File

@ -4,7 +4,6 @@
```mermaid ```mermaid
graph TD graph TD
RenderThread["RenderThread (base/thread.cpp)<br><small>calls Renderer::PaintFrame() x times per sec</small>"]
Renderer["Renderer (base/renderer.cpp)<br><small>breaks the text buffer down into GDI-oriented graphics<br>primitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)</small>"] Renderer["Renderer (base/renderer.cpp)<br><small>breaks the text buffer down into GDI-oriented graphics<br>primitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)</small>"]
RenderEngineBase[/"RenderEngineBase<br>(base/RenderEngineBase.cpp)<br><small>abstracts 24 LOC 👻</small>"\] RenderEngineBase[/"RenderEngineBase<br>(base/RenderEngineBase.cpp)<br><small>abstracts 24 LOC 👻</small>"\]
GdiEngine["GdiEngine (gdi/...)"] GdiEngine["GdiEngine (gdi/...)"]
@ -18,8 +17,6 @@ graph TD
BackendD3D.cpp["BackendD3D.cpp<br><small>Custom, performant text renderer<br>with our own glyph cache</small>"] BackendD3D.cpp["BackendD3D.cpp<br><small>Custom, performant text renderer<br>with our own glyph cache</small>"]
end end
RenderThread --> Renderer
Renderer -->|owns| RenderThread
Renderer -.-> RenderEngineBase Renderer -.-> RenderEngineBase
%% Mermaid.js has no support for backwards arrow at the moment %% Mermaid.js has no support for backwards arrow at the moment
RenderEngineBase <-.->|extends| GdiEngine RenderEngineBase <-.->|extends| GdiEngine

View File

@ -59,11 +59,6 @@ void RenderSettings::RestoreDefaultSettings() noexcept
void RenderSettings::SetRenderMode(const Mode mode, const bool enabled) noexcept void RenderSettings::SetRenderMode(const Mode mode, const bool enabled) noexcept
{ {
_renderMode.set(mode, enabled); _renderMode.set(mode, enabled);
// If blinking is disabled, make sure blinking content is not faint.
if (mode == Mode::BlinkAllowed && !enabled)
{
_blinkShouldBeFaint = false;
}
} }
// Routine Description: // Routine Description:
@ -187,8 +182,6 @@ void RenderSettings::RestoreDefaultColorAliasIndex(const ColorAlias alias) noexc
// - The color values of the attribute's foreground and background. // - The color values of the attribute's foreground and background.
std::pair<COLORREF, COLORREF> RenderSettings::GetAttributeColors(const TextAttribute& attr) const noexcept std::pair<COLORREF, COLORREF> RenderSettings::GetAttributeColors(const TextAttribute& attr) const noexcept
{ {
_blinkIsInUse = _blinkIsInUse || attr.IsBlinking();
const auto fgTextColor = attr.GetForeground(); const auto fgTextColor = attr.GetForeground();
const auto bgTextColor = attr.GetBackground(); const auto bgTextColor = attr.GetBackground();
@ -296,35 +289,7 @@ COLORREF RenderSettings::GetAttributeUnderlineColor(const TextAttribute& attr) c
return ul; return ul;
} }
// Routine Description: void RenderSettings::ToggleBlinkRendition() noexcept
// - Increments the position in the blink cycle, toggling the blink rendition
// state on every second call, potentially triggering a redraw of the given
// renderer if there are blinking cells currently in view.
// Arguments:
// - renderer: the renderer that will be redrawn.
void RenderSettings::ToggleBlinkRendition(Renderer* renderer) noexcept
try
{ {
if (GetRenderMode(Mode::BlinkAllowed)) _blinkShouldBeFaint = !_blinkShouldBeFaint;
{
// 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 blink cycle that loops through four phases...
_blinkCycle = (_blinkCycle + 1) % 4;
// ... and two of those four render the blink attributes as faint.
_blinkShouldBeFaint = _blinkCycle >= 2;
// Every two cycles (when the state changes), we need to trigger a
// redraw, but only if there are actually blink attributes in use.
if (_blinkIsInUse && _blinkCycle % 2 == 0)
{
// We reset the _blinkIsInUse flag before redrawing, so we can
// get a fresh assessment of the current blink attribute usage.
_blinkIsInUse = false;
if (renderer)
{
renderer->TriggerRedrawAll();
}
}
}
} }
CATCH_LOG()

View File

@ -19,7 +19,6 @@
<ClCompile Include="..\RenderEngineBase.cpp" /> <ClCompile Include="..\RenderEngineBase.cpp" />
<ClCompile Include="..\RenderSettings.cpp" /> <ClCompile Include="..\RenderSettings.cpp" />
<ClCompile Include="..\renderer.cpp" /> <ClCompile Include="..\renderer.cpp" />
<ClCompile Include="..\thread.cpp" />
<ClCompile Include="..\precomp.cpp"> <ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader> <PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile> </ClCompile>
@ -39,7 +38,6 @@
<ClInclude Include="..\FontCache.h" /> <ClInclude Include="..\FontCache.h" />
<ClInclude Include="..\precomp.h" /> <ClInclude Include="..\precomp.h" />
<ClInclude Include="..\renderer.hpp" /> <ClInclude Include="..\renderer.hpp" />
<ClInclude Include="..\thread.hpp" />
</ItemGroup> </ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. --> <!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" /> <Import Project="$(SolutionDir)src\common.build.post.props" />

View File

@ -33,9 +33,6 @@
<ClCompile Include="..\renderer.cpp"> <ClCompile Include="..\renderer.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\thread.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\precomp.cpp"> <ClCompile Include="..\precomp.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
@ -56,9 +53,6 @@
<ClInclude Include="..\renderer.hpp"> <ClInclude Include="..\renderer.hpp">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\thread.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\FontInfo.hpp"> <ClInclude Include="..\..\inc\FontInfo.hpp">
<Filter>Header Files\inc</Filter> <Filter>Header Files\inc</Filter>
</ClInclude> </ClInclude>
@ -98,5 +92,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" /> <Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -4,20 +4,17 @@
#include "precomp.h" #include "precomp.h"
#include "renderer.hpp" #include "renderer.hpp"
#include <til/atomic.h>
using namespace Microsoft::Console::Render; using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Types;
using PointTree = interval_tree::IntervalTree<til::point, size_t>; using PointTree = interval_tree::IntervalTree<til::point, size_t>;
static constexpr auto maxRetriesForRenderEngine = 3; static constexpr TimerRepr TimerReprMax = std::numeric_limits<TimerRepr>::max();
static constexpr DWORD maxRetriesForRenderEngine = 3;
// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again. // The renderer will wait this number of milliseconds * how many tries have elapsed before trying again.
static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 }; static constexpr DWORD renderBackoffBaseTimeMilliseconds = 150;
#define FOREACH_ENGINE(var) \
for (auto var : _engines) \
if (!var) \
break; \
else
// Routine Description: // Routine Description:
// - Creates a new renderer controller for a console. // - Creates a new renderer controller for a console.
@ -29,6 +26,18 @@ Renderer::Renderer(RenderSettings& renderSettings, IRenderData* pData) :
_renderSettings(renderSettings), _renderSettings(renderSettings),
_pData(pData) _pData(pData)
{ {
_cursorBlinker = RegisterTimer("cursor blink", [](Renderer& renderer, TimerHandle) {
renderer._cursorBlinkerOn = !renderer._cursorBlinkerOn;
});
_renditionBlinker = RegisterTimer("blink rendition", [](Renderer& renderer, TimerHandle) {
renderer._renderSettings.ToggleBlinkRendition();
renderer.TriggerRedrawAll();
});
}
Renderer::~Renderer()
{
TriggerTeardown();
} }
IRenderData* Renderer::GetRenderData() const noexcept IRenderData* Renderer::GetRenderData() const noexcept
@ -36,6 +45,279 @@ IRenderData* Renderer::GetRenderData() const noexcept
return _pData; return _pData;
} }
// Routine Description:
// - Sets an event in the render thread that allows it to proceed, thus enabling painting.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::EnablePainting()
{
// When the renderer is constructed, the initial viewport won't be available yet,
// but once EnablePainting is called it should be safe to retrieve.
_viewport = _pData->GetViewport();
_enable.SetEvent();
if (const auto guard = _threadMutex.lock_exclusive(); !_thread)
{
_threadKeepRunning.store(true, std::memory_order_relaxed);
_thread.reset(CreateThread(nullptr, 0, s_renderThread, this, 0, nullptr));
THROW_LAST_ERROR_IF(!_thread);
// SetThreadDescription only works on 1607 and higher. If we cannot find it,
// then it's no big deal. Just skip setting the description.
const auto func = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"kernel32.dll"), SetThreadDescription);
if (func)
{
LOG_IF_FAILED(func(_thread.get(), L"Rendering Output Thread"));
}
}
}
void Renderer::_disablePainting() noexcept
{
_enable.ResetEvent();
}
// Method Description:
// - Called when the host is about to die, to give the renderer one last chance
// to paint before the host exits.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerTeardown() noexcept
{
if (const auto guard = _threadMutex.lock_exclusive(); _thread)
{
// The render thread first waits for the event and then checks _threadKeepRunning. By doing it
// in reverse order here, we ensure that it's impossible for the render thread to miss this.
_threadKeepRunning.store(false, std::memory_order_relaxed);
NotifyPaintFrame();
_enable.SetEvent();
WaitForSingleObject(_thread.get(), INFINITE);
_thread.reset();
}
_disablePainting();
}
void Renderer::NotifyPaintFrame() noexcept
{
_redraw.store(true, std::memory_order_relaxed);
til::atomic_notify_one(_redraw);
}
DWORD WINAPI Renderer::s_renderThread(void* param) noexcept
{
return static_cast<Renderer*>(param)->_renderThread();
}
DWORD Renderer::_renderThread() noexcept
{
while (true)
{
_enable.wait();
_waitUntilCanRender();
_waitUntilTimerOrRedraw();
if (!_threadKeepRunning.load(std::memory_order_relaxed))
{
break;
}
LOG_IF_FAILED(PaintFrame());
}
return S_OK;
}
void Renderer::_waitUntilCanRender() noexcept
{
for (const auto pEngine : _engines)
{
pEngine->WaitUntilCanRender();
}
}
TimerHandle Renderer::RegisterTimer(const char* description, TimerCallback routine)
{
// If it doesn't crash now, it would crash later.
WI_ASSERT(routine != nullptr);
const auto id = _nextTimerId++;
_timers.push_back(TimerRoutine{
.description = description,
.interval = TimerReprMax,
.next = TimerReprMax,
.routine = std::move(routine),
});
return TimerHandle{ id };
}
bool Renderer::IsTimerRunning(TimerHandle handle) const
{
const auto& timer = _timers.at(handle.id);
return timer.next != TimerReprMax;
}
TimerDuration Renderer::GetTimerInterval(TimerHandle handle) const
{
const auto& timer = _timers.at(handle.id);
return TimerDuration{ timer.interval };
}
void Renderer::StarTimer(TimerHandle handle, TimerDuration delay)
{
_starTimer(handle, delay.count(), TimerReprMax);
}
void Renderer::StartRepeatingTimer(TimerHandle handle, TimerDuration interval)
{
_starTimer(handle, interval.count(), interval.count());
}
void Renderer::_starTimer(TimerHandle handle, TimerRepr delay, TimerRepr interval)
{
// Nothing breaks if these assertions are violated, but you should still violate them.
// A timer with a 1-hour delay is weird and indicative of a bug. It should have been
// a max-wait (TimerReprMax) instead, which turns into an INFINITE timeout
// for WaitOnAddress(), which in turn is less costly than one with timeout.
#ifndef NDEBUG
constexpr TimerRepr one_min_in_100ns = 60 * 1000 * 10000;
assert(delay > 0 && (delay < one_min_in_100ns || delay == TimerReprMax));
assert(interval > 0 && (interval < one_min_in_100ns || interval == TimerReprMax));
#endif
auto& timer = _timers.at(handle.id);
timer.interval = interval;
timer.next = _timerSaturatingAdd(_timerInstant(), delay);
// Tickle _waitUntilCanRender() into calling _calculateTimerMaxWait() again.
// WaitOnAddress() will return with TRUE, even if the atomic didn't change.
til::atomic_notify_one(_redraw);
}
void Renderer::StopTimer(TimerHandle handle)
{
auto& timer = _timers.at(handle.id);
timer.interval = TimerReprMax;
timer.next = TimerReprMax;
}
DWORD Renderer::_calculateTimerMaxWait() noexcept
{
if (_timers.empty())
{
return INFINITE;
}
const auto now = _timerInstant();
auto wait = TimerReprMax;
for (const auto& timer : _timers)
{
wait = std::min(wait, _timerSaturatingSub(timer.next, now));
}
return _timerToMillis(wait);
}
void Renderer::_waitUntilTimerOrRedraw() noexcept
{
// Did we get an explicit rendering request? Yes? Exit.
//
// We don't reset _redraw just yet because we can delay that until we
// actually acquired the console lock. That's the main synchronization
// point and the instant we know everyone else is blocked. See PaintFrame().
while (!_redraw.load(std::memory_order_relaxed))
{
// Otherwise calculate when the next timer expires.
const auto wait = _calculateTimerMaxWait();
if (wait == 0)
{
break;
}
// and wait until the timer expires, or we potentially got a rendering request.
constexpr auto bad = false;
if (!til::atomic_wait(_redraw, bad, wait))
{
// The timer expired.
assert(GetLastError() == ERROR_TIMEOUT); // What else could it be?
break;
}
// If WaitOnAddress returned TRUE, we got signaled and retry.
}
}
void Renderer::_tickTimers() noexcept
{
const auto now = _timerInstant();
size_t id = 0;
for (auto& timer : _timers)
{
if (now >= timer.next)
{
// Prevent clock drift by incrementing the originally scheduled time.
timer.next = _timerSaturatingAdd(timer.next, timer.interval);
// ...but still take care to not schedule in the past.
if (timer.next <= now)
{
timer.next = now + timer.interval;
}
try
{
timer.routine(*this, TimerHandle{ id });
}
CATCH_LOG();
}
id++;
}
}
ULONGLONG Renderer::_timerInstant() noexcept
{
// QueryUnbiasedInterruptTime is what WaitOnAddress uses internally.
ULONGLONG now;
QueryUnbiasedInterruptTime(&now);
return now;
}
TimerRepr Renderer::_timerSaturatingAdd(TimerRepr a, TimerRepr b) noexcept
{
auto c = a + b;
if (c < a)
{
c = TimerReprMax;
}
return c;
}
TimerRepr Renderer::_timerSaturatingSub(TimerRepr a, TimerRepr b) noexcept
{
auto c = a - b;
if (c > a)
{
c = 0;
}
return c;
}
DWORD Renderer::_timerToMillis(TimerRepr t) noexcept
{
return gsl::narrow_cast<DWORD>(std::min<TimerRepr>(t / 10000, DWORD_MAX));
}
// Routine Description: // Routine Description:
// - Walks through the console data structures to compose a new frame based on the data that has changed since last call and outputs it to the connected rendering engine. // - Walks through the console data structures to compose a new frame based on the data that has changed since last call and outputs it to the connected rendering engine.
// Arguments: // Arguments:
@ -61,7 +343,7 @@ IRenderData* Renderer::GetRenderData() const noexcept
if (--tries == 0) if (--tries == 0)
{ {
// Stop trying. // Stop trying.
_thread.DisablePainting(); _disablePainting();
if (_pfnRendererEnteredErrorState) if (_pfnRendererEnteredErrorState)
{ {
_pfnRendererEnteredErrorState(); _pfnRendererEnteredErrorState();
@ -93,25 +375,36 @@ IRenderData* Renderer::GetRenderData() const noexcept
_synchronizeWithOutput(); _synchronizeWithOutput();
} }
// Last chance check if anything scrolled without an explicit invalidate notification since the last frame. _tickTimers();
// We reset _redraw after _tickTimers() so that NotifyPaintFrame() calls
// are picked up and ignored. We're about to render a frame after all.
// We do it before the remaining code below so that if we do have an
// intentional call to NotifyPaintFrame(), it triggers a redraw.
_redraw.store(false, std::memory_order_relaxed);
// NOTE: _CheckViewportAndScroll() updates _viewport which is used by all other functions.
_CheckViewportAndScroll(); _CheckViewportAndScroll();
_invalidateCurrentCursor(); // Invalidate the previous cursor position. _scheduleRenditionBlink();
// Add the previous cursor / composition to the dirty rect.
_invalidateCurrentCursor();
_invalidateOldComposition(); _invalidateOldComposition();
// Add the new cursor position to the dirt rect.
// Prepare the composition for insertion into the output screen.
_updateCursorInfo(); _updateCursorInfo();
_compositionCache.reset(); _invalidateCurrentCursor(); // NOTE: This now refers to the updated cursor position.
_invalidateCurrentCursor(); // Invalidate the new cursor position.
_prepareNewComposition(); _prepareNewComposition();
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
RETURN_IF_FAILED(_PaintFrameForEngine(pEngine)); RETURN_IF_FAILED(_PaintFrameForEngine(pEngine));
} }
} }
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
RETURN_IF_FAILED(pEngine->Present()); RETURN_IF_FAILED(pEngine->Present());
} }
@ -180,12 +473,6 @@ try
} }
CATCH_RETURN() CATCH_RETURN()
void Renderer::NotifyPaintFrame() noexcept
{
// The thread will provide throttling for us.
_thread.NotifyPaint();
}
// NOTE: You must be holding the console lock when calling this function. // NOTE: You must be holding the console lock when calling this function.
void Renderer::SynchronizedOutputChanged() noexcept void Renderer::SynchronizedOutputChanged() noexcept
{ {
@ -257,6 +544,28 @@ void Renderer::_synchronizeWithOutput() noexcept
_renderSettings.SetRenderMode(RenderSettings::Mode::SynchronizedOutput, false); _renderSettings.SetRenderMode(RenderSettings::Mode::SynchronizedOutput, false);
} }
void Renderer::AllowCursorVisibility(InhibitionSource source, bool enable) noexcept
{
const auto before = _cursorVisibilityInhibitors.any();
_cursorVisibilityInhibitors.set(source, !enable);
const auto after = _cursorVisibilityInhibitors.any();
if (before != after)
{
NotifyPaintFrame();
}
}
void Renderer::AllowCursorBlinking(InhibitionSource source, bool enable) noexcept
{
const auto before = _cursorBlinkingInhibitors.any();
_cursorBlinkingInhibitors.set(source, !enable);
const auto after = _cursorBlinkingInhibitors.any();
if (before != after)
{
NotifyPaintFrame();
}
}
// Routine Description: // Routine Description:
// - Called when the system has requested we redraw a portion of the console. // - Called when the system has requested we redraw a portion of the console.
// Arguments: // Arguments:
@ -265,7 +574,7 @@ void Renderer::_synchronizeWithOutput() noexcept
// - <none> // - <none>
void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient) void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient)
{ {
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->InvalidateSystem(prcDirtyClient)); LOG_IF_FAILED(pEngine->InvalidateSystem(prcDirtyClient));
} }
@ -299,7 +608,7 @@ void Renderer::TriggerRedraw(const Viewport& region)
if (view.TrimToViewport(&srUpdateRegion)) if (view.TrimToViewport(&srUpdateRegion))
{ {
view.ConvertToOrigin(&srUpdateRegion); view.ConvertToOrigin(&srUpdateRegion);
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->Invalidate(&srUpdateRegion)); LOG_IF_FAILED(pEngine->Invalidate(&srUpdateRegion));
} }
@ -329,7 +638,7 @@ void Renderer::TriggerRedraw(const til::point* const pcoord)
// - <none> // - <none>
void Renderer::TriggerRedrawAll(const bool backgroundChanged, const bool frameChanged) void Renderer::TriggerRedrawAll(const bool backgroundChanged, const bool frameChanged)
{ {
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->InvalidateAll()); LOG_IF_FAILED(pEngine->InvalidateAll());
} }
@ -347,19 +656,6 @@ void Renderer::TriggerRedrawAll(const bool backgroundChanged, const bool frameCh
} }
} }
// Method Description:
// - Called when the host is about to die, to give the renderer one last chance
// to paint before the host exits.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::TriggerTeardown() noexcept
{
// We need to shut down the paint thread on teardown.
_thread.TriggerTeardown();
}
// Routine Description: // Routine Description:
// - Called when the selected area in the console has changed. // - Called when the selected area in the console has changed.
// Arguments: // Arguments:
@ -394,7 +690,7 @@ try
} }
} }
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->InvalidateSelection(_lastSelectionRectsByViewport)); LOG_IF_FAILED(pEngine->InvalidateSelection(_lastSelectionRectsByViewport));
LOG_IF_FAILED(pEngine->InvalidateSelection(newSelectionViewportRects)); LOG_IF_FAILED(pEngine->InvalidateSelection(newSelectionViewportRects));
@ -423,7 +719,7 @@ try
const auto& buffer = _pData->GetTextBuffer(); const auto& buffer = _pData->GetTextBuffer();
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->InvalidateHighlight(oldHighlights, buffer)); LOG_IF_FAILED(pEngine->InvalidateHighlight(oldHighlights, buffer));
LOG_IF_FAILED(pEngine->InvalidateHighlight(newHighlights, buffer)); LOG_IF_FAILED(pEngine->InvalidateHighlight(newHighlights, buffer));
@ -456,7 +752,7 @@ bool Renderer::_CheckViewportAndScroll()
coordDelta.x = srOldViewport.left - srNewViewport.left; coordDelta.x = srOldViewport.left - srNewViewport.left;
coordDelta.y = srOldViewport.top - srNewViewport.top; coordDelta.y = srOldViewport.top - srNewViewport.top;
FOREACH_ENGINE(engine) for (const auto engine : _engines)
{ {
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport)); LOG_IF_FAILED(engine->UpdateViewport(srNewViewport));
LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta)); LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta));
@ -485,6 +781,38 @@ bool Renderer::_CheckViewportAndScroll()
return true; return true;
} }
void Renderer::_scheduleRenditionBlink()
{
const auto& buffer = _pData->GetTextBuffer();
bool blinkUsed = false;
for (auto row = _viewport.Top(); row < _viewport.BottomExclusive(); ++row)
{
const auto& r = buffer.GetRowByOffset(row);
for (const auto& attr : r.Attributes())
{
if (attr.IsBlinking())
{
blinkUsed = true;
goto why_does_cpp_not_have_labeled_loops;
}
}
}
why_does_cpp_not_have_labeled_loops:
if (blinkUsed != IsTimerRunning(_renditionBlinker))
{
if (blinkUsed)
{
StartRepeatingTimer(_renditionBlinker, std::chrono::seconds(1));
}
else
{
StopTimer(_renditionBlinker);
}
}
}
// Routine Description: // Routine Description:
// - Called when a scroll operation has occurred by manipulating the viewport. // - Called when a scroll operation has occurred by manipulating the viewport.
// - This is a special case as calling out scrolls explicitly drastically improves performance. // - This is a special case as calling out scrolls explicitly drastically improves performance.
@ -511,7 +839,7 @@ void Renderer::TriggerScroll()
// - <none> // - <none>
void Renderer::TriggerScroll(const til::point* const pcoordDelta) void Renderer::TriggerScroll(const til::point* const pcoordDelta)
{ {
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->InvalidateScroll(pcoordDelta)); LOG_IF_FAILED(pEngine->InvalidateScroll(pcoordDelta));
} }
@ -531,7 +859,7 @@ void Renderer::TriggerScroll(const til::point* const pcoordDelta)
void Renderer::TriggerTitleChange() void Renderer::TriggerTitleChange()
{ {
const auto newTitle = _pData->GetConsoleTitle(); const auto newTitle = _pData->GetConsoleTitle();
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->InvalidateTitle(newTitle)); LOG_IF_FAILED(pEngine->InvalidateTitle(newTitle));
} }
@ -540,7 +868,7 @@ void Renderer::TriggerTitleChange()
void Renderer::TriggerNewTextNotification(const std::wstring_view newText) void Renderer::TriggerNewTextNotification(const std::wstring_view newText)
{ {
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->NotifyNewText(newText)); LOG_IF_FAILED(pEngine->NotifyNewText(newText));
} }
@ -568,7 +896,7 @@ HRESULT Renderer::_PaintTitle(IRenderEngine* const pEngine)
// - <none> // - <none>
void Renderer::TriggerFontChange(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) void Renderer::TriggerFontChange(const int iDpi, const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo)
{ {
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->UpdateDpi(iDpi)); LOG_IF_FAILED(pEngine->UpdateDpi(iDpi));
LOG_IF_FAILED(pEngine->UpdateFont(FontInfoDesired, FontInfo)); LOG_IF_FAILED(pEngine->UpdateFont(FontInfoDesired, FontInfo));
@ -594,7 +922,7 @@ void Renderer::UpdateSoftFont(const std::span<const uint16_t> bitPattern, const
const auto softFontCharCount = cellSize.height ? bitPattern.size() / cellSize.height : 0; const auto softFontCharCount = cellSize.height ? bitPattern.size() / cellSize.height : 0;
_lastSoftFontChar = _firstSoftFontChar + softFontCharCount - 1; _lastSoftFontChar = _firstSoftFontChar + softFontCharCount - 1;
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->UpdateSoftFont(bitPattern, cellSize, centeringHint)); LOG_IF_FAILED(pEngine->UpdateSoftFont(bitPattern, cellSize, centeringHint));
} }
@ -624,7 +952,7 @@ bool Renderer::s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSo
// renderer. We won't know which is which, so iterate over them. // renderer. We won't know which is which, so iterate over them.
// Only return the result of the successful one if it's not S_FALSE (which is the VT renderer) // Only return the result of the successful one if it's not S_FALSE (which is the VT renderer)
// TODO: 14560740 - The Window might be able to get at this info in a more sane manner // TODO: 14560740 - The Window might be able to get at this info in a more sane manner
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
const auto hr = LOG_IF_FAILED(pEngine->GetProposedFont(FontInfoDesired, FontInfo, iDpi)); const auto hr = LOG_IF_FAILED(pEngine->GetProposedFont(FontInfoDesired, FontInfo, iDpi));
// We're looking for specifically S_OK, S_FALSE is not good enough. // We're looking for specifically S_OK, S_FALSE is not good enough.
@ -655,7 +983,7 @@ bool Renderer::IsGlyphWideByFont(const std::wstring_view glyph)
// renderer. We won't know which is which, so iterate over them. // renderer. We won't know which is which, so iterate over them.
// Only return the result of the successful one if it's not S_FALSE (which is the VT renderer) // Only return the result of the successful one if it's not S_FALSE (which is the VT renderer)
// TODO: 14560740 - The Window might be able to get at this info in a more sane manner // TODO: 14560740 - The Window might be able to get at this info in a more sane manner
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
const auto hr = LOG_IF_FAILED(pEngine->IsGlyphWideByFont(glyph, &fIsFullWidth)); const auto hr = LOG_IF_FAILED(pEngine->IsGlyphWideByFont(glyph, &fIsFullWidth));
// We're looking for specifically S_OK, S_FALSE is not good enough. // We're looking for specifically S_OK, S_FALSE is not good enough.
@ -668,20 +996,6 @@ bool Renderer::IsGlyphWideByFont(const std::wstring_view glyph)
return fIsFullWidth; return fIsFullWidth;
} }
// Routine Description:
// - Sets an event in the render thread that allows it to proceed, thus enabling painting.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Renderer::EnablePainting()
{
// When the renderer is constructed, the initial viewport won't be available yet,
// but once EnablePainting is called it should be safe to retrieve.
_viewport = _pData->GetViewport();
_thread.EnablePainting();
}
// Routine Description: // Routine Description:
// - Paint helper to fill in the background color of the invalid area within the frame. // - Paint helper to fill in the background color of the invalid area within the frame.
// Arguments: // Arguments:
@ -706,7 +1020,6 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
// This is the subsection of the entire screen buffer that is currently being presented. // This is the subsection of the entire screen buffer that is currently being presented.
// It can move left/right or top/bottom depending on how the viewport is scrolled // It can move left/right or top/bottom depending on how the viewport is scrolled
// relative to the entire buffer. // relative to the entire buffer.
const auto view = _pData->GetViewport();
const auto compositionRow = _compositionCache ? _compositionCache->absoluteOrigin.y : -1; const auto compositionRow = _compositionCache ? _compositionCache->absoluteOrigin.y : -1;
const auto& activeComposition = _pData->GetActiveComposition(); const auto& activeComposition = _pData->GetActiveComposition();
@ -731,12 +1044,12 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
// Shift the origin of the dirty region to match the underlying buffer so we can // Shift the origin of the dirty region to match the underlying buffer so we can
// compare the two regions directly for intersection. // compare the two regions directly for intersection.
dirty = Viewport::Offset(dirty, view.Origin()); dirty = Viewport::Offset(dirty, _viewport.Origin());
// The intersection between what is dirty on the screen (in need of repaint) // The intersection between what is dirty on the screen (in need of repaint)
// and what is supposed to be visible on the screen (the viewport) is what // and what is supposed to be visible on the screen (the viewport) is what
// we need to walk through line-by-line and repaint onto the screen. // we need to walk through line-by-line and repaint onto the screen.
const auto redraw = Viewport::Intersect(dirty, view); const auto redraw = Viewport::Intersect(dirty, _viewport);
// Retrieve the text buffer so we can read information out of it. // Retrieve the text buffer so we can read information out of it.
auto& buffer = _pData->GetTextBuffer(); auto& buffer = _pData->GetTextBuffer();
@ -804,7 +1117,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
// For example, the screen might say we need to paint line 1 because it is dirty but the viewport // For example, the screen might say we need to paint line 1 because it is dirty but the viewport
// is actually looking at line 26 relative to the buffer. This means that we need line 27 out // is actually looking at line 26 relative to the buffer. This means that we need line 27 out
// of the backing buffer to fill in line 1 of the screen. // of the backing buffer to fill in line 1 of the screen.
const auto screenPosition = bufferLine.Origin() - til::point{ 0, view.Top() }; const auto screenPosition = bufferLine.Origin() - til::point{ 0, _viewport.Top() };
// Retrieve the cell information iterator limited to just this line we want to redraw. // Retrieve the cell information iterator limited to just this line we want to redraw.
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine); auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
@ -817,7 +1130,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
(bufferLine.RightExclusive() == buffer.GetSize().Width()); (bufferLine.RightExclusive() == buffer.GetSize().Width());
// Prepare the appropriate line transform for the current row and viewport offset. // Prepare the appropriate line transform for the current row and viewport offset.
LOG_IF_FAILED(pEngine->PrepareLineTransform(lineRendition, screenPosition.y, view.Left())); LOG_IF_FAILED(pEngine->PrepareLineTransform(lineRendition, screenPosition.y, _viewport.Left()));
// Ask the helper to paint through this specific line. // Ask the helper to paint through this specific line.
_PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped); _PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped);
@ -826,7 +1139,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
const auto imageSlice = buffer.GetRowByOffset(row).GetImageSlice(); const auto imageSlice = buffer.GetRowByOffset(row).GetImageSlice();
if (imageSlice) [[unlikely]] if (imageSlice) [[unlikely]]
{ {
LOG_IF_FAILED(pEngine->PaintImageSlice(*imageSlice, screenPosition.y, view.Left())); LOG_IF_FAILED(pEngine->PaintImageSlice(*imageSlice, screenPosition.y, _viewport.Left()));
} }
} }
} }
@ -1119,8 +1432,10 @@ bool Renderer::_isInHoveredInterval(const til::point coordTarget) const noexcept
// - nullopt if the cursor is off or out-of-frame; otherwise, a CursorOptions // - nullopt if the cursor is off or out-of-frame; otherwise, a CursorOptions
void Renderer::_updateCursorInfo() void Renderer::_updateCursorInfo()
{ {
// Get cursor position in buffer const auto& buffer = _pData->GetTextBuffer();
auto coordCursor = _pData->GetCursorPosition(); const auto& cursor = buffer.GetCursor();
const auto cursorPosition = cursor.GetPosition();
auto coordCursor = cursorPosition; // Later this will be viewport-relative
// GH#3166: Only draw the cursor if it's actually in the viewport. It // GH#3166: Only draw the cursor if it's actually in the viewport. It
// might be on the line that's in that partially visible row at the // might be on the line that's in that partially visible row at the
@ -1130,12 +1445,12 @@ void Renderer::_updateCursorInfo()
// The cursor is never rendered as double height, so we don't care about // The cursor is never rendered as double height, so we don't care about
// the exact line rendition - only whether it's double width or not. // the exact line rendition - only whether it's double width or not.
const auto doubleWidth = _pData->GetTextBuffer().IsDoubleWidthLine(coordCursor.y); const auto doubleWidth = buffer.IsDoubleWidthLine(coordCursor.y);
const auto lineRendition = doubleWidth ? LineRendition::DoubleWidth : LineRendition::SingleWidth; const auto lineRendition = doubleWidth ? LineRendition::DoubleWidth : LineRendition::SingleWidth;
// We need to convert the screen coordinates of the viewport to an // We need to convert the screen coordinates of the viewport to an
// equivalent range of buffer cells, taking line rendition into account. // equivalent range of buffer cells, taking line rendition into account.
const auto viewport = _pData->GetViewport().ToInclusive(); const auto viewport = _viewport.ToInclusive();
const auto view = ScreenToBufferLine(viewport, lineRendition); const auto view = ScreenToBufferLine(viewport, lineRendition);
// Note that we allow the X coordinate to be outside the left border by 1 position, // Note that we allow the X coordinate to be outside the left border by 1 position,
@ -1150,17 +1465,80 @@ void Renderer::_updateCursorInfo()
const auto cursorColor = _renderSettings.GetColorTableEntry(TextColor::CURSOR_COLOR); const auto cursorColor = _renderSettings.GetColorTableEntry(TextColor::CURSOR_COLOR);
const auto useColor = cursorColor != INVALID_COLOR; const auto useColor = cursorColor != INVALID_COLOR;
// Update inhibitors based on whatever the VT parser (= client app) wants.
AllowCursorVisibility(InhibitionSource::Client, cursor.IsVisible());
AllowCursorBlinking(InhibitionSource::Client, cursor.IsBlinking());
// If the buffer or cursor changed, turn the cursor on for the next cycle. This makes it
// so that rapidly typing/printing keeps the cursor on continuously, which looks nicer.
{
const auto cursorBufferMutationId = buffer.GetLastMutationId();
const auto cursorCursorMutationId = cursor.GetLastMutationId();
if (_cursorBufferMutationId != cursorBufferMutationId || _cursorCursorMutationId != cursorCursorMutationId)
{
_cursorBufferMutationId = cursorBufferMutationId;
_cursorCursorMutationId = cursorCursorMutationId;
_cursorBlinkerOn = true;
// We'll restart the timer below if there are no inhibitors.
StopTimer(_cursorBlinker);
}
}
if (_cursorVisibilityInhibitors.any() || _cursorBlinkingInhibitors.any())
{
StopTimer(_cursorBlinker);
}
else if (!IsTimerRunning(_cursorBlinker))
{
const auto actual = GetTimerInterval(_cursorBlinker);
const auto expected = _pData->GetBlinkInterval();
if (expected > TimerDuration::zero() && expected < TimerDuration::max())
{
if (expected != actual)
{
StartRepeatingTimer(_cursorBlinker, expected);
}
}
else
{
// If blinking is disabled due to the OS settings, then we force-enable it.
_cursorBlinkerOn = true;
}
}
// If blinking is disabled, the cursor is always on.
_cursorBlinkerOn |= _cursorBlinkingInhibitors.any();
auto cursorHeight = cursor.GetSize();
// Now adjust the height for the overwrite/insert mode. If we're in overwrite mode, IsDouble will be set.
// When IsDouble is set, we either need to double the height of the cursor, or if it's already too big,
// then we need to shrink it by half.
if (cursor.IsDouble())
{
if (cursorHeight > 50) // 50 because 50 percent is half of 100 percent which is the max size.
{
cursorHeight >>= 1;
}
else
{
cursorHeight <<= 1;
}
}
_currentCursorOptions.coordCursor = coordCursor; _currentCursorOptions.coordCursor = coordCursor;
_currentCursorOptions.viewportLeft = viewport.left; _currentCursorOptions.viewportLeft = viewport.left;
_currentCursorOptions.lineRendition = lineRendition; _currentCursorOptions.lineRendition = lineRendition;
_currentCursorOptions.ulCursorHeightPercent = _pData->GetCursorHeight(); _currentCursorOptions.ulCursorHeightPercent = cursorHeight;
_currentCursorOptions.cursorPixelWidth = _pData->GetCursorPixelWidth(); _currentCursorOptions.cursorPixelWidth = _pData->GetCursorPixelWidth();
_currentCursorOptions.fIsDoubleWidth = _pData->IsCursorDoubleWidth(); _currentCursorOptions.fIsDoubleWidth = buffer.GetRowByOffset(cursorPosition.y).DbcsAttrAt(cursorPosition.x) != DbcsAttribute::Single;
_currentCursorOptions.cursorType = _pData->GetCursorStyle(); _currentCursorOptions.cursorType = cursor.GetType();
_currentCursorOptions.fUseColor = useColor; _currentCursorOptions.fUseColor = useColor;
_currentCursorOptions.cursorColor = cursorColor; _currentCursorOptions.cursorColor = cursorColor;
_currentCursorOptions.isVisible = _pData->IsCursorVisible(); _currentCursorOptions.isVisible = !_cursorVisibilityInhibitors.any();
_currentCursorOptions.isOn = _currentCursorOptions.isVisible && _pData->IsCursorOn(); _currentCursorOptions.isOn = _currentCursorOptions.isVisible && _cursorBlinkerOn;
_currentCursorOptions.inViewport = xInRange && yInRange; _currentCursorOptions.inViewport = xInRange && yInRange;
} }
@ -1184,7 +1562,7 @@ void Renderer::_invalidateCurrentCursor() const
if (view.TrimToViewport(&rect)) if (view.TrimToViewport(&rect))
{ {
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->InvalidateCursor(&rect)); LOG_IF_FAILED(pEngine->InvalidateCursor(&rect));
} }
@ -1194,9 +1572,16 @@ void Renderer::_invalidateCurrentCursor() const
// If we had previously drawn a composition at the previous cursor position // If we had previously drawn a composition at the previous cursor position
// we need to invalidate the entire line because who knows what changed. // we need to invalidate the entire line because who knows what changed.
// (It's possible to figure that out, but not worth the effort right now.) // (It's possible to figure that out, but not worth the effort right now.)
void Renderer::_invalidateOldComposition() const void Renderer::_invalidateOldComposition()
{ {
if (!_compositionCache || !_currentCursorOptions.inViewport) if (!_compositionCache)
{
return;
}
_compositionCache.reset();
if (!_currentCursorOptions.inViewport)
{ {
return; return;
} }
@ -1208,7 +1593,7 @@ void Renderer::_invalidateOldComposition() const
til::rect rect{ 0, coord.y, til::CoordTypeMax, coord.y + 1 }; til::rect rect{ 0, coord.y, til::CoordTypeMax, coord.y + 1 };
if (view.TrimToViewport(&rect)) if (view.TrimToViewport(&rect))
{ {
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->Invalidate(&rect)); LOG_IF_FAILED(pEngine->Invalidate(&rect));
} }
@ -1224,20 +1609,19 @@ void Renderer::_prepareNewComposition()
return; return;
} }
const auto viewport = _pData->GetViewport(); auto& buffer = _pData->GetTextBuffer();
const auto coordCursor = _pData->GetCursorPosition(); const auto coordCursor = buffer.GetCursor().GetPosition();
til::rect line{ 0, coordCursor.y, til::CoordTypeMax, coordCursor.y + 1 }; til::rect line{ 0, coordCursor.y, til::CoordTypeMax, coordCursor.y + 1 };
if (viewport.TrimToViewport(&line)) if (_viewport.TrimToViewport(&line))
{ {
viewport.ConvertToOrigin(&line); _viewport.ConvertToOrigin(&line);
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
LOG_IF_FAILED(pEngine->Invalidate(&line)); LOG_IF_FAILED(pEngine->Invalidate(&line));
} }
auto& buffer = _pData->GetTextBuffer();
auto& scratch = buffer.GetScratchpadRow(); auto& scratch = buffer.GetScratchpadRow();
const auto& activeComposition = _pData->GetActiveComposition(); const auto& activeComposition = _pData->GetActiveComposition();
@ -1399,32 +1783,17 @@ void Renderer::_ScrollPreviousSelection(const til::point delta)
void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine) void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine)
{ {
THROW_HR_IF_NULL(E_INVALIDARG, pEngine); THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
_engines.push_back(pEngine);
for (auto& p : _engines)
{
if (!p)
{
p = pEngine;
_forceUpdateViewport = true; _forceUpdateViewport = true;
return;
}
}
THROW_HR_MSG(E_UNEXPECTED, "engines array is full");
} }
void Renderer::RemoveRenderEngine(_In_ IRenderEngine* const pEngine) void Renderer::RemoveRenderEngine(_In_ IRenderEngine* const pEngine)
{ {
THROW_HR_IF_NULL(E_INVALIDARG, pEngine); THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
for (auto& p : _engines) std::erase_if(_engines, [=](IRenderEngine* e) {
{ return pEngine == e;
if (p == pEngine) });
{
p = nullptr;
return;
}
}
} }
// Method Description: // Method Description:
@ -1464,7 +1833,7 @@ void Renderer::SetRendererEnteredErrorStateCallback(std::function<void()> pfn)
void Renderer::UpdateHyperlinkHoveredId(uint16_t id) noexcept void Renderer::UpdateHyperlinkHoveredId(uint16_t id) noexcept
{ {
_hyperlinkHoveredId = id; _hyperlinkHoveredId = id;
FOREACH_ENGINE(pEngine) for (const auto pEngine : _engines)
{ {
pEngine->UpdateHyperlinkHoveredId(id); pEngine->UpdateHyperlinkHoveredId(id);
} }
@ -1474,13 +1843,3 @@ void Renderer::UpdateLastHoveredInterval(const std::optional<PointTree::interval
{ {
_hoveredInterval = newInterval; _hoveredInterval = newInterval;
} }
// Method Description:
// - Blocks until the engines are able to render without blocking.
void Renderer::WaitUntilCanRender()
{
FOREACH_ENGINE(pEngine)
{
pEngine->WaitUntilCanRender();
}
}

View File

@ -1,41 +1,40 @@
/*++ // Copyright (c) Microsoft Corporation.
Copyright (c) Microsoft Corporation // Licensed under the MIT license.
Licensed under the MIT license.
Module Name:
- Renderer.hpp
Abstract:
- This is the definition of our renderer.
- It provides interfaces for the console application to notify when various portions of the console state have changed and need to be redrawn.
- It requires a data interface to fetch relevant console structures required for drawing and a drawing engine target for output.
Author(s):
- Michael Niksa (MiNiksa) 17-Nov-2015
--*/
#pragma once #pragma once
#include "../../buffer/out/textBuffer.hpp"
#include "../inc/IRenderEngine.hpp" #include "../inc/IRenderEngine.hpp"
#include "../inc/RenderSettings.hpp" #include "../inc/RenderSettings.hpp"
#include "thread.hpp"
#include "../../buffer/out/textBuffer.hpp"
namespace Microsoft::Console::Render namespace Microsoft::Console::Render
{ {
enum class InhibitionSource
{
Client, // E.g. VT sequences
Host, // E.g. because the window is out of focus
User, // The user turned it off
};
class Renderer class Renderer
{ {
public: public:
Renderer(RenderSettings& renderSettings, IRenderData* pData); Renderer(RenderSettings& renderSettings, IRenderData* pData);
~Renderer();
IRenderData* GetRenderData() const noexcept; IRenderData* GetRenderData() const noexcept;
[[nodiscard]] HRESULT PaintFrame(); TimerHandle RegisterTimer(const char* description, TimerCallback routine);
bool IsTimerRunning(TimerHandle handle) const;
TimerDuration GetTimerInterval(TimerHandle handle) const;
void StarTimer(TimerHandle handle, TimerDuration delay);
void StartRepeatingTimer(TimerHandle handle, TimerDuration interval);
void StopTimer(TimerHandle handle);
void NotifyPaintFrame() noexcept; void NotifyPaintFrame() noexcept;
void SynchronizedOutputChanged() noexcept; void SynchronizedOutputChanged() noexcept;
void AllowCursorVisibility(InhibitionSource source, bool enable) noexcept;
void AllowCursorBlinking(InhibitionSource source, bool enable) noexcept;
void TriggerSystemRedraw(const til::rect* const prcDirtyClient); void TriggerSystemRedraw(const til::rect* const prcDirtyClient);
void TriggerRedraw(const Microsoft::Console::Types::Viewport& region); void TriggerRedraw(const Microsoft::Console::Types::Viewport& region);
void TriggerRedraw(const til::point* const pcoord); void TriggerRedraw(const til::point* const pcoord);
@ -66,7 +65,6 @@ namespace Microsoft::Console::Render
bool IsGlyphWideByFont(const std::wstring_view glyph); bool IsGlyphWideByFont(const std::wstring_view glyph);
void EnablePainting(); void EnablePainting();
void WaitUntilCanRender();
void AddRenderEngine(_In_ IRenderEngine* const pEngine); void AddRenderEngine(_In_ IRenderEngine* const pEngine);
void RemoveRenderEngine(_In_ IRenderEngine* const pEngine); void RemoveRenderEngine(_In_ IRenderEngine* const pEngine);
@ -79,6 +77,14 @@ namespace Microsoft::Console::Render
void UpdateLastHoveredInterval(const std::optional<interval_tree::IntervalTree<til::point, size_t>::interval>& newInterval); void UpdateLastHoveredInterval(const std::optional<interval_tree::IntervalTree<til::point, size_t>::interval>& newInterval);
private: private:
struct TimerRoutine
{
const char* description = nullptr;
TimerRepr interval = 0; // Timers with a 0 interval are marked for deletion.
TimerRepr next = 0;
TimerCallback routine;
};
// Caches some essential information about the active composition. // Caches some essential information about the active composition.
// This allows us to properly invalidate it between frames, etc. // This allows us to properly invalidate it between frames, etc.
struct CompositionCache struct CompositionCache
@ -90,10 +96,29 @@ namespace Microsoft::Console::Render
static GridLineSet s_GetGridlines(const TextAttribute& textAttribute) noexcept; static GridLineSet s_GetGridlines(const TextAttribute& textAttribute) noexcept;
static bool s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar); static bool s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar);
// Base rendering loop
static DWORD WINAPI s_renderThread(void*) noexcept;
DWORD _renderThread() noexcept;
void _waitUntilCanRender() noexcept;
// Timer handling
void _starTimer(TimerHandle handle, TimerRepr delay, TimerRepr interval);
DWORD _calculateTimerMaxWait() noexcept;
void _waitUntilTimerOrRedraw() noexcept;
void _tickTimers() noexcept;
static TimerRepr _timerInstant() noexcept;
static TimerRepr _timerSaturatingAdd(TimerRepr a, TimerRepr b) noexcept;
static TimerRepr _timerSaturatingSub(TimerRepr a, TimerRepr b) noexcept;
static DWORD _timerToMillis(TimerRepr t) noexcept;
// Actual rendering
[[nodiscard]] HRESULT PaintFrame();
[[nodiscard]] HRESULT _PaintFrame() noexcept; [[nodiscard]] HRESULT _PaintFrame() noexcept;
[[nodiscard]] HRESULT _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept; [[nodiscard]] HRESULT _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept;
void _disablePainting() noexcept;
void _synchronizeWithOutput() noexcept; void _synchronizeWithOutput() noexcept;
bool _CheckViewportAndScroll(); bool _CheckViewportAndScroll();
void _scheduleRenditionBlink();
[[nodiscard]] HRESULT _PaintBackground(_In_ IRenderEngine* const pEngine); [[nodiscard]] HRESULT _PaintBackground(_In_ IRenderEngine* const pEngine);
void _PaintBufferOutput(_In_ IRenderEngine* const pEngine); void _PaintBufferOutput(_In_ IRenderEngine* const pEngine);
void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const til::point target, const bool lineWrapped); void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, TextBufferCellIterator it, const til::point target, const bool lineWrapped);
@ -108,19 +133,41 @@ namespace Microsoft::Console::Render
bool _isInHoveredInterval(til::point coordTarget) const noexcept; bool _isInHoveredInterval(til::point coordTarget) const noexcept;
void _updateCursorInfo(); void _updateCursorInfo();
void _invalidateCurrentCursor() const; void _invalidateCurrentCursor() const;
void _invalidateOldComposition() const; void _invalidateOldComposition();
void _prepareNewComposition(); void _prepareNewComposition();
[[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine); [[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine);
// Constructor parameters, weakly referenced
RenderSettings& _renderSettings; RenderSettings& _renderSettings;
std::array<IRenderEngine*, 2> _engines{};
IRenderData* _pData = nullptr; // Non-ownership pointer IRenderData* _pData = nullptr; // Non-ownership pointer
// Base render loop & timer management
wil::srwlock _threadMutex;
wil::unique_handle _thread;
wil::slim_event_manual_reset _enable;
std::atomic<bool> _redraw;
std::atomic<bool> _threadKeepRunning{ false };
til::small_vector<IRenderEngine*, 2> _engines;
til::small_vector<TimerRoutine, 4> _timers;
size_t _nextTimerId = 0;
static constexpr size_t _firstSoftFontChar = 0xEF20; static constexpr size_t _firstSoftFontChar = 0xEF20;
size_t _lastSoftFontChar = 0; size_t _lastSoftFontChar = 0;
uint16_t _hyperlinkHoveredId = 0; uint16_t _hyperlinkHoveredId = 0;
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _hoveredInterval; std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _hoveredInterval;
Microsoft::Console::Types::Viewport _viewport;
CursorOptions _currentCursorOptions{}; CursorOptions _currentCursorOptions{};
TimerHandle _cursorBlinker;
uint64_t _cursorBufferMutationId = 0;
uint64_t _cursorCursorMutationId = 0; // Stupid name, but it's _cursor related and stores the cursor mutation id.
til::enumset<InhibitionSource, uint8_t> _cursorVisibilityInhibitors;
til::enumset<InhibitionSource, uint8_t> _cursorBlinkingInhibitors;
bool _cursorBlinkerOn = false;
TimerHandle _renditionBlinker;
Microsoft::Console::Types::Viewport _viewport;
std::optional<CompositionCache> _compositionCache; std::optional<CompositionCache> _compositionCache;
std::vector<Cluster> _clusterBuffer; std::vector<Cluster> _clusterBuffer;
std::function<void()> _pfnBackgroundColorChanged; std::function<void()> _pfnBackgroundColorChanged;
@ -132,9 +179,5 @@ namespace Microsoft::Console::Render
til::point_span _lastSelectionPaintSpan{}; til::point_span _lastSelectionPaintSpan{};
size_t _lastSelectionPaintSize{}; size_t _lastSelectionPaintSize{};
std::vector<til::rect> _lastSelectionRectsByViewport{}; std::vector<til::rect> _lastSelectionRectsByViewport{};
// Ordered last, so that it gets destroyed first.
// This ensures that the render thread stops accessing us.
RenderThread _thread{ this };
}; };
} }

View File

@ -32,7 +32,6 @@ SOURCES = \
..\RenderEngineBase.cpp \ ..\RenderEngineBase.cpp \
..\RenderSettings.cpp \ ..\RenderSettings.cpp \
..\renderer.cpp \ ..\renderer.cpp \
..\thread.cpp \
INCLUDES = \ INCLUDES = \
$(INCLUDES); \ $(INCLUDES); \

View File

@ -1,106 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "thread.hpp"
#include "renderer.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Render;
RenderThread::RenderThread(Renderer* renderer) :
renderer(renderer)
{
}
RenderThread::~RenderThread()
{
TriggerTeardown();
}
DWORD WINAPI RenderThread::s_ThreadProc(_In_ LPVOID lpParameter)
{
const auto pContext = static_cast<RenderThread*>(lpParameter);
return pContext->_ThreadProc();
}
DWORD WINAPI RenderThread::_ThreadProc()
{
while (true)
{
_enable.wait();
// Between waiting on _hEvent and calling PaintFrame() there should be a minimal delay,
// so that a key press progresses to a drawing operation as quickly as possible.
// As such, we wait for the renderer to complete _before_ waiting on `_redraw`.
renderer->WaitUntilCanRender();
_redraw.wait();
if (!_keepRunning.load(std::memory_order_relaxed))
{
break;
}
LOG_IF_FAILED(renderer->PaintFrame());
}
return S_OK;
}
void RenderThread::NotifyPaint() noexcept
{
_redraw.SetEvent();
}
// Spawns a new rendering thread if none exists yet.
void RenderThread::EnablePainting() noexcept
{
const auto guard = _threadMutex.lock_exclusive();
_enable.SetEvent();
if (!_thread)
{
_keepRunning.store(true, std::memory_order_relaxed);
_thread.reset(CreateThread(nullptr, 0, s_ThreadProc, this, 0, nullptr));
THROW_LAST_ERROR_IF(!_thread);
// SetThreadDescription only works on 1607 and higher. If we cannot find it,
// then it's no big deal. Just skip setting the description.
const auto func = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"kernel32.dll"), SetThreadDescription);
if (func)
{
LOG_IF_FAILED(func(_thread.get(), L"Rendering Output Thread"));
}
}
}
// This function is meant to only be called by `Renderer`. You should use `TriggerTeardown()` instead,
// even if you plan to call `EnablePainting()` later, because that ensures proper synchronization.
void RenderThread::DisablePainting() noexcept
{
_enable.ResetEvent();
}
// Stops the rendering thread, and waits for it to finish.
void RenderThread::TriggerTeardown() noexcept
{
const auto guard = _threadMutex.lock_exclusive();
if (_thread)
{
// The render thread first waits for the event and then checks _keepRunning. By doing it
// in reverse order here, we ensure that it's impossible for the render thread to miss this.
_keepRunning.store(false, std::memory_order_relaxed);
_redraw.SetEvent();
_enable.SetEvent();
WaitForSingleObject(_thread.get(), INFINITE);
_thread.reset();
}
DisablePainting();
}

View File

@ -1,43 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- Thread.hpp
Abstract:
- This is the definition of our rendering thread designed to throttle and compartmentalize drawing operations.
Author(s):
- Michael Niksa (MiNiksa) Feb 2016
--*/
#pragma once
namespace Microsoft::Console::Render
{
class Renderer;
class RenderThread
{
public:
RenderThread(Renderer* renderer);
~RenderThread();
void NotifyPaint() noexcept;
void EnablePainting() noexcept;
void DisablePainting() noexcept;
void TriggerTeardown() noexcept;
private:
static DWORD WINAPI s_ThreadProc(_In_ LPVOID lpParameter);
DWORD WINAPI _ThreadProc();
Renderer* renderer;
wil::slim_event_manual_reset _enable;
wil::slim_event_auto_reset _redraw;
wil::srwlock _threadMutex;
wil::unique_handle _thread;
std::atomic<bool> _keepRunning{ false };
};
}

View File

@ -1,16 +1,5 @@
/*++ // Copyright (c) Microsoft Corporation.
Copyright (c) Microsoft Corporation // Licensed under the MIT license.
Licensed under the MIT license.
Module Name:
- IRenderData.hpp
Abstract:
- This serves as the interface defining all information needed to render to the screen.
Author(s):
- Michael Niksa (MiNiksa) 17-Nov-2015
--*/
#pragma once #pragma once
@ -23,6 +12,8 @@ class TextBuffer;
namespace Microsoft::Console::Render namespace Microsoft::Console::Render
{ {
class Renderer;
struct CompositionRange struct CompositionRange
{ {
size_t len; // The number of chars in Composition::text that this .attr applies to size_t len; // The number of chars in Composition::text that this .attr applies to
@ -36,6 +27,21 @@ namespace Microsoft::Console::Render
size_t cursorPos = 0; size_t cursorPos = 0;
}; };
// Technically this entire block of definitions is specific to the Renderer class,
// but defining it here allows us to use the TimerDuration definition for IRenderData.
struct TimerHandle
{
explicit operator bool() const noexcept
{
return id != SIZE_T_MAX;
}
size_t id = SIZE_T_MAX;
};
using TimerRepr = ULONGLONG;
using TimerDuration = std::chrono::duration<TimerRepr, std::ratio<1, 10000000>>;
using TimerCallback = std::function<void(Renderer&, TimerHandle)>;
class IRenderData class IRenderData
{ {
public: public:
@ -53,28 +59,23 @@ namespace Microsoft::Console::Render
virtual void UnlockConsole() noexcept = 0; virtual void UnlockConsole() noexcept = 0;
// This block used to be the original IRenderData. // This block used to be the original IRenderData.
virtual til::point GetCursorPosition() const noexcept = 0; virtual TimerDuration GetBlinkInterval() noexcept = 0; // Return ::zero() or ::max() for no blink.
virtual bool IsCursorVisible() const noexcept = 0;
virtual bool IsCursorOn() const noexcept = 0;
virtual ULONG GetCursorHeight() const noexcept = 0;
virtual CursorType GetCursorStyle() const noexcept = 0;
virtual ULONG GetCursorPixelWidth() const noexcept = 0; virtual ULONG GetCursorPixelWidth() const noexcept = 0;
virtual bool IsCursorDoubleWidth() const = 0; virtual bool IsGridLineDrawingAllowed() noexcept = 0;
virtual const bool IsGridLineDrawingAllowed() noexcept = 0; virtual std::wstring_view GetConsoleTitle() const noexcept = 0;
virtual const std::wstring_view GetConsoleTitle() const noexcept = 0; virtual std::wstring GetHyperlinkUri(uint16_t id) const = 0;
virtual const std::wstring GetHyperlinkUri(uint16_t id) const = 0; virtual std::wstring GetHyperlinkCustomId(uint16_t id) const = 0;
virtual const std::wstring GetHyperlinkCustomId(uint16_t id) const = 0; virtual std::vector<size_t> GetPatternId(const til::point location) const = 0;
virtual const std::vector<size_t> GetPatternId(const til::point location) const = 0;
// This block used to be IUiaData. // This block used to be IUiaData.
virtual std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept = 0; virtual std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept = 0;
virtual const bool IsSelectionActive() const = 0; virtual bool IsSelectionActive() const = 0;
virtual const bool IsBlockSelection() const = 0; virtual bool IsBlockSelection() const = 0;
virtual void ClearSelection() = 0; virtual void ClearSelection() = 0;
virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0; virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0;
virtual const til::point GetSelectionAnchor() const noexcept = 0; virtual til::point GetSelectionAnchor() const noexcept = 0;
virtual const til::point GetSelectionEnd() const noexcept = 0; virtual til::point GetSelectionEnd() const noexcept = 0;
virtual const bool IsUiaDataInitialized() const noexcept = 0; virtual bool IsUiaDataInitialized() const noexcept = 0;
// Ideally this would not be stored on an interface, however ideally IRenderData should not be an interface in the first place. // Ideally this would not be stored on an interface, however ideally IRenderData should not be an interface in the first place.
// This is because we should have only 1 way how to represent render data across the codebase anyway, and it should // This is because we should have only 1 way how to represent render data across the codebase anyway, and it should

View File

@ -20,7 +20,6 @@ namespace Microsoft::Console::Render
public: public:
enum class Mode : size_t enum class Mode : size_t
{ {
BlinkAllowed,
IndexedDistinguishableColors, IndexedDistinguishableColors,
AlwaysDistinguishableColors, AlwaysDistinguishableColors,
IntenseIsBold, IntenseIsBold,
@ -30,6 +29,7 @@ namespace Microsoft::Console::Render
}; };
RenderSettings() noexcept; RenderSettings() noexcept;
void SaveDefaultSettings() noexcept; void SaveDefaultSettings() noexcept;
void RestoreDefaultSettings() noexcept; void RestoreDefaultSettings() noexcept;
void SetRenderMode(const Mode mode, const bool enabled) noexcept; void SetRenderMode(const Mode mode, const bool enabled) noexcept;
@ -48,16 +48,14 @@ namespace Microsoft::Console::Render
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept; std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept;
std::pair<COLORREF, COLORREF> GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept; std::pair<COLORREF, COLORREF> GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept;
COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept; COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept;
void ToggleBlinkRendition(class Renderer* renderer) noexcept; void ToggleBlinkRendition() noexcept;
private: private:
til::enumset<Mode> _renderMode{ Mode::BlinkAllowed, Mode::IntenseIsBright }; til::enumset<Mode> _renderMode{ Mode::IntenseIsBright };
std::array<COLORREF, TextColor::TABLE_SIZE> _colorTable; std::array<COLORREF, TextColor::TABLE_SIZE> _colorTable;
std::array<size_t, static_cast<size_t>(ColorAlias::ENUM_COUNT)> _colorAliasIndices; std::array<size_t, static_cast<size_t>(ColorAlias::ENUM_COUNT)> _colorAliasIndices;
std::array<COLORREF, TextColor::TABLE_SIZE> _defaultColorTable; std::array<COLORREF, TextColor::TABLE_SIZE> _defaultColorTable;
std::array<size_t, static_cast<size_t>(ColorAlias::ENUM_COUNT)> _defaultColorAliasIndices; std::array<size_t, static_cast<size_t>(ColorAlias::ENUM_COUNT)> _defaultColorAliasIndices;
size_t _blinkCycle = 0;
mutable bool _blinkIsInUse = false;
bool _blinkShouldBeFaint = false; bool _blinkShouldBeFaint = false;
}; };
} }

View File

@ -110,9 +110,6 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
lineWidth = std::min(lineWidth, rightMargin + 1); lineWidth = std::min(lineWidth, rightMargin + 1);
} }
// Turn off the cursor until we're done, so it isn't refreshed unnecessarily.
cursor.SetIsOn(false);
RowWriteState state{ RowWriteState state{
.text = string, .text = string,
.columnLimit = lineWidth, .columnLimit = lineWidth,
@ -120,9 +117,8 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
while (!state.text.empty()) while (!state.text.empty())
{ {
if (cursor.IsDelayedEOLWrap() && wrapAtEOL) if (const auto delayedCursorPosition = cursor.GetDelayEOLWrap(); delayedCursorPosition && wrapAtEOL)
{ {
const auto delayedCursorPosition = cursor.GetDelayedAtPosition();
cursor.ResetDelayEOLWrap(); cursor.ResetDelayEOLWrap();
// Only act on a delayed EOL if we didn't move the cursor to a // Only act on a delayed EOL if we didn't move the cursor to a
// different position from where the EOL was marked. // different position from where the EOL was marked.
@ -192,8 +188,6 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
} }
} }
_ApplyCursorMovementFlags(cursor);
// Notify terminal and UIA of new text. // Notify terminal and UIA of new text.
// It's important to do this here instead of in TextBuffer, because here you // It's important to do this here instead of in TextBuffer, because here you
// have access to the entire line of text, whereas TextBuffer writes it one // have access to the entire line of text, whereas TextBuffer writes it one
@ -400,22 +394,6 @@ void AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
// Finally, attempt to set the adjusted cursor position back into the console. // Finally, attempt to set the adjusted cursor position back into the console.
cursor.SetPosition(page.Buffer().ClampPositionWithinLine({ col, row })); cursor.SetPosition(page.Buffer().ClampPositionWithinLine({ col, row }));
_ApplyCursorMovementFlags(cursor);
}
// Routine Description:
// - Helper method which applies a bunch of flags that are typically set whenever
// the cursor is moved. The IsOn flag is set to true, and the Delay flag to false,
// to force a blinking cursor to be visible, so the user can immediately see the
// new position.
// Arguments:
// - cursor - The cursor instance to be updated
// Return Value:
// - <none>
void AdaptDispatch::_ApplyCursorMovementFlags(Cursor& cursor) noexcept
{
cursor.SetDelay(false);
cursor.SetIsOn(true);
} }
// Routine Description: // Routine Description:
@ -494,7 +472,7 @@ void AdaptDispatch::CursorSaveState()
savedCursorState.Column = cursorPosition.x + 1; savedCursorState.Column = cursorPosition.x + 1;
savedCursorState.Row = cursorPosition.y + 1; savedCursorState.Row = cursorPosition.y + 1;
savedCursorState.Page = page.Number(); savedCursorState.Page = page.Number();
savedCursorState.IsDelayedEOLWrap = page.Cursor().IsDelayedEOLWrap(); savedCursorState.IsDelayedEOLWrap = page.Cursor().GetDelayEOLWrap().has_value();
savedCursorState.IsOriginModeRelative = _modes.test(Mode::Origin); savedCursorState.IsOriginModeRelative = _modes.test(Mode::Origin);
savedCursorState.Attributes = page.Attributes(); savedCursorState.Attributes = page.Attributes();
savedCursorState.TermOutput = _termOutput; savedCursorState.TermOutput = _termOutput;
@ -1825,7 +1803,7 @@ void AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
_terminalInput.SetInputMode(TerminalInput::Mode::AutoRepeat, enable); _terminalInput.SetInputMode(TerminalInput::Mode::AutoRepeat, enable);
break; break;
case DispatchTypes::ModeParams::ATT610_StartCursorBlink: case DispatchTypes::ModeParams::ATT610_StartCursorBlink:
_pages.ActivePage().Cursor().SetBlinkingAllowed(enable); _pages.ActivePage().Cursor().SetIsBlinking(enable);
break; break;
case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode: case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode:
_pages.ActivePage().Cursor().SetIsVisible(enable); _pages.ActivePage().Cursor().SetIsVisible(enable);
@ -1990,7 +1968,7 @@ void AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
state = mapTemp(_terminalInput.GetInputMode(TerminalInput::Mode::AutoRepeat)); state = mapTemp(_terminalInput.GetInputMode(TerminalInput::Mode::AutoRepeat));
break; break;
case DispatchTypes::ModeParams::ATT610_StartCursorBlink: case DispatchTypes::ModeParams::ATT610_StartCursorBlink:
state = mapTemp(_pages.ActivePage().Cursor().IsBlinkingAllowed()); state = mapTemp(_pages.ActivePage().Cursor().IsBlinking());
break; break;
case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode: case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode:
state = mapTemp(_pages.ActivePage().Cursor().IsVisible()); state = mapTemp(_pages.ActivePage().Cursor().IsVisible());
@ -2099,7 +2077,6 @@ void AdaptDispatch::_InsertDeleteLineHelper(const VTInt delta)
// The IL and DL controls are also expected to move the cursor to the left margin. // The IL and DL controls are also expected to move the cursor to the left margin.
cursor.SetXPosition(leftMargin); cursor.SetXPosition(leftMargin);
_ApplyCursorMovementFlags(cursor);
} }
} }
@ -2457,10 +2434,7 @@ bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const b
textBuffer.IncrementCircularBuffer(eraseAttributes); textBuffer.IncrementCircularBuffer(eraseAttributes);
_api.NotifyBufferRotation(1); _api.NotifyBufferRotation(1);
// We trigger a scroll rather than a redraw, since that's more efficient, // We trigger a scroll rather than a redraw, since that's more efficient.
// but we need to turn the cursor off before doing so; otherwise, a ghost
// cursor can be left behind in the previous position.
cursor.SetIsOn(false);
textBuffer.TriggerScroll({ 0, -1 }); textBuffer.TriggerScroll({ 0, -1 });
// And again, if the bottom margin didn't cover the full page, we // And again, if the bottom margin didn't cover the full page, we
@ -2472,7 +2446,6 @@ bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const b
} }
cursor.SetPosition(newPosition); cursor.SetPosition(newPosition);
_ApplyCursorMovementFlags(cursor);
return viewportMoved; return viewportMoved;
} }
@ -2524,7 +2497,6 @@ void AdaptDispatch::ReverseLineFeed()
{ {
// Otherwise we move the cursor up, but not past the top of the page. // Otherwise we move the cursor up, but not past the top of the page.
cursor.SetPosition(textBuffer.ClampPositionWithinLine({ cursorPosition.x, cursorPosition.y - 1 })); cursor.SetPosition(textBuffer.ClampPositionWithinLine({ cursorPosition.x, cursorPosition.y - 1 }));
_ApplyCursorMovementFlags(cursor);
} }
} }
@ -2550,7 +2522,6 @@ void AdaptDispatch::BackIndex()
else if (cursorPosition.x > 0) else if (cursorPosition.x > 0)
{ {
cursor.SetXPosition(cursorPosition.x - 1); cursor.SetXPosition(cursorPosition.x - 1);
_ApplyCursorMovementFlags(cursor);
} }
} }
@ -2576,7 +2547,6 @@ void AdaptDispatch::ForwardIndex()
else if (cursorPosition.x < page.Buffer().GetLineWidth(cursorPosition.y) - 1) else if (cursorPosition.x < page.Buffer().GetLineWidth(cursorPosition.y) - 1)
{ {
cursor.SetXPosition(cursorPosition.x + 1); cursor.SetXPosition(cursorPosition.x + 1);
_ApplyCursorMovementFlags(cursor);
} }
} }
@ -2639,9 +2609,8 @@ void AdaptDispatch::ForwardTab(const VTInt numTabs)
// approach (i.e. they don't reset). For us this is a bit messy, since all // approach (i.e. they don't reset). For us this is a bit messy, since all
// cursor movement resets the flag automatically, so we need to save the // cursor movement resets the flag automatically, so we need to save the
// original state here, and potentially reapply it after the move. // original state here, and potentially reapply it after the move.
const auto delayedWrapOriginallySet = cursor.IsDelayedEOLWrap(); const auto delayedWrapOriginallySet = cursor.GetDelayEOLWrap().has_value();
cursor.SetXPosition(column); cursor.SetXPosition(column);
_ApplyCursorMovementFlags(cursor);
if (delayedWrapOriginallySet) if (delayedWrapOriginallySet)
{ {
cursor.DelayEOLWrap(); cursor.DelayEOLWrap();
@ -2678,7 +2647,6 @@ void AdaptDispatch::BackwardsTab(const VTInt numTabs)
} }
cursor.SetXPosition(column); cursor.SetXPosition(column);
_ApplyCursorMovementFlags(cursor);
} }
//Routine Description: //Routine Description:
@ -3055,7 +3023,7 @@ void AdaptDispatch::HardReset()
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, false); _api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, false);
// Restore cursor blinking mode. // Restore cursor blinking mode.
_pages.ActivePage().Cursor().SetBlinkingAllowed(true); _pages.ActivePage().Cursor().SetIsBlinking(true);
// Delete all current tab stops and reapply // Delete all current tab stops and reapply
TabSet(DispatchTypes::TabSetType::SetEvery8Columns); TabSet(DispatchTypes::TabSetType::SetEvery8Columns);
@ -3240,7 +3208,7 @@ void AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
auto& cursor = _pages.ActivePage().Cursor(); auto& cursor = _pages.ActivePage().Cursor();
cursor.SetType(actualType); cursor.SetType(actualType);
cursor.SetBlinkingAllowed(fEnableBlinking); cursor.SetIsBlinking(fEnableBlinking);
} }
// Routine Description: // Routine Description:
@ -4310,7 +4278,7 @@ void AdaptDispatch::_ReportDECSLRMSetting()
void AdaptDispatch::_ReportDECSCUSRSetting() const void AdaptDispatch::_ReportDECSCUSRSetting() const
{ {
const auto& cursor = _pages.ActivePage().Cursor(); const auto& cursor = _pages.ActivePage().Cursor();
const auto blinking = cursor.IsBlinkingAllowed(); const auto blinking = cursor.IsBlinking();
// A valid response always starts with 1 $ r. This is followed by a // A valid response always starts with 1 $ r. This is followed by a
// number from 1 to 6 representing the cursor style. The ' q' indicates // number from 1 to 6 representing the cursor style. The ' q' indicates
// this is a DECSCUSR response. // this is a DECSCUSR response.
@ -4488,7 +4456,7 @@ void AdaptDispatch::_ReportCursorInformation()
flags += (_modes.test(Mode::Origin) ? 1 : 0); flags += (_modes.test(Mode::Origin) ? 1 : 0);
flags += (_termOutput.IsSingleShiftPending(2) ? 2 : 0); flags += (_termOutput.IsSingleShiftPending(2) ? 2 : 0);
flags += (_termOutput.IsSingleShiftPending(3) ? 4 : 0); flags += (_termOutput.IsSingleShiftPending(3) ? 4 : 0);
flags += (cursor.IsDelayedEOLWrap() ? 8 : 0); flags += (cursor.GetDelayEOLWrap().has_value() ? 8 : 0);
// Character set designations. // Character set designations.
const auto leftSetNumber = _termOutput.GetLeftSetNumber(); const auto leftSetNumber = _termOutput.GetLeftSetNumber();

View File

@ -241,7 +241,6 @@ namespace Microsoft::Console::VirtualTerminal
std::pair<int, int> _GetVerticalMargins(const Page& page, const bool absolute) noexcept; std::pair<int, int> _GetVerticalMargins(const Page& page, const bool absolute) noexcept;
std::pair<int, int> _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept; std::pair<int, int> _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept;
void _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins); void _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins);
void _ApplyCursorMovementFlags(Cursor& cursor) noexcept;
void _FillRect(const Page& page, const til::rect& fillRect, const std::wstring_view& fillChar, const TextAttribute& fillAttrs) const; void _FillRect(const Page& page, const til::rect& fillRect, const std::wstring_view& fillChar, const TextAttribute& fillAttrs) const;
void _SelectiveEraseRect(const Page& page, const til::rect& eraseRect); void _SelectiveEraseRect(const Page& page, const til::rect& eraseRect);
void _ChangeRectAttributes(const Page& page, const til::rect& changeRect, const ChangeOps& changeOps); void _ChangeRectAttributes(const Page& page, const til::rect& changeRect, const ChangeOps& changeOps);

View File

@ -1977,49 +1977,49 @@ public:
Log::Comment(L"Requesting DECSCUSR style (blinking block)."); Log::Comment(L"Requesting DECSCUSR style (blinking block).");
_testGetSet->PrepData(); _testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox); _testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox);
requestSetting(L" q"); requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r1 q\033\\"); _testGetSet->ValidateInputEvent(L"\033P1$r1 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (steady block)."); Log::Comment(L"Requesting DECSCUSR style (steady block).");
_testGetSet->PrepData(); _testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox); _testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox);
requestSetting(L" q"); requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r2 q\033\\"); _testGetSet->ValidateInputEvent(L"\033P1$r2 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (blinking underline)."); Log::Comment(L"Requesting DECSCUSR style (blinking underline).");
_testGetSet->PrepData(); _testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore); _testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore);
requestSetting(L" q"); requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r3 q\033\\"); _testGetSet->ValidateInputEvent(L"\033P1$r3 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (steady underline)."); Log::Comment(L"Requesting DECSCUSR style (steady underline).");
_testGetSet->PrepData(); _testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore); _testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore);
requestSetting(L" q"); requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r4 q\033\\"); _testGetSet->ValidateInputEvent(L"\033P1$r4 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (blinking bar)."); Log::Comment(L"Requesting DECSCUSR style (blinking bar).");
_testGetSet->PrepData(); _testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar); _testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar);
requestSetting(L" q"); requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r5 q\033\\"); _testGetSet->ValidateInputEvent(L"\033P1$r5 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (steady bar)."); Log::Comment(L"Requesting DECSCUSR style (steady bar).");
_testGetSet->PrepData(); _testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar); _testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar);
requestSetting(L" q"); requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r6 q\033\\"); _testGetSet->ValidateInputEvent(L"\033P1$r6 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (non-standard)."); Log::Comment(L"Requesting DECSCUSR style (non-standard).");
_testGetSet->PrepData(); _testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Legacy); _testGetSet->_textBuffer->GetCursor().SetType(CursorType::Legacy);
requestSetting(L" q"); requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r0 q\033\\"); _testGetSet->ValidateInputEvent(L"\033P1$r0 q\033\\");
@ -2814,12 +2814,12 @@ public:
VERIFY_IS_TRUE(_pDispatch->_modes.test(AdaptDispatch::Mode::Origin)); VERIFY_IS_TRUE(_pDispatch->_modes.test(AdaptDispatch::Mode::Origin));
VERIFY_IS_FALSE(termOutput.IsSingleShiftPending(2)); VERIFY_IS_FALSE(termOutput.IsSingleShiftPending(2));
VERIFY_IS_TRUE(termOutput.IsSingleShiftPending(3)); VERIFY_IS_TRUE(termOutput.IsSingleShiftPending(3));
VERIFY_IS_FALSE(textBuffer.GetCursor().IsDelayedEOLWrap()); VERIFY_IS_FALSE(textBuffer.GetCursor().GetDelayEOLWrap().has_value());
stateMachine.ProcessString(L"\033P1$t1;1;1;@;@;J;0;2;@;BBBB\033\\"); stateMachine.ProcessString(L"\033P1$t1;1;1;@;@;J;0;2;@;BBBB\033\\");
VERIFY_IS_FALSE(_pDispatch->_modes.test(AdaptDispatch::Mode::Origin)); VERIFY_IS_FALSE(_pDispatch->_modes.test(AdaptDispatch::Mode::Origin));
VERIFY_IS_TRUE(termOutput.IsSingleShiftPending(2)); VERIFY_IS_TRUE(termOutput.IsSingleShiftPending(2));
VERIFY_IS_FALSE(termOutput.IsSingleShiftPending(3)); VERIFY_IS_FALSE(termOutput.IsSingleShiftPending(3));
VERIFY_IS_TRUE(textBuffer.GetCursor().IsDelayedEOLWrap()); VERIFY_IS_TRUE(textBuffer.GetCursor().GetDelayEOLWrap().has_value());
Log::Comment(L"Restore charset configuration"); Log::Comment(L"Restore charset configuration");
stateMachine.ProcessString(L"\033P1$t1;1;1;@;@;@;3;1;H;ABCF\033\\"); stateMachine.ProcessString(L"\033P1$t1;1;1;@;@;@;3;1;H;ABCF\033\\");
@ -2895,15 +2895,15 @@ public:
// success cases // success cases
// set blinking mode = true // set blinking mode = true
Log::Comment(L"Test 1: enable blinking = true"); Log::Comment(L"Test 1: enable blinking = true");
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_pDispatch->SetMode(DispatchTypes::ATT610_StartCursorBlink); _pDispatch->SetMode(DispatchTypes::ATT610_StartCursorBlink);
VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCursor().IsBlinkingAllowed()); VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCursor().IsBlinking());
// set blinking mode = false // set blinking mode = false
Log::Comment(L"Test 2: enable blinking = false"); Log::Comment(L"Test 2: enable blinking = false");
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true); _testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_pDispatch->ResetMode(DispatchTypes::ATT610_StartCursorBlink); _pDispatch->ResetMode(DispatchTypes::ATT610_StartCursorBlink);
VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCursor().IsBlinkingAllowed()); VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCursor().IsBlinking());
} }
TEST_METHOD(ScrollMarginsTest) TEST_METHOD(ScrollMarginsTest)