Break everything out of App except Xaml platform init (#3465)
This commit breaks everything out of App except the base initialization for XAML. AppLogic is the new home for all terminal-specific singleton magic.
This commit is contained in:
parent
d26865f460
commit
3e8a1a78bc
|
@ -3,667 +3,60 @@
|
|||
|
||||
#include "pch.h"
|
||||
#include "App.h"
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "App.g.cpp"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace ::TerminalApp;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
namespace MUX = Microsoft::UI::Xaml;
|
||||
using IInspectable = Windows::Foundation::IInspectable;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// !!! IMPORTANT !!!
|
||||
// Make sure that these keys are in the same order as the
|
||||
// SettingsLoadWarnings/Errors enum is!
|
||||
static const std::array<std::wstring_view, 3> settingsLoadWarningsLabels {
|
||||
USES_RESOURCE(L"MissingDefaultProfileText"),
|
||||
USES_RESOURCE(L"DuplicateProfileText"),
|
||||
USES_RESOURCE(L"UnknownColorSchemeText")
|
||||
};
|
||||
static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
|
||||
USES_RESOURCE(L"NoProfilesText"),
|
||||
USES_RESOURCE(L"AllProfilesHiddenText")
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// 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<std::size_t N>
|
||||
static winrt::hstring _GetMessageText(uint32_t index, std::array<std::wstring_view, N> keys)
|
||||
{
|
||||
if (index < keys.size())
|
||||
{
|
||||
return GetLibraryResourceString(keys.at(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(::TerminalApp::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(::TerminalApp::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;
|
||||
}
|
||||
using namespace winrt;
|
||||
using namespace Windows::ApplicationModel;
|
||||
using namespace Windows::ApplicationModel::Activation;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::UI::Xaml::Controls;
|
||||
using namespace Windows::UI::Xaml::Navigation;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
App::App() :
|
||||
_dialogLock{},
|
||||
_loadedInitialSettings{ false },
|
||||
_settingsLoadedResult{ S_OK }
|
||||
App::App()
|
||||
{
|
||||
// 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.
|
||||
// This is the same trick that Initialize() is about to use to figure out whether we're coming
|
||||
// from a UWP context or from a Win32 context
|
||||
// See https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/blob/52611c57d89554f357f281d0c79036426a7d9257/Microsoft.Toolkit.Win32.UI.XamlApplication/XamlApplication.cpp#L42
|
||||
const auto dispatcherQueue = ::winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
|
||||
if (dispatcherQueue)
|
||||
{
|
||||
_isUwp = true;
|
||||
}
|
||||
|
||||
// Initialize will become protected or be deleted when GH#1339 (workaround for MSFT:22116519) are fixed.
|
||||
Initialize();
|
||||
|
||||
// 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<TerminalPage>();
|
||||
}
|
||||
|
||||
// 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 App::Create()
|
||||
AppLogic App::Logic()
|
||||
{
|
||||
// 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->ShowDialog({ this, &App::_ShowDialog });
|
||||
|
||||
_root->SetSettings(_settings, false);
|
||||
_root->Loaded({ this, &App::_OnLoaded });
|
||||
_root->Create();
|
||||
|
||||
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"AppCreated",
|
||||
TraceLoggingDescription("Event emitted when the application is started"),
|
||||
TraceLoggingBool(_settings->GlobalSettings().GetShowTabsInTitlebar(), "TabsInTitlebar"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
static AppLogic logic;
|
||||
return logic;
|
||||
}
|
||||
|
||||
// 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 termimal,
|
||||
// this allows the users to abondon 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:
|
||||
// sender: unused
|
||||
// dialog: the dialog object that is going to show up
|
||||
fire_and_forget App::_ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog)
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// </summary>
|
||||
/// <param name="e">Details about the launch request and process.</param>
|
||||
void App::OnLaunched(LaunchActivatedEventArgs const& /*e*/)
|
||||
{
|
||||
// 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)
|
||||
// if this is a UWP... it means its our problem to hook up the content to the window here.
|
||||
if (_isUwp)
|
||||
{
|
||||
// Another dialog is visible.
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
dialog.RequestedTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
|
||||
// Display the dialog.
|
||||
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 App::_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);
|
||||
|
||||
if (FAILED(settingsLoadedResult))
|
||||
{
|
||||
if (!_settingsLoadExceptionText.empty())
|
||||
auto content = Window::Current().Content();
|
||||
if (content == nullptr)
|
||||
{
|
||||
warningsTextBlock.Inlines().Append(_BuildErrorRun(_settingsLoadExceptionText, Resources()));
|
||||
auto logic = Logic();
|
||||
logic.LoadSettings();
|
||||
logic.Create();
|
||||
|
||||
auto page = logic.GetRoot().as<TerminalPage>();
|
||||
|
||||
Window::Current().Content(page);
|
||||
Window::Current().Activate();
|
||||
}
|
||||
}
|
||||
|
||||
// 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(usingDefaultsRun);
|
||||
|
||||
Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(winrt::box_value(warningsTextBlock));
|
||||
dialog.CloseButtonText(buttonText);
|
||||
|
||||
_ShowDialog(nullptr, 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 App::_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);
|
||||
|
||||
const auto& warnings = _settings->GetWarnings();
|
||||
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, Resources()));
|
||||
}
|
||||
}
|
||||
|
||||
Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(winrt::box_value(warningsTextBlock));
|
||||
dialog.CloseButtonText(buttonText);
|
||||
|
||||
_ShowDialog(nullptr, dialog);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Triggered when the application is fiished 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 App::_OnLoaded(const IInspectable& /*sender*/,
|
||||
const RoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
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:
|
||||
// - 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::Point App::GetLaunchDimensions(uint32_t dpi)
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
// Use the default profile to determine how big of a window we need.
|
||||
TerminalSettings settings = _settings->MakeSettings(std::nullopt);
|
||||
|
||||
// TODO MSFT:21150597 - 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.
|
||||
|
||||
return TermControl::GetProposedDimensions(settings, dpi);
|
||||
}
|
||||
|
||||
// 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 App::GetLaunchMode()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings->GlobalSettings().GetLaunchMode();
|
||||
}
|
||||
|
||||
// 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 defualt y coordinate value
|
||||
// Return Value:
|
||||
// - a point containing the requested initial position in pixels.
|
||||
winrt::Windows::Foundation::Point App::GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY)
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::Point point((float)defaultInitialX, (float)defaultInitialY);
|
||||
|
||||
auto initialX = _settings->GlobalSettings().GetInitialX();
|
||||
auto initialY = _settings->GlobalSettings().GetInitialY();
|
||||
if (initialX.has_value())
|
||||
{
|
||||
point.X = gsl::narrow_cast<float>(initialX.value());
|
||||
}
|
||||
if (initialY.has_value())
|
||||
{
|
||||
point.Y = gsl::narrow_cast<float>(initialY.value());
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::ElementTheme App::GetRequestedTheme()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings->GlobalSettings().GetRequestedTheme();
|
||||
}
|
||||
|
||||
bool App::GetShowTabsInTitlebar()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings->GlobalSettings().GetShowTabsInTitlebar();
|
||||
}
|
||||
|
||||
// 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 App::_TryLoadSettings() noexcept
|
||||
{
|
||||
HRESULT hr = E_FAIL;
|
||||
|
||||
try
|
||||
{
|
||||
auto newSettings = CascadiaSettings::LoadAll();
|
||||
_settings = std::move(newSettings);
|
||||
const auto& warnings = _settings->GetWarnings();
|
||||
hr = warnings.size() == 0 ? S_OK : S_FALSE;
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
hr = e.code();
|
||||
_settingsLoadExceptionText = e.message();
|
||||
LOG_HR(hr);
|
||||
}
|
||||
catch (const ::TerminalApp::SettingsException& ex)
|
||||
{
|
||||
hr = E_INVALIDARG;
|
||||
_settingsLoadExceptionText = _GetErrorText(ex.Error());
|
||||
}
|
||||
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 App::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();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Registers for changes to the settings folder and upon a updated settings
|
||||
// profile calls _ReloadSettings().
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void App::_RegisterSettingsChange()
|
||||
{
|
||||
// Get the containing folder.
|
||||
std::filesystem::path settingsPath{ CascadiaSettings::GetSettingsPath() };
|
||||
const auto folder = settingsPath.parent_path();
|
||||
|
||||
_reader.create(folder.c_str(),
|
||||
false,
|
||||
wil::FolderChangeEvents::All,
|
||||
[this, settingsPath](wil::FolderChangeEvent event, PCWSTR fileModified) {
|
||||
// We want file modifications, AND when files are renamed to be
|
||||
// profiles.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.
|
||||
if (!(event == wil::FolderChangeEvent::Modified ||
|
||||
event == wil::FolderChangeEvent::RenameNewName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path modifiedFilePath = fileModified;
|
||||
|
||||
// Getting basename (filename.ext)
|
||||
const auto settingsBasename = settingsPath.filename();
|
||||
const auto modifiedBasename = modifiedFilePath.filename();
|
||||
|
||||
if (settingsBasename == modifiedBasename)
|
||||
{
|
||||
this->_DispatchReloadSettings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dispatches a settings reload with debounce.
|
||||
// Text editors implement Save in a bunch of different ways, so
|
||||
// this stops us from reloading too many times or too quickly.
|
||||
fire_and_forget App::_DispatchReloadSettings()
|
||||
{
|
||||
static constexpr auto FileActivityQuiesceTime{ std::chrono::milliseconds(50) };
|
||||
if (!_settingsReloadQueued.exchange(true))
|
||||
{
|
||||
co_await winrt::resume_after(FileActivityQuiesceTime);
|
||||
_ReloadSettings();
|
||||
_settingsReloadQueued.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Reloads the settings from the profile.json.
|
||||
void App::_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))
|
||||
{
|
||||
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
|
||||
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
|
||||
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_settingsLoadedResult == S_FALSE)
|
||||
{
|
||||
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
_ShowLoadWarningsDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// Here, we successfully reloaded the settings, and created a new
|
||||
// TerminalSettings object.
|
||||
|
||||
// Update the settings in TerminalPage
|
||||
_root->SetSettings(_settings, true);
|
||||
|
||||
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
// Refresh the UI theme
|
||||
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
});
|
||||
}
|
||||
|
||||
// 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 App::_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 App::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 App::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 recieve 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 App::TitlebarClicked()
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
_root->TitlebarClicked();
|
||||
}
|
||||
}
|
||||
|
||||
// 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 App::WindowCloseButtonClicked()
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
_root->CloseWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(App, RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::ElementTheme);
|
||||
}
|
||||
|
|
|
@ -3,21 +3,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Tab.h"
|
||||
#include "CascadiaSettings.h"
|
||||
#include "TerminalPage.h"
|
||||
#include "App.g.h"
|
||||
#include "App.base.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
|
||||
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
|
@ -25,68 +12,12 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
public:
|
||||
App();
|
||||
~App() = default;
|
||||
void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&);
|
||||
|
||||
void Create();
|
||||
void LoadSettings();
|
||||
|
||||
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
||||
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
|
||||
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
bool GetShowTabsInTitlebar();
|
||||
|
||||
Windows::UI::Xaml::UIElement GetRoot() noexcept;
|
||||
|
||||
hstring Title();
|
||||
void TitlebarClicked();
|
||||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::ElementTheme);
|
||||
TerminalApp::AppLogic Logic();
|
||||
|
||||
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 app.
|
||||
// ALSO: If you add any UIElements as roots here, make sure they're
|
||||
// updated in _ApplyTheme. The root currently is _root.
|
||||
winrt::com_ptr<TerminalPage> _root{ nullptr };
|
||||
|
||||
std::shared_ptr<::TerminalApp::CascadiaSettings> _settings{ nullptr };
|
||||
|
||||
HRESULT _settingsLoadedResult;
|
||||
winrt::hstring _settingsLoadExceptionText{};
|
||||
|
||||
bool _loadedInitialSettings;
|
||||
|
||||
wil::unique_folder_change_reader_nothrow _reader;
|
||||
|
||||
std::shared_mutex _dialogLock;
|
||||
|
||||
std::atomic<bool> _settingsReloadQueued{ false };
|
||||
|
||||
fire_and_forget _ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
|
||||
void _ShowLoadWarningsDialog();
|
||||
|
||||
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
[[nodiscard]] HRESULT _TryLoadSettings() noexcept;
|
||||
void _RegisterSettingsChange();
|
||||
fire_and_forget _DispatchReloadSettings();
|
||||
void _ReloadSettings();
|
||||
|
||||
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
|
||||
|
||||
// These are events that are handled by the TerminalPage, but are
|
||||
// exposed through the App. 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(ToggleFullscreen, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs, _root, ToggleFullscreen);
|
||||
bool _isUwp = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,46 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../TerminalPage.idl";
|
||||
import "../AppLogic.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
enum LaunchMode
|
||||
{
|
||||
DefaultMode,
|
||||
MaximizedMode,
|
||||
};
|
||||
|
||||
// ADD ARBITRARY APP LOGIC TO APPLOGIC.IDL, NOT HERE.
|
||||
// This is for XAML platform setup only.
|
||||
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
|
||||
{
|
||||
App();
|
||||
|
||||
void Initialize();
|
||||
|
||||
// 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();
|
||||
|
||||
void LoadSettings();
|
||||
Windows.UI.Xaml.UIElement GetRoot();
|
||||
|
||||
String Title { get; };
|
||||
|
||||
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
|
||||
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
|
||||
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
Boolean GetShowTabsInTitlebar();
|
||||
void TitlebarClicked();
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
event Windows.Foundation.TypedEventHandler<App, Windows.UI.Xaml.ElementTheme> RequestedThemeChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ToggleFullscreenEventArgs> ToggleFullscreen;
|
||||
AppLogic Logic { get; };
|
||||
}
|
||||
}
|
||||
|
|
665
src/cascadia/TerminalApp/AppLogic.cpp
Normal file
665
src/cascadia/TerminalApp/AppLogic.cpp
Normal file
|
@ -0,0 +1,665 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AppLogic.h"
|
||||
#include "AppLogic.g.cpp"
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace ::TerminalApp;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
namespace MUX = Microsoft::UI::Xaml;
|
||||
using IInspectable = Windows::Foundation::IInspectable;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// !!! IMPORTANT !!!
|
||||
// Make sure that these keys are in the same order as the
|
||||
// SettingsLoadWarnings/Errors enum is!
|
||||
static const std::array<std::wstring_view, 3> settingsLoadWarningsLabels {
|
||||
USES_RESOURCE(L"MissingDefaultProfileText"),
|
||||
USES_RESOURCE(L"DuplicateProfileText"),
|
||||
USES_RESOURCE(L"UnknownColorSchemeText")
|
||||
};
|
||||
static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
|
||||
USES_RESOURCE(L"NoProfilesText"),
|
||||
USES_RESOURCE(L"AllProfilesHiddenText")
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// 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<std::size_t N>
|
||||
static winrt::hstring _GetMessageText(uint32_t index, std::array<std::wstring_view, N> keys)
|
||||
{
|
||||
if (index < keys.size())
|
||||
{
|
||||
return GetLibraryResourceString(keys.at(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(::TerminalApp::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(::TerminalApp::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;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
AppLogic::AppLogic() :
|
||||
_dialogLock{},
|
||||
_loadedInitialSettings{ false },
|
||||
_settingsLoadedResult{ S_OK }
|
||||
{
|
||||
// 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
|
||||
_root = winrt::make_self<TerminalPage>();
|
||||
}
|
||||
|
||||
// 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->ShowDialog({ this, &AppLogic::_ShowDialog });
|
||||
|
||||
_root->SetSettings(_settings, false);
|
||||
_root->Loaded({ this, &AppLogic::_OnLoaded });
|
||||
_root->Create();
|
||||
|
||||
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"AppCreated",
|
||||
TraceLoggingDescription("Event emitted when the application is started"),
|
||||
TraceLoggingBool(_settings->GlobalSettings().GetShowTabsInTitlebar(), "TabsInTitlebar"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
|
||||
// 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 termimal,
|
||||
// this allows the users to abondon 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:
|
||||
// sender: unused
|
||||
// dialog: the dialog object that is going to show up
|
||||
fire_and_forget AppLogic::_ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, 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.
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
dialog.RequestedTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
|
||||
// Display the dialog.
|
||||
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);
|
||||
|
||||
if (FAILED(settingsLoadedResult))
|
||||
{
|
||||
if (!_settingsLoadExceptionText.empty())
|
||||
{
|
||||
warningsTextBlock.Inlines().Append(_BuildErrorRun(_settingsLoadExceptionText, ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Resources()));
|
||||
}
|
||||
}
|
||||
|
||||
// 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(usingDefaultsRun);
|
||||
|
||||
Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(winrt::box_value(warningsTextBlock));
|
||||
dialog.CloseButtonText(buttonText);
|
||||
|
||||
_ShowDialog(nullptr, 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);
|
||||
|
||||
const auto& warnings = _settings->GetWarnings();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(winrt::box_value(warningsTextBlock));
|
||||
dialog.CloseButtonText(buttonText);
|
||||
|
||||
_ShowDialog(nullptr, dialog);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Triggered when the application is fiished 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 (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:
|
||||
// - 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::Point AppLogic::GetLaunchDimensions(uint32_t dpi)
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
// Use the default profile to determine how big of a window we need.
|
||||
TerminalSettings settings = _settings->MakeSettings(std::nullopt);
|
||||
|
||||
// TODO MSFT:21150597 - 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.
|
||||
|
||||
return TermControl::GetProposedDimensions(settings, dpi);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
return _settings->GlobalSettings().GetLaunchMode();
|
||||
}
|
||||
|
||||
// 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 defualt y coordinate value
|
||||
// Return Value:
|
||||
// - a point containing the requested initial position in pixels.
|
||||
winrt::Windows::Foundation::Point AppLogic::GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY)
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::Point point((float)defaultInitialX, (float)defaultInitialY);
|
||||
|
||||
auto initialX = _settings->GlobalSettings().GetInitialX();
|
||||
auto initialY = _settings->GlobalSettings().GetInitialY();
|
||||
if (initialX.has_value())
|
||||
{
|
||||
point.X = gsl::narrow_cast<float>(initialX.value());
|
||||
}
|
||||
if (initialY.has_value())
|
||||
{
|
||||
point.Y = gsl::narrow_cast<float>(initialY.value());
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::ElementTheme AppLogic::GetRequestedTheme()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings->GlobalSettings().GetRequestedTheme();
|
||||
}
|
||||
|
||||
bool AppLogic::GetShowTabsInTitlebar()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings->GlobalSettings().GetShowTabsInTitlebar();
|
||||
}
|
||||
|
||||
// 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 = CascadiaSettings::LoadAll();
|
||||
_settings = std::move(newSettings);
|
||||
const auto& warnings = _settings->GetWarnings();
|
||||
hr = warnings.size() == 0 ? S_OK : S_FALSE;
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
hr = e.code();
|
||||
_settingsLoadExceptionText = e.message();
|
||||
LOG_HR(hr);
|
||||
}
|
||||
catch (const ::TerminalApp::SettingsException& ex)
|
||||
{
|
||||
hr = E_INVALIDARG;
|
||||
_settingsLoadExceptionText = _GetErrorText(ex.Error());
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
// 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()
|
||||
{
|
||||
// Get the containing folder.
|
||||
std::filesystem::path settingsPath{ CascadiaSettings::GetSettingsPath() };
|
||||
const auto folder = settingsPath.parent_path();
|
||||
|
||||
_reader.create(folder.c_str(),
|
||||
false,
|
||||
wil::FolderChangeEvents::All,
|
||||
[this, settingsPath](wil::FolderChangeEvent event, PCWSTR fileModified) {
|
||||
// We want file modifications, AND when files are renamed to be
|
||||
// profiles.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.
|
||||
if (!(event == wil::FolderChangeEvent::Modified ||
|
||||
event == wil::FolderChangeEvent::RenameNewName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path modifiedFilePath = fileModified;
|
||||
|
||||
// Getting basename (filename.ext)
|
||||
const auto settingsBasename = settingsPath.filename();
|
||||
const auto modifiedBasename = modifiedFilePath.filename();
|
||||
|
||||
if (settingsBasename == modifiedBasename)
|
||||
{
|
||||
this->_DispatchReloadSettings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dispatches a settings reload with debounce.
|
||||
// Text editors implement Save in a bunch of different ways, so
|
||||
// this stops us from reloading too many times or too quickly.
|
||||
fire_and_forget AppLogic::_DispatchReloadSettings()
|
||||
{
|
||||
static constexpr auto FileActivityQuiesceTime{ std::chrono::milliseconds(50) };
|
||||
if (!_settingsReloadQueued.exchange(true))
|
||||
{
|
||||
co_await winrt::resume_after(FileActivityQuiesceTime);
|
||||
_ReloadSettings();
|
||||
_settingsReloadQueued.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Reloads the settings from the profile.json.
|
||||
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))
|
||||
{
|
||||
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
|
||||
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
|
||||
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_settingsLoadedResult == S_FALSE)
|
||||
{
|
||||
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
_ShowLoadWarningsDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// Here, we successfully reloaded the settings, and created a new
|
||||
// TerminalSettings object.
|
||||
|
||||
// Update the settings in TerminalPage
|
||||
_root->SetSettings(_settings, true);
|
||||
|
||||
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
// Refresh the UI theme
|
||||
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
});
|
||||
}
|
||||
|
||||
// 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 recieve 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:
|
||||
// - 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();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(AppLogic, RequestedThemeChanged, _requestedThemeChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme);
|
||||
}
|
98
src/cascadia/TerminalApp/AppLogic.h
Normal file
98
src/cascadia/TerminalApp/AppLogic.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AppLogic.g.h"
|
||||
|
||||
#include "Tab.h"
|
||||
#include "CascadiaSettings.h"
|
||||
#include "TerminalPage.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
|
||||
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct AppLogic : AppLogicT<AppLogic>
|
||||
{
|
||||
public:
|
||||
AppLogic();
|
||||
~AppLogic() = default;
|
||||
|
||||
void Create();
|
||||
void LoadSettings();
|
||||
|
||||
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
||||
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
|
||||
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
bool GetShowTabsInTitlebar();
|
||||
|
||||
Windows::UI::Xaml::UIElement GetRoot() noexcept;
|
||||
|
||||
hstring Title();
|
||||
void TitlebarClicked();
|
||||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(RequestedThemeChanged, _requestedThemeChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme);
|
||||
|
||||
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 _AppLogiclyTheme. The root currently is _root.
|
||||
winrt::com_ptr<TerminalPage> _root{ nullptr };
|
||||
|
||||
std::shared_ptr<::TerminalApp::CascadiaSettings> _settings{ nullptr };
|
||||
|
||||
HRESULT _settingsLoadedResult;
|
||||
winrt::hstring _settingsLoadExceptionText{};
|
||||
|
||||
bool _loadedInitialSettings;
|
||||
|
||||
wil::unique_folder_change_reader_nothrow _reader;
|
||||
|
||||
std::shared_mutex _dialogLock;
|
||||
|
||||
std::atomic<bool> _settingsReloadQueued{ false };
|
||||
|
||||
fire_and_forget _ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
|
||||
void _ShowLoadWarningsDialog();
|
||||
|
||||
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
[[nodiscard]] HRESULT _TryLoadSettings() noexcept;
|
||||
void _RegisterSettingsChange();
|
||||
fire_and_forget _DispatchReloadSettings();
|
||||
void _ReloadSettings();
|
||||
|
||||
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
|
||||
|
||||
// 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(ToggleFullscreen, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs, _root, ToggleFullscreen);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
struct AppLogic : AppLogicT<AppLogic, implementation::AppLogic>
|
||||
{
|
||||
};
|
||||
}
|
44
src/cascadia/TerminalApp/AppLogic.idl
Normal file
44
src/cascadia/TerminalApp/AppLogic.idl
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../TerminalPage.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
enum LaunchMode
|
||||
{
|
||||
DefaultMode,
|
||||
MaximizedMode,
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass AppLogic
|
||||
{
|
||||
AppLogic();
|
||||
|
||||
// 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();
|
||||
|
||||
void LoadSettings();
|
||||
Windows.UI.Xaml.UIElement GetRoot();
|
||||
|
||||
String Title { get; };
|
||||
|
||||
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
|
||||
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
|
||||
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
Boolean GetShowTabsInTitlebar();
|
||||
void TitlebarClicked();
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.ElementTheme> RequestedThemeChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ToggleFullscreenEventArgs> ToggleFullscreen;
|
||||
}
|
||||
}
|
|
@ -17,19 +17,17 @@
|
|||
-->
|
||||
<NoOutputRedirection>true</NoOutputRedirection>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
|
||||
<!-- ========================= XAML files ======================== -->
|
||||
<ItemGroup>
|
||||
<!-- DON'T PUT XAML FILES HERE! Put them in TerminalAppLib.vcxproj -->
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<!-- Only put headers for winrt types in here. Don't put other header files
|
||||
in here - put them in TerminalAppLib.vcxproj instead! -->
|
||||
<ClInclude Include="AppLogic.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="TerminalPage.h" />
|
||||
<ClInclude Include="MinMaxCloseControl.h" />
|
||||
|
@ -64,48 +62,38 @@
|
|||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
|
||||
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
<!-- The midl compiler however, _will_ aggregate our winmd dependencies
|
||||
somehow. So make sure to only include top-level dependencies here (don't
|
||||
include Settings and Connection, since Control will include them for us) -->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
|
||||
|
||||
<!-- Reference TerminalAppLib here, so we can use it's TerminalApp.winmd as
|
||||
our TerminalApp.winmd. This didn't work correctly in VS2017, you'd need to
|
||||
manually reference the lib -->
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj" >
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj">
|
||||
<Private>true</Private>
|
||||
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- A small helper for paths to the compiled cppwinrt projects -->
|
||||
<_BinRoot Condition="'$(Platform)' != 'Win32'">$(OpenConsoleDir)$(Platform)\$(Configuration)\</_BinRoot>
|
||||
<_BinRoot Condition="'$(Platform)' == 'Win32'">$(OpenConsoleDir)$(Configuration)\</_BinRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>User32.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
|
||||
<!-- TerminalAppLib contains a DllMain that we need to force the use of. -->
|
||||
<AdditionalOptions Condition="'$(Platform)'=='Win32'">/INCLUDE:_DllMain@12</AdditionalOptions>
|
||||
<AdditionalOptions Condition="'$(Platform)'!='Win32'">/INCLUDE:DllMain</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<!-- Import this set of targets that fixes a VS bug that manifests when using
|
||||
the TerminalAppLib project -->
|
||||
<Import Project="FixVisualStudioBug.targets" />
|
||||
|
||||
</Project>
|
||||
</Project>
|
|
@ -96,6 +96,9 @@
|
|||
<ClInclude Include="../App.h" >
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../AppLogic.h" >
|
||||
<DependentUpon>../AppLogic.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
|
@ -144,6 +147,9 @@
|
|||
<ClCompile Include="../AppActionHandlers.cpp" >
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../AppLogic.cpp" >
|
||||
<DependentUpon>../AppLogic.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
||||
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
|
||||
you want to use jsoncpp -->
|
||||
|
@ -161,6 +167,7 @@
|
|||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="../AppKeyBindings.idl" />
|
||||
<Midl Include="../AppLogic.idl" />
|
||||
<Midl Include="../ActionArgs.idl" />
|
||||
<Midl Include="../MinMaxCloseControl.idl">
|
||||
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
|
||||
|
|
|
@ -16,13 +16,16 @@ using namespace ::Microsoft::Console::Types;
|
|||
|
||||
AppHost::AppHost() noexcept :
|
||||
_app{},
|
||||
_logic{ nullptr }, // don't make one, we're going to take a ref on app's
|
||||
_window{ nullptr }
|
||||
{
|
||||
_useNonClientArea = _app.GetShowTabsInTitlebar();
|
||||
_logic = _app.Logic(); // get a ref to app's logic
|
||||
|
||||
_useNonClientArea = _logic.GetShowTabsInTitlebar();
|
||||
|
||||
if (_useNonClientArea)
|
||||
{
|
||||
_window = std::make_unique<NonClientIslandWindow>(_app.GetRequestedTheme());
|
||||
_window = std::make_unique<NonClientIslandWindow>(_logic.GetRequestedTheme());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -68,30 +71,30 @@ void AppHost::Initialize()
|
|||
// Register our callbar 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.
|
||||
_app.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent });
|
||||
_logic.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent });
|
||||
}
|
||||
|
||||
// Register the 'X' button of the window for a warning experience of multiple
|
||||
// tabs opened, this is consistent with Alt+F4 closing
|
||||
_window->WindowCloseButtonClicked([this]() { _app.WindowCloseButtonClicked(); });
|
||||
_window->WindowCloseButtonClicked([this]() { _logic.WindowCloseButtonClicked(); });
|
||||
|
||||
// Add an event handler to plumb clicks in the titlebar area down to the
|
||||
// application layer.
|
||||
_window->DragRegionClicked([this]() { _app.TitlebarClicked(); });
|
||||
_window->DragRegionClicked([this]() { _logic.TitlebarClicked(); });
|
||||
|
||||
_app.RequestedThemeChanged({ this, &AppHost::_UpdateTheme });
|
||||
_app.ToggleFullscreen({ this, &AppHost::_ToggleFullscreen });
|
||||
_logic.RequestedThemeChanged({ this, &AppHost::_UpdateTheme });
|
||||
_logic.ToggleFullscreen({ this, &AppHost::_ToggleFullscreen });
|
||||
|
||||
_app.Create();
|
||||
_logic.Create();
|
||||
|
||||
_app.TitleChanged({ this, &AppHost::AppTitleChanged });
|
||||
_app.LastTabClosed({ this, &AppHost::LastTabClosed });
|
||||
_logic.TitleChanged({ this, &AppHost::AppTitleChanged });
|
||||
_logic.LastTabClosed({ this, &AppHost::LastTabClosed });
|
||||
|
||||
_window->UpdateTitle(_app.Title());
|
||||
_window->UpdateTitle(_logic.Title());
|
||||
|
||||
// Set up the content of the application. If the app has a custom titlebar,
|
||||
// set that content as well.
|
||||
_window->SetContent(_app.GetRoot());
|
||||
_window->SetContent(_logic.GetRoot());
|
||||
_window->OnAppInitialized();
|
||||
}
|
||||
|
||||
|
@ -136,10 +139,10 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se
|
|||
// - None
|
||||
void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::TerminalApp::LaunchMode& launchMode)
|
||||
{
|
||||
launchMode = _app.GetLaunchMode();
|
||||
launchMode = _logic.GetLaunchMode();
|
||||
|
||||
// Acquire the actual intial position
|
||||
winrt::Windows::Foundation::Point initialPosition = _app.GetLaunchInitialPositions(proposedRect.left, proposedRect.top);
|
||||
winrt::Windows::Foundation::Point initialPosition = _logic.GetLaunchInitialPositions(proposedRect.left, proposedRect.top);
|
||||
proposedRect.left = gsl::narrow_cast<long>(initialPosition.X);
|
||||
proposedRect.top = gsl::narrow_cast<long>(initialPosition.Y);
|
||||
|
||||
|
@ -187,7 +190,7 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::Ter
|
|||
proposedRect.top = monitorInfo.rcWork.top;
|
||||
}
|
||||
|
||||
auto initialSize = _app.GetLaunchDimensions(dpix);
|
||||
auto initialSize = _logic.GetLaunchDimensions(dpix);
|
||||
|
||||
const short islandWidth = Utils::ClampToShortMax(
|
||||
static_cast<long>(ceil(initialSize.X)), 1);
|
||||
|
@ -264,7 +267,7 @@ void AppHost::_UpdateTitleBarContent(const winrt::Windows::Foundation::IInspecta
|
|||
// - arg: the ElementTheme to use as the new theme for the UI
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppHost::_UpdateTheme(const winrt::TerminalApp::App&, const winrt::Windows::UI::Xaml::ElementTheme& arg)
|
||||
void AppHost::_UpdateTheme(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::ElementTheme& arg)
|
||||
{
|
||||
_window->OnApplicationThemeChanged(arg);
|
||||
}
|
||||
|
|
|
@ -23,11 +23,12 @@ private:
|
|||
|
||||
std::unique_ptr<IslandWindow> _window;
|
||||
winrt::TerminalApp::App _app;
|
||||
winrt::TerminalApp::AppLogic _logic;
|
||||
|
||||
void _HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::TerminalApp::LaunchMode& launchMode);
|
||||
void _UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::UI::Xaml::UIElement& arg);
|
||||
void _UpdateTheme(const winrt::TerminalApp::App&,
|
||||
void _UpdateTheme(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Windows::UI::Xaml::ElementTheme& arg);
|
||||
void _ToggleFullscreen(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::TerminalApp::ToggleFullscreenEventArgs& arg);
|
||||
|
|
Loading…
Reference in a new issue