Skip to content

Commit

Permalink
Persist window layout on window close (#10972)
Browse files Browse the repository at this point in the history
This commit adds initial support for saving window layout on application
close.

Done:
- Add user setting for if tabs should be maintained.
- Added events to track the number of open windows for the monarch, and
  then save if you are the last window closing.
- Saves layout when the user explicitly hits the "Close Window" button.
- If the user manually closed all of their tabs (through the tab x
  button or through closing all panes on the tab) then remove any saved
  state.
- Saves in the ApplicationState file a list of actions the terminal can
  perform to restore its layout and the window size/position
  information.
- This saves an action to focus the correct pane, but this won't
  actually work without #10978. Note that if you have a pane zoomed, it
  does still zoom the correct pane, but when you unzoom it will have a
  different pane selected.

Todo:
- multiple windows? Right now it can only handle loading/saving one
  window.
   - PR #11083 will save multiple windows.
- This also sometimes runs into the existing bug where multiple tabs
  appear to be focused on opening.

Next Steps:
- The business logic of when the save is triggered can be adjusted as
  necessary.
- Right now I am taking the pragmatic approach and just saving the state
  as an array of objects, but only ever populate it with 1, that way
  saving multiple windows in the future could be added without breaking
  schema compatibility. Selfishly I'm hoping that handling multiple
  windows could be spun off into another pr/feature for now.
- One possible thing that can maybe be done is that the commandline can
  be augmented with a "--saved ##" attribute that would load from the
  nth saved state if it exists. e.g. if there are 3 saved windows, on
  first load it can spawn three wt --saved {0,1,2} that would reopen the
  windows? This way there also exists a way to load a copy of a previous
  window (if it is in the saved state).
- Is the application state something that is planned to be public/user
  editable? In theory the user could since it is just json, but I don't
  know what it buys them over just modifying their settings and
  startupActions.

Validation Steps Performed:
- The happy path: open terminal -> set setting to true -> close terminal
  -> reopen and see tabs. Tested with powershell/cmd/wsl windows.
- That closing all panes/tabs on their own will remove the saved
  session.
- Open multiple windows, close windows and confirm that the last window
  closed saves its state.

The generated file stores a sequence of actions that will be executed to
restore the terminal to its saved form.

References #8324
This is also one of the items on #5000
Closes #766
  • Loading branch information
Rosefield authored Sep 8, 2021
1 parent 0a48836 commit 13e9546
Show file tree
Hide file tree
Showing 41 changed files with 865 additions and 168 deletions.
9 changes: 9 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,15 @@
"description": "When set to true, this enables the launch of Windows Terminal at startup. Setting this to false will disable the startup task entry. If the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect.",
"type": "boolean"
},
"firstWindowPreference": {
"default": "defaultProfile",
"description": "Defines what behavior the terminal takes when it starts. \"defaultProfile\" will have the terminal launch with one tab of the default profile, and \"persistedWindowLayout\" will cause the terminal to save its layout on close and reload it on open.",
"enum": [
"defaultProfile",
"persistedWindowLayout"
],
"type": "string"
},
"launchMode": {
"default": "default",
"description": "Defines whether the terminal will launch as maximized, full screen, or in a window. Setting this to \"focus\" is equivalent to launching the terminal in the \"default\" mode, but with the focus mode enabled. Similar, setting this to \"maximizedFocus\" will result in launching the terminal in a maximized window with the focus mode enabled.",
Expand Down
41 changes: 41 additions & 0 deletions src/cascadia/Remoting/Monarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingUInt64(newPeasantsId, "peasantID", "the ID of the new peasant"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));

_WindowCreatedHandlers(nullptr, nullptr);
return newPeasantsId;
}
catch (...)
Expand All @@ -107,6 +109,45 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}

// Method Description:
// - Tells the monarch that a peasant is being closed.
// Arguments:
// - peasantId: the id of the peasant
// Return Value:
// - <none>
void Monarch::SignalClose(const uint64_t peasantId)
{
_peasants.erase(peasantId);
_WindowClosedHandlers(nullptr, nullptr);
}

// Method Description:
// - Counts the number of living peasants.
// Arguments:
// - <none>
// Return Value:
// - the number of active peasants.
uint64_t Monarch::GetNumberOfPeasants()
{
auto num = 0;
auto callback = [&](const auto& /*id*/, const auto& p) {
// Check that the peasant is alive, and if so increment the count
p.GetID();
num += 1;
};
auto onError = [](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_GetNumberOfPeasants_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not enumerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};

_forEachPeasant(callback, onError);

return num;
}

// Method Description:
// - Event handler for the Peasant::WindowActivated event. Used as an
// opportunity for us to update our internal stack of the "most recent
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/Remoting/Monarch.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
uint64_t GetPID();

uint64_t AddPeasant(winrt::Microsoft::Terminal::Remoting::IPeasant peasant);
void SignalClose(const uint64_t peasantId);

uint64_t GetNumberOfPeasants();

winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
Expand All @@ -59,6 +62,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

private:
uint64_t _ourPID;
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/Remoting/Monarch.idl
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ namespace Microsoft.Terminal.Remoting

UInt64 GetPID();
UInt64 AddPeasant(IPeasant peasant);
UInt64 GetNumberOfPeasants();
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
void HandleActivatePeasant(WindowActivatedArgs args);
void SummonWindow(SummonWindowSelectionArgs args);
void SignalClose(UInt64 peasantId);

void SummonAllWindows();
Boolean DoesQuakeWindowExist();
Expand All @@ -54,5 +56,7 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
};
}
30 changes: 29 additions & 1 deletion src/cascadia/Remoting/WindowManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// monarch!
CoRevokeClassObject(_registrationHostClass);
_registrationHostClass = 0;
SignalClose();
_monarchWaitInterrupt.SetEvent();

// A thread is joinable once it's been started. Basically this just
Expand All @@ -64,6 +65,18 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}

void WindowManager::SignalClose()
{
if (_monarch)
{
try
{
_monarch.SignalClose(_peasant.GetID());
}
CATCH_LOG()
}
}

void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
{
// If we're the king, we _definitely_ want to process the arguments, we were
Expand Down Expand Up @@ -250,9 +263,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// Here, we're the king!
//
// This is where you should do any additional setup that might need to be
// done when we become the king. THis will be called both for the first
// done when we become the king. This will be called both for the first
// window, and when the current monarch dies.

_monarch.WindowCreated({ get_weak(), &WindowManager::_WindowCreatedHandlers });
_monarch.WindowClosed({ get_weak(), &WindowManager::_WindowClosedHandlers });
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
_monarch.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
Expand Down Expand Up @@ -526,6 +541,19 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return _monarch.GetPeasantInfos();
}

uint64_t WindowManager::GetNumberOfPeasants()
{
if (_monarch)
{
try
{
return _monarch.GetNumberOfPeasants();
}
CATCH_LOG()
}
return 0;
}

// Method Description:
// - Ask the monarch to show a tray icon.
// Arguments:
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/Remoting/WindowManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
bool IsMonarch();
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SignalClose();

void SummonAllWindows();
uint64_t GetNumberOfPeasants();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();

winrt::fire_and_forget RequestShowTrayIcon();
Expand All @@ -50,6 +52,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation

TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/Remoting/WindowManager.idl
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ namespace Microsoft.Terminal.Remoting
{
WindowManager();
void ProposeCommandline(CommandlineArgs args);
void SignalClose();
Boolean ShouldCreateWindow { get; };
IPeasant CurrentWindow();
Boolean IsMonarch { get; };
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
UInt64 GetNumberOfPeasants();
void UpdateActiveTabTitle(String title);
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
};
Expand Down
47 changes: 42 additions & 5 deletions src/cascadia/TerminalApp/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -596,12 +596,30 @@ namespace winrt::TerminalApp::implementation
LoadSettings();
}

// Use the default profile to determine how big of a window we need.
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, nullptr, nullptr) };

auto proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(), dpi);
winrt::Windows::Foundation::Size proposedSize{};

const float scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
if (_root->ShouldUsePersistedLayout(_settings))
{
const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();

if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialSize())
{
proposedSize = layouts.GetAt(0).InitialSize().Value();
// The size is saved as a non-scaled real pixel size,
// so we need to scale it appropriately.
proposedSize.Height = proposedSize.Height * scale;
proposedSize.Width = proposedSize.Width * scale;
}
}

if (proposedSize.Width == 0 && proposedSize.Height == 0)
{
// Use the default profile to determine how big of a window we need.
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, nullptr, nullptr) };

proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(), dpi);
}

// GH#2061 - If the global setting "Always show tab bar" is
// set or if "Show tabs in title bar" is set, then we'll need to add
Expand Down Expand Up @@ -683,7 +701,18 @@ namespace winrt::TerminalApp::implementation
LoadSettings();
}

const auto initialPosition{ _settings.GlobalSettings().InitialPosition() };
auto initialPosition{ _settings.GlobalSettings().InitialPosition() };

if (_root->ShouldUsePersistedLayout(_settings))
{
const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();

if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialPosition())
{
initialPosition = layouts.GetAt(0).InitialPosition().Value();
}
}

return {
initialPosition.X ? initialPosition.X.Value() : defaultInitialX,
initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY
Expand Down Expand Up @@ -1429,6 +1458,14 @@ namespace winrt::TerminalApp::implementation
}
}

void AppLogic::SetNumberOfOpenWindows(const uint64_t num)
{
if (_root)
{
_root->SetNumberOfOpenWindows(num);
}
}

void AppLogic::RenameFailed()
{
if (_root)
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ namespace winrt::TerminalApp::implementation
void WindowName(const winrt::hstring& name);
uint64_t WindowId();
void WindowId(const uint64_t& id);
void SetNumberOfOpenWindows(const uint64_t num);
bool IsQuakeWindow() const noexcept;

Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi);
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.idl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ namespace TerminalApp
void IdentifyWindow();
String WindowName;
UInt64 WindowId;
void SetNumberOfOpenWindows(UInt64 num);
void RenameFailed();
Boolean IsQuakeWindow();

Expand Down
Loading

0 comments on commit 13e9546

Please sign in to comment.