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,
Content,
MovePane,
Persist,
PersistLayout,
PersistAll
};
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.
// We shouldn't persist such windows.
@ -1968,7 +1968,7 @@ namespace winrt::TerminalApp::implementation
for (auto tab : _tabs)
{
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()));
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -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)
{

View File

@ -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<bool> _currentSystemThemeIsDark;
int32_t _windowCount = 0;
int32_t _messageBoxCount = 0;