Skip to content

Conversation

@lhecker
Copy link
Member

@lhecker lhecker commented Apr 23, 2025

Now that we have passthrough mode (#17510), as well as the render thread
rewrite (#18632), adding support for DECSET 2026 is a piece of cake.

Closes #8331

Validation Steps Performed

  • Run btop via ssh in a huge window
    • Open the Options menu
    • No flicker ✅
  • Run the following in PowerShell 7+:
    1..10000 | % { Write-Host -NoNewline ("`e[?2026h" + "foobar`r`e[K$_" * 10000 + "`e[?2026l") }
    • "foobar" is not visible ❌
    • The number count goes up smoothly ❌

@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Task It's a feature request, but it doesn't really need a major design. Area-VT Virtual Terminal sequence support Product-Conhost For issues in the Console codebase labels Apr 23, 2025
@lhecker lhecker force-pushed the dev/lhecker/8331-synchronized-output branch from b511704 to 424b1d3 Compare April 23, 2025 00:45
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

No notes / 360 noscope

@lhecker
Copy link
Member Author

lhecker commented Apr 23, 2025

After sleeping through it, I think I figured out why I felt like the logic wasn't completely correct.

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

I'm still cool with this but the "jiggle the handle" operation worries me

@lhecker
Copy link
Member Author

lhecker commented Apr 23, 2025

You could think of it as "give the renderer an opportunity at this precise time to render" which makes sense IMO given that 2026l operates a bit like a V-Sync.

@DHowett DHowett merged commit 773a4b9 into main Apr 23, 2025
19 checks passed
@DHowett DHowett deleted the dev/lhecker/8331-synchronized-output branch April 23, 2025 17:15
@j4james
Copy link
Collaborator

j4james commented Apr 23, 2025

Ideally I think this should be tracked as a render mode in RenderSettings, and maybe then you could get by a with a single renderer method that updates the render state by referencing that mode. Because it needs to be reported in AdaptDispatch::RequestMode, the same as all the other modes. If you don't do that, some apps will assume it's not supported and won't make use of this functionality.

This is less crucial, but it should really also be reset in AdaptDispatch::HardReset, which for render modes is handled in the RenderSettings::RestoreDefaultSettings method. That would assumedly then need to be followed by a renderer call to get the renderer in sync with the new state, similar to the TriggerRedrawAll call in HardReset.

@lhecker
Copy link
Member Author

lhecker commented Apr 23, 2025

I was reading the linked issue, and it seemed to me like apps are not expected to do feature detection. It did seem to work at least in btop. I'll happily make the changes though.

and maybe then you could get by a with a single renderer method that updates the render state by referencing that mode

Could you explain what you meant with that?

This is less crucial, but it should really also be reset in AdaptDispatch::HardReset

I'm indeed not entirely sure how important this is since the delay set by DECSET 2026 h expires after 100ms anyway and I guess TUI applications won't use RIS repeatedly, right? I'll see how easy it is and do it in that case.

@ldemailly
Copy link

yes the codes should work without having to ask if supported, that’s important (as by definition unsupported codes are ignored)

@j4james
Copy link
Collaborator

j4james commented Apr 23, 2025

I was reading the linked issue, and it seemed to me like apps are not expected to do feature detection. It did seem to work at least in btop. I'll happily make the changes though.

Apps shouldn't need to do feature detection, but some apps may still choose to skip using the mode if they know it's not supported. Because if you're trying to avoid flickering, and the target terminal doesn't support this mode, then sending a bunch of non-functional mode changes is not going to help matters.

I also suspect the fact that the feature detection is documented in the spec is partially responsible for a lot of apps and libraries choosing to make use of that functionality.

and maybe then you could get by a with a single renderer method that updates the render state by referencing that mode

Could you explain what you meant with that?

I was envisioning something simpler in in the _ModeParamsHelper method, like this:

_renderSettings.SetRenderMode(RenderSettings::Mode::SynchronizedOutput, enable);
if (_renderer)
{
    _renderer->UpdateSynchronizedOutput();
}

And then the Renderer::UpdateSynchronizedOutput might have something like this:

_isSynchronizingOutput = _renderSetting.GetRenderMode(RenderSettings::Mode::SynchronizedOutput);
if (!_isSynchronizingOutput)
{
    // Do the the wakeup thing...
}

This is assuming you still need _isSynchronizingOutput as a separate variable for the wakeup.

This is less crucial, but it should really also be reset in AdaptDispatch::HardReset

I'm indeed not entirely sure how important this is since the delay set by DECSET 2026 h expires after 100ms anyway and I guess TUI applications won't use RIS repeatedly, right?

Yeah, it's just a technicality really. But if you set the mode, send a hard reset, and then query the mode immediately afterwards, it should report as unset, but that wouldn't be the case if all of that happened within 100ms. Nobody is likely to care, but it's technically wrong.

But all that's really required to fix that, assuming you're using a render mode, is to add it to the reset call here (and update the comment):

// For now, DECSCNM is the only render mode we need to reset. The others are
// all user preferences that can't be changed programmatically.
_renderMode.reset(Mode::ScreenReversed);

If you want to make sure the screen will also be immediately refreshed (which is less important), then we'd also need a call to UpdateSynchronizedOutput after the RestoreDefaultSettings call here:

_renderSettings.RestoreDefaultSettings();
// Let the renderer know that the background and frame colors may have changed.
if (_renderer)
{
_renderer->TriggerRedrawAll(true, true);
}

@lhecker
Copy link
Member Author

lhecker commented Apr 23, 2025

Since _isSynchronizingOutput is a separate variable from the render mode, querying the mode will return the wrong value if the VT app fails to reset it and the renderer times out after 100ms and forces a reset. But if we add it to RIS, I guess that is a much more minor issue then…

@j4james
Copy link
Collaborator

j4james commented Apr 24, 2025

Since _isSynchronizingOutput is a separate variable from the render mode, querying the mode will return the wrong value if the VT app fails to reset it and the renderer times out after 100ms and forces a reset.

Yeah, if it has to be separate variable then the value needs to be synced back to the mode. It should just require a call to _renderSetting.SetRenderMode at the end of _synchronizeWithOutput.

DHowett pushed a commit that referenced this pull request Apr 24, 2025
`RenderSettings` already stores `DECSCNM` (reversed screen),
so it only makes sense to also store DECSET 2026 there.

## Validation Steps Performed
* Same as in #18826
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-VT Virtual Terminal sequence support Issue-Task It's a feature request, but it doesn't really need a major design. Product-Conhost For issues in the Console codebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement synchronized update control sequences

5 participants