-
Notifications
You must be signed in to change notification settings - Fork 8.5k
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
Implement buffer restore #16598
Implement buffer restore #16598
Conversation
@@ -124,8 +124,7 @@ namespace winrt::TerminalApp::implementation | |||
return appLogic->GetSettings(); | |||
} | |||
|
|||
AppLogic::AppLogic() : | |||
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe not monitoring state.json
for changes anymore is important as it prevents disturbing the session state while session persistence is still ongoing. That's because when ApplicationState
flushes to disk, this FS monitor will be triggered and reload the ApplicationState
again.
I figured it's not a problem anymore to do this since we're effectively a singleton application now.
This contains all the parts of #16598 that aren't specific to session restore, but are required for the code in #16598: * Adds new GUID<>String functions that remove the `{}` brackets. * Adds `SessionId` to the `ITerminalConnection` interface. * Flush the `ApplicationState` before we terminate the process. * Not monitoring `state.json` for changes is important as it prevents disturbing the session state while session persistence is ongoing. That's because when `ApplicationState` flushes to disk, the FS monitor will be triggered and reload the `ApplicationState` again.
5dbb09a
to
f9511d9
Compare
notes:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of this I'm cool with, but I have a couple last questions. Honestly, not as scary as I expected
@@ -1713,6 +1695,85 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |||
} | |||
} | |||
|
|||
void ControlCore::PersistToPath(const wchar_t* path) const | |||
{ | |||
const wil::unique_handle file{ CreateFileW(path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kinda weird to me that the actual mechanics of reading/writing the file are down at the level of the control.
Benefit is that the file contents don't need to ever traverse a winrt boundary. And theoretically, anyone using a TermControl could then also get fairly trivial session restore from a file.
Downsides: we can't reuse our file loader (that Dustin's moving in #15329 so maybe this isn't important), and should other consumers get this? I suppose why not?
Okay I think I'm on board
{ | ||
const auto settingsDir = CascadiaSettings::SettingsDirectory(); | ||
const auto idStr = Utils::GuidToPlainString(id); | ||
const auto path = fmt::format(FMT_COMPILE(L"{}\\buffer_{}.txt"), settingsDir, idStr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some part of me wants to abstract out this fmt::format
call to a common util lib, so that the app and control don't need to be updated in parallel if we change this, but also meh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I also felt a bit disgusted about putting this here, especially when considering the counterpart in WindowEmperor
. But I couldn't come up with a good way to put it anywhere else. I think the saving grace of this code is that it's at least encapsulated to just 3 "small" sections of TerminalPage
and WindowEmperor
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
funny enough, it was my plan to add support for automatic recordings which would use state/sessionIdGuid files!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm commander Shepard, and this is my favorite release of the Terminal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I am exceptionally cool with this. Everything makes sense, it simplifies the state persistence logic severely, and it gives us a generic TextBuffer -> VT function (!)
src/buffer/out/TextColor.h
Outdated
return _index; | ||
} | ||
|
||
BYTE GetIndex() const noexcept; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dumb question - does moving this out of the header harm the inlineability of it? does LTCG largely help with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inline
isn't a hint for inlining anymore, so it shouldn't make any difference. LTCG makes no difference either I think, because if anything it's probably inlined during initial compilation already due to its small size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I don't mean because of the inline
keyword. Method bodies in header files can be inlined automatically, by the compiler, without the linker's help (LTCG). If you move the body into the cpp file, it can't be inlined during initial compilation because the compiler can only see the body from a single translation unit. It would require LTCG to come through and inline it at the call sites, now, because the other translation units couldn't see the body at compile time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good point. This would be a good reason to move it back to the header.
@@ -340,6 +337,9 @@ | |||
<data name="CmdProfileArgDesc" xml:space="preserve"> | |||
<value>Open with the given profile. Accepts either the name or GUID of a profile</value> | |||
</data> | |||
<data name="CmdSessionIdArgDesc" xml:space="preserve"> | |||
<value>Sets the WT_SESSION variable</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<value>Sets the WT_SESSION variable</value> | |
<value>Sets the WT_SESSION variable; must be a GUID</value> |
maybe? or, is there an opportunity for like, yeeting an environment variable injection into this arg?
{ | ||
const auto settingsDir = CascadiaSettings::SettingsDirectory(); | ||
const auto idStr = Utils::GuidToPlainString(id); | ||
const auto path = fmt::format(FMT_COMPILE(L"{}\\buffer_{}.txt"), settingsDir, idStr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
funny enough, it was my plan to add support for automatic recordings which would use state/sessionIdGuid files!
} | ||
} | ||
|
||
const auto lock = _terminal->LockForReading(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the read lock, should it be the write lock?
@@ -32,7 +32,8 @@ namespace Microsoft.Terminal.Control | |||
String ProfileName; | |||
String ProfileSource; | |||
|
|||
Boolean EnableUnfocusedAcrylic; | |||
Boolean EnableUnfocusedAcrylic { get; }; | |||
Guid SessionId { get; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see this one get used; do we use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, via the TerminalSettings
object to get the sessionId
into the connection.
@@ -1244,6 +1244,13 @@ winrt::hstring CascadiaSettings::_calculateHash(std::string_view settings, const | |||
return winrt::hstring{ hash }; | |||
} | |||
|
|||
// This returns something akin to %LOCALAPPDATA%\Packages\WindowsTerminalDev_8wekyb3d8bbwe\LocalState | |||
// just like SettingsPath(), but without the trailing \settings.json. | |||
winrt::hstring CascadiaSettings::SettingsDirectory() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh dang! @zadjii-msft you could have used this for WT_SETTINGS_DIR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THATS WHY I thought this method already existed!
si.cb = sizeof(si); | ||
wil::unique_process_information pi; | ||
|
||
LOG_IF_WIN32_BOOL_FALSE(CreateProcessW(nullptr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
holy sh--, multi-window restore starts WindowsTerminal.exe which then gets a handle to our Monarch and sends it a commandline.
What the heck.
We could just... use Monarch directly here. I know you didn't write this code but I think we missed it when we migrated to single-proc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup. I felt like it would be too off-topic if I changed this part within the PR. But I agree that it's ripe for a refactoring.
FYI the merge commit is non-trivial. If you do want to review my latest changes, you may have to review the merge as well. |
static constexpr Mapping mappings[] = { | ||
{ CharacterAttributes::Intense, { 22, 1 } }, | ||
{ CharacterAttributes::Italics, { 23, 3 } }, | ||
{ CharacterAttributes::Blinking, { 25, 5 } }, | ||
{ CharacterAttributes::Invisible, { 28, 8 } }, | ||
{ CharacterAttributes::CrossedOut, { 29, 9 } }, | ||
{ CharacterAttributes::Faint, { 22, 2 } }, | ||
{ CharacterAttributes::TopGridline, { 55, 53 } }, | ||
{ CharacterAttributes::ReverseVideo, { 27, 7 } }, | ||
{ CharacterAttributes::BottomGridline, { 24, 4 } }, | ||
}; | ||
for (const auto& mapping : mappings) | ||
{ | ||
if (WI_IsAnyFlagSet(attrDelta, mapping.attr)) | ||
{ | ||
const auto n = til::at(mapping.change, WI_IsAnyFlagSet(attr, mapping.attr)); | ||
fmt::format_to(std::back_inserter(buffer), FMT_COMPILE(L"\x1b[{}m"), n); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this will work correctly when mixing intense and faint. For example, a sequence like \e[1mBOLD\e[2mBOTH\e[0;2mFAINT
will assumedly serialize as \e[1mBOLD\e[2mBOTH\e[22mFAINT
, which isn't correct (the FAINT
text won't be faint).
It's also worth mentioning that BottomGridLine
isn't the same as underline - we don't have a VT mapping for that. So AFAIK, it shouldn't ever show up in the Windows Terminal buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I didn't even notice that I used 22
twice. Thanks for noticing this!
I've opened #16970 which should fix both issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine dealing with the intense/faint thing in post.
This got lost in #16598. `TerminalPage` needs to ask the window where the it actually is, so it can persist it. More details in #16598 (comment) Closes #17010
This contains all the changes of 5dda507, c4c5206, 9f08ee7, and 5f10159 (#16598 and all related PRs), but without the buffer restore feature. The hope is that these changes fix some rarer issues we've been hearing about, where persistence doesn't work correctly. ## Validation Steps Performed This changeset was tested on Windows 11 with 2 windows and 4 tabs where 1 tab had 2 mixed split panes. All windows and tabs got restored properly. It didn't crash on Windows 10.
This got lost in #16598. `TerminalPage` needs to ask the window where the it actually is, so it can persist it. More details in #16598 (comment) Closes #17010 (cherry picked from commit 643f716) Service-Card-Id: 92360686 Service-Version: 1.20
This changeset allows Windows Terminal to dump its buffer contents as
UTF-16LE VT text onto disk and restore it later. This functionality is
enabled whenever
persistedWindowLayout
is being used.Closes #961
Closes #16741
Validation Steps Performed
Everything's restored ✅
RenderingTests.exe
Everything's restored ✅