Repository: microsoft/terminal (language: cpp)

Issue:
"Open windows from a previous session" not working due to session not being saved at exit
### Windows Terminal version

1.23.10353.0 (Preview) / 1.24.352.0 (Canary

### Windows build number

[Environment]::OSVersion

### Other Software

_No response_

### Steps to reproduce

1. Install Windows Terminal Preview or Windows Terminal Canary
2. Launch Windows Terminal Preview/Canary
3. Launch Settings and set **Startup** > **When Terminal starts** to _Open windows from a previous session_
4. Open up one or more additional tabs/windows
5. (Optional) Do things in any of the open tabs/windows
6. Close Windows Terminal Preview/Canary using the Close (X) window button or "Close all windows" from the command palette
7. Relaunch Windows Terminal Preview/Canary

### Expected Behavior

Previously opened tabs/windows are restored, including previous session history for each 

### Actual Behavior

Windows Terminal opens a single window with a single tab and launches a new instance of my default profile without any session history from the previous instance.


Discussion / hints:
Comparing the behavior to how the release version of Windows Terminal works, it looks like something is preventing **persistedWindowLayouts** in _state.json_ from being populated. 

When I close Windows Terminal with multiple tabs open, _state.json_ looks like this: 

```json
{
	"dismissedMessages" : 
	[
		"closeOnExitInfo"
	],
	"generatedProfiles" : 
	[
		"{574e775e-4f2a-5b96-ac1e-a2962a402336}",
		"{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
		"{d8e96812-b789-5068-a5ae-10b2fb53e95f}",
		"{963ff2f7-6aed-5ce3-9d91-90d99571f53a}",
		"{2ece5bfe-50ed-5f3a-ab87-5cd4baafed2b}",
		"{bc2da056-dd06-5879-b969-1dfcefc8d2c7}",
		"{47302f9c-1ac4-566c-aa3e-8cf29889d6ab}",
		"{b453ae62-4e3d-5e58-b989-0a998ec441b8}"
	],
	"persistedWindowLayouts" : 
	[
		{
			"initialPosition" : "104,104",
			"initialSize" : 
			{
				"height" : 852,
				"width" : 1517
			},
			"launchMode" : "default",
			"tabLayout" : 
			[
				{
					"action" : "newTab",
					"commandline" : "\"C:\\Program Files\\PowerShell\\7\\pwsh.exe\"",
					"profile" : "PowerShell",
					"sessionId" : "{redacted}",
					"startingDirectory" : "C:\\Users\\Username",
					"suppressApplicationTitle" : false,
					"tabTitle" : "PowerShell"
				},
				{
					"action" : "newTab",
					"commandline" : "ubuntu2404.exe",
					"profile" : "Ubuntu 24.04.1 LTS",
					"sessionId" : "{redacted}",
					"startingDirectory" : null,
					"suppressApplicationTitle" : false,
					"tabTitle" : "Ubuntu 24.04.1 LTS"
				},
				{
					"action" : "switchToTab",
					"index" : 0
				}
			]
		}
	],
	"settingsHash" : "{redacted}"
}
```

When I do the same with either Preview or Canary, the saved _state.json_ looks like this: 

``` json
{
	"persistedWindowLayouts" : [],
	"settingsHash" : "{redacted}"
}
```
I implemented "Close all windows" as closing all windows. When persistence is enabled, it should turn into shutting down the application immediately after showing the dialog.

To work around this, you can open the command palette (Ctrl+Shift+P) and type "quit" ("Quit the terminal").
(FWIW: we'll fix this!)
@lhecker Thanks for the quick response and workaround! I updated my description to clarify this only happens with the close button/"Close all windows" action, for anyone else who finds this.

@DHowett  Thanks for the clarification! I assumed as much, otherwise it would've been closed as WONTFIX. :)
As a more comfortable workaround, I tried to add a keyboard shortcut to the action "Quit the Terminal". This didn't work; the terminal didn't quit. This may be related.
I'm not sure if I have the same exact issue, but only some of my panes get saved. It seems ones created by duplicating (alt+shift+enter) are not saved.

Fix patch:
diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp
index f5a646e991f..6a25256af6f 100644
--- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp
+++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp
@@ -354,7 +354,10 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
         }
 
         // If we created no windows, e.g. because the args are "/?" we can just exit now.
-        _postQuitMessageIfNeeded();
+        if (_windows.empty())
+        {
+            _postQuitMessageIfNeeded();
+        }
     }
 
     // ALWAYS change the _real_ CWD of the Terminal to system32,
@@ -735,9 +738,30 @@ void WindowEmperor::_createMessageWindow(const wchar_t* className)
     StringCchCopy(_notificationIcon.szTip, ARRAYSIZE(_notificationIcon.szTip), appNameLoc.c_str());
 }
 
+// Counterpart to _postQuitMessageIfNeeded:
+// If it returns true, don't close that last window, if any.
+// This ensures we persist the last window.
+bool WindowEmperor::_shouldSkipClosingWindows() const
+{
+    const auto globalSettings = _app.Logic().Settings().GlobalSettings();
+    const size_t windowLimit = globalSettings.ShouldUsePersistedLayout() ? 1 : 0;
+    return _windows.size() <= windowLimit;
+}
+
+// Posts a WM_QUIT as soon as we have no reason to exist anymore.
+// That basically means no windows [^1] and no message boxes.
+//
+// [^1] Unless:
+// * We've been asked to persist the last remaining window
+//   in which case we exit with 1 remaining window.
+// * We're allowed to be headless
+//   in which case we never exit.
 void WindowEmperor::_postQuitMessageIfNeeded() const
 {
-    if (_messageBoxCount <= 0 && _windows.empty() && !_app.Logic().Settings().GlobalSettings().AllowHeadless())
+    const auto globalSettings = _app.Logic().Settings().GlobalSettings();
+    const size_t windowLimit = globalSettings.ShouldUsePersistedLayout() ? 1 : 0;
+
+    if (_messageBoxCount <= 0 && _windows.size() <= windowLimit && !globalSettings.AllowHeadless())
     {
         PostQuitMessage(0);
     }
@@ -771,17 +795,20 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
         {
         case WM_CLOSE_TERMINAL_WINDOW:
         {
-            const auto host = reinterpret_cast<AppHost*>(lParam);
-            auto it = _windows.begin();
-            const auto end = _windows.end();
-
-            for (; it != end; ++it)
+            if (!_shouldSkipClosingWindows())
             {
-                if (host == it->get())
+                const auto host = reinterpret_cast<AppHost*>(lParam);
+                auto it = _windows.begin();
+                const auto end = _windows.end();
+
+                for (; it != end; ++it)
                 {
-                    host->Close();
-                    _windows.erase(it);
-                    break;
+                    if (host == it->get())
+                    {
+                        host->Close();
+                        _windows.erase(it);
+                        break;
+                    }
                 }
             }
 
diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h
index ca30159ac2d..7012a7d66ee 100644
--- a/src/cascadia/WindowsTerminal/WindowEmperor.h
+++ b/src/cascadia/WindowsTerminal/WindowEmperor.h
@@ -55,6 +55,7 @@ class WindowEmperor
     safe_void_coroutine _dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args);
     LRESULT _messageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept;
     void _createMessageWindow(const wchar_t* className);
+    bool _shouldSkipClosingWindows() const;
     void _postQuitMessageIfNeeded() const;
     safe_void_coroutine _showMessageBox(winrt::hstring message, bool error);
     void _notificationAreaMenuRequested(WPARAM wParam);


Test patch: