// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "AppLogic.h"
#include "../inc/WindowingBehavior.h"
#include "AppLogic.g.cpp"
#include "FindTargetWindowResult.g.cpp"
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

#include <LibraryResources.h>
#include <WtExeUtils.h>
#include <wil/token_helpers.h >

using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace ::TerminalApp;

namespace winrt
{
    namespace MUX = Microsoft::UI::Xaml;
    using IInspectable = Windows::Foundation::IInspectable;
}

static constexpr std::wstring_view StartupTaskName = L"StartTerminalOnLoginTask";
// clang-format off
// !!! IMPORTANT !!!
// Make sure that these keys are in the same order as the
// SettingsLoadWarnings/Errors enum is!
static const std::array settingsLoadWarningsLabels {
    USES_RESOURCE(L"MissingDefaultProfileText"),
    USES_RESOURCE(L"DuplicateProfileText"),
    USES_RESOURCE(L"UnknownColorSchemeText"),
    USES_RESOURCE(L"InvalidBackgroundImage"),
    USES_RESOURCE(L"InvalidIcon"),
    USES_RESOURCE(L"AtLeastOneKeybindingWarning"),
    USES_RESOURCE(L"TooManyKeysForChord"),
    USES_RESOURCE(L"MissingRequiredParameter"),
    USES_RESOURCE(L"FailedToParseCommandJson"),
    USES_RESOURCE(L"FailedToWriteToSettings"),
    USES_RESOURCE(L"InvalidColorSchemeInCmd"),
    USES_RESOURCE(L"InvalidSplitSize"),
    USES_RESOURCE(L"FailedToParseStartupActions"),
    USES_RESOURCE(L"FailedToParseSubCommands"),
};
static const std::array settingsLoadErrorsLabels {
    USES_RESOURCE(L"NoProfilesText"),
    USES_RESOURCE(L"AllProfilesHiddenText")
};
// clang-format on

static_assert(settingsLoadWarningsLabels.size() == static_cast<size_t>(SettingsLoadWarnings::WARNINGS_SIZE));
static_assert(settingsLoadErrorsLabels.size() == static_cast<size_t>(SettingsLoadErrors::ERRORS_SIZE));

// Function Description:
// - General-purpose helper for looking up a localized string for a
//   warning/error. First will look for the given key in the provided map of
//   keys->strings, where the values in the map are ResourceKeys. If it finds
//   one, it will lookup the localized string from that ResourceKey.
// - If it does not find a key, it'll return an empty string
// Arguments:
// - key: the value to use to look for a resource key in the given map
// - map: A map of keys->Resource keys.
// Return Value:
// - the localized string for the given type, if it exists.
template<typename T>
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<uint32_t>(warning), settingsLoadWarningsLabels);
}

// Function Description:
// - Gets the text from our ResourceDictionary for the given
//   SettingsLoadError. If there is no such text, we'll return nullptr.
// - The warning should have an entry in settingsLoadErrorsLabels.
// Arguments:
// - error: the SettingsLoadErrors value to get the localized text for.
// Return Value:
// - localized text for the given error
static winrt::hstring _GetErrorText(SettingsLoadErrors error)
{
    return _GetMessageText(static_cast<uint32_t>(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
    winrt::IInspectable key = winrt::box_value(L"ErrorTextBrush");
    if (resources.HasKey(key))
    {
        winrt::IInspectable g = resources.Lookup(key);
        auto brush = g.try_as<winrt::Windows::UI::Xaml::Media::Brush>();
        textRun.Foreground(brush);
    }

    return textRun;
}

// Method Description:
// - Returns whether the user is either a member of the Administrators group or
//   is currently elevated.
// - This will return **FALSE** if the user has UAC disabled entirely, because
//   there's no separation of power between the user and an admin in that case.
// Return Value:
// - true if the user is an administrator
static bool _isUserAdmin() noexcept
try
{
    wil::unique_handle processToken{ GetCurrentProcessToken() };
    const auto elevationType = wil::get_token_information<TOKEN_ELEVATION_TYPE>(processToken.get());
    const auto elevationState = wil::get_token_information<TOKEN_ELEVATION>(processToken.get());
    if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
    {
        // In this case, the user has UAC entirely disabled. This is sort of
        // weird, we treat this like the user isn't an admin at all. There's no
        // separation of powers, so the things we normally want to gate on
        // "having special powers" doesn't apply.
        //
        // See GH#7754, GH#11096
        return false;
    }

    return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
}
catch (...)
{
    LOG_CAUGHT_EXCEPTION();
    return false;
}

namespace winrt::TerminalApp::implementation
{
    // Function Description:
    // - Get the AppLogic for the current active Xaml application, or null if there isn't one.
    // Return value:
    // - A pointer (bare) to the AppLogic, or nullptr. The app logic outlives all other objects,
    //   unless the application is in a terrible way, so this is "safe."
    AppLogic* AppLogic::Current() noexcept
    try
    {
        if (auto currentXamlApp{ winrt::Windows::UI::Xaml::Application::Current().try_as<winrt::TerminalApp::App>() })
        {
            if (auto appLogicPointer{ winrt::get_self<AppLogic>(currentXamlApp.Logic()) })
            {
                return appLogicPointer;
            }
        }
        return nullptr;
    }
    catch (...)
    {
        LOG_CAUGHT_EXCEPTION();
        return nullptr;
    }

    // Method Description:
    // - Returns the settings currently in use by the entire Terminal application.
    // - IMPORTANT! This can throw! Make sure to try/catch this, so that the
    //   LocalTests don't crash (because their Application::Current() won't be a
    //   AppLogic)
    // Throws:
    // - HR E_INVALIDARG if the app isn't up and running.
    const CascadiaSettings AppLogic::CurrentAppSettings()
    {
        auto appLogic{ ::winrt::TerminalApp::implementation::AppLogic::Current() };
        THROW_HR_IF_NULL(E_INVALIDARG, appLogic);
        return appLogic->GetSettings();
    }

    AppLogic::AppLogic() :
        _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } }
    {
        // For your own sanity, it's better to do setup outside the ctor.
        // If you do any setup in the ctor that ends up throwing an exception,
        // then it might look like App just failed to activate, which will
        // cause you to chase down the rabbit hole of "why is App not
        // registered?" when it definitely is.

        // The TerminalPage has to be constructed during our construction, to
        // make sure that there's a terminal page for callers of
        // SetTitleBarContent
        _isElevated = _isUserAdmin();
        _root = winrt::make_self<TerminalPage>();

        _reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
            if (auto self{ weakSelf.get() })
            {
                self->_ReloadSettings();
            }
        });

        _languageProfileNotifier = winrt::make_self<LanguageProfileNotifier>([this]() {
            _reloadSettings->Run();
        });
    }

    // 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:
    // - <none> - reports internal state
    // Return Value:
    // - True if UWP, false otherwise.
    bool AppLogic::IsUwp() const noexcept
    {
        return _isUwp;
    }

    // Method Description:
    // - Called around the codebase to discover if Terminal is running elevated
    // Arguments:
    // - <none> - reports internal state
    // Return Value:
    // - True if elevated, false otherwise.
    bool AppLogic::IsElevated() const noexcept
    {
        return _isElevated;
    }

    // Method Description:
    // - Called by UWP context invoker to let us know that we may have to change some of our behaviors
    //   for being a UWP
    // Arguments:
    // - <none> (sets to UWP = true, one way change)
    // Return Value:
    // - <none>
    void AppLogic::RunAsUwp()
    {
        _isUwp = true;
    }

    // Method Description:
    // - Build the UI for the terminal app. Before this method is called, it
    //   should not be assumed that the TerminalApp is usable. The Settings
    //   should be loaded before this is called, either with LoadSettings or
    //   GetLaunchDimensions (which will call LoadSettings)
    // Arguments:
    // - <none>
    // Return Value:
    // - <none>
    void AppLogic::Create()
    {
        // Assert that we've already loaded our settings. We have to do
        // 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)
        {
            _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)
        {
            _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.
            //
            // We know at the start, that the root TerminalPage definitely isn't
            // in focus nor fullscreen mode. So "Toggle" here will always work
            // to "enable".
            const auto launchMode = this->GetLaunchMode();
            if (IsQuakeWindow())
            {
                _root->ToggleFocusMode();
            }
            else if (launchMode == LaunchMode::FullscreenMode)
            {
                _root->ToggleFullscreen();
            }
            else if (launchMode == LaunchMode::FocusMode ||
                     launchMode == LaunchMode::MaximizedFocusMode)
            {
                _root->ToggleFocusMode();
            }
        });
        _root->Create();

        _ApplyLanguageSettingChange();
        _RefreshThemeRoutine();
        _ApplyStartupTaskStateChange();

        TraceLoggingWrite(
            g_hTerminalAppProvider,
            "AppCreated",
            TraceLoggingDescription("Event emitted when the application is started"),
            TraceLoggingBool(_settings.GlobalSettings().ShowTabsInTitlebar(), "TabsInTitlebar"),
            TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
            TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
    }

    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<ContentDialogResult> 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;
        }

        // 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().Theme() };
            auto element{ sender.try_as<winrt::Windows::UI::Xaml::FrameworkElement>() };
            while (element)
            {
                element.RequestedTheme(theme);
                element = element.Parent().try_as<winrt::Windows::UI::Xaml::FrameworkElement>();
            }
        } };

        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:
    // - 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:
    // - <unused>
    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{ OpenSCManager(nullptr, nullptr, 0) };

        if (LOG_LAST_ERROR_IF(!hManager.is_valid()))
        {
            return true;
        }

        // Get a handle to the keyboard service
        wil::unique_schandle hService{ OpenService(hManager.get(), TabletInputServiceKey.data(), SERVICE_QUERY_STATUS) };
        if (LOG_LAST_ERROR_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:
    // - <none>
    // 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
            LoadSettings();
        }

        winrt::Windows::Foundation::Size proposedSize{};

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

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

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

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

        // GH#2061 - If the global setting "Always show tab bar" is
        // set or if "Show tabs in title bar" is set, then we'll need to add
        // 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<uint64_t>(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:
    // - <none>
    // Return Value:
    // - LaunchMode enum that indicates the launch mode
    LaunchMode AppLogic::GetLaunchMode()
    {
        if (!_loadedInitialSettings)
        {
            // Load settings if we haven't already
            LoadSettings();
        }

        // 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();
        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
            LoadSettings();
        }

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

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

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

        return {
            initialPosition.X ? initialPosition.X.Value() : defaultInitialX,
            initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY
        };
    }

    bool AppLogic::CenterOnLaunch()
    {
        if (!_loadedInitialSettings)
        {
            // Load settings if we haven't already
            LoadSettings();
        }
        return _settings.GlobalSettings().CenterOnLaunch();
    }

    winrt::Windows::UI::Xaml::ElementTheme AppLogic::GetRequestedTheme()
    {
        if (!_loadedInitialSettings)
        {
            // Load settings if we haven't already
            LoadSettings();
        }

        return _settings.GlobalSettings().Theme();
    }

    bool AppLogic::GetShowTabsInTitlebar()
    {
        if (!_loadedInitialSettings)
        {
            // Load settings if we haven't already
            LoadSettings();
        }

        return _settings.GlobalSettings().ShowTabsInTitlebar();
    }

    bool AppLogic::GetInitialAlwaysOnTop()
    {
        if (!_loadedInitialSettings)
        {
            // Load settings if we haven't already
            LoadSettings();
        }

        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:
    // - S_OK if we successfully parsed the settings, otherwise an appropriate HRESULT.
    [[nodiscard]] HRESULT AppLogic::_TryLoadSettings() noexcept
    {
        HRESULT hr = E_FAIL;

        try
        {
            auto newSettings = _isUwp ? CascadiaSettings::LoadUniversal() : CascadiaSettings::LoadAll();
            _settings = newSettings;

            if (_settings.GetLoadingError())
            {
                _settingsLoadExceptionText = _GetErrorText(_settings.GetLoadingError().Value());
                return E_INVALIDARG;
            }
            else if (!_settings.GetSerializationErrorMessage().empty())
            {
                _settingsLoadExceptionText = _settings.GetSerializationErrorMessage();
                return E_INVALIDARG;
            }

            _warnings.clear();
            for (uint32_t i = 0; i < _settings.Warnings().Size(); i++)
            {
                _warnings.push_back(_settings.Warnings().GetAt(i));
            }

            _hasSettingsStartupActions = false;
            const auto startupActions = _settings.GlobalSettings().StartupActions();
            if (!startupActions.empty())
            {
                _settingsAppArgs.FullResetState();

                ExecuteCommandlineArgs args{ _settings.GlobalSettings().StartupActions() };
                auto result = _settingsAppArgs.ParseArgs(args);
                if (result == 0)
                {
                    _hasSettingsStartupActions = true;

                    // Validation also injects new-tab command if implicit new-tab was provided.
                    _settingsAppArgs.ValidateStartupCommands();
                }
                else
                {
                    _warnings.push_back(SettingsLoadWarnings::FailedToParseStartupActions);
                }
            }

            hr = _warnings.empty() ? S_OK : S_FALSE;
        }
        catch (const winrt::hresult_error& e)
        {
            hr = e.code();
            _settingsLoadExceptionText = e.message();
            LOG_HR(hr);
        }
        catch (...)
        {
            hr = wil::ResultFromCaughtException();
            LOG_HR(hr);
        }
        return hr;
    }

    // Method Description:
    // - Initialized our settings. See CascadiaSettings for more details.
    //      Additionally hooks up our callbacks for keybinding events to the
    //      keybindings object.
    // NOTE: This must be called from a MTA if we're running as a packaged
    //      application. The Windows.Storage APIs require a MTA. If this isn't
    //      happening during startup, it'll need to happen on a background thread.
    void AppLogic::LoadSettings()
    {
        auto start = std::chrono::high_resolution_clock::now();

        TraceLoggingWrite(
            g_hTerminalAppProvider,
            "SettingsLoadStarted",
            TraceLoggingDescription("Event emitted before loading the settings"),
            TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
            TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));

        // Attempt to load the settings.
        // If it fails,
        //  - use Default settings,
        //  - don't persist them (LoadAll won't save them in this case).
        //  - _settingsLoadedResult will be set to an error, indicating that
        //    we should display the loading error.
        //    * We can't display the error now, because we might not have a
        //      UI yet. We'll display the error in _OnLoaded.
        _settingsLoadedResult = _TryLoadSettings();

        if (FAILED(_settingsLoadedResult))
        {
            _settings = CascadiaSettings::LoadDefaults();
        }

        auto end = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double> delta = end - start;

        TraceLoggingWrite(
            g_hTerminalAppProvider,
            "SettingsLoadComplete",
            TraceLoggingDescription("Event emitted when loading the settings is finished"),
            TraceLoggingFloat64(delta.count(), "Duration"),
            TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
            TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));

        _loadedInitialSettings = true;

        // Register for directory change notification.
        _RegisterSettingsChange();

        Jumplist::UpdateJumplist(_settings);
    }

    // Method Description:
    // - Registers for changes to the settings folder and upon a updated settings
    //      profile calls _ReloadSettings().
    // Arguments:
    // - <none>
    // Return Value:
    // - <none>
    void AppLogic::_RegisterSettingsChange()
    {
        const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } };
        const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };

        _reader.create(
            settingsPath.parent_path().c_str(),
            false,
            // We want file modifications, AND when files are renamed to be
            // settings.json. This second case will oftentimes happen with text
            // editors, who will write a temp file, then rename it to be the
            // actual file you wrote. So listen for that too.
            wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime,
            [this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
                const auto modifiedBasename = std::filesystem::path{ fileModified }.filename();

                if (modifiedBasename == settingsBasename)
                {
                    _reloadSettings->Run();
                }
                else if (modifiedBasename == stateBasename)
                {
                    _reloadState();
                }
            });
    }

    void AppLogic::_ApplyLanguageSettingChange() noexcept
    try
    {
        if (!IsPackaged())
        {
            return;
        }

        using ApplicationLanguages = winrt::Windows::Globalization::ApplicationLanguages;

        // NOTE: PrimaryLanguageOverride throws if this instance is unpackaged.
        const auto primaryLanguageOverride = ApplicationLanguages::PrimaryLanguageOverride();
        const auto language = _settings.GlobalSettings().Language();

        if (primaryLanguageOverride != language)
        {
            ApplicationLanguages::PrimaryLanguageOverride(language);
        }
    }
    CATCH_LOG()

    void AppLogic::_RefreshThemeRoutine()
    {
        _ApplyTheme(_settings.GlobalSettings().Theme());
    }

    // Function Description:
    // Returns the current app package or nullptr.
    // TRANSITIONAL
    // Exists to work around a compiler bug. This function encapsulates the
    // exception handling that we used to keep around calls to Package::Current,
    // so that when it's called inside a coroutine and fails it doesn't explode
    // terribly.
    static winrt::Windows::ApplicationModel::Package GetCurrentPackageNoThrow() noexcept
    {
        try
        {
            return winrt::Windows::ApplicationModel::Package::Current();
        }
        catch (...)
        {
            // discard any exception -- literally pretend we're not in a package
        }
        return nullptr;
    }

    fire_and_forget AppLogic::_ApplyStartupTaskStateChange()
    try
    {
        // First, make sure we're running in a packaged context. This method
        // won't work, and will crash mysteriously if we're running unpackaged.
        const auto package{ GetCurrentPackageNoThrow() };
        if (package == nullptr)
        {
            return;
        }

        const auto tryEnableStartupTask = _settings.GlobalSettings().StartOnUserLogin();
        const auto task = co_await StartupTask::GetAsync(StartupTaskName);

        switch (task.State())
        {
        case StartupTaskState::Disabled:
            if (tryEnableStartupTask)
            {
                co_await task.RequestEnableAsync();
            }
            break;
        case StartupTaskState::DisabledByUser:
            // TODO: GH#6254: define UX for other StartupTaskStates
            break;
        case StartupTaskState::Enabled:
            if (!tryEnableStartupTask)
            {
                task.Disable();
            }
            break;
        }
    }
    CATCH_LOG();

    // Method Description:
    // - Reloads the settings from the settings.json file.
    void AppLogic::_ReloadSettings()
    {
        // Attempt to load our settings.
        // If it fails,
        //  - don't change the settings (and don't actually apply the new settings)
        //  - don't persist them.
        //  - display a loading error
        _settingsLoadedResult = _TryLoadSettings();

        if (FAILED(_settingsLoadedResult))
        {
            const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
            const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
            _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
            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();

        Jumplist::UpdateJumplist(_settings);

        _SettingsChangedHandlers(*this, nullptr);
    }

    // Method Description:
    // - Returns a pointer to the global shared settings.
    [[nodiscard]] CascadiaSettings AppLogic::GetSettings() const noexcept
    {
        return _settings;
    }

    // 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::_ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme)
    {
        // Propagate the event to the host layer, so it can update its own UI
        _RequestedThemeChangedHandlers(*this, newTheme);
    }

    UIElement AppLogic::GetRoot() noexcept
    {
        return _root.as<winrt::Windows::UI::Xaml::Controls::Control>();
    }

    // Method Description:
    // - Gets the title of the currently focused terminal control. If there
    //   isn't a control selected for any reason, returns "Windows Terminal"
    // Arguments:
    // - <none>
    // Return Value:
    // - the title of the focused control if there is one, else "Windows Terminal"
    hstring AppLogic::Title()
    {
        if (_root)
        {
            return _root->Title();
        }
        return { L"Windows 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:
    // - <none>
    // Return Value:
    // - <none>
    void AppLogic::TitlebarClicked()
    {
        if (_root)
        {
            _root->TitlebarClicked();
        }
    }

    // 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<IDirectKeyListener>() })
                {
                    if (keyListener.OnDirectKeyEvent(vkey, scanCode, down))
                    {
                        return true;
                    }
                    // otherwise, keep walking. bubble the event manually.
                }

                if (auto focusedElement{ focusedObject.try_as<Windows::UI::Xaml::FrameworkElement>() })
                {
                    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:
    // - <none>
    // Return Value:
    // - <none>
    void AppLogic::WindowCloseButtonClicked()
    {
        if (_root)
        {
            _root->CloseWindow(false);
        }
    }

    winrt::TerminalApp::TaskbarState AppLogic::TaskbarState()
    {
        if (_root)
        {
            return _root->TaskbarState();
        }
        return {};
    }

    // 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<const winrt::hstring> 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();
            _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:
    // - <none>
    // Return Value:
    // - <none>
    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<const winrt::hstring> args,
                                         const winrt::hstring& cwd)
    {
        ::TerminalApp::AppCommandlineArgs appArgs;
        auto result = appArgs.ParseArgs(args);
        if (result == 0)
        {
            auto actions = winrt::single_threaded_vector<ActionAndArgs>(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
    //   whenever the commandline gets to the window it was intended for).
    // - Note that this function will only ever be called by the monarch. A
    //   return value of `0` in this case does not mean "run the commandline in
    //   _this_ process", rather it means "run the commandline in the current
    //   process", whoever that may be.
    // Arguments:
    // - args: an array of strings to process as a commandline. These args can contain spaces
    // Return Value:
    // - 0: We should handle the args "in the current window".
    // - WindowingBehaviorUseNew: We should handle the args in a new window
    // - WindowingBehaviorUseExisting: We should handle the args "in
    //   the current window ON THIS DESKTOP"
    // - WindowingBehaviorUseAnyExisting: We should handle the args "in the current
    //   window ON ANY DESKTOP"
    // - anything else: We should handle the commandline in the window with the given ID.
    TerminalApp::FindTargetWindowResult AppLogic::FindTargetWindow(array_view<const winrt::hstring> args)
    {
        if (!_loadedInitialSettings)
        {
            // Load settings if we haven't already
            LoadSettings();
        }

        return AppLogic::_doFindTargetWindow(args, _settings.GlobalSettings().WindowingBehavior());
    }

    // The main body of this function is a static helper, to facilitate unit-testing
    TerminalApp::FindTargetWindowResult AppLogic::_doFindTargetWindow(array_view<const winrt::hstring> args,
                                                                      const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior)
    {
        ::TerminalApp::AppCommandlineArgs appArgs;
        const auto result = appArgs.ParseArgs(args);
        if (result == 0)
        {
            if (!appArgs.GetExitMessage().empty())
            {
                return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseNew);
            }

            const std::string parsedTarget{ appArgs.GetTargetWindow() };

            // If the user did not provide any value on the commandline,
            // then lookup our windowing behavior to determine what to do
            // now.
            if (parsedTarget.empty())
            {
                int32_t windowId = WindowingBehaviorUseNew;
                switch (windowingBehavior)
                {
                case WindowingMode::UseNew:
                    windowId = WindowingBehaviorUseNew;
                    break;
                case WindowingMode::UseExisting:
                    windowId = WindowingBehaviorUseExisting;
                    break;
                case WindowingMode::UseAnyExisting:
                    windowId = WindowingBehaviorUseAnyExisting;
                    break;
                }
                return winrt::make<FindTargetWindowResult>(windowId);
            }

            // Here, the user _has_ provided a window-id on the commandline.
            // What is it? Let's start by checking if it's an int, for the
            // window's ID:
            try
            {
                int32_t windowId = ::base::saturated_cast<int32_t>(std::stoi(parsedTarget));

                // If the user provides _any_ negative number, then treat it as
                // -1, for "use a new window".
                if (windowId < 0)
                {
                    windowId = -1;
                }

                // Hooray! This is a valid integer. The set of possible values
                // here is {-1, 0, ℤ+}. Let's return that window ID.
                return winrt::make<FindTargetWindowResult>(windowId);
            }
            catch (...)
            {
                // Value was not a valid int. It could be any other string to
                // use as a title though!
                //
                // First, check the reserved keywords:
                if (parsedTarget == "new")
                {
                    return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseNew);
                }
                else if (parsedTarget == "last")
                {
                    return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseExisting);
                }
                else
                {
                    // The string they provided wasn't an int, it wasn't "new"
                    // or "last", so whatever it is, that's the name they get.
                    winrt::hstring winrtName{ til::u8u16(parsedTarget) };
                    return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseName, winrtName);
                }
            }
        }

        // Any unsuccessful parse will be a new window. That new window will try
        // to handle the commandline itself, and find that the commandline
        // failed to parse. When that happens, the new window will display the
        // message box.
        //
        // This will also work for the case where the user specifies an invalid
        // commandline in conjunction with `-w 0`. This function will determine
        // that the commandline has a  parse error, and indicate that we should
        // create a new window. Then, in that new window, we'll try to  set the
        // StartupActions, which will again fail, returning the correct error
        // message.
        return winrt::make<FindTargetWindowResult>(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:
    // - <none>
    // 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:
    // - <none>
    // 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;
    }

    bool AppLogic::AlwaysOnTop() const
    {
        return _root ? _root->AlwaysOnTop() : false;
    }

    Windows::Foundation::Collections::IMapView<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::Command> AppLogic::GlobalHotkeys()
    {
        return _settings.GlobalSettings().ActionMap().GlobalHotkeys();
    }

    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::SetNumberOfOpenWindows(const uint64_t num)
    {
        if (_root)
        {
            _root->SetNumberOfOpenWindows(num);
        }
    }

    void AppLogic::RenameFailed()
    {
        if (_root)
        {
            _root->RenameFailed();
        }
    }

    bool AppLogic::IsQuakeWindow() const noexcept
    {
        return _root->IsQuakeWindow();
    }

    bool AppLogic::GetMinimizeToNotificationArea()
    {
        if constexpr (Feature_NotificationIcon::IsEnabled())
        {
            if (!_loadedInitialSettings)
            {
                // Load settings if we haven't already
                LoadSettings();
            }

            return _settings.GlobalSettings().MinimizeToNotificationArea();
        }
        else
        {
            return false;
        }
    }

    bool AppLogic::GetAlwaysShowNotificationIcon()
    {
        if constexpr (Feature_NotificationIcon::IsEnabled())
        {
            if (!_loadedInitialSettings)
            {
                // Load settings if we haven't already
                LoadSettings();
            }

            return _settings.GlobalSettings().AlwaysShowNotificationIcon();
        }
        else
        {
            return false;
        }
    }

    bool AppLogic::GetShowTitleInTitlebar()
    {
        return _settings.GlobalSettings().ShowTitleInTitlebar();
    }
}
