Skip to content

Move all blink handling into Renderer - #19330

Merged
Leonard Hecker (lhecker) merged 9 commits into
mainfrom
dev/lhecker/cursor-blinker
Nov 11, 2025
Merged

Leonard Hecker (lhecker) merged 9 commits into
mainfrom
dev/lhecker/cursor-blinker

Conversation

@lhecker

@lhecker Leonard Hecker (lhecker) commented Sep 8, 2025 •

Copy link
Copy Markdown
Member

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 ✅

Comment thread src/renderer/base/renderer.cpp Fixed
Comment thread src/renderer/base/renderer.cpp Fixed
Comment thread src/renderer/base/renderer.cpp Fixed
Comment thread src/renderer/base/renderer.cpp Fixed
Comment thread src/renderer/base/renderer.cpp Fixed
@github-actions

This comment has been minimized.

@lhecker
Leonard Hecker (lhecker) changed the base branch from main to dev/lhecker/BEGONE-FOUL-SPIRIT September 14, 2025 11:16
Dustin L. Howett (DHowett) pushed a commit that referenced this pull request Sep 22, 2025
Goal: Remove `CursorBlinker`.
Problem: Spooky action at a distance via `Cursor::HasMoved`.
Solution: Moved all the a11y event raising into `_stream.cpp` and pray
for the best.

Goal: Prevent node.js from tanking conhost performance via MSAA (WHY).
Problem: `ServiceLocator`.
Solution: Unserviced the locator. Debounced event raising. Performance
increased by >10x.
Problem 2: Lots of files changed.

This PR is a prerequisite for #19330

## Validation Steps Performed
Ran NVDA with and without UIA enabled and with different delays. ✅
@lhecker
Leonard Hecker (lhecker) changed the base branch from dev/lhecker/BEGONE-FOUL-SPIRIT to main September 22, 2025 22:59
@lhecker
Leonard Hecker (lhecker) marked this pull request as ready for review September 22, 2025 22:59
Comment thread src/renderer/base/renderer.cpp Resolved
Comment thread src/renderer/base/renderer.cpp Resolved
Comment thread src/host/renderData.cpp Resolved
Comment thread src/host/output.cpp Resolved
Comment thread src/terminal/adapter/adaptDispatch.cpp Resolved

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

50/56

// - <none>
// Return Value:
// - <none>
void Window::_UpdateSystemMetrics() const

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Make sure nobody else called this who cares about the cursor metrics (probably not)

cursor.SetSize(_d->ulSavedCursorSize);
cursor.SetIsVisible(_d->fSavedCursorVisible);
cursor.SetType(_d->savedCursorType);
cursor.SetIsOn(true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 make sure selection has the same cursor behavior

Comment thread src/host/renderData.cpp Resolved
Comment thread src/host/output.cpp Resolved
Comment thread src/host/AccessibilityNotifier.cpp Resolved
Comment thread src/cascadia/TerminalCore/Terminal.cpp Resolved
Comment thread src/buffer/out/cursor.cpp Resolved

void TermControl::CursorVisibility(Control::CursorDisplayState cursorVisibility)
{
_cursorVisibility = cursorVisibility;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like cursor visibility is stored across 3 variables:

  • TermControl::_cursorVisibility
  • ControlCore::_forceCursorVisible
  • Renderer::_cursorVisibilityInhibitors

Could we remove the one in TermControl and just query the one in ControlCore? Or, even better, just pipe the value from Renderer all the way up, rather than having a separate boolean member on each layer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually tried this, but that doesn't work unless we refactor how focus is handled in ControlInteractivity. This is because we make a distinction in TermControl between cursor-visible-because-of-xaml-focus and cursor-visible-because-of-cursor-display-state. ControlCore then ties these together into a single boolean again, with some extra logic on top. If you ask me, it's a mistake to handle XAML/UI-logic in the ControlCore layer like that. It should just be "do I have focus? yes/no" and TermControl decides that.

I would not expose _forceCursorVisible from ControlCore just to remove the _cursorVisibility in TermControl. The variables are duplicated now, but they serve a different purpose. It'd be better to remove the need for forcing the cursor visibility in the first place IMO.

Comment thread src/cascadia/TerminalCore/Terminal.cpp Resolved
static DWORD _timerToMillis(TimerRepr t) noexcept;

// Actual rendering
[[nodiscard]] HRESULT PaintFrame();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit weird that this one is marked private, but doesn't have the _ prefix. Plus there's also _PaintFrame() just below it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC I did this to help git with reducing the diff size.

Comment thread src/host/selection.cpp Resolved
Comment thread src/host/selection.cpp Resolved
@carlos-zamora

Copy link
Copy Markdown
Contributor

Fully reviewed. I want to get some resolution on my comments before approving.

Regarding accessibility (#19374), I plan on taking a look at a reported memory leak in WT when UIA is enabled soon-ish. I'll lump looking into #19374 too and Leonard and I can tag team work on a11y then.

@carlos-zamora

Copy link
Copy Markdown
Contributor

Oh, and you probably want to update the PR body too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for doing this 🙂

@lhecker
Leonard Hecker (lhecker) merged commit 2e78665 into main Nov 11, 2025
19 checks passed
@lhecker

Copy link
Copy Markdown
Member Author

I've merged this now Dustin L. Howett (@DHowett) so we can move on and fix it in post if needed.

}
}

why_does_cpp_not_have_labeled_loops:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved 63/63 after merge. Well done. One nit: StarTimer ⭐

@DHowett
Dustin L. Howett (DHowett) deleted the dev/lhecker/cursor-blinker branch November 20, 2025 19:45
Dustin L. Howett (DHowett) added a commit that referenced this pull request Mar 12, 2026
With the rendering thread changes in GH-18632 and GH-19330, we
strengthened a requirement that render shutdown be done outside of the
console lock. This works on all platforms except OneCore, where ConIoSrv
manages the focus of multiple console windows and we need to explicitly
order the handling of tearing down and relinquishing device resources.

The old code (before GH-18632) used to wait for a whole second before
giving up.

Instead, let's just unlock the console to let the final frame drain out.

I created a synthetic repro: start two cmd sessions with a lot of
rendering load (`cmdd start cmd /c dir /s c:\`), and then run `cmdd date
/t` in a tight loop while tabbing between the console windows.

Before this fix, it hangs within a couple tens of date invocations.
With this fix, it does not hang at all.

Fixes #61354695
Dustin L. Howett (DHowett) added a commit that referenced this pull request Mar 12, 2026
…evt (#19967)

With the rendering thread changes in #18632 and #19330, we strengthened
a requirement that render shutdown be done outside of the console lock.
This works on all platforms except OneCore, where ConIoSrv manages the
focus of multiple console windows and we need to explicitly order the
handling of tearing down and relinquishing device resources.

The old code (before #18632) used to wait for a whole second before
giving up.

Instead, let's just unlock the console to let the final frame drain out.

I created a synthetic repro: start two cmd sessions with a lot of
rendering load (`cmdd start cmd /c dir /s c:\`), and then run `cmdd date
/t` in a tight loop while tabbing between the console windows.

Before this fix, it hangs within a couple tens of date invocations. With
this fix, it does not hang at all.

Fixes MSFT-61354695
Dustin L. Howett (DHowett) added a commit that referenced this pull request Sep 29, 2026
This pull request builds on the render timer infrastructure in #19330
and the pointer ID work in #20017 to move automatic scrolling down into
the Interactivity layer.

Leonard tells me that this is an acceptable use of the render timer
infrastructure because it directly results in a new frame or new content
being rendered (therefore, it does not spin the renderer for no reason.)

Much of the code is the same. I recommend reviewing with:

```
git diff --color-moved=dimmed-zebra --color-moved-ws=ignore-all-space (git merge-base origin/main origin/dev/duhowett/wpf-1-autoscroll-to-interactivity) origin/dev/duhowett/wpf-1-autoscroll-to-interactivity
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants