Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues with cursor invalidation in GDI #17150

Closed
j4james opened this issue Apr 28, 2024 · 3 comments · Fixed by #17181
Closed

Issues with cursor invalidation in GDI #17150

j4james opened this issue Apr 28, 2024 · 3 comments · Fixed by #17181
Assignees
Labels
Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues Issue-Bug It either shouldn't be doing this or needs an investigation. Needs-Tag-Fix Doesn't match tag requirements Product-Terminal The new Windows Terminal. Severity-Blocking We won't ship a release like this! No-siree.

Comments

@j4james
Copy link
Collaborator

j4james commented Apr 28, 2024

Windows Terminal version

Commit 378b659

Windows build number

10.0.19045.4291

Other Software

No response

Steps to reproduce

  1. Make sure the conhost render engine is set to GDI (i.e. the UseDx registry entry is 0).
  2. Open a WSL shell in a recent build of OpenConsole (commit 20b0bed or later)
  3. Execute the following statements:
    printf "\e[2 q"; sleep 2; printf "\e[A"; sleep 2; printf "\e[B"
    

Expected Behavior

The above script is setting the cursor to a steady block so it's easy to see, sleeping for 2 seconds, moving up a line and sleeping for another 2 seconds, then moving the cursor back down again. You should be able to see the cursor the entire time.

Actual Behavior

Initially the cursor is visible, but as soon as we move up a line it becomes invisible for 2 seconds.

I believe this is a regression from PR #15500. Originally we would call InvalidateCursor (via TriggerRedrawCursor) whenever the cursor was moved, both to invalidate the old position and the new position. But since PR #15500, TriggerRedrawCursor has been nuked, and InvalidateCursor is now called at the start of every frame (regardless of whether the cursor is moved or not), and it only invalidates the old cursor position. So when the cursor moves, it's erased from the old position, but not redrawn in the new position.

This typically only affects vertical movement (other than some edge cases), because at some point I think we stopped caring about the horizontal extent of the invalidated region and just refresh the full width of the screen regardless. This also doesn't seem to affect the DX and Atlas engines, but I have no idea why.

@j4james j4james added Issue-Bug It either shouldn't be doing this or needs an investigation. Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting labels Apr 28, 2024
Copy link

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

@j4james
Copy link
Collaborator Author

j4james commented Apr 28, 2024

I love you github-actions bot, and I hope you'll spare my life when the robot revolution comes, but I feel a little bit hurt when you suggest a bunch of my own issues back to me, like idiot human can't remember that he's already raised this issue before. Although to be fair, that's not an unreasonable assumption.

@j4james
Copy link
Collaborator Author

j4james commented Apr 28, 2024

As to how we fix this issue, I can get it to work again by replicating most of the following code a couple of lines down:

if (_currentCursorOptions)
{
const auto& buffer = _pData->GetTextBuffer();
const auto view = buffer.GetSize();
const auto coord = _currentCursorOptions->coordCursor;
// 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.)
if (_compositionCache)
{
til::rect rect{ 0, coord.y, til::CoordTypeMax, coord.y + 1 };
if (view.TrimToViewport(&rect))
{
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->Invalidate(&rect));
}
}
}
else
{
const auto lineRendition = buffer.GetLineRendition(coord.y);
const auto cursorWidth = _pData->IsCursorDoubleWidth() ? 2 : 1;
til::rect rect{ coord.x, coord.y, coord.x + cursorWidth, coord.y + 1 };
rect = BufferToScreenLine(rect, lineRendition);
if (view.TrimToViewport(&rect))
{
FOREACH_ENGINE(pEngine)
{
LOG_IF_FAILED(pEngine->InvalidateCursor(&rect));
}
}
}
}

That is, after we've reinitialized _currentCursorOptions with the new position, we need to invalidate that new position as well (I'm not sure about the _compositionCache, so I just ignored that bit). I don't know if this is a good solution, though.

@lhecker lhecker self-assigned this Apr 29, 2024
@carlos-zamora carlos-zamora added this to the Terminal v1.21 milestone May 1, 2024
@carlos-zamora carlos-zamora added Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues Product-Terminal The new Windows Terminal. and removed Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting labels May 1, 2024
@zadjii-msft zadjii-msft added the Severity-Blocking We won't ship a release like this! No-siree. label May 2, 2024
DHowett pushed a commit that referenced this issue May 2, 2024
There were multiple bugs:
* GDI engine only paints whatever has been invalidated.
  This means we need to not just invalidate the old cursor rect
  but also the new one, or else movements may not be visible.
* The composition should be drawn at the cursor position even if
  the cursor is invisible, but inside the renderer viewport.
* Conceptually, scrolling the viewport moves the relative cursor
  position even if the cursor is invisible.
* An invisible cursor is not the same as one that's outside the
  viewport. It's more like a cursor that's not turned on.

To resolve the first issue we simply need to call `InvalidateCursor`
again. To do so, it was abstracted into `_invalidateCurrentCursor()`.

The next 2 issues are resolved by un-`optional`-izing `CursorOptions`.
After all, even an invisible or an out-of-bounds cursor still has a
coordinate and it may still be scrolled into view.
Instead, it has the new `inViewport` property as a replacement.
This allows for instance the IME composition code in the renderer
to use the cursor coordinate while the cursor is invisible.

The last issue is fixed by simply changing the `.isOn` logic.

Closes #17150

## Validation Steps Performed
* In conhost with the GDI renderer:
  `printf "\e[2 q"; sleep 2; printf "\e[A"; sleep 2; printf "\e[B"`
  Cursor moves up after 2s and then down again after 2s. ✅
* Hide the cursor (`"\e[?25l"`) and use a CJK IME.
  Words can still be written and deleted correctly. ✅
* Turning the cursor back on (`"\e[?25h"`) works ✅
* Scrolling shows/hides the cursor ✅
@microsoft-github-policy-service microsoft-github-policy-service bot added the Needs-Tag-Fix Doesn't match tag requirements label May 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues Issue-Bug It either shouldn't be doing this or needs an investigation. Needs-Tag-Fix Doesn't match tag requirements Product-Terminal The new Windows Terminal. Severity-Blocking We won't ship a release like this! No-siree.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants