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
uldash
uldb
ULONGLONG
ulwave
Unadvise
unattend

View File

@ -13,40 +13,28 @@
// - ulSize - The height of the cursor within this buffer
Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept :
_parentBuffer{ parentBuffer },
_fIsVisible(true),
_fIsOn(true),
_fIsDouble(false),
_fBlinkingAllowed(true),
_fDelay(false),
_fIsConversionArea(false),
_fDelayedEolWrap(false),
_fDeferCursorRedraw(false),
_fHaveDeferredCursorRedraw(false),
_ulSize(ulSize),
_cursorType(CursorType::Legacy)
_ulSize(ulSize)
{
}
Cursor::~Cursor() = default;
til::point Cursor::GetPosition() const noexcept
{
return _cPosition;
}
uint64_t Cursor::GetLastMutationId() const noexcept
{
return _mutationId;
}
bool Cursor::IsVisible() const noexcept
{
return _fIsVisible;
return _isVisible;
}
bool Cursor::IsOn() const noexcept
bool Cursor::IsBlinking() const noexcept
{
return _fIsOn;
}
bool Cursor::IsBlinkingAllowed() const noexcept
{
return _fBlinkingAllowed;
return _isBlinking;
}
bool Cursor::IsDouble() const noexcept
@ -54,173 +42,128 @@ bool Cursor::IsDouble() const noexcept
return _fIsDouble;
}
bool Cursor::IsConversionArea() const noexcept
{
return _fIsConversionArea;
}
bool Cursor::GetDelay() const noexcept
{
return _fDelay;
}
ULONG Cursor::GetSize() const noexcept
{
return _ulSize;
}
void Cursor::SetIsVisible(const bool fIsVisible) noexcept
void Cursor::SetIsVisible(bool enable) noexcept
{
_fIsVisible = fIsVisible;
_RedrawCursor();
if (_isVisible != enable)
{
_isVisible = enable;
_redrawIfVisible();
}
}
void Cursor::SetIsOn(const bool fIsOn) noexcept
void Cursor::SetIsBlinking(bool enable) noexcept
{
_fIsOn = fIsOn;
_RedrawCursorAlways();
}
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();
if (_isBlinking != enable)
{
_isBlinking = enable;
_redrawIfVisible();
}
}
void Cursor::SetIsDouble(const bool fIsDouble) noexcept
{
_fIsDouble = fIsDouble;
_RedrawCursor();
}
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;
if (_fIsDouble != fIsDouble)
{
_fIsDouble = fIsDouble;
_redrawIfVisible();
}
}
void Cursor::SetSize(const ULONG ulSize) noexcept
{
_ulSize = ulSize;
_RedrawCursor();
if (_ulSize != ulSize)
{
_ulSize = ulSize;
_redrawIfVisible();
}
}
void Cursor::SetStyle(const ULONG ulSize, const CursorType type) noexcept
{
_ulSize = ulSize;
_cursorType = type;
_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 (_ulSize != ulSize || _cursorType != type)
{
if (_fDeferCursorRedraw)
{
_fHaveDeferredCursorRedraw = true;
}
else
{
_RedrawCursorAlways();
}
_ulSize = ulSize;
_cursorType = type;
_redrawIfVisible();
}
}
// 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
{
_RedrawCursor();
_cPosition = cPosition;
_RedrawCursor();
// The VT code assumes that moving the cursor implicitly resets the delayed EOL wrap,
// so we call ResetDelayEOLWrap() independent of _cPosition != cPosition.
// You can see the effect of this with "`e[1;9999Ha`e[1;9999Hb", which should print just "b".
ResetDelayEOLWrap();
if (_cPosition != cPosition)
{
_cPosition = cPosition;
_redrawIfVisible();
}
}
void Cursor::SetXPosition(const til::CoordType NewX) noexcept
{
_RedrawCursor();
_cPosition.x = NewX;
_RedrawCursor();
ResetDelayEOLWrap();
if (_cPosition.x != NewX)
{
_cPosition.x = NewX;
_redrawIfVisible();
}
}
void Cursor::SetYPosition(const til::CoordType NewY) noexcept
{
_RedrawCursor();
_cPosition.y = NewY;
_RedrawCursor();
ResetDelayEOLWrap();
if (_cPosition.y != NewY)
{
_cPosition.y = NewY;
_redrawIfVisible();
}
}
void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
{
_RedrawCursor();
_cPosition.x += DeltaX;
_RedrawCursor();
ResetDelayEOLWrap();
if (DeltaX != 0)
{
_cPosition.x = _cPosition.x + DeltaX;
_redrawIfVisible();
}
}
void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
{
_RedrawCursor();
_cPosition.y += DeltaY;
_RedrawCursor();
ResetDelayEOLWrap();
if (DeltaY != 0)
{
_cPosition.y = _cPosition.y + DeltaY;
_redrawIfVisible();
}
}
void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
{
_RedrawCursor();
_cPosition.x -= DeltaX;
_RedrawCursor();
ResetDelayEOLWrap();
if (DeltaX != 0)
{
_cPosition.x = _cPosition.x - DeltaX;
_redrawIfVisible();
}
}
void Cursor::DecrementYPosition(const til::CoordType DeltaY) noexcept
{
_RedrawCursor();
_cPosition.y -= DeltaY;
_RedrawCursor();
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
// Return Value:
// - <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 = pOtherCursor->_cPosition;
_fIsVisible = OtherCursor._fIsVisible;
_fIsOn = OtherCursor._fIsOn;
_fIsDouble = OtherCursor._fIsDouble;
_fBlinkingAllowed = OtherCursor._fBlinkingAllowed;
_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;
_cPosition = other._cPosition;
_coordDelayedAt = other._coordDelayedAt;
_ulSize = other._ulSize;
_cursorType = other._cursorType;
_isVisible = other._isVisible;
_isBlinking = other._isBlinking;
_fIsDouble = other._fIsDouble;
}
void Cursor::DelayEOLWrap() noexcept
{
_coordDelayedAt = _cPosition;
_fDelayedEolWrap = true;
}
void Cursor::ResetDelayEOLWrap() noexcept
{
_coordDelayedAt = {};
_fDelayedEolWrap = false;
_coordDelayedAt.reset();
}
til::point Cursor::GetDelayedAtPosition() const noexcept
const std::optional<til::point>& Cursor::GetDelayEOLWrap() const noexcept
{
return _coordDelayedAt;
}
bool Cursor::IsDelayedEOLWrap() 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
CursorType Cursor::GetType() const noexcept
{
return _cursorType;
}
@ -308,3 +211,18 @@ void Cursor::SetType(const CursorType type) noexcept
{
_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();
// No Copy. It will copy the timer handle. Bad news.
Cursor(const Cursor&) = delete;
Cursor& operator=(const Cursor&) & = delete;
@ -38,27 +36,17 @@ public:
Cursor(Cursor&&) = default;
Cursor& operator=(Cursor&&) & = delete;
uint64_t GetLastMutationId() const noexcept;
bool IsVisible() const noexcept;
bool IsOn() const noexcept;
bool IsBlinkingAllowed() const noexcept;
bool IsBlinking() const noexcept;
bool IsDouble() const noexcept;
bool IsConversionArea() const noexcept;
bool GetDelay() const noexcept;
ULONG GetSize() const noexcept;
til::point GetPosition() const noexcept;
CursorType GetType() const noexcept;
const CursorType GetType() const 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 SetIsVisible(bool enable) noexcept;
void SetIsBlinking(bool enable) 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 SetStyle(const ULONG ulSize, const CursorType type) noexcept;
@ -70,41 +58,30 @@ public:
void DecrementXPosition(const til::CoordType DeltaX) noexcept;
void DecrementYPosition(const til::CoordType DeltaY) noexcept;
void CopyProperties(const Cursor& OtherCursor) noexcept;
void CopyProperties(const Cursor& other) noexcept;
void DelayEOLWrap() noexcept;
void ResetDelayEOLWrap() noexcept;
til::point GetDelayedAtPosition() const noexcept;
bool IsDelayedEOLWrap() const noexcept;
const std::optional<til::point>& GetDelayEOLWrap() const noexcept;
void SetType(const CursorType type) noexcept;
private:
void _redrawIfVisible() noexcept;
void _redraw() noexcept;
TextBuffer& _parentBuffer;
//TODO: separate the rendering and text placement
// 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).
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?
std::optional<til::point> _coordDelayedAt; // coordinate the EOL wrap was delayed at.
ULONG _ulSize;
void _RedrawCursor() noexcept;
void _RedrawCursorAlways() noexcept;
CursorType _cursorType;
CursorType _cursorType = CursorType::Legacy;
bool _isVisible = true;
bool _isBlinking = true;
bool _fIsDouble = false; // whether the cursor size should be doubled
};

View File

@ -1883,7 +1883,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (read < sizeof(buffer))
{
// 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");
}
@ -1938,31 +1938,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
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()
{
// 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();
}
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".
// 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
@ -2073,7 +2060,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
//
// 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.
const auto cursorPos{ _terminal->GetCursorPosition() };
// Does the current buffer line have a mark on it?
const auto& marks{ _terminal->GetMarkExtents() };
@ -2081,8 +2067,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
const auto& last{ marks.back() };
const auto [start, end] = last.GetExtent();
const auto bufferSize = _terminal->GetTextBuffer().GetSize();
auto lastNonSpace = _terminal->GetTextBuffer().GetLastNonSpaceCharacter();
const auto& buffer = _terminal->GetTextBuffer();
const auto cursorPos = buffer.GetCursor().GetPosition();
const auto bufferSize = buffer.GetSize();
auto lastNonSpace = buffer.GetLastNonSpaceCharacter();
bufferSize.IncrementInBounds(lastNonSpace, true);
// 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;
{
const auto lock = _terminal->LockForReading();
const auto lock = _terminal->LockForWriting();
_renderer->AllowCursorVisibility(Render::InhibitionSource::Host, focused || _forceCursorVisible);
out = _terminal->FocusChanged(focused);
}
if (out && !out->empty())

View File

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

View File

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

View File

@ -63,7 +63,7 @@ RECT HwndTerminal::TsfDataProvider::GetCursorPosition()
til::size fontSize;
{
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
}
POINT ptSuggestion = {
@ -706,15 +706,6 @@ void HwndTerminal::_ClearSelection()
_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)
try
{
@ -986,60 +977,52 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->Refresh(windowSize, &dimensions);
}
void _stdcall TerminalBlinkCursor(void* terminal)
try
void __stdcall TerminalSetFocused(void* terminal, bool focused)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_setFocused(focused);
}
void HwndTerminal::_setFocused(bool focused) noexcept
{
if (_focused == focused)
{
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)
TerminalInput::OutputType out;
{
return;
const auto lock = _terminal->LockForWriting();
_focused = focused;
if (focused)
{
if (!_tsfHandle)
{
_tsfHandle = Microsoft::Console::TSF::Handle::Create();
_tsfHandle.AssociateFocus(&_tsfDataProvider);
}
if (const auto uiaEngine = _uiaEngine.get())
{
LOG_IF_FAILED(uiaEngine->Enable());
}
}
else
{
if (const auto uiaEngine = _uiaEngine.get())
{
LOG_IF_FAILED(uiaEngine->Disable());
}
}
_renderer->AllowCursorVisibility(Microsoft::Console::Render::InhibitionSource::Host, focused);
out = _terminal->FocusChanged(focused);
}
const auto lock = publicTerminal->_terminal->LockForWriting();
publicTerminal->_terminal->SetCursorOn(visible);
}
CATCH_LOG()
void __stdcall TerminalSetFocus(void* terminal)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_focused = true;
if (auto uiaEngine = publicTerminal->_uiaEngine.get())
if (out)
{
LOG_IF_FAILED(uiaEngine->Enable());
}
publicTerminal->_FocusTSF();
}
void HwndTerminal::_FocusTSF() noexcept
{
if (!_tsfHandle)
{
_tsfHandle = Microsoft::Console::TSF::Handle::Create();
_tsfHandle.AssociateFocus(&_tsfDataProvider);
}
}
void __stdcall TerminalKillFocus(void* terminal)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_focused = false;
if (auto uiaEngine = publicTerminal->_uiaEngine.get())
{
LOG_IF_FAILED(uiaEngine->Disable());
_WriteTextToConnection(*out);
}
}

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) void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
__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) bool _stdcall TerminalIsSelectionActive(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 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 TerminalBlinkCursor(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
__declspec(dllexport) void _stdcall TerminalSetFocus(void* terminal);
__declspec(dllexport) void _stdcall TerminalKillFocus(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetFocused(void* terminal, bool focused);
};
struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
@ -91,9 +87,9 @@ private:
TsfDataProvider(HwndTerminal* t) :
_terminal(t) {}
virtual ~TsfDataProvider() = default;
STDMETHODIMP TsfDataProvider::QueryInterface(REFIID, void**) noexcept override;
ULONG STDMETHODCALLTYPE TsfDataProvider::AddRef() noexcept override;
ULONG STDMETHODCALLTYPE TsfDataProvider::Release() noexcept override;
STDMETHODIMP QueryInterface(REFIID, void**) noexcept override;
ULONG STDMETHODCALLTYPE AddRef() noexcept override;
ULONG STDMETHODCALLTYPE Release() noexcept override;
HWND GetHwnd() override;
RECT GetViewport() 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 void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
friend void _stdcall TerminalUserScroll(void* terminal, int viewTop);
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(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 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 TerminalBlinkCursor(void* terminal);
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
friend void _stdcall TerminalSetFocus(void* terminal);
friend void _stdcall TerminalKillFocus(void* terminal);
friend void _stdcall TerminalSetFocused(void* terminal, bool focused);
void _UpdateFont(int newDpi);
void _WriteTextToConnection(const std::wstring_view text) noexcept;
@ -149,7 +141,7 @@ private:
HRESULT _CopyToSystemClipboard(wil::zstring_view stringToCopy, LPCWSTR lpszFormat) const;
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;
HRESULT _StartSelection(LPARAM lParam) noexcept;

View File

@ -1401,51 +1401,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
ScrollBar().ViewportSize(bufferHeight);
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
_UpdateAppearanceFromUIThread(_core.FocusedAppearance());
@ -1938,14 +1893,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
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;
}
@ -2403,17 +2350,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
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
// unfocused config does not exist then we never would have switched
@ -2450,17 +2386,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_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
// upon losing focus
if (_core.HasUnfocusedAppearance())
@ -2528,34 +2453,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_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:
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
// Arguments:
@ -2724,8 +2621,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// while the thread is supposed to be idle. Stop these timers avoids this.
_autoScrollTimer.Stop();
_bellLightTimer.Stop();
_cursorTimer.Stop();
_blinkTimer.Stop();
// 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.
@ -4168,44 +4063,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_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
{
return _cursorVisibility;
}
void TermControl::CursorVisibility(Control::CursorDisplayState 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.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);
_core.ForceCursorVisible(cursorVisibility == CursorDisplayState::Shown);
}
}
}

View File

@ -313,9 +313,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellDarkAnimation{ nullptr };
SafeDispatcherTimer _bellLightTimer;
SafeDispatcherTimer _cursorTimer;
SafeDispatcherTimer _blinkTimer;
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
winrt::hstring _restorePath;
bool _showMarksInScrollbar{ false };
@ -387,8 +384,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
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 _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 _SelectOutputHandler(const IInspectable& sender, const IInspectable& args);
bool _displayCursorWhileBlurred() const noexcept;
struct Revokers
{

View File

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

View File

@ -784,6 +784,19 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s
// - none
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);
}
@ -1021,6 +1034,11 @@ int Terminal::ViewEndIndex() const noexcept
return _inAltBuffer() ? _altBufferSize.height - 1 : _mutableViewport.BottomInclusive();
}
bool Terminal::IsFocused() const noexcept
{
return _focused;
}
RenderSettings& Terminal::GetRenderSettings() noexcept
{
_assertLocked();
@ -1182,31 +1200,6 @@ void Terminal::SetPlayMidiNoteCallback(std::function<void(const int, const int,
_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:
// - Update our internal knowledge about where regex patterns are on the screen
// - 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
til::point Terminal::GetViewportRelativeCursorPosition() const noexcept
{
const auto absoluteCursorPosition{ GetCursorPosition() };
const auto absoluteCursorPosition{ _activeBuffer().GetCursor().GetPosition() };
const auto mutableViewport{ _GetMutableViewport() };
const auto relativeCursorPos = absoluteCursorPosition - mutableViewport.Origin();
return { relativeCursorPos.x, relativeCursorPos.y + _scrollOffset };

View File

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

View File

@ -249,7 +249,7 @@ void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs)
auto& tgtCursor = _altBuffer->GetCursor();
tgtCursor.SetStyle(myCursor.GetSize(), myCursor.GetType());
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.
auto tgtCursorPos = myCursor.GetPosition();
@ -307,7 +307,7 @@ void Terminal::UseMainScreenBuffer()
mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType());
mainCursor.SetIsVisible(altCursor.IsVisible());
mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed());
mainCursor.SetIsBlinking(altCursor.IsBlinking());
auto tgtCursorPos = altCursor.GetPosition();
tgtCursorPos.y += _mutableViewport.Top();
@ -359,7 +359,7 @@ void Terminal::SearchMissingCommand(const std::wstring_view command)
{
if (_pfnSearchMissingCommand)
{
const auto bufferRow = GetCursorPosition().y;
const auto bufferRow = _activeBuffer().GetCursor().GetPosition().y;
_pfnSearchMissingCommand(command, bufferRow);
}
}

View File

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

View File

@ -3,7 +3,6 @@
#include "pch.h"
#include "Terminal.hpp"
#include <DefaultSettings.h>
using namespace Microsoft::Terminal::Core;
using namespace Microsoft::Console::Types;
@ -39,22 +38,17 @@ void Terminal::SetFontInfo(const FontInfo& fontInfo)
_fontInfo = fontInfo;
}
til::point Terminal::GetCursorPosition() const noexcept
TimerDuration Terminal::GetBlinkInterval() noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return cursor.GetPosition();
}
bool Terminal::IsCursorVisible() const noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return cursor.IsVisible();
}
bool Terminal::IsCursorOn() const noexcept
{
const auto& cursor = _activeBuffer().GetCursor();
return cursor.IsOn();
if (!_cursorBlinkInterval)
{
const auto enabled = GetSystemMetrics(SM_CARETBLINKINGENABLED);
const auto interval = GetCaretBlinkTime();
// >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();
}
return *_cursorBlinkInterval;
}
ULONG Terminal::GetCursorPixelWidth() const noexcept
@ -62,34 +56,17 @@ ULONG Terminal::GetCursorPixelWidth() const noexcept
return 1;
}
ULONG Terminal::GetCursorHeight() const 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
bool Terminal::IsGridLineDrawingAllowed() noexcept
{
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);
}
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);
}
@ -100,7 +77,7 @@ const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uin
// - The location
// Return value:
// - 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();
@ -218,7 +195,7 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo
_activeBuffer().TriggerSelection();
}
const std::wstring_view Terminal::GetConsoleTitle() const noexcept
std::wstring_view Terminal::GetConsoleTitle() const noexcept
{
_assertLocked();
if (_title.has_value())
@ -246,7 +223,7 @@ void Terminal::UnlockConsole() noexcept
_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
// 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.IsEnabled(false);
_previewControl.AllowFocusWhenDisabled(false);
_previewControl.CursorVisibility(Microsoft::Terminal::Control::CursorDisplayState::Shown);
ControlPreview().Child(_previewControl);
}

View File

@ -26,14 +26,12 @@ namespace TerminalCoreUnitTests
TEST_METHOD(SetColorTableEntry);
TEST_METHOD(CursorVisibility);
TEST_METHOD(CursorVisibilityViaStateMachine);
// Terminal::_WriteBuffer used to enter infinite loops under certain conditions.
// This test ensures that Terminal::_WriteBuffer doesn't get stuck when
// PrintString() is called with more code units than the buffer width.
TEST_METHOD(PrintStringOfSurrogatePairs);
TEST_METHOD(CheckDoubleWidthCursor);
TEST_METHOD(AddHyperlink);
TEST_METHOD(AddHyperlinkCustomId);
@ -131,39 +129,6 @@ void TerminalApiTest::PrintStringOfSurrogatePairs()
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()
{
// 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();
stateMachine.ProcessString(L"Hello World");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
cursor.SetIsOn(false);
stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25h");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12;25l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsBlinking());
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()
{
// 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,
}
public enum VirtualKey : ushort
{
/// <summary>
/// ALT key
/// </summary>
VK_MENU = 0x12,
}
[Flags]
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)]
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)]
[return: MarshalAs(UnmanagedType.LPWStr)]
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)]
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)]
public static extern void TerminalSetFocus(IntPtr terminal);
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)]
public static extern void TerminalKillFocus(IntPtr terminal);
public static extern void TerminalSetFocused(IntPtr terminal, bool focused);
[DllImport("user32.dll", SetLastError = true)]
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)]
public struct WINDOWPOS
{

View File

@ -24,7 +24,6 @@ namespace Microsoft.Terminal.Wpf
private ITerminalConnection connection;
private IntPtr hwnd;
private IntPtr terminal;
private DispatcherTimer blinkTimer;
private NativeMethods.ScrollCallback scrollCallback;
private NativeMethods.WriteCallback writeCallback;
@ -36,23 +35,6 @@ namespace Microsoft.Terminal.Wpf
this.MessageHook += this.TerminalContainer_MessageHook;
this.GotFocus += this.TerminalContainer_GotFocus;
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>
@ -314,15 +296,6 @@ namespace Microsoft.Terminal.Wpf
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);
}
@ -360,13 +333,10 @@ namespace Microsoft.Terminal.Wpf
switch ((NativeMethods.WindowMessage)msg)
{
case NativeMethods.WindowMessage.WM_SETFOCUS:
NativeMethods.TerminalSetFocus(this.terminal);
this.blinkTimer?.Start();
NativeMethods.TerminalSetFocused(this.terminal, true);
break;
case NativeMethods.WindowMessage.WM_KILLFOCUS:
NativeMethods.TerminalKillFocus(this.terminal);
this.blinkTimer?.Stop();
NativeMethods.TerminalSetCursorVisible(this.terminal, false);
NativeMethods.TerminalSetFocused(this.terminal, false);
break;
case NativeMethods.WindowMessage.WM_MOUSEACTIVATE:
this.Focus();
@ -375,12 +345,8 @@ namespace Microsoft.Terminal.Wpf
case NativeMethods.WindowMessage.WM_SYSKEYDOWN: // fallthrough
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);
NativeMethods.TerminalSendKeyEvent(this.terminal, vkey, scanCode, flags, true);
this.blinkTimer?.Start();
break;
}

View File

@ -4,6 +4,7 @@
#include "precomp.h"
#include "AccessibilityNotifier.h"
#include "../types/inc/convert.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
using namespace Microsoft::Console;
@ -70,17 +71,19 @@ void AccessibilityNotifier::Initialize(HWND hwnd, DWORD msaaDelay, DWORD uiaDela
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.
// It would trigger unnecessary work.
//
// NOTE: We check this before the assert() below so that unit tests don't trigger the assert.
if (!_uiaEnabled)
{
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.
if (provider)
{
@ -90,7 +93,10 @@ void AccessibilityNotifier::SetUIAProvider(IRawElementProviderSimple* provider)
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.
WaitForThreadpoolTimerCallbacks(_timer.get(), TRUE);
if (_timer)
{
WaitForThreadpoolTimerCallbacks(_timer.get(), TRUE);
}
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.
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),
// because we need to emit a ConsoleControl() call regardless.
if (_msaaEnabled)
if (_msaaEnabled || uiaEnabled)
{
const auto guard = _lock.lock_exclusive();
_state.eventConsoleCaretPositionX = position.x;
_state.eventConsoleCaretPositionY = position.y;
_state.eventConsoleCaretSelecting = activeSelection;
_state.eventConsoleCaretPrimed = true;
if (_msaaEnabled)
{
_state.eventConsoleCaretPositionX = position.x;
_state.eventConsoleCaretPositionY = position.y;
_state.eventConsoleCaretSelecting = activeSelection;
_state.eventConsoleCaretPrimed = true;
}
if (uiaEnabled)
{
_state.textSelectionChanged = true;
}
_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.
// `end` is expected to be an inclusive coordinate.
void AccessibilityNotifier::RegionChanged(til::point begin, til::point end) noexcept
{
if (begin >= end)
if (begin > end)
{
return;
}
@ -306,7 +330,7 @@ void AccessibilityNotifier::_timerSet() noexcept
{
if (!_delay)
{
_emitMSAA(_state);
_emitEvents(_state);
}
else if (!_state.timerScheduled)
{
@ -341,40 +365,79 @@ void NTAPI AccessibilityNotifier::_timerEmitMSAA(PTP_CALLBACK_INSTANCE, PVOID co
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 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;
if (state.eventConsoleCaretPrimed)
// 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)
{
const auto x = castSaturated<SHORT>(state.eventConsoleCaretPositionX);
const auto y = castSaturated<SHORT>(state.eventConsoleCaretPositionY);
// 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;
// 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));
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
if (state.eventConsoleUpdateRegionPrimed)
{
std::optional<CONSOLE_CARET_INFO> caretInfo;
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)
{
const auto caretX = castSaturated<SHORT>(state.eventConsoleCaretPositionX);
const auto caretY = castSaturated<SHORT>(state.eventConsoleCaretPositionY);
caretPosition = MAKELONG(caretX, caretY);
// Convert the buffer position to the equivalent screen coordinates
// required by CONSOLE_CARET_INFO, taking line rendition into account.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
if (gci.HasActiveOutputBuffer())
{
auto& screenInfo = gci.GetActiveOutputBuffer();
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 fontSize = screenInfo.GetScreenFontSize();
const auto left = (position.x - viewport.Left()) * fontSize.width;
@ -389,86 +452,75 @@ void AccessibilityNotifier::_emitMSAA(State& state) const noexcept
},
});
}
gci.UnlockConsole();
if (caretInfo)
{
cc->Control(ControlType::ConsoleSetCaretInfo, &*caretInfo, sizeof(*caretInfo));
}
}
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)
{
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.eventConsoleCaretPositionY = 0;
state.eventConsoleCaretSelecting = false;
state.eventConsoleCaretPrimed = false;
}
if (state.eventConsoleUpdateRegionPrimed)
if (state.textSelectionChanged)
{
const auto begX = castSaturated<SHORT>(state.eventConsoleUpdateRegionBeginX);
const auto begY = castSaturated<SHORT>(state.eventConsoleUpdateRegionBeginY);
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;
_emitUIAEvent(provider, UIA_Text_TextSelectionChangedEventId);
state.textSelectionChanged = false;
}
if (state.eventConsoleUpdateScrollPrimed)
@ -488,18 +540,6 @@ void AccessibilityNotifier::_emitMSAA(State& state) const noexcept
cc->NotifyWinEvent(EVENT_CONSOLE_LAYOUT, _hwnd, 0, 0);
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

View File

@ -26,6 +26,7 @@ namespace Microsoft::Console
void CursorChanged(til::point position, bool activeSelection) noexcept;
void SelectionChanged() noexcept;
bool WantsRegionChangedEvents() const noexcept;
void RegionChanged(til::point begin, til::point end) noexcept;
void ScrollBuffer(til::CoordType delta) noexcept;
void ScrollViewport(til::point delta) noexcept;
@ -70,7 +71,7 @@ namespace Microsoft::Console
PTP_TIMER _createTimer(PTP_TIMER_CALLBACK callback) noexcept;
void _timerSet() 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;
// 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);
}
if (result.cellsModified > 0)
{
// Notify accessibility
auto endingCoordinate = startingCoordinate;
bufferSize.WalkInBounds(endingCoordinate, result.cellsModified);
if (result.cellsModified > 1)
{
bufferSize.WalkInBounds(endingCoordinate, result.cellsModified - 1);
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.RegionChanged(startingCoordinate, endingCoordinate);

View File

@ -17,7 +17,6 @@
#include "VtIo.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/GlyphWidth.hpp"
#include "../types/inc/Viewport.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
@ -34,29 +33,46 @@ constexpr bool controlCharPredicate(wchar_t wch)
static auto raiseAccessibilityEventsOnExit(SCREEN_INFORMATION& screenInfo)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& bufferBefore = gci.GetActiveOutputBuffer();
const auto cursorBefore = bufferBefore.GetTextBuffer().GetCursor().GetPosition();
const auto bufferBefore = &gci.GetActiveOutputBuffer();
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
// sequences can switch between the main and alternative screen buffer.
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& bufferAfter = gci.GetActiveOutputBuffer();
const auto cursorAfter = bufferAfter.GetTextBuffer().GetCursor().GetPosition();
const auto& bufferAfter = &gci.GetActiveOutputBuffer();
const auto cursorAfter = bufferAfter->GetTextBuffer().GetCursor().GetPosition();
if (&bufferBefore == &bufferAfter)
{
an.RegionChanged(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);
}
});
// Don't raise any events for inactive buffers.
if (&bufferBefore != &screenInfo)
if (bufferBefore != &screenInfo)
{
raise.release();
}
@ -131,7 +147,7 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point
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.
@ -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.
// 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).
if (cursor.IsDelayedEOLWrap() && wrapAtEOL)
if (const auto delayed = cursor.GetDelayEOLWrap(); delayed && wrapAtEOL)
{
auto pos = cursor.GetPosition();
const auto delayed = cursor.GetDelayedAtPosition();
cursor.ResetDelayEOLWrap();
if (delayed == pos)
{

View File

@ -355,17 +355,6 @@ const std::wstring_view CONSOLE_INFORMATION::GetLinkTitle() const noexcept
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:
// - Returns the MIDI audio instance.
// Arguments:

View File

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

View File

@ -749,7 +749,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
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
// 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="..\cmdline.cpp" />
<ClCompile Include="..\ConsoleArguments.cpp" />
<ClCompile Include="..\CursorBlinker.cpp" />
<ClCompile Include="..\readDataCooked.cpp" />
<ClCompile Include="..\consoleInformation.cpp" />
<ClCompile Include="..\dbcs.cpp" />
@ -59,7 +58,6 @@
<ClInclude Include="..\ConsoleArguments.hpp" />
<ClInclude Include="..\conserv.h" />
<ClInclude Include="..\conwinuserrefs.h" />
<ClInclude Include="..\CursorBlinker.hpp" />
<ClInclude Include="..\dbcs.h" />
<ClInclude Include="..\directio.h" />
<ClInclude Include="..\getset.h" />

View File

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

View File

@ -435,21 +435,6 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
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
screenInfo.RefreshFontWithRenderer();

View File

@ -6,15 +6,20 @@
#include "renderData.hpp"
#include "dbcs.h"
#include "handle.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::Render;
using Microsoft::Console::Interactivity::ServiceLocator;
void RenderData::UpdateSystemMetrics()
{
_cursorBlinkInterval.reset();
}
// Routine Description:
// - Retrieves the viewport that applies over the data available in the GetTextBuffer() call
// Return Value:
@ -96,93 +101,17 @@ void RenderData::UnlockConsole() noexcept
gci.UnlockConsole();
}
// Method Description:
// - 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
TimerDuration RenderData::GetBlinkInterval() noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
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 (!_cursorBlinkInterval)
{
if (ulHeight > 50) // 50 because 50 percent is half of 100 percent which is the max size.
{
ulHeight >>= 1;
}
else
{
ulHeight <<= 1;
}
const auto enabled = ServiceLocator::LocateSystemConfigurationProvider()->IsCaretBlinkingEnabled();
const auto interval = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime();
// >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();
}
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();
return *_cursorBlinkInterval;
}
// Method Description:
@ -197,26 +126,13 @@ ULONG RenderData::GetCursorPixelWidth() const noexcept
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:
// - 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.
// Return Value:
// - If true, line drawing information retrieved from the text buffer can/should be displayed.
// - 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 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
// Return Value:
// - 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();
return gci.GetTitleAndPrefix();
@ -252,7 +168,7 @@ const std::wstring_view RenderData::GetConsoleTitle() const noexcept
// - The hyperlink ID
// Return Value:
// - 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();
return gci.GetActiveOutputBuffer().GetTextBuffer().GetHyperlinkUriFromId(id);
@ -264,14 +180,14 @@ const std::wstring RenderData::GetHyperlinkUri(uint16_t id) const
// - The hyperlink ID
// Return Value:
// - 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();
return gci.GetActiveOutputBuffer().GetTextBuffer().GetCustomIdFromId(id);
}
// 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 {};
}
@ -293,12 +209,12 @@ std::pair<COLORREF, COLORREF> RenderData::GetAttributeColors(const TextAttribute
// - <none>
// Return Value:
// - True if the selection variables contain valid selection data. False otherwise.
const bool RenderData::IsSelectionActive() const
bool RenderData::IsSelectionActive() const
{
return Selection::Instance().IsAreaSelected();
}
const bool RenderData::IsBlockSelection() const noexcept
bool RenderData::IsBlockSelection() const
{
return !Selection::Instance().IsLineSelection();
}
@ -338,7 +254,7 @@ const til::point_span* RenderData::GetSearchHighlightFocused() const noexcept
// - none
// Return Value:
// - current selection anchor
const til::point RenderData::GetSelectionAnchor() const noexcept
til::point RenderData::GetSelectionAnchor() const noexcept
{
return Selection::Instance().GetSelectionAnchor();
}
@ -349,7 +265,7 @@ const til::point RenderData::GetSelectionAnchor() const noexcept
// - none
// Return Value:
// - 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...
// - 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 };
}
bool RenderData::IsUiaDataInitialized() const noexcept
{
return true;
}

View File

@ -1,16 +1,5 @@
/*++
Copyright (c) Microsoft Corporation
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
--*/
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
@ -20,41 +9,43 @@ class RenderData final :
public Microsoft::Console::Render::IRenderData
{
public:
void UpdateSystemMetrics();
//
// BEGIN IRenderData
//
Microsoft::Console::Types::Viewport GetViewport() noexcept override;
til::point GetTextBufferEndPosition() const noexcept override;
TextBuffer& GetTextBuffer() 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;
void LockConsole() noexcept override;
void UnlockConsole() noexcept override;
til::point GetCursorPosition() const noexcept override;
bool IsCursorVisible() const noexcept override;
bool IsCursorOn() const noexcept override;
ULONG GetCursorHeight() const noexcept override;
CursorType GetCursorStyle() const noexcept override;
Microsoft::Console::Render::TimerDuration GetBlinkInterval() noexcept override;
ULONG GetCursorPixelWidth() const noexcept override;
bool IsCursorDoubleWidth() const override;
const bool IsGridLineDrawingAllowed() noexcept override;
const std::wstring_view GetConsoleTitle() const noexcept 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;
bool IsGridLineDrawingAllowed() noexcept override;
std::wstring_view GetConsoleTitle() const noexcept override;
std::wstring GetHyperlinkUri(uint16_t id) const override;
std::wstring GetHyperlinkCustomId(uint16_t id) const override;
std::vector<size_t> GetPatternId(const til::point location) const override;
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override;
const bool IsSelectionActive() const override;
const bool IsBlockSelection() const noexcept override;
bool IsSelectionActive() const override;
bool IsBlockSelection() const override;
void ClearSelection() override;
void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override;
std::span<const til::point_span> GetSearchHighlights() const noexcept override;
const til::point_span* GetSearchHighlightFocused() const noexcept override;
const til::point GetSelectionAnchor() const noexcept override;
const til::point GetSelectionEnd() const noexcept override;
const bool IsUiaDataInitialized() const noexcept override { return true; }
til::point GetSelectionAnchor() const noexcept override;
til::point GetSelectionEnd() const noexcept override;
bool IsUiaDataInitialized() const noexcept override;
//
// 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
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());
// 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)
try
{
_textBuffer->GetCursor().StartDeferDrawing();
auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); });
_textBuffer->ResizeTraditional(coordNewScreenSize);
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
// Return Value:
// - 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();
//
@ -1581,20 +1572,6 @@ void SCREEN_INFORMATION::SetCursorDBMode(const bool DoubleCursor)
_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;
}
@ -1746,7 +1723,7 @@ const SCREEN_INFORMATION* SCREEN_INFORMATION::GetAltBuffer() const noexcept
auto& altCursor = createdBuffer->GetTextBuffer().GetCursor();
altCursor.SetStyle(myCursor.GetSize(), myCursor.GetType());
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.
auto altCursorPos = myCursor.GetPosition();
altCursorPos.y -= GetVirtualViewport().Top();
@ -1895,7 +1872,7 @@ void SCREEN_INFORMATION::UseMainScreenBuffer()
auto& mainCursor = psiMain->GetTextBuffer().GetCursor();
mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType());
mainCursor.SetIsVisible(altCursor.IsVisible());
mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed());
mainCursor.SetIsBlinking(altCursor.IsBlinking());
// Copy the alt buffer's output mode back to the main buffer.
psiMain->OutputMode = psiAlt->OutputMode;
@ -2336,19 +2313,6 @@ Viewport SCREEN_INFORMATION::GetVtPageArea() const noexcept
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:
// - Gets the current font of the screen buffer.
// Arguments:

View File

@ -70,7 +70,7 @@ public:
const UINT uiCursorSize,
_Outptr_ SCREEN_INFORMATION** const ppScreen);
~SCREEN_INFORMATION();
~SCREEN_INFORMATION() override;
void GetScreenBufferInformation(_Out_ til::size* pcoordSize,
_Out_ til::point* pcoordCursorPosition,
@ -166,8 +166,6 @@ public:
InputBuffer* const GetActiveInputBuffer() const override;
#pragma endregion
bool CursorIsDoubleWidth() const;
DWORD OutputMode;
short WheelDelta;
@ -194,7 +192,7 @@ public:
void SetCursorType(const CursorType Type, const bool setMain = false) noexcept;
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);
void UseMainScreenBuffer();

View File

@ -177,9 +177,6 @@ void Selection::InitializeMouseSelection(const til::point coordBufferPos)
if (pWindow != nullptr)
{
pWindow->UpdateWindowText();
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.SelectionChanged();
}
// 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.
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(coordBufferPos, true);
an.SelectionChanged();
}
// Routine Description:
@ -313,9 +309,6 @@ void Selection::_ExtendSelection(Selection::SelectionData* d, _In_ til::point co
// - <none>
void Selection::_CancelMouseSelection()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& ScreenInfo = gci.GetActiveOutputBuffer();
// invert old select rect. if we're selecting by mouse, we
// always have a selection rect.
HideSelection();
@ -331,6 +324,8 @@ void Selection::_CancelMouseSelection()
// 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.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& ScreenInfo = gci.GetActiveOutputBuffer();
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(ScreenInfo.GetTextBuffer().GetCursor().GetPosition(), false);
}
@ -504,7 +499,7 @@ void Selection::InitializeMarkSelection()
screenInfo.SetCursorInformation(100, TRUE);
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
// 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.SetIsVisible(_d->fSavedCursorVisible);
cursor.SetType(_d->savedCursorType);
cursor.SetIsOn(true);
cursor.SetPosition(_d->coordSavedCursorPosition);
}

View File

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

View File

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

View File

@ -408,7 +408,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
mainCursor.SetPosition(mainCursorPos);
mainCursor.SetIsVisible(mainCursorVisible);
mainCursor.SetStyle(mainCursorSize, mainCursorType);
mainCursor.SetBlinkingAllowed(mainCursorBlinking);
mainCursor.SetIsBlinking(mainCursorBlinking);
Log::Comment(L"Switch to the alternate buffer.");
VERIFY_SUCCEEDED(mainBuffer.UseAlternateScreenBuffer({}));
@ -423,7 +423,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
Log::Comment(L"Confirm the cursor style is inherited from the main buffer.");
VERIFY_ARE_EQUAL(mainCursorSize, altCursor.GetSize());
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.");
auto altCursorPos = til::point{ 5, 3 };
@ -434,7 +434,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
altCursor.SetPosition(altCursorPos);
altCursor.SetIsVisible(altCursorVisible);
altCursor.SetStyle(altCursorSize, altCursorType);
altCursor.SetBlinkingAllowed(altCursorBlinking);
altCursor.SetIsBlinking(altCursorBlinking);
Log::Comment(L"Switch back to the main buffer.");
useMain.release();
@ -448,7 +448,7 @@ void ScreenBufferTests::AlternateBufferCursorInheritanceTest()
Log::Comment(L"Confirm the cursor style is inherited from the alt buffer.");
VERIFY_ARE_EQUAL(altCursorSize, mainCursor.GetSize());
VERIFY_ARE_EQUAL(altCursorType, mainCursor.GetType());
VERIFY_ARE_EQUAL(altCursorBlinking, mainCursor.IsBlinkingAllowed());
VERIFY_ARE_EQUAL(altCursorBlinking, mainCursor.IsBlinking());
}
void ScreenBufferTests::TestReverseLineFeed()
@ -3829,7 +3829,7 @@ void ScreenBufferTests::ScrollOperations()
}
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());
// 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.");
// 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.
stateMachine.ProcessString(L"\033[4h");
// 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.");
// 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.
stateMachine.ProcessString(L"\033[4l");
// Write out some new content.
@ -3999,7 +3999,7 @@ void ScreenBufferTests::InsertChars()
auto insertPos = til::CoordType{ 20 };
// 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.
const auto& cursor = si.GetTextBuffer().GetCursor();
@ -4067,7 +4067,7 @@ void ScreenBufferTests::InsertChars()
// Move cursor to right edge.
insertPos = horizontalMarginsActive ? viewportEnd - 1 : bufferWidth - 1;
VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }, true));
VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }));
expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green.
@ -4116,7 +4116,7 @@ void ScreenBufferTests::InsertChars()
// Move cursor to left edge.
insertPos = horizontalMarginsActive ? viewportStart : 0;
VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }, true));
VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }));
expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green.
@ -4199,7 +4199,7 @@ void ScreenBufferTests::DeleteChars()
auto deletePos = til::CoordType{ 20 };
// 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.
const auto& cursor = si.GetTextBuffer().GetCursor();
@ -4267,7 +4267,7 @@ void ScreenBufferTests::DeleteChars()
// Move cursor to right edge.
deletePos = horizontalMarginsActive ? viewportEnd - 1 : bufferWidth - 1;
VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }, true));
VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }));
expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green.
@ -4316,7 +4316,7 @@ void ScreenBufferTests::DeleteChars()
// Move cursor to left edge.
deletePos = horizontalMarginsActive ? viewportStart : 0;
VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }, true));
VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }));
expectedCursor = cursor.GetPosition();
// Fill the entire line with Qs. Blue on Green.
@ -4484,7 +4484,7 @@ void ScreenBufferTests::ScrollingWideCharsHorizontally()
_FillLine(testRow, testChars, testAttr);
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");
stateMachine.ProcessString(L"\033[@");
@ -4552,7 +4552,7 @@ void ScreenBufferTests::EraseScrollbackTests()
const auto cursorPos = til::point{ centerX, centerY };
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");
// 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;
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());
// Get cursor position and viewport range.
@ -5756,7 +5756,7 @@ void ScreenBufferTests::HardResetBuffer()
si.SetAttributes(TextAttribute());
si.ClearTextData();
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());
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(); });
// 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());
VerifyText(altBuffer.GetTextBuffer());
@ -6890,7 +6890,7 @@ void ScreenBufferTests::CursorSaveRestore()
stateMachine.ProcessString(restoreCursor);
// Verify initial position, delayed wrap, colors, and graphic character set.
VERIFY_ARE_EQUAL(til::point(20, 10), cursor.GetPosition());
VERIFY_IS_TRUE(cursor.IsDelayedEOLWrap());
VERIFY_IS_TRUE(cursor.GetDelayEOLWrap().has_value());
cursor.ResetDelayEOLWrap();
VERIFY_ARE_EQUAL(colorAttrs, si.GetAttributes());
stateMachine.ProcessString(asciiText);
@ -6905,7 +6905,7 @@ void ScreenBufferTests::CursorSaveRestore()
stateMachine.ProcessString(restoreCursor);
// Verify initial saved position, delayed wrap, colors, and graphic character set.
VERIFY_ARE_EQUAL(til::point(20, 10), cursor.GetPosition());
VERIFY_IS_TRUE(cursor.IsDelayedEOLWrap());
VERIFY_IS_TRUE(cursor.GetDelayEOLWrap().has_value());
cursor.ResetDelayEOLWrap();
VERIFY_ARE_EQUAL(colorAttrs, si.GetAttributes());
stateMachine.ProcessString(asciiText);
@ -6923,7 +6923,7 @@ void ScreenBufferTests::CursorSaveRestore()
stateMachine.ProcessString(restoreCursor);
// Verify home position, no delayed wrap, default attributes, and ascii character set.
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());
stateMachine.ProcessString(asciiText);
VERIFY_IS_TRUE(_ValidateLineContains(til::point(0, 0), asciiText, defaultAttrs));
@ -7023,7 +7023,7 @@ void ScreenBufferTests::ScreenAlignmentPattern()
// Place the cursor in the center.
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.");
stateMachine.ProcessString(L"\x1b#8");
@ -7059,44 +7059,35 @@ void ScreenBufferTests::TestCursorIsOn()
auto& cursor = tbi.GetCursor();
stateMachine.ProcessString(L"Hello World");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
cursor.SetIsOn(false);
stateMachine.ProcessString(L"\x1b[?12l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12h");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?25h");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
VERIFY_IS_TRUE(cursor.IsBlinking());
VERIFY_IS_TRUE(cursor.IsVisible());
stateMachine.ProcessString(L"\x1b[?12;25l");
VERIFY_IS_TRUE(cursor.IsOn());
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
VERIFY_IS_FALSE(cursor.IsBlinking());
VERIFY_IS_FALSE(cursor.IsVisible());
}
@ -8158,7 +8149,7 @@ void ScreenBufferTests::DelayedWrapReset()
stateMachine.ProcessCharacter(L'X');
{
auto& cursor = si.GetTextBuffer().GetCursor();
VERIFY_IS_TRUE(cursor.IsDelayedEOLWrap());
VERIFY_IS_TRUE(cursor.GetDelayEOLWrap().has_value());
VERIFY_ARE_EQUAL(startPos, cursor.GetPosition());
}
@ -8170,7 +8161,7 @@ void ScreenBufferTests::DelayedWrapReset()
{
auto& cursor = si.GetTextBuffer().GetCursor();
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);
}
}

View File

@ -366,23 +366,15 @@ void TextBufferTests::TestCopyProperties()
testTextBuffer->GetCursor().SetIsVisible(false);
otherTbi.GetCursor().SetIsVisible(true);
testTextBuffer->GetCursor().SetIsOn(false);
otherTbi.GetCursor().SetIsOn(true);
testTextBuffer->GetCursor().SetIsDouble(false);
otherTbi.GetCursor().SetIsDouble(true);
testTextBuffer->GetCursor().SetDelay(false);
otherTbi.GetCursor().SetDelay(true);
// run copy
testTextBuffer->CopyProperties(otherTbi);
// test that new now contains values from other
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsVisible());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsDouble());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().GetDelay());
}
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)

View File

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

View File

@ -41,13 +41,6 @@ namespace Microsoft::Console::Interactivity::OneCore
_Inout_opt_ IconInfo* iconInfo) override;
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;
};
}

View File

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

View File

@ -39,8 +39,5 @@ namespace Microsoft::Console::Interactivity::Win32
_In_ PCWSTR pwszCurrDir,
_In_ PCWSTR pwszAppName,
_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>();
auto& g = ServiceLocator::LocateGlobals();
auto& gci = g.getConsoleInformation();
Scrolling::s_UpdateSystemMetrics();
g.sVerticalScrollSize = dpiApi->GetSystemMetricsForDpi(SM_CXVSCROLL, g.dpi);
g.sHorizontalScrollSize = dpiApi->GetSystemMetricsForDpi(SM_CYHSCROLL, g.dpi);
gci.GetCursorBlinker().UpdateSystemMetrics();
const auto sysConfig = ServiceLocator::LocateSystemConfigurationProvider();
g.cursorPixelWidth = sysConfig->GetCursorWidth();

View File

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

View File

@ -4,7 +4,6 @@
```mermaid
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>"]
RenderEngineBase[/"RenderEngineBase<br>(base/RenderEngineBase.cpp)<br><small>abstracts 24 LOC 👻</small>"\]
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>"]
end
RenderThread --> Renderer
Renderer -->|owns| RenderThread
Renderer -.-> RenderEngineBase
%% Mermaid.js has no support for backwards arrow at the moment
RenderEngineBase <-.->|extends| GdiEngine

View File

@ -59,11 +59,6 @@ void RenderSettings::RestoreDefaultSettings() noexcept
void RenderSettings::SetRenderMode(const Mode mode, const bool enabled) noexcept
{
_renderMode.set(mode, enabled);
// If blinking is disabled, make sure blinking content is not faint.
if (mode == Mode::BlinkAllowed && !enabled)
{
_blinkShouldBeFaint = false;
}
}
// Routine Description:
@ -187,8 +182,6 @@ void RenderSettings::RestoreDefaultColorAliasIndex(const ColorAlias alias) noexc
// - The color values of the attribute's foreground and background.
std::pair<COLORREF, COLORREF> RenderSettings::GetAttributeColors(const TextAttribute& attr) const noexcept
{
_blinkIsInUse = _blinkIsInUse || attr.IsBlinking();
const auto fgTextColor = attr.GetForeground();
const auto bgTextColor = attr.GetBackground();
@ -296,35 +289,7 @@ COLORREF RenderSettings::GetAttributeUnderlineColor(const TextAttribute& attr) c
return ul;
}
// Routine Description:
// - 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
void RenderSettings::ToggleBlinkRendition() noexcept
{
if (GetRenderMode(Mode::BlinkAllowed))
{
// 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();
}
}
}
_blinkShouldBeFaint = !_blinkShouldBeFaint;
}
CATCH_LOG()

View File

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

View File

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

View File

@ -4,20 +4,17 @@
#include "precomp.h"
#include "renderer.hpp"
#include <til/atomic.h>
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
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.
static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
#define FOREACH_ENGINE(var) \
for (auto var : _engines) \
if (!var) \
break; \
else
static constexpr DWORD renderBackoffBaseTimeMilliseconds = 150;
// Routine Description:
// - Creates a new renderer controller for a console.
@ -29,6 +26,18 @@ Renderer::Renderer(RenderSettings& renderSettings, IRenderData* pData) :
_renderSettings(renderSettings),
_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
@ -36,6 +45,279 @@ IRenderData* Renderer::GetRenderData() const noexcept
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:
// - 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:
@ -61,7 +343,7 @@ IRenderData* Renderer::GetRenderData() const noexcept
if (--tries == 0)
{
// Stop trying.
_thread.DisablePainting();
_disablePainting();
if (_pfnRendererEnteredErrorState)
{
_pfnRendererEnteredErrorState();
@ -93,25 +375,36 @@ IRenderData* Renderer::GetRenderData() const noexcept
_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();
_invalidateCurrentCursor(); // Invalidate the previous cursor position.
_scheduleRenditionBlink();
// Add the previous cursor / composition to the dirty rect.
_invalidateCurrentCursor();
_invalidateOldComposition();
// Add the new cursor position to the dirt rect.
// Prepare the composition for insertion into the output screen.
_updateCursorInfo();
_compositionCache.reset();
_invalidateCurrentCursor(); // Invalidate the new cursor position.
_invalidateCurrentCursor(); // NOTE: This now refers to the updated cursor position.
_prepareNewComposition();
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
RETURN_IF_FAILED(_PaintFrameForEngine(pEngine));
}
}
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
RETURN_IF_FAILED(pEngine->Present());
}
@ -180,12 +473,6 @@ try
}
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.
void Renderer::SynchronizedOutputChanged() noexcept
{
@ -257,6 +544,28 @@ void Renderer::_synchronizeWithOutput() noexcept
_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:
// - Called when the system has requested we redraw a portion of the console.
// Arguments:
@ -265,7 +574,7 @@ void Renderer::_synchronizeWithOutput() noexcept
// - <none>
void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient)
{
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
LOG_IF_FAILED(pEngine->InvalidateSystem(prcDirtyClient));
}
@ -299,7 +608,7 @@ void Renderer::TriggerRedraw(const Viewport& region)
if (view.TrimToViewport(&srUpdateRegion))
{
view.ConvertToOrigin(&srUpdateRegion);
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
LOG_IF_FAILED(pEngine->Invalidate(&srUpdateRegion));
}
@ -329,7 +638,7 @@ void Renderer::TriggerRedraw(const til::point* const pcoord)
// - <none>
void Renderer::TriggerRedrawAll(const bool backgroundChanged, const bool frameChanged)
{
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
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:
// - Called when the selected area in the console has changed.
// 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(newSelectionViewportRects));
@ -423,7 +719,7 @@ try
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(newHighlights, buffer));
@ -456,7 +752,7 @@ bool Renderer::_CheckViewportAndScroll()
coordDelta.x = srOldViewport.left - srNewViewport.left;
coordDelta.y = srOldViewport.top - srNewViewport.top;
FOREACH_ENGINE(engine)
for (const auto engine : _engines)
{
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport));
LOG_IF_FAILED(engine->InvalidateScroll(&coordDelta));
@ -485,6 +781,38 @@ bool Renderer::_CheckViewportAndScroll()
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:
// - Called when a scroll operation has occurred by manipulating the viewport.
// - This is a special case as calling out scrolls explicitly drastically improves performance.
@ -511,7 +839,7 @@ void Renderer::TriggerScroll()
// - <none>
void Renderer::TriggerScroll(const til::point* const pcoordDelta)
{
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
LOG_IF_FAILED(pEngine->InvalidateScroll(pcoordDelta));
}
@ -531,7 +859,7 @@ void Renderer::TriggerScroll(const til::point* const pcoordDelta)
void Renderer::TriggerTitleChange()
{
const auto newTitle = _pData->GetConsoleTitle();
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
LOG_IF_FAILED(pEngine->InvalidateTitle(newTitle));
}
@ -540,7 +868,7 @@ void Renderer::TriggerTitleChange()
void Renderer::TriggerNewTextNotification(const std::wstring_view newText)
{
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
LOG_IF_FAILED(pEngine->NotifyNewText(newText));
}
@ -568,7 +896,7 @@ HRESULT Renderer::_PaintTitle(IRenderEngine* const pEngine)
// - <none>
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->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;
_lastSoftFontChar = _firstSoftFontChar + softFontCharCount - 1;
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
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.
// 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
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
const auto hr = LOG_IF_FAILED(pEngine->GetProposedFont(FontInfoDesired, FontInfo, iDpi));
// 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.
// 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
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
const auto hr = LOG_IF_FAILED(pEngine->IsGlyphWideByFont(glyph, &fIsFullWidth));
// 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;
}
// 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:
// - Paint helper to fill in the background color of the invalid area within the frame.
// 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.
// It can move left/right or top/bottom depending on how the viewport is scrolled
// relative to the entire buffer.
const auto view = _pData->GetViewport();
const auto compositionRow = _compositionCache ? _compositionCache->absoluteOrigin.y : -1;
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
// 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)
// 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.
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.
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
// 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.
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.
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
@ -817,7 +1130,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
(bufferLine.RightExclusive() == buffer.GetSize().Width());
// 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.
_PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped);
@ -826,7 +1139,7 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
const auto imageSlice = buffer.GetRowByOffset(row).GetImageSlice();
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
void Renderer::_updateCursorInfo()
{
// Get cursor position in buffer
auto coordCursor = _pData->GetCursorPosition();
const auto& buffer = _pData->GetTextBuffer();
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
// 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 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;
// We need to convert the screen coordinates of the viewport to an
// 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);
// 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 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.viewportLeft = viewport.left;
_currentCursorOptions.lineRendition = lineRendition;
_currentCursorOptions.ulCursorHeightPercent = _pData->GetCursorHeight();
_currentCursorOptions.ulCursorHeightPercent = cursorHeight;
_currentCursorOptions.cursorPixelWidth = _pData->GetCursorPixelWidth();
_currentCursorOptions.fIsDoubleWidth = _pData->IsCursorDoubleWidth();
_currentCursorOptions.cursorType = _pData->GetCursorStyle();
_currentCursorOptions.fIsDoubleWidth = buffer.GetRowByOffset(cursorPosition.y).DbcsAttrAt(cursorPosition.x) != DbcsAttribute::Single;
_currentCursorOptions.cursorType = cursor.GetType();
_currentCursorOptions.fUseColor = useColor;
_currentCursorOptions.cursorColor = cursorColor;
_currentCursorOptions.isVisible = _pData->IsCursorVisible();
_currentCursorOptions.isOn = _currentCursorOptions.isVisible && _pData->IsCursorOn();
_currentCursorOptions.isVisible = !_cursorVisibilityInhibitors.any();
_currentCursorOptions.isOn = _currentCursorOptions.isVisible && _cursorBlinkerOn;
_currentCursorOptions.inViewport = xInRange && yInRange;
}
@ -1184,7 +1562,7 @@ void Renderer::_invalidateCurrentCursor() const
if (view.TrimToViewport(&rect))
{
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
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
// 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.)
void Renderer::_invalidateOldComposition() const
void Renderer::_invalidateOldComposition()
{
if (!_compositionCache || !_currentCursorOptions.inViewport)
if (!_compositionCache)
{
return;
}
_compositionCache.reset();
if (!_currentCursorOptions.inViewport)
{
return;
}
@ -1208,7 +1593,7 @@ void Renderer::_invalidateOldComposition() const
til::rect rect{ 0, coord.y, til::CoordTypeMax, coord.y + 1 };
if (view.TrimToViewport(&rect))
{
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
LOG_IF_FAILED(pEngine->Invalidate(&rect));
}
@ -1224,20 +1609,19 @@ void Renderer::_prepareNewComposition()
return;
}
const auto viewport = _pData->GetViewport();
const auto coordCursor = _pData->GetCursorPosition();
auto& buffer = _pData->GetTextBuffer();
const auto coordCursor = buffer.GetCursor().GetPosition();
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));
}
auto& buffer = _pData->GetTextBuffer();
auto& scratch = buffer.GetScratchpadRow();
const auto& activeComposition = _pData->GetActiveComposition();
@ -1399,32 +1783,17 @@ void Renderer::_ScrollPreviousSelection(const til::point delta)
void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine)
{
THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
for (auto& p : _engines)
{
if (!p)
{
p = pEngine;
_forceUpdateViewport = true;
return;
}
}
THROW_HR_MSG(E_UNEXPECTED, "engines array is full");
_engines.push_back(pEngine);
_forceUpdateViewport = true;
}
void Renderer::RemoveRenderEngine(_In_ IRenderEngine* const pEngine)
{
THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
for (auto& p : _engines)
{
if (p == pEngine)
{
p = nullptr;
return;
}
}
std::erase_if(_engines, [=](IRenderEngine* e) {
return pEngine == e;
});
}
// Method Description:
@ -1464,7 +1833,7 @@ void Renderer::SetRendererEnteredErrorStateCallback(std::function<void()> pfn)
void Renderer::UpdateHyperlinkHoveredId(uint16_t id) noexcept
{
_hyperlinkHoveredId = id;
FOREACH_ENGINE(pEngine)
for (const auto pEngine : _engines)
{
pEngine->UpdateHyperlinkHoveredId(id);
}
@ -1474,13 +1843,3 @@ void Renderer::UpdateLastHoveredInterval(const std::optional<PointTree::interval
{
_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
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
--*/
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "../../buffer/out/textBuffer.hpp"
#include "../inc/IRenderEngine.hpp"
#include "../inc/RenderSettings.hpp"
#include "thread.hpp"
#include "../../buffer/out/textBuffer.hpp"
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
{
public:
Renderer(RenderSettings& renderSettings, IRenderData* pData);
~Renderer();
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 SynchronizedOutputChanged() noexcept;
void AllowCursorVisibility(InhibitionSource source, bool enable) noexcept;
void AllowCursorBlinking(InhibitionSource source, bool enable) noexcept;
void TriggerSystemRedraw(const til::rect* const prcDirtyClient);
void TriggerRedraw(const Microsoft::Console::Types::Viewport& region);
void TriggerRedraw(const til::point* const pcoord);
@ -66,7 +65,6 @@ namespace Microsoft::Console::Render
bool IsGlyphWideByFont(const std::wstring_view glyph);
void EnablePainting();
void WaitUntilCanRender();
void AddRenderEngine(_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);
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.
// This allows us to properly invalidate it between frames, etc.
struct CompositionCache
@ -90,10 +96,29 @@ namespace Microsoft::Console::Render
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);
// 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 _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept;
void _disablePainting() noexcept;
void _synchronizeWithOutput() noexcept;
bool _CheckViewportAndScroll();
void _scheduleRenditionBlink();
[[nodiscard]] HRESULT _PaintBackground(_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);
@ -108,19 +133,41 @@ namespace Microsoft::Console::Render
bool _isInHoveredInterval(til::point coordTarget) const noexcept;
void _updateCursorInfo();
void _invalidateCurrentCursor() const;
void _invalidateOldComposition() const;
void _invalidateOldComposition();
void _prepareNewComposition();
[[nodiscard]] HRESULT _PrepareRenderInfo(_In_ IRenderEngine* const pEngine);
// Constructor parameters, weakly referenced
RenderSettings& _renderSettings;
std::array<IRenderEngine*, 2> _engines{};
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;
size_t _lastSoftFontChar = 0;
uint16_t _hyperlinkHoveredId = 0;
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _hoveredInterval;
Microsoft::Console::Types::Viewport _viewport;
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::vector<Cluster> _clusterBuffer;
std::function<void()> _pfnBackgroundColorChanged;
@ -132,9 +179,5 @@ namespace Microsoft::Console::Render
til::point_span _lastSelectionPaintSpan{};
size_t _lastSelectionPaintSize{};
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 \
..\RenderSettings.cpp \
..\renderer.cpp \
..\thread.cpp \
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
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
--*/
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
@ -23,6 +12,8 @@ class TextBuffer;
namespace Microsoft::Console::Render
{
class Renderer;
struct CompositionRange
{
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;
};
// 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
{
public:
@ -53,28 +59,23 @@ namespace Microsoft::Console::Render
virtual void UnlockConsole() noexcept = 0;
// This block used to be the original IRenderData.
virtual til::point GetCursorPosition() const noexcept = 0;
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 TimerDuration GetBlinkInterval() noexcept = 0; // Return ::zero() or ::max() for no blink.
virtual ULONG GetCursorPixelWidth() const noexcept = 0;
virtual bool IsCursorDoubleWidth() const = 0;
virtual const bool IsGridLineDrawingAllowed() noexcept = 0;
virtual const std::wstring_view GetConsoleTitle() const noexcept = 0;
virtual const std::wstring GetHyperlinkUri(uint16_t id) const = 0;
virtual const std::wstring GetHyperlinkCustomId(uint16_t id) const = 0;
virtual const std::vector<size_t> GetPatternId(const til::point location) const = 0;
virtual bool IsGridLineDrawingAllowed() noexcept = 0;
virtual std::wstring_view GetConsoleTitle() const noexcept = 0;
virtual std::wstring GetHyperlinkUri(uint16_t id) const = 0;
virtual std::wstring GetHyperlinkCustomId(uint16_t id) const = 0;
virtual std::vector<size_t> GetPatternId(const til::point location) const = 0;
// This block used to be IUiaData.
virtual std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept = 0;
virtual const bool IsSelectionActive() const = 0;
virtual const bool IsBlockSelection() const = 0;
virtual bool IsSelectionActive() const = 0;
virtual bool IsBlockSelection() const = 0;
virtual void ClearSelection() = 0;
virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0;
virtual const til::point GetSelectionAnchor() const noexcept = 0;
virtual const til::point GetSelectionEnd() const noexcept = 0;
virtual const bool IsUiaDataInitialized() const noexcept = 0;
virtual til::point GetSelectionAnchor() const noexcept = 0;
virtual til::point GetSelectionEnd() 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.
// 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:
enum class Mode : size_t
{
BlinkAllowed,
IndexedDistinguishableColors,
AlwaysDistinguishableColors,
IntenseIsBold,
@ -30,6 +29,7 @@ namespace Microsoft::Console::Render
};
RenderSettings() noexcept;
void SaveDefaultSettings() noexcept;
void RestoreDefaultSettings() 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> GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept;
COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept;
void ToggleBlinkRendition(class Renderer* renderer) noexcept;
void ToggleBlinkRendition() noexcept;
private:
til::enumset<Mode> _renderMode{ Mode::BlinkAllowed, Mode::IntenseIsBright };
til::enumset<Mode> _renderMode{ Mode::IntenseIsBright };
std::array<COLORREF, TextColor::TABLE_SIZE> _colorTable;
std::array<size_t, static_cast<size_t>(ColorAlias::ENUM_COUNT)> _colorAliasIndices;
std::array<COLORREF, TextColor::TABLE_SIZE> _defaultColorTable;
std::array<size_t, static_cast<size_t>(ColorAlias::ENUM_COUNT)> _defaultColorAliasIndices;
size_t _blinkCycle = 0;
mutable bool _blinkIsInUse = false;
bool _blinkShouldBeFaint = false;
};
}

View File

@ -110,9 +110,6 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
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{
.text = string,
.columnLimit = lineWidth,
@ -120,9 +117,8 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
while (!state.text.empty())
{
if (cursor.IsDelayedEOLWrap() && wrapAtEOL)
if (const auto delayedCursorPosition = cursor.GetDelayEOLWrap(); delayedCursorPosition && wrapAtEOL)
{
const auto delayedCursorPosition = cursor.GetDelayedAtPosition();
cursor.ResetDelayEOLWrap();
// Only act on a delayed EOL if we didn't move the cursor to a
// 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.
// 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
@ -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.
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:
@ -494,7 +472,7 @@ void AdaptDispatch::CursorSaveState()
savedCursorState.Column = cursorPosition.x + 1;
savedCursorState.Row = cursorPosition.y + 1;
savedCursorState.Page = page.Number();
savedCursorState.IsDelayedEOLWrap = page.Cursor().IsDelayedEOLWrap();
savedCursorState.IsDelayedEOLWrap = page.Cursor().GetDelayEOLWrap().has_value();
savedCursorState.IsOriginModeRelative = _modes.test(Mode::Origin);
savedCursorState.Attributes = page.Attributes();
savedCursorState.TermOutput = _termOutput;
@ -1825,7 +1803,7 @@ void AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
_terminalInput.SetInputMode(TerminalInput::Mode::AutoRepeat, enable);
break;
case DispatchTypes::ModeParams::ATT610_StartCursorBlink:
_pages.ActivePage().Cursor().SetBlinkingAllowed(enable);
_pages.ActivePage().Cursor().SetIsBlinking(enable);
break;
case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode:
_pages.ActivePage().Cursor().SetIsVisible(enable);
@ -1990,7 +1968,7 @@ void AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
state = mapTemp(_terminalInput.GetInputMode(TerminalInput::Mode::AutoRepeat));
break;
case DispatchTypes::ModeParams::ATT610_StartCursorBlink:
state = mapTemp(_pages.ActivePage().Cursor().IsBlinkingAllowed());
state = mapTemp(_pages.ActivePage().Cursor().IsBlinking());
break;
case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode:
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.
cursor.SetXPosition(leftMargin);
_ApplyCursorMovementFlags(cursor);
}
}
@ -2457,10 +2434,7 @@ bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const b
textBuffer.IncrementCircularBuffer(eraseAttributes);
_api.NotifyBufferRotation(1);
// 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);
// We trigger a scroll rather than a redraw, since that's more efficient.
textBuffer.TriggerScroll({ 0, -1 });
// 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);
_ApplyCursorMovementFlags(cursor);
return viewportMoved;
}
@ -2524,7 +2497,6 @@ void AdaptDispatch::ReverseLineFeed()
{
// Otherwise we move the cursor up, but not past the top of the page.
cursor.SetPosition(textBuffer.ClampPositionWithinLine({ cursorPosition.x, cursorPosition.y - 1 }));
_ApplyCursorMovementFlags(cursor);
}
}
@ -2550,7 +2522,6 @@ void AdaptDispatch::BackIndex()
else if (cursorPosition.x > 0)
{
cursor.SetXPosition(cursorPosition.x - 1);
_ApplyCursorMovementFlags(cursor);
}
}
@ -2576,7 +2547,6 @@ void AdaptDispatch::ForwardIndex()
else if (cursorPosition.x < page.Buffer().GetLineWidth(cursorPosition.y) - 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
// cursor movement resets the flag automatically, so we need to save the
// 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);
_ApplyCursorMovementFlags(cursor);
if (delayedWrapOriginallySet)
{
cursor.DelayEOLWrap();
@ -2678,7 +2647,6 @@ void AdaptDispatch::BackwardsTab(const VTInt numTabs)
}
cursor.SetXPosition(column);
_ApplyCursorMovementFlags(cursor);
}
//Routine Description:
@ -3055,7 +3023,7 @@ void AdaptDispatch::HardReset()
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, false);
// Restore cursor blinking mode.
_pages.ActivePage().Cursor().SetBlinkingAllowed(true);
_pages.ActivePage().Cursor().SetIsBlinking(true);
// Delete all current tab stops and reapply
TabSet(DispatchTypes::TabSetType::SetEvery8Columns);
@ -3240,7 +3208,7 @@ void AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
auto& cursor = _pages.ActivePage().Cursor();
cursor.SetType(actualType);
cursor.SetBlinkingAllowed(fEnableBlinking);
cursor.SetIsBlinking(fEnableBlinking);
}
// Routine Description:
@ -4310,7 +4278,7 @@ void AdaptDispatch::_ReportDECSLRMSetting()
void AdaptDispatch::_ReportDECSCUSRSetting() const
{
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
// number from 1 to 6 representing the cursor style. The ' q' indicates
// this is a DECSCUSR response.
@ -4488,7 +4456,7 @@ void AdaptDispatch::_ReportCursorInformation()
flags += (_modes.test(Mode::Origin) ? 1 : 0);
flags += (_termOutput.IsSingleShiftPending(2) ? 2 : 0);
flags += (_termOutput.IsSingleShiftPending(3) ? 4 : 0);
flags += (cursor.IsDelayedEOLWrap() ? 8 : 0);
flags += (cursor.GetDelayEOLWrap().has_value() ? 8 : 0);
// Character set designations.
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> _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept;
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 _SelectiveEraseRect(const Page& page, const til::rect& eraseRect);
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).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r1 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (steady block).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r2 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (blinking underline).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r3 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (steady underline).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r4 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (blinking bar).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r5 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (steady bar).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r6 q\033\\");
Log::Comment(L"Requesting DECSCUSR style (non-standard).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Legacy);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r0 q\033\\");
@ -2814,12 +2814,12 @@ public:
VERIFY_IS_TRUE(_pDispatch->_modes.test(AdaptDispatch::Mode::Origin));
VERIFY_IS_FALSE(termOutput.IsSingleShiftPending(2));
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\\");
VERIFY_IS_FALSE(_pDispatch->_modes.test(AdaptDispatch::Mode::Origin));
VERIFY_IS_TRUE(termOutput.IsSingleShiftPending(2));
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");
stateMachine.ProcessString(L"\033P1$t1;1;1;@;@;@;3;1;H;ABCF\033\\");
@ -2895,15 +2895,15 @@ public:
// success cases
// set blinking mode = true
Log::Comment(L"Test 1: enable blinking = true");
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(false);
_pDispatch->SetMode(DispatchTypes::ATT610_StartCursorBlink);
VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCursor().IsBlinkingAllowed());
VERIFY_IS_TRUE(_testGetSet->_textBuffer->GetCursor().IsBlinking());
// set blinking mode = false
Log::Comment(L"Test 2: enable blinking = false");
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetIsBlinking(true);
_pDispatch->ResetMode(DispatchTypes::ATT610_StartCursorBlink);
VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCursor().IsBlinkingAllowed());
VERIFY_IS_FALSE(_testGetSet->_textBuffer->GetCursor().IsBlinking());
}
TEST_METHOD(ScrollMarginsTest)