From f769597d8914fbff88db3d72cff1d7b89334d4a8 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 14 May 2025 11:08:07 -0700 Subject: [PATCH] Persist window layout every few minutes (#18898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If `persistedWindowLayout` is enabled, we now persist the window layout every few minutes (excluding the text buffer). This was done by adding a `SafeDispatcherTimer` to the `WindowEmperor` that calls `PersistState()` every 5 minutes. For `BuildStartupKind`, I split up `Persist` into `PersistAll` and `PersistLayout`. This way, we go through all the same code flow that `Persist` had except for specifically serializing the buffer. ## Validation Steps Performed ✅ (with the timer set to 3 seconds) create a window layout and ensure the layout is restored after forcefully stopping Terminal (aka simulating a "crash") Closes #18838 --- src/cascadia/TerminalApp/IPaneContent.idl | 3 +- src/cascadia/TerminalApp/TerminalPage.cpp | 4 +-- src/cascadia/TerminalApp/TerminalPage.h | 2 +- .../TerminalApp/TerminalPaneContent.cpp | 3 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 4 +-- src/cascadia/TerminalApp/TerminalWindow.h | 2 +- src/cascadia/TerminalApp/TerminalWindow.idl | 2 +- .../WindowsTerminal/WindowEmperor.cpp | 29 ++++++++++++++++--- src/cascadia/WindowsTerminal/WindowEmperor.h | 3 ++ 9 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl index f4c6ce1395..ea2e900ca6 100644 --- a/src/cascadia/TerminalApp/IPaneContent.idl +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -8,7 +8,8 @@ namespace TerminalApp None, Content, MovePane, - Persist, + PersistLayout, + PersistAll }; runtimeclass BellEventArgs diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 581b0ddcf3..135e7cca94 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1953,7 +1953,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalPage::PersistState() + void TerminalPage::PersistState(bool serializeBuffer) { // This method may be called for a window even if it hasn't had a tab yet or lost all of them. // We shouldn't persist such windows. @@ -1968,7 +1968,7 @@ namespace winrt::TerminalApp::implementation for (auto tab : _tabs) { auto t = winrt::get_self(tab); - auto tabActions = t->BuildStartupActions(BuildStartupKind::Persist); + auto tabActions = t->BuildStartupActions(serializeBuffer ? BuildStartupKind::PersistAll : BuildStartupKind::PersistLayout); actions.insert(actions.end(), std::make_move_iterator(tabActions.begin()), std::make_move_iterator(tabActions.end())); } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 9837f9fe61..b369bd920e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -113,7 +113,7 @@ namespace winrt::TerminalApp::implementation safe_void_coroutine RequestQuit(); safe_void_coroutine CloseWindow(); - void PersistState(); + void PersistState(bool serializeBuffer); void ToggleFocusMode(); void ToggleFullscreen(); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 1ab5cd013c..c85009ea0c 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -141,7 +141,7 @@ namespace winrt::TerminalApp::implementation // "attach existing" rather than a "create" args.ContentId(_control.ContentId()); break; - case BuildStartupKind::Persist: + case BuildStartupKind::PersistAll: { const auto connection = _control.Connection(); const auto id = connection ? connection.SessionId() : winrt::guid{}; @@ -156,6 +156,7 @@ namespace winrt::TerminalApp::implementation } break; } + case BuildStartupKind::PersistLayout: default: break; } diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 9776679691..e1fd1741d1 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -266,11 +266,11 @@ namespace winrt::TerminalApp::implementation AppLogic::Current()->NotifyRootInitialized(); } - void TerminalWindow::PersistState() + void TerminalWindow::PersistState(bool serializeBuffer) { if (_root) { - _root->PersistState(); + _root->PersistState(serializeBuffer); } } diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 6e37e17947..fd27617846 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -71,7 +71,7 @@ namespace winrt::TerminalApp::implementation void Create(); - void PersistState(); + void PersistState(bool serializeBuffer); void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index a8fa0c97c5..baf58317df 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -59,7 +59,7 @@ namespace TerminalApp Boolean ShouldImmediatelyHandoffToElevated(); void HandoffToElevated(); - void PersistState(); + void PersistState(Boolean serializeBuffer); Windows.UI.Xaml.UIElement GetRoot(); diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 381b7f4c8e..cdf34d9a3b 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -314,6 +314,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) _createMessageWindow(windowClassName.c_str()); _setupGlobalHotkeys(); _checkWindowsForNotificationIcon(); + _setupSessionPersistence(_app.Logic().Settings().GlobalSettings().ShouldUsePersistedLayout()); // When the settings change, we'll want to update our global hotkeys // and our notification icon based on the new settings. @@ -323,6 +324,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) _assertIsMainThread(); _setupGlobalHotkeys(); _checkWindowsForNotificationIcon(); + _setupSessionPersistence(args.NewSettings().GlobalSettings().ShouldUsePersistedLayout()); } }); @@ -921,10 +923,22 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c return DefWindowProcW(window, message, wParam, lParam); } -void WindowEmperor::_finalizeSessionPersistence() const +void WindowEmperor::_setupSessionPersistence(bool enabled) { - const auto state = ApplicationState::SharedInstance(); + if (!enabled) + { + _persistStateTimer.Stop(); + return; + } + _persistStateTimer.Interval(std::chrono::minutes(5)); + _persistStateTimer.Tick([&](auto&&, auto&&) { + _persistState(ApplicationState::SharedInstance(), false); + }); + _persistStateTimer.Start(); +} +void WindowEmperor::_persistState(const ApplicationState& state, bool serializeBuffer) const +{ // Calling an `ApplicationState` setter triggers a write to state.json. // With this if condition we avoid an unnecessary write when persistence is disabled. if (state.PersistedWindowLayouts()) @@ -936,12 +950,19 @@ void WindowEmperor::_finalizeSessionPersistence() const { for (const auto& w : _windows) { - w->Logic().PersistState(); + w->Logic().PersistState(serializeBuffer); } } - // Ensure to write the state.json before we TerminateProcess() + // Ensure to write the state.json state.Flush(); +} + +void WindowEmperor::_finalizeSessionPersistence() const +{ + const auto state = ApplicationState::SharedInstance(); + + _persistState(state, true); if (_needsPersistenceCleanup) { diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 3024ab7a2c..bf63214030 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -64,6 +64,8 @@ private: void _registerHotKey(int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept; void _unregisterHotKey(int index) noexcept; void _setupGlobalHotkeys(); + void _setupSessionPersistence(bool enabled); + void _persistState(const winrt::Microsoft::Terminal::Settings::Model::ApplicationState& state, bool serializeBuffer) const; void _finalizeSessionPersistence() const; void _checkWindowsForNotificationIcon(); @@ -77,6 +79,7 @@ private: bool _notificationIconShown = false; bool _forcePersistence = false; bool _needsPersistenceCleanup = false; + SafeDispatcherTimer _persistStateTimer; std::optional _currentSystemThemeIsDark; int32_t _windowCount = 0; int32_t _messageBoxCount = 0;