From 5b434dcda4b267aed2c12205c5b78cf66bb54b32 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 17 Mar 2023 09:17:11 -0500 Subject: [PATCH] Split `AppLogic` into "App logic" and "Window logic" (#14825) _And so begins the first chapter in the epic tale of the Terminal's tab tear-out. This commit, though humble in its nature, shall mark the beginning of a grand journey._ _This initial offering, though small in its scope, doth serve to divide the code that currently resides within TerminalPage and AppLogic, moving it unto a new entity known as TerminalWindow. In the ages to come, these classes shall take on separate responsibilities, each with their own purpose._ _The AppLogic shall hold sway over the entire process, shared among all Terminal windows, while the AppHost, TerminalWindow, and TerminalPage shall rule over each individual window._ _This pull request prepares the way for the future, moving state that pertains to the individual windows into the TerminalWindow. This is a task of great labor, for it requires moving much code, but the end result shall bring greater organization to the codebase._ _And so the stage is set, for in the next pull request, the Process Model v3 shall be revealed, unifying all Terminal windows into a single process, a grand accomplishment indeed._ _courtesy of G.P.T. Tolkien_
Or, as I wrote it originally. This is the first of the commits in the long saga which will culminate in tab tear-out for the Terminal. This the most functionally trivial of the PRs. It mostly just splits up code that's currently in TerminalPage & AppLogic, and moves it into a new class `TerminalWindow`. In the future, these classes will separate responsibility as such: * There will be one `AppLogic` per process, shared across all Terminal windows. * There will be one `AppHost`, `TerminalWindow`, and `TerminalPage` for each individual window in the process. This PR prepares for that by moving some state that's applicable to _individual windows_ into `TerminalWindow`. This is almost exclusively a code moving PR. There should be minimal functional changes.
In the next PR, we'll introduce the actual "Process Model v3", merging all Terminal windows into a single terminal process. Related to #5000. See https://github.com/Microsoft/terminal/issues/5000#issuecomment-1407110045 for my current todo list. Related to #1256. These commits are all artificially broken down pieces. Honestly, I don't want to really merge them till they're all ready, so we know that the work e2e. This my feigned attempt to break it into digestable PRs. Lightly manually tested, things seem to still all work? Most of this code was actually written in deeper branches, it was only today I realized it all needed to come back to this branch. * [x] The window persistence fishy-ness of the subsequent PR isn't present here. So that's something. * [x] Localtests still pass ### Detailed description > Q: Does `AppLogic` not keep track of its windows? Sure doesn't! I didn't think that was something it needed to know. >Q: Why does `TerminalWindow` (per-window) have access to the commandline args (per-process) It's because it's _not_ per process. Commandline args _are_ per-window. Consider - you launch the Terminal, then run a `wt -w -1 -- foo`. That makes its own window. In this process, yes. But that new window has its own commandline args, separate from the ones that started the original process. --- .../LocalTests_TerminalApp/TabTests.cpp | 25 +- src/cascadia/TerminalApp/App.cpp | 19 +- src/cascadia/TerminalApp/AppLogic.cpp | 1108 +------------- src/cascadia/TerminalApp/AppLogic.h | 148 +- src/cascadia/TerminalApp/AppLogic.idl | 107 +- .../TerminalApp/SettingsLoadEventArgs.h | 30 + src/cascadia/TerminalApp/TabManagement.cpp | 8 +- .../TerminalApp/TerminalAppLib.vcxproj | 10 + src/cascadia/TerminalApp/TerminalPage.cpp | 196 +-- src/cascadia/TerminalApp/TerminalPage.h | 37 +- src/cascadia/TerminalApp/TerminalPage.idl | 25 +- src/cascadia/TerminalApp/TerminalPage.xaml | 8 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 1280 +++++++++++++++++ src/cascadia/TerminalApp/TerminalWindow.h | 223 +++ src/cascadia/TerminalApp/TerminalWindow.idl | 142 ++ .../GlobalAppSettings.cpp | 5 + .../TerminalSettingsModel/GlobalAppSettings.h | 1 + .../GlobalAppSettings.idl | 1 + src/cascadia/WindowsTerminal/AppHost.cpp | 274 ++-- src/cascadia/WindowsTerminal/AppHost.h | 47 +- src/cascadia/WindowsTerminal/IslandWindow.cpp | 8 +- 21 files changed, 2051 insertions(+), 1651 deletions(-) create mode 100644 src/cascadia/TerminalApp/SettingsLoadEventArgs.h 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/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 53013f9372..b5fa445ab7 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" @@ -110,6 +111,7 @@ namespace TerminalAppLocalTests void _initializeTerminalPage(winrt::com_ptr& page, CascadiaSettings initialSettings); winrt::com_ptr _commonSetup(); + winrt::com_ptr _windowProperties; }; template @@ -194,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); @@ -239,9 +244,11 @@ namespace TerminalAppLocalTests // it's weird. winrt::TerminalApp::TerminalPage projectedPage{ nullptr }; + _windowProperties = winrt::make_self(); + winrt::TerminalApp::WindowProperties props = *_windowProperties; Log::Comment(NoThrowString().Format(L"Construct the TerminalPage")); - auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() { - 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; }); @@ -1242,14 +1249,16 @@ 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. - page->WindowName(args.ProposedName()); + // + // This replicates how TerminalWindow works + _windowProperties->WindowName(args.ProposedName()); }); 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; @@ -1260,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"); }); diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index 138d4a7d24..b4019d9522 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -77,22 +77,7 @@ namespace winrt::TerminalApp::implementation /// Details about the launch request and process. void App::OnLaunched(const LaunchActivatedEventArgs& /*e*/) { - // 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 4381e24e0b..c8677eb7f8 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 @@ -31,36 +30,23 @@ namespace winrt using IInspectable = Windows::Foundation::IInspectable; } -static constexpr std::wstring_view StartupTaskName = L"StartTerminalOnLoginTask"; -// clang-format off +//////////////////////////////////////////////////////////////////////////////// +// Error message handling. This is in this file rather than with the warnings in +// 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 // 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 { +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: @@ -83,20 +69,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 +82,7 @@ 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; -} +static constexpr std::wstring_view StartupTaskName = L"StartTerminalOnLoginTask"; namespace winrt::TerminalApp::implementation { @@ -183,11 +132,7 @@ 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(); - _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 +146,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 +195,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 +202,11 @@ 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) + // 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. TODO:GH#14957 - evaluate moving + // this after the Page is 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 +237,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 +251,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: @@ -852,6 +302,7 @@ namespace winrt::TerminalApp::implementation } _settings = std::move(newSettings); + hr = _warnings.empty() ? S_OK : S_FALSE; } catch (const winrt::hresult_error& e) @@ -868,6 +319,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. @@ -957,18 +413,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,9 +497,17 @@ 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); + auto warnings{ winrt::multi_threaded_vector() }; + for (auto&& warn : _warnings) + { + warnings.Append(warn); + } + auto ev = winrt::make_self(true, + static_cast(_settingsLoadedResult), + _settingsLoadExceptionText, + warnings, + _settings); + _SettingsChangedHandlers(*this, *ev); return; } } @@ -1067,28 +519,24 @@ namespace winrt::TerminalApp::implementation return; } - 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(); + auto warnings{ winrt::multi_threaded_vector() }; + for (auto&& warn : _warnings) + { + warnings.Append(warn); + } + auto ev = winrt::make_self(!initialLoad, + _settingsLoadedResult, + _settingsLoadExceptionText, + warnings, + _settings); + _SettingsChangedHandlers(*this, *ev); } // Method Description: @@ -1098,255 +546,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. - // Arguments: - // - - // Return Value: - // - - void AppLogic::SetInboundListener() - { - _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) - { - ::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: // - 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 @@ -1468,97 +667,47 @@ 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() + Microsoft::Terminal::Settings::Model::Theme AppLogic::Theme() { - return _root != nullptr ? _root->ShouldUsePersistedLayout(_settings) : false; + return _settings.GlobalSettings().CurrentTheme(); } - bool AppLogic::ShouldImmediatelyHandoffToElevated() + TerminalApp::TerminalWindow AppLogic::CreateNewWindow() { - if (!_loadedInitialSettings) + if (_settings == nullptr) { - // Load settings if we haven't already ReloadSettings(); } - return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false; - } - void AppLogic::HandoffToElevated() - { - if (_root) + auto warnings{ winrt::multi_threaded_vector() }; + for (auto&& warn : _warnings) { - _root->HandoffToElevated(_settings); + warnings.Append(warn); } + 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) + { + window->SetSettingsStartupArgs(_settingsAppArgs.GetStartupActions()); + } + return *window; + } + + bool AppLogic::ShouldUsePersistedLayout() const + { + return _settings.GlobalSettings().ShouldUsePersistedLayout(); } void AppLogic::SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts) @@ -1576,121 +725,4 @@ namespace winrt::TerminalApp::implementation 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 5534979b97..821ec0f5f1 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -5,10 +5,11 @@ #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 "TerminalWindow.h" #include #include @@ -36,18 +37,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,124 +46,47 @@ namespace winrt::TerminalApp::implementation AppLogic(); ~AppLogic() = default; - STDMETHODIMP Initialize(HWND hwnd); - void Create(); bool IsUwp() const noexcept; void RunAsUwp(); bool IsElevated() const noexcept; void ReloadSettings(); + bool HasSettingsStartupActions() const noexcept; + + bool ShouldUsePersistedLayout() const; + void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector& layouts); + [[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); } + TerminalApp::TerminalWindow CreateNewWindow(); - 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); + TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs); 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 +95,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 b7a92ea110..a5db001de6 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -1,41 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. - -import "TerminalPage.idl"; -import "ShortcutActionDispatch.idl"; -import "IDirectKeyListener.idl"; +import "TerminalWindow.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 +27,22 @@ 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(); + void ReloadSettings(); Microsoft.Terminal.Settings.Model.Theme Theme { get; }; FindTargetWindowResult FindTargetWindow(String[] args); + TerminalWindow CreateNewWindow(); + 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 SettingsChanged; - 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/SettingsLoadEventArgs.h b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h new file mode 100644 index 0000000000..e094810784 --- /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, + winrt::hstring exceptionText, + winrt::Windows::Foundation::Collections::IVector warnings, + Microsoft::Terminal::Settings::Model::CascadiaSettings newSettings) : + _Reload{ reload }, + _Result{ result }, + _ExceptionText{ std::move(exceptionText) }, + _Warnings{ std::move(warnings) }, + _NewSettings{ std::move(newSettings) } {}; + }; +} diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index c417af3c4e..511c4925fe 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 && ShouldUsePersistedLayout(_settings) && _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/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 6123f0de1c..d72908d5b2 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -138,6 +138,12 @@ AppLogic.idl + + TerminalWindow.idl + + + TerminalWindow.idl + @@ -231,6 +237,9 @@ AppLogic.idl + + TerminalWindow.idl + @@ -252,6 +261,7 @@ + MinMaxCloseControl.xaml Code diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 94e9865e63..6cfbc4bdf0 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 @@ -51,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{ std::move(properties) } { InitializeComponent(); + + _WindowProperties.PropertyChanged({ get_weak(), &TerminalPage::_windowPropertyChanged }); } // Method Description: @@ -321,18 +325,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 +339,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 +436,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 +519,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 @@ -704,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 { @@ -1847,11 +1803,11 @@ namespace winrt::TerminalApp::implementation } // If the user set a custom name, save it - if (_WindowName != L"") + if (const auto& windowName{ _WindowProperties.WindowName() }; !windowName.empty()) { ActionAndArgs action; action.Action(ShortcutAction::RenameWindow); - RenameWindowArgs args{ _WindowName }; + RenameWindowArgs args{ windowName }; action.Args(args); actions.emplace_back(std::move(action)); @@ -1901,7 +1857,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 @@ -3856,105 +3812,6 @@ 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) - { - _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 @@ -4066,17 +3923,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. @@ -4452,4 +4304,28 @@ namespace winrt::TerminalApp::implementation _activated = activated; _updateThemeColors(); } + + // 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() }) + { + // 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 9c1705706a..a99854374c 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); @@ -53,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 @@ -63,11 +73,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 +124,8 @@ 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); + TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; }; - 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(); @@ -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); @@ -192,10 +186,8 @@ 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 }; bool _maintainStateOnTabClose{ false }; bool _rearranging{ false }; @@ -230,6 +222,8 @@ namespace winrt::TerminalApp::implementation int _renamerLayoutCount{ 0 }; bool _renamerPressedEnter{ false }; + TerminalApp::WindowProperties _WindowProperties{ nullptr }; + winrt::Windows::Foundation::IAsyncOperation _ShowDialogHelper(const std::wstring_view& name); void _ShowAboutDialog(); @@ -461,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 5535c527a2..2fc333afe1 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 { @@ -17,9 +21,19 @@ namespace TerminalApp Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); }; + [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; }; @@ -29,13 +43,9 @@ namespace TerminalApp Boolean Fullscreen { get; }; Boolean AlwaysOnTop { get; }; + WindowProperties 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, @@ -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/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index 3434cd868e..38d7c0f69f 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 new file mode 100644 index 0000000000..eb0ef7c78b --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -0,0 +1,1280 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TerminalWindow.h" +#include "../inc/WindowingBehavior.h" +#include "TerminalWindow.g.cpp" +#include "SettingsLoadEventArgs.g.cpp" +#include "WindowProperties.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; + namespace WUX = Windows::UI::Xaml; + using IInspectable = Windows::Foundation::IInspectable; +} + +// !!! 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_assert(settingsLoadWarningsLabels.size() == static_cast(SettingsLoadWarnings::WARNINGS_SIZE)); +// Errors are defined in AppLogic.cpp + +// 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: +// - 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 TerminalApp::SettingsLoadEventArgs& settingsLoadedResult) : + _settings{ settingsLoadedResult.NewSettings() }, + _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(*_WindowProperties); + _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 + // cause you to chase down the rabbit hole of "why is App not + // registered?" when it definitely is. + } + + // Method Description: + // - Implements the IInitializeWithWindow interface from shobjidl_core. + HRESULT TerminalWindow::Initialize(HWND hwnd) + { + // Pass commandline args into the TerminalPage. If we were supposed to + // load from a persisted layout, do that instead. + + // 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()) + { + std::vector actions; + for (const auto& a : layout.TabLayout()) + { + actions.emplace_back(a); + } + _root->SetStartupActions(actions); + } + else + { + _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: + // - 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 + { + // 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: + // - 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 + { + // 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 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: + // - 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); + + // 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 }); + _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 (_WindowProperties->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) && !_WindowProperties->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::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. + 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, + const winrt::hstring& exceptionText) + { + 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 (!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. + 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(const Windows::Foundation::Collections::IVector& warnings) + { + 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::WUX::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(); + } + } + + 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: + // - 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 = LoadPersistedLayout()) + { + 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()) + { + // 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 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()) + { + // Same comment as above, but with a TabRowControl. + // + // 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; + } + + // 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 = LoadPersistedLayout()) + { + 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 = LoadPersistedLayout()) + { + 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()); + } + + winrt::fire_and_forget TerminalWindow::UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args) + { + _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, + gsl::narrow_cast(args.Result()), + args.ExceptionText()); + co_return; + } + else if (args.Result() == S_FALSE) + { + _ShowLoadWarningsDialog(args.Warnings()); + } + _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, 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() && isLastWindow) + { + if (const auto layout = _root->GetWindowLayout()) + { + layout.InitialPosition(pos); + const auto state = ApplicationState::SharedInstance(); + state.PersistedWindowLayouts(winrt::single_threaded_vector({ layout })); + } + } + + _root->CloseWindow(false); + } + } + + void TerminalWindow::ClearPersistedWindowState() + { + if (_settings.GlobalSettings().ShouldUsePersistedLayout()) + { + auto state = ApplicationState::SharedInstance(); + state.PersistedWindowLayouts(nullptr); + } + } + + 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) + { + if (_root) + { + _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; + } + + void TerminalWindow::SetSettingsStartupArgs(const std::vector& actions) + { + for (const auto& action : actions) + { + _settingsStartupArgs.push_back(action); + } + _gotSettingsStartupActions = true; + } + + 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) + { + // 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) + { + // 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(); + + // 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; + } + + // 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()); + } + + 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::SetPersistedLayoutIdx(const uint32_t idx) + { + _loadFromPersistedLayoutIdx = idx; + _cachedLayout = std::nullopt; + } + + // 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 _settings.GlobalSettings().ShouldUsePersistedLayout() ? _loadFromPersistedLayoutIdx : std::nullopt; + } + + 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) + { + 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() && layout.TabLayout().Size() > 0) ? layout : nullptr; + return *_cachedLayout; + } + } + _cachedLayout = nullptr; + return *_cachedLayout; + } + + void TerminalWindow::IdentifyWindow() + { + if (_root) + { + _root->IdentifyWindow(); + } + } + + void TerminalWindow::RenameFailed() + { + if (_root) + { + _root->RenameFailed(); + } + } + + void TerminalWindow::WindowName(const winrt::hstring& name) + { + const auto oldIsQuakeMode = _WindowProperties->IsQuakeWindow(); + _WindowProperties->WindowName(name); + const auto newIsQuakeMode = _WindowProperties->IsQuakeWindow(); + if (newIsQuakeMode != 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); + } + } + void TerminalWindow::WindowId(const uint64_t& id) + { + _WindowProperties->WindowId(id); + } + + void TerminalWindow::RequestExitFullscreen() + { + _root->SetFullscreen(false); + } + + bool TerminalWindow::AutoHideWindow() + { + return _settings.GlobalSettings().AutoHideWindow(); + } + + void TerminalWindow::UpdateSettingsHandler(const winrt::IInspectable& /*sender*/, + 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 (_root) + { + _root->HandoffToElevated(_settings); + return; + } + } + + 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 new file mode 100644 index 0000000000..1a44601076 --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "TerminalWindow.g.h" +#include "SystemMenuChangeArgs.g.h" +#include "WindowProperties.g.h" + +#include "SettingsLoadEventArgs.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 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: + TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult); + ~TerminalWindow() = default; + + STDMETHODIMP Initialize(HWND hwnd); + + void Create(); + + bool IsUwp() const noexcept; + bool IsElevated() const noexcept; + + void Quit(); + + winrt::fire_and_forget UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); + + bool HasCommandlineArguments() 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(); + + bool ShouldImmediatelyHandoffToElevated(); + void HandoffToElevated(); + + bool FocusMode() const; + bool Fullscreen() const; + void Maximized(bool newMaximized); + bool AlwaysOnTop() const; + bool AutoHideWindow(); + + hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position); + void IdentifyWindow(); + void RenameFailed(); + + std::optional LoadPersistedLayoutIdx() const; + winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout(); + + void SetPersistedLayoutIdx(const uint32_t idx); + void SetNumberOfOpenWindows(const uint64_t num); + bool ShouldUsePersistedLayout() const; + void ClearPersistedWindowState(); + + 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, const bool isLastWindow); + 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(); + void UpdateSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::SettingsLoadEventArgs& arg); + + void WindowName(const winrt::hstring& value); + void WindowId(const uint64_t& value); + 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. + // 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: + // 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; + bool _gotSettingsStartupActions{ false }; + std::vector _settingsStartupArgs{}; + + winrt::com_ptr _WindowProperties{ nullptr }; + + std::optional _loadFromPersistedLayoutIdx{}; + std::optional _cachedLayout{ std::nullopt }; + + 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); + + 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(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 + }; +} + +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 0000000000..a945f88e00 --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -0,0 +1,142 @@ +// 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; }; + }; + + [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 + { + 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, + // 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 IsElevated(); + + Boolean HasCommandlineArguments(); + + Int32 SetStartupCommandline(String[] commands); + + Int32 ExecuteCommandline(String[] commands, String cwd); + String ParseCommandlineMessage { get; }; + Boolean ShouldExitEarly { get; }; + + Boolean ShouldImmediatelyHandoffToElevated(); + void HandoffToElevated(); + + 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(); + void SetPersistedLayoutIdx(UInt32 idx); + void ClearPersistedWindowState(); + + void RenameFailed(); + void RequestExitFullscreen(); + + 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, Boolean isLastWindow); + void WindowVisibilityChanged(Boolean showOrHide); + + TaskbarState TaskbarState{ get; }; + Windows.UI.Xaml.Media.Brush TitlebarBrush { get; }; + void WindowActivated(Boolean activated); + + String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position); + + Boolean GetMinimizeToNotificationArea(); + Boolean GetAlwaysShowNotificationIcon(); + Boolean GetShowTitleInTitlebar(); + + // These already have accessors as a part of IWindowProperties, but we + // also want to be able to set them. + WindowProperties WindowProperties { get; }; + void WindowName(String name); + void WindowId(UInt64 id); + Boolean IsQuakeWindow(); + + // See IDialogPresenter and TerminalPage's DialogPresenter for more + // information. + 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; + + event Windows.Foundation.TypedEventHandler SettingsChanged; + + } +} diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 55ec4d634b..249bc060f7 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 4ee2b0bf9e..738159b5eb 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 906b53e1df..93e68dd0b6 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,11 +79,6 @@ AppHost::AppHost() noexcept : std::placeholders::_3); _window->SetCreateCallback(pfn); - _window->SetSnapDimensionCallback(std::bind(&winrt::TerminalApp::AppLogic::CalcSnappedDimension, - _logic, - std::placeholders::_1, - std::placeholders::_2)); - // Event handlers: // MAKE SURE THEY ARE ALL: // * winrt::auto_revoke @@ -100,10 +97,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(); @@ -118,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() @@ -152,9 +136,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 +153,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 +215,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 +237,7 @@ void AppHost::_HandleCommandlineArgs() GetStringResource(messageTitle).data(), MB_OK | messageIcon); - if (_logic.ShouldExitEarly()) + if (_windowLogic.ShouldExitEarly()) { ExitProcess(result); } @@ -263,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 (_logic.ShouldImmediatelyHandoffToElevated()) + if (_windowLogic.ShouldImmediatelyHandoffToElevated()) { _shouldCreateWindow = false; - _logic.HandoffToElevated(); + _windowLogic.HandoffToElevated(); return; } @@ -288,7 +276,7 @@ void AppHost::_HandleCommandlineArgs() { const auto numPeasants = _windowManager.GetNumberOfPeasants(); const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts(); - if (_logic.ShouldUsePersistedLayout() && + if (_appLogic.ShouldUsePersistedLayout() && layouts && layouts.Size() > 0) { @@ -299,9 +287,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 +316,9 @@ void AppHost::_HandleCommandlineArgs() )); } } - _logic.SetNumberOfOpenWindows(numPeasants); } - _logic.WindowName(peasant.WindowName()); - _logic.WindowId(peasant.GetID()); + _windowLogic.WindowName(peasant.WindowName()); + _windowLogic.WindowId(peasant.GetID()); } } @@ -350,7 +337,7 @@ void AppHost::Initialize() { _window->Initialize(); - if (auto withWindow{ _logic.try_as() }) + if (auto withWindow{ _windowLogic.try_as() }) { withWindow->Initialize(_window->GetHandle()); } @@ -360,7 +347,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 +370,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 (_windowLogic) { - _logic.Maximized(newMaximize); + _windowLogic.Maximized(newMaximize); } }); @@ -416,21 +403,28 @@ 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 }); + _revokers.PropertyChanged = _windowLogic.PropertyChanged(winrt::auto_revoke, { this, &AppHost::_PropertyChangedHandler }); - _logic.Create(); + _appLogic.Create(); + _windowLogic.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.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 }); + + // 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 }); + _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 +433,31 @@ 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->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, // set that content as well. - _window->SetContent(_logic.GetRoot()); + _window->SetContent(_windowLogic.GetRoot()); _window->OnAppInitialized(); // BODGY @@ -478,7 +490,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); } @@ -492,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) { @@ -511,6 +523,16 @@ 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. @@ -564,11 +586,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 +637,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 +671,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 +728,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 +751,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 +795,7 @@ void AppHost::_AlwaysOnTopChanged(const winrt::Windows::Foundation::IInspectable return; } - _window->SetAlwaysOnTop(_logic.AlwaysOnTop()); + _window->SetAlwaysOnTop(_windowLogic.AlwaysOnTop()); } // Method Description @@ -801,14 +823,14 @@ void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&, // - void AppHost::_WindowMouseWheeled(const til::point coord, const int32_t delta) { - if (_logic) + if (_windowLogic) { // 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 = 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 { @@ -862,7 +884,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 +903,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 +930,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 +977,23 @@ 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()); - _WindowCreatedToken = _windowManager.WindowCreated([this](auto&&, auto&&) { - if (_getWindowLayoutThrottler) { + if (_getWindowLayoutThrottler) + { _getWindowLayoutThrottler.value()(); } - _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); + }); _WindowClosedToken = _windowManager.WindowClosed([this](auto&&, auto&&) { if (_getWindowLayoutThrottler) { _getWindowLayoutThrottler.value()(); } - _logic.SetNumberOfOpenWindows(_windowManager.GetNumberOfPeasants()); }); // These events are coming from peasants that become or un-become quake windows. @@ -1000,7 +1018,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 (_appLogic.ShouldUsePersistedLayout()) { try { @@ -1015,7 +1033,7 @@ winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts() TraceLoggingDescription("Logged when writing window state"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - _logic.SaveWindowLayoutJsons(layoutJsons); + _appLogic.SaveWindowLayoutJsons(layoutJsons); } catch (...) { @@ -1056,15 +1074,10 @@ winrt::fire_and_forget AppHost::_SaveWindowLayoutsRepeat() } } -void AppHost::_listenForInboundConnections() -{ - _logic.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 +1102,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 +1324,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 +1348,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 +1386,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); @@ -1392,7 +1405,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(); @@ -1409,11 +1422,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 +1434,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 +1447,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 +1598,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 +1620,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 +1656,8 @@ void AppHost::_CloseRequested(const winrt::Windows::Foundation::IInspectable& /* const winrt::Windows::Foundation::IInspectable& /*args*/) { const auto pos = _GetWindowLaunchPosition(); - _logic.CloseWindow(pos); + const bool isLastWindow = _windowManager.GetNumberOfPeasants() == 1; + _windowLogic.CloseWindow(pos, isLastWindow); } void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, @@ -1654,7 +1668,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 2de8474218..abee5a9e66 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -19,12 +19,15 @@ public: 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; winrt::TerminalApp::App _app; - winrt::TerminalApp::AppLogic _logic; + 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 }; @@ -88,7 +91,7 @@ private: 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); @@ -151,28 +154,28 @@ private: 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::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::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::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; - winrt::TerminalApp::AppLogic::PropertyChanged_revoker PropertyChanged; } _revokers{}; }; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index a06dd7818e..acc039ae44 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