mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
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:
parent
47018442cd
commit
2e78665ee0
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
@ -1759,6 +1759,7 @@ UINTs
|
||||
uld
|
||||
uldash
|
||||
uldb
|
||||
ULONGLONG
|
||||
ulwave
|
||||
Unadvise
|
||||
unattend
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -94,7 +94,7 @@ void InputTests::TestGetMouseButtonsValid()
|
||||
}
|
||||
else
|
||||
{
|
||||
dwButtonsExpected = Microsoft::Console::Interactivity::OneCore::SystemConfigurationProvider::s_DefaultNumberOfMouseButtons;
|
||||
dwButtonsExpected = 3;
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(dwButtonsExpected, nMouseButtons);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -45,7 +45,6 @@ SOURCES = \
|
||||
..\selectionState.cpp \
|
||||
..\scrolling.cpp \
|
||||
..\cmdline.cpp \
|
||||
..\CursorBlinker.cpp \
|
||||
..\alias.cpp \
|
||||
..\history.cpp \
|
||||
..\VtIo.cpp \
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ ULONG SystemConfigurationProvider::GetCursorWidth()
|
||||
else
|
||||
{
|
||||
LOG_LAST_ERROR();
|
||||
return s_DefaultCursorWidth;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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:
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
}
|
||||
|
||||
@ -32,7 +32,6 @@ SOURCES = \
|
||||
..\RenderEngineBase.cpp \
|
||||
..\RenderSettings.cpp \
|
||||
..\renderer.cpp \
|
||||
..\thread.cpp \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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 };
|
||||
};
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user