From 936c01f948ac58d1a520dc7fbced8bb6d25c36b6 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 30 Jan 2023 14:52:19 -0600 Subject: [PATCH 01/25] Start splitting AppLogic into AppLogic and Window logic We'll need this for #5000, for ainulindale. This refactoring will be annoying enough as it is so we may as well do it as a first, separate PR. --- src/cascadia/TerminalApp/App.cpp | 30 +- src/cascadia/TerminalApp/AppLogic.cpp | 1085 +--------------- src/cascadia/TerminalApp/AppLogic.h | 136 +- src/cascadia/TerminalApp/AppLogic.idl | 105 +- .../TerminalApp/TerminalAppLib.vcxproj | 7 + src/cascadia/TerminalApp/TerminalWindow.cpp | 1140 +++++++++++++++++ src/cascadia/TerminalApp/TerminalWindow.h | 181 +++ src/cascadia/TerminalApp/TerminalWindow.idl | 131 ++ 8 files changed, 1516 insertions(+), 1299 deletions(-) create mode 100644 src/cascadia/TerminalApp/TerminalWindow.cpp create mode 100644 src/cascadia/TerminalApp/TerminalWindow.h create mode 100644 src/cascadia/TerminalApp/TerminalWindow.idl diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index 138d4a7d249..1a22cfc5b32 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -77,22 +77,24 @@ namespace winrt::TerminalApp::implementation /// Details about the launch request and process. void App::OnLaunched(const LaunchActivatedEventArgs& /*e*/) { + // TODO! UWP mode is straight up not supported anymore, yea? + // // if this is a UWP... it means its our problem to hook up the content to the window here. - if (_isUwp) - { - auto content = Window::Current().Content(); - if (content == nullptr) - { - auto logic = Logic(); - logic.RunAsUwp(); // Must set UWP status first, settings might change based on it. - logic.ReloadSettings(); - logic.Create(); + //if (_isUwp) + //{ + // auto content = Window::Current().Content(); + // if (content == nullptr) + // { + // auto logic = Logic(); + // logic.RunAsUwp(); // Must set UWP status first, settings might change based on it. + // logic.ReloadSettings(); + // logic.Create(); - auto page = logic.GetRoot().as(); + // auto page = logic.GetRoot().as(); - Window::Current().Content(page); - Window::Current().Activate(); - } - } + // Window::Current().Content(page); + // Window::Current().Activate(); + // } + //} } } diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 4381e24e0b1..4d5cabbed7c 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -31,49 +31,14 @@ namespace winrt using IInspectable = Windows::Foundation::IInspectable; } -static constexpr std::wstring_view StartupTaskName = L"StartTerminalOnLoginTask"; +//////////////////////////////////////////////////////////////////////////////// +// TODO! This section probably should be in TerminalWindow with the warnings + // clang-format off -// !!! IMPORTANT !!! -// Make sure that these keys are in the same order as the -// SettingsLoadWarnings/Errors enum is! -static const std::array settingsLoadWarningsLabels { - USES_RESOURCE(L"MissingDefaultProfileText"), - USES_RESOURCE(L"DuplicateProfileText"), - USES_RESOURCE(L"UnknownColorSchemeText"), - USES_RESOURCE(L"InvalidBackgroundImage"), - USES_RESOURCE(L"InvalidIcon"), - USES_RESOURCE(L"AtLeastOneKeybindingWarning"), - USES_RESOURCE(L"TooManyKeysForChord"), - USES_RESOURCE(L"MissingRequiredParameter"), - USES_RESOURCE(L"FailedToParseCommandJson"), - USES_RESOURCE(L"FailedToWriteToSettings"), - USES_RESOURCE(L"InvalidColorSchemeInCmd"), - USES_RESOURCE(L"InvalidSplitSize"), - USES_RESOURCE(L"FailedToParseStartupActions"), - USES_RESOURCE(L"FailedToParseSubCommands"), - USES_RESOURCE(L"UnknownTheme"), - USES_RESOURCE(L"DuplicateRemainingProfilesEntry"), -}; static const std::array settingsLoadErrorsLabels { USES_RESOURCE(L"NoProfilesText"), USES_RESOURCE(L"AllProfilesHiddenText") }; -// clang-format on - -static_assert(settingsLoadWarningsLabels.size() == static_cast(SettingsLoadWarnings::WARNINGS_SIZE)); -static_assert(settingsLoadErrorsLabels.size() == static_cast(SettingsLoadErrors::ERRORS_SIZE)); - -// Function Description: -// - General-purpose helper for looking up a localized string for a -// warning/error. First will look for the given key in the provided map of -// keys->strings, where the values in the map are ResourceKeys. If it finds -// one, it will lookup the localized string from that ResourceKey. -// - If it does not find a key, it'll return an empty string -// Arguments: -// - key: the value to use to look for a resource key in the given map -// - map: A map of keys->Resource keys. -// Return Value: -// - the localized string for the given type, if it exists. template winrt::hstring _GetMessageText(uint32_t index, const T& keys) { @@ -83,20 +48,6 @@ winrt::hstring _GetMessageText(uint32_t index, const T& keys) } return {}; } - -// Function Description: -// - Gets the text from our ResourceDictionary for the given -// SettingsLoadWarning. If there is no such text, we'll return nullptr. -// - The warning should have an entry in settingsLoadWarningsLabels. -// Arguments: -// - warning: the SettingsLoadWarnings value to get the localized text for. -// Return Value: -// - localized text for the given warning -static winrt::hstring _GetWarningText(SettingsLoadWarnings warning) -{ - return _GetMessageText(static_cast(warning), settingsLoadWarningsLabels); -} - // Function Description: // - Gets the text from our ResourceDictionary for the given // SettingsLoadError. If there is no such text, we'll return nullptr. @@ -110,30 +61,11 @@ static winrt::hstring _GetErrorText(SettingsLoadErrors error) return _GetMessageText(static_cast(error), settingsLoadErrorsLabels); } -// Function Description: -// - Creates a Run of text to display an error message. The text is yellow or -// red for dark/light theme, respectively. -// Arguments: -// - text: The text of the error message. -// - resources: The application's resource loader. -// Return Value: -// - The fully styled text run. -static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceDictionary& resources) -{ - Documents::Run textRun; - textRun.Text(text); +// clang-format on - // Color the text red (light theme) or yellow (dark theme) based on the system theme - auto key = winrt::box_value(L"ErrorTextBrush"); - if (resources.HasKey(key)) - { - auto g = resources.Lookup(key); - auto brush = g.try_as(); - textRun.Foreground(brush); - } +//////////////////////////////////////////////////////////////////////////////// - return textRun; -} +static constexpr std::wstring_view StartupTaskName = L"StartTerminalOnLoginTask"; namespace winrt::TerminalApp::implementation { @@ -187,7 +119,6 @@ namespace winrt::TerminalApp::implementation // make sure that there's a terminal page for callers of // SetTitleBarContent _isElevated = ::Microsoft::Console::Utils::IsElevated(); - _root = winrt::make_self(); _reloadSettings = std::make_shared>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() { if (auto self{ weakSelf.get() }) @@ -201,13 +132,6 @@ namespace winrt::TerminalApp::implementation }); } - // Method Description: - // - Implements the IInitializeWithWindow interface from shobjidl_core. - HRESULT AppLogic::Initialize(HWND hwnd) - { - return _root->Initialize(hwnd); - } - // Method Description: // - Called around the codebase to discover if this is a UWP where we need to turn off specific settings. // Arguments: @@ -257,8 +181,6 @@ namespace winrt::TerminalApp::implementation // this as a MTA, before the app is Create()'d WINRT_ASSERT(_loadedInitialSettings); - _root->DialogPresenter(*this); - // In UWP mode, we cannot handle taking over the title bar for tabs, // so this setting is overridden to false no matter what the preference is. if (_isUwp) @@ -266,39 +188,8 @@ namespace winrt::TerminalApp::implementation _settings.GlobalSettings().ShowTabsInTitlebar(false); } - // Pay attention, that even if some command line arguments were parsed (like launch mode), - // we will not use the startup actions from settings. - // While this simplifies the logic, we might want to reconsider this behavior in the future. - if (!_hasCommandLineArguments && _hasSettingsStartupActions) + // TODO! These used to be in `_root->Initialized`: { - _root->SetStartupActions(_settingsAppArgs.GetStartupActions()); - } - - _root->SetSettings(_settings, false); - _root->Loaded({ this, &AppLogic::_OnLoaded }); - _root->Initialized([this](auto&&, auto&&) { - // GH#288 - When we finish initialization, if the user wanted us - // launched _fullscreen_, toggle fullscreen mode. This will make sure - // that the window size is _first_ set up as something sensible, so - // leaving fullscreen returns to a reasonable size. - const auto launchMode = this->GetLaunchMode(); - if (IsQuakeWindow() || WI_IsFlagSet(launchMode, LaunchMode::FocusMode)) - { - _root->SetFocusMode(true); - } - - // The IslandWindow handles (creating) the maximized state - // we just want to record it here on the page as well. - if (WI_IsFlagSet(launchMode, LaunchMode::MaximizedMode)) - { - _root->Maximized(true); - } - - if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !IsQuakeWindow()) - { - _root->SetFullscreen(true); - } - // Both LoadSettings and ReloadSettings are supposed to call this function, // but LoadSettings skips it, so that the UI starts up faster. // Now that the UI is present we can do them with a less significant UX impact. @@ -329,16 +220,11 @@ namespace winrt::TerminalApp::implementation TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } - }); - _root->Create(); + }; _ApplyLanguageSettingChange(); - _RefreshThemeRoutine(); _ApplyStartupTaskStateChange(); - auto args = winrt::make_self(RS_(L"SettingsMenuItem"), SystemMenuChangeAction::Add, SystemMenuItemHandler(this, &AppLogic::_OpenSettingsUI)); - _SystemMenuChangeRequestedHandlers(*this, *args); - TraceLoggingWrite( g_hTerminalAppProvider, "AppCreated", @@ -348,459 +234,6 @@ namespace winrt::TerminalApp::implementation TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } - void AppLogic::Quit() - { - if (_root) - { - _root->CloseWindow(true); - } - } - - // Method Description: - // - Show a ContentDialog with buttons to take further action. Uses the - // FrameworkElements provided as the title and content of this dialog, and - // displays buttons (or a single button). Two buttons (primary and secondary) - // will be displayed if this is an warning dialog for closing the terminal, - // this allows the users to abandon the closing action. Otherwise, a single - // close button will be displayed. - // - Only one dialog can be visible at a time. If another dialog is visible - // when this is called, nothing happens. - // Arguments: - // - dialog: the dialog object that is going to show up - // Return value: - // - an IAsyncOperation with the dialog result - winrt::Windows::Foundation::IAsyncOperation AppLogic::ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog) - { - // DON'T release this lock in a wil::scope_exit. The scope_exit will get - // called when we await, which is not what we want. - std::unique_lock lock{ _dialogLock, std::try_to_lock }; - if (!lock) - { - // Another dialog is visible. - co_return ContentDialogResult::None; - } - - _dialog = dialog; - - // IMPORTANT: This is necessary as documented in the ContentDialog MSDN docs. - // Since we're hosting the dialog in a Xaml island, we need to connect it to the - // xaml tree somehow. - dialog.XamlRoot(_root->XamlRoot()); - - // IMPORTANT: Set the requested theme of the dialog, because the - // PopupRoot isn't directly in the Xaml tree of our root. So the dialog - // won't inherit our RequestedTheme automagically. - // GH#5195, GH#3654 Because we cannot set RequestedTheme at the application level, - // we occasionally run into issues where parts of our UI end up themed incorrectly. - // Dialogs, for example, live under a different Xaml root element than the rest of - // our application. This makes our popup menus and buttons "disappear" when the - // user wants Terminal to be in a different theme than the rest of the system. - // This hack---and it _is_ a hack--walks up a dialog's ancestry and forces the - // theme on each element up to the root. We're relying a bit on Xaml's implementation - // details here, but it does have the desired effect. - // It's not enough to set the theme on the dialog alone. - auto themingLambda{ [this](const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&) { - auto theme{ _settings.GlobalSettings().CurrentTheme() }; - auto requestedTheme{ theme.RequestedTheme() }; - auto element{ sender.try_as() }; - while (element) - { - element.RequestedTheme(requestedTheme); - element = element.Parent().try_as(); - } - } }; - - themingLambda(dialog, nullptr); // if it's already in the tree - auto loadedRevoker{ dialog.Loaded(winrt::auto_revoke, themingLambda) }; // if it's not yet in the tree - - // Display the dialog. - co_return co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup); - - // After the dialog is dismissed, the dialog lock (held by `lock`) will - // be released so another can be shown - } - - // Method Description: - // - Dismiss the (only) visible ContentDialog - void AppLogic::DismissDialog() - { - if (auto localDialog = std::exchange(_dialog, nullptr)) - { - localDialog.Hide(); - } - } - - // Method Description: - // - Displays a dialog for errors found while loading or validating the - // settings. Uses the resources under the provided title and content keys - // as the title and first content of the dialog, then also displays a - // message for whatever exception was found while validating the settings. - // - Only one dialog can be visible at a time. If another dialog is visible - // when this is called, nothing happens. See ShowDialog for details - // Arguments: - // - titleKey: The key to use to lookup the title text from our resources. - // - contentKey: The key to use to lookup the content text from our resources. - void AppLogic::_ShowLoadErrorsDialog(const winrt::hstring& titleKey, - const winrt::hstring& contentKey, - HRESULT settingsLoadedResult) - { - auto title = GetLibraryResourceString(titleKey); - auto buttonText = RS_(L"Ok"); - - Controls::TextBlock warningsTextBlock; - // Make sure you can copy-paste - warningsTextBlock.IsTextSelectionEnabled(true); - // Make sure the lines of text wrap - warningsTextBlock.TextWrapping(TextWrapping::Wrap); - - winrt::Windows::UI::Xaml::Documents::Run errorRun; - const auto errorLabel = GetLibraryResourceString(contentKey); - errorRun.Text(errorLabel); - warningsTextBlock.Inlines().Append(errorRun); - warningsTextBlock.Inlines().Append(Documents::LineBreak{}); - - if (FAILED(settingsLoadedResult)) - { - if (!_settingsLoadExceptionText.empty()) - { - warningsTextBlock.Inlines().Append(_BuildErrorRun(_settingsLoadExceptionText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources())); - warningsTextBlock.Inlines().Append(Documents::LineBreak{}); - } - } - - // Add a note that we're using the default settings in this case. - winrt::Windows::UI::Xaml::Documents::Run usingDefaultsRun; - const auto usingDefaultsText = RS_(L"UsingDefaultSettingsText"); - usingDefaultsRun.Text(usingDefaultsText); - warningsTextBlock.Inlines().Append(Documents::LineBreak{}); - warningsTextBlock.Inlines().Append(usingDefaultsRun); - - Controls::ContentDialog dialog; - dialog.Title(winrt::box_value(title)); - dialog.Content(winrt::box_value(warningsTextBlock)); - dialog.CloseButtonText(buttonText); - dialog.DefaultButton(Controls::ContentDialogButton::Close); - - ShowDialog(dialog); - } - - // Method Description: - // - Displays a dialog for warnings found while loading or validating the - // settings. Displays messages for whatever warnings were found while - // validating the settings. - // - Only one dialog can be visible at a time. If another dialog is visible - // when this is called, nothing happens. See ShowDialog for details - void AppLogic::_ShowLoadWarningsDialog() - { - auto title = RS_(L"SettingsValidateErrorTitle"); - auto buttonText = RS_(L"Ok"); - - Controls::TextBlock warningsTextBlock; - // Make sure you can copy-paste - warningsTextBlock.IsTextSelectionEnabled(true); - // Make sure the lines of text wrap - warningsTextBlock.TextWrapping(TextWrapping::Wrap); - - for (const auto& warning : _warnings) - { - // Try looking up the warning message key for each warning. - const auto warningText = _GetWarningText(warning); - if (!warningText.empty()) - { - warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources())); - warningsTextBlock.Inlines().Append(Documents::LineBreak{}); - } - } - - Controls::ContentDialog dialog; - dialog.Title(winrt::box_value(title)); - dialog.Content(winrt::box_value(warningsTextBlock)); - dialog.CloseButtonText(buttonText); - dialog.DefaultButton(Controls::ContentDialogButton::Close); - - ShowDialog(dialog); - } - - // Method Description: - // - Triggered when the application is finished loading. If we failed to load - // the settings, then this will display the error dialog. This is done - // here instead of when loading the settings, because we need our UI to be - // visible to display the dialog, and when we're loading the settings, - // the UI might not be visible yet. - // Arguments: - // - - void AppLogic::_OnLoaded(const IInspectable& /*sender*/, - const RoutedEventArgs& /*eventArgs*/) - { - if (_settings.GlobalSettings().InputServiceWarning()) - { - const auto keyboardServiceIsDisabled = !_IsKeyboardServiceEnabled(); - if (keyboardServiceIsDisabled) - { - _root->ShowKeyboardServiceWarning(); - } - } - - if (FAILED(_settingsLoadedResult)) - { - const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle"); - const winrt::hstring textKey = USES_RESOURCE(L"InitialJsonParseErrorText"); - _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); - } - else if (_settingsLoadedResult == S_FALSE) - { - _ShowLoadWarningsDialog(); - } - } - - // Method Description: - // - Helper for determining if the "Touch Keyboard and Handwriting Panel - // Service" is enabled. If it isn't, we want to be able to display a - // warning to the user, because they won't be able to type in the - // Terminal. - // Return Value: - // - true if the service is enabled, or if we fail to query the service. We - // return true in that case, to be less noisy (though, that is unexpected) - bool AppLogic::_IsKeyboardServiceEnabled() - { - if (IsUwp()) - { - return true; - } - - // If at any point we fail to open the service manager, the service, - // etc, then just quick return true to disable the dialog. We'd rather - // not be noisy with this dialog if we failed for some reason. - - // Open the service manager. This will return 0 if it failed. - wil::unique_schandle hManager{ OpenSCManagerW(nullptr, nullptr, 0) }; - - if (LOG_LAST_ERROR_IF(!hManager.is_valid())) - { - return true; - } - - // Get a handle to the keyboard service - wil::unique_schandle hService{ OpenServiceW(hManager.get(), TabletInputServiceKey.data(), SERVICE_QUERY_STATUS) }; - - // Windows 11 doesn't have a TabletInputService. - // (It was renamed to TextInputManagementService, because people kept thinking that a - // service called "tablet-something" is system-irrelevant on PCs and can be disabled.) - if (!hService.is_valid()) - { - return true; - } - - // Get the current state of the service - SERVICE_STATUS status{ 0 }; - if (!LOG_IF_WIN32_BOOL_FALSE(QueryServiceStatus(hService.get(), &status))) - { - return true; - } - - const auto state = status.dwCurrentState; - return (state == SERVICE_RUNNING || state == SERVICE_START_PENDING); - } - - // Method Description: - // - Get the size in pixels of the client area we'll need to launch this - // terminal app. This method will use the default profile's settings to do - // this calculation, as well as the _system_ dpi scaling. See also - // TermControl::GetProposedDimensions. - // Arguments: - // - - // Return Value: - // - a point containing the requested dimensions in pixels. - winrt::Windows::Foundation::Size AppLogic::GetLaunchDimensions(uint32_t dpi) - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - winrt::Windows::Foundation::Size proposedSize{}; - - const auto scale = static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); - if (const auto layout = _root->LoadPersistedLayout(_settings)) - { - if (layout.InitialSize()) - { - proposedSize = layout.InitialSize().Value(); - // The size is saved as a non-scaled real pixel size, - // so we need to scale it appropriately. - proposedSize.Height = proposedSize.Height * scale; - proposedSize.Width = proposedSize.Width * scale; - } - } - - if (_appArgs.GetSize().has_value() || (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) }; - - const til::size emptySize{}; - const auto commandlineSize = _appArgs.GetSize().value_or(emptySize); - proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(), - dpi, - commandlineSize.width, - commandlineSize.height); - } - - // 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 - // the height of the tab bar here. - if (_settings.GlobalSettings().ShowTabsInTitlebar()) - { - // If we're showing the tabs in the titlebar, we need to use a - // TitlebarControl here to calculate how much space to reserve. - // - // We'll create a fake TitlebarControl, and we'll propose an - // available size to it with Measure(). After Measure() is called, - // the TitlebarControl's DesiredSize will contain the _unscaled_ - // size that the titlebar would like to use. We'll use that as part - // of the height calculation here. - auto titlebar = TitlebarControl{ static_cast(0) }; - titlebar.Measure({ SHRT_MAX, SHRT_MAX }); - proposedSize.Height += (titlebar.DesiredSize().Height) * scale; - } - else if (_settings.GlobalSettings().AlwaysShowTabs()) - { - // Otherwise, let's use a TabRowControl to calculate how much extra - // space we'll need. - // - // Similarly to above, we'll measure it with an arbitrarily large - // available space, to make sure we get all the space it wants. - auto tabControl = TabRowControl(); - tabControl.Measure({ SHRT_MAX, SHRT_MAX }); - - // For whatever reason, there's about 10px of unaccounted-for space - // in the application. I couldn't tell you where these 10px are - // coming from, but they need to be included in this math. - proposedSize.Height += (tabControl.DesiredSize().Height + 10) * scale; - } - - return proposedSize; - } - - // Method Description: - // - Get the launch mode in json settings file. Now there - // two launch mode: default, maximized. Default means the window - // will launch according to the launch dimensions provided. Maximized - // means the window will launch as a maximized window - // Arguments: - // - - // Return Value: - // - LaunchMode enum that indicates the launch mode - LaunchMode AppLogic::GetLaunchMode() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - // GH#4620/#5801 - If the user passed --maximized or --fullscreen on the - // commandline, then use that to override the value from the settings. - const auto valueFromSettings = _settings.GlobalSettings().LaunchMode(); - const auto valueFromCommandlineArgs = _appArgs.GetLaunchMode(); - if (const auto layout = _root->LoadPersistedLayout(_settings)) - { - if (layout.LaunchMode()) - { - return layout.LaunchMode().Value(); - } - } - return valueFromCommandlineArgs.has_value() ? - valueFromCommandlineArgs.value() : - valueFromSettings; - } - - // Method Description: - // - Get the user defined initial position from Json settings file. - // This position represents the top left corner of the Terminal window. - // This setting is optional, if not provided, we will use the system - // default size, which is provided in IslandWindow::MakeWindow. - // Arguments: - // - defaultInitialX: the system default x coordinate value - // - defaultInitialY: the system default y coordinate value - // Return Value: - // - a point containing the requested initial position in pixels. - TerminalApp::InitialPosition AppLogic::GetInitialPosition(int64_t defaultInitialX, int64_t defaultInitialY) - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - auto initialPosition{ _settings.GlobalSettings().InitialPosition() }; - - if (const auto layout = _root->LoadPersistedLayout(_settings)) - { - if (layout.InitialPosition()) - { - initialPosition = layout.InitialPosition().Value(); - } - } - - // Commandline args trump everything else - if (_appArgs.GetPosition().has_value()) - { - initialPosition = _appArgs.GetPosition().value(); - } - - return { - initialPosition.X ? initialPosition.X.Value() : defaultInitialX, - initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY - }; - } - - bool AppLogic::CenterOnLaunch() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - // If the position has been specified on the commandline, don't center on launch - return _settings.GlobalSettings().CenterOnLaunch() && !_appArgs.GetPosition().has_value(); - } - - winrt::Windows::UI::Xaml::ElementTheme AppLogic::GetRequestedTheme() - { - return Theme().RequestedTheme(); - } - - bool AppLogic::GetShowTabsInTitlebar() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - return _settings.GlobalSettings().ShowTabsInTitlebar(); - } - - bool AppLogic::GetInitialAlwaysOnTop() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - return _settings.GlobalSettings().AlwaysOnTop(); - } - - // Method Description: - // - See Pane::CalcSnappedDimension - float AppLogic::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const - { - return _root->CalcSnappedDimension(widthOrHeight, dimension); - } - // Method Description: // - Attempt to load the settings. If we fail for any reason, returns an error. // Return Value: @@ -830,6 +263,7 @@ namespace winrt::TerminalApp::implementation _warnings.push_back(newSettings.Warnings().GetAt(i)); } + // TODO! These _settingsAppArgs need to get plumbed into TerminalWindow somehow _hasSettingsStartupActions = false; const auto startupActions = newSettings.GlobalSettings().StartupActions(); if (!startupActions.empty()) @@ -957,18 +391,6 @@ namespace winrt::TerminalApp::implementation } CATCH_LOG() - // Method Description: - // - Update the current theme of the application. This will trigger our - // RequestedThemeChanged event, to have our host change the theme of the - // root of the application. - // Arguments: - // - newTheme: The ElementTheme to apply to our elements. - void AppLogic::_RefreshThemeRoutine() - { - // Propagate the event to the host layer, so it can update its own UI - _RequestedThemeChangedHandlers(*this, Theme()); - } - // Function Description: // Returns the current app package or nullptr. // TRANSITIONAL @@ -1053,10 +475,10 @@ namespace winrt::TerminalApp::implementation } else { - const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); - const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); - _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); - return; + // const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); + // const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); + // _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); + // return; } } @@ -1067,30 +489,21 @@ namespace winrt::TerminalApp::implementation return; } - if (_settingsLoadedResult == S_FALSE) - { - _ShowLoadWarningsDialog(); - } + // if (_settingsLoadedResult == S_FALSE) + // { + // _ShowLoadWarningsDialog(); + // } // Here, we successfully reloaded the settings, and created a new // TerminalSettings object. - // Update the settings in TerminalPage - _root->SetSettings(_settings, true); - _ApplyLanguageSettingChange(); - _RefreshThemeRoutine(); _ApplyStartupTaskStateChange(); _ProcessLazySettingsChanges(); _SettingsChangedHandlers(*this, nullptr); } - void AppLogic::_OpenSettingsUI() - { - _root->OpenSettingsUI(); - } - // Method Description: // - Returns a pointer to the global shared settings. [[nodiscard]] CascadiaSettings AppLogic::GetSettings() const noexcept @@ -1098,207 +511,6 @@ namespace winrt::TerminalApp::implementation return _settings; } - UIElement AppLogic::GetRoot() noexcept - { - return _root.as(); - } - - // Method Description: - // - Gets the title of the currently focused terminal control. If there - // isn't a control selected for any reason, returns "Terminal" - // Arguments: - // - - // Return Value: - // - the title of the focused control if there is one, else "Terminal" - hstring AppLogic::Title() - { - if (_root) - { - return _root->Title(); - } - return { L"Terminal" }; - } - - // Method Description: - // - Used to tell the app that the titlebar has been clicked. The App won't - // actually receive any clicks in the titlebar area, so this is a helper - // to clue the app in that a click has happened. The App will use this as - // a indicator that it needs to dismiss any open flyouts. - // Arguments: - // - - // Return Value: - // - - void AppLogic::TitlebarClicked() - { - if (_root) - { - _root->TitlebarClicked(); - } - } - - // Method Description: - // - Used to tell the PTY connection that the window visibility has changed. - // The underlying PTY might need to expose window visibility status to the - // client application for the `::GetConsoleWindow()` API. - // Arguments: - // - showOrHide - True is show; false is hide. - // Return Value: - // - - void AppLogic::WindowVisibilityChanged(const bool showOrHide) - { - if (_root) - { - _root->WindowVisibilityChanged(showOrHide); - } - } - - // Method Description: - // - Implements the F7 handler (per GH#638) - // - Implements the Alt handler (per GH#6421) - // Return value: - // - whether the key was handled - bool AppLogic::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down) - { - if (_root) - { - // Manually bubble the OnDirectKeyEvent event up through the focus tree. - auto xamlRoot{ _root->XamlRoot() }; - auto focusedObject{ Windows::UI::Xaml::Input::FocusManager::GetFocusedElement(xamlRoot) }; - do - { - if (auto keyListener{ focusedObject.try_as() }) - { - if (keyListener.OnDirectKeyEvent(vkey, scanCode, down)) - { - return true; - } - // otherwise, keep walking. bubble the event manually. - } - - if (auto focusedElement{ focusedObject.try_as() }) - { - focusedObject = focusedElement.Parent(); - - // Parent() seems to return null when the focusedElement is created from an ItemTemplate. - // Use the VisualTreeHelper's GetParent as a fallback. - if (!focusedObject) - { - focusedObject = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetParent(focusedElement); - } - } - else - { - break; // we hit a non-FE object, stop bubbling. - } - } while (focusedObject); - } - return false; - } - - // Method Description: - // - Used to tell the app that the 'X' button has been clicked and - // the user wants to close the app. We kick off the close warning - // experience. - // Arguments: - // - - // Return Value: - // - - void AppLogic::CloseWindow(LaunchPosition pos) - { - if (_root) - { - // If persisted layout is enabled and we are the last window closing - // we should save our state. - if (_root->ShouldUsePersistedLayout(_settings) && _numOpenWindows == 1) - { - if (const auto layout = _root->GetWindowLayout()) - { - layout.InitialPosition(pos); - const auto state = ApplicationState::SharedInstance(); - state.PersistedWindowLayouts(winrt::single_threaded_vector({ layout })); - } - } - - _root->CloseWindow(false); - } - } - - winrt::TerminalApp::TaskbarState AppLogic::TaskbarState() - { - if (_root) - { - return _root->TaskbarState(); - } - return {}; - } - - winrt::Windows::UI::Xaml::Media::Brush AppLogic::TitlebarBrush() - { - if (_root) - { - return _root->TitlebarBrush(); - } - return { nullptr }; - } - void AppLogic::WindowActivated(const bool activated) - { - _root->WindowActivated(activated); - } - - bool AppLogic::HasCommandlineArguments() const noexcept - { - return _hasCommandLineArguments; - } - - bool AppLogic::HasSettingsStartupActions() const noexcept - { - return _hasSettingsStartupActions; - } - - // Method Description: - // - Sets the initial commandline to process on startup, and attempts to - // parse it. Commands will be parsed into a list of ShortcutActions that - // will be processed on TerminalPage::Create(). - // - This function will have no effective result after Create() is called. - // - This function returns 0, unless a there was a non-zero result from - // trying to parse one of the commands provided. In that case, no commands - // after the failing command will be parsed, and the non-zero code - // returned. - // Arguments: - // - args: an array of strings to process as a commandline. These args can contain spaces - // Return Value: - // - the result of the first command who's parsing returned a non-zero code, - // or 0. (see AppLogic::_ParseArgs) - int32_t AppLogic::SetStartupCommandline(array_view args) - { - const auto result = _appArgs.ParseArgs(args); - if (result == 0) - { - // If the size of the arguments list is 1, - // then it contains only the executable name and no other arguments. - _hasCommandLineArguments = args.size() > 1; - _appArgs.ValidateStartupCommands(); - if (const auto idx = _appArgs.GetPersistedLayoutIdx()) - { - _root->SetPersistedLayoutIdx(idx.value()); - } - _root->SetStartupActions(_appArgs.GetStartupActions()); - - // Check if we were started as a COM server for inbound connections of console sessions - // coming out of the operating system default application feature. If so, - // tell TerminalPage to start the listener as we have to make sure it has the chance - // to register a handler to hear about the requests first and is all ready to receive - // them before the COM server registers itself. Otherwise, the request might come - // in and be routed to an event with no handlers or a non-ready Page. - if (_appArgs.IsHandoffListener()) - { - _root->SetInboundListener(true); - } - } - - return result; - } - // Method Description: // - Triggers the setup of the listener for incoming console connections // from the operating system. @@ -1308,43 +520,27 @@ namespace winrt::TerminalApp::implementation // - void AppLogic::SetInboundListener() { - _root->SetInboundListener(false); + // TODO! + // _root->SetInboundListener(false); } - // Method Description: - // - Parse the provided commandline arguments into actions, and try to - // perform them immediately. - // - This function returns 0, unless a there was a non-zero result from - // trying to parse one of the commands provided. In that case, no commands - // after the failing command will be parsed, and the non-zero code - // returned. - // - If a non-empty cwd is provided, the entire terminal exe will switch to - // that CWD while we handle these actions, then return to the original - // CWD. - // Arguments: - // - args: an array of strings to process as a commandline. These args can contain spaces - // - cwd: The directory to use as the CWD while performing these actions. - // Return Value: - // - the result of the first command who's parsing returned a non-zero code, - // or 0. (see AppLogic::_ParseArgs) - int32_t AppLogic::ExecuteCommandline(array_view args, - const winrt::hstring& cwd) + bool AppLogic::ShouldImmediatelyHandoffToElevated() { - ::TerminalApp::AppCommandlineArgs appArgs; - auto result = appArgs.ParseArgs(args); - if (result == 0) - { - auto actions = winrt::single_threaded_vector(std::move(appArgs.GetStartupActions())); + // TODO! Merge that code in here. + // * Probably need to pass in the startupActions that the first window is being started with + // * Or like, a reference to the first TerminalWindow object, or something - _root->ProcessStartupActions(actions, false, cwd); + // return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false; + return false; + } + void AppLogic::HandoffToElevated() + { + // TODO! Merge that code in here. - if (appArgs.IsHandoffListener()) - { - _root->SetInboundListener(true); - } - } - // Return the result of parsing with commandline, though it may or may not be used. - return result; + // if (_root) + // { + // _root->HandoffToElevated(_settings); + // } } // Method Description: @@ -1468,228 +664,13 @@ namespace winrt::TerminalApp::implementation return winrt::make(WindowingBehaviorUseNew); } - // Method Description: - // - If there were any errors parsing the commandline that was used to - // initialize the terminal, this will return a string containing that - // message. If there were no errors, this message will be blank. - // - If the user requested help on any command (using --help), this will - // contain the help message. - // - If the user requested the version number (using --version), this will - // contain the version string. - // Arguments: - // - - // Return Value: - // - the help text or error message for the provided commandline, if one - // exists, otherwise the empty string. - winrt::hstring AppLogic::ParseCommandlineMessage() - { - return winrt::to_hstring(_appArgs.GetExitMessage()); - } - - // Method Description: - // - Returns true if we should exit the application before even starting the - // window. We might want to do this if we're displaying an error message or - // the version string, or if we want to open the settings file. - // Arguments: - // - - // Return Value: - // - true iff we should exit the application before even starting the window - bool AppLogic::ShouldExitEarly() - { - return _appArgs.ShouldExitEarly(); - } - - bool AppLogic::FocusMode() const - { - return _root ? _root->FocusMode() : false; - } - - bool AppLogic::Fullscreen() const - { - return _root ? _root->Fullscreen() : false; - } - - void AppLogic::Maximized(bool newMaximized) - { - if (_root) - { - _root->Maximized(newMaximized); - } - } - - bool AppLogic::AlwaysOnTop() const - { - return _root ? _root->AlwaysOnTop() : false; - } - - bool AppLogic::AutoHideWindow() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - return _settings.GlobalSettings().AutoHideWindow(); - } - Windows::Foundation::Collections::IMapView AppLogic::GlobalHotkeys() { return _settings.GlobalSettings().ActionMap().GlobalHotkeys(); } - bool AppLogic::ShouldUsePersistedLayout() - { - return _root != nullptr ? _root->ShouldUsePersistedLayout(_settings) : false; - } - - bool AppLogic::ShouldImmediatelyHandoffToElevated() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false; - } - void AppLogic::HandoffToElevated() - { - if (_root) - { - _root->HandoffToElevated(_settings); - } - } - - void AppLogic::SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts) - { - std::vector converted; - converted.reserve(layouts.Size()); - - for (const auto& json : layouts) - { - if (json != L"") - { - converted.emplace_back(WindowLayout::FromJson(json)); - } - } - - ApplicationState::SharedInstance().PersistedWindowLayouts(winrt::single_threaded_vector(std::move(converted))); - } - - hstring AppLogic::GetWindowLayoutJson(LaunchPosition position) - { - if (_root != nullptr) - { - if (const auto layout = _root->GetWindowLayout()) - { - layout.InitialPosition(position); - return WindowLayout::ToJson(layout); - } - } - return L""; - } - - void AppLogic::IdentifyWindow() - { - if (_root) - { - _root->IdentifyWindow(); - } - } - - winrt::hstring AppLogic::WindowName() - { - return _root ? _root->WindowName() : L""; - } - void AppLogic::WindowName(const winrt::hstring& name) - { - if (_root) - { - _root->WindowName(name); - } - } - uint64_t AppLogic::WindowId() - { - return _root ? _root->WindowId() : 0; - } - void AppLogic::WindowId(const uint64_t& id) - { - if (_root) - { - _root->WindowId(id); - } - } - - void AppLogic::SetPersistedLayoutIdx(const uint32_t idx) - { - if (_root) - { - _root->SetPersistedLayoutIdx(idx); - } - } - - void AppLogic::SetNumberOfOpenWindows(const uint64_t num) - { - _numOpenWindows = num; - if (_root) - { - _root->SetNumberOfOpenWindows(num); - } - } - - void AppLogic::RenameFailed() - { - if (_root) - { - _root->RenameFailed(); - } - } - - bool AppLogic::IsQuakeWindow() const noexcept - { - return _root->IsQuakeWindow(); - } - - void AppLogic::RequestExitFullscreen() - { - _root->SetFullscreen(false); - } - - bool AppLogic::GetMinimizeToNotificationArea() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - return _settings.GlobalSettings().MinimizeToNotificationArea(); - } - - bool AppLogic::GetAlwaysShowNotificationIcon() - { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } - - return _settings.GlobalSettings().AlwaysShowNotificationIcon(); - } - - bool AppLogic::GetShowTitleInTitlebar() - { - return _settings.GlobalSettings().ShowTitleInTitlebar(); - } - Microsoft::Terminal::Settings::Model::Theme AppLogic::Theme() { - if (!_loadedInitialSettings) - { - // Load settings if we haven't already - ReloadSettings(); - } return _settings.GlobalSettings().CurrentTheme(); } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 5534979b973..b6a3a8b6ba9 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -5,10 +5,10 @@ #include "AppLogic.g.h" #include "FindTargetWindowResult.g.h" -#include "SystemMenuChangeArgs.g.h" + #include "Jumplist.h" #include "LanguageProfileNotifier.h" -#include "TerminalPage.h" +#include "AppCommandlineArgs.h" #include #include @@ -36,18 +36,7 @@ namespace winrt::TerminalApp::implementation FindTargetWindowResult(id, L""){}; }; - struct SystemMenuChangeArgs : SystemMenuChangeArgsT - { - WINRT_PROPERTY(winrt::hstring, Name, L""); - WINRT_PROPERTY(SystemMenuChangeAction, Action, SystemMenuChangeAction::Add); - WINRT_PROPERTY(SystemMenuItemHandler, Handler, nullptr); - - public: - SystemMenuChangeArgs(const winrt::hstring& name, SystemMenuChangeAction action, SystemMenuItemHandler handler = nullptr) : - _Name{ name }, _Action{ action }, _Handler{ handler } {}; - }; - - struct AppLogic : AppLogicT + struct AppLogic : AppLogicT { public: static AppLogic* Current() noexcept; @@ -56,8 +45,6 @@ namespace winrt::TerminalApp::implementation AppLogic(); ~AppLogic() = default; - STDMETHODIMP Initialize(HWND hwnd); - void Create(); bool IsUwp() const noexcept; void RunAsUwp(); @@ -66,114 +53,36 @@ namespace winrt::TerminalApp::implementation [[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept; - void Quit(); - - bool HasCommandlineArguments() const noexcept; - bool HasSettingsStartupActions() const noexcept; - int32_t SetStartupCommandline(array_view actions); - int32_t ExecuteCommandline(array_view actions, const winrt::hstring& cwd); TerminalApp::FindTargetWindowResult FindTargetWindow(array_view actions); - winrt::hstring ParseCommandlineMessage(); - bool ShouldExitEarly(); - - bool FocusMode() const; - bool Fullscreen() const; - void Maximized(bool newMaximized); - bool AlwaysOnTop() const; - bool AutoHideWindow(); - - bool ShouldUsePersistedLayout(); bool ShouldImmediatelyHandoffToElevated(); void HandoffToElevated(); - hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position); - void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts); - void IdentifyWindow(); - void RenameFailed(); - winrt::hstring WindowName(); - void WindowName(const winrt::hstring& name); - uint64_t WindowId(); - void WindowId(const uint64_t& id); - void SetPersistedLayoutIdx(const uint32_t idx); - void SetNumberOfOpenWindows(const uint64_t num); - bool IsQuakeWindow() const noexcept; - void RequestExitFullscreen(); - - Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi); - bool CenterOnLaunch(); - TerminalApp::InitialPosition GetInitialPosition(int64_t defaultInitialX, int64_t defaultInitialY); - winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme(); - Microsoft::Terminal::Settings::Model::LaunchMode GetLaunchMode(); - bool GetShowTabsInTitlebar(); - bool GetInitialAlwaysOnTop(); - float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; - - Windows::UI::Xaml::UIElement GetRoot() noexcept; void SetInboundListener(); - hstring Title(); - void TitlebarClicked(); - bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); - - void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position); - void WindowVisibilityChanged(const bool showOrHide); - - winrt::TerminalApp::TaskbarState TaskbarState(); - winrt::Windows::UI::Xaml::Media::Brush TitlebarBrush(); - void WindowActivated(const bool activated); - - bool GetMinimizeToNotificationArea(); - bool GetAlwaysShowNotificationIcon(); - bool GetShowTitleInTitlebar(); - - winrt::Windows::Foundation::IAsyncOperation ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); - void DismissDialog(); - Windows::Foundation::Collections::IMapView GlobalHotkeys(); Microsoft::Terminal::Settings::Model::Theme Theme(); - // -------------------------------- WinRT Events --------------------------------- - // PropertyChanged is surprisingly not a typed event, so we'll define that one manually. - // Usually we'd just do - // WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - // - // But what we're doing here is exposing the Page's PropertyChanged _as - // our own event_. It's a FORWARDED_CALLBACK, essentially. - winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler) { return _root->PropertyChanged(handler); } - void PropertyChanged(winrt::event_token const& token) { _root->PropertyChanged(token); } - - TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Settings::Model::Theme); TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); - TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs); private: bool _isUwp{ false }; bool _isElevated{ false }; - // If you add controls here, but forget to null them either here or in - // the ctor, you're going to have a bad time. It'll mysteriously fail to - // activate the AppLogic. - // ALSO: If you add any UIElements as roots here, make sure they're - // updated in _ApplyTheme. The root currently is _root. - winrt::com_ptr _root{ nullptr }; Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; winrt::hstring _settingsLoadExceptionText; HRESULT _settingsLoadedResult = S_OK; bool _loadedInitialSettings = false; - uint64_t _numOpenWindows{ 0 }; - - std::shared_mutex _dialogLock; - winrt::Windows::UI::Xaml::Controls::ContentDialog _dialog; - - ::TerminalApp::AppCommandlineArgs _appArgs; + bool _hasSettingsStartupActions{ false }; ::TerminalApp::AppCommandlineArgs _settingsAppArgs; std::shared_ptr> _reloadSettings; til::throttled_func_trailing<> _reloadState; + std::vector _warnings; + // These fields invoke _reloadSettings and must be destroyed before _reloadSettings. // (C++ destroys members in reverse-declaration-order.) winrt::com_ptr _languageProfileNotifier; @@ -182,46 +91,13 @@ namespace winrt::TerminalApp::implementation static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view args, const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior); - void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult); - void _ShowLoadWarningsDialog(); - bool _IsKeyboardServiceEnabled(); - void _ApplyLanguageSettingChange() noexcept; - void _RefreshThemeRoutine(); fire_and_forget _ApplyStartupTaskStateChange(); - void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - [[nodiscard]] HRESULT _TryLoadSettings() noexcept; void _ProcessLazySettingsChanges(); void _RegisterSettingsChange(); fire_and_forget _DispatchReloadSettings(); - void _OpenSettingsUI(); - - bool _hasCommandLineArguments{ false }; - bool _hasSettingsStartupActions{ false }; - std::vector _warnings; - - // These are events that are handled by the TerminalPage, but are - // exposed through the AppLogic. This macro is used to forward the event - // directly to them. - FORWARDED_TYPED_EVENT(SetTitleBarContent, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement, _root, SetTitleBarContent); - FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged); - FORWARDED_TYPED_EVENT(LastTabClosed, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs, _root, LastTabClosed); - FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged); - FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged); - FORWARDED_TYPED_EVENT(ChangeMaximizeRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, ChangeMaximizeRequested); - FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged); - FORWARDED_TYPED_EVENT(RaiseVisualBell, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, RaiseVisualBell); - FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress); - FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested); - FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested); - FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged); - FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested); - FORWARDED_TYPED_EVENT(CloseRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, CloseRequested); - FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu); - FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested); - FORWARDED_TYPED_EVENT(ShowWindowChanged, Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs, _root, ShowWindowChanged); #ifdef UNIT_TESTING friend class TerminalAppLocalTests::CommandlineTest; diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index b7a92ea1103..bc48cf5a965 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -1,41 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "TerminalPage.idl"; -import "ShortcutActionDispatch.idl"; -import "IDirectKeyListener.idl"; - namespace TerminalApp { - struct InitialPosition - { - Int64 X; - Int64 Y; - }; - [default_interface] runtimeclass FindTargetWindowResult { Int32 WindowId { get; }; String WindowName { get; }; }; - delegate void SystemMenuItemHandler(); - - enum SystemMenuChangeAction - { - Add = 0, - Remove = 1 - }; - - [default_interface] runtimeclass SystemMenuChangeArgs { - String Name { get; }; - SystemMenuChangeAction Action { get; }; - SystemMenuItemHandler Handler { get; }; - }; - // See IDialogPresenter and TerminalPage's DialogPresenter for more // information. - [default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged + [default_interface] runtimeclass AppLogic { AppLogic(); @@ -50,94 +26,17 @@ namespace TerminalApp void RunAsUwp(); Boolean IsElevated(); - Boolean HasCommandlineArguments(); - Boolean HasSettingsStartupActions(); - Int32 SetStartupCommandline(String[] commands); - Int32 ExecuteCommandline(String[] commands, String cwd); - String ParseCommandlineMessage { get; }; - Boolean ShouldExitEarly { get; }; - - void Quit(); - void ReloadSettings(); - Windows.UI.Xaml.UIElement GetRoot(); void SetInboundListener(); - String Title { get; }; - - Boolean FocusMode { get; }; - Boolean Fullscreen { get; }; - void Maximized(Boolean newMaximized); - Boolean AlwaysOnTop { get; }; - Boolean AutoHideWindow { get; }; - - void IdentifyWindow(); - String WindowName; - UInt64 WindowId; - void SetPersistedLayoutIdx(UInt32 idx); - void SetNumberOfOpenWindows(UInt64 num); - void RenameFailed(); - void RequestExitFullscreen(); - Boolean IsQuakeWindow(); - - Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi); - Boolean CenterOnLaunch { get; }; - - InitialPosition GetInitialPosition(Int64 defaultInitialX, Int64 defaultInitialY); - Windows.UI.Xaml.ElementTheme GetRequestedTheme(); - Microsoft.Terminal.Settings.Model.LaunchMode GetLaunchMode(); - Boolean GetShowTabsInTitlebar(); - Boolean GetInitialAlwaysOnTop(); - Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension); - void TitlebarClicked(); - void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position); - void WindowVisibilityChanged(Boolean showOrHide); - - TaskbarState TaskbarState{ get; }; - Windows.UI.Xaml.Media.Brush TitlebarBrush { get; }; - void WindowActivated(Boolean activated); - - Boolean ShouldUsePersistedLayout(); - Boolean ShouldImmediatelyHandoffToElevated(); - void HandoffToElevated(); - String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position); - void SaveWindowLayoutJsons(Windows.Foundation.Collections.IVector layouts); - - Boolean GetMinimizeToNotificationArea(); - Boolean GetAlwaysShowNotificationIcon(); - Boolean GetShowTitleInTitlebar(); - Microsoft.Terminal.Settings.Model.Theme Theme { get; }; FindTargetWindowResult FindTargetWindow(String[] args); Windows.Foundation.Collections.IMapView GlobalHotkeys(); - // See IDialogPresenter and TerminalPage's DialogPresenter for more - // information. - Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); - void DismissDialog(); - - event Windows.Foundation.TypedEventHandler SetTitleBarContent; - event Windows.Foundation.TypedEventHandler TitleChanged; - event Windows.Foundation.TypedEventHandler LastTabClosed; - event Windows.Foundation.TypedEventHandler RequestedThemeChanged; - event Windows.Foundation.TypedEventHandler FocusModeChanged; - event Windows.Foundation.TypedEventHandler FullscreenChanged; - event Windows.Foundation.TypedEventHandler ChangeMaximizeRequested; - event Windows.Foundation.TypedEventHandler AlwaysOnTopChanged; - event Windows.Foundation.TypedEventHandler RaiseVisualBell; - event Windows.Foundation.TypedEventHandler SetTaskbarProgress; - event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; - event Windows.Foundation.TypedEventHandler RenameWindowRequested; event Windows.Foundation.TypedEventHandler SettingsChanged; - event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; - event Windows.Foundation.TypedEventHandler SummonWindowRequested; - event Windows.Foundation.TypedEventHandler CloseRequested; - event Windows.Foundation.TypedEventHandler OpenSystemMenu; - event Windows.Foundation.TypedEventHandler QuitRequested; - event Windows.Foundation.TypedEventHandler SystemMenuChangeRequested; - event Windows.Foundation.TypedEventHandler ShowWindowChanged; + } } diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 6123f0de1c3..7fb88842a1c 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -138,6 +138,9 @@ AppLogic.idl + + TerminalWindow.idl + @@ -231,6 +234,9 @@ AppLogic.idl + + TerminalWindow.idl + @@ -252,6 +258,7 @@ + MinMaxCloseControl.xaml Code diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp new file mode 100644 index 00000000000..0b1c9e27632 --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -0,0 +1,1140 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TerminalWindow.h" +#include "../inc/WindowingBehavior.h" +#include "TerminalWindow.g.cpp" + +#include +#include +#include + +#include "../../types/inc/utils.hpp" + +using namespace winrt::Windows::ApplicationModel; +using namespace winrt::Windows::ApplicationModel::DataTransfer; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::System; +using namespace winrt::Microsoft::Terminal; +using namespace winrt::Microsoft::Terminal::Control; +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace ::TerminalApp; + +namespace winrt +{ + namespace MUX = Microsoft::UI::Xaml; + using IInspectable = Windows::Foundation::IInspectable; +} + +// clang-format off +// !!! IMPORTANT !!! +// Make sure that these keys are in the same order as the +// SettingsLoadWarnings/Errors enum is! +static const std::array settingsLoadWarningsLabels { + USES_RESOURCE(L"MissingDefaultProfileText"), + USES_RESOURCE(L"DuplicateProfileText"), + USES_RESOURCE(L"UnknownColorSchemeText"), + USES_RESOURCE(L"InvalidBackgroundImage"), + USES_RESOURCE(L"InvalidIcon"), + USES_RESOURCE(L"AtLeastOneKeybindingWarning"), + USES_RESOURCE(L"TooManyKeysForChord"), + USES_RESOURCE(L"MissingRequiredParameter"), + USES_RESOURCE(L"FailedToParseCommandJson"), + USES_RESOURCE(L"FailedToWriteToSettings"), + USES_RESOURCE(L"InvalidColorSchemeInCmd"), + USES_RESOURCE(L"InvalidSplitSize"), + USES_RESOURCE(L"FailedToParseStartupActions"), + USES_RESOURCE(L"FailedToParseSubCommands"), + USES_RESOURCE(L"UnknownTheme"), + USES_RESOURCE(L"DuplicateRemainingProfilesEntry"), +}; +static const std::array settingsLoadErrorsLabels { + USES_RESOURCE(L"NoProfilesText"), + USES_RESOURCE(L"AllProfilesHiddenText") +}; +// clang-format on + +static_assert(settingsLoadWarningsLabels.size() == static_cast(SettingsLoadWarnings::WARNINGS_SIZE)); +static_assert(settingsLoadErrorsLabels.size() == static_cast(SettingsLoadErrors::ERRORS_SIZE)); + +// Function Description: +// - General-purpose helper for looking up a localized string for a +// warning/error. First will look for the given key in the provided map of +// keys->strings, where the values in the map are ResourceKeys. If it finds +// one, it will lookup the localized string from that ResourceKey. +// - If it does not find a key, it'll return an empty string +// Arguments: +// - key: the value to use to look for a resource key in the given map +// - map: A map of keys->Resource keys. +// Return Value: +// - the localized string for the given type, if it exists. +template +winrt::hstring _GetMessageText(uint32_t index, const T& keys) +{ + if (index < keys.size()) + { + return GetLibraryResourceString(til::at(keys, index)); + } + return {}; +} + +// Function Description: +// - Gets the text from our ResourceDictionary for the given +// SettingsLoadWarning. If there is no such text, we'll return nullptr. +// - The warning should have an entry in settingsLoadWarningsLabels. +// Arguments: +// - warning: the SettingsLoadWarnings value to get the localized text for. +// Return Value: +// - localized text for the given warning +static winrt::hstring _GetWarningText(SettingsLoadWarnings warning) +{ + return _GetMessageText(static_cast(warning), settingsLoadWarningsLabels); +} + +// // Function Description: +// // - Gets the text from our ResourceDictionary for the given +// // SettingsLoadError. If there is no such text, we'll return nullptr. +// // - The warning should have an entry in settingsLoadErrorsLabels. +// // Arguments: +// // - error: the SettingsLoadErrors value to get the localized text for. +// // Return Value: +// // - localized text for the given error +// static winrt::hstring _GetErrorText(SettingsLoadErrors error) +// { +// return _GetMessageText(static_cast(error), settingsLoadErrorsLabels); +// } + +// Function Description: +// - Creates a Run of text to display an error message. The text is yellow or +// red for dark/light theme, respectively. +// Arguments: +// - text: The text of the error message. +// - resources: The application's resource loader. +// Return Value: +// - The fully styled text run. +static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceDictionary& resources) +{ + Documents::Run textRun; + textRun.Text(text); + + // Color the text red (light theme) or yellow (dark theme) based on the system theme + auto key = winrt::box_value(L"ErrorTextBrush"); + if (resources.HasKey(key)) + { + auto g = resources.Lookup(key); + auto brush = g.try_as(); + textRun.Foreground(brush); + } + + return textRun; +} + +namespace winrt::TerminalApp::implementation +{ + TerminalWindow::TerminalWindow(const CascadiaSettings& settings) : + _settings{ settings } + { + // For your own sanity, it's better to do setup outside the ctor. + // If you do any setup in the ctor that ends up throwing an exception, + // then it might look like App just failed to activate, which will + // cause you to chase down the rabbit hole of "why is App not + // registered?" when it definitely is. + + // The TerminalPage has to be constructed during our construction, to + // make sure that there's a terminal page for callers of + // SetTitleBarContent + _isElevated = ::Microsoft::Console::Utils::IsElevated(); + _root = winrt::make_self(); + } + + // Method Description: + // - Implements the IInitializeWithWindow interface from shobjidl_core. + HRESULT TerminalWindow::Initialize(HWND hwnd) + { + _root = winrt::make_self(); + _dialog = ContentDialog{}; + return _root->Initialize(hwnd); + } + // Method Description: + // - Called around the codebase to discover if this is a UWP where we need to turn off specific settings. + // Arguments: + // - - reports internal state + // Return Value: + // - True if UWP, false otherwise. + bool TerminalWindow::IsUwp() const noexcept + { + return _isUwp; + } + + // Method Description: + // - Called around the codebase to discover if Terminal is running elevated + // Arguments: + // - - reports internal state + // Return Value: + // - True if elevated, false otherwise. + bool TerminalWindow::IsElevated() const noexcept + { + return _isElevated; + } + + // Method Description: + // - Called by UWP context invoker to let us know that we may have to change some of our behaviors + // for being a UWP + // Arguments: + // - (sets to UWP = true, one way change) + // Return Value: + // - + void TerminalWindow::RunAsUwp() + { + _isUwp = true; + } + + // Method Description: + // - Build the UI for the terminal app. Before this method is called, it + // should not be assumed that the TerminalApp is usable. The Settings + // should be loaded before this is called, either with LoadSettings or + // GetLaunchDimensions (which will call LoadSettings) + // Arguments: + // - + // Return Value: + // - + void TerminalWindow::Create() + { + _root->DialogPresenter(*this); + + // In UWP mode, we cannot handle taking over the title bar for tabs, + // so this setting is overridden to false no matter what the preference is. + if (_isUwp) + { + _settings.GlobalSettings().ShowTabsInTitlebar(false); + } + + // TODO! handle startupActions + // + //// Pay attention, that even if some command line arguments were parsed (like launch mode), + //// we will not use the startup actions from settings. + //// While this simplifies the logic, we might want to reconsider this behavior in the future. + //if (!_hasCommandLineArguments && _hasSettingsStartupActions) + //{ + // _root->SetStartupActions(_settingsAppArgs.GetStartupActions()); + //} + + _root->SetSettings(_settings, false); + _root->Loaded({ this, &TerminalWindow::_OnLoaded }); + _root->Initialized([this](auto&&, auto&&) { + // GH#288 - When we finish initialization, if the user wanted us + // launched _fullscreen_, toggle fullscreen mode. This will make sure + // that the window size is _first_ set up as something sensible, so + // leaving fullscreen returns to a reasonable size. + const auto launchMode = this->GetLaunchMode(); + if (IsQuakeWindow() || WI_IsFlagSet(launchMode, LaunchMode::FocusMode)) + { + _root->SetFocusMode(true); + } + + // The IslandWindow handles (creating) the maximized state + // we just want to record it here on the page as well. + if (WI_IsFlagSet(launchMode, LaunchMode::MaximizedMode)) + { + _root->Maximized(true); + } + + if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !IsQuakeWindow()) + { + _root->SetFullscreen(true); + } + }); + _root->Create(); + + _RefreshThemeRoutine(); + + auto args = winrt::make_self(RS_(L"SettingsMenuItem"), SystemMenuChangeAction::Add, SystemMenuItemHandler(this, &TerminalWindow::_OpenSettingsUI)); + _SystemMenuChangeRequestedHandlers(*this, *args); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "WindowCreated", + TraceLoggingDescription("Event emitted when the window is started"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } + void TerminalWindow::Quit() + { + if (_root) + { + _root->CloseWindow(true); + } + } + + winrt::Windows::UI::Xaml::ElementTheme TerminalWindow::GetRequestedTheme() + { + return Theme().RequestedTheme(); + } + + bool TerminalWindow::GetShowTabsInTitlebar() + { + return _settings.GlobalSettings().ShowTabsInTitlebar(); + } + + bool TerminalWindow::GetInitialAlwaysOnTop() + { + return _settings.GlobalSettings().AlwaysOnTop(); + } + + bool TerminalWindow::GetMinimizeToNotificationArea() + { + return _settings.GlobalSettings().MinimizeToNotificationArea(); + } + + bool TerminalWindow::GetAlwaysShowNotificationIcon() + { + return _settings.GlobalSettings().AlwaysShowNotificationIcon(); + } + + bool TerminalWindow::GetShowTitleInTitlebar() + { + return _settings.GlobalSettings().ShowTitleInTitlebar(); + } + + Microsoft::Terminal::Settings::Model::Theme TerminalWindow::Theme() + { + return _settings.GlobalSettings().CurrentTheme(); + } + // Method Description: + // - Show a ContentDialog with buttons to take further action. Uses the + // FrameworkElements provided as the title and content of this dialog, and + // displays buttons (or a single button). Two buttons (primary and secondary) + // will be displayed if this is an warning dialog for closing the terminal, + // this allows the users to abandon the closing action. Otherwise, a single + // close button will be displayed. + // - Only one dialog can be visible at a time. If another dialog is visible + // when this is called, nothing happens. + // Arguments: + // - dialog: the dialog object that is going to show up + // Return value: + // - an IAsyncOperation with the dialog result + winrt::Windows::Foundation::IAsyncOperation TerminalWindow::ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog) + { + // DON'T release this lock in a wil::scope_exit. The scope_exit will get + // called when we await, which is not what we want. + std::unique_lock lock{ _dialogLock, std::try_to_lock }; + if (!lock) + { + // Another dialog is visible. + co_return ContentDialogResult::None; + } + + _dialog = dialog; + + // IMPORTANT: This is necessary as documented in the ContentDialog MSDN docs. + // Since we're hosting the dialog in a Xaml island, we need to connect it to the + // xaml tree somehow. + dialog.XamlRoot(_root->XamlRoot()); + + // IMPORTANT: Set the requested theme of the dialog, because the + // PopupRoot isn't directly in the Xaml tree of our root. So the dialog + // won't inherit our RequestedTheme automagically. + // GH#5195, GH#3654 Because we cannot set RequestedTheme at the application level, + // we occasionally run into issues where parts of our UI end up themed incorrectly. + // Dialogs, for example, live under a different Xaml root element than the rest of + // our application. This makes our popup menus and buttons "disappear" when the + // user wants Terminal to be in a different theme than the rest of the system. + // This hack---and it _is_ a hack--walks up a dialog's ancestry and forces the + // theme on each element up to the root. We're relying a bit on Xaml's implementation + // details here, but it does have the desired effect. + // It's not enough to set the theme on the dialog alone. + auto themingLambda{ [this](const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&) { + auto theme{ _settings.GlobalSettings().CurrentTheme() }; + auto requestedTheme{ theme.RequestedTheme() }; + auto element{ sender.try_as() }; + while (element) + { + element.RequestedTheme(requestedTheme); + element = element.Parent().try_as(); + } + } }; + + themingLambda(dialog, nullptr); // if it's already in the tree + auto loadedRevoker{ dialog.Loaded(winrt::auto_revoke, themingLambda) }; // if it's not yet in the tree + + // Display the dialog. + co_return co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup); + + // After the dialog is dismissed, the dialog lock (held by `lock`) will + // be released so another can be shown + } + + // Method Description: + // - Dismiss the (only) visible ContentDialog + void TerminalWindow::DismissDialog() + { + if (auto localDialog = std::exchange(_dialog, nullptr)) + { + localDialog.Hide(); + } + } + + // Method Description: + // - Displays a dialog for errors found while loading or validating the + // settings. Uses the resources under the provided title and content keys + // as the title and first content of the dialog, then also displays a + // message for whatever exception was found while validating the settings. + // - Only one dialog can be visible at a time. If another dialog is visible + // when this is called, nothing happens. See ShowDialog for details + // Arguments: + // - titleKey: The key to use to lookup the title text from our resources. + // - contentKey: The key to use to lookup the content text from our resources. + void TerminalWindow::_ShowLoadErrorsDialog(const winrt::hstring& titleKey, + const winrt::hstring& contentKey, + HRESULT settingsLoadedResult) + { + auto title = GetLibraryResourceString(titleKey); + auto buttonText = RS_(L"Ok"); + + Controls::TextBlock warningsTextBlock; + // Make sure you can copy-paste + warningsTextBlock.IsTextSelectionEnabled(true); + // Make sure the lines of text wrap + warningsTextBlock.TextWrapping(TextWrapping::Wrap); + + winrt::Windows::UI::Xaml::Documents::Run errorRun; + const auto errorLabel = GetLibraryResourceString(contentKey); + errorRun.Text(errorLabel); + warningsTextBlock.Inlines().Append(errorRun); + warningsTextBlock.Inlines().Append(Documents::LineBreak{}); + + if (FAILED(settingsLoadedResult)) + { + // TODO! _settingsLoadExceptionText needs to get into the TerminalWindow somehow + + // if (!_settingsLoadExceptionText.empty()) + // { + // warningsTextBlock.Inlines().Append(_BuildErrorRun(_settingsLoadExceptionText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources())); + // warningsTextBlock.Inlines().Append(Documents::LineBreak{}); + // } + } + + // Add a note that we're using the default settings in this case. + winrt::Windows::UI::Xaml::Documents::Run usingDefaultsRun; + const auto usingDefaultsText = RS_(L"UsingDefaultSettingsText"); + usingDefaultsRun.Text(usingDefaultsText); + warningsTextBlock.Inlines().Append(Documents::LineBreak{}); + warningsTextBlock.Inlines().Append(usingDefaultsRun); + + Controls::ContentDialog dialog; + dialog.Title(winrt::box_value(title)); + dialog.Content(winrt::box_value(warningsTextBlock)); + dialog.CloseButtonText(buttonText); + dialog.DefaultButton(Controls::ContentDialogButton::Close); + + ShowDialog(dialog); + } + + // Method Description: + // - Displays a dialog for warnings found while loading or validating the + // settings. Displays messages for whatever warnings were found while + // validating the settings. + // - Only one dialog can be visible at a time. If another dialog is visible + // when this is called, nothing happens. See ShowDialog for details + void TerminalWindow::_ShowLoadWarningsDialog() + { + auto title = RS_(L"SettingsValidateErrorTitle"); + auto buttonText = RS_(L"Ok"); + + Controls::TextBlock warningsTextBlock; + // Make sure you can copy-paste + warningsTextBlock.IsTextSelectionEnabled(true); + // Make sure the lines of text wrap + warningsTextBlock.TextWrapping(TextWrapping::Wrap); + + // TODO! warnings need to get into here somehow + // + // for (const auto& warning : _warnings) + // { + // // Try looking up the warning message key for each warning. + // const auto warningText = _GetWarningText(warning); + // if (!warningText.empty()) + // { + // warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources())); + // warningsTextBlock.Inlines().Append(Documents::LineBreak{}); + // } + // } + + Controls::ContentDialog dialog; + dialog.Title(winrt::box_value(title)); + dialog.Content(winrt::box_value(warningsTextBlock)); + dialog.CloseButtonText(buttonText); + dialog.DefaultButton(Controls::ContentDialogButton::Close); + + ShowDialog(dialog); + } + + // Method Description: + // - Triggered when the application is finished loading. If we failed to load + // the settings, then this will display the error dialog. This is done + // here instead of when loading the settings, because we need our UI to be + // visible to display the dialog, and when we're loading the settings, + // the UI might not be visible yet. + // Arguments: + // - + void TerminalWindow::_OnLoaded(const IInspectable& /*sender*/, + const RoutedEventArgs& /*eventArgs*/) + { + if (_settings.GlobalSettings().InputServiceWarning()) + { + const auto keyboardServiceIsDisabled = !_IsKeyboardServiceEnabled(); + if (keyboardServiceIsDisabled) + { + _root->ShowKeyboardServiceWarning(); + } + } + + // TODO! Yea, more of "how do we get _settingsLoadedResult / warnings / error" into here? + // + // if (FAILED(_settingsLoadedResult)) + // { + // const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle"); + // const winrt::hstring textKey = USES_RESOURCE(L"InitialJsonParseErrorText"); + // _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); + // } + // else if (_settingsLoadedResult == S_FALSE) + // { + // _ShowLoadWarningsDialog(); + // } + } + + // Method Description: + // - Helper for determining if the "Touch Keyboard and Handwriting Panel + // Service" is enabled. If it isn't, we want to be able to display a + // warning to the user, because they won't be able to type in the + // Terminal. + // Return Value: + // - true if the service is enabled, or if we fail to query the service. We + // return true in that case, to be less noisy (though, that is unexpected) + bool TerminalWindow::_IsKeyboardServiceEnabled() + { + if (IsUwp()) + { + return true; + } + + // If at any point we fail to open the service manager, the service, + // etc, then just quick return true to disable the dialog. We'd rather + // not be noisy with this dialog if we failed for some reason. + + // Open the service manager. This will return 0 if it failed. + wil::unique_schandle hManager{ OpenSCManagerW(nullptr, nullptr, 0) }; + + if (LOG_LAST_ERROR_IF(!hManager.is_valid())) + { + return true; + } + + // Get a handle to the keyboard service + wil::unique_schandle hService{ OpenServiceW(hManager.get(), TabletInputServiceKey.data(), SERVICE_QUERY_STATUS) }; + + // Windows 11 doesn't have a TabletInputService. + // (It was renamed to TextInputManagementService, because people kept thinking that a + // service called "tablet-something" is system-irrelevant on PCs and can be disabled.) + if (!hService.is_valid()) + { + return true; + } + + // Get the current state of the service + SERVICE_STATUS status{ 0 }; + if (!LOG_IF_WIN32_BOOL_FALSE(QueryServiceStatus(hService.get(), &status))) + { + return true; + } + + const auto state = status.dwCurrentState; + return (state == SERVICE_RUNNING || state == SERVICE_START_PENDING); + } + + // Method Description: + // - Get the size in pixels of the client area we'll need to launch this + // terminal app. This method will use the default profile's settings to do + // this calculation, as well as the _system_ dpi scaling. See also + // TermControl::GetProposedDimensions. + // Arguments: + // - + // Return Value: + // - a point containing the requested dimensions in pixels. + winrt::Windows::Foundation::Size TerminalWindow::GetLaunchDimensions(uint32_t dpi) + { + winrt::Windows::Foundation::Size proposedSize{}; + + const auto scale = static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); + if (const auto layout = _root->LoadPersistedLayout(_settings)) + { + if (layout.InitialSize()) + { + proposedSize = layout.InitialSize().Value(); + // The size is saved as a non-scaled real pixel size, + // so we need to scale it appropriately. + proposedSize.Height = proposedSize.Height * scale; + proposedSize.Width = proposedSize.Width * scale; + } + } + + if (_appArgs.GetSize().has_value() || (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) }; + + const til::size emptySize{}; + const auto commandlineSize = _appArgs.GetSize().value_or(emptySize); + proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(), + dpi, + commandlineSize.width, + commandlineSize.height); + } + + // 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 + // the height of the tab bar here. + if (_settings.GlobalSettings().ShowTabsInTitlebar()) + { + // If we're showing the tabs in the titlebar, we need to use a + // TitlebarControl here to calculate how much space to reserve. + // + // We'll create a fake TitlebarControl, and we'll propose an + // available size to it with Measure(). After Measure() is called, + // the TitlebarControl's DesiredSize will contain the _unscaled_ + // size that the titlebar would like to use. We'll use that as part + // of the height calculation here. + auto titlebar = TitlebarControl{ static_cast(0) }; + titlebar.Measure({ SHRT_MAX, SHRT_MAX }); + proposedSize.Height += (titlebar.DesiredSize().Height) * scale; + } + else if (_settings.GlobalSettings().AlwaysShowTabs()) + { + // Otherwise, let's use a TabRowControl to calculate how much extra + // space we'll need. + // + // Similarly to above, we'll measure it with an arbitrarily large + // available space, to make sure we get all the space it wants. + auto tabControl = TabRowControl(); + tabControl.Measure({ SHRT_MAX, SHRT_MAX }); + + // For whatever reason, there's about 10px of unaccounted-for space + // in the application. I couldn't tell you where these 10px are + // coming from, but they need to be included in this math. + proposedSize.Height += (tabControl.DesiredSize().Height + 10) * scale; + } + + return proposedSize; + } + + // Method Description: + // - Get the launch mode in json settings file. Now there + // two launch mode: default, maximized. Default means the window + // will launch according to the launch dimensions provided. Maximized + // means the window will launch as a maximized window + // Arguments: + // - + // Return Value: + // - LaunchMode enum that indicates the launch mode + LaunchMode TerminalWindow::GetLaunchMode() + { + // GH#4620/#5801 - If the user passed --maximized or --fullscreen on the + // commandline, then use that to override the value from the settings. + const auto valueFromSettings = _settings.GlobalSettings().LaunchMode(); + const auto valueFromCommandlineArgs = _appArgs.GetLaunchMode(); + if (const auto layout = _root->LoadPersistedLayout(_settings)) + { + if (layout.LaunchMode()) + { + return layout.LaunchMode().Value(); + } + } + return valueFromCommandlineArgs.has_value() ? + valueFromCommandlineArgs.value() : + valueFromSettings; + } + + // Method Description: + // - Get the user defined initial position from Json settings file. + // This position represents the top left corner of the Terminal window. + // This setting is optional, if not provided, we will use the system + // default size, which is provided in IslandWindow::MakeWindow. + // Arguments: + // - defaultInitialX: the system default x coordinate value + // - defaultInitialY: the system default y coordinate value + // Return Value: + // - a point containing the requested initial position in pixels. + TerminalApp::InitialPosition TerminalWindow::GetInitialPosition(int64_t defaultInitialX, int64_t defaultInitialY) + { + auto initialPosition{ _settings.GlobalSettings().InitialPosition() }; + + if (const auto layout = _root->LoadPersistedLayout(_settings)) + { + if (layout.InitialPosition()) + { + initialPosition = layout.InitialPosition().Value(); + } + } + + // Commandline args trump everything else + if (_appArgs.GetPosition().has_value()) + { + initialPosition = _appArgs.GetPosition().value(); + } + + return { + initialPosition.X ? initialPosition.X.Value() : defaultInitialX, + initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY + }; + } + + bool TerminalWindow::CenterOnLaunch() + { + // If the position has been specified on the commandline, don't center on launch + return _settings.GlobalSettings().CenterOnLaunch() && !_appArgs.GetPosition().has_value(); + } + + // Method Description: + // - See Pane::CalcSnappedDimension + float TerminalWindow::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const + { + return _root->CalcSnappedDimension(widthOrHeight, dimension); + } + + // Method Description: + // - Update the current theme of the application. This will trigger our + // RequestedThemeChanged event, to have our host change the theme of the + // root of the application. + // Arguments: + // - newTheme: The ElementTheme to apply to our elements. + void TerminalWindow::_RefreshThemeRoutine() + { + // Propagate the event to the host layer, so it can update its own UI + _RequestedThemeChangedHandlers(*this, Theme()); + } + + void TerminalWindow::UpdateSettings(const HRESULT settingsLoadedResult, const CascadiaSettings& settings) + { + if (FAILED(settingsLoadedResult)) + { + const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); + const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); + _ShowLoadErrorsDialog(titleKey, textKey, settingsLoadedResult); + return; + } + else if (settingsLoadedResult == S_FALSE) + { + _ShowLoadWarningsDialog(); + } + _settings = settings; + // Update the settings in TerminalPage + _root->SetSettings(_settings, true); + _RefreshThemeRoutine(); + } + void TerminalWindow::_OpenSettingsUI() + { + _root->OpenSettingsUI(); + } + UIElement TerminalWindow::GetRoot() noexcept + { + return _root.as(); + } + + // Method Description: + // - Gets the title of the currently focused terminal control. If there + // isn't a control selected for any reason, returns "Terminal" + // Arguments: + // - + // Return Value: + // - the title of the focused control if there is one, else "Terminal" + hstring TerminalWindow::Title() + { + if (_root) + { + return _root->Title(); + } + return { L"Terminal" }; + } + + // Method Description: + // - Used to tell the app that the titlebar has been clicked. The App won't + // actually receive any clicks in the titlebar area, so this is a helper + // to clue the app in that a click has happened. The App will use this as + // a indicator that it needs to dismiss any open flyouts. + // Arguments: + // - + // Return Value: + // - + void TerminalWindow::TitlebarClicked() + { + if (_root) + { + _root->TitlebarClicked(); + } + } + + // Method Description: + // - Used to tell the PTY connection that the window visibility has changed. + // The underlying PTY might need to expose window visibility status to the + // client application for the `::GetConsoleWindow()` API. + // Arguments: + // - showOrHide - True is show; false is hide. + // Return Value: + // - + void TerminalWindow::WindowVisibilityChanged(const bool showOrHide) + { + if (_root) + { + _root->WindowVisibilityChanged(showOrHide); + } + } + + // Method Description: + // - Implements the F7 handler (per GH#638) + // - Implements the Alt handler (per GH#6421) + // Return value: + // - whether the key was handled + bool TerminalWindow::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down) + { + if (_root) + { + // Manually bubble the OnDirectKeyEvent event up through the focus tree. + auto xamlRoot{ _root->XamlRoot() }; + auto focusedObject{ Windows::UI::Xaml::Input::FocusManager::GetFocusedElement(xamlRoot) }; + do + { + if (auto keyListener{ focusedObject.try_as() }) + { + if (keyListener.OnDirectKeyEvent(vkey, scanCode, down)) + { + return true; + } + // otherwise, keep walking. bubble the event manually. + } + + if (auto focusedElement{ focusedObject.try_as() }) + { + focusedObject = focusedElement.Parent(); + + // Parent() seems to return null when the focusedElement is created from an ItemTemplate. + // Use the VisualTreeHelper's GetParent as a fallback. + if (!focusedObject) + { + focusedObject = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetParent(focusedElement); + } + } + else + { + break; // we hit a non-FE object, stop bubbling. + } + } while (focusedObject); + } + return false; + } + + // Method Description: + // - Used to tell the app that the 'X' button has been clicked and + // the user wants to close the app. We kick off the close warning + // experience. + // Arguments: + // - + // Return Value: + // - + void TerminalWindow::CloseWindow(LaunchPosition pos) + { + if (_root) + { + // If persisted layout is enabled and we are the last window closing + // we should save our state. + if (_root->ShouldUsePersistedLayout(_settings) && _numOpenWindows == 1) + { + if (const auto layout = _root->GetWindowLayout()) + { + layout.InitialPosition(pos); + const auto state = ApplicationState::SharedInstance(); + state.PersistedWindowLayouts(winrt::single_threaded_vector({ layout })); + } + } + + _root->CloseWindow(false); + } + } + + winrt::TerminalApp::TaskbarState TerminalWindow::TaskbarState() + { + if (_root) + { + return _root->TaskbarState(); + } + return {}; + } + + winrt::Windows::UI::Xaml::Media::Brush TerminalWindow::TitlebarBrush() + { + if (_root) + { + return _root->TitlebarBrush(); + } + return { nullptr }; + } + void TerminalWindow::WindowActivated(const bool activated) + { + _root->WindowActivated(activated); + } + + // Method Description: + // - Returns true if we should exit the application before even starting the + // window. We might want to do this if we're displaying an error message or + // the version string, or if we want to open the settings file. + // Arguments: + // - + // Return Value: + // - true iff we should exit the application before even starting the window + bool TerminalWindow::ShouldExitEarly() + { + return _appArgs.ShouldExitEarly(); + } + + bool TerminalWindow::FocusMode() const + { + return _root ? _root->FocusMode() : false; + } + + bool TerminalWindow::Fullscreen() const + { + return _root ? _root->Fullscreen() : false; + } + + void TerminalWindow::Maximized(bool newMaximized) + { + if (_root) + { + _root->Maximized(newMaximized); + } + } + + bool TerminalWindow::AlwaysOnTop() const + { + return _root ? _root->AlwaysOnTop() : false; + } + + //////////////////////////////////////////////////////////////////////////// + + // bool TerminalWindow::HasSettingsStartupActions() const noexcept + // { + // return _hasSettingsStartupActions; + // } + + bool TerminalWindow::HasCommandlineArguments() const noexcept + { + return _hasCommandLineArguments; + } + + // Method Description: + // - Sets the initial commandline to process on startup, and attempts to + // parse it. Commands will be parsed into a list of ShortcutActions that + // will be processed on TerminalPage::Create(). + // - This function will have no effective result after Create() is called. + // - This function returns 0, unless a there was a non-zero result from + // trying to parse one of the commands provided. In that case, no commands + // after the failing command will be parsed, and the non-zero code + // returned. + // Arguments: + // - args: an array of strings to process as a commandline. These args can contain spaces + // Return Value: + // - the result of the first command who's parsing returned a non-zero code, + // or 0. (see TerminalWindow::_ParseArgs) + int32_t TerminalWindow::SetStartupCommandline(array_view args) + { + const auto result = _appArgs.ParseArgs(args); + if (result == 0) + { + // If the size of the arguments list is 1, + // then it contains only the executable name and no other arguments. + _hasCommandLineArguments = args.size() > 1; + _appArgs.ValidateStartupCommands(); + if (const auto idx = _appArgs.GetPersistedLayoutIdx()) + { + _root->SetPersistedLayoutIdx(idx.value()); + } + _root->SetStartupActions(_appArgs.GetStartupActions()); + + // Check if we were started as a COM server for inbound connections of console sessions + // coming out of the operating system default application feature. If so, + // tell TerminalPage to start the listener as we have to make sure it has the chance + // to register a handler to hear about the requests first and is all ready to receive + // them before the COM server registers itself. Otherwise, the request might come + // in and be routed to an event with no handlers or a non-ready Page. + if (_appArgs.IsHandoffListener()) + { + _root->SetInboundListener(true); + } + } + + return result; + } + + // Method Description: + // - Parse the provided commandline arguments into actions, and try to + // perform them immediately. + // - This function returns 0, unless a there was a non-zero result from + // trying to parse one of the commands provided. In that case, no commands + // after the failing command will be parsed, and the non-zero code + // returned. + // - If a non-empty cwd is provided, the entire terminal exe will switch to + // that CWD while we handle these actions, then return to the original + // CWD. + // Arguments: + // - args: an array of strings to process as a commandline. These args can contain spaces + // - cwd: The directory to use as the CWD while performing these actions. + // Return Value: + // - the result of the first command who's parsing returned a non-zero code, + // or 0. (see TerminalWindow::_ParseArgs) + int32_t TerminalWindow::ExecuteCommandline(array_view args, + const winrt::hstring& cwd) + { + ::TerminalApp::AppCommandlineArgs appArgs; + auto result = appArgs.ParseArgs(args); + if (result == 0) + { + auto actions = winrt::single_threaded_vector(std::move(appArgs.GetStartupActions())); + + _root->ProcessStartupActions(actions, false, cwd); + + if (appArgs.IsHandoffListener()) + { + _root->SetInboundListener(true); + } + } + // Return the result of parsing with commandline, though it may or may not be used. + return result; + } + + // Method Description: + // - If there were any errors parsing the commandline that was used to + // initialize the terminal, this will return a string containing that + // message. If there were no errors, this message will be blank. + // - If the user requested help on any command (using --help), this will + // contain the help message. + // - If the user requested the version number (using --version), this will + // contain the version string. + // Arguments: + // - + // Return Value: + // - the help text or error message for the provided commandline, if one + // exists, otherwise the empty string. + winrt::hstring TerminalWindow::ParseCommandlineMessage() + { + return winrt::to_hstring(_appArgs.GetExitMessage()); + } + + //////////////////////////////////////////////////////////////////////////// + + bool TerminalWindow::ShouldUsePersistedLayout() + { + return _root != nullptr ? _root->ShouldUsePersistedLayout(_settings) : false; + } + + void TerminalWindow::SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts) + { + std::vector converted; + converted.reserve(layouts.Size()); + + for (const auto& json : layouts) + { + if (json != L"") + { + converted.emplace_back(WindowLayout::FromJson(json)); + } + } + + ApplicationState::SharedInstance().PersistedWindowLayouts(winrt::single_threaded_vector(std::move(converted))); + } + + hstring TerminalWindow::GetWindowLayoutJson(LaunchPosition position) + { + if (_root != nullptr) + { + if (const auto layout = _root->GetWindowLayout()) + { + layout.InitialPosition(position); + return WindowLayout::ToJson(layout); + } + } + return L""; + } + + void TerminalWindow::IdentifyWindow() + { + if (_root) + { + _root->IdentifyWindow(); + } + } + + winrt::hstring TerminalWindow::WindowName() + { + return _root ? _root->WindowName() : L""; + } + void TerminalWindow::WindowName(const winrt::hstring& name) + { + if (_root) + { + _root->WindowName(name); + } + } + uint64_t TerminalWindow::WindowId() + { + return _root ? _root->WindowId() : 0; + } + void TerminalWindow::WindowId(const uint64_t& id) + { + if (_root) + { + _root->WindowId(id); + } + } + + void TerminalWindow::SetPersistedLayoutIdx(const uint32_t idx) + { + if (_root) + { + _root->SetPersistedLayoutIdx(idx); + } + } + + void TerminalWindow::SetNumberOfOpenWindows(const uint64_t num) + { + _numOpenWindows = num; + if (_root) + { + _root->SetNumberOfOpenWindows(num); + } + } + + void TerminalWindow::RenameFailed() + { + if (_root) + { + _root->RenameFailed(); + } + } + + bool TerminalWindow::IsQuakeWindow() const noexcept + { + return _root->IsQuakeWindow(); + } + + void TerminalWindow::RequestExitFullscreen() + { + _root->SetFullscreen(false); + } + + bool TerminalWindow::AutoHideWindow() + { + return _settings.GlobalSettings().AutoHideWindow(); + } +}; diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h new file mode 100644 index 00000000000..aee3173bcb8 --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "TerminalWindow.g.h" +#include "SystemMenuChangeArgs.g.h" + +#include "TerminalPage.h" + +#include +#include + +#ifdef UNIT_TESTING +// fwdecl unittest classes +namespace TerminalAppLocalTests +{ + class CommandlineTest; +}; +#endif + +namespace winrt::TerminalApp::implementation +{ + struct SystemMenuChangeArgs : SystemMenuChangeArgsT + { + WINRT_PROPERTY(winrt::hstring, Name, L""); + WINRT_PROPERTY(SystemMenuChangeAction, Action, SystemMenuChangeAction::Add); + WINRT_PROPERTY(SystemMenuItemHandler, Handler, nullptr); + + public: + SystemMenuChangeArgs(const winrt::hstring& name, SystemMenuChangeAction action, SystemMenuItemHandler handler = nullptr) : + _Name{ name }, _Action{ action }, _Handler{ handler } {}; + }; + + struct TerminalWindow : TerminalWindowT + { + public: + TerminalWindow(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); + ~TerminalWindow() = default; + + STDMETHODIMP Initialize(HWND hwnd); + + void Create(); + bool IsUwp() const noexcept; + void RunAsUwp(); + bool IsElevated() const noexcept; + + void Quit(); + + void UpdateSettings(const HRESULT settingsLoadedResult, const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); + + bool HasCommandlineArguments() const noexcept; + // bool HasSettingsStartupActions() const noexcept; + int32_t SetStartupCommandline(array_view actions); + int32_t ExecuteCommandline(array_view actions, const winrt::hstring& cwd); + winrt::hstring ParseCommandlineMessage(); + bool ShouldExitEarly(); + + bool FocusMode() const; + bool Fullscreen() const; + void Maximized(bool newMaximized); + bool AlwaysOnTop() const; + bool AutoHideWindow(); + + bool ShouldUsePersistedLayout(); + + hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position); + void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts); + void IdentifyWindow(); + void RenameFailed(); + winrt::hstring WindowName(); + void WindowName(const winrt::hstring& name); + uint64_t WindowId(); + void WindowId(const uint64_t& id); + void SetPersistedLayoutIdx(const uint32_t idx); + void SetNumberOfOpenWindows(const uint64_t num); + bool IsQuakeWindow() const noexcept; + void RequestExitFullscreen(); + + Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi); + bool CenterOnLaunch(); + TerminalApp::InitialPosition GetInitialPosition(int64_t defaultInitialX, int64_t defaultInitialY); + winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme(); + Microsoft::Terminal::Settings::Model::LaunchMode GetLaunchMode(); + bool GetShowTabsInTitlebar(); + bool GetInitialAlwaysOnTop(); + float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; + + Windows::UI::Xaml::UIElement GetRoot() noexcept; + + hstring Title(); + void TitlebarClicked(); + bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); + + void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position); + void WindowVisibilityChanged(const bool showOrHide); + + winrt::TerminalApp::TaskbarState TaskbarState(); + winrt::Windows::UI::Xaml::Media::Brush TitlebarBrush(); + void WindowActivated(const bool activated); + + bool GetMinimizeToNotificationArea(); + bool GetAlwaysShowNotificationIcon(); + bool GetShowTitleInTitlebar(); + + winrt::Windows::Foundation::IAsyncOperation ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); + void DismissDialog(); + + Microsoft::Terminal::Settings::Model::Theme Theme(); + + // -------------------------------- WinRT Events --------------------------------- + // PropertyChanged is surprisingly not a typed event, so we'll define that one manually. + // Usually we'd just do + // WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); + // + // But what we're doing here is exposing the Page's PropertyChanged _as + // our own event_. It's a FORWARDED_CALLBACK, essentially. + winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler) { return _root->PropertyChanged(handler); } + void PropertyChanged(winrt::event_token const& token) { _root->PropertyChanged(token); } + + TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Settings::Model::Theme); + + private: + bool _isUwp{ false }; + bool _isElevated{ false }; + // If you add controls here, but forget to null them either here or in + // the ctor, you're going to have a bad time. It'll mysteriously fail to + // activate the AppLogic. + // ALSO: If you add any UIElements as roots here, make sure they're + // updated in _ApplyTheme. The root currently is _root. + winrt::com_ptr _root{ nullptr }; + winrt::Windows::UI::Xaml::Controls::ContentDialog _dialog{ nullptr }; + std::shared_mutex _dialogLock; + + bool _hasCommandLineArguments{ false }; + ::TerminalApp::AppCommandlineArgs _appArgs; + + uint64_t _numOpenWindows{ 0 }; + + Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; + + void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult); + void _ShowLoadWarningsDialog(); + bool _IsKeyboardServiceEnabled(); + + void _RefreshThemeRoutine(); + void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); + void _OpenSettingsUI(); + // These are events that are handled by the TerminalPage, but are + // exposed through the AppLogic. This macro is used to forward the event + // directly to them. + FORWARDED_TYPED_EVENT(SetTitleBarContent, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement, _root, SetTitleBarContent); + FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged); + FORWARDED_TYPED_EVENT(LastTabClosed, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs, _root, LastTabClosed); + FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged); + FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged); + FORWARDED_TYPED_EVENT(ChangeMaximizeRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, ChangeMaximizeRequested); + FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged); + FORWARDED_TYPED_EVENT(RaiseVisualBell, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, RaiseVisualBell); + FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress); + FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested); + FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested); + FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged); + FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested); + FORWARDED_TYPED_EVENT(CloseRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, CloseRequested); + FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu); + FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested); + FORWARDED_TYPED_EVENT(ShowWindowChanged, Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs, _root, ShowWindowChanged); + + TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs); + +#ifdef UNIT_TESTING + friend class TerminalAppLocalTests::CommandlineTest; +#endif + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(TerminalWindow); +} diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl new file mode 100644 index 00000000000..fb5bfeeeaa8 --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "TerminalPage.idl"; +import "ShortcutActionDispatch.idl"; +import "IDirectKeyListener.idl"; + +namespace TerminalApp +{ + struct InitialPosition + { + Int64 X; + Int64 Y; + }; + + delegate void SystemMenuItemHandler(); + + enum SystemMenuChangeAction + { + Add = 0, + Remove = 1 + }; + + [default_interface] runtimeclass SystemMenuChangeArgs { + String Name { get; }; + SystemMenuChangeAction Action { get; }; + SystemMenuItemHandler Handler { get; }; + }; + + // See IDialogPresenter and TerminalPage's DialogPresenter for more + // information. + [default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged + { + TerminalWindow(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); + + + // For your own sanity, it's better to do setup outside the ctor. + // If you do any setup in the ctor that ends up throwing an exception, + // then it might look like TermApp just failed to activate, which will + // cause you to chase down the rabbit hole of "why is TermApp not + // registered?" when it definitely is. + void Create(); + + Boolean IsUwp(); + void RunAsUwp(); + Boolean IsElevated(); + + Boolean HasCommandlineArguments(); + // Boolean HasSettingsStartupActions(); + + Int32 SetStartupCommandline(String[] commands); + + Int32 ExecuteCommandline(String[] commands, String cwd); + String ParseCommandlineMessage { get; }; + Boolean ShouldExitEarly { get; }; + + void Quit(); + + + Windows.UI.Xaml.UIElement GetRoot(); + + String Title { get; }; + Boolean FocusMode { get; }; + Boolean Fullscreen { get; }; + void Maximized(Boolean newMaximized); + Boolean AlwaysOnTop { get; }; + Boolean AutoHideWindow { get; }; + + void IdentifyWindow(); + String WindowName; + UInt64 WindowId; + void SetPersistedLayoutIdx(UInt32 idx); + void SetNumberOfOpenWindows(UInt64 num); + void RenameFailed(); + void RequestExitFullscreen(); + Boolean IsQuakeWindow(); + + Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi); + Boolean CenterOnLaunch { get; }; + + InitialPosition GetInitialPosition(Int64 defaultInitialX, Int64 defaultInitialY); + Windows.UI.Xaml.ElementTheme GetRequestedTheme(); + Microsoft.Terminal.Settings.Model.LaunchMode GetLaunchMode(); + Boolean GetShowTabsInTitlebar(); + Boolean GetInitialAlwaysOnTop(); + Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension); + void TitlebarClicked(); + void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position); + void WindowVisibilityChanged(Boolean showOrHide); + + TaskbarState TaskbarState{ get; }; + Windows.UI.Xaml.Media.Brush TitlebarBrush { get; }; + void WindowActivated(Boolean activated); + + Boolean ShouldUsePersistedLayout(); + // Boolean ShouldImmediatelyHandoffToElevated(); + // void HandoffToElevated(); + String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position); + void SaveWindowLayoutJsons(Windows.Foundation.Collections.IVector layouts); + + Boolean GetMinimizeToNotificationArea(); + Boolean GetAlwaysShowNotificationIcon(); + Boolean GetShowTitleInTitlebar(); + + // See IDialogPresenter and TerminalPage's DialogPresenter for more + // information. + Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); + void DismissDialog(); + + event Windows.Foundation.TypedEventHandler SetTitleBarContent; + event Windows.Foundation.TypedEventHandler TitleChanged; + event Windows.Foundation.TypedEventHandler LastTabClosed; + event Windows.Foundation.TypedEventHandler RequestedThemeChanged; + event Windows.Foundation.TypedEventHandler FocusModeChanged; + event Windows.Foundation.TypedEventHandler FullscreenChanged; + event Windows.Foundation.TypedEventHandler ChangeMaximizeRequested; + event Windows.Foundation.TypedEventHandler AlwaysOnTopChanged; + event Windows.Foundation.TypedEventHandler RaiseVisualBell; + event Windows.Foundation.TypedEventHandler SetTaskbarProgress; + event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; + event Windows.Foundation.TypedEventHandler RenameWindowRequested; + event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; + event Windows.Foundation.TypedEventHandler SummonWindowRequested; + event Windows.Foundation.TypedEventHandler CloseRequested; + event Windows.Foundation.TypedEventHandler OpenSystemMenu; + event Windows.Foundation.TypedEventHandler QuitRequested; + event Windows.Foundation.TypedEventHandler SystemMenuChangeRequested; + event Windows.Foundation.TypedEventHandler ShowWindowChanged; + + } +} From 439b21f879be488b0ebc8e3901da6b3dd568bb99 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 30 Jan 2023 17:06:31 -0600 Subject: [PATCH 02/25] this is dangerously close to compiling --- src/cascadia/TerminalApp/AppLogic.cpp | 5 +- src/cascadia/TerminalApp/AppLogic.h | 3 + src/cascadia/TerminalApp/AppLogic.idl | 3 + src/cascadia/TerminalApp/TerminalWindow.idl | 1 - src/cascadia/WindowsTerminal/AppHost.cpp | 216 ++++++++++---------- src/cascadia/WindowsTerminal/AppHost.h | 41 ++-- 6 files changed, 142 insertions(+), 127 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 4d5cabbed7c..40d2637aa3b 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -6,7 +6,6 @@ #include "../inc/WindowingBehavior.h" #include "AppLogic.g.cpp" #include "FindTargetWindowResult.g.cpp" -#include #include #include @@ -674,4 +673,8 @@ namespace winrt::TerminalApp::implementation return _settings.GlobalSettings().CurrentTheme(); } + TerminalApp::TerminalWindow AppLogic::CreateNewWindow() + { + return *winrt::make_self(_settings); + } } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index b6a3a8b6ba9..eeee6673366 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -9,6 +9,7 @@ #include "Jumplist.h" #include "LanguageProfileNotifier.h" #include "AppCommandlineArgs.h" +#include "TerminalWindow.h" #include #include @@ -63,6 +64,8 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::Theme Theme(); + TerminalApp::TerminalWindow CreateNewWindow(); + TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); private: diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index bc48cf5a965..2a3488e6f95 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "TerminalWindow.idl"; namespace TerminalApp { @@ -34,6 +35,8 @@ namespace TerminalApp FindTargetWindowResult FindTargetWindow(String[] args); + TerminalWindow CreateNewWindow(); + Windows.Foundation.Collections.IMapView GlobalHotkeys(); event Windows.Foundation.TypedEventHandler SettingsChanged; diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index fb5bfeeeaa8..fcd8cd9346f 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -56,7 +56,6 @@ namespace TerminalApp void Quit(); - Windows.UI.Xaml.UIElement GetRoot(); String Title { get; }; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 9b20e3c7aa2..48bc98e683e 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -31,11 +31,12 @@ static constexpr short KeyPressed{ gsl::narrow_cast(0x8000) }; AppHost::AppHost() noexcept : _app{}, _windowManager{}, - _logic{ nullptr }, // don't make one, we're going to take a ref on app's + _appLogic{ nullptr }, // don't make one, we're going to take a ref on app's + _windowLogic{ nullptr }, _window{ nullptr }, _getWindowLayoutThrottler{} // this will get set if we become the monarch { - _logic = _app.Logic(); // get a ref to app's logic + _appLogic = _app.Logic(); // get a ref to app's logic // Inform the WindowManager that it can use us to find the target window for // a set of commandline args. This needs to be done before @@ -54,10 +55,11 @@ AppHost::AppHost() noexcept : return; } - _useNonClientArea = _logic.GetShowTabsInTitlebar(); + // _HandleCommandlineArgs will create a _windowLogic + _useNonClientArea = _windowLogic.GetShowTabsInTitlebar(); if (_useNonClientArea) { - _window = std::make_unique(_logic.GetRequestedTheme()); + _window = std::make_unique(_windowLogic.GetRequestedTheme()); } else { @@ -67,7 +69,7 @@ AppHost::AppHost() noexcept : // Update our own internal state tracking if we're in quake mode or not. _IsQuakeWindowChanged(nullptr, nullptr); - _window->SetMinimizeToNotificationAreaBehavior(_logic.GetMinimizeToNotificationArea()); + _window->SetMinimizeToNotificationAreaBehavior(_windowLogic.GetMinimizeToNotificationArea()); // Tell the window to callback to us when it's about to handle a WM_CREATE auto pfn = std::bind(&AppHost::_HandleCreateWindow, @@ -77,8 +79,8 @@ AppHost::AppHost() noexcept : std::placeholders::_3); _window->SetCreateCallback(pfn); - _window->SetSnapDimensionCallback(std::bind(&winrt::TerminalApp::AppLogic::CalcSnappedDimension, - _logic, + _window->SetSnapDimensionCallback(std::bind(&winrt::TerminalApp::TerminalWindow::CalcSnappedDimension, + _windowLogic, std::placeholders::_1, std::placeholders::_2)); @@ -100,10 +102,10 @@ AppHost::AppHost() noexcept : _window->WindowActivated({ this, &AppHost::_WindowActivated }); _window->WindowMoved({ this, &AppHost::_WindowMoved }); _window->HotkeyPressed({ this, &AppHost::_GlobalHotkeyPressed }); - _window->ShouldExitFullscreen({ &_logic, &winrt::TerminalApp::AppLogic::RequestExitFullscreen }); + _window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen }); - _window->SetAlwaysOnTop(_logic.GetInitialAlwaysOnTop()); - _window->SetAutoHideWindow(_logic.AutoHideWindow()); + _window->SetAlwaysOnTop(_windowLogic.GetInitialAlwaysOnTop()); + _window->SetAutoHideWindow(_windowLogic.AutoHideWindow()); _window->MakeWindow(); @@ -152,9 +154,9 @@ AppHost::~AppHost() bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down) { - if (_logic) + if (_windowLogic) { - return _logic.OnDirectKeyEvent(vkey, scanCode, down); + return _windowLogic.OnDirectKeyEvent(vkey, scanCode, down); } return false; } @@ -169,9 +171,9 @@ bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, cons void AppHost::SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/) { - if (_logic) + if (_windowLogic) { - const auto state = _logic.TaskbarState(); + const auto state = _windowLogic.TaskbarState(); _window->SetTaskbarProgress(gsl::narrow_cast(state.State()), gsl::narrow_cast(state.Progress())); } @@ -231,12 +233,16 @@ void AppHost::_HandleCommandlineArgs() return; } + // We did want to make a window, so let's instantiate it here. + // We don't have XAML yet, but we do have other stuff. + _windowLogic = _appLogic.CreateNewWindow(); + if (auto peasant{ _windowManager.CurrentWindow() }) { if (auto args{ peasant.InitialArgs() }) { - const auto result = _logic.SetStartupCommandline(args.Commandline()); - const auto message = _logic.ParseCommandlineMessage(); + const auto result = _windowLogic.SetStartupCommandline(args.Commandline()); + const auto message = _windowLogic.ParseCommandlineMessage(); if (!message.empty()) { const auto displayHelp = result == 0; @@ -249,7 +255,7 @@ void AppHost::_HandleCommandlineArgs() GetStringResource(messageTitle).data(), MB_OK | messageIcon); - if (_logic.ShouldExitEarly()) + if (_windowLogic.ShouldExitEarly()) { ExitProcess(result); } @@ -263,10 +269,10 @@ void AppHost::_HandleCommandlineArgs() // the window at all here. In that case, we're going through this // special escape hatch to dispatch all the calls to elevate-shim, and // then we're going to exit immediately. - if (_logic.ShouldImmediatelyHandoffToElevated()) + if (_windowLogic.ShouldImmediatelyHandoffToElevated()) { _shouldCreateWindow = false; - _logic.HandoffToElevated(); + _windowLogic.HandoffToElevated(); return; } @@ -288,7 +294,7 @@ void AppHost::_HandleCommandlineArgs() { const auto numPeasants = _windowManager.GetNumberOfPeasants(); const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); - if (_logic.ShouldUsePersistedLayout() && + if (_windowLogic.ShouldUsePersistedLayout() && layouts && layouts.Size() > 0) { @@ -299,9 +305,9 @@ void AppHost::_HandleCommandlineArgs() // Otherwise create this window normally with its commandline, and create // a new window using the first saved layout information. // The 2nd+ layout will always get a new window. - if (numPeasants == 1 && !_logic.HasCommandlineArguments() && !_logic.HasSettingsStartupActions()) + if (numPeasants == 1 && !_windowLogic.HasCommandlineArguments() && !_appLogic.HasSettingsStartupActions()) { - _logic.SetPersistedLayoutIdx(startIdx); + _windowLogic.SetPersistedLayoutIdx(startIdx); startIdx += 1; } @@ -328,10 +334,10 @@ void AppHost::_HandleCommandlineArgs() )); } } - _logic.SetNumberOfOpenWindows(numPeasants); + _windowLogic.SetNumberOfOpenWindows(numPeasants); } - _logic.WindowName(peasant.WindowName()); - _logic.WindowId(peasant.GetID()); + _windowLogic.WindowName(peasant.WindowName()); + _windowLogic.WindowId(peasant.GetID()); } } @@ -350,7 +356,7 @@ void AppHost::Initialize() { _window->Initialize(); - if (auto withWindow{ _logic.try_as() }) + if (auto withWindow{ _windowLogic.try_as() }) { withWindow->Initialize(_window->GetHandle()); } @@ -360,7 +366,7 @@ void AppHost::Initialize() // Register our callback for when the app's non-client content changes. // This has to be done _before_ App::Create, as the app might set the // content in Create. - _logic.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent }); + _windowLogic.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent }); } // MORE EVENT HANDLERS HERE! @@ -383,27 +389,27 @@ void AppHost::Initialize() }); // If the user requests a close in another way handle the same as if the 'X' // was clicked. - _revokers.CloseRequested = _logic.CloseRequested(winrt::auto_revoke, { this, &AppHost::_CloseRequested }); + _revokers.CloseRequested = _windowLogic.CloseRequested(winrt::auto_revoke, { this, &AppHost::_CloseRequested }); // Add an event handler to plumb clicks in the titlebar area down to the // application layer. - _window->DragRegionClicked([this]() { _logic.TitlebarClicked(); }); + _window->DragRegionClicked([this]() { _windowLogic.TitlebarClicked(); }); - _window->WindowVisibilityChanged([this](bool showOrHide) { _logic.WindowVisibilityChanged(showOrHide); }); - _window->UpdateSettingsRequested([this]() { _logic.ReloadSettings(); }); + _window->WindowVisibilityChanged([this](bool showOrHide) { _windowLogic.WindowVisibilityChanged(showOrHide); }); + _window->UpdateSettingsRequested([this]() { _appLogic.ReloadSettings(); }); - _revokers.RequestedThemeChanged = _logic.RequestedThemeChanged(winrt::auto_revoke, { this, &AppHost::_UpdateTheme }); - _revokers.FullscreenChanged = _logic.FullscreenChanged(winrt::auto_revoke, { this, &AppHost::_FullscreenChanged }); - _revokers.FocusModeChanged = _logic.FocusModeChanged(winrt::auto_revoke, { this, &AppHost::_FocusModeChanged }); - _revokers.AlwaysOnTopChanged = _logic.AlwaysOnTopChanged(winrt::auto_revoke, { this, &AppHost::_AlwaysOnTopChanged }); - _revokers.RaiseVisualBell = _logic.RaiseVisualBell(winrt::auto_revoke, { this, &AppHost::_RaiseVisualBell }); - _revokers.SystemMenuChangeRequested = _logic.SystemMenuChangeRequested(winrt::auto_revoke, { this, &AppHost::_SystemMenuChangeRequested }); - _revokers.ChangeMaximizeRequested = _logic.ChangeMaximizeRequested(winrt::auto_revoke, { this, &AppHost::_ChangeMaximizeRequested }); + _revokers.RequestedThemeChanged = _windowLogic.RequestedThemeChanged(winrt::auto_revoke, { this, &AppHost::_UpdateTheme }); + _revokers.FullscreenChanged = _windowLogic.FullscreenChanged(winrt::auto_revoke, { this, &AppHost::_FullscreenChanged }); + _revokers.FocusModeChanged = _windowLogic.FocusModeChanged(winrt::auto_revoke, { this, &AppHost::_FocusModeChanged }); + _revokers.AlwaysOnTopChanged = _windowLogic.AlwaysOnTopChanged(winrt::auto_revoke, { this, &AppHost::_AlwaysOnTopChanged }); + _revokers.RaiseVisualBell = _windowLogic.RaiseVisualBell(winrt::auto_revoke, { this, &AppHost::_RaiseVisualBell }); + _revokers.SystemMenuChangeRequested = _windowLogic.SystemMenuChangeRequested(winrt::auto_revoke, { this, &AppHost::_SystemMenuChangeRequested }); + _revokers.ChangeMaximizeRequested = _windowLogic.ChangeMaximizeRequested(winrt::auto_revoke, { this, &AppHost::_ChangeMaximizeRequested }); _window->MaximizeChanged([this](bool newMaximize) { - if (_logic) + if (_appLogic) { - _logic.Maximized(newMaximize); + _windowLogic.Maximized(newMaximize); } }); @@ -416,21 +422,21 @@ void AppHost::Initialize() // Load bearing: make sure the PropertyChanged handler is added before we // call Create, so that when the app sets up the titlebar brush, we're // already prepared to listen for the change notification - _revokers.PropertyChanged = _logic.PropertyChanged(winrt::auto_revoke, { this, &AppHost::_PropertyChangedHandler }); - - _logic.Create(); - - _revokers.TitleChanged = _logic.TitleChanged(winrt::auto_revoke, { this, &AppHost::AppTitleChanged }); - _revokers.LastTabClosed = _logic.LastTabClosed(winrt::auto_revoke, { this, &AppHost::LastTabClosed }); - _revokers.SetTaskbarProgress = _logic.SetTaskbarProgress(winrt::auto_revoke, { this, &AppHost::SetTaskbarProgress }); - _revokers.IdentifyWindowsRequested = _logic.IdentifyWindowsRequested(winrt::auto_revoke, { this, &AppHost::_IdentifyWindowsRequested }); - _revokers.RenameWindowRequested = _logic.RenameWindowRequested(winrt::auto_revoke, { this, &AppHost::_RenameWindowRequested }); - _revokers.SettingsChanged = _logic.SettingsChanged(winrt::auto_revoke, { this, &AppHost::_HandleSettingsChanged }); - _revokers.IsQuakeWindowChanged = _logic.IsQuakeWindowChanged(winrt::auto_revoke, { this, &AppHost::_IsQuakeWindowChanged }); - _revokers.SummonWindowRequested = _logic.SummonWindowRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowRequested }); - _revokers.OpenSystemMenu = _logic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu }); - _revokers.QuitRequested = _logic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll }); - _revokers.ShowWindowChanged = _logic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged }); + _revokers.PropertyChanged = _windowLogic.PropertyChanged(winrt::auto_revoke, { this, &AppHost::_PropertyChangedHandler }); + + _appLogic.Create(); + + _revokers.TitleChanged = _windowLogic.TitleChanged(winrt::auto_revoke, { this, &AppHost::AppTitleChanged }); + _revokers.LastTabClosed = _windowLogic.LastTabClosed(winrt::auto_revoke, { this, &AppHost::LastTabClosed }); + _revokers.SetTaskbarProgress = _windowLogic.SetTaskbarProgress(winrt::auto_revoke, { this, &AppHost::SetTaskbarProgress }); + _revokers.IdentifyWindowsRequested = _windowLogic.IdentifyWindowsRequested(winrt::auto_revoke, { this, &AppHost::_IdentifyWindowsRequested }); + _revokers.RenameWindowRequested = _windowLogic.RenameWindowRequested(winrt::auto_revoke, { this, &AppHost::_RenameWindowRequested }); + _revokers.SettingsChanged = _appLogic.SettingsChanged(winrt::auto_revoke, { this, &AppHost::_HandleSettingsChanged }); + _revokers.IsQuakeWindowChanged = _windowLogic.IsQuakeWindowChanged(winrt::auto_revoke, { this, &AppHost::_IsQuakeWindowChanged }); + _revokers.SummonWindowRequested = _windowLogic.SummonWindowRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowRequested }); + _revokers.OpenSystemMenu = _windowLogic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu }); + _revokers.QuitRequested = _windowLogic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll }); + _revokers.ShowWindowChanged = _windowLogic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged }); // BODGY // On certain builds of Windows, when Terminal is set as the default @@ -439,13 +445,13 @@ void AppHost::Initialize() // applications. This call into TerminalThemeHelpers will tell our // compositor to automatically complete animations that are scheduled // while the screen is off. - TerminalTrySetAutoCompleteAnimationsWhenOccluded(static_cast<::IUnknown*>(winrt::get_abi(_logic.GetRoot())), true); + TerminalTrySetAutoCompleteAnimationsWhenOccluded(static_cast<::IUnknown*>(winrt::get_abi(_windowLogic.GetRoot())), true); - _window->UpdateTitle(_logic.Title()); + _window->UpdateTitle(_windowLogic.Title()); // Set up the content of the application. If the app has a custom titlebar, // set that content as well. - _window->SetContent(_logic.GetRoot()); + _window->SetContent(_windowLogic.GetRoot()); _window->OnAppInitialized(); // BODGY @@ -478,7 +484,7 @@ void AppHost::Initialize() // - void AppHost::AppTitleChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/, winrt::hstring newTitle) { - if (_logic.GetShowTitleInTitlebar()) + if (_windowLogic.GetShowTitleInTitlebar()) { _window->UpdateTitle(newTitle); } @@ -564,11 +570,11 @@ LaunchPosition AppHost::_GetWindowLaunchPosition() // - None void AppHost::_HandleCreateWindow(const HWND hwnd, til::rect proposedRect, LaunchMode& launchMode) { - launchMode = _logic.GetLaunchMode(); + launchMode = _windowLogic.GetLaunchMode(); // Acquire the actual initial position - auto initialPos = _logic.GetInitialPosition(proposedRect.left, proposedRect.top); - const auto centerOnLaunch = _logic.CenterOnLaunch(); + auto initialPos = _windowLogic.GetInitialPosition(proposedRect.left, proposedRect.top); + const auto centerOnLaunch = _windowLogic.CenterOnLaunch(); proposedRect.left = gsl::narrow(initialPos.X); proposedRect.top = gsl::narrow(initialPos.Y); @@ -615,7 +621,7 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, til::rect proposedRect, Launc proposedRect.top = monitorInfo.rcWork.top; } - auto initialSize = _logic.GetLaunchDimensions(dpix); + auto initialSize = _windowLogic.GetLaunchDimensions(dpix); const auto islandWidth = Utils::ClampToShortMax( static_cast(ceil(initialSize.Width)), 1); @@ -649,7 +655,7 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, til::rect proposedRect, Launc til::point origin{ (proposedRect.left + nonClientFrame.left), (proposedRect.top) }; - if (_logic.IsQuakeWindow()) + if (_windowLogic.IsQuakeWindow()) { // If we just use rcWork by itself, we'll fail to account for the invisible // space reserved for the resize handles. So retrieve that size here. @@ -706,7 +712,7 @@ void AppHost::_UpdateTitleBarContent(const winrt::Windows::Foundation::IInspecta { auto nonClientWindow{ static_cast(_window.get()) }; nonClientWindow->SetTitlebarContent(arg); - nonClientWindow->SetTitlebarBackground(_logic.TitlebarBrush()); + nonClientWindow->SetTitlebarBackground(_windowLogic.TitlebarBrush()); } _updateTheme(); @@ -729,13 +735,13 @@ void AppHost::_UpdateTheme(const winrt::Windows::Foundation::IInspectable&, void AppHost::_FocusModeChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&) { - _window->FocusModeChanged(_logic.FocusMode()); + _window->FocusModeChanged(_windowLogic.FocusMode()); } void AppHost::_FullscreenChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&) { - _window->FullscreenChanged(_logic.Fullscreen()); + _window->FullscreenChanged(_windowLogic.Fullscreen()); } void AppHost::_ChangeMaximizeRequested(const winrt::Windows::Foundation::IInspectable&, @@ -773,7 +779,7 @@ void AppHost::_AlwaysOnTopChanged(const winrt::Windows::Foundation::IInspectable return; } - _window->SetAlwaysOnTop(_logic.AlwaysOnTop()); + _window->SetAlwaysOnTop(_windowLogic.AlwaysOnTop()); } // Method Description @@ -801,10 +807,10 @@ void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&, // - void AppHost::_WindowMouseWheeled(const til::point coord, const int32_t delta) { - if (_logic) + if (_appLogic) { // Find all the elements that are underneath the mouse - auto elems = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::FindElementsInHostCoordinates(coord.to_winrt_point(), _logic.GetRoot()); + auto elems = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::FindElementsInHostCoordinates(coord.to_winrt_point(), _windowLogic.GetRoot()); for (const auto& e : elems) { // If that element has implemented IMouseWheelListener, call OnMouseWheel on that element. @@ -862,7 +868,7 @@ void AppHost::_DispatchCommandline(winrt::Windows::Foundation::IInspectable send // Summon the window whenever we dispatch a commandline to it. This will // make it obvious when a new tab/pane is created in a window. _HandleSummon(sender, summonArgs); - _logic.ExecuteCommandline(args.Commandline(), args.CurrentDirectory()); + _windowLogic.ExecuteCommandline(args.Commandline(), args.CurrentDirectory()); } // Method Description: @@ -881,11 +887,11 @@ winrt::Windows::Foundation::IAsyncOperation AppHost::_GetWindowL winrt::hstring layoutJson = L""; // Use the main thread since we are accessing controls. - co_await wil::resume_foreground(_logic.GetRoot().Dispatcher()); + co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); try { const auto pos = _GetWindowLaunchPosition(); - layoutJson = _logic.GetWindowLayoutJson(pos); + layoutJson = _windowLogic.GetWindowLayoutJson(pos); } CATCH_LOG() @@ -908,14 +914,14 @@ winrt::Windows::Foundation::IAsyncOperation AppHost::_GetWindowL void AppHost::_FindTargetWindow(const winrt::Windows::Foundation::IInspectable& /*sender*/, const Remoting::FindTargetWindowArgs& args) { - const auto targetWindow = _logic.FindTargetWindow(args.Args().Commandline()); + const auto targetWindow = _appLogic.FindTargetWindow(args.Args().Commandline()); args.ResultTargetWindow(targetWindow.WindowId()); args.ResultTargetWindowName(targetWindow.WindowName()); } winrt::fire_and_forget AppHost::_WindowActivated(bool activated) { - _logic.WindowActivated(activated); + _windowLogic.WindowActivated(activated); if (!activated) { @@ -955,27 +961,27 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s if (_windowManager.DoesQuakeWindowExist() || _window->IsQuakeWindow() || - (_logic.GetAlwaysShowNotificationIcon() || _logic.GetMinimizeToNotificationArea())) + (_windowLogic.GetAlwaysShowNotificationIcon() || _windowLogic.GetMinimizeToNotificationArea())) { _CreateNotificationIcon(); } // 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()); + _windowLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); _WindowCreatedToken = _windowManager.WindowCreated([this](auto&&, auto&&) { if (_getWindowLayoutThrottler) { _getWindowLayoutThrottler.value()(); } - _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); + _appLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); _WindowClosedToken = _windowManager.WindowClosed([this](auto&&, auto&&) { if (_getWindowLayoutThrottler) { _getWindowLayoutThrottler.value()(); } - _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); + _appLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); // These events are coming from peasants that become or un-become quake windows. @@ -1000,7 +1006,7 @@ winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts() // Make sure we run on a background thread to not block anything. co_await winrt::resume_background(); - if (_logic.ShouldUsePersistedLayout()) + if (_windowLogic.ShouldUsePersistedLayout()) { try { @@ -1015,7 +1021,7 @@ winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts() TraceLoggingDescription("Logged when writing window state"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - _logic.SaveWindowLayoutJsons(layoutJsons); + _windowLogic.SaveWindowLayoutJsons(layoutJsons); } catch (...) { @@ -1058,13 +1064,13 @@ winrt::fire_and_forget AppHost::_SaveWindowLayoutsRepeat() void AppHost::_listenForInboundConnections() { - _logic.SetInboundListener(); + _appLogic.SetInboundListener(); } winrt::fire_and_forget AppHost::_setupGlobalHotkeys() { // The hotkey MUST be registered on the main thread. It will fail otherwise! - co_await wil::resume_foreground(_logic.GetRoot().Dispatcher()); + co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); if (!_window) { @@ -1089,7 +1095,7 @@ winrt::fire_and_forget AppHost::_setupGlobalHotkeys() _hotkeys.clear(); // Re-register all current hotkeys. - for (const auto& [keyChord, cmd] : _logic.GlobalHotkeys()) + for (const auto& [keyChord, cmd] : _appLogic.GlobalHotkeys()) { if (auto summonArgs = cmd.ActionAndArgs().Args().try_as()) { @@ -1311,7 +1317,7 @@ winrt::fire_and_forget AppHost::_IdentifyWindowsRequested(const winrt::Windows:: void AppHost::_DisplayWindowId(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/) { - _logic.IdentifyWindow(); + _windowLogic.IdentifyWindow(); } winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, @@ -1335,11 +1341,11 @@ winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Fou if (requestArgs.Succeeded()) { - _logic.WindowName(args.ProposedName()); + _windowLogic.WindowName(args.ProposedName()); } else { - _logic.RenameFailed(); + _windowLogic.RenameFailed(); } } } @@ -1373,11 +1379,11 @@ static bool _isActuallyDarkTheme(const auto requestedTheme) void AppHost::_updateTheme() { - auto theme = _logic.Theme(); + auto theme = _appLogic.Theme(); _window->OnApplicationThemeChanged(theme.RequestedTheme()); - const auto b = _logic.TitlebarBrush(); + const auto b = _windowLogic.TitlebarBrush(); const auto color = ThemeColor::ColorFromBrush(b); const auto colorOpacity = b ? color.A / 255.0 : 0.0; const auto brushOpacity = _opacityFromBrush(b); @@ -1409,11 +1415,11 @@ void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspecta { if (!_windowManager.DoesQuakeWindowExist()) { - if (!_notificationIcon && (_logic.GetMinimizeToNotificationArea() || _logic.GetAlwaysShowNotificationIcon())) + if (!_notificationIcon && (_windowLogic.GetMinimizeToNotificationArea() || _windowLogic.GetAlwaysShowNotificationIcon())) { _CreateNotificationIcon(); } - else if (_notificationIcon && !_logic.GetMinimizeToNotificationArea() && !_logic.GetAlwaysShowNotificationIcon()) + else if (_notificationIcon && !_windowLogic.GetMinimizeToNotificationArea() && !_windowLogic.GetAlwaysShowNotificationIcon()) { _windowManager.SummonAllWindows(); _DestroyNotificationIcon(); @@ -1421,8 +1427,8 @@ void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspecta } } - _window->SetMinimizeToNotificationAreaBehavior(_logic.GetMinimizeToNotificationArea()); - _window->SetAutoHideWindow(_logic.AutoHideWindow()); + _window->SetMinimizeToNotificationAreaBehavior(_windowLogic.GetMinimizeToNotificationArea()); + _window->SetAutoHideWindow(_windowLogic.AutoHideWindow()); _updateTheme(); } @@ -1434,25 +1440,25 @@ void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectab // to show regardless of the notification icon settings. // This also means we'll need to destroy the notification icon if it was created // specifically for the quake window. If not, it should not be destroyed. - if (!_window->IsQuakeWindow() && _logic.IsQuakeWindow()) + if (!_window->IsQuakeWindow() && _windowLogic.IsQuakeWindow()) { _ShowNotificationIconRequested(nullptr, nullptr); } - else if (_window->IsQuakeWindow() && !_logic.IsQuakeWindow()) + else if (_window->IsQuakeWindow() && !_windowLogic.IsQuakeWindow()) { _HideNotificationIconRequested(nullptr, nullptr); } - _window->IsQuakeWindow(_logic.IsQuakeWindow()); + _window->IsQuakeWindow(_windowLogic.IsQuakeWindow()); } winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&) { // Need to be on the main thread to close out all of the tabs. - co_await wil::resume_foreground(_logic.GetRoot().Dispatcher()); + co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); - _logic.Quit(); + _windowLogic.Quit(); } void AppHost::_RequestQuitAll(const winrt::Windows::Foundation::IInspectable&, @@ -1585,8 +1591,8 @@ void AppHost::_HideNotificationIconRequested(const winrt::Windows::Foundation::I { // Destroy it only if our settings allow it if (_notificationIcon && - !_logic.GetAlwaysShowNotificationIcon() && - !_logic.GetMinimizeToNotificationArea()) + !_windowLogic.GetAlwaysShowNotificationIcon() && + !_windowLogic.GetMinimizeToNotificationArea()) { _DestroyNotificationIcon(); } @@ -1607,14 +1613,14 @@ void AppHost::_HideNotificationIconRequested(const winrt::Windows::Foundation::I // - void AppHost::_WindowMoved() { - if (_logic) + if (_windowLogic) { // Ensure any open ContentDialog is dismissed. // Closing the popup in the UI tree as done below is not sufficient because // it does not terminate the dialog's async operation. - _logic.DismissDialog(); + _windowLogic.DismissDialog(); - const auto root{ _logic.GetRoot() }; + const auto root{ _windowLogic.GetRoot() }; if (root && root.XamlRoot()) { try @@ -1643,7 +1649,7 @@ void AppHost::_CloseRequested(const winrt::Windows::Foundation::IInspectable& /* const winrt::Windows::Foundation::IInspectable& /*args*/) { const auto pos = _GetWindowLaunchPosition(); - _logic.CloseWindow(pos); + _windowLogic.CloseWindow(pos); } void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, @@ -1654,7 +1660,7 @@ void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspect if (_useNonClientArea) { auto nonClientWindow{ static_cast(_window.get()) }; - nonClientWindow->SetTitlebarBackground(_logic.TitlebarBrush()); + nonClientWindow->SetTitlebarBackground(_windowLogic.TitlebarBrush()); _updateTheme(); } } diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 2de84742189..5f9b48dc33e 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -23,7 +23,8 @@ class AppHost private: std::unique_ptr _window; winrt::TerminalApp::App _app; - winrt::TerminalApp::AppLogic _logic; + winrt::TerminalApp::AppLogic _appLogic; + winrt::TerminalApp::TerminalWindow _windowLogic; winrt::Microsoft::Terminal::Remoting::WindowManager _windowManager{ nullptr }; std::vector _hotkeys; @@ -151,28 +152,28 @@ class AppHost winrt::Microsoft::Terminal::Remoting::Peasant::SummonRequested_revoker peasantSummonRequested; winrt::Microsoft::Terminal::Remoting::Peasant::DisplayWindowIdRequested_revoker peasantDisplayWindowIdRequested; winrt::Microsoft::Terminal::Remoting::Peasant::QuitRequested_revoker peasantQuitRequested; - winrt::TerminalApp::AppLogic::CloseRequested_revoker CloseRequested; - winrt::TerminalApp::AppLogic::RequestedThemeChanged_revoker RequestedThemeChanged; - winrt::TerminalApp::AppLogic::FullscreenChanged_revoker FullscreenChanged; - winrt::TerminalApp::AppLogic::FocusModeChanged_revoker FocusModeChanged; - winrt::TerminalApp::AppLogic::AlwaysOnTopChanged_revoker AlwaysOnTopChanged; - winrt::TerminalApp::AppLogic::RaiseVisualBell_revoker RaiseVisualBell; - winrt::TerminalApp::AppLogic::SystemMenuChangeRequested_revoker SystemMenuChangeRequested; - winrt::TerminalApp::AppLogic::ChangeMaximizeRequested_revoker ChangeMaximizeRequested; - winrt::TerminalApp::AppLogic::TitleChanged_revoker TitleChanged; - winrt::TerminalApp::AppLogic::LastTabClosed_revoker LastTabClosed; - winrt::TerminalApp::AppLogic::SetTaskbarProgress_revoker SetTaskbarProgress; - winrt::TerminalApp::AppLogic::IdentifyWindowsRequested_revoker IdentifyWindowsRequested; - winrt::TerminalApp::AppLogic::RenameWindowRequested_revoker RenameWindowRequested; + winrt::TerminalApp::TerminalWindow::CloseRequested_revoker CloseRequested; + winrt::TerminalApp::TerminalWindow::RequestedThemeChanged_revoker RequestedThemeChanged; + winrt::TerminalApp::TerminalWindow::FullscreenChanged_revoker FullscreenChanged; + winrt::TerminalApp::TerminalWindow::FocusModeChanged_revoker FocusModeChanged; + winrt::TerminalApp::TerminalWindow::AlwaysOnTopChanged_revoker AlwaysOnTopChanged; + winrt::TerminalApp::TerminalWindow::RaiseVisualBell_revoker RaiseVisualBell; + winrt::TerminalApp::TerminalWindow::SystemMenuChangeRequested_revoker SystemMenuChangeRequested; + winrt::TerminalApp::TerminalWindow::ChangeMaximizeRequested_revoker ChangeMaximizeRequested; + winrt::TerminalApp::TerminalWindow::TitleChanged_revoker TitleChanged; + winrt::TerminalApp::TerminalWindow::LastTabClosed_revoker LastTabClosed; + winrt::TerminalApp::TerminalWindow::SetTaskbarProgress_revoker SetTaskbarProgress; + winrt::TerminalApp::TerminalWindow::IdentifyWindowsRequested_revoker IdentifyWindowsRequested; + winrt::TerminalApp::TerminalWindow::RenameWindowRequested_revoker RenameWindowRequested; + winrt::TerminalApp::TerminalWindow::IsQuakeWindowChanged_revoker IsQuakeWindowChanged; + winrt::TerminalApp::TerminalWindow::SummonWindowRequested_revoker SummonWindowRequested; + winrt::TerminalApp::TerminalWindow::OpenSystemMenu_revoker OpenSystemMenu; + winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested; + winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged; + winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged; winrt::TerminalApp::AppLogic::SettingsChanged_revoker SettingsChanged; - winrt::TerminalApp::AppLogic::IsQuakeWindowChanged_revoker IsQuakeWindowChanged; - winrt::TerminalApp::AppLogic::SummonWindowRequested_revoker SummonWindowRequested; - winrt::TerminalApp::AppLogic::OpenSystemMenu_revoker OpenSystemMenu; - winrt::TerminalApp::AppLogic::QuitRequested_revoker QuitRequested; - winrt::TerminalApp::AppLogic::ShowWindowChanged_revoker ShowWindowChanged; winrt::Microsoft::Terminal::Remoting::WindowManager::ShowNotificationIconRequested_revoker ShowNotificationIconRequested; winrt::Microsoft::Terminal::Remoting::WindowManager::HideNotificationIconRequested_revoker HideNotificationIconRequested; winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested; - winrt::TerminalApp::AppLogic::PropertyChanged_revoker PropertyChanged; } _revokers{}; }; From 99bc2802076ec7fa833bb77ef4a2c3997d93e52b Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 31 Jan 2023 09:34:02 -0600 Subject: [PATCH 03/25] It doesn't crash on launch. That's something. There's no startupActions though, so it immediately exits --- src/cascadia/TerminalApp/AppLogic.cpp | 19 +++++++++++++++++-- src/cascadia/TerminalApp/AppLogic.h | 2 ++ src/cascadia/TerminalApp/AppLogic.idl | 5 +++++ src/cascadia/TerminalApp/TerminalWindow.cpp | 10 ++++++++++ src/cascadia/TerminalApp/TerminalWindow.h | 1 + src/cascadia/WindowsTerminal/AppHost.cpp | 13 +++++++------ 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 40d2637aa3b..1ab897ca2ea 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -301,6 +301,11 @@ namespace winrt::TerminalApp::implementation return hr; } + bool AppLogic::HasSettingsStartupActions() const noexcept + { + return _hasSettingsStartupActions; + } + // Call this function after loading your _settings. // It handles any CPU intensive settings updates (like updating the Jumplist) // which should thus only occur if the settings file actually changed. @@ -474,6 +479,10 @@ namespace winrt::TerminalApp::implementation } else { + // TODO! Arg should be a SettingsLoadEventArgs{ result, warnings, error, settings} + // + // _SettingsChangedHandlers(*this, make_self( _settingsLoadedResult, warnings, _settingsLoadExceptionText, settings})) + // const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); // const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); // _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); @@ -500,7 +509,7 @@ namespace winrt::TerminalApp::implementation _ApplyStartupTaskStateChange(); _ProcessLazySettingsChanges(); - _SettingsChangedHandlers(*this, nullptr); + _SettingsChangedHandlers(*this, _settings); } // Method Description: @@ -675,6 +684,12 @@ namespace winrt::TerminalApp::implementation TerminalApp::TerminalWindow AppLogic::CreateNewWindow() { - return *winrt::make_self(_settings); + if (_settings == nullptr) + { + ReloadSettings(); + } + auto window = winrt::make_self(_settings); + this->SettingsChanged({ window->get_weak(), &implementation::TerminalWindow::UpdateSettingsHandler }); + return *window; } } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index eeee6673366..ffe744b8978 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -52,6 +52,8 @@ namespace winrt::TerminalApp::implementation bool IsElevated() const noexcept; void ReloadSettings(); + bool HasSettingsStartupActions() const noexcept; + [[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept; TerminalApp::FindTargetWindowResult FindTargetWindow(array_view actions); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 2a3488e6f95..7946403aeed 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -27,6 +27,8 @@ namespace TerminalApp void RunAsUwp(); Boolean IsElevated(); + Boolean HasSettingsStartupActions(); + void ReloadSettings(); void SetInboundListener(); @@ -37,6 +39,9 @@ namespace TerminalApp TerminalWindow CreateNewWindow(); + Boolean ShouldImmediatelyHandoffToElevated(); + void HandoffToElevated(); + Windows.Foundation.Collections.IMapView GlobalHotkeys(); event Windows.Foundation.TypedEventHandler SettingsChanged; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 0b1c9e27632..89ce8856c17 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -1137,4 +1137,14 @@ namespace winrt::TerminalApp::implementation { return _settings.GlobalSettings().AutoHideWindow(); } + // TODO! Arg should be a SettingsLoadEventArgs{ result, warnings, error, settings} + void TerminalWindow::UpdateSettingsHandler(const winrt::IInspectable& /*sender*/, + const winrt::IInspectable& arg) + { + if (const auto& settings{ arg.try_as() }) + { + this->UpdateSettings(S_OK, settings); + _root->SetSettings(_settings, true); + } + } }; diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index aee3173bcb8..b2cef567f93 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -107,6 +107,7 @@ namespace winrt::TerminalApp::implementation void DismissDialog(); Microsoft::Terminal::Settings::Model::Theme Theme(); + void UpdateSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& arg); // -------------------------------- WinRT Events --------------------------------- // PropertyChanged is surprisingly not a typed event, so we'll define that one manually. diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 48bc98e683e..60ded0d3fe8 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -32,7 +32,7 @@ AppHost::AppHost() noexcept : _app{}, _windowManager{}, _appLogic{ nullptr }, // don't make one, we're going to take a ref on app's - _windowLogic{ nullptr }, + _windowLogic{ nullptr }, _window{ nullptr }, _getWindowLayoutThrottler{} // this will get set if we become the monarch { @@ -234,7 +234,7 @@ void AppHost::_HandleCommandlineArgs() } // We did want to make a window, so let's instantiate it here. - // We don't have XAML yet, but we do have other stuff. + // We don't have XAML yet, but we do have other stuff. _windowLogic = _appLogic.CreateNewWindow(); if (auto peasant{ _windowManager.CurrentWindow() }) @@ -269,10 +269,10 @@ void AppHost::_HandleCommandlineArgs() // the window at all here. In that case, we're going through this // special escape hatch to dispatch all the calls to elevate-shim, and // then we're going to exit immediately. - if (_windowLogic.ShouldImmediatelyHandoffToElevated()) + if (_appLogic.ShouldImmediatelyHandoffToElevated()) { _shouldCreateWindow = false; - _windowLogic.HandoffToElevated(); + _appLogic.HandoffToElevated(); return; } @@ -425,6 +425,7 @@ void AppHost::Initialize() _revokers.PropertyChanged = _windowLogic.PropertyChanged(winrt::auto_revoke, { this, &AppHost::_PropertyChangedHandler }); _appLogic.Create(); + _windowLogic.Create(); _revokers.TitleChanged = _windowLogic.TitleChanged(winrt::auto_revoke, { this, &AppHost::AppTitleChanged }); _revokers.LastTabClosed = _windowLogic.LastTabClosed(winrt::auto_revoke, { this, &AppHost::LastTabClosed }); @@ -974,14 +975,14 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s if (_getWindowLayoutThrottler) { _getWindowLayoutThrottler.value()(); } - _appLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); + _windowLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); _WindowClosedToken = _windowManager.WindowClosed([this](auto&&, auto&&) { if (_getWindowLayoutThrottler) { _getWindowLayoutThrottler.value()(); } - _appLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); + _windowLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); // These events are coming from peasants that become or un-become quake windows. From 219551593724eb1d822bc957c8497295d1b4fe5d Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 31 Jan 2023 10:44:38 -0600 Subject: [PATCH 04/25] it launches --- src/cascadia/TerminalApp/AppLogic.cpp | 4 ++++ src/cascadia/TerminalApp/TerminalPage.cpp | 11 ++++++++++ src/cascadia/TerminalApp/TerminalWindow.cpp | 23 ++++++++++++++------- src/cascadia/TerminalApp/TerminalWindow.h | 3 +++ 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 1ab897ca2ea..5a2ec597963 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -690,6 +690,10 @@ namespace winrt::TerminalApp::implementation } auto window = winrt::make_self(_settings); this->SettingsChanged({ window->get_weak(), &implementation::TerminalWindow::UpdateSettingsHandler }); + if (_hasSettingsStartupActions) + { + window->SetSettingsStartupArgs(_settingsAppArgs.GetStartupActions()); + } return *window; } } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index cf4f0140a27..e382d1e7743 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -621,6 +621,8 @@ namespace winrt::TerminalApp::implementation const bool initial, const winrt::hstring cwd) { + const auto paramFistSize = actions.Size(); + auto weakThis{ get_weak() }; // Handle it on a subsequent pass of the UI thread. @@ -651,6 +653,11 @@ namespace winrt::TerminalApp::implementation if (auto page{ weakThis.get() }) { + const auto memberSize = page->_startupActions.Size(); + const auto paramSecondSize = actions.Size(); + memberSize; + paramFistSize; + paramSecondSize; for (const auto& action : actions) { if (auto page{ weakThis.get() }) @@ -3045,11 +3052,15 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::SetStartupActions(std::vector& actions) { + const auto initSize = actions.size(); + initSize; // The fastest way to copy all the actions out of the std::vector and // put them into a winrt::IVector is by making a copy, then moving the // copy into the winrt vector ctor. auto listCopy = actions; _startupActions = winrt::single_threaded_vector(std::move(listCopy)); + const auto afterSize = _startupActions.Size(); + assert(initSize == afterSize);// you donkey } // Routine Description: diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 89ce8856c17..a46ba9390ad 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -154,7 +154,6 @@ namespace winrt::TerminalApp::implementation // - Implements the IInitializeWithWindow interface from shobjidl_core. HRESULT TerminalWindow::Initialize(HWND hwnd) { - _root = winrt::make_self(); _dialog = ContentDialog{}; return _root->Initialize(hwnd); } @@ -214,13 +213,13 @@ namespace winrt::TerminalApp::implementation // TODO! handle startupActions // - //// Pay attention, that even if some command line arguments were parsed (like launch mode), - //// we will not use the startup actions from settings. - //// While this simplifies the logic, we might want to reconsider this behavior in the future. - //if (!_hasCommandLineArguments && _hasSettingsStartupActions) - //{ - // _root->SetStartupActions(_settingsAppArgs.GetStartupActions()); - //} + // Pay attention, that even if some command line arguments were parsed (like launch mode), + // we will not use the startup actions from settings. + // While this simplifies the logic, we might want to reconsider this behavior in the future. + if (!_hasCommandLineArguments && _gotSettingsStartupActions) + { + _root->SetStartupActions(_settingsStartupArgs); + } _root->SetSettings(_settings, false); _root->Loaded({ this, &TerminalWindow::_OnLoaded }); @@ -927,6 +926,14 @@ namespace winrt::TerminalApp::implementation // { // return _hasSettingsStartupActions; // } + void TerminalWindow::SetSettingsStartupArgs(const std::vector& actions) + { + for (const auto& action : actions) + { + _settingsStartupArgs.push_back(action); + } + _gotSettingsStartupActions = true; + } bool TerminalWindow::HasCommandlineArguments() const noexcept { diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index b2cef567f93..9d5605d00ca 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -53,6 +53,7 @@ namespace winrt::TerminalApp::implementation // bool HasSettingsStartupActions() const noexcept; int32_t SetStartupCommandline(array_view actions); int32_t ExecuteCommandline(array_view actions, const winrt::hstring& cwd); + void SetSettingsStartupArgs(const std::vector& actions); winrt::hstring ParseCommandlineMessage(); bool ShouldExitEarly(); @@ -135,6 +136,8 @@ namespace winrt::TerminalApp::implementation bool _hasCommandLineArguments{ false }; ::TerminalApp::AppCommandlineArgs _appArgs; + bool _gotSettingsStartupActions{ false }; + std::vector _settingsStartupArgs{}; uint64_t _numOpenWindows{ 0 }; From 5116ca1e77ed7ed75ab51ab58e803aa211ef850f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 31 Jan 2023 10:47:46 -0600 Subject: [PATCH 05/25] I think the todo's that are left, we can move on without them for now. --- src/cascadia/TerminalApp/AppLogic.cpp | 3 +-- src/cascadia/TerminalApp/TerminalWindow.cpp | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 5a2ec597963..ad77ccdc3cf 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -187,7 +187,7 @@ namespace winrt::TerminalApp::implementation _settings.GlobalSettings().ShowTabsInTitlebar(false); } - // TODO! These used to be in `_root->Initialized`: + // TODO! These used to be in `_root->Initialized`. Where do they belong now? { // Both LoadSettings and ReloadSettings are supposed to call this function, // but LoadSettings skips it, so that the UI starts up faster. @@ -262,7 +262,6 @@ namespace winrt::TerminalApp::implementation _warnings.push_back(newSettings.Warnings().GetAt(i)); } - // TODO! These _settingsAppArgs need to get plumbed into TerminalWindow somehow _hasSettingsStartupActions = false; const auto startupActions = newSettings.GlobalSettings().StartupActions(); if (!startupActions.empty()) diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index a46ba9390ad..44ddc48c6f3 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -211,8 +211,6 @@ namespace winrt::TerminalApp::implementation _settings.GlobalSettings().ShowTabsInTitlebar(false); } - // TODO! handle startupActions - // // Pay attention, that even if some command line arguments were parsed (like launch mode), // we will not use the startup actions from settings. // While this simplifies the logic, we might want to reconsider this behavior in the future. From e40575b18ba848c0fa6de6499cde9a2f431bc6e7 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 10 Feb 2023 16:50:58 -0600 Subject: [PATCH 06/25] let's do it --- src/cascadia/TerminalApp/App.cpp | 21 +- src/cascadia/TerminalApp/AppLogic.cpp | 118 ++--- src/cascadia/TerminalApp/AppLogic.h | 11 +- src/cascadia/TerminalApp/AppLogic.idl | 10 +- .../TerminalApp/SettingsLoadEventArgs.h | 30 ++ src/cascadia/TerminalApp/TabManagement.cpp | 2 +- src/cascadia/TerminalApp/TerminalPage.cpp | 192 ++------ src/cascadia/TerminalApp/TerminalPage.h | 26 +- src/cascadia/TerminalApp/TerminalPage.idl | 21 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 430 +++++++++++------- src/cascadia/TerminalApp/TerminalWindow.h | 55 ++- src/cascadia/TerminalApp/TerminalWindow.idl | 35 +- .../CascadiaSettingsSerialization.cpp | 6 +- .../GlobalAppSettings.cpp | 5 + .../TerminalSettingsModel/GlobalAppSettings.h | 1 + .../GlobalAppSettings.idl | 1 + src/cascadia/WindowsTerminal/AppHost.cpp | 61 +-- src/cascadia/WindowsTerminal/AppHost.h | 6 +- src/cascadia/WindowsTerminal/IslandWindow.cpp | 8 +- 19 files changed, 549 insertions(+), 490 deletions(-) create mode 100644 src/cascadia/TerminalApp/SettingsLoadEventArgs.h diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index 1a22cfc5b32..b4019d95227 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -77,24 +77,7 @@ namespace winrt::TerminalApp::implementation /// Details about the launch request and process. void App::OnLaunched(const LaunchActivatedEventArgs& /*e*/) { - // TODO! UWP mode is straight up not supported anymore, yea? - // - // if this is a UWP... it means its our problem to hook up the content to the window here. - //if (_isUwp) - //{ - // auto content = Window::Current().Content(); - // if (content == nullptr) - // { - // auto logic = Logic(); - // logic.RunAsUwp(); // Must set UWP status first, settings might change based on it. - // logic.ReloadSettings(); - // logic.Create(); - - // auto page = logic.GetRoot().as(); - - // Window::Current().Content(page); - // Window::Current().Activate(); - // } - //} + // We used to support a pure UWP version of the Terminal. This method + // was only ever used to do UWP-specific setup of our App. } } diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index ad77ccdc3cf..adffee5f5c2 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -31,13 +31,20 @@ namespace winrt } //////////////////////////////////////////////////////////////////////////////// -// TODO! This section probably should be in TerminalWindow with the warnings - -// clang-format off -static const std::array settingsLoadErrorsLabels { +// Error message handling. This is in this file rather than with the warnings in +// TerminalWindow, becuase the error text might also just be a serializationgs +// error message. So AppLogic needs to know the actual text of the error. + +// !!! IMPORTANT !!! +// Make sure that these keys are in the same order as the +// SettingsLoadWarnings/Errors enum is! +static const std::array settingsLoadErrorsLabels{ USES_RESOURCE(L"NoProfilesText"), USES_RESOURCE(L"AllProfilesHiddenText") }; + +static_assert(settingsLoadErrorsLabels.size() == static_cast(SettingsLoadErrors::ERRORS_SIZE)); + template winrt::hstring _GetMessageText(uint32_t index, const T& keys) { @@ -187,7 +194,9 @@ namespace winrt::TerminalApp::implementation _settings.GlobalSettings().ShowTabsInTitlebar(false); } - // TODO! These used to be in `_root->Initialized`. Where do they belong now? + // These used to be in `TerminalPage::Initialized`, so that they started + // _after_ the Terminal window was started and displayed. These could + // theoretically move there again too. { // Both LoadSettings and ReloadSettings are supposed to call this function, // but LoadSettings skips it, so that the UI starts up faster. @@ -256,10 +265,10 @@ namespace winrt::TerminalApp::implementation return E_INVALIDARG; } - _warnings.clear(); + _warnings.Clear(); for (uint32_t i = 0; i < newSettings.Warnings().Size(); i++) { - _warnings.push_back(newSettings.Warnings().GetAt(i)); + _warnings.Append(newSettings.Warnings().GetAt(i)); } _hasSettingsStartupActions = false; @@ -279,12 +288,13 @@ namespace winrt::TerminalApp::implementation } else { - _warnings.push_back(SettingsLoadWarnings::FailedToParseStartupActions); + _warnings.Append(SettingsLoadWarnings::FailedToParseStartupActions); } } _settings = std::move(newSettings); - hr = _warnings.empty() ? S_OK : S_FALSE; + + hr = (_warnings.Size()) == 0 ? S_OK : S_FALSE; } catch (const winrt::hresult_error& e) { @@ -478,14 +488,13 @@ namespace winrt::TerminalApp::implementation } else { - // TODO! Arg should be a SettingsLoadEventArgs{ result, warnings, error, settings} - // - // _SettingsChangedHandlers(*this, make_self( _settingsLoadedResult, warnings, _settingsLoadExceptionText, settings})) - - // const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); - // const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); - // _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); - // return; + auto ev = winrt::make_self(true, + static_cast(_settingsLoadedResult), + _settingsLoadExceptionText, + _warnings, + _settings); + _SettingsChangedHandlers(*this, *ev); + return; } } @@ -496,11 +505,6 @@ namespace winrt::TerminalApp::implementation return; } - // if (_settingsLoadedResult == S_FALSE) - // { - // _ShowLoadWarningsDialog(); - // } - // Here, we successfully reloaded the settings, and created a new // TerminalSettings object. @@ -508,7 +512,12 @@ namespace winrt::TerminalApp::implementation _ApplyStartupTaskStateChange(); _ProcessLazySettingsChanges(); - _SettingsChangedHandlers(*this, _settings); + auto ev = winrt::make_self(!initialLoad, + _settingsLoadedResult, + _settingsLoadExceptionText, + _warnings, + _settings); + _SettingsChangedHandlers(*this, *ev); } // Method Description: @@ -518,38 +527,6 @@ namespace winrt::TerminalApp::implementation return _settings; } - // Method Description: - // - Triggers the setup of the listener for incoming console connections - // from the operating system. - // Arguments: - // - - // Return Value: - // - - void AppLogic::SetInboundListener() - { - // TODO! - // _root->SetInboundListener(false); - } - - bool AppLogic::ShouldImmediatelyHandoffToElevated() - { - // TODO! Merge that code in here. - // * Probably need to pass in the startupActions that the first window is being started with - // * Or like, a reference to the first TerminalWindow object, or something - - // return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false; - return false; - } - void AppLogic::HandoffToElevated() - { - // TODO! Merge that code in here. - - // if (_root) - // { - // _root->HandoffToElevated(_settings); - // } - } - // Method Description: // - Parse the given commandline args in an attempt to find the specified // window. The rest of the args are ignored for now (they'll be handled @@ -687,7 +664,15 @@ namespace winrt::TerminalApp::implementation { ReloadSettings(); } - auto window = winrt::make_self(_settings); + + auto ev = winrt::make_self(false, + _settingsLoadedResult, + _settingsLoadExceptionText, + _warnings, + _settings); + + auto window = winrt::make_self(*ev); + this->SettingsChanged({ window->get_weak(), &implementation::TerminalWindow::UpdateSettingsHandler }); if (_hasSettingsStartupActions) { @@ -695,4 +680,25 @@ namespace winrt::TerminalApp::implementation } return *window; } + + bool AppLogic::ShouldUsePersistedLayout() const + { + return _settings.GlobalSettings().ShouldUsePersistedLayout(); + } + + void AppLogic::SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts) + { + std::vector converted; + converted.reserve(layouts.Size()); + + for (const auto& json : layouts) + { + if (json != L"") + { + converted.emplace_back(WindowLayout::FromJson(json)); + } + } + + ApplicationState::SharedInstance().PersistedWindowLayouts(winrt::single_threaded_vector(std::move(converted))); + } } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index ffe744b8978..759cfb09500 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -54,13 +54,12 @@ namespace winrt::TerminalApp::implementation bool HasSettingsStartupActions() const noexcept; + bool ShouldUsePersistedLayout() const; + void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts); + [[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept; TerminalApp::FindTargetWindowResult FindTargetWindow(array_view actions); - bool ShouldImmediatelyHandoffToElevated(); - void HandoffToElevated(); - - void SetInboundListener(); Windows::Foundation::Collections::IMapView GlobalHotkeys(); @@ -68,7 +67,7 @@ namespace winrt::TerminalApp::implementation TerminalApp::TerminalWindow CreateNewWindow(); - TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs); private: bool _isUwp{ false }; @@ -86,7 +85,7 @@ namespace winrt::TerminalApp::implementation std::shared_ptr> _reloadSettings; til::throttled_func_trailing<> _reloadState; - std::vector _warnings; + winrt::Windows::Foundation::Collections::IVector _warnings{ winrt::multi_threaded_vector() }; // These fields invoke _reloadSettings and must be destroyed before _reloadSettings. // (C++ destroys members in reverse-declaration-order.) diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 7946403aeed..a5db001de67 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -29,9 +29,10 @@ namespace TerminalApp Boolean HasSettingsStartupActions(); - void ReloadSettings(); + Boolean ShouldUsePersistedLayout(); + void SaveWindowLayoutJsons(Windows.Foundation.Collections.IVector layouts); - void SetInboundListener(); + void ReloadSettings(); Microsoft.Terminal.Settings.Model.Theme Theme { get; }; @@ -39,12 +40,9 @@ namespace TerminalApp TerminalWindow CreateNewWindow(); - Boolean ShouldImmediatelyHandoffToElevated(); - void HandoffToElevated(); - Windows.Foundation.Collections.IMapView GlobalHotkeys(); - event Windows.Foundation.TypedEventHandler SettingsChanged; + event Windows.Foundation.TypedEventHandler SettingsChanged; } } diff --git a/src/cascadia/TerminalApp/SettingsLoadEventArgs.h b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h new file mode 100644 index 00000000000..e31dd5e2a91 --- /dev/null +++ b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "SettingsLoadEventArgs.g.h" +#include +namespace winrt::TerminalApp::implementation +{ + struct SettingsLoadEventArgs : SettingsLoadEventArgsT + { + WINRT_PROPERTY(bool, Reload, false); + WINRT_PROPERTY(uint64_t, Result, S_OK); + WINRT_PROPERTY(winrt::hstring, ExceptionText, L""); + WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVector, Warnings, nullptr); + WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::CascadiaSettings, NewSettings, nullptr); + + public: + SettingsLoadEventArgs(bool reload, + uint64_t result, + const winrt::hstring& exceptionText, + const winrt::Windows::Foundation::Collections::IVector& warnings, + const Microsoft::Terminal::Settings::Model::CascadiaSettings& newSettings) : + _Reload{ reload }, + _Result{ result }, + _ExceptionText{ exceptionText }, + _Warnings{ warnings }, + _NewSettings{ newSettings } {}; + }; +} diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index c417af3c4e2..f1134efbd36 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -523,7 +523,7 @@ namespace winrt::TerminalApp::implementation // if the user manually closed all tabs. // Do this only if we are the last window; the monarch will notice // we are missing and remove us that way otherwise. - if (!_maintainStateOnTabClose && ShouldUsePersistedLayout(_settings) && _numOpenWindows == 1) + if (!_maintainStateOnTabClose && _settings.GlobalSettings().ShouldUsePersistedLayout() && _numOpenWindows == 1) { auto state = ApplicationState::SharedInstance(); state.PersistedWindowLayouts(nullptr); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 64ae2fbc858..932ad147dbc 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -321,18 +321,6 @@ namespace winrt::TerminalApp::implementation ShowSetAsDefaultInfoBar(); } - // 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().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout; - } - // Method Description: // - This is a bit of trickiness: If we're running unelevated, and the user // passed in only --elevate actions, the we don't _actually_ want to @@ -347,7 +335,7 @@ namespace winrt::TerminalApp::implementation // GH#12267: Don't forget about defterm handoff here. If we're being // created for embedding, then _yea_, we don't need to handoff to an // elevated window. - if (!_startupActions || IsElevated() || _shouldStartInboundListener) + if (!_startupActions || IsElevated() || _shouldStartInboundListener || _startupActions.Size() == 0) { // there aren't startup actions, or we're elevated. In that case, go for it. return false; @@ -444,32 +432,6 @@ namespace winrt::TerminalApp::implementation } } - // Method Description; - // - Checks if the current window is configured to load a particular layout - // Arguments: - // - settings: The settings to use as this may be called before the page is - // fully initialized. - // Return Value: - // - non-null if there is a particular saved layout to use - std::optional TerminalPage::LoadPersistedLayoutIdx(CascadiaSettings& settings) const - { - return ShouldUsePersistedLayout(settings) ? _loadFromPersistedLayoutIdx : std::nullopt; - } - - WindowLayout TerminalPage::LoadPersistedLayout(CascadiaSettings& settings) const - { - if (const auto idx = LoadPersistedLayoutIdx(settings)) - { - const auto i = idx.value(); - const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); - if (layouts && layouts.Size() > i) - { - return layouts.GetAt(i); - } - } - return nullptr; - } - winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e) { Windows::Foundation::Collections::IVectorView items; @@ -553,16 +515,6 @@ namespace winrt::TerminalApp::implementation { _startupState = StartupState::InStartup; - // If we are provided with an index, the cases where we have - // commandline args and startup actions are already handled. - if (const auto layout = LoadPersistedLayout(_settings)) - { - if (layout.TabLayout().Size() > 0) - { - _startupActions = layout.TabLayout(); - } - } - ProcessStartupActions(_startupActions, true); // If we were told that the COM server needs to be started to listen for incoming @@ -1854,11 +1806,11 @@ namespace winrt::TerminalApp::implementation } // If the user set a custom name, save it - if (_WindowName != L"") + if (_WindowProperties.WindowName() != L"") { ActionAndArgs action; action.Action(ShortcutAction::RenameWindow); - RenameWindowArgs args{ _WindowName }; + RenameWindowArgs args{ _WindowProperties.WindowName() }; action.Args(args); actions.emplace_back(std::move(action)); @@ -1908,7 +1860,7 @@ namespace winrt::TerminalApp::implementation } } - if (ShouldUsePersistedLayout(_settings)) + if (_settings.GlobalSettings().ShouldUsePersistedLayout()) { // Don't delete the ApplicationState when all of the tabs are removed. // If there is still a monarch living they will get the event that @@ -3067,7 +3019,7 @@ namespace winrt::TerminalApp::implementation auto listCopy = actions; _startupActions = winrt::single_threaded_vector(std::move(listCopy)); const auto afterSize = _startupActions.Size(); - assert(initSize == afterSize);// you donkey + assert(initSize == afterSize); // you donkey } // Routine Description: @@ -3867,105 +3819,14 @@ namespace winrt::TerminalApp::implementation } } - // WindowName is a otherwise generic WINRT_OBSERVABLE_PROPERTY, but it needs - // to raise a PropertyChanged for WindowNameForDisplay, instead of - // WindowName. - winrt::hstring TerminalPage::WindowName() const noexcept - { - return _WindowName; - } - - winrt::fire_and_forget TerminalPage::WindowName(const winrt::hstring& value) - { - const auto oldIsQuakeMode = IsQuakeWindow(); - const auto changed = _WindowName != value; - if (changed) - { - _WindowName = value; - } - auto weakThis{ get_weak() }; - // On the foreground thread, raise property changed notifications, and - // display the success toast. - co_await wil::resume_foreground(Dispatcher()); - if (auto page{ weakThis.get() }) - { - if (changed) - { - page->_PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowName" }); - page->_PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowNameForDisplay" }); - - // DON'T display the confirmation if this is the name we were - // given on startup! - if (page->_startupState == StartupState::Initialized) - { - page->IdentifyWindow(); - - // If we're entering quake mode, or leaving it - if (IsQuakeWindow() != oldIsQuakeMode) - { - // If we're entering Quake Mode from ~Focus Mode, then this will enter Focus Mode - // If we're entering Quake Mode from Focus Mode, then this will do nothing - // If we're leaving Quake Mode (we're already in Focus Mode), then this will do nothing - SetFocusMode(true); - _IsQuakeWindowChangedHandlers(*this, nullptr); - } - } - } - } - } - - // WindowId is a otherwise generic WINRT_OBSERVABLE_PROPERTY, but it needs - // to raise a PropertyChanged for WindowIdForDisplay, instead of - // WindowId. - uint64_t TerminalPage::WindowId() const noexcept - { - return _WindowId; - } - void TerminalPage::WindowId(const uint64_t& value) - { - if (_WindowId != value) - { - _WindowId = value; - _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowIdForDisplay" }); - } - } - - void TerminalPage::SetPersistedLayoutIdx(const uint32_t idx) - { - _loadFromPersistedLayoutIdx = idx; - } - void TerminalPage::SetNumberOfOpenWindows(const uint64_t num) { + // This is used in TerminalPage::_RemoveTab, when we close a tab. If we + // close the last tab, and there's only one window open, then we will + // call to persist _no_ state. _numOpenWindows = num; } - // Method Description: - // - Returns a label like "Window: 1234" for the ID of this window - // Arguments: - // - - // Return Value: - // - a string for displaying the name of the window. - winrt::hstring TerminalPage::WindowIdForDisplay() const noexcept - { - return winrt::hstring{ fmt::format(L"{}: {}", - std::wstring_view(RS_(L"WindowIdLabel")), - _WindowId) }; - } - - // Method Description: - // - Returns a label like "" when the window has no name, or the name of the window. - // Arguments: - // - - // Return Value: - // - a string for displaying the name of the window. - winrt::hstring TerminalPage::WindowNameForDisplay() const noexcept - { - return _WindowName.empty() ? - winrt::hstring{ fmt::format(L"<{}>", RS_(L"UnnamedWindowName")) } : - _WindowName; - } - // Method Description: // - Called when an attempt to rename the window has failed. This will open // the toast displaying a message to the user that the attempt to rename @@ -4077,17 +3938,12 @@ namespace winrt::TerminalApp::implementation else if (key == Windows::System::VirtualKey::Escape) { // User wants to discard the changes they made - WindowRenamerTextBox().Text(WindowName()); + WindowRenamerTextBox().Text(WindowProperties().WindowName()); WindowRenamer().IsOpen(false); _renamerPressedEnter = false; } } - bool TerminalPage::IsQuakeWindow() const noexcept - { - return WindowName() == QuakeWindowName; - } - // Method Description: // - This function stops people from duplicating the base profile, because // it gets ~ ~ weird ~ ~ when they do. Remove when TODO GH#5047 is done. @@ -4463,4 +4319,34 @@ namespace winrt::TerminalApp::implementation _activated = activated; _updateThemeColors(); } + + TerminalApp::IWindowProperties TerminalPage::WindowProperties() + { + return _WindowProperties; + } + void TerminalPage::WindowProperties(const TerminalApp::IWindowProperties& props) + { + _WindowProperties = props; + } + + winrt::fire_and_forget TerminalPage::WindowNameChanged() + { + auto weakThis{ get_weak() }; + // On the foreground thread, raise property changed notifications, and + // display the success toast. + co_await wil::resume_foreground(Dispatcher()); + if (auto page{ weakThis.get() }) + { + _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowName" }); + _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowNameForDisplay" }); + _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowIdForDisplay" }); + + // DON'T display the confirmation if this is the name we were + // given on startup! + if (page->_startupState == StartupState::Initialized) + { + page->IdentifyWindow(); + } + } + } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 9c1705706a0..28b1db98c25 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -63,11 +63,8 @@ namespace winrt::TerminalApp::implementation void Create(); - bool ShouldUsePersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const; bool ShouldImmediatelyHandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const; void HandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); - std::optional LoadPersistedLayoutIdx(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const; - winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const; Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout(); winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e); @@ -117,20 +114,14 @@ namespace winrt::TerminalApp::implementation const bool initial, const winrt::hstring cwd = L""); - // Normally, WindowName and WindowId would be - // WINRT_OBSERVABLE_PROPERTY's, but we want them to raise - // WindowNameForDisplay and WindowIdForDisplay instead - winrt::hstring WindowName() const noexcept; - winrt::fire_and_forget WindowName(const winrt::hstring& value); - uint64_t WindowId() const noexcept; - void WindowId(const uint64_t& value); + // For the sake of XAML binding: + winrt::hstring WindowName() const noexcept { return _WindowProperties.WindowName(); }; + uint64_t WindowId() const noexcept { return _WindowProperties.WindowId(); }; + winrt::hstring WindowIdForDisplay() const noexcept { return _WindowProperties.WindowIdForDisplay(); }; + winrt::hstring WindowNameForDisplay() const noexcept { return _WindowProperties.WindowNameForDisplay(); }; void SetNumberOfOpenWindows(const uint64_t value); - void SetPersistedLayoutIdx(const uint32_t value); - winrt::hstring WindowIdForDisplay() const noexcept; - winrt::hstring WindowNameForDisplay() const noexcept; - bool IsQuakeWindow() const noexcept; bool IsElevated() const noexcept; void OpenSettingsUI(); @@ -138,6 +129,10 @@ namespace winrt::TerminalApp::implementation bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); + TerminalApp::IWindowProperties WindowProperties(); + void WindowProperties(const TerminalApp::IWindowProperties& props); + winrt::fire_and_forget WindowNameChanged(); + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); // -------------------------------- WinRT Events --------------------------------- @@ -153,7 +148,6 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(Initialized, IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs); TYPED_EVENT(IdentifyWindowsRequested, IInspectable, IInspectable); TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs); - TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable); TYPED_EVENT(SummonWindowRequested, IInspectable, IInspectable); TYPED_EVENT(CloseRequested, IInspectable, IInspectable); TYPED_EVENT(OpenSystemMenu, IInspectable, IInspectable); @@ -230,6 +224,8 @@ namespace winrt::TerminalApp::implementation int _renamerLayoutCount{ 0 }; bool _renamerPressedEnter{ false }; + TerminalApp::IWindowProperties _WindowProperties{ nullptr }; + winrt::Windows::Foundation::IAsyncOperation _ShowDialogHelper(const std::wstring_view& name); void _ShowAboutDialog(); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 5535c527a21..b363e183355 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -17,6 +17,15 @@ namespace TerminalApp Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); }; + interface IWindowProperties + { + String WindowName { get; }; + UInt64 WindowId { get; }; + String WindowNameForDisplay { get; }; + String WindowIdForDisplay { get; }; + Boolean IsQuakeWindow(); + }; + [default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener { TerminalPage(); @@ -29,13 +38,9 @@ namespace TerminalApp Boolean Fullscreen { get; }; Boolean AlwaysOnTop { get; }; + IWindowProperties WindowProperties { get; }; void IdentifyWindow(); - String WindowName; - UInt64 WindowId; - String WindowNameForDisplay { get; }; - String WindowIdForDisplay { get; }; void RenameFailed(); - Boolean IsQuakeWindow(); // We cannot use the default XAML APIs because we want to make sure // that there's only one application-global dialog visible at a time, @@ -49,6 +54,11 @@ namespace TerminalApp Windows.UI.Xaml.Media.Brush TitlebarBrush { get; }; void WindowActivated(Boolean activated); + String WindowName { get; }; + UInt64 WindowId { get; }; + String WindowNameForDisplay { get; }; + String WindowIdForDisplay { get; }; + event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; event Windows.Foundation.TypedEventHandler SetTitleBarContent; @@ -59,7 +69,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler RenameWindowRequested; - event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; event Windows.Foundation.TypedEventHandler SummonWindowRequested; event Windows.Foundation.TypedEventHandler CloseRequested; event Windows.Foundation.TypedEventHandler OpenSystemMenu; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 44ddc48c6f3..75d0c4c6592 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -5,6 +5,7 @@ #include "TerminalWindow.h" #include "../inc/WindowingBehavior.h" #include "TerminalWindow.g.cpp" +#include "SettingsLoadEventArgs.g.cpp" #include #include @@ -26,14 +27,14 @@ using namespace ::TerminalApp; namespace winrt { namespace MUX = Microsoft::UI::Xaml; + namespace WUX = Windows::UI::Xaml; using IInspectable = Windows::Foundation::IInspectable; } -// clang-format off // !!! IMPORTANT !!! // Make sure that these keys are in the same order as the // SettingsLoadWarnings/Errors enum is! -static const std::array settingsLoadWarningsLabels { +static const std::array settingsLoadWarningsLabels{ USES_RESOURCE(L"MissingDefaultProfileText"), USES_RESOURCE(L"DuplicateProfileText"), USES_RESOURCE(L"UnknownColorSchemeText"), @@ -51,14 +52,9 @@ static const std::array settingsLoadWarningsLabels { USES_RESOURCE(L"UnknownTheme"), USES_RESOURCE(L"DuplicateRemainingProfilesEntry"), }; -static const std::array settingsLoadErrorsLabels { - USES_RESOURCE(L"NoProfilesText"), - USES_RESOURCE(L"AllProfilesHiddenText") -}; -// clang-format on static_assert(settingsLoadWarningsLabels.size() == static_cast(SettingsLoadWarnings::WARNINGS_SIZE)); -static_assert(settingsLoadErrorsLabels.size() == static_cast(SettingsLoadErrors::ERRORS_SIZE)); +// Errors are defined in AppLogic.cpp // Function Description: // - General-purpose helper for looking up a localized string for a @@ -94,19 +90,6 @@ static winrt::hstring _GetWarningText(SettingsLoadWarnings warning) return _GetMessageText(static_cast(warning), settingsLoadWarningsLabels); } -// // Function Description: -// // - Gets the text from our ResourceDictionary for the given -// // SettingsLoadError. If there is no such text, we'll return nullptr. -// // - The warning should have an entry in settingsLoadErrorsLabels. -// // Arguments: -// // - error: the SettingsLoadErrors value to get the localized text for. -// // Return Value: -// // - localized text for the given error -// static winrt::hstring _GetErrorText(SettingsLoadErrors error) -// { -// return _GetMessageText(static_cast(error), settingsLoadErrorsLabels); -// } - // Function Description: // - Creates a Run of text to display an error message. The text is yellow or // red for dark/light theme, respectively. @@ -134,9 +117,14 @@ static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceD namespace winrt::TerminalApp::implementation { - TerminalWindow::TerminalWindow(const CascadiaSettings& settings) : - _settings{ settings } + TerminalWindow::TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult) : + _settings{ settingsLoadedResult.NewSettings() }, + _initialLoadResult{ settingsLoadedResult } { + _root = winrt::make_self(); + _root->WindowProperties(*this); + _dialog = ContentDialog{}; + // For your own sanity, it's better to do setup outside the ctor. // If you do any setup in the ctor that ends up throwing an exception, // then it might look like App just failed to activate, which will @@ -147,14 +135,44 @@ namespace winrt::TerminalApp::implementation // make sure that there's a terminal page for callers of // SetTitleBarContent _isElevated = ::Microsoft::Console::Utils::IsElevated(); - _root = winrt::make_self(); } // Method Description: // - Implements the IInitializeWithWindow interface from shobjidl_core. HRESULT TerminalWindow::Initialize(HWND hwnd) { - _dialog = ContentDialog{}; + // Pass commandline args into the TerminalPage. If we were supposed to + // load from a persisted layout, do that instead. + auto foundLayout = false; + if (const auto& layout = LoadPersistedLayout()) + { + if (layout.TabLayout().Size() > 0) + { + std::vector actions; + for (const auto& a : layout.TabLayout()) + { + actions.emplace_back(a); + } + _root->SetStartupActions(actions); + foundLayout = true; + } + } + if (!foundLayout) + { + _root->SetStartupActions(_appArgs.GetStartupActions()); + } + + // Check if we were started as a COM server for inbound connections of console sessions + // coming out of the operating system default application feature. If so, + // tell TerminalPage to start the listener as we have to make sure it has the chance + // to register a handler to hear about the requests first and is all ready to receive + // them before the COM server registers itself. Otherwise, the request might come + // in and be routed to an event with no handlers or a non-ready Page. + if (_appArgs.IsHandoffListener()) + { + _root->SetInboundListener(true); + } + return _root->Initialize(hwnd); } // Method Description: @@ -248,7 +266,9 @@ namespace winrt::TerminalApp::implementation _RefreshThemeRoutine(); - auto args = winrt::make_self(RS_(L"SettingsMenuItem"), SystemMenuChangeAction::Add, SystemMenuItemHandler(this, &TerminalWindow::_OpenSettingsUI)); + auto args = winrt::make_self(RS_(L"SettingsMenuItem"), + SystemMenuChangeAction::Add, + SystemMenuItemHandler(this, &TerminalWindow::_OpenSettingsUI)); _SystemMenuChangeRequestedHandlers(*this, *args); TraceLoggingWrite( @@ -313,7 +333,7 @@ namespace winrt::TerminalApp::implementation // - dialog: the dialog object that is going to show up // Return value: // - an IAsyncOperation with the dialog result - winrt::Windows::Foundation::IAsyncOperation TerminalWindow::ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog) + winrt::Windows::Foundation::IAsyncOperation TerminalWindow::ShowDialog(winrt::WUX::Controls::ContentDialog dialog) { // DON'T release this lock in a wil::scope_exit. The scope_exit will get // called when we await, which is not what we want. @@ -386,7 +406,8 @@ namespace winrt::TerminalApp::implementation // - contentKey: The key to use to lookup the content text from our resources. void TerminalWindow::_ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, - HRESULT settingsLoadedResult) + HRESULT settingsLoadedResult, + const winrt::hstring& exceptionText) { auto title = GetLibraryResourceString(titleKey); auto buttonText = RS_(L"Ok"); @@ -405,13 +426,12 @@ namespace winrt::TerminalApp::implementation if (FAILED(settingsLoadedResult)) { - // TODO! _settingsLoadExceptionText needs to get into the TerminalWindow somehow - - // if (!_settingsLoadExceptionText.empty()) - // { - // warningsTextBlock.Inlines().Append(_BuildErrorRun(_settingsLoadExceptionText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources())); - // warningsTextBlock.Inlines().Append(Documents::LineBreak{}); - // } + if (!exceptionText.empty()) + { + warningsTextBlock.Inlines().Append(_BuildErrorRun(exceptionText, + winrt::WUX::Application::Current().as<::winrt::TerminalApp::App>().Resources())); + warningsTextBlock.Inlines().Append(Documents::LineBreak{}); + } } // Add a note that we're using the default settings in this case. @@ -436,7 +456,7 @@ namespace winrt::TerminalApp::implementation // validating the settings. // - Only one dialog can be visible at a time. If another dialog is visible // when this is called, nothing happens. See ShowDialog for details - void TerminalWindow::_ShowLoadWarningsDialog() + void TerminalWindow::_ShowLoadWarningsDialog(const Windows::Foundation::Collections::IVector& warnings) { auto title = RS_(L"SettingsValidateErrorTitle"); auto buttonText = RS_(L"Ok"); @@ -447,18 +467,16 @@ namespace winrt::TerminalApp::implementation // Make sure the lines of text wrap warningsTextBlock.TextWrapping(TextWrapping::Wrap); - // TODO! warnings need to get into here somehow - // - // for (const auto& warning : _warnings) - // { - // // Try looking up the warning message key for each warning. - // const auto warningText = _GetWarningText(warning); - // if (!warningText.empty()) - // { - // warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources())); - // warningsTextBlock.Inlines().Append(Documents::LineBreak{}); - // } - // } + for (const auto& warning : warnings) + { + // Try looking up the warning message key for each warning. + const auto warningText = _GetWarningText(warning); + if (!warningText.empty()) + { + warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, winrt::WUX::Application::Current().as<::winrt::TerminalApp::App>().Resources())); + warningsTextBlock.Inlines().Append(Documents::LineBreak{}); + } + } Controls::ContentDialog dialog; dialog.Title(winrt::box_value(title)); @@ -489,18 +507,17 @@ namespace winrt::TerminalApp::implementation } } - // TODO! Yea, more of "how do we get _settingsLoadedResult / warnings / error" into here? - // - // if (FAILED(_settingsLoadedResult)) - // { - // const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle"); - // const winrt::hstring textKey = USES_RESOURCE(L"InitialJsonParseErrorText"); - // _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); - // } - // else if (_settingsLoadedResult == S_FALSE) - // { - // _ShowLoadWarningsDialog(); - // } + const auto& settingsLoadedResult = gsl::narrow_cast(_initialLoadResult.Result()); + if (FAILED(settingsLoadedResult)) + { + const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle"); + const winrt::hstring textKey = USES_RESOURCE(L"InitialJsonParseErrorText"); + _ShowLoadErrorsDialog(titleKey, textKey, settingsLoadedResult, _initialLoadResult.ExceptionText()); + } + else if (settingsLoadedResult == S_FALSE) + { + _ShowLoadWarningsDialog(_initialLoadResult.Warnings()); + } } // Method Description: @@ -566,7 +583,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Foundation::Size proposedSize{}; const auto scale = static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); - if (const auto layout = _root->LoadPersistedLayout(_settings)) + if (const auto layout = LoadPersistedLayout()) { if (layout.InitialSize()) { @@ -596,32 +613,34 @@ namespace winrt::TerminalApp::implementation // the height of the tab bar here. if (_settings.GlobalSettings().ShowTabsInTitlebar()) { - // If we're showing the tabs in the titlebar, we need to use a - // TitlebarControl here to calculate how much space to reserve. + // In the past, we used to actually instantiate a TitlebarControl + // and use Measure() to determine the DesiredSize of the control, to + // reserve exactly what we'd need. // - // We'll create a fake TitlebarControl, and we'll propose an - // available size to it with Measure(). After Measure() is called, - // the TitlebarControl's DesiredSize will contain the _unscaled_ - // size that the titlebar would like to use. We'll use that as part - // of the height calculation here. - auto titlebar = TitlebarControl{ static_cast(0) }; - titlebar.Measure({ SHRT_MAX, SHRT_MAX }); - proposedSize.Height += (titlebar.DesiredSize().Height) * scale; + // We can't do that anymore, because this is now called _before_ + // we've initialized XAML for this thread. We can't start XAML till + + // we have an HWND, and we can't finish creating the window till we + // know how big it should be. + // + // Instead, we'll just hardcode how big the titlebar should be. If + + // the titlebar / tab row ever change size, these numbers will have + // to change accordingly. + + static constexpr auto titlebarHeight = 40; + proposedSize.Height += (titlebarHeight)*scale; } else if (_settings.GlobalSettings().AlwaysShowTabs()) { - // Otherwise, let's use a TabRowControl to calculate how much extra - // space we'll need. + // Same comment as above, but with a TabRowControl. // - // Similarly to above, we'll measure it with an arbitrarily large - // available space, to make sure we get all the space it wants. - auto tabControl = TabRowControl(); - tabControl.Measure({ SHRT_MAX, SHRT_MAX }); - - // For whatever reason, there's about 10px of unaccounted-for space - // in the application. I couldn't tell you where these 10px are - // coming from, but they need to be included in this math. - proposedSize.Height += (tabControl.DesiredSize().Height + 10) * scale; + // A note from before: For whatever reason, there's about 10px of + // unaccounted-for space in the application. I couldn't tell you + // where these 10px are coming from, but they need to be included in + // this math. + static constexpr auto tabRowHeight = 32; + proposedSize.Height += (tabRowHeight + 10) * scale; } return proposedSize; @@ -642,7 +661,7 @@ namespace winrt::TerminalApp::implementation // commandline, then use that to override the value from the settings. const auto valueFromSettings = _settings.GlobalSettings().LaunchMode(); const auto valueFromCommandlineArgs = _appArgs.GetLaunchMode(); - if (const auto layout = _root->LoadPersistedLayout(_settings)) + if (const auto layout = LoadPersistedLayout()) { if (layout.LaunchMode()) { @@ -668,7 +687,7 @@ namespace winrt::TerminalApp::implementation { auto initialPosition{ _settings.GlobalSettings().InitialPosition() }; - if (const auto layout = _root->LoadPersistedLayout(_settings)) + if (const auto layout = LoadPersistedLayout()) { if (layout.InitialPosition()) { @@ -713,24 +732,34 @@ namespace winrt::TerminalApp::implementation _RequestedThemeChangedHandlers(*this, Theme()); } - void TerminalWindow::UpdateSettings(const HRESULT settingsLoadedResult, const CascadiaSettings& settings) + winrt::fire_and_forget TerminalWindow::UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args) { - if (FAILED(settingsLoadedResult)) + _settings = args.NewSettings(); + // Update the settings in TerminalPage + _root->SetSettings(_settings, true); + + co_await wil::resume_foreground(_root->Dispatcher()); + + // Bubble the notification up to the AppHost, now that we've updated our _settings. + _SettingsChangedHandlers(*this, args); + + if (FAILED(args.Result())) { const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); - _ShowLoadErrorsDialog(titleKey, textKey, settingsLoadedResult); - return; + _ShowLoadErrorsDialog(titleKey, + textKey, + gsl::narrow_cast(args.Result()), + args.ExceptionText()); + co_return; } - else if (settingsLoadedResult == S_FALSE) + else if (args.Result() == S_FALSE) { - _ShowLoadWarningsDialog(); + _ShowLoadWarningsDialog(args.Warnings()); } - _settings = settings; - // Update the settings in TerminalPage - _root->SetSettings(_settings, true); _RefreshThemeRoutine(); } + void TerminalWindow::_OpenSettingsUI() { _root->OpenSettingsUI(); @@ -846,7 +875,7 @@ namespace winrt::TerminalApp::implementation { // If persisted layout is enabled and we are the last window closing // we should save our state. - if (_root->ShouldUsePersistedLayout(_settings) && _numOpenWindows == 1) + if (_settings.GlobalSettings().ShouldUsePersistedLayout() && _numOpenWindows == 1) { if (const auto layout = _root->GetWindowLayout()) { @@ -879,7 +908,10 @@ namespace winrt::TerminalApp::implementation } void TerminalWindow::WindowActivated(const bool activated) { - _root->WindowActivated(activated); + if (_root) + { + _root->WindowActivated(activated); + } } // Method Description: @@ -920,10 +952,6 @@ namespace winrt::TerminalApp::implementation //////////////////////////////////////////////////////////////////////////// - // bool TerminalWindow::HasSettingsStartupActions() const noexcept - // { - // return _hasSettingsStartupActions; - // } void TerminalWindow::SetSettingsStartupArgs(const std::vector& actions) { for (const auto& action : actions) @@ -954,6 +982,8 @@ namespace winrt::TerminalApp::implementation // or 0. (see TerminalWindow::_ParseArgs) int32_t TerminalWindow::SetStartupCommandline(array_view args) { + // This is called in AppHost::ctor(), before we've created the window + // (or called TerminalWindow::Initialize) const auto result = _appArgs.ParseArgs(args); if (result == 0) { @@ -961,22 +991,15 @@ namespace winrt::TerminalApp::implementation // then it contains only the executable name and no other arguments. _hasCommandLineArguments = args.size() > 1; _appArgs.ValidateStartupCommands(); - if (const auto idx = _appArgs.GetPersistedLayoutIdx()) - { - _root->SetPersistedLayoutIdx(idx.value()); - } - _root->SetStartupActions(_appArgs.GetStartupActions()); - // Check if we were started as a COM server for inbound connections of console sessions - // coming out of the operating system default application feature. If so, - // tell TerminalPage to start the listener as we have to make sure it has the chance - // to register a handler to hear about the requests first and is all ready to receive - // them before the COM server registers itself. Otherwise, the request might come - // in and be routed to an event with no handlers or a non-ready Page. - if (_appArgs.IsHandoffListener()) - { - _root->SetInboundListener(true); - } + // DON'T pass the args into the page yet. It doesn't exist yet. + // Instead, we'll handle that in Initialize, when we first instantiate the page. + } + + // If we have a -s param passed to us to load a saved layout, cache that now. + if (const auto idx = _appArgs.GetPersistedLayoutIdx()) + { + SetPersistedLayoutIdx(idx.value()); } return result; @@ -1038,27 +1061,6 @@ namespace winrt::TerminalApp::implementation //////////////////////////////////////////////////////////////////////////// - bool TerminalWindow::ShouldUsePersistedLayout() - { - return _root != nullptr ? _root->ShouldUsePersistedLayout(_settings) : false; - } - - void TerminalWindow::SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts) - { - std::vector converted; - converted.reserve(layouts.Size()); - - for (const auto& json : layouts) - { - if (json != L"") - { - converted.emplace_back(WindowLayout::FromJson(json)); - } - } - - ApplicationState::SharedInstance().PersistedWindowLayouts(winrt::single_threaded_vector(std::move(converted))); - } - hstring TerminalWindow::GetWindowLayoutJson(LaunchPosition position) { if (_root != nullptr) @@ -1072,65 +1074,135 @@ namespace winrt::TerminalApp::implementation return L""; } - void TerminalWindow::IdentifyWindow() + void TerminalWindow::SetPersistedLayoutIdx(const uint32_t idx) { - if (_root) - { - _root->IdentifyWindow(); - } + _loadFromPersistedLayoutIdx = idx; } - winrt::hstring TerminalWindow::WindowName() + // Method Description; + // - Checks if the current window is configured to load a particular layout + // Arguments: + // - settings: The settings to use as this may be called before the page is + // fully initialized. + // Return Value: + // - non-null if there is a particular saved layout to use + std::optional TerminalWindow::LoadPersistedLayoutIdx() const { - return _root ? _root->WindowName() : L""; + return _settings.GlobalSettings().ShouldUsePersistedLayout() ? _loadFromPersistedLayoutIdx : std::nullopt; } - void TerminalWindow::WindowName(const winrt::hstring& name) + + WindowLayout TerminalWindow::LoadPersistedLayout() const { - if (_root) + if (const auto idx = LoadPersistedLayoutIdx()) { - _root->WindowName(name); + const auto i = idx.value(); + const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); + if (layouts && layouts.Size() > i) + { + return layouts.GetAt(i); + } } + return nullptr; } - uint64_t TerminalWindow::WindowId() - { - return _root ? _root->WindowId() : 0; - } - void TerminalWindow::WindowId(const uint64_t& id) + + void TerminalWindow::SetNumberOfOpenWindows(const uint64_t num) { + _numOpenWindows = num; if (_root) { - _root->WindowId(id); + _root->SetNumberOfOpenWindows(num); } } + //////////////////////////////////////////////////////////////////////////// - void TerminalWindow::SetPersistedLayoutIdx(const uint32_t idx) + void TerminalWindow::IdentifyWindow() { if (_root) { - _root->SetPersistedLayoutIdx(idx); + _root->IdentifyWindow(); } } - void TerminalWindow::SetNumberOfOpenWindows(const uint64_t num) + void TerminalWindow::RenameFailed() { - _numOpenWindows = num; if (_root) { - _root->SetNumberOfOpenWindows(num); + _root->RenameFailed(); } } - void TerminalWindow::RenameFailed() + winrt::hstring TerminalWindow::WindowName() const noexcept { - if (_root) + return _WindowName; + } + void TerminalWindow::WindowName(const winrt::hstring& name) + { + const auto oldIsQuakeMode = IsQuakeWindow(); + + const auto changed = _WindowName != name; + if (changed) { - _root->RenameFailed(); + _WindowName = name; + if (_root) + { + _root->WindowNameChanged(); + + // If we're entering quake mode, or leaving it + if (IsQuakeWindow() != oldIsQuakeMode) + { + // If we're entering Quake Mode from ~Focus Mode, then this will enter Focus Mode + // If we're entering Quake Mode from Focus Mode, then this will do nothing + // If we're leaving Quake Mode (we're already in Focus Mode), then this will do nothing + _root->SetFocusMode(true); + _IsQuakeWindowChangedHandlers(*this, nullptr); + } + } + } + } + uint64_t TerminalWindow::WindowId() const noexcept + { + return _WindowId; + } + void TerminalWindow::WindowId(const uint64_t& id) + { + if (_WindowId != id) + { + _WindowId = id; + if (_root) + { + _root->WindowNameChanged(); + } } } + // Method Description: + // - Returns a label like "Window: 1234" for the ID of this window + // Arguments: + // - + // Return Value: + // - a string for displaying the name of the window. + winrt::hstring TerminalWindow::WindowIdForDisplay() const noexcept + { + return winrt::hstring{ fmt::format(L"{}: {}", + std::wstring_view(RS_(L"WindowIdLabel")), + _WindowId) }; + } + + // Method Description: + // - Returns a label like "" when the window has no name, or the name of the window. + // Arguments: + // - + // Return Value: + // - a string for displaying the name of the window. + winrt::hstring TerminalWindow::WindowNameForDisplay() const noexcept + { + return _WindowName.empty() ? + winrt::hstring{ fmt::format(L"<{}>", RS_(L"UnnamedWindowName")) } : + _WindowName; + } bool TerminalWindow::IsQuakeWindow() const noexcept { - return _root->IsQuakeWindow(); + return _WindowName == QuakeWindowName; } void TerminalWindow::RequestExitFullscreen() @@ -1142,14 +1214,42 @@ namespace winrt::TerminalApp::implementation { return _settings.GlobalSettings().AutoHideWindow(); } - // TODO! Arg should be a SettingsLoadEventArgs{ result, warnings, error, settings} + void TerminalWindow::UpdateSettingsHandler(const winrt::IInspectable& /*sender*/, - const winrt::IInspectable& arg) + const winrt::TerminalApp::SettingsLoadEventArgs& arg) + { + UpdateSettings(arg); + } + //////////////////////////////////////////////////////////////////////////// + + bool TerminalWindow::ShouldImmediatelyHandoffToElevated() + { + return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false; + } + + // Method Description: + // - Escape hatch for immediately dispatching requests to elevated windows + // when first launched. At this point in startup, the window doesn't exist + // yet, XAML hasn't been started, but we need to dispatch these actions. + // We can't just go through ProcessStartupActions, because that processes + // the actions async using the XAML dispatcher (which doesn't exist yet) + // - DON'T CALL THIS if you haven't already checked + // ShouldImmediatelyHandoffToElevated. If you're thinking about calling + // this outside of the one place it's used, that's probably the wrong + // solution. + // Arguments: + // - settings: the settings we should use for dispatching these actions. At + // this point in startup, we hadn't otherwise been initialized with these, + // so use them now. + // Return Value: + // - + void TerminalWindow::HandoffToElevated() { - if (const auto& settings{ arg.try_as() }) + if (_root) { - this->UpdateSettings(S_OK, settings); - _root->SetSettings(_settings, true); + _root->HandoffToElevated(_settings); + return; } } + }; diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 9d5605d00ca..b7e6a22ee9e 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -6,6 +6,7 @@ #include "TerminalWindow.g.h" #include "SystemMenuChangeArgs.g.h" +#include "SettingsLoadEventArgs.h" #include "TerminalPage.h" #include @@ -35,7 +36,7 @@ namespace winrt::TerminalApp::implementation struct TerminalWindow : TerminalWindowT { public: - TerminalWindow(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); + TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult); ~TerminalWindow() = default; STDMETHODIMP Initialize(HWND hwnd); @@ -47,7 +48,7 @@ namespace winrt::TerminalApp::implementation void Quit(); - void UpdateSettings(const HRESULT settingsLoadedResult, const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); + winrt::fire_and_forget UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); bool HasCommandlineArguments() const noexcept; // bool HasSettingsStartupActions() const noexcept; @@ -57,25 +58,26 @@ namespace winrt::TerminalApp::implementation winrt::hstring ParseCommandlineMessage(); bool ShouldExitEarly(); + bool ShouldImmediatelyHandoffToElevated(); + void HandoffToElevated(); + bool FocusMode() const; bool Fullscreen() const; void Maximized(bool newMaximized); bool AlwaysOnTop() const; bool AutoHideWindow(); - bool ShouldUsePersistedLayout(); - hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position); - void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts); void IdentifyWindow(); void RenameFailed(); - winrt::hstring WindowName(); - void WindowName(const winrt::hstring& name); - uint64_t WindowId(); - void WindowId(const uint64_t& id); + + std::optional LoadPersistedLayoutIdx() const; + winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout() const; + void SetPersistedLayoutIdx(const uint32_t idx); void SetNumberOfOpenWindows(const uint64_t num); - bool IsQuakeWindow() const noexcept; + bool ShouldUsePersistedLayout() const; + void RequestExitFullscreen(); Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi); @@ -108,7 +110,18 @@ namespace winrt::TerminalApp::implementation void DismissDialog(); Microsoft::Terminal::Settings::Model::Theme Theme(); - void UpdateSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& arg); + void UpdateSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::SettingsLoadEventArgs& arg); + + // Normally, WindowName and WindowId would be + // WINRT_OBSERVABLE_PROPERTY's, but we want them to raise + // WindowNameForDisplay and WindowIdForDisplay instead + winrt::hstring WindowName() const noexcept; + void WindowName(const winrt::hstring& value); + uint64_t WindowId() const noexcept; + void WindowId(const uint64_t& value); + winrt::hstring WindowIdForDisplay() const noexcept; + winrt::hstring WindowNameForDisplay() const noexcept; + bool IsQuakeWindow() const noexcept; // -------------------------------- WinRT Events --------------------------------- // PropertyChanged is surprisingly not a typed event, so we'll define that one manually. @@ -139,12 +152,21 @@ namespace winrt::TerminalApp::implementation bool _gotSettingsStartupActions{ false }; std::vector _settingsStartupArgs{}; - uint64_t _numOpenWindows{ 0 }; + winrt::hstring _WindowName{}; + uint64_t _WindowId{ 0 }; + + uint64_t _numOpenWindows{ 1 }; + std::optional _loadFromPersistedLayoutIdx{}; Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; + TerminalApp::SettingsLoadEventArgs _initialLoadResult{ nullptr }; + + void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, + const winrt::hstring& contentKey, + HRESULT settingsLoadedResult, + const winrt::hstring& exceptionText); + void _ShowLoadWarningsDialog(const Windows::Foundation::Collections::IVector& warnings); - void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult); - void _ShowLoadWarningsDialog(); bool _IsKeyboardServiceEnabled(); void _RefreshThemeRoutine(); @@ -164,15 +186,18 @@ namespace winrt::TerminalApp::implementation FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress); FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested); FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested); - FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged); FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested); FORWARDED_TYPED_EVENT(CloseRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, CloseRequested); FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu); FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested); FORWARDED_TYPED_EVENT(ShowWindowChanged, Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs, _root, ShowWindowChanged); + TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable); + TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs); + TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs); + #ifdef UNIT_TESTING friend class TerminalAppLocalTests::CommandlineTest; #endif diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index fcd8cd9346f..a35836cc9a4 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -27,12 +27,22 @@ namespace TerminalApp SystemMenuItemHandler Handler { get; }; }; + [default_interface] runtimeclass SettingsLoadEventArgs + { + Boolean Reload { get; }; + UInt64 Result { get; }; + IVector Warnings { get; }; + String ExceptionText { get; }; + + Microsoft.Terminal.Settings.Model.CascadiaSettings NewSettings { get; }; + + }; + // See IDialogPresenter and TerminalPage's DialogPresenter for more // information. - [default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged + [default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, IWindowProperties, Windows.UI.Xaml.Data.INotifyPropertyChanged { - TerminalWindow(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); - + TerminalWindow(SettingsLoadEventArgs result); // For your own sanity, it's better to do setup outside the ctor. // If you do any setup in the ctor that ends up throwing an exception, @@ -46,7 +56,6 @@ namespace TerminalApp Boolean IsElevated(); Boolean HasCommandlineArguments(); - // Boolean HasSettingsStartupActions(); Int32 SetStartupCommandline(String[] commands); @@ -54,6 +63,9 @@ namespace TerminalApp String ParseCommandlineMessage { get; }; Boolean ShouldExitEarly { get; }; + Boolean ShouldImmediatelyHandoffToElevated(); + void HandoffToElevated(); + void Quit(); Windows.UI.Xaml.UIElement GetRoot(); @@ -66,13 +78,10 @@ namespace TerminalApp Boolean AutoHideWindow { get; }; void IdentifyWindow(); - String WindowName; - UInt64 WindowId; void SetPersistedLayoutIdx(UInt32 idx); void SetNumberOfOpenWindows(UInt64 num); void RenameFailed(); void RequestExitFullscreen(); - Boolean IsQuakeWindow(); Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi); Boolean CenterOnLaunch { get; }; @@ -91,19 +100,19 @@ namespace TerminalApp Windows.UI.Xaml.Media.Brush TitlebarBrush { get; }; void WindowActivated(Boolean activated); - Boolean ShouldUsePersistedLayout(); - // Boolean ShouldImmediatelyHandoffToElevated(); - // void HandoffToElevated(); String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position); - void SaveWindowLayoutJsons(Windows.Foundation.Collections.IVector layouts); Boolean GetMinimizeToNotificationArea(); Boolean GetAlwaysShowNotificationIcon(); Boolean GetShowTitleInTitlebar(); + // These already have accessort as a part of IWindowProperties, but we + // also want to be able to set them. + String WindowName { set; }; + UInt64 WindowId { set; }; + // See IDialogPresenter and TerminalPage's DialogPresenter for more // information. - Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); void DismissDialog(); event Windows.Foundation.TypedEventHandler SetTitleBarContent; @@ -126,5 +135,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SystemMenuChangeRequested; event Windows.Foundation.TypedEventHandler ShowWindowChanged; + event Windows.Foundation.TypedEventHandler SettingsChanged; + } } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index e337ba4e452..aaa3f63a66d 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -954,9 +954,9 @@ void CascadiaSettings::_researchOnLoad() // dark: 2 // a custom theme: 3 const auto themeChoice = themeInUse == L"system" ? 0 : - themeInUse == L"light" ? 1 : - themeInUse == L"dark" ? 2 : - 3; + themeInUse == L"light" ? 1 : + themeInUse == L"dark" ? 2 : + 3; TraceLoggingWrite( g_hSettingsModelProvider, diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 55ec4d634bf..249bc060f7e 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -239,3 +239,8 @@ winrt::Windows::Foundation::Collections::IMapView Themes() noexcept; void AddTheme(const Model::Theme& theme); Model::Theme CurrentTheme() noexcept; + bool ShouldUsePersistedLayout() const; INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L""); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 4ee2b0bf9e6..738159b5eba 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -108,5 +108,6 @@ namespace Microsoft.Terminal.Settings.Model void AddTheme(Theme theme); INHERITABLE_SETTING(ThemePair, Theme); Theme CurrentTheme { get; }; + Boolean ShouldUsePersistedLayout(); } } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 60ded0d3fe8..255af507370 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -79,11 +79,6 @@ AppHost::AppHost() noexcept : std::placeholders::_3); _window->SetCreateCallback(pfn); - _window->SetSnapDimensionCallback(std::bind(&winrt::TerminalApp::TerminalWindow::CalcSnappedDimension, - _windowLogic, - std::placeholders::_1, - std::placeholders::_2)); - // Event handlers: // MAKE SURE THEY ARE ALL: // * winrt::auto_revoke @@ -120,19 +115,6 @@ AppHost::AppHost() noexcept : { _BecomeMonarch(nullptr, nullptr); } - - // Create a throttled function for updating the window state, to match the - // one requested by the pty. A 200ms delay was chosen because it's the - // typical animation timeout in Windows. This does result in a delay between - // the PTY requesting a change to the window state and the Terminal - // realizing it, but should mitigate issues where the Terminal and PTY get - // de-sync'd. - _showHideWindowThrottler = std::make_shared>( - winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), - std::chrono::milliseconds(200), - [this](const bool show) { - _window->ShowWindowChanged(show); - }); } AppHost::~AppHost() @@ -269,10 +251,10 @@ void AppHost::_HandleCommandlineArgs() // the window at all here. In that case, we're going through this // special escape hatch to dispatch all the calls to elevate-shim, and // then we're going to exit immediately. - if (_appLogic.ShouldImmediatelyHandoffToElevated()) + if (_windowLogic.ShouldImmediatelyHandoffToElevated()) { _shouldCreateWindow = false; - _appLogic.HandoffToElevated(); + _windowLogic.HandoffToElevated(); return; } @@ -294,7 +276,7 @@ void AppHost::_HandleCommandlineArgs() { const auto numPeasants = _windowManager.GetNumberOfPeasants(); const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); - if (_windowLogic.ShouldUsePersistedLayout() && + if (_appLogic.ShouldUsePersistedLayout() && layouts && layouts.Size() > 0) { @@ -432,7 +414,13 @@ void AppHost::Initialize() _revokers.SetTaskbarProgress = _windowLogic.SetTaskbarProgress(winrt::auto_revoke, { this, &AppHost::SetTaskbarProgress }); _revokers.IdentifyWindowsRequested = _windowLogic.IdentifyWindowsRequested(winrt::auto_revoke, { this, &AppHost::_IdentifyWindowsRequested }); _revokers.RenameWindowRequested = _windowLogic.RenameWindowRequested(winrt::auto_revoke, { this, &AppHost::_RenameWindowRequested }); - _revokers.SettingsChanged = _appLogic.SettingsChanged(winrt::auto_revoke, { this, &AppHost::_HandleSettingsChanged }); + + // A note: make sure to listen to our _window_'s settings changed, not the + // applogic's. We want to make sure the event has gone through the window + // logic _before_ we handle it, so we can ask the window about it's newest + // properties. + _revokers.SettingsChanged = _windowLogic.SettingsChanged(winrt::auto_revoke, { this, &AppHost::_HandleSettingsChanged }); + _revokers.IsQuakeWindowChanged = _windowLogic.IsQuakeWindowChanged(winrt::auto_revoke, { this, &AppHost::_IsQuakeWindowChanged }); _revokers.SummonWindowRequested = _windowLogic.SummonWindowRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowRequested }); _revokers.OpenSystemMenu = _windowLogic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu }); @@ -448,6 +436,24 @@ void AppHost::Initialize() // while the screen is off. TerminalTrySetAutoCompleteAnimationsWhenOccluded(static_cast<::IUnknown*>(winrt::get_abi(_windowLogic.GetRoot())), true); + _window->SetSnapDimensionCallback(std::bind(&winrt::TerminalApp::TerminalWindow::CalcSnappedDimension, + _windowLogic, + std::placeholders::_1, + std::placeholders::_2)); + + // Create a throttled function for updating the window state, to match the + // one requested by the pty. A 200ms delay was chosen because it's the + // typical animation timeout in Windows. This does result in a delay between + // the PTY requesting a change to the window state and the Terminal + // realizing it, but should mitigate issues where the Terminal and PTY get + // de-sync'd. + _showHideWindowThrottler = std::make_shared>( + winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), + std::chrono::milliseconds(200), + [this](const bool show) { + _window->ShowWindowChanged(show); + }); + _window->UpdateTitle(_windowLogic.Title()); // Set up the content of the application. If the app has a custom titlebar, @@ -1007,7 +1013,7 @@ winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts() // Make sure we run on a background thread to not block anything. co_await winrt::resume_background(); - if (_windowLogic.ShouldUsePersistedLayout()) + if (_appLogic.ShouldUsePersistedLayout()) { try { @@ -1022,7 +1028,7 @@ winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts() TraceLoggingDescription("Logged when writing window state"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - _windowLogic.SaveWindowLayoutJsons(layoutJsons); + _appLogic.SaveWindowLayoutJsons(layoutJsons); } catch (...) { @@ -1063,11 +1069,6 @@ winrt::fire_and_forget AppHost::_SaveWindowLayoutsRepeat() } } -void AppHost::_listenForInboundConnections() -{ - _appLogic.SetInboundListener(); -} - winrt::fire_and_forget AppHost::_setupGlobalHotkeys() { // The hotkey MUST be registered on the main thread. It will fail otherwise! @@ -1399,7 +1400,7 @@ void AppHost::_updateTheme() } void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*args*/) + const winrt::TerminalApp::SettingsLoadEventArgs& /*args*/) { _setupGlobalHotkeys(); diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 5f9b48dc33e..abee5a9e668 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -19,6 +19,7 @@ class AppHost void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); bool HasWindow(); + winrt::TerminalApp::TerminalWindow Logic(); private: std::unique_ptr _window; @@ -26,6 +27,7 @@ class AppHost winrt::TerminalApp::AppLogic _appLogic; winrt::TerminalApp::TerminalWindow _windowLogic; winrt::Microsoft::Terminal::Remoting::WindowManager _windowManager{ nullptr }; + winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr }; std::vector _hotkeys; winrt::com_ptr _desktopManager{ nullptr }; @@ -89,7 +91,7 @@ class AppHost winrt::fire_and_forget _setupGlobalHotkeys(); winrt::fire_and_forget _createNewTerminalWindow(winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs args); void _HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& sender, - const winrt::Windows::Foundation::IInspectable& args); + const winrt::TerminalApp::SettingsLoadEventArgs& args); void _IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); @@ -171,7 +173,7 @@ class AppHost winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested; winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged; winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged; - winrt::TerminalApp::AppLogic::SettingsChanged_revoker SettingsChanged; + winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged; winrt::Microsoft::Terminal::Remoting::WindowManager::ShowNotificationIconRequested_revoker ShowNotificationIconRequested; winrt::Microsoft::Terminal::Remoting::WindowManager::HideNotificationIconRequested_revoker HideNotificationIconRequested; winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index a06dd7818e2..acc039ae447 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -405,7 +405,13 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam // - The total dimension long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize, const long nonClientSize) { - return gsl::narrow_cast(_pfnSnapDimensionCallback(isWidth, gsl::narrow_cast(clientSize)) + nonClientSize); + if (_pfnSnapDimensionCallback) + { + return gsl::narrow_cast(_pfnSnapDimensionCallback(isWidth, gsl::narrow_cast(clientSize)) + nonClientSize); + } + // We might have been called in WM_CREATE, before we've initialized XAML or + // our page. That's okay. + return clientSize + nonClientSize; } [[nodiscard]] LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept From 33685d9e9dc8ba4eae348fcdb8731c04af081586 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 13 Feb 2023 09:26:51 -0600 Subject: [PATCH 07/25] bwahahahahaha --- src/cascadia/TerminalApp/TerminalPage.cpp | 9 --------- .../CascadiaSettingsSerialization.cpp | 6 +++--- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 932ad147dbc..798f869e981 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -605,11 +605,6 @@ namespace winrt::TerminalApp::implementation if (auto page{ weakThis.get() }) { - const auto memberSize = page->_startupActions.Size(); - const auto paramSecondSize = actions.Size(); - memberSize; - paramFistSize; - paramSecondSize; for (const auto& action : actions) { if (auto page{ weakThis.get() }) @@ -3011,15 +3006,11 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::SetStartupActions(std::vector& actions) { - const auto initSize = actions.size(); - initSize; // The fastest way to copy all the actions out of the std::vector and // put them into a winrt::IVector is by making a copy, then moving the // copy into the winrt vector ctor. auto listCopy = actions; _startupActions = winrt::single_threaded_vector(std::move(listCopy)); - const auto afterSize = _startupActions.Size(); - assert(initSize == afterSize); // you donkey } // Routine Description: diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index aaa3f63a66d..e337ba4e452 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -954,9 +954,9 @@ void CascadiaSettings::_researchOnLoad() // dark: 2 // a custom theme: 3 const auto themeChoice = themeInUse == L"system" ? 0 : - themeInUse == L"light" ? 1 : - themeInUse == L"dark" ? 2 : - 3; + themeInUse == L"light" ? 1 : + themeInUse == L"dark" ? 2 : + 3; TraceLoggingWrite( g_hSettingsModelProvider, From a4f19a9dff33497d3e313b320ba3b790a8fbac9f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 13 Feb 2023 09:28:13 -0600 Subject: [PATCH 08/25] spel --- src/cascadia/TerminalApp/AppLogic.cpp | 2 +- src/cascadia/TerminalApp/TerminalWindow.idl | 2 +- src/cascadia/WindowsTerminal/AppHost.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index adffee5f5c2..9ed6078cfcd 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -32,7 +32,7 @@ namespace winrt //////////////////////////////////////////////////////////////////////////////// // Error message handling. This is in this file rather than with the warnings in -// TerminalWindow, becuase the error text might also just be a serializationgs +// TerminalWindow, because the error text might also just be a serialization // error message. So AppLogic needs to know the actual text of the error. // !!! IMPORTANT !!! diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index a35836cc9a4..5bcb0294982 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -106,7 +106,7 @@ namespace TerminalApp Boolean GetAlwaysShowNotificationIcon(); Boolean GetShowTitleInTitlebar(); - // These already have accessort as a part of IWindowProperties, but we + // These already have accessors as a part of IWindowProperties, but we // also want to be able to set them. String WindowName { set; }; UInt64 WindowId { set; }; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 255af507370..30af459f4be 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -416,7 +416,7 @@ void AppHost::Initialize() _revokers.RenameWindowRequested = _windowLogic.RenameWindowRequested(winrt::auto_revoke, { this, &AppHost::_RenameWindowRequested }); // A note: make sure to listen to our _window_'s settings changed, not the - // applogic's. We want to make sure the event has gone through the window + // AppLogic's. We want to make sure the event has gone through the window // logic _before_ we handle it, so we can ask the window about it's newest // properties. _revokers.SettingsChanged = _windowLogic.SettingsChanged(winrt::auto_revoke, { this, &AppHost::_HandleSettingsChanged }); From 82224bc87a4b44b414f911c8be6ce8fcc6b8680f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 13 Feb 2023 11:48:32 -0600 Subject: [PATCH 09/25] this was usnused --- src/cascadia/TerminalApp/TerminalPage.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 798f869e981..89169200e08 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -573,8 +573,6 @@ namespace winrt::TerminalApp::implementation const bool initial, const winrt::hstring cwd) { - const auto paramFistSize = actions.Size(); - auto weakThis{ get_weak() }; // Handle it on a subsequent pass of the UI thread. From d9d4d2e62dcef2e63e1086df8f231c7931c634f0 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 13 Feb 2023 12:08:54 -0600 Subject: [PATCH 10/25] get the tests to build, at least --- src/cascadia/LocalTests_TerminalApp/TabTests.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 53013f93721..cee11484427 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -1245,7 +1245,9 @@ namespace TerminalAppLocalTests page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) { // In the real terminal, this would bounce up to the monarch and // come back down. Instead, immediately call back and set the name. - page->WindowName(args.ProposedName()); + + // TODO! This doesn't work _at all_ anymore. May need to re-evaluate if this test is even possible as-is. + // page->WindowName(args.ProposedName()); }); auto windowNameChanged = false; From 118bffa6cdcbd1144e267f863ab9b29202530ae9 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 14 Feb 2023 15:51:18 -0600 Subject: [PATCH 11/25] fix the tests --- .../LocalTests_TerminalApp/TabTests.cpp | 29 +++++++++++++++---- src/cascadia/TerminalApp/TerminalPage.h | 3 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index cee11484427..c1edac3c313 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -42,6 +42,17 @@ namespace TerminalAppLocalTests // an updated TAEF that will let us install framework packages when the test // package is deployed. Until then, these tests won't deploy in CI. + struct WindowProperties : winrt::implements + { + WINRT_PROPERTY(winrt::hstring, WindowName); + WINRT_PROPERTY(uint64_t, WindowId); + WINRT_PROPERTY(winrt::hstring, WindowNameForDisplay); + WINRT_PROPERTY(winrt::hstring, WindowIdForDisplay); + + public: + bool IsQuakeWindow() { return _WindowName == L"_quake"; }; + }; + class TabTests { // For this set of tests, we need to activate some XAML content. For @@ -110,6 +121,7 @@ namespace TerminalAppLocalTests void _initializeTerminalPage(winrt::com_ptr& page, CascadiaSettings initialSettings); winrt::com_ptr _commonSetup(); + winrt::com_ptr _windowProperties; }; template @@ -239,11 +251,15 @@ namespace TerminalAppLocalTests // it's weird. winrt::TerminalApp::TerminalPage projectedPage{ nullptr }; + _windowProperties = winrt::make_self(); + winrt::TerminalApp::IWindowProperties iProps{ *_windowProperties }; + Log::Comment(NoThrowString().Format(L"Construct the TerminalPage")); - auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() { + auto result = RunOnUIThread([&projectedPage, &page, initialSettings, iProps]() { projectedPage = winrt::TerminalApp::TerminalPage(); page.copy_from(winrt::get_self(projectedPage)); page->_settings = initialSettings; + page->WindowProperties(iProps); }); VERIFY_SUCCEEDED(result); @@ -1242,12 +1258,13 @@ namespace TerminalAppLocalTests END_TEST_METHOD_PROPERTIES() auto page = _commonSetup(); - page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) { + page->RenameWindowRequested([&page, this](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) { // In the real terminal, this would bounce up to the monarch and // come back down. Instead, immediately call back and set the name. - - // TODO! This doesn't work _at all_ anymore. May need to re-evaluate if this test is even possible as-is. - // page->WindowName(args.ProposedName()); + // + // This replicates how TerminalWindow works + _windowProperties->WindowName(args.ProposedName()); + page->WindowNameChanged(); }); auto windowNameChanged = false; @@ -1262,7 +1279,7 @@ namespace TerminalAppLocalTests page->_RequestWindowRename(winrt::hstring{ L"Foo" }); }); TestOnUIThread([&]() { - VERIFY_ARE_EQUAL(L"Foo", page->_WindowName); + VERIFY_ARE_EQUAL(L"Foo", page->WindowName()); VERIFY_IS_TRUE(windowNameChanged, L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed"); }); diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 28b1db98c25..c82a365c4b8 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -186,8 +186,7 @@ namespace winrt::TerminalApp::implementation bool _isFullscreen{ false }; bool _isMaximized{ false }; bool _isAlwaysOnTop{ false }; - winrt::hstring _WindowName{}; - uint64_t _WindowId{ 0 }; + std::optional _loadFromPersistedLayoutIdx{}; uint64_t _numOpenWindows{ 0 }; From c79f27c3dac686c5f30b6448831d856a61fd57ca Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 27 Feb 2023 08:32:18 -0600 Subject: [PATCH 12/25] std::vectors are better --- src/cascadia/TerminalApp/AppLogic.cpp | 14 +++++++------- src/cascadia/TerminalApp/AppLogic.h | 2 +- src/cascadia/TerminalApp/TerminalPage.cpp | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 9ed6078cfcd..55e1e78d8bd 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -265,10 +265,10 @@ namespace winrt::TerminalApp::implementation return E_INVALIDARG; } - _warnings.Clear(); + _warnings.clear(); for (uint32_t i = 0; i < newSettings.Warnings().Size(); i++) { - _warnings.Append(newSettings.Warnings().GetAt(i)); + _warnings.push_back(newSettings.Warnings().GetAt(i)); } _hasSettingsStartupActions = false; @@ -288,13 +288,13 @@ namespace winrt::TerminalApp::implementation } else { - _warnings.Append(SettingsLoadWarnings::FailedToParseStartupActions); + _warnings.push_back(SettingsLoadWarnings::FailedToParseStartupActions); } } _settings = std::move(newSettings); - hr = (_warnings.Size()) == 0 ? S_OK : S_FALSE; + hr = (_warnings.size()) == 0u ? S_OK : S_FALSE; } catch (const winrt::hresult_error& e) { @@ -491,7 +491,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(true, static_cast(_settingsLoadedResult), _settingsLoadExceptionText, - _warnings, + winrt::multi_threaded_vector(std::move(_warnings)), _settings); _SettingsChangedHandlers(*this, *ev); return; @@ -515,7 +515,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(!initialLoad, _settingsLoadedResult, _settingsLoadExceptionText, - _warnings, + winrt::multi_threaded_vector(std::move(_warnings)), _settings); _SettingsChangedHandlers(*this, *ev); } @@ -668,7 +668,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(false, _settingsLoadedResult, _settingsLoadExceptionText, - _warnings, + winrt::multi_threaded_vector(std::move(_warnings)), _settings); auto window = winrt::make_self(*ev); diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 759cfb09500..821ec0f5f10 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -85,7 +85,7 @@ namespace winrt::TerminalApp::implementation std::shared_ptr> _reloadSettings; til::throttled_func_trailing<> _reloadState; - winrt::Windows::Foundation::Collections::IVector _warnings{ winrt::multi_threaded_vector() }; + std::vector _warnings{}; // These fields invoke _reloadSettings and must be destroyed before _reloadSettings. // (C++ destroys members in reverse-declaration-order.) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 89169200e08..9361f533460 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1799,11 +1799,11 @@ namespace winrt::TerminalApp::implementation } // If the user set a custom name, save it - if (_WindowProperties.WindowName() != L"") + if (const auto windowName = _WindowProperties.WindowName(); !windowName.empty()) { ActionAndArgs action; action.Action(ShortcutAction::RenameWindow); - RenameWindowArgs args{ _WindowProperties.WindowName() }; + RenameWindowArgs args{ windowName }; action.Args(args); actions.emplace_back(std::move(action)); From b589d092e9cae72186549ca953e9da5b6bdc206d Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 28 Feb 2023 11:22:29 -0600 Subject: [PATCH 13/25] Lots of PR nits --- src/cascadia/TerminalApp/AppLogic.cpp | 18 +++-- .../TerminalApp/TerminalAppLib.vcxproj | 3 + src/cascadia/TerminalApp/TerminalWindow.cpp | 74 ++++++++++--------- src/cascadia/TerminalApp/TerminalWindow.h | 6 +- src/cascadia/TerminalApp/TerminalWindow.idl | 2 - 5 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 55e1e78d8bd..9af911a7813 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -45,6 +45,17 @@ static const std::array settingsLoadErrorsLabels{ static_assert(settingsLoadErrorsLabels.size() == static_cast(SettingsLoadErrors::ERRORS_SIZE)); +// Function Description: +// - General-purpose helper for looking up a localized string for a +// warning/error. First will look for the given key in the provided map of +// keys->strings, where the values in the map are ResourceKeys. If it finds +// one, it will lookup the localized string from that ResourceKey. +// - If it does not find a key, it'll return an empty string +// Arguments: +// - key: the value to use to look for a resource key in the given map +// - map: A map of keys->Resource keys. +// Return Value: +// - the localized string for the given type, if it exists. template winrt::hstring _GetMessageText(uint32_t index, const T& keys) { @@ -67,10 +78,6 @@ static winrt::hstring _GetErrorText(SettingsLoadErrors error) return _GetMessageText(static_cast(error), settingsLoadErrorsLabels); } -// clang-format on - -//////////////////////////////////////////////////////////////////////////////// - static constexpr std::wstring_view StartupTaskName = L"StartTerminalOnLoginTask"; namespace winrt::TerminalApp::implementation @@ -121,9 +128,6 @@ namespace winrt::TerminalApp::implementation // cause you to chase down the rabbit hole of "why is App not // registered?" when it definitely is. - // The TerminalPage has to be constructed during our construction, to - // make sure that there's a terminal page for callers of - // SetTitleBarContent _isElevated = ::Microsoft::Console::Utils::IsElevated(); _reloadSettings = std::make_shared>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() { diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 7fb88842a1c..d72908d5b2a 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -141,6 +141,9 @@ TerminalWindow.idl + + TerminalWindow.idl + diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 75d0c4c6592..b1c0c4142ff 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -121,6 +121,9 @@ namespace winrt::TerminalApp::implementation _settings{ settingsLoadedResult.NewSettings() }, _initialLoadResult{ settingsLoadedResult } { + // The TerminalPage has to be constructed during our construction, to + // make sure that there's a terminal page for callers of + // SetTitleBarContent _root = winrt::make_self(); _root->WindowProperties(*this); _dialog = ContentDialog{}; @@ -131,10 +134,6 @@ namespace winrt::TerminalApp::implementation // cause you to chase down the rabbit hole of "why is App not // registered?" when it definitely is. - // The TerminalPage has to be constructed during our construction, to - // make sure that there's a terminal page for callers of - // SetTitleBarContent - _isElevated = ::Microsoft::Console::Utils::IsElevated(); } // Method Description: @@ -183,7 +182,25 @@ namespace winrt::TerminalApp::implementation // - True if UWP, false otherwise. bool TerminalWindow::IsUwp() const noexcept { - return _isUwp; + // use C++11 magic statics to make sure we only do this once. + // This won't change over the lifetime of the application + + static const auto isUwp = []() { + // *** THIS IS A SINGLETON *** + auto result = false; + + // GH#2455 - Make sure to try/catch calls to Application::Current, + // because that _won't_ be an instance of TerminalApp::App in the + // LocalTests + try + { + result = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp(); + } + CATCH_LOG(); + return result; + }(); + + return isUwp; } // Method Description: @@ -194,19 +211,25 @@ namespace winrt::TerminalApp::implementation // - True if elevated, false otherwise. bool TerminalWindow::IsElevated() const noexcept { - return _isElevated; - } + // use C++11 magic statics to make sure we only do this once. + // This won't change over the lifetime of the application - // Method Description: - // - Called by UWP context invoker to let us know that we may have to change some of our behaviors - // for being a UWP - // Arguments: - // - (sets to UWP = true, one way change) - // Return Value: - // - - void TerminalWindow::RunAsUwp() - { - _isUwp = true; + static const auto isElevated = []() { + // *** THIS IS A SINGLETON *** + auto result = false; + + // GH#2455 - Make sure to try/catch calls to Application::Current, + // because that _won't_ be an instance of TerminalApp::App in the + // LocalTests + try + { + result = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated(); + } + CATCH_LOG(); + return result; + }(); + + return isElevated; } // Method Description: @@ -222,13 +245,6 @@ namespace winrt::TerminalApp::implementation { _root->DialogPresenter(*this); - // In UWP mode, we cannot handle taking over the title bar for tabs, - // so this setting is overridden to false no matter what the preference is. - if (_isUwp) - { - _settings.GlobalSettings().ShowTabsInTitlebar(false); - } - // Pay attention, that even if some command line arguments were parsed (like launch mode), // we will not use the startup actions from settings. // While this simplifies the logic, we might want to reconsider this behavior in the future. @@ -619,12 +635,10 @@ namespace winrt::TerminalApp::implementation // // We can't do that anymore, because this is now called _before_ // we've initialized XAML for this thread. We can't start XAML till - // we have an HWND, and we can't finish creating the window till we // know how big it should be. // // Instead, we'll just hardcode how big the titlebar should be. If - // the titlebar / tab row ever change size, these numbers will have // to change accordingly. @@ -950,9 +964,7 @@ namespace winrt::TerminalApp::implementation return _root ? _root->AlwaysOnTop() : false; } - //////////////////////////////////////////////////////////////////////////// - - void TerminalWindow::SetSettingsStartupArgs(const std::vector& actions) + void TerminalWindow::SetSettingsStartupArgs(const std::vector& actions) { for (const auto& action : actions) { @@ -1059,8 +1071,6 @@ namespace winrt::TerminalApp::implementation return winrt::to_hstring(_appArgs.GetExitMessage()); } - //////////////////////////////////////////////////////////////////////////// - hstring TerminalWindow::GetWindowLayoutJson(LaunchPosition position) { if (_root != nullptr) @@ -1113,7 +1123,6 @@ namespace winrt::TerminalApp::implementation _root->SetNumberOfOpenWindows(num); } } - //////////////////////////////////////////////////////////////////////////// void TerminalWindow::IdentifyWindow() { @@ -1220,7 +1229,6 @@ namespace winrt::TerminalApp::implementation { UpdateSettings(arg); } - //////////////////////////////////////////////////////////////////////////// bool TerminalWindow::ShouldImmediatelyHandoffToElevated() { diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index b7e6a22ee9e..0e5f83e4934 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -42,8 +42,8 @@ namespace winrt::TerminalApp::implementation STDMETHODIMP Initialize(HWND hwnd); void Create(); + bool IsUwp() const noexcept; - void RunAsUwp(); bool IsElevated() const noexcept; void Quit(); @@ -51,7 +51,7 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); bool HasCommandlineArguments() const noexcept; - // bool HasSettingsStartupActions() const noexcept; + int32_t SetStartupCommandline(array_view actions); int32_t ExecuteCommandline(array_view actions, const winrt::hstring& cwd); void SetSettingsStartupArgs(const std::vector& actions); @@ -136,8 +136,6 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Settings::Model::Theme); private: - bool _isUwp{ false }; - bool _isElevated{ false }; // If you add controls here, but forget to null them either here or in // the ctor, you're going to have a bad time. It'll mysteriously fail to // activate the AppLogic. diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index 5bcb0294982..8474fc33908 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -51,8 +51,6 @@ namespace TerminalApp // registered?" when it definitely is. void Create(); - Boolean IsUwp(); - void RunAsUwp(); Boolean IsElevated(); Boolean HasCommandlineArguments(); From 0199aba286534cd871bfe1c15a32dcf2e785627f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 28 Feb 2023 11:29:53 -0600 Subject: [PATCH 14/25] the last nits, I think --- src/cascadia/WindowsTerminal/AppHost.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index c22fc0d115a..a82963b6a67 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -389,7 +389,7 @@ void AppHost::Initialize() _revokers.ChangeMaximizeRequested = _windowLogic.ChangeMaximizeRequested(winrt::auto_revoke, { this, &AppHost::_ChangeMaximizeRequested }); _window->MaximizeChanged([this](bool newMaximize) { - if (_appLogic) + if (_windowLogic) { _windowLogic.Maximized(newMaximize); } @@ -814,14 +814,14 @@ void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&, // - void AppHost::_WindowMouseWheeled(const til::point coord, const int32_t delta) { - if (_appLogic) + if (_windowLogic) { // Find all the elements that are underneath the mouse - auto elems = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::FindElementsInHostCoordinates(coord.to_winrt_point(), _windowLogic.GetRoot()); + auto elems = Xaml::Media::VisualTreeHelper::FindElementsInHostCoordinates(coord.to_winrt_point(), _windowLogic.GetRoot()); for (const auto& e : elems) { // If that element has implemented IMouseWheelListener, call OnMouseWheel on that element. - if (auto control{ e.try_as() }) + if (auto control{ e.try_as() }) { try { From ada3f427a07c8cae676ce4f516243873512490c7 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 28 Feb 2023 11:56:37 -0600 Subject: [PATCH 15/25] well that is an underwhelming codeformat commit. Thanks VS --- src/cascadia/TerminalApp/TerminalWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index b1c0c4142ff..3215a1f546f 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -133,7 +133,6 @@ namespace winrt::TerminalApp::implementation // then it might look like App just failed to activate, which will // cause you to chase down the rabbit hole of "why is App not // registered?" when it definitely is. - } // Method Description: From 6dead9934156f687702afae776819ebcec92a4b9 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Mar 2023 12:29:23 -0600 Subject: [PATCH 16/25] PR nits --- src/cascadia/TerminalApp/AppLogic.cpp | 7 ++++--- src/cascadia/TerminalApp/SettingsLoadEventArgs.h | 12 ++++++------ src/cascadia/TerminalApp/TerminalPage.cpp | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 9af911a7813..1fbac932e68 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -200,7 +200,8 @@ namespace winrt::TerminalApp::implementation // These used to be in `TerminalPage::Initialized`, so that they started // _after_ the Terminal window was started and displayed. These could - // theoretically move there again too. + // theoretically move there again too. TODO:GH#14957 - evaluate moving + // this after the Page is initialized { // Both LoadSettings and ReloadSettings are supposed to call this function, // but LoadSettings skips it, so that the UI starts up faster. @@ -232,7 +233,7 @@ namespace winrt::TerminalApp::implementation TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } - }; + } _ApplyLanguageSettingChange(); _ApplyStartupTaskStateChange(); @@ -298,7 +299,7 @@ namespace winrt::TerminalApp::implementation _settings = std::move(newSettings); - hr = (_warnings.size()) == 0u ? S_OK : S_FALSE; + hr = _warnings.empty() ? S_OK : S_FALSE; } catch (const winrt::hresult_error& e) { diff --git a/src/cascadia/TerminalApp/SettingsLoadEventArgs.h b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h index e31dd5e2a91..e094810784b 100644 --- a/src/cascadia/TerminalApp/SettingsLoadEventArgs.h +++ b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h @@ -18,13 +18,13 @@ namespace winrt::TerminalApp::implementation public: SettingsLoadEventArgs(bool reload, uint64_t result, - const winrt::hstring& exceptionText, - const winrt::Windows::Foundation::Collections::IVector& warnings, - const Microsoft::Terminal::Settings::Model::CascadiaSettings& newSettings) : + winrt::hstring exceptionText, + winrt::Windows::Foundation::Collections::IVector warnings, + Microsoft::Terminal::Settings::Model::CascadiaSettings newSettings) : _Reload{ reload }, _Result{ result }, - _ExceptionText{ exceptionText }, - _Warnings{ warnings }, - _NewSettings{ newSettings } {}; + _ExceptionText{ std::move(exceptionText) }, + _Warnings{ std::move(warnings) }, + _NewSettings{ std::move(newSettings) } {}; }; } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 9361f533460..fd299249ab3 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1799,7 +1799,7 @@ namespace winrt::TerminalApp::implementation } // If the user set a custom name, save it - if (const auto windowName = _WindowProperties.WindowName(); !windowName.empty()) + if (const auto& windowName{ _WindowProperties.WindowName() }; !windowName.empty()) { ActionAndArgs action; action.Action(ShortcutAction::RenameWindow); @@ -3927,7 +3927,7 @@ namespace winrt::TerminalApp::implementation else if (key == Windows::System::VirtualKey::Escape) { // User wants to discard the changes they made - WindowRenamerTextBox().Text(WindowProperties().WindowName()); + WindowRenamerTextBox().Text(_WindowProperties.WindowName()); WindowRenamer().IsOpen(false); _renamerPressedEnter = false; } From 434abc247403a15e3395c0d7473633663805f9f1 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Mar 2023 12:52:28 -0600 Subject: [PATCH 17/25] Remove the need for TerminalPage to know the number of open windows This removes all the weirdness around the way that TerminalPage needs to track the number of open windows. Instead of TerminalPage persisting empty state when the last tab closes, it lets the AppHost know that the last tab was closed due to _closing the tab_ (as opposed to closing the window / quitting). This gives AppHost an opportunity to persist empty state for that case, because _it_ knows how many windows there are. This could basically be its own PR. Probably worth xlinking this commit to #9800 --- src/cascadia/TerminalApp/TabManagement.cpp | 8 +------ src/cascadia/TerminalApp/TerminalPage.cpp | 9 +------- src/cascadia/TerminalApp/TerminalPage.h | 13 ++++++++--- src/cascadia/TerminalApp/TerminalPage.idl | 6 ++++- src/cascadia/TerminalApp/TerminalWindow.cpp | 22 +++++++++--------- src/cascadia/TerminalApp/TerminalWindow.h | 4 ++-- src/cascadia/TerminalApp/TerminalWindow.idl | 5 +++-- src/cascadia/WindowsTerminal/AppHost.cpp | 25 ++++++++++++--------- 8 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index f1134efbd36..511c4925fe9 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -523,13 +523,7 @@ namespace winrt::TerminalApp::implementation // if the user manually closed all tabs. // Do this only if we are the last window; the monarch will notice // we are missing and remove us that way otherwise. - if (!_maintainStateOnTabClose && _settings.GlobalSettings().ShouldUsePersistedLayout() && _numOpenWindows == 1) - { - auto state = ApplicationState::SharedInstance(); - state.PersistedWindowLayouts(nullptr); - } - - _LastTabClosedHandlers(*this, nullptr); + _LastTabClosedHandlers(*this, winrt::make(!_maintainStateOnTabClose)); } 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 fd299249ab3..87cf3df5e90 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -5,6 +5,7 @@ #include "pch.h" #include "TerminalPage.h" #include "TerminalPage.g.cpp" +#include "LastTabClosedEventArgs.g.cpp" #include "RenameWindowRequestedArgs.g.cpp" #include @@ -3808,14 +3809,6 @@ namespace winrt::TerminalApp::implementation } } - void TerminalPage::SetNumberOfOpenWindows(const uint64_t num) - { - // This is used in TerminalPage::_RemoveTab, when we close a tab. If we - // close the last tab, and there's only one window open, then we will - // call to persist _no_ state. - _numOpenWindows = num; - } - // Method Description: // - Called when an attempt to rename the window has failed. This will open // the toast displaying a message to the user that the attempt to rename diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index c82a365c4b8..f01034a2a4f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -7,6 +7,7 @@ #include "TerminalTab.h" #include "AppKeyBindings.h" #include "AppCommandlineArgs.h" +#include "LastTabClosedEventArgs.g.h" #include "RenameWindowRequestedArgs.g.h" #include "Toast.h" @@ -41,6 +42,15 @@ namespace winrt::TerminalApp::implementation ScrollDown = 1 }; + struct LastTabClosedEventArgs : LastTabClosedEventArgsT + { + WINRT_PROPERTY(bool, ClearPersistedState); + + public: + LastTabClosedEventArgs(const bool& shouldClear) : + _ClearPersistedState{ shouldClear } {}; + }; + struct RenameWindowRequestedArgs : RenameWindowRequestedArgsT { WINRT_PROPERTY(winrt::hstring, ProposedName); @@ -120,8 +130,6 @@ namespace winrt::TerminalApp::implementation winrt::hstring WindowIdForDisplay() const noexcept { return _WindowProperties.WindowIdForDisplay(); }; winrt::hstring WindowNameForDisplay() const noexcept { return _WindowProperties.WindowNameForDisplay(); }; - void SetNumberOfOpenWindows(const uint64_t value); - bool IsElevated() const noexcept; void OpenSettingsUI(); @@ -188,7 +196,6 @@ namespace winrt::TerminalApp::implementation bool _isAlwaysOnTop{ false }; std::optional _loadFromPersistedLayoutIdx{}; - uint64_t _numOpenWindows{ 0 }; bool _maintainStateOnTabClose{ false }; bool _rearranging{ false }; diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index b363e183355..a753da2a2b7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -5,7 +5,11 @@ import "IDirectKeyListener.idl"; namespace TerminalApp { - delegate void LastTabClosedEventArgs(); + + [default_interface] runtimeclass LastTabClosedEventArgs + { + Boolean ClearPersistedState { get; }; + }; [default_interface] runtimeclass RenameWindowRequestedArgs { diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 3215a1f546f..258612dfd00 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -882,13 +882,13 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalWindow::CloseWindow(LaunchPosition pos) + void TerminalWindow::CloseWindow(LaunchPosition pos, const bool isLastWindow) { if (_root) { // If persisted layout is enabled and we are the last window closing // we should save our state. - if (_settings.GlobalSettings().ShouldUsePersistedLayout() && _numOpenWindows == 1) + if (_settings.GlobalSettings().ShouldUsePersistedLayout() && isLastWindow) { if (const auto layout = _root->GetWindowLayout()) { @@ -902,6 +902,15 @@ namespace winrt::TerminalApp::implementation } } + void TerminalWindow::ClearPersistedWindowState() + { + if (_settings.GlobalSettings().ShouldUsePersistedLayout()) + { + auto state = ApplicationState::SharedInstance(); + state.PersistedWindowLayouts(nullptr); + } + } + winrt::TerminalApp::TaskbarState TerminalWindow::TaskbarState() { if (_root) @@ -1114,15 +1123,6 @@ namespace winrt::TerminalApp::implementation return nullptr; } - void TerminalWindow::SetNumberOfOpenWindows(const uint64_t num) - { - _numOpenWindows = num; - if (_root) - { - _root->SetNumberOfOpenWindows(num); - } - } - void TerminalWindow::IdentifyWindow() { if (_root) diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 0e5f83e4934..753cd0e2e81 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -77,6 +77,7 @@ namespace winrt::TerminalApp::implementation void SetPersistedLayoutIdx(const uint32_t idx); void SetNumberOfOpenWindows(const uint64_t num); bool ShouldUsePersistedLayout() const; + void ClearPersistedWindowState(); void RequestExitFullscreen(); @@ -95,7 +96,7 @@ namespace winrt::TerminalApp::implementation void TitlebarClicked(); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); - void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position); + void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position, const bool isLastWindow); void WindowVisibilityChanged(const bool showOrHide); winrt::TerminalApp::TaskbarState TaskbarState(); @@ -153,7 +154,6 @@ namespace winrt::TerminalApp::implementation winrt::hstring _WindowName{}; uint64_t _WindowId{ 0 }; - uint64_t _numOpenWindows{ 1 }; std::optional _loadFromPersistedLayoutIdx{}; Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index 8474fc33908..a4567734121 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -77,7 +77,8 @@ namespace TerminalApp void IdentifyWindow(); void SetPersistedLayoutIdx(UInt32 idx); - void SetNumberOfOpenWindows(UInt64 num); + void ClearPersistedWindowState(); + void RenameFailed(); void RequestExitFullscreen(); @@ -91,7 +92,7 @@ namespace TerminalApp Boolean GetInitialAlwaysOnTop(); Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension); void TitlebarClicked(); - void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position); + void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position, Boolean isLastWindow); void WindowVisibilityChanged(Boolean showOrHide); TaskbarState TaskbarState{ get; }; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index a82963b6a67..0d41492ee40 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -316,7 +316,6 @@ void AppHost::_HandleCommandlineArgs() )); } } - _windowLogic.SetNumberOfOpenWindows(numPeasants); } _windowLogic.WindowName(peasant.WindowName()); _windowLogic.WindowId(peasant.GetID()); @@ -505,7 +504,7 @@ void AppHost::AppTitleChanged(const winrt::Windows::Foundation::IInspectable& /* // - LastTabClosedEventArgs: unused // Return Value: // - -void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::TerminalApp::LastTabClosedEventArgs& /*args*/) +void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::TerminalApp::LastTabClosedEventArgs& args) { if (_windowManager.IsMonarch() && _notificationIcon) { @@ -524,11 +523,22 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se _windowManager.WindowCreated(_WindowCreatedToken); _windowManager.WindowClosed(_WindowClosedToken); + // If the user closes the last tab, in the last window, _by closing the tab_ + // (not by closing the whole window), we need to manually persist an empty + // window state here. That will cause the terminal to re-open with the usual + // settings (not the persisted state) + if (args.ClearPersistedState() && + _windowManager.GetNumberOfPeasants() == 1) + { + _windowLogic.ClearPersistedWindowState(); + } + // Remove ourself from the list of peasants so that we aren't included in // any future requests. This will also mean we block until any existing // event handler finishes. _windowManager.SignalClose(); + _window->Close(); } @@ -973,22 +983,16 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s _CreateNotificationIcon(); } - // 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. - _windowLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); - _WindowCreatedToken = _windowManager.WindowCreated([this](auto&&, auto&&) { if (_getWindowLayoutThrottler) { _getWindowLayoutThrottler.value()(); - } - _windowLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); + } }); _WindowClosedToken = _windowManager.WindowClosed([this](auto&&, auto&&) { if (_getWindowLayoutThrottler) { _getWindowLayoutThrottler.value()(); } - _windowLogic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); // These events are coming from peasants that become or un-become quake windows. @@ -1651,7 +1655,8 @@ void AppHost::_CloseRequested(const winrt::Windows::Foundation::IInspectable& /* const winrt::Windows::Foundation::IInspectable& /*args*/) { const auto pos = _GetWindowLaunchPosition(); - _windowLogic.CloseWindow(pos); + const bool isLastWindow = _windowManager.GetNumberOfPeasants() == 1; + _windowLogic.CloseWindow(pos, isLastWindow); } void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, From 1b59eb9238a419724e43b454c289630452965c62 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Mar 2023 13:50:13 -0600 Subject: [PATCH 18/25] save this for later, we should only need ot look it up once --- src/cascadia/TerminalApp/TerminalWindow.cpp | 38 ++++++++++++++------- src/cascadia/TerminalApp/TerminalWindow.h | 3 +- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 258612dfd00..e62d4c1ec30 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -141,21 +141,19 @@ namespace winrt::TerminalApp::implementation { // Pass commandline args into the TerminalPage. If we were supposed to // load from a persisted layout, do that instead. - auto foundLayout = false; + + // layout will only ever be non-null if there were >0 tabs persisted in + // .TabLayout(). We can re-evaluate that as a part of TODO: GH#12633 if (const auto& layout = LoadPersistedLayout()) { - if (layout.TabLayout().Size() > 0) + std::vector actions; + for (const auto& a : layout.TabLayout()) { - std::vector actions; - for (const auto& a : layout.TabLayout()) - { - actions.emplace_back(a); - } - _root->SetStartupActions(actions); - foundLayout = true; + actions.emplace_back(a); } + _root->SetStartupActions(actions); } - if (!foundLayout) + else { _root->SetStartupActions(_appArgs.GetStartupActions()); } @@ -1095,6 +1093,7 @@ namespace winrt::TerminalApp::implementation void TerminalWindow::SetPersistedLayoutIdx(const uint32_t idx) { _loadFromPersistedLayoutIdx = idx; + _cachedLayout = std::nullopt; } // Method Description; @@ -1109,18 +1108,31 @@ namespace winrt::TerminalApp::implementation return _settings.GlobalSettings().ShouldUsePersistedLayout() ? _loadFromPersistedLayoutIdx : std::nullopt; } - WindowLayout TerminalWindow::LoadPersistedLayout() const + WindowLayout TerminalWindow::LoadPersistedLayout() { + if (_cachedLayout.has_value()) + { + return *_cachedLayout; + } + if (const auto idx = LoadPersistedLayoutIdx()) { const auto i = idx.value(); const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); if (layouts && layouts.Size() > i) { - return layouts.GetAt(i); + auto layout = layouts.GetAt(i); + + // TODO: GH#12633: Right now, we're manually making sure that we + // have at least one tab to restore. If we ever want to come + // back and make it so that you can persist position and size, + // but not the tabs themselves, we can revisit this assumption. + _cachedLayout = (layout.TabLayout().Size() > 0) ? layout : nullptr; + return *_cachedLayout; } } - return nullptr; + _cachedLayout = nullptr; + return *_cachedLayout; } void TerminalWindow::IdentifyWindow() diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 753cd0e2e81..4a83fdf4c11 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -72,7 +72,7 @@ namespace winrt::TerminalApp::implementation void RenameFailed(); std::optional LoadPersistedLayoutIdx() const; - winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout() const; + winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout(); void SetPersistedLayoutIdx(const uint32_t idx); void SetNumberOfOpenWindows(const uint64_t num); @@ -155,6 +155,7 @@ namespace winrt::TerminalApp::implementation uint64_t _WindowId{ 0 }; std::optional _loadFromPersistedLayoutIdx{}; + std::optional _cachedLayout{ std::nullopt }; Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; TerminalApp::SettingsLoadEventArgs _initialLoadResult{ nullptr }; From 39a94503fc01d29e6893875e5a875ddf61e525ca Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Mar 2023 13:51:43 -0600 Subject: [PATCH 19/25] to wit, also make sure that there is a tablayout --- src/cascadia/TerminalApp/TerminalWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index e62d4c1ec30..f307ca66a1e 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -1127,7 +1127,7 @@ namespace winrt::TerminalApp::implementation // have at least one tab to restore. If we ever want to come // back and make it so that you can persist position and size, // but not the tabs themselves, we can revisit this assumption. - _cachedLayout = (layout.TabLayout().Size() > 0) ? layout : nullptr; + _cachedLayout = (layout.TabLayout() && layout.TabLayout().Size() > 0) ? layout : nullptr; return *_cachedLayout; } } From 44b238e777fe2114d170791bdb797227c83bef5a Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Mar 2023 16:55:05 -0600 Subject: [PATCH 20/25] Redo how ownership of WindowProperties works. This is cleaner by a good amount --- src/cascadia/TerminalApp/TerminalPage.cpp | 29 ++-- src/cascadia/TerminalApp/TerminalPage.h | 15 +- src/cascadia/TerminalApp/TerminalPage.idl | 12 +- src/cascadia/TerminalApp/TerminalPage.xaml | 8 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 146 ++++++++++---------- src/cascadia/TerminalApp/TerminalWindow.h | 34 +++-- src/cascadia/TerminalApp/TerminalWindow.idl | 8 +- 7 files changed, 130 insertions(+), 122 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 87cf3df5e90..95b3f7834e5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -52,13 +52,16 @@ namespace winrt namespace winrt::TerminalApp::implementation { - TerminalPage::TerminalPage() : + TerminalPage::TerminalPage(TerminalApp::WindowProperties properties) : _tabs{ winrt::single_threaded_observable_vector() }, _mruTabs{ winrt::single_threaded_observable_vector() }, _startupActions{ winrt::single_threaded_vector() }, - _hostingHwnd{} + _hostingHwnd{}, + _WindowProperties{ properties } { InitializeComponent(); + + _WindowProperties.PropertyChanged({ get_weak(), &TerminalPage::_windowPropertyChanged }); } // Method Description: @@ -4302,27 +4305,21 @@ namespace winrt::TerminalApp::implementation _updateThemeColors(); } - TerminalApp::IWindowProperties TerminalPage::WindowProperties() - { - return _WindowProperties; - } - void TerminalPage::WindowProperties(const TerminalApp::IWindowProperties& props) - { - _WindowProperties = props; - } - - winrt::fire_and_forget TerminalPage::WindowNameChanged() + // Handler for our WindowProperties's PropertyChanged event. We'll use this + // to pop the "Identify Window" toast when the user renames our window. + winrt::fire_and_forget TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/, + const WUX::Data::PropertyChangedEventArgs& args) { + if (args.PropertyName() != L"WindowName") + { + co_return; + } auto weakThis{ get_weak() }; // On the foreground thread, raise property changed notifications, and // display the success toast. co_await wil::resume_foreground(Dispatcher()); if (auto page{ weakThis.get() }) { - _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowName" }); - _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowNameForDisplay" }); - _PropertyChangedHandlers(*this, WUX::Data::PropertyChangedEventArgs{ L"WindowIdForDisplay" }); - // DON'T display the confirmation if this is the name we were // given on startup! if (page->_startupState == StartupState::Initialized) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index f01034a2a4f..a99854374ca 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -63,7 +63,7 @@ namespace winrt::TerminalApp::implementation struct TerminalPage : TerminalPageT { public: - TerminalPage(); + TerminalPage(TerminalApp::WindowProperties properties); // This implements shobjidl's IInitializeWithWindow, but due to a XAML Compiler bug we cannot // put it in our inheritance graph. https://github.com/microsoft/microsoft-ui-xaml/issues/3331 @@ -124,11 +124,7 @@ namespace winrt::TerminalApp::implementation const bool initial, const winrt::hstring cwd = L""); - // For the sake of XAML binding: - winrt::hstring WindowName() const noexcept { return _WindowProperties.WindowName(); }; - uint64_t WindowId() const noexcept { return _WindowProperties.WindowId(); }; - winrt::hstring WindowIdForDisplay() const noexcept { return _WindowProperties.WindowIdForDisplay(); }; - winrt::hstring WindowNameForDisplay() const noexcept { return _WindowProperties.WindowNameForDisplay(); }; + TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; }; bool IsElevated() const noexcept; @@ -137,10 +133,6 @@ namespace winrt::TerminalApp::implementation bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); - TerminalApp::IWindowProperties WindowProperties(); - void WindowProperties(const TerminalApp::IWindowProperties& props); - winrt::fire_and_forget WindowNameChanged(); - WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); // -------------------------------- WinRT Events --------------------------------- @@ -230,7 +222,7 @@ namespace winrt::TerminalApp::implementation int _renamerLayoutCount{ 0 }; bool _renamerPressedEnter{ false }; - TerminalApp::IWindowProperties _WindowProperties{ nullptr }; + TerminalApp::WindowProperties _WindowProperties{ nullptr }; winrt::Windows::Foundation::IAsyncOperation _ShowDialogHelper(const std::wstring_view& name); @@ -463,6 +455,7 @@ namespace winrt::TerminalApp::implementation void _updateTabCloseButton(const winrt::Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem); winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args); + winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index a753da2a2b7..2fc333afe12 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -21,18 +21,19 @@ namespace TerminalApp Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); }; - interface IWindowProperties + [default_interface] runtimeclass WindowProperties : Windows.UI.Xaml.Data.INotifyPropertyChanged { String WindowName { get; }; UInt64 WindowId { get; }; String WindowNameForDisplay { get; }; String WindowIdForDisplay { get; }; + Boolean IsQuakeWindow(); }; [default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener { - TerminalPage(); + TerminalPage(WindowProperties properties); // XAML bound properties String ApplicationDisplayName { get; }; @@ -42,7 +43,7 @@ namespace TerminalApp Boolean Fullscreen { get; }; Boolean AlwaysOnTop { get; }; - IWindowProperties WindowProperties { get; }; + WindowProperties WindowProperties { get; }; void IdentifyWindow(); void RenameFailed(); @@ -58,11 +59,6 @@ namespace TerminalApp Windows.UI.Xaml.Media.Brush TitlebarBrush { get; }; void WindowActivated(Boolean activated); - String WindowName { get; }; - UInt64 WindowId { get; }; - String WindowNameForDisplay { get; }; - String WindowIdForDisplay { get; }; - event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; event Windows.Foundation.TypedEventHandler SetTitleBarContent; diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index 3434cd868ee..38d7c0f69f7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -201,10 +201,10 @@ tracked by MUX#4382 --> + Subtitle="{x:Bind WindowProperties.WindowNameForDisplay, Mode=OneWay}" /> + Text="{x:Bind WindowProperties.WindowName, Mode=OneWay}" /> diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index f307ca66a1e..eb0ef7c78b9 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -6,6 +6,7 @@ #include "../inc/WindowingBehavior.h" #include "TerminalWindow.g.cpp" #include "SettingsLoadEventArgs.g.cpp" +#include "WindowProperties.g.cpp" #include #include @@ -119,13 +120,13 @@ namespace winrt::TerminalApp::implementation { TerminalWindow::TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult) : _settings{ settingsLoadedResult.NewSettings() }, - _initialLoadResult{ settingsLoadedResult } + _initialLoadResult{ settingsLoadedResult }, + _WindowProperties{ winrt::make_self() } { // The TerminalPage has to be constructed during our construction, to // make sure that there's a terminal page for callers of // SetTitleBarContent - _root = winrt::make_self(); - _root->WindowProperties(*this); + _root = winrt::make_self(*_WindowProperties); _dialog = ContentDialog{}; // For your own sanity, it's better to do setup outside the ctor. @@ -258,7 +259,7 @@ namespace winrt::TerminalApp::implementation // that the window size is _first_ set up as something sensible, so // leaving fullscreen returns to a reasonable size. const auto launchMode = this->GetLaunchMode(); - if (IsQuakeWindow() || WI_IsFlagSet(launchMode, LaunchMode::FocusMode)) + if (_WindowProperties->IsQuakeWindow() || WI_IsFlagSet(launchMode, LaunchMode::FocusMode)) { _root->SetFocusMode(true); } @@ -270,7 +271,7 @@ namespace winrt::TerminalApp::implementation _root->Maximized(true); } - if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !IsQuakeWindow()) + if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !_WindowProperties->IsQuakeWindow()) { _root->SetFullscreen(true); } @@ -1151,78 +1152,23 @@ namespace winrt::TerminalApp::implementation } } - winrt::hstring TerminalWindow::WindowName() const noexcept - { - return _WindowName; - } void TerminalWindow::WindowName(const winrt::hstring& name) { - const auto oldIsQuakeMode = IsQuakeWindow(); - - const auto changed = _WindowName != name; - if (changed) + const auto oldIsQuakeMode = _WindowProperties->IsQuakeWindow(); + _WindowProperties->WindowName(name); + const auto newIsQuakeMode = _WindowProperties->IsQuakeWindow(); + if (newIsQuakeMode != oldIsQuakeMode) { - _WindowName = name; - if (_root) - { - _root->WindowNameChanged(); - - // If we're entering quake mode, or leaving it - if (IsQuakeWindow() != oldIsQuakeMode) - { - // If we're entering Quake Mode from ~Focus Mode, then this will enter Focus Mode - // If we're entering Quake Mode from Focus Mode, then this will do nothing - // If we're leaving Quake Mode (we're already in Focus Mode), then this will do nothing - _root->SetFocusMode(true); - _IsQuakeWindowChangedHandlers(*this, nullptr); - } - } + // If we're entering Quake Mode from ~Focus Mode, then this will enter Focus Mode + // If we're entering Quake Mode from Focus Mode, then this will do nothing + // If we're leaving Quake Mode (we're already in Focus Mode), then this will do nothing + _root->SetFocusMode(true); + _IsQuakeWindowChangedHandlers(*this, nullptr); } } - uint64_t TerminalWindow::WindowId() const noexcept - { - return _WindowId; - } void TerminalWindow::WindowId(const uint64_t& id) { - if (_WindowId != id) - { - _WindowId = id; - if (_root) - { - _root->WindowNameChanged(); - } - } - } - - // Method Description: - // - Returns a label like "Window: 1234" for the ID of this window - // Arguments: - // - - // Return Value: - // - a string for displaying the name of the window. - winrt::hstring TerminalWindow::WindowIdForDisplay() const noexcept - { - return winrt::hstring{ fmt::format(L"{}: {}", - std::wstring_view(RS_(L"WindowIdLabel")), - _WindowId) }; - } - - // Method Description: - // - Returns a label like "" when the window has no name, or the name of the window. - // Arguments: - // - - // Return Value: - // - a string for displaying the name of the window. - winrt::hstring TerminalWindow::WindowNameForDisplay() const noexcept - { - return _WindowName.empty() ? - winrt::hstring{ fmt::format(L"<{}>", RS_(L"UnnamedWindowName")) } : - _WindowName; - } - bool TerminalWindow::IsQuakeWindow() const noexcept - { - return _WindowName == QuakeWindowName; + _WindowProperties->WindowId(id); } void TerminalWindow::RequestExitFullscreen() @@ -1271,4 +1217,64 @@ namespace winrt::TerminalApp::implementation } } + winrt::hstring WindowProperties::WindowName() const noexcept + { + return _WindowName; + } + + void WindowProperties::WindowName(const winrt::hstring& value) + { + if (_WindowName != value) + { + _WindowName = value; + _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowName" }); + _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowNameForDisplay" }); + } + } + uint64_t WindowProperties::WindowId() const noexcept + { + return _WindowId; + } + + void WindowProperties::WindowId(const uint64_t& value) + { + if (_WindowId != value) + { + _WindowId = value; + _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowId" }); + _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowIdForDisplay" }); + } + } + + // Method Description: + // - Returns a label like "Window: 1234" for the ID of this window + // Arguments: + // - + // Return Value: + // - a string for displaying the name of the window. + winrt::hstring WindowProperties::WindowIdForDisplay() const noexcept + { + return winrt::hstring{ fmt::format(L"{}: {}", + std::wstring_view(RS_(L"WindowIdLabel")), + _WindowId) }; + } + + // Method Description: + // - Returns a label like "" when the window has no name, or the name of the window. + // Arguments: + // - + // Return Value: + // - a string for displaying the name of the window. + winrt::hstring WindowProperties::WindowNameForDisplay() const noexcept + { + return _WindowName.empty() ? + winrt::hstring{ fmt::format(L"<{}>", RS_(L"UnnamedWindowName")) } : + _WindowName; + } + + bool WindowProperties::IsQuakeWindow() const noexcept + { + return _WindowName == QuakeWindowName; + } + }; diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 4a83fdf4c11..1a446010761 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -5,6 +5,7 @@ #include "TerminalWindow.g.h" #include "SystemMenuChangeArgs.g.h" +#include "WindowProperties.g.h" #include "SettingsLoadEventArgs.h" #include "TerminalPage.h" @@ -33,6 +34,26 @@ namespace winrt::TerminalApp::implementation _Name{ name }, _Action{ action }, _Handler{ handler } {}; }; + struct WindowProperties : WindowPropertiesT + { + // Normally, WindowName and WindowId would be + // WINRT_OBSERVABLE_PROPERTY's, but we want them to raise + // WindowNameForDisplay and WindowIdForDisplay instead + winrt::hstring WindowName() const noexcept; + void WindowName(const winrt::hstring& value); + uint64_t WindowId() const noexcept; + void WindowId(const uint64_t& value); + winrt::hstring WindowIdForDisplay() const noexcept; + winrt::hstring WindowNameForDisplay() const noexcept; + bool IsQuakeWindow() const noexcept; + + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); + + private: + winrt::hstring _WindowName{}; + uint64_t _WindowId{ 0 }; + }; + struct TerminalWindow : TerminalWindowT { public: @@ -113,16 +134,10 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::Theme Theme(); void UpdateSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::SettingsLoadEventArgs& arg); - // Normally, WindowName and WindowId would be - // WINRT_OBSERVABLE_PROPERTY's, but we want them to raise - // WindowNameForDisplay and WindowIdForDisplay instead - winrt::hstring WindowName() const noexcept; void WindowName(const winrt::hstring& value); - uint64_t WindowId() const noexcept; void WindowId(const uint64_t& value); - winrt::hstring WindowIdForDisplay() const noexcept; - winrt::hstring WindowNameForDisplay() const noexcept; - bool IsQuakeWindow() const noexcept; + bool IsQuakeWindow() const noexcept { return _WindowProperties->IsQuakeWindow(); } + TerminalApp::WindowProperties WindowProperties() { return *_WindowProperties; } // -------------------------------- WinRT Events --------------------------------- // PropertyChanged is surprisingly not a typed event, so we'll define that one manually. @@ -151,8 +166,7 @@ namespace winrt::TerminalApp::implementation bool _gotSettingsStartupActions{ false }; std::vector _settingsStartupArgs{}; - winrt::hstring _WindowName{}; - uint64_t _WindowId{ 0 }; + winrt::com_ptr _WindowProperties{ nullptr }; std::optional _loadFromPersistedLayoutIdx{}; std::optional _cachedLayout{ std::nullopt }; diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index a4567734121..a945f88e007 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -40,7 +40,7 @@ namespace TerminalApp // See IDialogPresenter and TerminalPage's DialogPresenter for more // information. - [default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, IWindowProperties, Windows.UI.Xaml.Data.INotifyPropertyChanged + [default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged { TerminalWindow(SettingsLoadEventArgs result); @@ -107,8 +107,10 @@ namespace TerminalApp // These already have accessors as a part of IWindowProperties, but we // also want to be able to set them. - String WindowName { set; }; - UInt64 WindowId { set; }; + WindowProperties WindowProperties { get; }; + void WindowName(String name); + void WindowId(UInt64 id); + Boolean IsQuakeWindow(); // See IDialogPresenter and TerminalPage's DialogPresenter for more // information. From 073350e66fb9d8319d59c72ba76e8861ef96fb32 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 10 Mar 2023 09:02:46 -0600 Subject: [PATCH 21/25] fix the tests too --- .../LocalTests_TerminalApp/TabTests.cpp | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index c1edac3c313..b5fa445ab77 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "../TerminalApp/TerminalPage.h" +#include "../TerminalApp/TerminalWindow.h" #include "../TerminalApp/MinMaxCloseControl.h" #include "../TerminalApp/TabRowControl.h" #include "../TerminalApp/ShortcutActionDispatch.h" @@ -42,17 +43,6 @@ namespace TerminalAppLocalTests // an updated TAEF that will let us install framework packages when the test // package is deployed. Until then, these tests won't deploy in CI. - struct WindowProperties : winrt::implements - { - WINRT_PROPERTY(winrt::hstring, WindowName); - WINRT_PROPERTY(uint64_t, WindowId); - WINRT_PROPERTY(winrt::hstring, WindowNameForDisplay); - WINRT_PROPERTY(winrt::hstring, WindowIdForDisplay); - - public: - bool IsQuakeWindow() { return _WindowName == L"_quake"; }; - }; - class TabTests { // For this set of tests, we need to activate some XAML content. For @@ -121,7 +111,7 @@ namespace TerminalAppLocalTests void _initializeTerminalPage(winrt::com_ptr& page, CascadiaSettings initialSettings); winrt::com_ptr _commonSetup(); - winrt::com_ptr _windowProperties; + winrt::com_ptr _windowProperties; }; template @@ -206,8 +196,11 @@ namespace TerminalAppLocalTests { winrt::com_ptr page{ nullptr }; - auto result = RunOnUIThread([&page]() { - page = winrt::make_self(); + _windowProperties = winrt::make_self(); + winrt::TerminalApp::WindowProperties props = *_windowProperties; + + auto result = RunOnUIThread([&page, props]() { + page = winrt::make_self(props); VERIFY_IS_NOT_NULL(page); }); VERIFY_SUCCEEDED(result); @@ -251,15 +244,13 @@ namespace TerminalAppLocalTests // it's weird. winrt::TerminalApp::TerminalPage projectedPage{ nullptr }; - _windowProperties = winrt::make_self(); - winrt::TerminalApp::IWindowProperties iProps{ *_windowProperties }; - + _windowProperties = winrt::make_self(); + winrt::TerminalApp::WindowProperties props = *_windowProperties; Log::Comment(NoThrowString().Format(L"Construct the TerminalPage")); - auto result = RunOnUIThread([&projectedPage, &page, initialSettings, iProps]() { - projectedPage = winrt::TerminalApp::TerminalPage(); + auto result = RunOnUIThread([&projectedPage, &page, initialSettings, props]() { + projectedPage = winrt::TerminalApp::TerminalPage(props); page.copy_from(winrt::get_self(projectedPage)); page->_settings = initialSettings; - page->WindowProperties(iProps); }); VERIFY_SUCCEEDED(result); @@ -1264,11 +1255,10 @@ namespace TerminalAppLocalTests // // This replicates how TerminalWindow works _windowProperties->WindowName(args.ProposedName()); - page->WindowNameChanged(); }); auto windowNameChanged = false; - page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable { + _windowProperties->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable { if (args.PropertyName() == L"WindowNameForDisplay") { windowNameChanged = true; @@ -1279,7 +1269,7 @@ namespace TerminalAppLocalTests page->_RequestWindowRename(winrt::hstring{ L"Foo" }); }); TestOnUIThread([&]() { - VERIFY_ARE_EQUAL(L"Foo", page->WindowName()); + VERIFY_ARE_EQUAL(L"Foo", page->WindowProperties().WindowName()); VERIFY_IS_TRUE(windowNameChanged, L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed"); }); From ca511c9cc9f4d2f365cafdab08a51a88602fdd80 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 10 Mar 2023 09:04:05 -0600 Subject: [PATCH 22/25] this is why I don't use VS --- src/cascadia/WindowsTerminal/AppHost.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 0d41492ee40..bd77664f685 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -538,7 +538,6 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se // event handler finishes. _windowManager.SignalClose(); - _window->Close(); } From f70775aca0ee0fee931b4350769b6bebbd6a1d66 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 10 Mar 2023 10:04:54 -0600 Subject: [PATCH 23/25] I don't trust you phyllis --- src/cascadia/TerminalApp/AppLogic.cpp | 29 ++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 1fbac932e68..c8677eb7f82 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -32,8 +32,12 @@ namespace winrt //////////////////////////////////////////////////////////////////////////////// // Error message handling. This is in this file rather than with the warnings in -// TerminalWindow, because the error text might also just be a serialization -// error message. So AppLogic needs to know the actual text of the error. +// TerminalWindow, because the error text might be: +// * A error we defined here +// * An error from deserializing the json +// * Any other fatal error loading the settings +// So all we pass on is the actual text of the error, rather than the +// combination of things that might have caused an error. // !!! IMPORTANT !!! // Make sure that these keys are in the same order as the @@ -493,10 +497,15 @@ namespace winrt::TerminalApp::implementation } else { + auto warnings{ winrt::multi_threaded_vector() }; + for (auto&& warn : _warnings) + { + warnings.Append(warn); + } auto ev = winrt::make_self(true, static_cast(_settingsLoadedResult), _settingsLoadExceptionText, - winrt::multi_threaded_vector(std::move(_warnings)), + warnings, _settings); _SettingsChangedHandlers(*this, *ev); return; @@ -517,10 +526,15 @@ namespace winrt::TerminalApp::implementation _ApplyStartupTaskStateChange(); _ProcessLazySettingsChanges(); + auto warnings{ winrt::multi_threaded_vector() }; + for (auto&& warn : _warnings) + { + warnings.Append(warn); + } auto ev = winrt::make_self(!initialLoad, _settingsLoadedResult, _settingsLoadExceptionText, - winrt::multi_threaded_vector(std::move(_warnings)), + warnings, _settings); _SettingsChangedHandlers(*this, *ev); } @@ -670,10 +684,15 @@ namespace winrt::TerminalApp::implementation ReloadSettings(); } + auto warnings{ winrt::multi_threaded_vector() }; + for (auto&& warn : _warnings) + { + warnings.Append(warn); + } auto ev = winrt::make_self(false, _settingsLoadedResult, _settingsLoadExceptionText, - winrt::multi_threaded_vector(std::move(_warnings)), + warnings, _settings); auto window = winrt::make_self(*ev); From 6aec80b8e7dd20cde2e0495fd05ce1d13d127b04 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 13 Mar 2023 11:25:28 -0500 Subject: [PATCH 24/25] A hotfix for the selfhost build --- 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 95b3f7834e5..4353776914a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -660,7 +660,7 @@ namespace winrt::TerminalApp::implementation // have a tab yet, but will once we're initialized. if (_tabs.Size() == 0 && !(_shouldStartInboundListener || _isEmbeddingInboundListener)) { - _LastTabClosedHandlers(*this, nullptr); + _LastTabClosedHandlers(*this, winrt::make(false)); } else { From 5b3dc083e03c000b7ed24406e25f7624290ad62c Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 17 Mar 2023 08:42:41 -0500 Subject: [PATCH 25/25] last nits, let's do this --- src/cascadia/TerminalApp/TerminalPage.cpp | 2 +- src/cascadia/WindowsTerminal/AppHost.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 4353776914a..6cfbc4bdf0b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -57,7 +57,7 @@ namespace winrt::TerminalApp::implementation _mruTabs{ winrt::single_threaded_observable_vector() }, _startupActions{ winrt::single_threaded_vector() }, _hostingHwnd{}, - _WindowProperties{ properties } + _WindowProperties{ std::move(properties) } { InitializeComponent(); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index bd77664f685..93e68dd0b65 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -983,9 +983,11 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s } _WindowCreatedToken = _windowManager.WindowCreated([this](auto&&, auto&&) { - if (_getWindowLayoutThrottler) { + if (_getWindowLayoutThrottler) + { _getWindowLayoutThrottler.value()(); - } }); + } + }); _WindowClosedToken = _windowManager.WindowClosed([this](auto&&, auto&&) { if (_getWindowLayoutThrottler)