Persist window layout every few minutes (#18898)

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
This commit is contained in:
Carlos Zamora 2025-05-14 11:08:07 -07:00 committed by GitHub
parent 9e0ca3aac0
commit f769597d89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 39 additions and 13 deletions

View File

@ -8,7 +8,8 @@ namespace TerminalApp
None, None,
Content, Content,
MovePane, MovePane,
Persist, PersistLayout,
PersistAll
}; };
runtimeclass BellEventArgs runtimeclass BellEventArgs

View File

@ -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. // 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. // We shouldn't persist such windows.
@ -1968,7 +1968,7 @@ namespace winrt::TerminalApp::implementation
for (auto tab : _tabs) for (auto tab : _tabs)
{ {
auto t = winrt::get_self<implementation::TabBase>(tab); auto t = winrt::get_self<implementation::TabBase>(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())); actions.insert(actions.end(), std::make_move_iterator(tabActions.begin()), std::make_move_iterator(tabActions.end()));
} }

View File

@ -113,7 +113,7 @@ namespace winrt::TerminalApp::implementation
safe_void_coroutine RequestQuit(); safe_void_coroutine RequestQuit();
safe_void_coroutine CloseWindow(); safe_void_coroutine CloseWindow();
void PersistState(); void PersistState(bool serializeBuffer);
void ToggleFocusMode(); void ToggleFocusMode();
void ToggleFullscreen(); void ToggleFullscreen();

View File

@ -141,7 +141,7 @@ namespace winrt::TerminalApp::implementation
// "attach existing" rather than a "create" // "attach existing" rather than a "create"
args.ContentId(_control.ContentId()); args.ContentId(_control.ContentId());
break; break;
case BuildStartupKind::Persist: case BuildStartupKind::PersistAll:
{ {
const auto connection = _control.Connection(); const auto connection = _control.Connection();
const auto id = connection ? connection.SessionId() : winrt::guid{}; const auto id = connection ? connection.SessionId() : winrt::guid{};
@ -156,6 +156,7 @@ namespace winrt::TerminalApp::implementation
} }
break; break;
} }
case BuildStartupKind::PersistLayout:
default: default:
break; break;
} }

View File

@ -266,11 +266,11 @@ namespace winrt::TerminalApp::implementation
AppLogic::Current()->NotifyRootInitialized(); AppLogic::Current()->NotifyRootInitialized();
} }
void TerminalWindow::PersistState() void TerminalWindow::PersistState(bool serializeBuffer)
{ {
if (_root) if (_root)
{ {
_root->PersistState(); _root->PersistState(serializeBuffer);
} }
} }

View File

@ -71,7 +71,7 @@ namespace winrt::TerminalApp::implementation
void Create(); void Create();
void PersistState(); void PersistState(bool serializeBuffer);
void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args);

View File

@ -59,7 +59,7 @@ namespace TerminalApp
Boolean ShouldImmediatelyHandoffToElevated(); Boolean ShouldImmediatelyHandoffToElevated();
void HandoffToElevated(); void HandoffToElevated();
void PersistState(); void PersistState(Boolean serializeBuffer);
Windows.UI.Xaml.UIElement GetRoot(); Windows.UI.Xaml.UIElement GetRoot();

View File

@ -314,6 +314,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
_createMessageWindow(windowClassName.c_str()); _createMessageWindow(windowClassName.c_str());
_setupGlobalHotkeys(); _setupGlobalHotkeys();
_checkWindowsForNotificationIcon(); _checkWindowsForNotificationIcon();
_setupSessionPersistence(_app.Logic().Settings().GlobalSettings().ShouldUsePersistedLayout());
// When the settings change, we'll want to update our global hotkeys // When the settings change, we'll want to update our global hotkeys
// and our notification icon based on the new settings. // and our notification icon based on the new settings.
@ -323,6 +324,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
_assertIsMainThread(); _assertIsMainThread();
_setupGlobalHotkeys(); _setupGlobalHotkeys();
_checkWindowsForNotificationIcon(); _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); 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. // Calling an `ApplicationState` setter triggers a write to state.json.
// With this if condition we avoid an unnecessary write when persistence is disabled. // With this if condition we avoid an unnecessary write when persistence is disabled.
if (state.PersistedWindowLayouts()) if (state.PersistedWindowLayouts())
@ -936,12 +950,19 @@ void WindowEmperor::_finalizeSessionPersistence() const
{ {
for (const auto& w : _windows) 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(); state.Flush();
}
void WindowEmperor::_finalizeSessionPersistence() const
{
const auto state = ApplicationState::SharedInstance();
_persistState(state, true);
if (_needsPersistenceCleanup) if (_needsPersistenceCleanup)
{ {

View File

@ -64,6 +64,8 @@ private:
void _registerHotKey(int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept; void _registerHotKey(int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept;
void _unregisterHotKey(int index) noexcept; void _unregisterHotKey(int index) noexcept;
void _setupGlobalHotkeys(); void _setupGlobalHotkeys();
void _setupSessionPersistence(bool enabled);
void _persistState(const winrt::Microsoft::Terminal::Settings::Model::ApplicationState& state, bool serializeBuffer) const;
void _finalizeSessionPersistence() const; void _finalizeSessionPersistence() const;
void _checkWindowsForNotificationIcon(); void _checkWindowsForNotificationIcon();
@ -77,6 +79,7 @@ private:
bool _notificationIconShown = false; bool _notificationIconShown = false;
bool _forcePersistence = false; bool _forcePersistence = false;
bool _needsPersistenceCleanup = false; bool _needsPersistenceCleanup = false;
SafeDispatcherTimer _persistStateTimer;
std::optional<bool> _currentSystemThemeIsDark; std::optional<bool> _currentSystemThemeIsDark;
int32_t _windowCount = 0; int32_t _windowCount = 0;
int32_t _messageBoxCount = 0; int32_t _messageBoxCount = 0;