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

Improve the DECSC/DECRC implementation #3160

Merged
merged 8 commits into from
Nov 5, 2019

Conversation

j4james
Copy link
Collaborator

@j4james j4james commented Oct 11, 2019

Summary of the Pull Request

The current DECSC implementation only saves the cursor position and origin mode. This PR improves that functionality with additional support for saving the SGR attributes, as well as the active character set.

It also fixes the way the saved state interacts with the alt buffer (private mode 1049), triggering a save when switching to the alt buffer, and a restore when switching back, and tracking the alt buffer state independently from the main state.

PR Checklist

Detailed Description of the Pull Request / Additional comments

In order to properly save and restore the SGR attributes, we first needed to add a pair of APIs in the ConGetSet interface which could round-trip the attributes with full 32-bit colors (the existing methods only work with legacy attributes).

I then added a struct in the AdaptDispatch implementation to make it easier to manage all of the parameters that needed to be saved. This includes the cursor position and origin mode that we were already tracking, and now also the SGR text attributes and the active character set (via the TermOutput class).

Two instances of this structure are required, since changes made to the saved state in the alt buffer need to be tracked separately from changes in the main buffer. I've added a boolean property that specifies whether we're in the alt buffer or not, and use that to decide which of the two instances we're working with.

I also needed to explicitly trigger a save when switching to the alt buffer, and a restore when switching back, since we weren't already doing that (the existing implementation gave the impression that the state was saved, because each buffer has its own cursor position, but that doesn't properly match the XTerm behaviour).

For the state tracking itself, we've now got two additional properties - the SGR attributes, which we obtain via the new private API, and the active character set, which we get from a local AdaptDispatch field. I'm saving the whole TermOutput class for the character set, since I'm hoping that will make it automatically supports future enhancements.

When restoring the cursor position, there is also now a fix to handle the relative origin mode correctly. If the margins are changed between the position being saved and restored, it's possible for the cursor to end up outside of the new margins, which would be illegal. So there is now an additional step that clamps the Y coordinate within the margin boundaries if the origin mode is relative.

Validation Steps Performed

I've added a couple of screen buffer tests which check that the various parameters are saved and restored as expected, as well as checking that the Y coordinate is clamped appropriately when the relative origin mode is set.

I've also tested manually with Vttest and confirmed that the SAVE/RESTORE CURSOR test (the last page of the Test of screen features)) is now working a lot better than it used to.

@j4james j4james marked this pull request as ready for review October 11, 2019 20:18
@DHowett-MSFT DHowett-MSFT added Product-Conhost For issues in the Console codebase Area-VT Virtual Terminal sequence support labels Oct 15, 2019
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

I kinda got a little off-topic on this review. I think it's probably a fine and safe change, though I'm holding off with an approval mostly due to where we are in the internal release cycle. @DHowett-MSFT can we accept conhost PRs currently?

// - pAttrs: Receives the TextAttribute value.
// Return Value:
// - TRUE if successful. FALSE otherwise.
BOOL ConhostInternalGetSet::PrivateGetTextAttributes(TextAttribute* const pAttrs) const
Copy link
Member

Choose a reason for hiding this comment

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

I suppose this change was inevitable. We've kinda just been patching around not just exposing the entire TextAttribute through this layer, but this might be easier. I know there was previously reluctance to doing this, something about it exposing too much of the implementation details of how conhost itself was implemented. @miniksa might remember more. I think we've come a long way since then, and this might be reasonable nowadays.

I guess from a design perspective, it would change things pretty dramatically - because the adapter could directly control the state of the text attributes, it could just avoid using any of the other functions, and put all the combined attribute manipulation logic directly in the adapter. There might be other things that conhost needs to do during some of these calls that might get circumnavigated by just using this method. These things might be a little trickier to move up into the adapter. I haven't dug in on this too much, so it might not be a problem.

If it was possible to move this logic into the adapter, then we could probably get rid of a bunch of these more specific boilerplate-y functions. That would probably make life easier for everyone.

The alternative here would be exposing a PrivateSaveState/PrivateRestoreState pair that lets conhost implement it however it likes. Though, when I think about it, the implementation would be highly similar between conhost and Terminal...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If it was possible to move this logic into the adapter, then we could probably get rid of a bunch of these more specific boilerplate-y functions. That would probably make life easier for everyone.

Absolutely! I had exactly the same thought when I added this. There's a lot of weirdness in the current SGR implementation which I think could be greatly simplified if we had access to an API like this. Was going to raise a separate issue for that once I knew you were happy with the API and I had some time to play around with potential implementations.

The alternative here would be exposing a PrivateSaveState/PrivateRestoreState pair that lets conhost implement it however it likes. Though, when I think about it, the implementation would be highly similar between conhost and Terminal...

If there was a need for conhost and Terminal to handle this functionality differently, that's already not a problem, since Terminal shares none of the AdaptDispatch implementation. That said, I'm not sure that's the right approach in the long term, because it seems we're slowly reimplementing all of the AdaptDispatch code in TerminalDispatch when those implementations should really be identical 99% of the time. But that's a topic for another issue.

Copy link
Member

Choose a reason for hiding this comment

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

If it was possible to move this logic into the adapter, then we could probably get rid of a bunch of these more specific boilerplate-y functions. That would probably make life easier for everyone.

Absolutely! I had exactly the same thought when I added this. There's a lot of weirdness in the current SGR implementation which I think could be greatly simplified if we had access to an API like this. Was going to raise a separate issue for that once I knew you were happy with the API and I had some time to play around with potential implementations.

I'm OK with additional private methods being added here, I just don't want to expose any more publicly. @zadjii-msft, I'm not sure I had an extreme reticence to adding new private APIs given we've done a ton of them. I think it was more "I'm going to try to accomplish as much as possible without adding new APIs at all" and it led us to the non-ideal situations that we had and discovered over time.

The alternative here would be exposing a PrivateSaveState/PrivateRestoreState pair that lets conhost implement it however it likes. Though, when I think about it, the implementation would be highly similar between conhost and Terminal...

If there was a need for conhost and Terminal to handle this functionality differently, that's already not a problem, since Terminal shares none of the AdaptDispatch implementation. That said, I'm not sure that's the right approach in the long term, because it seems we're slowly reimplementing all of the AdaptDispatch code in TerminalDispatch when those implementations should really be identical 99% of the time. But that's a topic for another issue.

Yes, if we're duplicating a big portion of the dispatch... then we probably need to somehow abstract the buffers or whatever the two adapters are calling and make that a shared component. Feel free to file another issue for that if you feel you can articulate it.

@DHowett-MSFT
Copy link
Contributor

We can accept conhost changes, but I am only flowing high-priority fixes back into Windows 20H1.

// - pAttrs: Receives the TextAttribute value.
// Return Value:
// - TRUE if successful. FALSE otherwise.
BOOL ConhostInternalGetSet::PrivateGetTextAttributes(TextAttribute* const pAttrs) const
Copy link
Member

Choose a reason for hiding this comment

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

If it was possible to move this logic into the adapter, then we could probably get rid of a bunch of these more specific boilerplate-y functions. That would probably make life easier for everyone.

Absolutely! I had exactly the same thought when I added this. There's a lot of weirdness in the current SGR implementation which I think could be greatly simplified if we had access to an API like this. Was going to raise a separate issue for that once I knew you were happy with the API and I had some time to play around with potential implementations.

I'm OK with additional private methods being added here, I just don't want to expose any more publicly. @zadjii-msft, I'm not sure I had an extreme reticence to adding new private APIs given we've done a ton of them. I think it was more "I'm going to try to accomplish as much as possible without adding new APIs at all" and it led us to the non-ideal situations that we had and discovered over time.

The alternative here would be exposing a PrivateSaveState/PrivateRestoreState pair that lets conhost implement it however it likes. Though, when I think about it, the implementation would be highly similar between conhost and Terminal...

If there was a need for conhost and Terminal to handle this functionality differently, that's already not a problem, since Terminal shares none of the AdaptDispatch implementation. That said, I'm not sure that's the right approach in the long term, because it seems we're slowly reimplementing all of the AdaptDispatch code in TerminalDispatch when those implementations should really be identical 99% of the time. But that's a topic for another issue.

Yes, if we're duplicating a big portion of the dispatch... then we probably need to somehow abstract the buffers or whatever the two adapters are calling and make that a shared component. Feel free to file another issue for that if you feel you can articulate it.

Copy link
Member

@miniksa miniksa 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 good with this. Thanks!

Copy link
Contributor

@DHowett-MSFT DHowett-MSFT left a comment

Choose a reason for hiding this comment

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

This looks great. Thanks, @j4james! Quick question about standards: this implementation seems to support restoring the saved cursor position any number of times. Is that expected? If so, awesome. If not, is there a lack of consensus on whether to support it across other terminal emulators?

@j4james
Copy link
Collaborator Author

j4james commented Nov 5, 2019

Quick question about standards: this implementation seems to support restoring the saved cursor position any number of times. Is that expected?

Yep, this is intentional. Quoting the DEC STD 070 documentation for DECRC:

The values stored in the Cursor Save Buffer are not affected by execution of this control.

i.e. the DECRC control doesn't do anything like clearing the saved data that it has just restored, so you should be able to restore over and over again. And this does match the behaviour in XTerm (and every other terminal I've tested).

@DHowett-MSFT
Copy link
Contributor

Excellent. Thanks!

@DHowett-MSFT DHowett-MSFT merged commit 53b6f14 into microsoft:master Nov 5, 2019
@j4james j4james deleted the fix-decsc branch November 21, 2019 13:25
@ghost
Copy link

ghost commented Nov 26, 2019

🎉Windows Terminal Preview v0.7.3291.0 has been released which incorporates this pull request.:tada:

Handy links:

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 Product-Conhost For issues in the Console codebase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DECSC (ESC 7) doesn't save the character set and SGR attributes
4 participants