From 405c8531876c44216a4ec626ffc4d7c8dffa87dd Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 01:45:17 -0400 Subject: [PATCH 01/37] GH766 Persist tab layout on window close - Add user setting for if tabs should be maintained (currently only supports the 1st window) - Saves in the ApplicationState file a list of actions the terminal can perform to restore its layout. --- src/cascadia/TerminalApp/Pane.cpp | 94 +++++++++++++++++++ src/cascadia/TerminalApp/Pane.h | 3 + src/cascadia/TerminalApp/TerminalPage.cpp | 62 ++++++++++++ src/cascadia/TerminalApp/TerminalPage.h | 2 + src/cascadia/TerminalApp/TerminalTab.cpp | 25 +++++ src/cascadia/TerminalApp/TerminalTab.h | 2 + .../TerminalSettingsEditor/Launch.xaml | 5 + .../Resources/en-US/Resources.resw | 8 ++ .../ApplicationState.cpp | 79 +++++++++++++++- .../TerminalSettingsModel/ApplicationState.h | 5 +- .../ApplicationState.idl | 3 + .../GlobalAppSettings.cpp | 5 + .../TerminalSettingsModel/GlobalAppSettings.h | 1 + .../GlobalAppSettings.idl | 1 + .../TerminalSettingsModel/JsonUtils.h | 51 ++++++++++ .../TerminalSettingsSerializationHelpers.h | 3 +- 16 files changed, 345 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index f78a1c61e3f..8de739e8ec2 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -71,6 +71,100 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus }); } +NewTerminalArgs Pane::GetTerminalArgsForPane() const +{ + // Leaves are the only things that have controls + assert(_IsLeaf()); + + NewTerminalArgs args{}; + auto controlSettings = _control.Settings().as(); + + args.Profile(controlSettings.ProfileName()); + args.StartingDirectory(controlSettings.StartingDirectory()); + args.TabTitle(controlSettings.StartingTitle()); + args.Commandline(controlSettings.Commandline()); + args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle()); + + //TODO: color scheme? + + return args; +} + +// Method Description: +// - Serializes the state of this tab as a series of commands that can be +// executed to recreate it. +// - This will always result in the right-most child being the focus +// after the commands finish executing. +// Arguments: +// - +// Return Value: +// - A vector of commands and the original root pane for this new tree +std::pair, std::shared_ptr> Pane::BuildStartupActions() +{ + // if we are a leaf then all there is to do is defer to the parent. + if (_IsLeaf()) + { + return { {}, shared_from_this() }; + } + + auto buildSplitPane = [&](auto newPane) { + ActionAndArgs actionAndArgs; + actionAndArgs.Action(ShortcutAction::SplitPane); + auto terminalArgs{ newPane->GetTerminalArgsForPane() }; + SplitPaneArgs args{ SplitType::Manual, _splitState, _desiredSplitPosition, terminalArgs }; + actionAndArgs.Args(args); + + return actionAndArgs; + }; + + auto buildMoveFocus = [](auto direction) { + MoveFocusArgs args{ direction }; + + ActionAndArgs actionAndArgs{}; + actionAndArgs.Action(ShortcutAction::MoveFocus); + actionAndArgs.Args(args); + + return actionAndArgs; + }; + + // Handle simple case of a single split (a minor optimization for clarity) + // Here we just create the second child (by splitting) and return the first + // child for the parent to deal with. + if (_firstChild->_IsLeaf() && _secondChild->_IsLeaf()) + { + auto actionAndArgs = buildSplitPane(_secondChild); + return { { actionAndArgs }, _firstChild }; + } + + // We now need to execute the commands for each side of the tree + auto [a1, p1] = _firstChild->BuildStartupActions(); + auto [a2, p2] = _secondChild->BuildStartupActions(); + + std::vector actions; + actions.reserve(a1.size() + a2.size() + 3); + + // first we make our split + auto newSplit = buildSplitPane(p2); + actions.push_back(newSplit); + + if (a1.size() > 0) + { + // Then move to the first child and execute any actions on the left branch + // then move back + actions.push_back(buildMoveFocus(FocusDirection::PreviousInOrder)); + actions.insert(actions.end(), a1.begin(), a1.end()); + actions.push_back(buildMoveFocus(FocusDirection::NextInOrder)); + } + + // And if there are any commands to run on the right branch do so + if (a2.size() > 0) + { + actions.insert(actions.end(), a2.begin(), a2.end()); + } + + return { { actions }, p1 }; +} + // Method Description: // - Update the size of this pane. Resizes each of our columns so they have the // same relative sizes, given the newSize. diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 510229cbc58..f5f3511bbb1 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -62,6 +62,9 @@ class Pane : public std::enable_shared_from_this void ClearActive(); void SetActive(); + std::pair, std::shared_ptr> BuildStartupActions(); + winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane() const; + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const GUID& profile); void ResizeContent(const winrt::Windows::Foundation::Size& newSize); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 558b71b8748..361d2842266 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -320,6 +320,19 @@ namespace winrt::TerminalApp::implementation if (_startupState == StartupState::NotInitialized) { _startupState = StartupState::InStartup; + + // If the user selected to save their tab layout, we are the first + // window opened, and wt was not run with any other arguments, then + // we should use the saved settings. + if (_settings.GlobalSettings().PersistTabLayout() && _WindowId == 1 && _startupActions.Size() == 1) + { + auto actions = ApplicationState::SharedInstance().PersistedTabLayout(); + if (actions && actions.Size() > 0) + { + _startupActions = actions; + } + } + ProcessStartupActions(_startupActions, true); // If we were told that the COM server needs to be started to listen for incoming @@ -1146,6 +1159,50 @@ namespace winrt::TerminalApp::implementation return nullptr; } + // Method Description: + // - Saves the tab layout to the application state + // Arguments: + // - + // Return Value: + // - + void TerminalPage::PersistTabLayout() + { + std::vector actions; + + for (auto tab : _tabs) + { + if (auto terminalTab = _GetTerminalTabImpl(tab)) + { + auto tabActions = terminalTab->BuildStartupActions(); + actions.insert(actions.end(), tabActions.begin(), tabActions.end()); + } + else if (tab.try_as()) + { + ActionAndArgs action; + action.Action(ShortcutAction::OpenSettings); + OpenSettingsArgs args{ SettingsTarget::SettingsUI }; + action.Args(args); + + actions.push_back(action); + } + } + + // if the focused tab was not the last tab, restore that + auto idx = _GetFocusedTabIndex(); + if (idx && idx != _tabs.Size() - 1) + { + ActionAndArgs action; + action.Action(ShortcutAction::SwitchToTab); + SwitchToTabArgs switchToTabArgs{ idx.value() }; + action.Args(switchToTabArgs); + + actions.push_back(action); + } + + auto state = ApplicationState::SharedInstance(); + state.PersistedTabLayout(winrt::single_threaded_vector(std::move(actions))); + } + // Method Description: // - Close the terminal app. If there is more // than one tab opened, show a warning dialog. @@ -1165,6 +1222,11 @@ namespace winrt::TerminalApp::implementation } } + if (_settings.GlobalSettings().PersistTabLayout() && _WindowId == 1) + { + PersistTabLayout(); + } + _RemoveAllTabs(); } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 13e4007eeb1..f70effbb857 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -79,6 +79,8 @@ namespace winrt::TerminalApp::implementation bool AlwaysOnTop() const; void SetStartupActions(std::vector& actions); + void PersistTabLayout(); + void SetInboundListener(bool isEmbedding); static std::vector ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args); diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 2fbca6c5606..f497c55b459 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -440,6 +440,31 @@ namespace winrt::TerminalApp::implementation control.ScrollViewport(::base::ClampAdd(currentOffset, delta)); } + // Method Description: + // - Serializes the state of this tab as a series of commands that can be + // executed to recreate it. + // Arguments: + // - + // Return Value: + // - A vector of commands + std::vector TerminalTab::BuildStartupActions() const + { + auto [args, pane] = _rootPane->BuildStartupActions(); + + ActionAndArgs newTabAction{}; + newTabAction.Action(ShortcutAction::NewTab); + // _getNewTerminalArgs MUST be called before parsing any other options, + // as it might clear those options while finding the commandline + NewTabArgs newTabArgs{ pane->GetTerminalArgsForPane() }; + newTabAction.Args(newTabArgs); + + args.insert(args.begin(), newTabAction); + + // TODO: persist focused pane? zoomed pane? + + return args; + } + // Method Description: // - Split the focused pane in our tree of panes, and place the // given TermControl into the newly created pane. diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index f22d7e646c6..fe59a7a4d51 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -84,6 +84,8 @@ namespace winrt::TerminalApp::implementation void EnterZoom(); void ExitZoom(); + std::vector BuildStartupActions() const; + int GetLeafPaneCount() const noexcept; void TogglePaneReadOnly(); diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index b556a488925..4da6237e335 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -133,6 +133,11 @@ + + + + + When enabled, this enables the launch of Windows Terminal at machine startup. A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header". + + Persist terminal tab layout + Header for a control to toggle whether the app should launch when the user's machine starts up, or not. + + + When enabled, the tab layout of terminal windows will be saved on close and reloaded on open. + A description for what the "start on user login" setting does. Presented near "Globals_PersistTabLayout.Header". + Always on top Header for a control to toggle if the app will always be presented on top of other windows, or is treated normally (when disabled). diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 0984a5330cf..a02904acb07 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -5,7 +5,7 @@ #include "ApplicationState.h" #include "CascadiaSettings.h" #include "ApplicationState.g.cpp" - +#include "ActionAndArgs.h" #include "JsonUtils.h" #include "FileUtils.h" @@ -55,6 +55,83 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); } }; + + template + struct ConversionTrait> + { + winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) + { + winrt::Windows::Foundation::Collections::IVector vec = winrt::single_threaded_vector(); + + for (auto it = json.begin(), end = json.end(); it != end; ++it) + { + vec.Append(GetValue(*it)); + } + + return vec; + } + + bool CanConvert(const Json::Value& json) const + { + if (!json.isArray()) + { + return false; + } + ConversionTrait trait; + for (const auto& v : json) + { + if (!trait.CanConvert(v)) + { + return false; + } + } + return true; + } + + Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) + { + Json::Value json{ Json::arrayValue }; + + ConversionTrait trait; + for (const auto& v : val) + { + json.append(trait.ToJson(v)); + } + + return json; + } + std::string TypeDescription() const + { + return fmt::format("{} array", ConversionTrait{}.TypeDescription()); + } + }; + + using namespace winrt::Microsoft::Terminal::Settings::Model; + + template<> + struct ConversionTrait + { + ActionAndArgs FromJson(const Json::Value& json) + { + std::vector v; + return *implementation::ActionAndArgs::FromJson(json, v); + } + + bool CanConvert(const Json::Value& json) + { + return json.isObject(); + } + + Json::Value ToJson(const winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs& val) + { + return implementation::ActionAndArgs::ToJson(val); + } + + std::string TypeDescription() const + { + return "ActionAndArgs"; + } + }; } using namespace ::Microsoft::Terminal::Settings::Model; diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 90320f0e2a4..7fd5ea19fc0 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -21,8 +21,9 @@ Module Name: // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: // (type, function name, JSON key, ...variadic construction arguments) -#define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") +#define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ + X(Windows::Foundation::Collections::IVector, PersistedTabLayout, "persistedTabLayout") namespace winrt::Microsoft::Terminal::Settings::Model::implementation { diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.idl b/src/cascadia/TerminalSettingsModel/ApplicationState.idl index 8f5eed84ed4..d1618a004f2 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.idl +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.idl @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "Command.idl"; namespace Microsoft.Terminal.Settings.Model { @@ -9,5 +10,7 @@ namespace Microsoft.Terminal.Settings.Model void Reload(); String FilePath { get; }; + + Windows.Foundation.Collections.IVector PersistedTabLayout { get; set; }; } } diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index aeca8499dc3..b04ab9d909f 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -40,6 +40,7 @@ static constexpr std::string_view LaunchModeKey{ "launchMode" }; static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" }; static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" }; static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" }; +static constexpr std::string_view PersistTabLayoutKey{ "persistTabLayout" }; static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" }; static constexpr std::string_view LegacyUseTabSwitcherModeKey{ "useTabSwitcher" }; static constexpr std::string_view TabSwitcherModeKey{ "tabSwitcherMode" }; @@ -123,6 +124,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_ForceVTInput = _ForceVTInput; globals->_DebugFeaturesEnabled = _DebugFeaturesEnabled; globals->_StartOnUserLogin = _StartOnUserLogin; + globals->_PersistTabLayout = _PersistTabLayout; globals->_AlwaysOnTop = _AlwaysOnTop; globals->_TabSwitcherMode = _TabSwitcherMode; globals->_DisableAnimations = _DisableAnimations; @@ -303,6 +305,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin); + JsonUtils::GetValueForKey(json, PersistTabLayoutKey, _PersistTabLayout); + JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); // GH#8076 - when adding enum values to this key, we also changed it from @@ -414,6 +418,7 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering); JsonUtils::SetValueForKey(json, ForceVTInputKey, _ForceVTInput); JsonUtils::SetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin); + JsonUtils::SetValueForKey(json, PersistTabLayoutKey, _PersistTabLayout); JsonUtils::SetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); JsonUtils::SetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode); JsonUtils::SetValueForKey(json, DisableAnimationsKey, _DisableAnimations); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 9a1536a106c..8aeabf50afa 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -90,6 +90,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ForceVTInput, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DebugFeaturesEnabled, _getDefaultDebugFeaturesValue()); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, StartOnUserLogin, false); + INHERITABLE_SETTING(Model::GlobalAppSettings, bool, PersistTabLayout, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysOnTop, false); INHERITABLE_SETTING(Model::GlobalAppSettings, Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::InOrder); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DisableAnimations, false); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index e9d1fccad7d..4963341bbbd 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -65,6 +65,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, ForceVTInput); INHERITABLE_SETTING(Boolean, DebugFeaturesEnabled); INHERITABLE_SETTING(Boolean, StartOnUserLogin); + INHERITABLE_SETTING(Boolean, PersistTabLayout); INHERITABLE_SETTING(Boolean, AlwaysOnTop); INHERITABLE_SETTING(TabSwitcherMode, TabSwitcherMode); INHERITABLE_SETTING(Boolean, DisableAnimations); diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index 220ec1df372..49eddbcfef6 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -177,6 +177,57 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils } }; + template + struct ConversionTrait> + { + std::vector FromJson(const Json::Value& json) + { + std::vector vec; + vec.reserve(json.size()); + + for (auto it = json.begin(), end = json.end(); it != end; ++it) + { + vec.push_back(GetValue(*it)); + } + + return vec; + } + + bool CanConvert(const Json::Value& json) const + { + if (!json.isArray()) + { + return false; + } + ConversionTrait trait; + for (const auto& v : json) + { + if (!trait.CanConvert(v)) + { + return false; + } + } + return true; + } + + Json::Value ToJson(const std::vector& val) + { + Json::Value json{ Json::arrayValue }; + + ConversionTrait trait; + for (const auto& v : val) + { + json.append(trait.ToJson(v)); + } + + return json; + } + std::string TypeDescription() const + { + return fmt::format("{} array", ConversionTrait{}.TypeDescription()); + } + }; + template struct ConversionTrait> { diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index abfdebf21c7..046b5724c45 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -372,7 +372,8 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::SplitState) // Possible SplitType values JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::SplitType) { - JSON_MAPPINGS(1) = { + JSON_MAPPINGS(2) = { + pair_type{ "manual", ValueType::Manual }, pair_type{ "duplicate", ValueType::Duplicate }, }; }; From 6297417e8fef11401bcb84a685a4269885ab5d70 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 02:33:48 -0400 Subject: [PATCH 02/37] Add missing comment --- src/cascadia/TerminalApp/Pane.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 8de739e8ec2..0ce1efac199 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -71,6 +71,13 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus }); } +// Method Description: +// - Extract the terminal settings from the current (leaf) pane's control +// to be used to create an equivalent control +// Arguments: +// - +// Return Value: +// - Arguments appropriate for a SplitPane or NewTab action NewTerminalArgs Pane::GetTerminalArgsForPane() const { // Leaves are the only things that have controls From a265e814c9b1124295fe33d2f1a32b8a282e4228 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 13:15:31 -0400 Subject: [PATCH 03/37] Try to save pane color information --- .../TerminalApp/AppActionHandlers.cpp | 2 ++ src/cascadia/TerminalApp/Pane.cpp | 22 ++++++++++++++++++- .../TerminalSettingsModel/ColorScheme.idl | 1 + .../TerminalSettings.cpp | 1 + .../TerminalSettingsModel/TerminalSettings.h | 1 + .../TerminalSettings.idl | 2 ++ 6 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 9aabdc24868..81ab11a53f9 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -463,10 +463,12 @@ namespace winrt::TerminalApp::implementation // 1 is important to make sure that the effects of // something like `colortool` are cleared when setting // the scheme. + /* if (controlSettings.GetParent() != nullptr) { parentSettings = controlSettings.GetParent(); } + */ // ApplyColorScheme(nullptr) will clear the old color scheme. controlSettings.ApplyColorScheme(nullptr); diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 0ce1efac199..60b129f5a51 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -91,8 +91,28 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const args.TabTitle(controlSettings.StartingTitle()); args.Commandline(controlSettings.Commandline()); args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle()); + if (controlSettings.TabColor() || controlSettings.StartingTabColor()) + { - //TODO: color scheme? + til::color c; + // StartingTabColor is prioritized over other colors + if (controlSettings.StartingTabColor()) + { + c = til::color(controlSettings.StartingTabColor().Value()); + } + else + { + c = til::color(controlSettings.TabColor().Value()); + } + + args.TabColor(winrt::Windows::Foundation::IReference(c)); + } + + if (controlSettings.AppliedColorScheme()) + { + auto name = controlSettings.AppliedColorScheme().Name(); + args.ColorScheme(name); + } return args; } diff --git a/src/cascadia/TerminalSettingsModel/ColorScheme.idl b/src/cascadia/TerminalSettingsModel/ColorScheme.idl index bb812aaac0f..418fbb531f2 100644 --- a/src/cascadia/TerminalSettingsModel/ColorScheme.idl +++ b/src/cascadia/TerminalSettingsModel/ColorScheme.idl @@ -4,6 +4,7 @@ namespace Microsoft.Terminal.Settings.Model { [default_interface] runtimeclass ColorScheme : Windows.Foundation.IStringable { + ColorScheme(); ColorScheme(String name); String Name; diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index d5202f1d2ff..8308f774b55 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -342,6 +342,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // If the scheme was nullptr, then just clear out the current color // settings. + AppliedColorScheme(scheme); if (scheme == nullptr) { ClearDefaultForeground(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index bda1f24cd65..29f80cb5a5b 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -131,6 +131,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, IFontAxesMap, FontAxes); INHERITABLE_SETTING(Model::TerminalSettings, IFontFeatureMap, FontFeatures); + INHERITABLE_SETTING(Model::TerminalSettings, Model::ColorScheme, AppliedColorScheme); INHERITABLE_SETTING(Model::TerminalSettings, hstring, BackgroundImage); INHERITABLE_SETTING(Model::TerminalSettings, double, BackgroundImageOpacity, 1.0); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl index a28c6ab81a8..859ad469fb6 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl @@ -34,5 +34,7 @@ namespace Microsoft.Terminal.Settings.Model void SetParent(TerminalSettings parent); TerminalSettings GetParent(); void ApplyColorScheme(ColorScheme scheme); + + ColorScheme AppliedColorScheme; }; } From 48cde2028ba20c1a20a983e546adcdc99ab14caf Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 13:31:13 -0400 Subject: [PATCH 04/37] formatting --- src/cascadia/TerminalApp/Pane.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 60b129f5a51..9aaf600566d 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -93,7 +93,6 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle()); if (controlSettings.TabColor() || controlSettings.StartingTabColor()) { - til::color c; // StartingTabColor is prioritized over other colors if (controlSettings.StartingTabColor()) From 4b897d0a45c3698f0622ae8308bb48ef3413941e Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 17:16:05 -0400 Subject: [PATCH 05/37] Also persist window size and location information --- src/cascadia/TerminalApp/AppLogic.cpp | 34 ++- src/cascadia/TerminalApp/TerminalPage.cpp | 34 ++- src/cascadia/TerminalApp/TerminalPage.h | 2 + .../TerminalSettingsModel/ApplicationState.h | 8 +- .../ApplicationState.idl | 3 + .../TerminalSettingsModel/JsonUtils.h | 282 ++++++++++-------- 6 files changed, 232 insertions(+), 131 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 106da0fbdea..27f5e3a02d7 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -596,12 +596,25 @@ 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(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); + if (_root->ShouldUsePersistedLayout(_settings)) + { + auto state = ApplicationState::SharedInstance(); + + proposedSize = state.PersistedInitialSize(); + 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 @@ -683,7 +696,18 @@ namespace winrt::TerminalApp::implementation LoadSettings(); } - const auto initialPosition{ _settings.GlobalSettings().InitialPosition() }; + auto initialPosition{ _settings.GlobalSettings().InitialPosition() }; + + if (_root->ShouldUsePersistedLayout(_settings)) + { + auto state = ApplicationState::SharedInstance(); + + if (state.PersistedInitialPosition().X && state.PersistedInitialPosition().Y) + { + initialPosition = state.PersistedInitialPosition(); + } + } + return { initialPosition.X ? initialPosition.X.Value() : defaultInitialX, initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 361d2842266..b174e2d750e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -238,6 +238,18 @@ namespace winrt::TerminalApp::implementation CATCH_LOG(); } + // Method Description; + // - Checks if the current terminal window should load or save its layout information. + // Arguments: + // - settings: The settings to use as this may be called before the page is + // fully initialized. + // Return Value: + // - true if the ApplicationState should be used. + bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const + { + return settings.GlobalSettings().PersistTabLayout() && _WindowId == 1; + } + winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e) { Windows::Foundation::Collections::IVectorView items; @@ -324,7 +336,7 @@ namespace winrt::TerminalApp::implementation // If the user selected to save their tab layout, we are the first // window opened, and wt was not run with any other arguments, then // we should use the saved settings. - if (_settings.GlobalSettings().PersistTabLayout() && _WindowId == 1 && _startupActions.Size() == 1) + if (ShouldUsePersistedLayout(_settings) && _startupActions.Size() == 1) { auto actions = ApplicationState::SharedInstance().PersistedTabLayout(); if (actions && actions.Size() > 0) @@ -1201,6 +1213,24 @@ namespace winrt::TerminalApp::implementation auto state = ApplicationState::SharedInstance(); state.PersistedTabLayout(winrt::single_threaded_vector(std::move(actions))); + + // Only save the content size because the tab size will be added on load. + const float contentWidth = ::base::saturated_cast(_tabContent.ActualWidth()); + const float contentHeight = ::base::saturated_cast(_tabContent.ActualHeight()); + const winrt::Windows::Foundation::Size windowSize{ contentWidth, contentHeight }; + + state.PersistedInitialSize(windowSize); + + if (_hostingHwnd) + { + RECT window; + GetWindowRect(_hostingHwnd.value(), &window); + LaunchPosition pos{}; + pos.X = window.left; + pos.Y = window.top; + + state.PersistedInitialPosition(pos); + } } // Method Description: @@ -1222,7 +1252,7 @@ namespace winrt::TerminalApp::implementation } } - if (_settings.GlobalSettings().PersistTabLayout() && _WindowId == 1) + if (ShouldUsePersistedLayout(_settings)) { PersistTabLayout(); } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index f70effbb857..38ee19ba955 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -58,6 +58,8 @@ namespace winrt::TerminalApp::implementation void Create(); + bool ShouldUsePersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const; + winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e); hstring Title(); diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 7fd5ea19fc0..ee743f9bd0f 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -21,9 +21,11 @@ Module Name: // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: // (type, function name, JSON key, ...variadic construction arguments) -#define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ - X(Windows::Foundation::Collections::IVector, PersistedTabLayout, "persistedTabLayout") +#define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ + X(Windows::Foundation::Collections::IVector, PersistedTabLayout, "persistedTabLayout") \ + X(Microsoft::Terminal::Settings::Model::LaunchPosition, PersistedInitialPosition, "persistedInitialPosition") \ + X(winrt::Windows::Foundation::Size, PersistedInitialSize, "persistedInitialSize") namespace winrt::Microsoft::Terminal::Settings::Model::implementation { diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.idl b/src/cascadia/TerminalSettingsModel/ApplicationState.idl index d1618a004f2..f2d7b4f2a77 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.idl +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.idl @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import "Command.idl"; +import "GlobalAppSettings.idl"; namespace Microsoft.Terminal.Settings.Model { @@ -12,5 +13,7 @@ namespace Microsoft.Terminal.Settings.Model String FilePath { get; }; Windows.Foundation.Collections.IVector PersistedTabLayout { get; set; }; + LaunchPosition PersistedInitialPosition; + Windows.Foundation.Size PersistedInitialSize; } } diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index 49eddbcfef6..6a2d98b78c8 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -117,6 +117,127 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils std::string expectedType; }; + // Method Description: + // - Helper that will populate a reference with a value converted from a json object. + // Arguments: + // - json: the json object to convert + // - target: the value to populate with the converted result + // Return Value: + // - a boolean indicating whether the value existed (in this case, was non-null) + // + // GetValue, type-deduced, manual converter + template + bool GetValue(const Json::Value& json, T& target, Converter&& conv) + { + if (!conv.CanConvert(json)) + { + DeserializationError e{ json }; + e.expectedType = conv.TypeDescription(); + throw e; + } + + target = conv.FromJson(json); + return true; + } + + // GetValue, forced return type, manual converter + template + std::decay_t GetValue(const Json::Value& json, Converter&& conv) + { + std::decay_t local{}; + GetValue(json, local, std::forward(conv)); + return local; // returns zero-initialized or value + } + + // GetValueForKey, type-deduced, manual converter + template + bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv) + { + if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) }) + { + try + { + return GetValue(*found, target, std::forward(conv)); + } + catch (DeserializationError& e) + { + e.SetKey(key); + throw; // rethrow now that it has a key + } + } + return false; + } + + // GetValueForKey, forced return type, manual converter + template + std::decay_t GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv) + { + std::decay_t local{}; + GetValueForKey(json, key, local, std::forward(conv)); + return local; // returns zero-initialized? + } + + // GetValue, type-deduced, with automatic converter + template + bool GetValue(const Json::Value& json, T& target) + { + return GetValue(json, target, ConversionTrait::type>{}); + } + + // GetValue, forced return type, with automatic converter + template + std::decay_t GetValue(const Json::Value& json) + { + std::decay_t local{}; + GetValue(json, local, ConversionTrait::type>{}); + return local; // returns zero-initialized or value + } + + // GetValueForKey, type-deduced, with automatic converter + template + bool GetValueForKey(const Json::Value& json, std::string_view key, T& target) + { + return GetValueForKey(json, key, target, ConversionTrait::type>{}); + } + + // GetValueForKey, forced return type, with automatic converter + template + std::decay_t GetValueForKey(const Json::Value& json, std::string_view key) + { + return GetValueForKey(json, key, ConversionTrait::type>{}); + } + + // Get multiple values for keys (json, k, &v, k, &v, k, &v, ...). + // Uses the default converter for each v. + // Careful: this can cause a template explosion. + constexpr void GetValuesForKeys(const Json::Value& /*json*/) {} + + template + void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args) + { + GetValueForKey(json, key1, val1); + GetValuesForKeys(json, std::forward(args)...); + } + + // SetValueForKey, type-deduced, manual converter + template + void SetValueForKey(Json::Value& json, std::string_view key, const T& target, Converter&& conv) + { + // We don't want to write any empty optionals into JSON (right now). + if (OptionOracle::HasValue(target)) + { + // demand guarantees that it will return a value or throw an exception + *json.demand(&*key.cbegin(), (&*key.cbegin()) + key.size()) = conv.ToJson(target); + } + } + + // SetValueForKey, type-deduced, with automatic converter + template + void SetValueForKey(Json::Value& json, std::string_view key, const T& target) + { + SetValueForKey(json, key, target, ConversionTrait::type>{}); + } + template struct ConversionTrait { @@ -569,6 +690,46 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils } }; +#ifdef WINRT_Windows_Foundation_H + template<> + struct ConversionTrait + { + winrt::Windows::Foundation::Size FromJson(const Json::Value& json) + { + winrt::Windows::Foundation::Size size{}; + GetValueForKey(json, std::string_view("width"), size.Width); + GetValueForKey(json, std::string_view("height"), size.Height); + + return size; + } + + bool CanConvert(const Json::Value& json) + { + if (!json.isObject()) + { + return false; + } + + return json.isMember("width") && json.isMember("height"); + } + + Json::Value ToJson(const winrt::Windows::Foundation::Size& val) + { + Json::Value json{ Json::objectValue }; + + SetValueForKey(json, std::string_view("width"), val.Width); + SetValueForKey(json, std::string_view("height"), val.Height); + + return json; + } + + std::string TypeDescription() const + { + return "size { width, height }"; + } + }; +#endif + #ifdef WINRT_Windows_UI_H template<> struct ConversionTrait @@ -826,127 +987,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return "any"; } }; - - // Method Description: - // - Helper that will populate a reference with a value converted from a json object. - // Arguments: - // - json: the json object to convert - // - target: the value to populate with the converted result - // Return Value: - // - a boolean indicating whether the value existed (in this case, was non-null) - // - // GetValue, type-deduced, manual converter - template - bool GetValue(const Json::Value& json, T& target, Converter&& conv) - { - if (!conv.CanConvert(json)) - { - DeserializationError e{ json }; - e.expectedType = conv.TypeDescription(); - throw e; - } - - target = conv.FromJson(json); - return true; - } - - // GetValue, forced return type, manual converter - template - std::decay_t GetValue(const Json::Value& json, Converter&& conv) - { - std::decay_t local{}; - GetValue(json, local, std::forward(conv)); - return local; // returns zero-initialized or value - } - - // GetValueForKey, type-deduced, manual converter - template - bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv) - { - if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) }) - { - try - { - return GetValue(*found, target, std::forward(conv)); - } - catch (DeserializationError& e) - { - e.SetKey(key); - throw; // rethrow now that it has a key - } - } - return false; - } - - // GetValueForKey, forced return type, manual converter - template - std::decay_t GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv) - { - std::decay_t local{}; - GetValueForKey(json, key, local, std::forward(conv)); - return local; // returns zero-initialized? - } - - // GetValue, type-deduced, with automatic converter - template - bool GetValue(const Json::Value& json, T& target) - { - return GetValue(json, target, ConversionTrait::type>{}); - } - - // GetValue, forced return type, with automatic converter - template - std::decay_t GetValue(const Json::Value& json) - { - std::decay_t local{}; - GetValue(json, local, ConversionTrait::type>{}); - return local; // returns zero-initialized or value - } - - // GetValueForKey, type-deduced, with automatic converter - template - bool GetValueForKey(const Json::Value& json, std::string_view key, T& target) - { - return GetValueForKey(json, key, target, ConversionTrait::type>{}); - } - - // GetValueForKey, forced return type, with automatic converter - template - std::decay_t GetValueForKey(const Json::Value& json, std::string_view key) - { - return GetValueForKey(json, key, ConversionTrait::type>{}); - } - - // Get multiple values for keys (json, k, &v, k, &v, k, &v, ...). - // Uses the default converter for each v. - // Careful: this can cause a template explosion. - constexpr void GetValuesForKeys(const Json::Value& /*json*/) {} - - template - void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args) - { - GetValueForKey(json, key1, val1); - GetValuesForKeys(json, std::forward(args)...); - } - - // SetValueForKey, type-deduced, manual converter - template - void SetValueForKey(Json::Value& json, std::string_view key, const T& target, Converter&& conv) - { - // We don't want to write any empty optionals into JSON (right now). - if (OptionOracle::HasValue(target)) - { - // demand guarantees that it will return a value or throw an exception - *json.demand(&*key.cbegin(), (&*key.cbegin()) + key.size()) = conv.ToJson(target); - } - } - - // SetValueForKey, type-deduced, with automatic converter - template - void SetValueForKey(Json::Value& json, std::string_view key, const T& target) - { - SetValueForKey(json, key, target, ConversionTrait::type>{}); - } }; #define JSON_ENUM_MAPPER(...) \ From 773cc796200412d34544dce7d2718063da0ffb73 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 19:16:56 -0400 Subject: [PATCH 06/37] Remove state if the user manually closed all of their tabs (instead of closing the window specifically) --- src/cascadia/TerminalApp/AppLogic.cpp | 13 ++++++++----- src/cascadia/TerminalApp/TabManagement.cpp | 11 +++++++++++ src/cascadia/TerminalApp/TerminalPage.cpp | 2 ++ src/cascadia/TerminalApp/TerminalPage.h | 1 + .../TerminalSettingsModel/ApplicationState.cpp | 9 ++++++--- .../TerminalSettingsModel/ApplicationState.h | 4 ++-- .../TerminalSettingsModel/ApplicationState.idl | 4 ++-- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 27f5e3a02d7..08f93a3dc9b 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -603,9 +603,12 @@ namespace winrt::TerminalApp::implementation { auto state = ApplicationState::SharedInstance(); - proposedSize = state.PersistedInitialSize(); - proposedSize.Height = proposedSize.Height * scale; - proposedSize.Width = proposedSize.Width * scale; + if (state.PersistedInitialSize()) + { + proposedSize = state.PersistedInitialSize().Value(); + proposedSize.Height = proposedSize.Height * scale; + proposedSize.Width = proposedSize.Width * scale; + } } if (proposedSize.Width == 0 && proposedSize.Height == 0) @@ -702,9 +705,9 @@ namespace winrt::TerminalApp::implementation { auto state = ApplicationState::SharedInstance(); - if (state.PersistedInitialPosition().X && state.PersistedInitialPosition().Y) + if (state.PersistedInitialPosition()) { - initialPosition = state.PersistedInitialPosition(); + initialPosition = state.PersistedInitialPosition().Value(); } } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 62d67d2603b..3e04aae777d 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -463,6 +463,17 @@ namespace winrt::TerminalApp::implementation // To close the window here, we need to close the hosting window. if (_tabs.Size() == 0) { + // If we are supposed to save state, make sure we clear it out + // if the user manually closed all tabs. + if (!_maintainStateOnTabClose && ShouldUsePersistedLayout(_settings)) + { + auto state = ApplicationState::SharedInstance(); + state.PersistedInitialPosition(nullptr); + state.PersistedInitialSize(nullptr); + state.PersistedTabLayout(nullptr); + } + + _LastTabClosedHandlers(*this, nullptr); } else if (focusedTabIndex.has_value() && focusedTabIndex.value() == gsl::narrow_cast(tabIndex)) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index b174e2d750e..e17547d9cc1 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1255,6 +1255,8 @@ namespace winrt::TerminalApp::implementation if (ShouldUsePersistedLayout(_settings)) { PersistTabLayout(); + // don't delete the applicationstate when all of the tabs are removed. + _maintainStateOnTabClose = true; } _RemoveAllTabs(); diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 38ee19ba955..2113cac2ddb 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -160,6 +160,7 @@ namespace winrt::TerminalApp::implementation winrt::hstring _WindowName{}; uint64_t _WindowId{ 0 }; + bool _maintainStateOnTabClose; bool _rearranging; std::optional _rearrangeFrom; std::optional _rearrangeTo; diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index a02904acb07..033e67511ba 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -92,10 +92,13 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { Json::Value json{ Json::arrayValue }; - ConversionTrait trait; - for (const auto& v : val) + if (val) { - json.append(trait.ToJson(v)); + ConversionTrait trait; + for (const auto& v : val) + { + json.append(trait.ToJson(v)); + } } return json; diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index ee743f9bd0f..40c5bf86866 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -24,8 +24,8 @@ Module Name: #define MTSM_APPLICATION_STATE_FIELDS(X) \ X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ X(Windows::Foundation::Collections::IVector, PersistedTabLayout, "persistedTabLayout") \ - X(Microsoft::Terminal::Settings::Model::LaunchPosition, PersistedInitialPosition, "persistedInitialPosition") \ - X(winrt::Windows::Foundation::Size, PersistedInitialSize, "persistedInitialSize") + X(winrt::Windows::Foundation::IReference, PersistedInitialPosition, "persistedInitialPosition") \ + X(winrt::Windows::Foundation::IReference, PersistedInitialSize, "persistedInitialSize") namespace winrt::Microsoft::Terminal::Settings::Model::implementation { diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.idl b/src/cascadia/TerminalSettingsModel/ApplicationState.idl index f2d7b4f2a77..04a0d7ae066 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.idl +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.idl @@ -13,7 +13,7 @@ namespace Microsoft.Terminal.Settings.Model String FilePath { get; }; Windows.Foundation.Collections.IVector PersistedTabLayout { get; set; }; - LaunchPosition PersistedInitialPosition; - Windows.Foundation.Size PersistedInitialSize; + Windows.Foundation.IReference PersistedInitialPosition; + Windows.Foundation.IReference PersistedInitialSize; } } From 37e5bd40872d5ddf54a3c0d8bedd2059c92208c9 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 19:23:36 -0400 Subject: [PATCH 07/37] Capitalize to make the spellchecker happy? --- src/cascadia/TerminalApp/TerminalPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index e17547d9cc1..bbeeebf74ce 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1255,7 +1255,7 @@ namespace winrt::TerminalApp::implementation if (ShouldUsePersistedLayout(_settings)) { PersistTabLayout(); - // don't delete the applicationstate when all of the tabs are removed. + // don't delete the ApplicationState when all of the tabs are removed. _maintainStateOnTabClose = true; } From 1b6dfc5c2a46d4bf81cbd95ba20157373965ca42 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 18 Aug 2021 19:28:26 -0400 Subject: [PATCH 08/37] Also format. Again. Maybe Ill learn my lesson one of these days. --- src/cascadia/TerminalApp/TabManagement.cpp | 1 - src/cascadia/TerminalSettingsModel/ApplicationState.h | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 3e04aae777d..a65a464d735 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -473,7 +473,6 @@ namespace winrt::TerminalApp::implementation state.PersistedTabLayout(nullptr); } - _LastTabClosedHandlers(*this, nullptr); } else if (focusedTabIndex.has_value() && focusedTabIndex.value() == gsl::narrow_cast(tabIndex)) diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 40c5bf86866..23f40524a89 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -21,10 +21,10 @@ Module Name: // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: // (type, function name, JSON key, ...variadic construction arguments) -#define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ - X(Windows::Foundation::Collections::IVector, PersistedTabLayout, "persistedTabLayout") \ - X(winrt::Windows::Foundation::IReference, PersistedInitialPosition, "persistedInitialPosition") \ +#define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ + X(Windows::Foundation::Collections::IVector, PersistedTabLayout, "persistedTabLayout") \ + X(winrt::Windows::Foundation::IReference, PersistedInitialPosition, "persistedInitialPosition") \ X(winrt::Windows::Foundation::IReference, PersistedInitialSize, "persistedInitialSize") namespace winrt::Microsoft::Terminal::Settings::Model::implementation From c1722c76a6d772ff923e8d8fa34c655cd6dbd3cb Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 19 Aug 2021 10:09:14 -0400 Subject: [PATCH 09/37] Update setting name to better reflect what it actually does --- src/cascadia/TerminalApp/TerminalPage.cpp | 6 +++--- src/cascadia/TerminalApp/TerminalPage.h | 2 +- src/cascadia/TerminalSettingsEditor/Launch.xaml | 6 +++--- .../Resources/en-US/Resources.resw | 14 +++++++------- .../TerminalSettingsModel/GlobalAppSettings.cpp | 8 ++++---- .../TerminalSettingsModel/GlobalAppSettings.h | 2 +- .../TerminalSettingsModel/GlobalAppSettings.idl | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index bbeeebf74ce..5aab6d273f5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -247,7 +247,7 @@ namespace winrt::TerminalApp::implementation // - true if the ApplicationState should be used. bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const { - return settings.GlobalSettings().PersistTabLayout() && _WindowId == 1; + return settings.GlobalSettings().PersistWindowLayout() && _WindowId == 1; } winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e) @@ -1177,7 +1177,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalPage::PersistTabLayout() + void TerminalPage::PersistWindowLayout() { std::vector actions; @@ -1254,7 +1254,7 @@ namespace winrt::TerminalApp::implementation if (ShouldUsePersistedLayout(_settings)) { - PersistTabLayout(); + PersistWindowLayout(); // don't delete the ApplicationState when all of the tabs are removed. _maintainStateOnTabClose = true; } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 2113cac2ddb..bd456e6ef0c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -81,7 +81,7 @@ namespace winrt::TerminalApp::implementation bool AlwaysOnTop() const; void SetStartupActions(std::vector& actions); - void PersistTabLayout(); + void PersistWindowLayout(); void SetInboundListener(bool isEmbedding); static std::vector ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args); diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 4da6237e335..929e8ef849f 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -133,9 +133,9 @@ - - - + + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index d7bfeec98f1..e208119ea5a 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -375,13 +375,13 @@ When enabled, this enables the launch of Windows Terminal at machine startup. A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header". - - Persist terminal tab layout - Header for a control to toggle whether the app should launch when the user's machine starts up, or not. + + Persist terminal window layout + Header for a control to toggle whether the terminal layout should be saved between runs. - - When enabled, the tab layout of terminal windows will be saved on close and reloaded on open. - A description for what the "start on user login" setting does. Presented near "Globals_PersistTabLayout.Header". + + When enabled, the tab layout, size, and position of the primary terminal window will be saved on close and reloaded on open. + A description for what the "Persist terminal window layout" setting does. Presented near "Globals_PersistWindowLayout.Header". Always on top @@ -1218,4 +1218,4 @@ Both An option to choose from for the "intense text format" setting. When selected, "intense" text will be rendered as both bold text and in a brighter color - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index b04ab9d909f..a2f5ddb58fe 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -40,7 +40,7 @@ static constexpr std::string_view LaunchModeKey{ "launchMode" }; static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" }; static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" }; static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" }; -static constexpr std::string_view PersistTabLayoutKey{ "persistTabLayout" }; +static constexpr std::string_view PersistWindowLayoutKey{ "persistWindowLayout" }; static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" }; static constexpr std::string_view LegacyUseTabSwitcherModeKey{ "useTabSwitcher" }; static constexpr std::string_view TabSwitcherModeKey{ "tabSwitcherMode" }; @@ -124,7 +124,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_ForceVTInput = _ForceVTInput; globals->_DebugFeaturesEnabled = _DebugFeaturesEnabled; globals->_StartOnUserLogin = _StartOnUserLogin; - globals->_PersistTabLayout = _PersistTabLayout; + globals->_PersistWindowLayout = _PersistWindowLayout; globals->_AlwaysOnTop = _AlwaysOnTop; globals->_TabSwitcherMode = _TabSwitcherMode; globals->_DisableAnimations = _DisableAnimations; @@ -305,7 +305,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin); - JsonUtils::GetValueForKey(json, PersistTabLayoutKey, _PersistTabLayout); + JsonUtils::GetValueForKey(json, PersistWindowLayoutKey, _PersistWindowLayout); JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); @@ -418,7 +418,7 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering); JsonUtils::SetValueForKey(json, ForceVTInputKey, _ForceVTInput); JsonUtils::SetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin); - JsonUtils::SetValueForKey(json, PersistTabLayoutKey, _PersistTabLayout); + JsonUtils::SetValueForKey(json, PersistWindowLayoutKey, _PersistWindowLayout); JsonUtils::SetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); JsonUtils::SetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode); JsonUtils::SetValueForKey(json, DisableAnimationsKey, _DisableAnimations); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 8aeabf50afa..6cf9d695a9a 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -90,7 +90,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ForceVTInput, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DebugFeaturesEnabled, _getDefaultDebugFeaturesValue()); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, StartOnUserLogin, false); - INHERITABLE_SETTING(Model::GlobalAppSettings, bool, PersistTabLayout, false); + INHERITABLE_SETTING(Model::GlobalAppSettings, bool, PersistWindowLayout, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysOnTop, false); INHERITABLE_SETTING(Model::GlobalAppSettings, Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::InOrder); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DisableAnimations, false); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 4963341bbbd..bdc37bed179 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -65,7 +65,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, ForceVTInput); INHERITABLE_SETTING(Boolean, DebugFeaturesEnabled); INHERITABLE_SETTING(Boolean, StartOnUserLogin); - INHERITABLE_SETTING(Boolean, PersistTabLayout); + INHERITABLE_SETTING(Boolean, PersistWindowLayout); INHERITABLE_SETTING(Boolean, AlwaysOnTop); INHERITABLE_SETTING(TabSwitcherMode, TabSwitcherMode); INHERITABLE_SETTING(Boolean, DisableAnimations); From d6997f99bc0886fc629726ef9d90ce41bf93b5fe Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 19 Aug 2021 10:25:30 -0400 Subject: [PATCH 10/37] Use inheritable settings correctly for the applied color scheme --- src/cascadia/TerminalApp/AppActionHandlers.cpp | 2 -- src/cascadia/TerminalSettingsModel/TerminalSettings.cpp | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 81ab11a53f9..9aabdc24868 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -463,12 +463,10 @@ namespace winrt::TerminalApp::implementation // 1 is important to make sure that the effects of // something like `colortool` are cleared when setting // the scheme. - /* if (controlSettings.GetParent() != nullptr) { parentSettings = controlSettings.GetParent(); } - */ // ApplyColorScheme(nullptr) will clear the old color scheme. controlSettings.ApplyColorScheme(nullptr); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 8308f774b55..ae9d18add2f 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -342,9 +342,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // If the scheme was nullptr, then just clear out the current color // settings. - AppliedColorScheme(scheme); if (scheme == nullptr) { + ClearAppliedColorScheme(); ClearDefaultForeground(); ClearDefaultBackground(); ClearSelectionBackground(); @@ -353,6 +353,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { + AppliedColorScheme(scheme); _DefaultForeground = til::color{ scheme.Foreground() }; _DefaultBackground = til::color{ scheme.Background() }; _SelectionBackground = til::color{ scheme.SelectionBackground() }; From 8c64dd54a7e43a7c69747002a9e8795b4a6f3923 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 19 Aug 2021 15:19:01 -0400 Subject: [PATCH 11/37] Also save the tabs focused pane and zoom information --- src/cascadia/TerminalApp/Pane.cpp | 41 ++++++++++++++++--- src/cascadia/TerminalApp/Pane.h | 10 ++++- src/cascadia/TerminalApp/TerminalTab.cpp | 25 +++++++++-- .../ApplicationState.cpp | 3 +- 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 9aaf600566d..2d6063eec4f 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -125,12 +125,17 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const // - // Return Value: // - A vector of commands and the original root pane for this new tree -std::pair, std::shared_ptr> Pane::BuildStartupActions() +Pane::BuildStartupState Pane::BuildStartupActions() { // if we are a leaf then all there is to do is defer to the parent. if (_IsLeaf()) { - return { {}, shared_from_this() }; + if (_lastActive) + { + return { {}, shared_from_this(), 0, 0 }; + } + + return { {}, shared_from_this(), std::nullopt, 0 }; } auto buildSplitPane = [&](auto newPane) { @@ -159,12 +164,22 @@ std::pair, std::shared_ptr> Pane::BuildStartupA if (_firstChild->_IsLeaf() && _secondChild->_IsLeaf()) { auto actionAndArgs = buildSplitPane(_secondChild); - return { { actionAndArgs }, _firstChild }; + std::optional focusedPaneId = std::nullopt; + if (_firstChild->_lastActive) + { + focusedPaneId = 0; + } + else if (_secondChild->_lastActive) + { + focusedPaneId = 1; + } + + return { { actionAndArgs }, _firstChild, focusedPaneId, 1 }; } // We now need to execute the commands for each side of the tree - auto [a1, p1] = _firstChild->BuildStartupActions(); - auto [a2, p2] = _secondChild->BuildStartupActions(); + auto [a1, p1, f1, n1] = _firstChild->BuildStartupActions(); + auto [a2, p2, f2, n2] = _secondChild->BuildStartupActions(); std::vector actions; actions.reserve(a1.size() + a2.size() + 3); @@ -188,7 +203,20 @@ std::pair, std::shared_ptr> Pane::BuildStartupA actions.insert(actions.end(), a2.begin(), a2.end()); } - return { { actions }, p1 }; + // if the tree is well-formed then f1.has_value and f2.has_value are + // mutually exclusive. If f1 then we use the value as is. + std::optional focusedPaneId = f1; + + // If the focus is on the second side, its id depends on the number of panes + // created so far. + if (f2.has_value()) + { + // Add 1 for this split, and the number of panes created on the + // left side of this tree. + focusedPaneId = f2.value() + 1 + n1; + } + + return { { actions }, p1, focusedPaneId, n1 + n2 }; } // Method Description: @@ -574,6 +602,7 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) else if (parent->_secondChild == oldChild) { parent->_secondChild->Closed(parent->_secondClosedToken); + parent->_secondChild = newChild; } // Clear now to ensure that we can add the child's grid to us later diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index f5f3511bbb1..895830a06a3 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -44,6 +44,7 @@ enum class Borders : int }; DEFINE_ENUM_FLAG_OPERATORS(Borders); + class Pane : public std::enable_shared_from_this { public: @@ -62,7 +63,14 @@ class Pane : public std::enable_shared_from_this void ClearActive(); void SetActive(); - std::pair, std::shared_ptr> BuildStartupActions(); + struct BuildStartupState + { + std::vector args; + std::shared_ptr firstPane; + std::optional focusedPaneId; + uint32_t panesCreated; + }; + BuildStartupState BuildStartupActions(); winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane() const; void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index f497c55b459..1446f23959a 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -449,18 +449,35 @@ namespace winrt::TerminalApp::implementation // - A vector of commands std::vector TerminalTab::BuildStartupActions() const { - auto [args, pane] = _rootPane->BuildStartupActions(); + auto [args, pane, focusedPaneId, _] = _rootPane->BuildStartupActions(); ActionAndArgs newTabAction{}; newTabAction.Action(ShortcutAction::NewTab); - // _getNewTerminalArgs MUST be called before parsing any other options, - // as it might clear those options while finding the commandline NewTabArgs newTabArgs{ pane->GetTerminalArgsForPane() }; newTabAction.Args(newTabArgs); args.insert(args.begin(), newTabAction); - // TODO: persist focused pane? zoomed pane? + // If we only have one arg, we only have 1 pane so we don't need any + // special focus logic + if (args.size() > 1 && focusedPaneId.has_value()) + { + ActionAndArgs focusPaneAction{}; + focusPaneAction.Action(ShortcutAction::FocusPane); + FocusPaneArgs focusArgs{ focusedPaneId.value() }; + focusPaneAction.Args(focusArgs); + + args.push_back(focusPaneAction); + } + + if (_zoomedPane) + { + // we start without any panes zoomed so toggle zoom will enable zoom. + ActionAndArgs zoomPaneAction{}; + zoomPaneAction.Action(ShortcutAction::TogglePaneZoom); + + args.push_back(zoomPaneAction); + } return args; } diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 033e67511ba..cd57c625ef3 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -122,7 +122,8 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils bool CanConvert(const Json::Value& json) { - return json.isObject(); + // commands without args might just be a string + return json.isString() || json.isObject(); } Json::Value ToJson(const winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs& val) From 97bce3212026e9b01b698524551cfbc51c2eea97 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 19 Aug 2021 16:11:19 -0400 Subject: [PATCH 12/37] Fix pane id calculation, save the correct split size --- src/cascadia/TerminalApp/Pane.cpp | 30 +++++++++++++----------- src/cascadia/TerminalApp/Pane.h | 2 +- src/cascadia/TerminalApp/TerminalTab.cpp | 4 +++- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 2d6063eec4f..569cbd0437b 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -125,14 +125,14 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const // - // Return Value: // - A vector of commands and the original root pane for this new tree -Pane::BuildStartupState Pane::BuildStartupActions() +Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t nextId) { // if we are a leaf then all there is to do is defer to the parent. if (_IsLeaf()) { if (_lastActive) { - return { {}, shared_from_this(), 0, 0 }; + return { {}, shared_from_this(), currentId, 0 }; } return { {}, shared_from_this(), std::nullopt, 0 }; @@ -142,7 +142,9 @@ Pane::BuildStartupState Pane::BuildStartupActions() ActionAndArgs actionAndArgs; actionAndArgs.Action(ShortcutAction::SplitPane); auto terminalArgs{ newPane->GetTerminalArgsForPane() }; - SplitPaneArgs args{ SplitType::Manual, _splitState, _desiredSplitPosition, terminalArgs }; + // When creating a pane the split size is the size of the new pane + // and not position. + SplitPaneArgs args{ SplitType::Manual, _splitState, 1. - _desiredSplitPosition, terminalArgs }; actionAndArgs.Args(args); return actionAndArgs; @@ -167,19 +169,23 @@ Pane::BuildStartupState Pane::BuildStartupActions() std::optional focusedPaneId = std::nullopt; if (_firstChild->_lastActive) { - focusedPaneId = 0; + focusedPaneId = currentId; } else if (_secondChild->_lastActive) { - focusedPaneId = 1; + focusedPaneId = nextId; } return { { actionAndArgs }, _firstChild, focusedPaneId, 1 }; } // We now need to execute the commands for each side of the tree - auto [a1, p1, f1, n1] = _firstChild->BuildStartupActions(); - auto [a2, p2, f2, n2] = _secondChild->BuildStartupActions(); + // We've done one split, so the firstmost child will have currentId, and the + // one after it will be incremented. + auto [a1, p1, f1, n1] = _firstChild->BuildStartupActions(currentId, nextId + 1); + // the next id for the second branch depends on how many splits were in the + // first child. + auto [a2, p2, f2, n2] = _secondChild->BuildStartupActions(nextId, nextId + n1 + 1); std::vector actions; actions.reserve(a1.size() + a2.size() + 3); @@ -204,19 +210,15 @@ Pane::BuildStartupState Pane::BuildStartupActions() } // if the tree is well-formed then f1.has_value and f2.has_value are - // mutually exclusive. If f1 then we use the value as is. + // mutually exclusive. std::optional focusedPaneId = f1; - // If the focus is on the second side, its id depends on the number of panes - // created so far. if (f2.has_value()) { - // Add 1 for this split, and the number of panes created on the - // left side of this tree. - focusedPaneId = f2.value() + 1 + n1; + focusedPaneId = f2.value(); } - return { { actions }, p1, focusedPaneId, n1 + n2 }; + return { { actions }, p1, focusedPaneId, n1 + n2 + 1 }; } // Method Description: diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 895830a06a3..d0a21405d4d 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -70,7 +70,7 @@ class Pane : public std::enable_shared_from_this std::optional focusedPaneId; uint32_t panesCreated; }; - BuildStartupState BuildStartupActions(); + BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId); winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane() const; void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 1446f23959a..96e8923ddd7 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -449,7 +449,9 @@ namespace winrt::TerminalApp::implementation // - A vector of commands std::vector TerminalTab::BuildStartupActions() const { - auto [args, pane, focusedPaneId, _] = _rootPane->BuildStartupActions(); + // Give initial ids (0 for the child created with this tab, + // 1 for the child after the first split. + auto [args, pane, focusedPaneId, _] = _rootPane->BuildStartupActions(0, 1); ActionAndArgs newTabAction{}; newTabAction.Action(ShortcutAction::NewTab); From ec79c93197e6d30bb51048c9154bdd5dc6a7a866 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 19 Aug 2021 16:13:45 -0400 Subject: [PATCH 13/37] Foiled by whitespace again. --- src/cascadia/TerminalApp/Pane.cpp | 2 +- src/cascadia/TerminalApp/Pane.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 569cbd0437b..b262c2d333b 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -210,7 +210,7 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n } // if the tree is well-formed then f1.has_value and f2.has_value are - // mutually exclusive. + // mutually exclusive. std::optional focusedPaneId = f1; if (f2.has_value()) diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index d0a21405d4d..1e657ed0cff 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -44,7 +44,6 @@ enum class Borders : int }; DEFINE_ENUM_FLAG_OPERATORS(Borders); - class Pane : public std::enable_shared_from_this { public: From d7ce2de99d1d9ffe02558fc5ff92277a138b5c2c Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 19 Aug 2021 16:17:39 -0400 Subject: [PATCH 14/37] hyphenation to appease the spell checker --- src/cascadia/TerminalApp/Pane.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index b262c2d333b..f5c4f1e1003 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -180,7 +180,7 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n } // We now need to execute the commands for each side of the tree - // We've done one split, so the firstmost child will have currentId, and the + // We've done one split, so the first-most child will have currentId, and the // one after it will be incremented. auto [a1, p1, f1, n1] = _firstChild->BuildStartupActions(currentId, nextId + 1); // the next id for the second branch depends on how many splits were in the From 91c3d693d413dcf115ec4bf75e18433835f67da4 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 19 Aug 2021 17:23:34 -0400 Subject: [PATCH 15/37] Update comment on Pane::BuildStartupActions --- src/cascadia/TerminalApp/Pane.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index f5c4f1e1003..03bdc2e9910 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -122,9 +122,12 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const // - This will always result in the right-most child being the focus // after the commands finish executing. // Arguments: -// - +// - currentId: the id to use for the current/first pane +// - nextId: the id to use for a new pane if we split // Return Value: -// - A vector of commands and the original root pane for this new tree +// - The state from building the startup actions, includes a vector of commands, +// the original root pane, the id of the focused pane, and the number of panes +// created. Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t nextId) { // if we are a leaf then all there is to do is defer to the parent. From ef80c663983b564f2b0ae6223dec5e3f2f1ea61a Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Fri, 20 Aug 2021 15:03:18 -0400 Subject: [PATCH 16/37] Switch to serializing an array of window layouts so that the schema can stay the stay if we choose to have multiple windows saved in the future. --- src/cascadia/TerminalApp/AppLogic.cpp | 14 +-- src/cascadia/TerminalApp/TabManagement.cpp | 4 +- src/cascadia/TerminalApp/TerminalPage.cpp | 17 ++-- .../ApplicationState.cpp | 96 ++++++------------ .../TerminalSettingsModel/ApplicationState.h | 19 +++- .../ApplicationState.idl | 13 ++- .../TerminalSettingsModel/JsonUtils.h | 97 ++++++++++++++++++- 7 files changed, 169 insertions(+), 91 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 08f93a3dc9b..9439c625af7 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -601,11 +601,13 @@ namespace winrt::TerminalApp::implementation const float scale = static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); if (_root->ShouldUsePersistedLayout(_settings)) { - auto state = ApplicationState::SharedInstance(); + auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); - if (state.PersistedInitialSize()) + if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialSize()) { - proposedSize = state.PersistedInitialSize().Value(); + 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; } @@ -703,11 +705,11 @@ namespace winrt::TerminalApp::implementation if (_root->ShouldUsePersistedLayout(_settings)) { - auto state = ApplicationState::SharedInstance(); + auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); - if (state.PersistedInitialPosition()) + if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialPosition()) { - initialPosition = state.PersistedInitialPosition().Value(); + initialPosition = layouts.GetAt(0).InitialPosition().Value(); } } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index a65a464d735..230bf39893a 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -468,9 +468,7 @@ namespace winrt::TerminalApp::implementation if (!_maintainStateOnTabClose && ShouldUsePersistedLayout(_settings)) { auto state = ApplicationState::SharedInstance(); - state.PersistedInitialPosition(nullptr); - state.PersistedInitialSize(nullptr); - state.PersistedTabLayout(nullptr); + state.PersistedWindowLayouts(nullptr); } _LastTabClosedHandlers(*this, nullptr); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 5aab6d273f5..2377f77b160 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -338,10 +338,10 @@ namespace winrt::TerminalApp::implementation // we should use the saved settings. if (ShouldUsePersistedLayout(_settings) && _startupActions.Size() == 1) { - auto actions = ApplicationState::SharedInstance().PersistedTabLayout(); - if (actions && actions.Size() > 0) + auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); + if (layouts && layouts.Size() > 0 && layouts.GetAt(0).TabLayout() && layouts.GetAt(0).TabLayout().Size() > 0) { - _startupActions = actions; + _startupActions = layouts.GetAt(0).TabLayout(); } } @@ -1211,15 +1211,15 @@ namespace winrt::TerminalApp::implementation actions.push_back(action); } - auto state = ApplicationState::SharedInstance(); - state.PersistedTabLayout(winrt::single_threaded_vector(std::move(actions))); + WindowLayout layout{}; + layout.TabLayout(winrt::single_threaded_vector(std::move(actions))); // Only save the content size because the tab size will be added on load. const float contentWidth = ::base::saturated_cast(_tabContent.ActualWidth()); const float contentHeight = ::base::saturated_cast(_tabContent.ActualHeight()); const winrt::Windows::Foundation::Size windowSize{ contentWidth, contentHeight }; - state.PersistedInitialSize(windowSize); + layout.InitialSize(windowSize); if (_hostingHwnd) { @@ -1229,8 +1229,11 @@ namespace winrt::TerminalApp::implementation pos.X = window.left; pos.Y = window.top; - state.PersistedInitialPosition(pos); + layout.InitialPosition(pos); } + + auto state = ApplicationState::SharedInstance(); + state.PersistedWindowLayouts(winrt::single_threaded_vector({ layout })); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index cd57c625ef3..7b8adf495d3 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -5,6 +5,7 @@ #include "ApplicationState.h" #include "CascadiaSettings.h" #include "ApplicationState.g.cpp" +#include "WindowLayout.g.cpp" #include "ActionAndArgs.h" #include "JsonUtils.h" #include "FileUtils.h" @@ -13,104 +14,67 @@ constexpr std::wstring_view stateFileName{ L"state.json" }; namespace Microsoft::Terminal::Settings::Model::JsonUtils { - // This trait exists in order to serialize the std::unordered_set for GeneratedProfiles. - template - struct ConversionTrait> + using namespace winrt::Microsoft::Terminal::Settings::Model; + + template<> + struct ConversionTrait { - std::unordered_set FromJson(const Json::Value& json) const + WindowLayout FromJson(const Json::Value& json) { - ConversionTrait trait; - std::unordered_set val; - val.reserve(json.size()); + auto layout = winrt::make_self(); - for (const auto& element : json) + if (json.isMember("tabLayout")) { - val.emplace(trait.FromJson(element)); + auto val = GetValueForKey>(json, std::string_view("tabLayout")); + layout->TabLayout(val); } - return val; - } - - bool CanConvert(const Json::Value& json) const - { - ConversionTrait trait; - return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); - } - - Json::Value ToJson(const std::unordered_set& val) - { - ConversionTrait trait; - Json::Value json{ Json::arrayValue }; + if (json.isMember("initialPosition")) + { + layout->InitialPosition(GetValueForKey(json, std::string_view("initialPosition"))); + } - for (const auto& key : val) + if (json.isMember("initialSize")) { - json.append(trait.ToJson(key)); + layout->InitialSize(GetValueForKey(json, std::string_view("initialSize"))); } - return json; + return *layout; } - std::string TypeDescription() const + bool CanConvert(const Json::Value& json) { - return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); + return json.isObject(); } - }; - template - struct ConversionTrait> - { - winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) + Json::Value ToJson(const WindowLayout& val) { - winrt::Windows::Foundation::Collections::IVector vec = winrt::single_threaded_vector(); + Json::Value json{ Json::objectValue }; - for (auto it = json.begin(), end = json.end(); it != end; ++it) + if (val.TabLayout()) { - vec.Append(GetValue(*it)); + SetValueForKey(json, std::string_view("tabLayout"), val.TabLayout()); } - return vec; - } - - bool CanConvert(const Json::Value& json) const - { - if (!json.isArray()) - { - return false; - } - ConversionTrait trait; - for (const auto& v : json) + if (val.InitialPosition()) { - if (!trait.CanConvert(v)) - { - return false; - } + SetValueForKey(json, std::string_view("initialPosition"), val.InitialPosition()); } - return true; - } - - Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) - { - Json::Value json{ Json::arrayValue }; - if (val) + if (val.InitialSize()) { - ConversionTrait trait; - for (const auto& v : val) - { - json.append(trait.ToJson(v)); - } + SetValueForKey(json, std::string_view("initialSize"), val.InitialSize()); } return json; } + std::string TypeDescription() const { - return fmt::format("{} array", ConversionTrait{}.TypeDescription()); + return "WindowLayout"; } }; - using namespace winrt::Microsoft::Terminal::Settings::Model; - template<> struct ConversionTrait { @@ -126,7 +90,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return json.isString() || json.isObject(); } - Json::Value ToJson(const winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs& val) + Json::Value ToJson(const ActionAndArgs& val) { return implementation::ActionAndArgs::ToJson(val); } diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 23f40524a89..ba560b29480 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -13,6 +13,7 @@ Module Name: #pragma once #include "ApplicationState.g.h" +#include "WindowLayout.g.h" #include #include @@ -21,14 +22,21 @@ Module Name: // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: // (type, function name, JSON key, ...variadic construction arguments) -#define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ - X(Windows::Foundation::Collections::IVector, PersistedTabLayout, "persistedTabLayout") \ - X(winrt::Windows::Foundation::IReference, PersistedInitialPosition, "persistedInitialPosition") \ - X(winrt::Windows::Foundation::IReference, PersistedInitialSize, "persistedInitialSize") +#define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ + X(Windows::Foundation::Collections::IVector, PersistedWindowLayouts, "persistedWindowLayouts") namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + struct WindowLayout : WindowLayoutT + { + WindowLayout(){}; + + WINRT_PROPERTY(Windows::Foundation::Collections::IVector, TabLayout, nullptr); + WINRT_PROPERTY(winrt::Windows::Foundation::IReference, InitialPosition, nullptr); + WINRT_PROPERTY(winrt::Windows::Foundation::IReference, InitialSize, nullptr); + }; + struct ApplicationState : ApplicationStateT { static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance(); @@ -68,5 +76,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation { + BASIC_FACTORY(WindowLayout) BASIC_FACTORY(ApplicationState); } diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.idl b/src/cascadia/TerminalSettingsModel/ApplicationState.idl index 04a0d7ae066..d60c3431d59 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.idl +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.idl @@ -5,6 +5,15 @@ import "GlobalAppSettings.idl"; namespace Microsoft.Terminal.Settings.Model { + runtimeclass WindowLayout + { + WindowLayout(); + + Windows.Foundation.Collections.IVector TabLayout; + Windows.Foundation.IReference InitialPosition; + Windows.Foundation.IReference InitialSize; + }; + [default_interface] runtimeclass ApplicationState { static ApplicationState SharedInstance(); @@ -12,8 +21,6 @@ namespace Microsoft.Terminal.Settings.Model String FilePath { get; }; - Windows.Foundation.Collections.IVector PersistedTabLayout { get; set; }; - Windows.Foundation.IReference PersistedInitialPosition; - Windows.Foundation.IReference PersistedInitialSize; + Windows.Foundation.Collections.IVector PersistedWindowLayouts { get; set; }; } } diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index 6a2d98b78c8..e2cb058baa9 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -345,7 +345,49 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils } std::string TypeDescription() const { - return fmt::format("{} array", ConversionTrait{}.TypeDescription()); + return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); + } + }; + + template + struct ConversionTrait> + { + std::unordered_set FromJson(const Json::Value& json) const + { + ConversionTrait trait; + std::unordered_set val; + val.reserve(json.size()); + + for (const auto& element : json) + { + val.emplace(trait.FromJson(element)); + } + + return val; + } + + bool CanConvert(const Json::Value& json) const + { + ConversionTrait trait; + return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); + } + + Json::Value ToJson(const std::unordered_set& val) + { + ConversionTrait trait; + Json::Value json{ Json::arrayValue }; + + for (const auto& key : val) + { + json.append(trait.ToJson(key)); + } + + return json; + } + + std::string TypeDescription() const + { + return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); } }; @@ -431,6 +473,59 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils } }; + template + struct ConversionTrait> + { + winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) + { + winrt::Windows::Foundation::Collections::IVector vec = winrt::single_threaded_vector(); + + for (auto it = json.begin(), end = json.end(); it != end; ++it) + { + vec.Append(GetValue(*it)); + } + + return vec; + } + + bool CanConvert(const Json::Value& json) const + { + if (!json.isArray()) + { + return false; + } + ConversionTrait trait; + for (const auto& v : json) + { + if (!trait.CanConvert(v)) + { + return false; + } + } + return true; + } + + Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) + { + Json::Value json{ Json::arrayValue }; + + if (val) + { + ConversionTrait trait; + for (const auto& v : val) + { + json.append(trait.ToJson(v)); + } + } + + return json; + } + std::string TypeDescription() const + { + return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); + } + }; + template struct ConversionTrait> { From 0a3ccd141ea8e534c6ae3dc299a1fc29728363bb Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Sat, 21 Aug 2021 23:30:53 -0400 Subject: [PATCH 17/37] Add consts for json keys --- src/cascadia/TerminalApp/Pane.cpp | 1 - src/cascadia/TerminalApp/TerminalPage.cpp | 2 +- .../TerminalSettingsModel/ApplicationState.cpp | 17 ++++++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 03bdc2e9910..b4dd2dba18d 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -607,7 +607,6 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) else if (parent->_secondChild == oldChild) { parent->_secondChild->Closed(parent->_secondClosedToken); - parent->_secondChild = newChild; } // Clear now to ensure that we can add the child's grid to us later diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 2377f77b160..90af88e7776 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1172,7 +1172,7 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Saves the tab layout to the application state + // - Saves the window position and tab layout to the application state // Arguments: // - // Return Value: diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 7b8adf495d3..d0ea0720518 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -10,7 +10,10 @@ #include "JsonUtils.h" #include "FileUtils.h" -constexpr std::wstring_view stateFileName{ L"state.json" }; +static constexpr std::wstring_view stateFileName{ L"state.json" }; +static constexpr std::string_view TabLayoutKey{ "tabLayout" }; +static constexpr std::string_view InitialPositionKey{ "initialPosition" }; +static constexpr std::string_view InitialSizeKey{ "initialSize" }; namespace Microsoft::Terminal::Settings::Model::JsonUtils { @@ -25,18 +28,18 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils if (json.isMember("tabLayout")) { - auto val = GetValueForKey>(json, std::string_view("tabLayout")); + auto val = GetValueForKey>(json, TabLayoutKey); layout->TabLayout(val); } if (json.isMember("initialPosition")) { - layout->InitialPosition(GetValueForKey(json, std::string_view("initialPosition"))); + layout->InitialPosition(GetValueForKey(json, InitialPositionKey)); } if (json.isMember("initialSize")) { - layout->InitialSize(GetValueForKey(json, std::string_view("initialSize"))); + layout->InitialSize(GetValueForKey(json, InitialSizeKey)); } return *layout; @@ -53,17 +56,17 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils if (val.TabLayout()) { - SetValueForKey(json, std::string_view("tabLayout"), val.TabLayout()); + SetValueForKey(json, TabLayoutKey, val.TabLayout()); } if (val.InitialPosition()) { - SetValueForKey(json, std::string_view("initialPosition"), val.InitialPosition()); + SetValueForKey(json, InitialPositionKey, val.InitialPosition()); } if (val.InitialSize()) { - SetValueForKey(json, std::string_view("initialSize"), val.InitialSize()); + SetValueForKey(json, InitialSizeKey, val.InitialSize()); } return json; From 8d6a7ee3069a5908e4cd73452e3e7ffcbb07dbf8 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 23 Aug 2021 11:59:05 -0400 Subject: [PATCH 18/37] use consts in more places --- src/cascadia/TerminalSettingsModel/ApplicationState.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index d0ea0720518..285befa2487 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -26,18 +26,18 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { auto layout = winrt::make_self(); - if (json.isMember("tabLayout")) + if (json.isMember(TabLayoutKey.data())) { auto val = GetValueForKey>(json, TabLayoutKey); layout->TabLayout(val); } - if (json.isMember("initialPosition")) + if (json.isMember(InitialPositionKey.data())) { layout->InitialPosition(GetValueForKey(json, InitialPositionKey)); } - if (json.isMember("initialSize")) + if (json.isMember(InitialSizeKey.data())) { layout->InitialSize(GetValueForKey(json, InitialSizeKey)); } From 0a6d4f72c681717d811856f88c6454c72f9e8931 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 23 Aug 2021 12:32:15 -0400 Subject: [PATCH 19/37] Add to schema --- doc/cascadia/profiles.schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 13e3a2b0cbb..d8280bffca1 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -1195,6 +1195,11 @@ "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" }, + "persistWindowLayout": { + "default": false, + "description": "When set to true, Windows Terminal will save the tab layout and position information of the window when closed with remaining tabs.", + "type": "boolean" + }, "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.", From c0f7eaa5db0ba8e626de1a10addc013ea2af98e4 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 23 Aug 2021 15:28:57 -0400 Subject: [PATCH 20/37] Save the correct location of the terminal. --- src/cascadia/TerminalApp/TerminalPage.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 90af88e7776..8315ea6f514 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1223,10 +1223,24 @@ namespace winrt::TerminalApp::implementation if (_hostingHwnd) { - RECT window; + // Get the position of the current window. This includes the + // non-client already. + RECT window{}; GetWindowRect(_hostingHwnd.value(), &window); + + // We want to remove the non-client area so calculate that. + // We don't have access to the (NonClient)IslandWindow directly so + // just replicate the logic. + const auto windowStyle = static_cast(GetWindowLong(_hostingHwnd.value(), GWL_STYLE)); + + auto dpi = GetDpiForWindow(_hostingHwnd.value()); + RECT nonClientArea{}; + LOG_IF_WIN32_BOOL_FALSE(AdjustWindowRectExForDpi(&nonClientArea, windowStyle, false, 0, dpi)); + + // The nonClientArea adjustment is negative, so subtract that out. + // This way we save the user-visible location of the terminal. LaunchPosition pos{}; - pos.X = window.left; + pos.X = window.left - nonClientArea.left; pos.Y = window.top; layout.InitialPosition(pos); From 2062eb3745207fc8517494add920b03b0a64db28 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 23 Aug 2021 18:55:35 -0400 Subject: [PATCH 21/37] Save if we are the only window open, instead of if we are the first window only. --- src/cascadia/Remoting/Monarch.cpp | 35 +++++++++++++++++++++++ src/cascadia/Remoting/Monarch.h | 5 ++++ src/cascadia/Remoting/Monarch.idl | 4 +++ src/cascadia/Remoting/WindowManager.cpp | 31 +++++++++++++++++++- src/cascadia/Remoting/WindowManager.h | 4 +++ src/cascadia/Remoting/WindowManager.idl | 4 +++ src/cascadia/TerminalApp/AppLogic.cpp | 8 ++++++ src/cascadia/TerminalApp/AppLogic.h | 1 + src/cascadia/TerminalApp/AppLogic.idl | 1 + src/cascadia/TerminalApp/TerminalPage.cpp | 8 +++++- src/cascadia/TerminalApp/TerminalPage.h | 4 +++ src/cascadia/TerminalApp/TerminalPage.idl | 1 + src/cascadia/WindowsTerminal/AppHost.cpp | 7 +++++ 13 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index 05f9876612c..e8561aaeaba 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -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 (...) @@ -107,6 +109,39 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation } } + void Monarch::SignalClose(const uint64_t peasantId) + { + _peasants.erase(peasantId); + _WindowClosedHandlers(nullptr, nullptr); + } + + // Method Description: + // - Counts the number of living peasants. + // Arguments: + // - + // Return Value: + // - the number of active peasants. + uint64_t Monarch::GetNumberOfPeasants() + { + auto num = 0; + auto callback = [&](auto&& p, auto&& /*id*/) { + // Check that the peasant is alive, and if so increment the count + p.GetID(); + num += 1; + }; + auto onError = [](auto&& id) { + TraceLoggingWrite(g_hRemotingProvider, + "Monarch_GetNumberOfPeasants_Failed", + TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + }; + + _forAllPeasantsIgnoringTheDead(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 diff --git a/src/cascadia/Remoting/Monarch.h b/src/cascadia/Remoting/Monarch.h index 2aa30dab444..1d104823f25 100644 --- a/src/cascadia/Remoting/Monarch.h +++ b/src/cascadia/Remoting/Monarch.h @@ -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); @@ -58,6 +61,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; diff --git a/src/cascadia/Remoting/Monarch.idl b/src/cascadia/Remoting/Monarch.idl index f4e8bd8b4e1..e1c25a68a13 100644 --- a/src/cascadia/Remoting/Monarch.idl +++ b/src/cascadia/Remoting/Monarch.idl @@ -37,9 +37,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(); Windows.Foundation.Collections.IMapView GetPeasantNames { get; }; @@ -47,5 +49,7 @@ namespace Microsoft.Terminal.Remoting event Windows.Foundation.TypedEventHandler FindTargetWindowRequested; event Windows.Foundation.TypedEventHandler ShowTrayIconRequested; event Windows.Foundation.TypedEventHandler HideTrayIconRequested; + event Windows.Foundation.TypedEventHandler WindowCreated; + event Windows.Foundation.TypedEventHandler WindowClosed; }; } diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index 551bd093096..dfe09c9d29a 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -54,6 +54,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // monarch! CoRevokeClassObject(_registrationHostClass); _registrationHostClass = 0; + if (_monarch) + { + _monarch.SignalClose(_peasant.GetID()); + } _monarchWaitInterrupt.SetEvent(); // A thread is joinable once it's been started. Basically this just @@ -64,6 +68,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation } } + void WindowManager::SignalClose() + { + if (_monarch) + { + _monarch.SignalClose(_peasant.GetID()); + } + } + void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args) { // If we're the king, we _definitely_ want to process the arguments, we were @@ -187,6 +199,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // Otherwise, we'll do _nothing_. } + void SignalClose() + { + } + bool WindowManager::ShouldCreateWindow() { return _shouldCreateWindow; @@ -250,9 +266,13 @@ 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. + auto weakThis{ get_weak() }; + + _monarch.WindowCreated([weakThis](auto&&, auto&&) { if (auto mgr{ weakThis.get() }) { mgr->_WindowCreatedHandlers(nullptr, nullptr); } }); + _monarch.WindowClosed([weakThis](auto&&, auto&&) { if (auto mgr{ weakThis.get() }) { mgr->_WindowCreatedHandlers(nullptr, nullptr); } }); _monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested }); _monarch.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); }); _monarch.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); }); @@ -526,6 +546,15 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation return _monarch.GetPeasantNames(); } + uint64_t WindowManager::GetNumberOfPeasants() + { + if (_monarch) + { + return _monarch.GetNumberOfPeasants(); + } + return 0; + } + // Method Description: // - Ask the monarch to show a tray icon. // Arguments: diff --git a/src/cascadia/Remoting/WindowManager.h b/src/cascadia/Remoting/WindowManager.h index 8169f37379b..b8da81cfa38 100644 --- a/src/cascadia/Remoting/WindowManager.h +++ b/src/cascadia/Remoting/WindowManager.h @@ -39,9 +39,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow(); bool IsMonarch(); void SummonWindow(const Remoting::SummonWindowSelectionArgs& args); + void SignalClose(); void SummonAllWindows(); Windows::Foundation::Collections::IMapView GetPeasantNames(); + uint64_t GetNumberOfPeasants(); void RequestShowTrayIcon(); void RequestHideTrayIcon(); @@ -49,6 +51,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); diff --git a/src/cascadia/Remoting/WindowManager.idl b/src/cascadia/Remoting/WindowManager.idl index ca1f9f747b9..d209e9e7c28 100644 --- a/src/cascadia/Remoting/WindowManager.idl +++ b/src/cascadia/Remoting/WindowManager.idl @@ -8,6 +8,7 @@ namespace Microsoft.Terminal.Remoting { WindowManager(); void ProposeCommandline(CommandlineArgs args); + void SignalClose(); Boolean ShouldCreateWindow { get; }; IPeasant CurrentWindow(); Boolean IsMonarch { get; }; @@ -15,10 +16,13 @@ namespace Microsoft.Terminal.Remoting void SummonAllWindows(); void RequestShowTrayIcon(); void RequestHideTrayIcon(); + UInt64 GetNumberOfPeasants(); Boolean DoesQuakeWindowExist(); Windows.Foundation.Collections.IMapView GetPeasantNames(); event Windows.Foundation.TypedEventHandler FindTargetWindowRequested; event Windows.Foundation.TypedEventHandler BecameMonarch; + event Windows.Foundation.TypedEventHandler WindowCreated; + event Windows.Foundation.TypedEventHandler WindowClosed; event Windows.Foundation.TypedEventHandler ShowTrayIconRequested; event Windows.Foundation.TypedEventHandler HideTrayIconRequested; }; diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 9439c625af7..09be604faf1 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -1458,6 +1458,14 @@ namespace winrt::TerminalApp::implementation } } + void AppLogic::SetNumberOfOpenWindows(const uint64_t num) + { + if (_root) + { + _root->SetNumberOfOpenWindows(num); + } + } + void AppLogic::RenameFailed() { if (_root) diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index dbaf301c3cc..16256f94fad 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -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); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 1f7675ad4f7..c34323d9677 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -53,6 +53,7 @@ namespace TerminalApp void IdentifyWindow(); String WindowName; UInt64 WindowId; + void SetNumberOfOpenWindows(UInt64 num); void RenameFailed(); Boolean IsQuakeWindow(); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 8315ea6f514..854f5501dd5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -247,7 +247,8 @@ namespace winrt::TerminalApp::implementation // - true if the ApplicationState should be used. bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const { - return settings.GlobalSettings().PersistWindowLayout() && _WindowId == 1; + // If the setting is enabled, and we are the only window. + return settings.GlobalSettings().PersistWindowLayout() && _numOpenWindows == 1; } winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e) @@ -2957,6 +2958,11 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::SetNumberOfOpenWindows(const uint64_t num) + { + _numOpenWindows = num; + } + // Method Description: // - Returns a label like "Window: 1234" for the ID of this window // Arguments: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index bd456e6ef0c..f52f54107fc 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -108,6 +108,9 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget WindowName(const winrt::hstring& value); uint64_t WindowId() const noexcept; void WindowId(const uint64_t& value); + + void SetNumberOfOpenWindows(const uint64_t value); + winrt::hstring WindowIdForDisplay() const noexcept; winrt::hstring WindowNameForDisplay() const noexcept; bool IsQuakeWindow() const noexcept; @@ -159,6 +162,7 @@ namespace winrt::TerminalApp::implementation bool _isAlwaysOnTop{ false }; winrt::hstring _WindowName{}; uint64_t _WindowId{ 0 }; + uint64_t _numOpenWindows{ 0 }; bool _maintainStateOnTabClose; bool _rearranging; diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 7d4c580e9f3..a57b1aada15 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -33,6 +33,7 @@ namespace TerminalApp UInt64 WindowId; String WindowNameForDisplay { get; }; String WindowIdForDisplay { get; }; + void SetNumberOfOpenWindows(UInt64 num); void RenameFailed(); Boolean IsQuakeWindow(); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 8c554749341..af4fbdadd3a 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -667,6 +667,13 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s _CreateTrayIcon(); } + // Set the number of open windows (so we know if we are the last window) + // and subscribe for updates if there are any changes to that number. + _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); + + _windowManager.WindowCreated([this](auto&&, auto&&) { _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); + _windowManager.WindowClosed([this](auto&&, auto&&) { _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); + // These events are coming from peasants that become or un-become quake windows. _windowManager.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequested(); }); _windowManager.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequested(); }); From 464ca5922044d5dd04aa87bfec414fdfcdd66dd2 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 23 Aug 2021 20:36:05 -0400 Subject: [PATCH 22/37] Code review changes. Also fix restoring position on startup. --- src/cascadia/TerminalApp/Pane.cpp | 31 +++++++--------- src/cascadia/TerminalApp/TerminalTab.cpp | 16 ++++---- .../TerminalSettingsModel/ApplicationState.h | 14 +++---- .../TerminalSettingsModel/JsonUtils.h | 37 ++++++------------- src/cascadia/WindowsTerminal/AppHost.cpp | 4 ++ 5 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index b4dd2dba18d..890605b859a 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -95,9 +95,9 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const { til::color c; // StartingTabColor is prioritized over other colors - if (controlSettings.StartingTabColor()) + if (const auto color = controlSettings.StartingTabColor()) { - c = til::color(controlSettings.StartingTabColor().Value()); + c = til::color(color.Value()); } else { @@ -185,43 +185,38 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n // We now need to execute the commands for each side of the tree // We've done one split, so the first-most child will have currentId, and the // one after it will be incremented. - auto [a1, p1, f1, n1] = _firstChild->BuildStartupActions(currentId, nextId + 1); + const auto firstState = _firstChild->BuildStartupActions(currentId, nextId + 1); // the next id for the second branch depends on how many splits were in the // first child. - auto [a2, p2, f2, n2] = _secondChild->BuildStartupActions(nextId, nextId + n1 + 1); + const auto secondState = _secondChild->BuildStartupActions(nextId, nextId + firstState.panesCreated + 1); - std::vector actions; - actions.reserve(a1.size() + a2.size() + 3); + std::vector actions{}; + actions.reserve(firstState.args.size() + secondState.args.size() + 3); // first we make our split - auto newSplit = buildSplitPane(p2); + const auto newSplit = buildSplitPane(secondState.firstPane); actions.push_back(newSplit); - if (a1.size() > 0) + if (firstState.args.size() > 0) { // Then move to the first child and execute any actions on the left branch // then move back actions.push_back(buildMoveFocus(FocusDirection::PreviousInOrder)); - actions.insert(actions.end(), a1.begin(), a1.end()); + actions.insert(actions.end(), firstState.args.begin(), firstState.args.end()); actions.push_back(buildMoveFocus(FocusDirection::NextInOrder)); } // And if there are any commands to run on the right branch do so - if (a2.size() > 0) + if (secondState.args.size() > 0) { - actions.insert(actions.end(), a2.begin(), a2.end()); + actions.insert(actions.end(), secondState.args.begin(), secondState.args.end()); } // if the tree is well-formed then f1.has_value and f2.has_value are // mutually exclusive. - std::optional focusedPaneId = f1; + const auto focusedPaneId = firstState.focusedPaneId.has_value() ? firstState.focusedPaneId : secondState.focusedPaneId; - if (f2.has_value()) - { - focusedPaneId = f2.value(); - } - - return { { actions }, p1, focusedPaneId, n1 + n2 + 1 }; + return { actions, firstState.firstPane, focusedPaneId, firstState.panesCreated + secondState.panesCreated + 1 }; } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 96e8923ddd7..583fa9cb5b0 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -451,25 +451,25 @@ namespace winrt::TerminalApp::implementation { // Give initial ids (0 for the child created with this tab, // 1 for the child after the first split. - auto [args, pane, focusedPaneId, _] = _rootPane->BuildStartupActions(0, 1); + auto state = _rootPane->BuildStartupActions(0, 1); ActionAndArgs newTabAction{}; newTabAction.Action(ShortcutAction::NewTab); - NewTabArgs newTabArgs{ pane->GetTerminalArgsForPane() }; + NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane() }; newTabAction.Args(newTabArgs); - args.insert(args.begin(), newTabAction); + state.args.insert(state.args.begin(), newTabAction); // If we only have one arg, we only have 1 pane so we don't need any // special focus logic - if (args.size() > 1 && focusedPaneId.has_value()) + if (state.args.size() > 1 && state.focusedPaneId.has_value()) { ActionAndArgs focusPaneAction{}; focusPaneAction.Action(ShortcutAction::FocusPane); - FocusPaneArgs focusArgs{ focusedPaneId.value() }; + FocusPaneArgs focusArgs{ state.focusedPaneId.value() }; focusPaneAction.Args(focusArgs); - args.push_back(focusPaneAction); + state.args.push_back(focusPaneAction); } if (_zoomedPane) @@ -478,10 +478,10 @@ namespace winrt::TerminalApp::implementation ActionAndArgs zoomPaneAction{}; zoomPaneAction.Action(ShortcutAction::TogglePaneZoom); - args.push_back(zoomPaneAction); + state.args.push_back(zoomPaneAction); } - return args; + return state.args; } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index ba560b29480..e0865609686 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -22,18 +22,16 @@ Module Name: // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: // (type, function name, JSON key, ...variadic construction arguments) -#define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ - X(Windows::Foundation::Collections::IVector, PersistedWindowLayouts, "persistedWindowLayouts") - namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + #define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ + X(Windows::Foundation::Collections::IVector, PersistedWindowLayouts, "persistedWindowLayouts") + struct WindowLayout : WindowLayoutT { - WindowLayout(){}; - - WINRT_PROPERTY(Windows::Foundation::Collections::IVector, TabLayout, nullptr); - WINRT_PROPERTY(winrt::Windows::Foundation::IReference, InitialPosition, nullptr); + WINRT_PROPERTY(Windows::Foundation::Collections::IVector, TabLayout, nullptr); + WINRT_PROPERTY(winrt::Windows::Foundation::IReference, InitialPosition, nullptr); WINRT_PROPERTY(winrt::Windows::Foundation::IReference, InitialSize, nullptr); }; diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index e2cb058baa9..c60c16da09d 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -303,15 +303,16 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { std::vector FromJson(const Json::Value& json) { - std::vector vec; - vec.reserve(json.size()); + std::vector val; + val.reserve(json.size()); - for (auto it = json.begin(), end = json.end(); it != end; ++it) + ConversionTrait trait; + for (const auto& element : json) { - vec.push_back(GetValue(*it)); + val.push_back(trait.FromJson(element)); } - return vec; + return val; } bool CanConvert(const Json::Value& json) const @@ -478,35 +479,19 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) { - winrt::Windows::Foundation::Collections::IVector vec = winrt::single_threaded_vector(); - - for (auto it = json.begin(), end = json.end(); it != end; ++it) - { - vec.Append(GetValue(*it)); - } - - return vec; + ConversionTrait> trait; + return winrt::single_threaded_vector(std::move(trait.FromJson(json))); } bool CanConvert(const Json::Value& json) const { - if (!json.isArray()) - { - return false; - } - ConversionTrait trait; - for (const auto& v : json) - { - if (!trait.CanConvert(v)) - { - return false; - } - } - return true; + ConversionTrait> trait; + return trait.CanConvert(json); } Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) { + Json::Value json{ Json::arrayValue }; if (val) diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index af4fbdadd3a..08b10d69257 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -219,6 +219,10 @@ void AppHost::_HandleCommandlineArgs() peasant.DisplayWindowIdRequested({ this, &AppHost::_DisplayWindowId }); + if (_windowManager.IsMonarch()) + { + _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); + } _logic.WindowName(peasant.WindowName()); _logic.WindowId(peasant.GetID()); } From 3b02719345b48be96b1e5043f6b4ed1cda859ffd Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 23 Aug 2021 20:39:44 -0400 Subject: [PATCH 23/37] even more formatting. --- src/cascadia/TerminalSettingsModel/ApplicationState.h | 6 +++--- src/cascadia/TerminalSettingsModel/JsonUtils.h | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index e0865609686..0b089026f87 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -24,9 +24,9 @@ Module Name: // (type, function name, JSON key, ...variadic construction arguments) namespace winrt::Microsoft::Terminal::Settings::Model::implementation { - #define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ - X(Windows::Foundation::Collections::IVector, PersistedWindowLayouts, "persistedWindowLayouts") +#define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ + X(Windows::Foundation::Collections::IVector, PersistedWindowLayouts, "persistedWindowLayouts") struct WindowLayout : WindowLayoutT { diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index c60c16da09d..23ce38a67c4 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -491,7 +491,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) { - Json::Value json{ Json::arrayValue }; if (val) From d204f00d3edb588e7da38632c3b98d0bc193b054 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 23 Aug 2021 21:00:06 -0400 Subject: [PATCH 24/37] minor cleanup --- src/cascadia/Remoting/Monarch.cpp | 6 ++++++ src/cascadia/Remoting/WindowManager.cpp | 9 +-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index e8561aaeaba..cbf9f2bc5e9 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -109,6 +109,12 @@ 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: + // - void Monarch::SignalClose(const uint64_t peasantId) { _peasants.erase(peasantId); diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index dfe09c9d29a..cfd7082f3d3 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -54,10 +54,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // monarch! CoRevokeClassObject(_registrationHostClass); _registrationHostClass = 0; - if (_monarch) - { - _monarch.SignalClose(_peasant.GetID()); - } + SignalClose(); _monarchWaitInterrupt.SetEvent(); // A thread is joinable once it's been started. Basically this just @@ -199,10 +196,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // Otherwise, we'll do _nothing_. } - void SignalClose() - { - } - bool WindowManager::ShouldCreateWindow() { return _shouldCreateWindow; From 1cd14b3526dcf7bcbcb059f2ec39fec903344788 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Tue, 24 Aug 2021 16:40:50 -0400 Subject: [PATCH 25/37] Put json converters in more reasonable places, improve the WindowLayout ct converter. --- .../TerminalSettingsModel/ActionAndArgs.h | 31 ++++++++++ .../ApplicationState.cpp | 61 ++----------------- .../TerminalSettingsModel/ApplicationState.h | 3 + .../TerminalSettingsModel/JsonUtils.h | 15 +---- 4 files changed, 42 insertions(+), 68 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h index 892b5405060..abf9a482383 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h @@ -35,3 +35,34 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation { BASIC_FACTORY(ActionAndArgs); } + +namespace Microsoft::Terminal::Settings::Model::JsonUtils +{ + using namespace winrt::Microsoft::Terminal::Settings::Model; + + template<> + struct ConversionTrait + { + ActionAndArgs FromJson(const Json::Value& json) + { + std::vector v; + return *implementation::ActionAndArgs::FromJson(json, v); + } + + bool CanConvert(const Json::Value& json) const + { + // commands without args might just be a string + return json.isString() || json.isObject(); + } + + Json::Value ToJson(const ActionAndArgs& val) + { + return implementation::ActionAndArgs::ToJson(val); + } + + std::string TypeDescription() const + { + return "ActionAndArgs"; + } + }; +} diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 285befa2487..5a00ba2b410 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -26,21 +26,9 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { auto layout = winrt::make_self(); - if (json.isMember(TabLayoutKey.data())) - { - auto val = GetValueForKey>(json, TabLayoutKey); - layout->TabLayout(val); - } - - if (json.isMember(InitialPositionKey.data())) - { - layout->InitialPosition(GetValueForKey(json, InitialPositionKey)); - } - - if (json.isMember(InitialSizeKey.data())) - { - layout->InitialSize(GetValueForKey(json, InitialSizeKey)); - } + GetValueForKey(json, TabLayoutKey, layout->_TabLayout); + GetValueForKey(json, InitialPositionKey, layout->_InitialPosition); + GetValueForKey(json, InitialSizeKey, layout->_InitialSize); return *layout; } @@ -54,20 +42,9 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { Json::Value json{ Json::objectValue }; - if (val.TabLayout()) - { - SetValueForKey(json, TabLayoutKey, val.TabLayout()); - } - - if (val.InitialPosition()) - { - SetValueForKey(json, InitialPositionKey, val.InitialPosition()); - } - - if (val.InitialSize()) - { - SetValueForKey(json, InitialSizeKey, val.InitialSize()); - } + SetValueForKey(json, TabLayoutKey, val.TabLayout()); + SetValueForKey(json, InitialPositionKey, val.InitialPosition()); + SetValueForKey(json, InitialSizeKey, val.InitialSize()); return json; } @@ -77,32 +54,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return "WindowLayout"; } }; - - template<> - struct ConversionTrait - { - ActionAndArgs FromJson(const Json::Value& json) - { - std::vector v; - return *implementation::ActionAndArgs::FromJson(json, v); - } - - bool CanConvert(const Json::Value& json) - { - // commands without args might just be a string - return json.isString() || json.isObject(); - } - - Json::Value ToJson(const ActionAndArgs& val) - { - return implementation::ActionAndArgs::ToJson(val); - } - - std::string TypeDescription() const - { - return "ActionAndArgs"; - } - }; } using namespace ::Microsoft::Terminal::Settings::Model; diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 0b089026f87..02685ef1631 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -18,6 +18,7 @@ Module Name: #include #include #include +#include // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: @@ -33,6 +34,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation WINRT_PROPERTY(Windows::Foundation::Collections::IVector, TabLayout, nullptr); WINRT_PROPERTY(winrt::Windows::Foundation::IReference, InitialPosition, nullptr); WINRT_PROPERTY(winrt::Windows::Foundation::IReference, InitialSize, nullptr); + + friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait; }; struct ApplicationState : ApplicationStateT diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index 23ce38a67c4..78b087e4f1e 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -317,19 +317,8 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils bool CanConvert(const Json::Value& json) const { - if (!json.isArray()) - { - return false; - } ConversionTrait trait; - for (const auto& v : json) - { - if (!trait.CanConvert(v)) - { - return false; - } - } - return true; + return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) mutable -> bool { return trait.CanConvert(json); }); } Json::Value ToJson(const std::vector& val) @@ -370,7 +359,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils bool CanConvert(const Json::Value& json) const { ConversionTrait trait; - return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); + return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) mutable -> bool { return trait.CanConvert(json); }); } Json::Value ToJson(const std::unordered_set& val) From cfe3583641cbb6a911d9e1d4eb227651a3fc323d Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 25 Aug 2021 23:32:09 -0400 Subject: [PATCH 26/37] try to move the actions more forcefully --- src/cascadia/TerminalApp/Pane.cpp | 14 +++++++------- src/cascadia/TerminalApp/TerminalTab.cpp | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 890605b859a..3819a77214e 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -185,31 +185,31 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n // We now need to execute the commands for each side of the tree // We've done one split, so the first-most child will have currentId, and the // one after it will be incremented. - const auto firstState = _firstChild->BuildStartupActions(currentId, nextId + 1); + auto firstState = _firstChild->BuildStartupActions(currentId, nextId + 1); // the next id for the second branch depends on how many splits were in the // first child. - const auto secondState = _secondChild->BuildStartupActions(nextId, nextId + firstState.panesCreated + 1); + auto secondState = _secondChild->BuildStartupActions(nextId, nextId + firstState.panesCreated + 1); std::vector actions{}; actions.reserve(firstState.args.size() + secondState.args.size() + 3); // first we make our split const auto newSplit = buildSplitPane(secondState.firstPane); - actions.push_back(newSplit); + actions.emplace_back(std::move(newSplit)); if (firstState.args.size() > 0) { // Then move to the first child and execute any actions on the left branch // then move back - actions.push_back(buildMoveFocus(FocusDirection::PreviousInOrder)); - actions.insert(actions.end(), firstState.args.begin(), firstState.args.end()); - actions.push_back(buildMoveFocus(FocusDirection::NextInOrder)); + actions.emplace_back(buildMoveFocus(FocusDirection::PreviousInOrder)); + actions.insert(actions.end(), std::make_move_iterator(std::begin(firstState.args)), std::make_move_iterator(std::end(firstState.args))); + actions.emplace_back(buildMoveFocus(FocusDirection::NextInOrder)); } // And if there are any commands to run on the right branch do so if (secondState.args.size() > 0) { - actions.insert(actions.end(), secondState.args.begin(), secondState.args.end()); + actions.insert(actions.end(), std::make_move_iterator(secondState.args.begin()), std::make_move_iterator(secondState.args.end())); } // if the tree is well-formed then f1.has_value and f2.has_value are diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 583fa9cb5b0..1e2964b829d 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -458,7 +458,7 @@ namespace winrt::TerminalApp::implementation NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane() }; newTabAction.Args(newTabArgs); - state.args.insert(state.args.begin(), newTabAction); + state.args.emplace(state.args.begin(), std::move(newTabAction)); // If we only have one arg, we only have 1 pane so we don't need any // special focus logic @@ -469,7 +469,7 @@ namespace winrt::TerminalApp::implementation FocusPaneArgs focusArgs{ state.focusedPaneId.value() }; focusPaneAction.Args(focusArgs); - state.args.push_back(focusPaneAction); + state.args.emplace_back(std::move(focusPaneAction)); } if (_zoomedPane) @@ -478,7 +478,7 @@ namespace winrt::TerminalApp::implementation ActionAndArgs zoomPaneAction{}; zoomPaneAction.Action(ShortcutAction::TogglePaneZoom); - state.args.push_back(zoomPaneAction); + state.args.emplace_back(std::move(zoomPaneAction)); } return state.args; From 072822a39b263f1a14fad6be19df83731f1d4140 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 25 Aug 2021 20:46:30 -0400 Subject: [PATCH 27/37] Switch to using a first window behavior setting (for future expansion) instead of specific persist window layout setting. --- doc/cascadia/profiles.schema.json | 9 +++++++ src/cascadia/TerminalApp/TerminalPage.cpp | 2 +- .../TerminalSettingsEditor/Launch.cpp | 1 + src/cascadia/TerminalSettingsEditor/Launch.h | 1 + .../TerminalSettingsEditor/Launch.idl | 3 +++ .../TerminalSettingsEditor/Launch.xaml | 9 ++++--- .../Resources/en-US/Resources.resw | 26 ++++++++++++------- .../TerminalSettingsModel/EnumMappings.cpp | 1 + .../TerminalSettingsModel/EnumMappings.h | 1 + .../TerminalSettingsModel/EnumMappings.idl | 1 + .../GlobalAppSettings.cpp | 10 +++---- .../TerminalSettingsModel/GlobalAppSettings.h | 2 +- .../GlobalAppSettings.idl | 8 +++++- .../TerminalSettingsSerializationHelpers.h | 8 ++++++ 14 files changed, 62 insertions(+), 20 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index d8280bffca1..716c70b1fc5 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -1200,6 +1200,15 @@ "description": "When set to true, Windows Terminal will save the tab layout and position information of the window when closed with remaining tabs.", "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.", diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 854f5501dd5..f66d81ab40a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -248,7 +248,7 @@ namespace winrt::TerminalApp::implementation bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const { // If the setting is enabled, and we are the only window. - return settings.GlobalSettings().PersistWindowLayout() && _numOpenWindows == 1; + return settings.GlobalSettings().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout && _numOpenWindows == 1; } winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e) diff --git a/src/cascadia/TerminalSettingsEditor/Launch.cpp b/src/cascadia/TerminalSettingsEditor/Launch.cpp index 99b15cf73cb..0ab482e416d 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.cpp +++ b/src/cascadia/TerminalSettingsEditor/Launch.cpp @@ -17,6 +17,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { InitializeComponent(); + INITIALIZE_BINDABLE_ENUM_SETTING(FirstWindowPreference, FirstWindowPreference, FirstWindowPreference, L"Globals_FirstWindowPreference", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_LaunchMode", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(WindowingBehavior, WindowingMode, WindowingMode, L"Globals_WindowingBehavior", L"Content"); diff --git a/src/cascadia/TerminalSettingsEditor/Launch.h b/src/cascadia/TerminalSettingsEditor/Launch.h index 799a4fc32fc..10d14aa19da 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.h +++ b/src/cascadia/TerminalSettingsEditor/Launch.h @@ -30,6 +30,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(Editor::LaunchPageNavigationState, State, nullptr); + GETSET_BINDABLE_ENUM_SETTING(FirstWindowPreference, Model::FirstWindowPreference, State().Settings().GlobalSettings, FirstWindowPreference); GETSET_BINDABLE_ENUM_SETTING(LaunchMode, Model::LaunchMode, State().Settings().GlobalSettings, LaunchMode); GETSET_BINDABLE_ENUM_SETTING(WindowingBehavior, Model::WindowingMode, State().Settings().GlobalSettings, WindowingBehavior); }; diff --git a/src/cascadia/TerminalSettingsEditor/Launch.idl b/src/cascadia/TerminalSettingsEditor/Launch.idl index d59228bad90..166efb6233c 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.idl +++ b/src/cascadia/TerminalSettingsEditor/Launch.idl @@ -17,6 +17,9 @@ namespace Microsoft.Terminal.Settings.Editor IInspectable CurrentDefaultProfile; + IInspectable CurrentFirstWindowPreference; + IObservableVector FirstWindowPreferenceList { get; }; + IInspectable CurrentLaunchMode; IObservableVector LaunchModeList { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 929e8ef849f..4fd27724861 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -133,11 +133,14 @@ - - - + + + + The number of rows displayed in the window upon first load. Measured in characters. A description for what the "rows" setting does. Presented near "Globals_InitialRows.Header". + + When Terminal starts + Header for a control to select how the terminal should load its first window. + + + What should be shown when the first terminal is created. + + + + Open a tab with the default profile + An option to choose from for the "First window preference" setting. Open the default profile. + + + Open tabs from a previous session + An option to choose from for the "First window preference" setting. Reopen the layout from the last session. + Launch mode Header for a control to select what mode to launch the terminal in. @@ -375,14 +391,6 @@ When enabled, this enables the launch of Windows Terminal at machine startup. A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header". - - Persist terminal window layout - Header for a control to toggle whether the terminal layout should be saved between runs. - - - When enabled, the tab layout, size, and position of the primary terminal window will be saved on close and reloaded on open. - A description for what the "Persist terminal window layout" setting does. Presented near "Globals_PersistWindowLayout.Header". - Always on top Header for a control to toggle if the app will always be presented on top of other windows, or is treated normally (when disabled). @@ -1218,4 +1226,4 @@ Both An option to choose from for the "intense text format" setting. When selected, "intense" text will be rendered as both bold text and in a brighter color - \ No newline at end of file + diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index e0c9d2b45d0..2eb69c64918 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -32,6 +32,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Global Settings DEFINE_ENUM_MAP(winrt::Windows::UI::Xaml::ElementTheme, ElementTheme); DEFINE_ENUM_MAP(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabViewWidthMode); + DEFINE_ENUM_MAP(Model::FirstWindowPreference, FirstWindowPreference); DEFINE_ENUM_MAP(Model::LaunchMode, LaunchMode); DEFINE_ENUM_MAP(Model::TabSwitcherMode, TabSwitcherMode); DEFINE_ENUM_MAP(Microsoft::Terminal::Control::CopyFormat, CopyFormat); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index c597c6a774b..00f3b4ff124 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -28,6 +28,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Global Settings static winrt::Windows::Foundation::Collections::IMap ElementTheme(); static winrt::Windows::Foundation::Collections::IMap TabViewWidthMode(); + static winrt::Windows::Foundation::Collections::IMap FirstWindowPreference(); static winrt::Windows::Foundation::Collections::IMap LaunchMode(); static winrt::Windows::Foundation::Collections::IMap TabSwitcherMode(); static winrt::Windows::Foundation::Collections::IMap CopyFormat(); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index 4a7bb593b7e..341f5699c25 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -10,6 +10,7 @@ namespace Microsoft.Terminal.Settings.Model // Global Settings static Windows.Foundation.Collections.IMap ElementTheme { get; }; static Windows.Foundation.Collections.IMap TabViewWidthMode { get; }; + static Windows.Foundation.Collections.IMap FirstWindowPreference { get; }; static Windows.Foundation.Collections.IMap LaunchMode { get; }; static Windows.Foundation.Collections.IMap TabSwitcherMode { get; }; static Windows.Foundation.Collections.IMap CopyFormat { get; }; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index a2f5ddb58fe..f3a10986f98 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -40,7 +40,7 @@ static constexpr std::string_view LaunchModeKey{ "launchMode" }; static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" }; static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" }; static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" }; -static constexpr std::string_view PersistWindowLayoutKey{ "persistWindowLayout" }; +static constexpr std::string_view FirstWindowPreferenceKey{ "firstWindowPreference" }; static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" }; static constexpr std::string_view LegacyUseTabSwitcherModeKey{ "useTabSwitcher" }; static constexpr std::string_view TabSwitcherModeKey{ "tabSwitcherMode" }; @@ -124,7 +124,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_ForceVTInput = _ForceVTInput; globals->_DebugFeaturesEnabled = _DebugFeaturesEnabled; globals->_StartOnUserLogin = _StartOnUserLogin; - globals->_PersistWindowLayout = _PersistWindowLayout; + globals->_FirstWindowPreference = _FirstWindowPreference; globals->_AlwaysOnTop = _AlwaysOnTop; globals->_TabSwitcherMode = _TabSwitcherMode; globals->_DisableAnimations = _DisableAnimations; @@ -285,6 +285,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste); + JsonUtils::GetValueForKey(json, FirstWindowPreferenceKey, _FirstWindowPreference); + JsonUtils::GetValueForKey(json, LaunchModeKey, _LaunchMode); JsonUtils::GetValueForKey(json, LanguageKey, _Language); @@ -305,8 +307,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin); - JsonUtils::GetValueForKey(json, PersistWindowLayoutKey, _PersistWindowLayout); - JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); // GH#8076 - when adding enum values to this key, we also changed it from @@ -408,6 +408,7 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, CopyFormattingKey, _CopyFormatting); JsonUtils::SetValueForKey(json, WarnAboutLargePasteKey, _WarnAboutLargePaste); JsonUtils::SetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste); + JsonUtils::SetValueForKey(json, FirstWindowPreferenceKey, _FirstWindowPreference); JsonUtils::SetValueForKey(json, LaunchModeKey, _LaunchMode); JsonUtils::SetValueForKey(json, LanguageKey, _Language); JsonUtils::SetValueForKey(json, ThemeKey, _Theme); @@ -418,7 +419,6 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering); JsonUtils::SetValueForKey(json, ForceVTInputKey, _ForceVTInput); JsonUtils::SetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin); - JsonUtils::SetValueForKey(json, PersistWindowLayoutKey, _PersistWindowLayout); JsonUtils::SetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); JsonUtils::SetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode); JsonUtils::SetValueForKey(json, DisableAnimationsKey, _DisableAnimations); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 6cf9d695a9a..c01edfea39d 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -83,6 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::GlobalAppSettings, bool, WarnAboutMultiLinePaste, true); INHERITABLE_SETTING(Model::GlobalAppSettings, Model::LaunchPosition, InitialPosition, nullptr, nullptr); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, CenterOnLaunch, false); + INHERITABLE_SETTING(Model::GlobalAppSettings, Model::FirstWindowPreference, FirstWindowPreference, FirstWindowPreference::DefaultProfile); INHERITABLE_SETTING(Model::GlobalAppSettings, Model::LaunchMode, LaunchMode, LaunchMode::DefaultMode); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, SnapToGridOnResize, true); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ForceFullRepaintRendering, false); @@ -90,7 +91,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ForceVTInput, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DebugFeaturesEnabled, _getDefaultDebugFeaturesValue()); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, StartOnUserLogin, false); - INHERITABLE_SETTING(Model::GlobalAppSettings, bool, PersistWindowLayout, false); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysOnTop, false); INHERITABLE_SETTING(Model::GlobalAppSettings, Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::InOrder); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DisableAnimations, false); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index bdc37bed179..8360180e263 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -34,6 +34,12 @@ namespace Microsoft.Terminal.Settings.Model UseExisting, }; + enum FirstWindowPreference + { + DefaultProfile, + PersistedWindowLayout, + }; + [default_interface] runtimeclass GlobalAppSettings { Guid DefaultProfile; @@ -58,6 +64,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, WarnAboutMultiLinePaste); INHERITABLE_SETTING(LaunchPosition, InitialPosition); INHERITABLE_SETTING(Boolean, CenterOnLaunch); + INHERITABLE_SETTING(FirstWindowPreference, FirstWindowPreference); INHERITABLE_SETTING(LaunchMode, LaunchMode); INHERITABLE_SETTING(Boolean, SnapToGridOnResize); INHERITABLE_SETTING(Boolean, ForceFullRepaintRendering); @@ -65,7 +72,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, ForceVTInput); INHERITABLE_SETTING(Boolean, DebugFeaturesEnabled); INHERITABLE_SETTING(Boolean, StartOnUserLogin); - INHERITABLE_SETTING(Boolean, PersistWindowLayout); INHERITABLE_SETTING(Boolean, AlwaysOnTop); INHERITABLE_SETTING(TabSwitcherMode, TabSwitcherMode); INHERITABLE_SETTING(Boolean, DisableAnimations); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 046b5724c45..0b85dec85fe 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -209,6 +209,14 @@ JSON_ENUM_MAPPER(::winrt::Windows::UI::Xaml::ElementTheme) }; }; +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FirstWindowPreference) +{ + JSON_MAPPINGS(2) = { + pair_type{ "defaultProfile", ValueType::DefaultProfile }, + pair_type{ "persistedWindowLayout", ValueType::PersistedWindowLayout }, + }; +}; + JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::LaunchMode) { JSON_MAPPINGS(5) = { From b8b7d489102c4ea9df1b28a532a1addff50204bb Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 26 Aug 2021 12:31:34 -0400 Subject: [PATCH 28/37] code review: comments, better event handlers, and exception logging. --- src/cascadia/Remoting/Monarch.cpp | 2 +- src/cascadia/Remoting/WindowManager.cpp | 16 ++++++++++++---- src/cascadia/TerminalApp/TerminalPage.cpp | 1 + src/cascadia/WindowsTerminal/AppHost.cpp | 3 +++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index cbf9f2bc5e9..b5d18dd551c 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -138,7 +138,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation auto onError = [](auto&& id) { TraceLoggingWrite(g_hRemotingProvider, "Monarch_GetNumberOfPeasants_Failed", - TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"), + TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not enumerate"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); }; diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index cfd7082f3d3..7eb5d7be050 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -69,7 +69,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation { if (_monarch) { - _monarch.SignalClose(_peasant.GetID()); + try + { + _monarch.SignalClose(_peasant.GetID()); + } + CATCH_LOG() } } @@ -264,8 +268,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation auto weakThis{ get_weak() }; - _monarch.WindowCreated([weakThis](auto&&, auto&&) { if (auto mgr{ weakThis.get() }) { mgr->_WindowCreatedHandlers(nullptr, nullptr); } }); - _monarch.WindowClosed([weakThis](auto&&, auto&&) { if (auto mgr{ weakThis.get() }) { mgr->_WindowCreatedHandlers(nullptr, nullptr); } }); + _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); }); @@ -543,7 +547,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation { if (_monarch) { - return _monarch.GetNumberOfPeasants(); + try + { + return _monarch.GetNumberOfPeasants(); + } + CATCH_LOG() } return 0; } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index f66d81ab40a..0ae5d606d54 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1224,6 +1224,7 @@ namespace winrt::TerminalApp::implementation if (_hostingHwnd) { + // Get the position of the current window. This includes the // non-client already. RECT window{}; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 08b10d69257..5913695a7d4 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -219,6 +219,9 @@ void AppHost::_HandleCommandlineArgs() peasant.DisplayWindowIdRequested({ this, &AppHost::_DisplayWindowId }); + // We need this property to be set before we get the InitialSize/Position + // and BecameMonarch which normally sets it is only run after the window + // is created. if (_windowManager.IsMonarch()) { _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); From 4b1bbbdfba54717822d3453e02817287797c4b03 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 26 Aug 2021 13:13:52 -0400 Subject: [PATCH 29/37] Try to more accurately detect if the user provided no commandline args (we have a full default NewTab action) --- src/cascadia/TerminalApp/TerminalPage.cpp | 18 ++++++++++++++++-- .../TerminalSettingsModel/ActionArgs.h | 19 +++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 0ae5d606d54..f10dac20bbd 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -337,7 +337,22 @@ namespace winrt::TerminalApp::implementation // If the user selected to save their tab layout, we are the first // window opened, and wt was not run with any other arguments, then // we should use the saved settings. - if (ShouldUsePersistedLayout(_settings) && _startupActions.Size() == 1) + auto firstActionIsDefault = [](ActionAndArgs action) { + if (action.Action() != ShortcutAction::NewTab) + { + return false; + } + + // If no commands were given, we will have default args + if (const auto args = action.Args().try_as()) + { + NewTerminalArgs defaultArgs{}; + return args.TerminalArgs() == nullptr || args.TerminalArgs().Equals(defaultArgs); + } + + return false; + }; + if (ShouldUsePersistedLayout(_settings) && _startupActions.Size() == 1 && firstActionIsDefault(_startupActions.GetAt(0))) { auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); if (layouts && layouts.Size() > 0 && layouts.GetAt(0).TabLayout() && layouts.GetAt(0).TabLayout().Size() > 0) @@ -1224,7 +1239,6 @@ namespace winrt::TerminalApp::implementation if (_hostingHwnd) { - // Get the position of the current window. This includes the // non-client already. RECT window{}; diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 1d67ef809b4..bbd03266a8f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -124,14 +124,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool Equals(const Model::NewTerminalArgs& other) { - return other.Commandline() == _Commandline && - other.StartingDirectory() == _StartingDirectory && - other.TabTitle() == _TabTitle && - other.TabColor() == _TabColor && - other.ProfileIndex() == _ProfileIndex && - other.Profile() == _Profile && - other.SuppressApplicationTitle() == _SuppressApplicationTitle && - other.ColorScheme() == _ColorScheme; + // Treat "default value" as the same as nullopt so that we can + // compare two terminal args that don't have all settings set. + auto nullEqual = [](auto first, auto& second) { return (first == second) || (first == decltype(first){} && second == std::nullopt); }; + return nullEqual(other.Commandline(), _Commandline) && + nullEqual(other.StartingDirectory(), _StartingDirectory) && + nullEqual(other.TabTitle(), _TabTitle) && + nullEqual(other.TabColor(), _TabColor) && + nullEqual(other.ProfileIndex(), _ProfileIndex) && + nullEqual(other.Profile(), _Profile) && + nullEqual(other.SuppressApplicationTitle(), _SuppressApplicationTitle) && + nullEqual(other.ColorScheme(), _ColorScheme); }; static Model::NewTerminalArgs FromJson(const Json::Value& json) { From 81af0651a2e3e99978f866ac114d7e0ab2bc4f34 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Tue, 31 Aug 2021 14:40:05 -0400 Subject: [PATCH 30/37] even merges arent safe from the formatter. --- src/cascadia/TerminalSettingsModel/ApplicationState.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 122158b0111..5edf21a7eda 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -25,8 +25,8 @@ Module Name: // (type, function name, JSON key, ...variadic construction arguments) namespace winrt::Microsoft::Terminal::Settings::Model::implementation { -#define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ +#define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ X(Windows::Foundation::Collections::IVector, PersistedWindowLayouts, "persistedWindowLayouts") \ X(Windows::Foundation::Collections::IVector, RecentCommands, "recentCommands") From ef21ca6a9298d3d08eeb5bb1c048270d0713befd Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Tue, 31 Aug 2021 14:46:49 -0400 Subject: [PATCH 31/37] remove old setting from the schema. --- doc/cascadia/profiles.schema.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 28b0dc66fb8..632c6b79f9b 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -1212,11 +1212,6 @@ "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" }, - "persistWindowLayout": { - "default": false, - "description": "When set to true, Windows Terminal will save the tab layout and position information of the window when closed with remaining tabs.", - "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.", From 6aeabb16a51c5da0d3e3c43c327f75f6f45e128f Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 2 Sep 2021 15:38:16 -0400 Subject: [PATCH 32/37] Make the NewTerminalArgs::Equals more like the other Equals implementations and less weird. --- .../TerminalSettingsModel/ActionArgs.h | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 69ee6cb4649..021c792d0e0 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -125,17 +125,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool Equals(const Model::NewTerminalArgs& other) { - // Treat "default value" as the same as nullopt so that we can - // compare two terminal args that don't have all settings set. - auto nullEqual = [](auto first, auto& second) { return (first == second) || (first == decltype(first){} && second == std::nullopt); }; - return nullEqual(other.Commandline(), _Commandline) && - nullEqual(other.StartingDirectory(), _StartingDirectory) && - nullEqual(other.TabTitle(), _TabTitle) && - nullEqual(other.TabColor(), _TabColor) && - nullEqual(other.ProfileIndex(), _ProfileIndex) && - nullEqual(other.Profile(), _Profile) && - nullEqual(other.SuppressApplicationTitle(), _SuppressApplicationTitle) && - nullEqual(other.ColorScheme(), _ColorScheme); + + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_Commandline == _Commandline && + otherAsUs->_StartingDirectory == _StartingDirectory && + otherAsUs->_TabTitle == _TabTitle && + otherAsUs->_TabColor == _TabColor && + otherAsUs->_ProfileIndex == _ProfileIndex && + otherAsUs->_Profile == _Profile && + otherAsUs->_SuppressApplicationTitle == _SuppressApplicationTitle && + otherAsUs->_ColorScheme == _ColorScheme; + } + return false; }; static Model::NewTerminalArgs FromJson(const Json::Value& json) { From 7f14c7dc7c903fb2ab137e3fa1a884b510e1556b Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Thu, 2 Sep 2021 15:40:32 -0400 Subject: [PATCH 33/37] formatting --- src/cascadia/TerminalSettingsModel/ActionArgs.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 021c792d0e0..96070b10bc6 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -125,7 +125,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool Equals(const Model::NewTerminalArgs& other) { - auto otherAsUs = other.try_as(); if (otherAsUs) { From 8e2223160fbbdfcf675fd291e3c82ef5ed4b577b Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Fri, 3 Sep 2021 12:45:14 -0400 Subject: [PATCH 34/37] Add a feature flag to disable this feature in release. --- src/cascadia/TerminalApp/TerminalPage.cpp | 4 +++- src/cascadia/TerminalSettingsEditor/Launch.cpp | 5 +++++ src/cascadia/TerminalSettingsEditor/Launch.h | 2 ++ src/cascadia/TerminalSettingsEditor/Launch.idl | 3 +++ src/cascadia/TerminalSettingsEditor/Launch.xaml | 3 ++- src/features.xml | 9 +++++++++ 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 4e8c2bd153e..fdba888a5f7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -286,7 +286,9 @@ namespace winrt::TerminalApp::implementation bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const { // If the setting is enabled, and we are the only window. - return settings.GlobalSettings().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout && _numOpenWindows == 1; + return Feature_PersistedWindowLayout::IsEnabled() && + settings.GlobalSettings().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout && + _numOpenWindows == 1; } winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e) diff --git a/src/cascadia/TerminalSettingsEditor/Launch.cpp b/src/cascadia/TerminalSettingsEditor/Launch.cpp index ca95dec5901..09215561de8 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.cpp +++ b/src/cascadia/TerminalSettingsEditor/Launch.cpp @@ -69,4 +69,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return winrt::single_threaded_observable_vector(std::move(profiles)); } + + bool Launch::ShowFirstWindowPreference() const noexcept + { + return Feature_PersistedWindowLayout::IsEnabled(); + } } diff --git a/src/cascadia/TerminalSettingsEditor/Launch.h b/src/cascadia/TerminalSettingsEditor/Launch.h index bcab4dcf6ed..baf110c52e4 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.h +++ b/src/cascadia/TerminalSettingsEditor/Launch.h @@ -29,6 +29,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void CurrentDefaultProfile(const IInspectable& value); winrt::Windows::Foundation::Collections::IObservableVector DefaultProfiles() const; + bool ShowFirstWindowPreference() const noexcept; + WINRT_PROPERTY(Editor::LaunchPageNavigationState, State, nullptr); GETSET_BINDABLE_ENUM_SETTING(FirstWindowPreference, Model::FirstWindowPreference, State().Settings().GlobalSettings, FirstWindowPreference); diff --git a/src/cascadia/TerminalSettingsEditor/Launch.idl b/src/cascadia/TerminalSettingsEditor/Launch.idl index e7ba647d1dc..1dfc9e86bb5 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.idl +++ b/src/cascadia/TerminalSettingsEditor/Launch.idl @@ -20,6 +20,9 @@ namespace Microsoft.Terminal.Settings.Editor // https://github.com/microsoft/microsoft-ui-xaml/issues/5395 IObservableVector DefaultProfiles { get; }; + + Boolean ShowFirstWindowPreference { get; }; + IInspectable CurrentFirstWindowPreference; IObservableVector FirstWindowPreferenceList { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index e5e1785ee28..ece569affe0 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -134,7 +134,8 @@ - + diff --git a/src/features.xml b/src/features.xml index c50399f4a19..14c1364ff8a 100644 --- a/src/features.xml +++ b/src/features.xml @@ -79,4 +79,13 @@ + + + Feature_PersistedWindowLayout + Whether to allow the user to enable persisted window layout saving and loading + 766 + AlwaysEnabled + + + From 735de10333495958501f0e40a243de1bb79005cf Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Fri, 3 Sep 2021 17:02:29 -0400 Subject: [PATCH 35/37] formatting --- src/cascadia/Remoting/Monarch.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index d61a9080491..d6a12c8c39d 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -130,8 +130,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation uint64_t Monarch::GetNumberOfPeasants() { auto num = 0; - auto callback = [&](const auto& /*id*/, const auto& p) - { + auto callback = [&](const auto& /*id*/, const auto& p) { // Check that the peasant is alive, and if so increment the count p.GetID(); num += 1; From 230a1aba2a5740adc43da5e845994356cd051687 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Fri, 3 Sep 2021 18:47:25 -0400 Subject: [PATCH 36/37] Code review: Make more things const that can be const --- src/cascadia/Remoting/WindowManager.cpp | 2 -- src/cascadia/TerminalApp/AppLogic.cpp | 4 ++-- src/cascadia/TerminalApp/Pane.cpp | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index 1949fb582e0..668178892b1 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -266,8 +266,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // done when we become the king. This will be called both for the first // window, and when the current monarch dies. - auto weakThis{ get_weak() }; - _monarch.WindowCreated({ get_weak(), &WindowManager::_WindowCreatedHandlers }); _monarch.WindowClosed({ get_weak(), &WindowManager::_WindowClosedHandlers }); _monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested }); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 144a4e0a5cf..6680fd75bb7 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -601,7 +601,7 @@ namespace winrt::TerminalApp::implementation const float scale = static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); if (_root->ShouldUsePersistedLayout(_settings)) { - auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); + const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialSize()) { @@ -705,7 +705,7 @@ namespace winrt::TerminalApp::implementation if (_root->ShouldUsePersistedLayout(_settings)) { - auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); + const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialPosition()) { diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index eaa74d8556a..0898f0613eb 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -144,7 +144,7 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n auto buildSplitPane = [&](auto newPane) { ActionAndArgs actionAndArgs; actionAndArgs.Action(ShortcutAction::SplitPane); - auto terminalArgs{ newPane->GetTerminalArgsForPane() }; + const auto terminalArgs{ newPane->GetTerminalArgsForPane() }; // When creating a pane the split size is the size of the new pane // and not position. SplitPaneArgs args{ SplitType::Manual, _splitState, 1. - _desiredSplitPosition, terminalArgs }; From 8f38e1b9f8010fe4afff32d2887c360397c2eaf7 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Wed, 8 Sep 2021 18:19:24 -0400 Subject: [PATCH 37/37] Initialize properties that were uninitialized --- src/cascadia/TerminalApp/TerminalPage.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 2d378f6d5b5..6451e5158a2 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -164,10 +164,10 @@ namespace winrt::TerminalApp::implementation uint64_t _WindowId{ 0 }; uint64_t _numOpenWindows{ 0 }; - bool _maintainStateOnTabClose; - bool _rearranging; - std::optional _rearrangeFrom; - std::optional _rearrangeTo; + bool _maintainStateOnTabClose{ false }; + bool _rearranging{ false }; + std::optional _rearrangeFrom{}; + std::optional _rearrangeTo{}; bool _removing{ false }; uint32_t _systemRowsToScroll{ DefaultRowsToScroll };