From b7fa32881d5451222852ad9fcbd4517c0e8379c2 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Wed, 28 Apr 2021 03:43:30 -0700 Subject: [PATCH] Implement UI for choosing default terminal inside Settings page (#9907) Implement dropdown menu for choosing a default terminal application from inside the Windows Terminal Settings UI ## PR Checklist * [x] Closes #9463 * [x] I work here. * [x] Manual tests passed * [x] https://github.com/MicrosoftDocs/terminal/issues/314 (and cross reference #9462) ## Detailed Description of the Pull Request / Additional comments - Adds dropdown menu and a template card for displaying the available default applications (using the same lookup code as the console property sheet `console.dll`) - Adds model to TSM for adapting the data for display and binding on XAML - Lookup occurs on every page reload. Persistence only happens on Save Changes. - Manifest changed for Terminal to add capability to opt-out of registry redirection so we can edit this setting ## Validation Steps Performed - [x] Flipped the menu and pressed Save Changes and launched cmd from run box... it moved between the two. - [x] Flipped system theme from light to dark and ensured secondary color looked good - [x] Flipped the status with a different mechanism (conhost propsheet) and then reopened settings page and confirmed it loaded the updated status --- .github/actions/spelling/dictionary/apis.txt | 5 + .../actions/spelling/dictionary/microsoft.txt | 2 + .github/actions/spelling/expect/expect.txt | 2 + .../SettingsModel.LocalTests.vcxproj | 1 + .../TerminalSettingsEditor/Launch.cpp | 27 ++---- src/cascadia/TerminalSettingsEditor/Launch.h | 4 - .../TerminalSettingsEditor/Launch.idl | 3 - .../TerminalSettingsEditor/Launch.xaml | 63 +++++++++++-- .../Resources/en-US/Resources.resw | 2 +- .../CascadiaSettings.cpp | 85 ++++++++++++++++- .../TerminalSettingsModel/CascadiaSettings.h | 10 ++ .../CascadiaSettings.idl | 6 ++ .../CascadiaSettingsSerialization.cpp | 4 + .../TerminalSettingsModel/DefaultTerminal.cpp | 91 ++++++++++++++++++ .../TerminalSettingsModel/DefaultTerminal.h | 52 ++++++++++ .../TerminalSettingsModel/DefaultTerminal.idl | 16 ++++ ...crosoft.Terminal.Settings.ModelLib.vcxproj | 9 +- ...Terminal.Settings.ModelLib.vcxproj.filters | 8 +- .../Resources/en-US/Resources.resw | 64 +++++++------ .../Microsoft.Terminal.Settings.Model.vcxproj | 3 + .../ut_app/TerminalApp.UnitTests.vcxproj | 1 + src/propslib/DelegationConfig.cpp | 94 +++++++++++++++---- src/propslib/DelegationConfig.hpp | 11 ++- 23 files changed, 474 insertions(+), 89 deletions(-) create mode 100644 src/cascadia/TerminalSettingsModel/DefaultTerminal.cpp create mode 100644 src/cascadia/TerminalSettingsModel/DefaultTerminal.h create mode 100644 src/cascadia/TerminalSettingsModel/DefaultTerminal.idl diff --git a/.github/actions/spelling/dictionary/apis.txt b/.github/actions/spelling/dictionary/apis.txt index 6d75da302..68137d692 100644 --- a/.github/actions/spelling/dictionary/apis.txt +++ b/.github/actions/spelling/dictionary/apis.txt @@ -3,6 +3,7 @@ ACCESSDENIED alignof bitfield bitfields +BUILDNUMBER CLASSNOTAVAILABLE cmdletbinding colspan @@ -16,6 +17,7 @@ dcomp DERR dlldata DONTADDTORECENT +DWORDLONG environstrings EXPCMDFLAGS EXPCMDSTATE @@ -56,6 +58,7 @@ istream IStringable ITab ITaskbar +IUri IVirtual LCID llabs @@ -77,6 +80,8 @@ NOREDIRECTIONBITMAP ntprivapi oaidl ocidl +osver +OSVERSIONINFOEXW otms OUTLINETEXTMETRICW overridable diff --git a/.github/actions/spelling/dictionary/microsoft.txt b/.github/actions/spelling/dictionary/microsoft.txt index 68aa9fa70..e0097f8f7 100644 --- a/.github/actions/spelling/dictionary/microsoft.txt +++ b/.github/actions/spelling/dictionary/microsoft.txt @@ -43,8 +43,10 @@ systemroot taskkill tasklist tdbuildteamid +unvirtualized VCRT vcruntime +Virtualization visualstudio vscode VSTHRD diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 5ae175875..20c20fc14 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -170,6 +170,7 @@ blog Blt BLUESCROLL bmp +BODGY BOLDFONT BOOLIFY bools @@ -676,6 +677,7 @@ dwmapi dword dwrite dwriteglyphrundescriptionclustermap +dwl dxgi dxgidwm dxinterop diff --git a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj index 7576770b4..7de78c041 100644 --- a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj @@ -55,6 +55,7 @@ + diff --git a/src/cascadia/TerminalSettingsEditor/Launch.cpp b/src/cascadia/TerminalSettingsEditor/Launch.cpp index 3b86bb3d3..99b15cf73 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.cpp +++ b/src/cascadia/TerminalSettingsEditor/Launch.cpp @@ -19,11 +19,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_LaunchMode", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(WindowingBehavior, WindowingMode, WindowingMode, L"Globals_WindowingBehavior", L"Content"); + + // BODGY + // Xaml code generator for x:Bind to this will fail to find UnloadObject() on Launch class. + // To work around, check it ourselves on construction and FindName to force load. + // It's specified as x:Load=false in the XAML. So it only loads if this passes. + if (CascadiaSettings::IsDefaultTerminalAvailable()) + { + FindName(L"DefaultTerminalDropdown"); + } } void Launch::OnNavigatedTo(const NavigationEventArgs& e) { _State = e.Parameter().as(); + _State.Settings().RefreshDefaultTerminals(); } IInspectable Launch::CurrentDefaultProfile() @@ -37,21 +47,4 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation const auto profile{ winrt::unbox_value(value) }; _State.Settings().GlobalSettings().DefaultProfile(profile.Guid()); } - - // TODO GH#9463 - Complete hookup of Terminal UX to choose defapp. - Windows::Foundation::Collections::IObservableVector Launch::DefaultTerminals() - { - Windows::Foundation::Collections::IObservableVector vec; - return vec; - } - - IInspectable Launch::CurrentDefaultTerminal() - { - return nullptr; - } - - void Launch::CurrentDefaultTerminal(const IInspectable& value) - { - value; - } } diff --git a/src/cascadia/TerminalSettingsEditor/Launch.h b/src/cascadia/TerminalSettingsEditor/Launch.h index 3009e2c36..8219939e0 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.h +++ b/src/cascadia/TerminalSettingsEditor/Launch.h @@ -28,10 +28,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation IInspectable CurrentDefaultProfile(); void CurrentDefaultProfile(const IInspectable& value); - Windows::Foundation::Collections::IObservableVector DefaultTerminals(); - IInspectable CurrentDefaultTerminal(); - void CurrentDefaultTerminal(const IInspectable& value); - WINRT_PROPERTY(Editor::LaunchPageNavigationState, State, nullptr); GETSET_BINDABLE_ENUM_SETTING(LaunchMode, Model::LaunchMode, State().Settings().GlobalSettings, LaunchMode); diff --git a/src/cascadia/TerminalSettingsEditor/Launch.idl b/src/cascadia/TerminalSettingsEditor/Launch.idl index 62e6ba929..d59228bad 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.idl +++ b/src/cascadia/TerminalSettingsEditor/Launch.idl @@ -17,9 +17,6 @@ namespace Microsoft.Terminal.Settings.Editor IInspectable CurrentDefaultProfile; - IInspectable CurrentDefaultTerminal; - IObservableVector DefaultTerminals { get; }; - IInspectable CurrentLaunchMode; IObservableVector LaunchModeList { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 465895b89..d043e5472 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -70,16 +70,63 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + - --> diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 158fe1ff5..f48db5fce 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -226,7 +226,7 @@ A description for what the default profile is and when it's used. - Default terminal + Default terminal application Header for a drop down that permits the user to select which installed Terminal application will launch when command line tools like CMD are run from the Windows Explorer run box or start menu or anywhere else that they do not already have a graphical window assigned. diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 1cef8481b..142678a7b 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -48,7 +48,9 @@ CascadiaSettings::CascadiaSettings(const bool addDynamicProfiles) : _allProfiles{ winrt::single_threaded_observable_vector() }, _activeProfiles{ winrt::single_threaded_observable_vector() }, _warnings{ winrt::single_threaded_vector() }, - _deserializationErrorMessage{ L"" } + _deserializationErrorMessage{ L"" }, + _defaultTerminals{ winrt::single_threaded_observable_vector() }, + _currentDefaultTerminal{ nullptr } { if (addDynamicProfiles) { @@ -82,6 +84,9 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings:: settings->_userSettings = _userSettings; settings->_defaultSettings = _defaultSettings; + settings->_defaultTerminals = _defaultTerminals; + settings->_currentDefaultTerminal = _currentDefaultTerminal; + _CopyProfileInheritanceTree(settings); return *settings; @@ -999,3 +1004,81 @@ winrt::hstring CascadiaSettings::ApplicationVersion() return RS_(L"ApplicationVersionUnknown"); } + +// Method Description: +// - Forces a refresh of all default terminal state +// Arguments: +// - +// Return Value: +// - - Updates internal state +void CascadiaSettings::RefreshDefaultTerminals() +{ + _defaultTerminals.Clear(); + + for (const auto& term : Model::DefaultTerminal::Available()) + { + _defaultTerminals.Append(term); + } + + _currentDefaultTerminal = Model::DefaultTerminal::Current(); +} + +// Helper to do the version check +static bool _isOnBuildWithDefTerm() noexcept +{ + OSVERSIONINFOEXW osver{ 0 }; + osver.dwOSVersionInfoSize = sizeof(osver); + osver.dwBuildNumber = 21359; + + DWORDLONG dwlConditionMask = 0; + VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL); + + return VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask); +} + +// Method Description: +// - Determines if we're on an OS platform that supports +// the default terminal handoff functionality. +// Arguments: +// - +// Return Value: +// - True if OS supports default terminal. False otherwise. +bool CascadiaSettings::IsDefaultTerminalAvailable() noexcept +{ + // Cached on first use since the OS version shouldn't change while we're running. + static bool isAvailable = _isOnBuildWithDefTerm(); + return isAvailable; +} + +// Method Description: +// - Returns an iterable collection of all available terminals. +// Arguments: +// - +// Return Value: +// - an iterable collection of all available terminals that could be the default. +IObservableVector CascadiaSettings::DefaultTerminals() const noexcept +{ + return _defaultTerminals; +} + +// Method Description: +// - Returns the currently selected default terminal application +// Arguments: +// - +// Return Value: +// - the selected default terminal application +winrt::Microsoft::Terminal::Settings::Model::DefaultTerminal CascadiaSettings::CurrentDefaultTerminal() const noexcept +{ + return _currentDefaultTerminal; +} + +// Method Description: +// - Sets the current default terminal application +// Arguments: +// - terminal - Terminal from `DefaultTerminals` list to set as default +// Return Value: +// - +void CascadiaSettings::CurrentDefaultTerminal(winrt::Microsoft::Terminal::Settings::Model::DefaultTerminal terminal) +{ + _currentDefaultTerminal = terminal; +} diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 2dbeb4eda..e2d88e9b0 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -98,6 +98,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::guid GetProfileForArgs(const Model::NewTerminalArgs& newTerminalArgs) const; + void RefreshDefaultTerminals(); + + static bool IsDefaultTerminalAvailable() noexcept; + Windows::Foundation::Collections::IObservableVector DefaultTerminals() const noexcept; + Model::DefaultTerminal CurrentDefaultTerminal() const noexcept; + void CurrentDefaultTerminal(Model::DefaultTerminal terminal); + private: com_ptr _globals; Windows::Foundation::Collections::IObservableVector _allProfiles; @@ -106,6 +113,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Windows::Foundation::IReference _loadError; hstring _deserializationErrorMessage; + Windows::Foundation::Collections::IObservableVector _defaultTerminals; + Model::DefaultTerminal _currentDefaultTerminal; + std::vector> _profileGenerators; std::string _userSettingsString; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index eb71b85e4..6f5023541 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -4,6 +4,7 @@ import "GlobalAppSettings.idl"; import "Profile.idl"; import "TerminalWarnings.idl"; +import "DefaultTerminal.idl"; namespace Microsoft.Terminal.Settings.Model { @@ -42,5 +43,10 @@ namespace Microsoft.Terminal.Settings.Model void UpdateColorSchemeReferences(String oldName, String newName); Guid GetProfileForArgs(NewTerminalArgs newTerminalArgs); + + void RefreshDefaultTerminals(); + static Boolean IsDefaultTerminalAvailable { get; }; + Windows.Foundation.Collections.IObservableVector DefaultTerminals { get; }; + DefaultTerminal CurrentDefaultTerminal; } } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index e63775a19..c96b78005 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -1323,6 +1323,7 @@ const Json::Value& CascadiaSettings::_GetDisabledProfileSourcesJsonObject(const // Method Description: // - Write the current state of CascadiaSettings to our settings file // - Create a backup file with the current contents, if one does not exist +// - Persists the default terminal handler choice to the registry // Arguments: // - // Return Value: @@ -1348,6 +1349,9 @@ void CascadiaSettings::WriteSettingsToDisk() const const auto styledString{ Json::writeString(wbuilder, ToJson()) }; _WriteSettings(styledString, settingsPath); + + // Persists the default terminal choice + Model::DefaultTerminal::Current(_currentDefaultTerminal); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/DefaultTerminal.cpp b/src/cascadia/TerminalSettingsModel/DefaultTerminal.cpp new file mode 100644 index 000000000..8dced934b --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/DefaultTerminal.cpp @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "DefaultTerminal.h" +#include "DefaultTerminal.g.cpp" + +#include + +using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; + +winrt::Windows::Foundation::Collections::IVector DefaultTerminal::_available = winrt::single_threaded_vector(); +std::optional DefaultTerminal::_current; + +DefaultTerminal::DefaultTerminal(DelegationConfig::DelegationPackage pkg) : + _pkg(pkg) +{ +} + +winrt::hstring DefaultTerminal::Name() const +{ + static const std::wstring def{ RS_(L"InboxWindowsConsoleName") }; + return _pkg.terminal.name.empty() ? winrt::hstring{ def } : winrt::hstring{ _pkg.terminal.name }; +} + +winrt::hstring DefaultTerminal::Version() const +{ + // If there's no version information... return empty string instead. + if (DelegationConfig::PkgVersion{} == _pkg.terminal.version) + { + return winrt::hstring{}; + } + + const auto name = fmt::format(L"{}.{}.{}.{}", _pkg.terminal.version.major, _pkg.terminal.version.minor, _pkg.terminal.version.build, _pkg.terminal.version.revision); + return winrt::hstring{ name }; +} + +winrt::hstring DefaultTerminal::Author() const +{ + static const std::wstring def{ RS_(L"InboxWindowsConsoleAuthor") }; + return _pkg.terminal.author.empty() ? winrt::hstring{ def } : winrt::hstring{ _pkg.terminal.author }; +} + +winrt::hstring DefaultTerminal::Icon() const +{ + static const std::wstring_view def{ L"\uE756" }; + return _pkg.terminal.logo.empty() ? winrt::hstring{ def } : winrt::hstring{ _pkg.terminal.logo }; +} + +void DefaultTerminal::Refresh() +{ + std::vector allPackages; + DelegationConfig::DelegationPackage currentPackage; + + LOG_IF_FAILED(DelegationConfig::s_GetAvailablePackages(allPackages, currentPackage)); + + _available.Clear(); + for (const auto& pkg : allPackages) + { + auto p = winrt::make(pkg); + + _available.Append(p); + + if (pkg == currentPackage) + { + _current = p; + } + } +} + +winrt::Windows::Foundation::Collections::IVectorView DefaultTerminal::Available() +{ + Refresh(); + return _available.GetView(); +} + +Model::DefaultTerminal DefaultTerminal::Current() +{ + if (!_current) + { + Refresh(); + } + return _current.value(); +} + +void DefaultTerminal::Current(const Model::DefaultTerminal& term) +{ + THROW_IF_FAILED(DelegationConfig::s_SetDefaultByPackage(winrt::get_self(term)->_pkg, true)); + _current = term; +} diff --git a/src/cascadia/TerminalSettingsModel/DefaultTerminal.h b/src/cascadia/TerminalSettingsModel/DefaultTerminal.h new file mode 100644 index 000000000..56884f534 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/DefaultTerminal.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- DefaultTerminal.h + +Abstract: +- A Default Terminal is an application that can register + as the handler window or "terminal" for a command-line + application. This class is the model for presenting + handler options in the Windows Terminal Settings UI. + +Author(s): +- Michael Niksa - 20-Apr-2021 + +--*/ + +#pragma once + +#include "DefaultTerminal.g.h" +#include "../inc/cppwinrt_utils.h" + +#include "../../propslib/DelegationConfig.hpp" + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + struct DefaultTerminal : public DefaultTerminalT + { + DefaultTerminal(DelegationConfig::DelegationPackage pkg); + + hstring Name() const; + hstring Author() const; + hstring Version() const; + hstring Icon() const; + + static void Refresh(); + static Windows::Foundation::Collections::IVectorView Available(); + static Model::DefaultTerminal Current(); + static void Current(const Model::DefaultTerminal& term); + + private: + DelegationConfig::DelegationPackage _pkg; + static Windows::Foundation::Collections::IVector _available; + static std::optional _current; + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation +{ + BASIC_FACTORY(DefaultTerminal); +} diff --git a/src/cascadia/TerminalSettingsModel/DefaultTerminal.idl b/src/cascadia/TerminalSettingsModel/DefaultTerminal.idl new file mode 100644 index 000000000..9b3cc662d --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/DefaultTerminal.idl @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.Settings.Model +{ + runtimeclass DefaultTerminal + { + String Name { get; }; + String Author { get; }; + String Version { get; }; + String Icon { get; }; + + static DefaultTerminal Current; + static Windows.Foundation.Collections.IVectorView Available { get; }; + } +} diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index ef8229af2..09463cbea 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -16,6 +16,9 @@ + + DefaultTerminal.idl + IconPathConverter.idl @@ -71,6 +74,9 @@ + + DefaultTerminal.idl + IconPathConverter.idl @@ -137,6 +143,7 @@ + @@ -233,6 +240,6 @@ - + diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index 08b1d69af..62b725e66 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -33,6 +33,7 @@ + @@ -64,6 +65,7 @@ + @@ -77,6 +79,10 @@ + + + + @@ -89,4 +95,4 @@ {81a6314f-aa5b-4533-a499-13bc3a5c4af0} - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index c85aa0146..2f1ed50ad 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -1,17 +1,17 @@  - @@ -391,4 +391,12 @@ Rename window... - + + Microsoft Corporation + Paired with `InboxWindowsConsoleName`, this is the application author... which is us: Microsoft. + + + Windows Console Host + Name describing the usage of the classic windows console as the terminal UI. (`conhost.exe`) + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj index 5d2f1d87e..730a85182 100644 --- a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj +++ b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj @@ -52,6 +52,9 @@ {18D09A24-8240-42D6-8CB6-236EEE820263} + + {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC} + diff --git a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj index 626577956..822db5a74 100644 --- a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj +++ b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj @@ -52,6 +52,7 @@ + diff --git a/src/propslib/DelegationConfig.cpp b/src/propslib/DelegationConfig.cpp index cbd0727bb..1559676d9 100644 --- a/src/propslib/DelegationConfig.cpp +++ b/src/propslib/DelegationConfig.cpp @@ -84,6 +84,19 @@ HRESULT _lookupCatalog(PCWSTR extensionName, std::vector& vec) noexcept RETURN_IF_FAILED(extensionPackage2->get_PublisherDisplayName(publisher.GetAddressOf())); extensionMetadata.author = std::wstring{ publisher.GetRawBuffer(nullptr) }; + // Try to get the logo. Don't completely bail if we fail to get it. It's non-critical. + ComPtr logoUri; + LOG_IF_FAILED(extensionPackage2->get_Logo(logoUri.GetAddressOf())); + + // If we did manage to get one, extract the string and store in the structure + if (logoUri) + { + HString logo; + + RETURN_IF_FAILED(logoUri->get_AbsoluteUri(logo.GetAddressOf())); + extensionMetadata.logo = std::wstring{ logo.GetRawBuffer(nullptr) }; + } + HString pfn; RETURN_IF_FAILED(extensionPackageId->get_FamilyName(pfn.GetAddressOf())); extensionMetadata.pfn = std::wstring{ pfn.GetRawBuffer(nullptr) }; @@ -202,9 +215,9 @@ try // We also find the default here while we have the list of available ones so // we can return the opaque structure instead of the raw IID. IID defCon; - RETURN_IF_FAILED(s_GetDefaultConsoleId(defCon)); + LOG_IF_FAILED(s_GetDefaultConsoleId(defCon)); IID defTerm; - RETURN_IF_FAILED(s_GetDefaultTerminalId(defTerm)); + LOG_IF_FAILED(s_GetDefaultTerminalId(defTerm)); // The default one is the 0th one because that's supposed to be the inbox conhost one. DelegationPackage chosenPackage = packages.at(0); @@ -226,20 +239,20 @@ try } CATCH_RETURN() -[[nodiscard]] HRESULT DelegationConfig::s_SetDefaultConsoleById(const IID& iid) noexcept +[[nodiscard]] HRESULT DelegationConfig::s_SetDefaultConsoleById(const IID& iid, const bool useRegExe) noexcept { - return s_Set(DELEGATION_CONSOLE_KEY_NAME, iid); + return s_Set(DELEGATION_CONSOLE_KEY_NAME, iid, useRegExe); } -[[nodiscard]] HRESULT DelegationConfig::s_SetDefaultTerminalById(const IID& iid) noexcept +[[nodiscard]] HRESULT DelegationConfig::s_SetDefaultTerminalById(const IID& iid, const bool useRegExe) noexcept { - return s_Set(DELEGATION_TERMINAL_KEY_NAME, iid); + return s_Set(DELEGATION_TERMINAL_KEY_NAME, iid, useRegExe); } -[[nodiscard]] HRESULT DelegationConfig::s_SetDefaultByPackage(const DelegationPackage& package) noexcept +[[nodiscard]] HRESULT DelegationConfig::s_SetDefaultByPackage(const DelegationPackage& package, const bool useRegExe) noexcept { - RETURN_IF_FAILED(s_SetDefaultConsoleById(package.console.clsid)); - RETURN_IF_FAILED(s_SetDefaultTerminalById(package.terminal.clsid)); + RETURN_IF_FAILED(s_SetDefaultConsoleById(package.console.clsid, useRegExe)); + RETURN_IF_FAILED(s_SetDefaultTerminalById(package.terminal.clsid, useRegExe)); return S_OK; } @@ -294,23 +307,64 @@ CATCH_RETURN() return S_OK; } -[[nodiscard]] HRESULT DelegationConfig::s_Set(PCWSTR value, const CLSID clsid) noexcept +[[nodiscard]] HRESULT DelegationConfig::s_Set(PCWSTR value, const CLSID clsid, const bool useRegExe) noexcept try { - wil::unique_hkey currentUserKey; - wil::unique_hkey consoleKey; + // BODGY + // A Centennial application is not allowed to write the system registry and is redirected + // to a per-package copy-on-write hive. + // The restricted capability "unvirtualizedResources" can be combined with + // desktop6:RegistryWriteVirtualization to opt-out... but... + // - It will no longer be possible to double-click install through the App Installer + // - It requires a special exception to submit to the store + // - There MAY be some cleanup logic where the app catalog may try to undo + // whatever the package did. + // This works around it by shelling out to reg.exe because somehow that's just peachy. + if (useRegExe) + { + wil::unique_cotaskmem_string str; + RETURN_IF_FAILED(StringFromCLSID(clsid, &str)); - RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_OpenConsoleKey(¤tUserKey, &consoleKey)); + auto regExePath = wil::ExpandEnvironmentStringsW(L"%WINDIR%\\System32\\reg.exe"); - // Create method for registry is a "create if not exists, otherwise open" function. - wil::unique_hkey startupKey; - RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_CreateKey(consoleKey.get(), L"%%Startup", &startupKey)); + auto command = wil::str_printf(L"%s ADD HKCU\\Console\\%%%%Startup /v %s /t REG_SZ /d %s /f", regExePath.c_str(), value, str.get()); - wil::unique_cotaskmem_string str; - RETURN_IF_FAILED(StringFromCLSID(clsid, &str)); + wil::unique_process_information pi; + STARTUPINFOEX siEx{ 0 }; + siEx.StartupInfo.cb = sizeof(siEx); - RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_SetValue(startupKey.get(), value, REG_SZ, reinterpret_cast(str.get()), gsl::narrow(wcslen(str.get()) * sizeof(wchar_t)))); + RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW( + nullptr, + command.data(), + nullptr, // lpProcessAttributes + nullptr, // lpThreadAttributes + false, // bInheritHandles + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW, // dwCreationFlags + nullptr, // lpEnvironment + nullptr, + &siEx.StartupInfo, // lpStartupInfo + &pi // lpProcessInformation + )); - return S_OK; + return S_OK; + } + else + { + wil::unique_hkey currentUserKey; + wil::unique_hkey consoleKey; + + RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_OpenConsoleKey(¤tUserKey, &consoleKey)); + + // Create method for registry is a "create if not exists, otherwise open" function. + wil::unique_hkey startupKey; + RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_CreateKey(consoleKey.get(), L"%%Startup", &startupKey)); + + wil::unique_cotaskmem_string str; + RETURN_IF_FAILED(StringFromCLSID(clsid, &str)); + + RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_SetValue(startupKey.get(), value, REG_SZ, reinterpret_cast(str.get()), gsl::narrow(wcslen(str.get()) * sizeof(wchar_t)))); + + return S_OK; + } } CATCH_RETURN() diff --git a/src/propslib/DelegationConfig.hpp b/src/propslib/DelegationConfig.hpp index 9bc4ad89b..8590333d0 100644 --- a/src/propslib/DelegationConfig.hpp +++ b/src/propslib/DelegationConfig.hpp @@ -40,6 +40,7 @@ public: std::wstring name; std::wstring author; std::wstring pfn; + std::wstring logo; PkgVersion version; bool IsFromSamePackage(const DelegationBase& other) const @@ -80,9 +81,9 @@ public: } }; - [[nodiscard]] static HRESULT s_GetAvailablePackages(std::vector& packages, DelegationPackage& default) noexcept; + [[nodiscard]] static HRESULT s_GetAvailablePackages(std::vector& packages, DelegationPackage& def) noexcept; - [[nodiscard]] static HRESULT s_SetDefaultByPackage(const DelegationPackage& pkg) noexcept; + [[nodiscard]] static HRESULT s_SetDefaultByPackage(const DelegationPackage& pkg, const bool useRegExe = false) noexcept; [[nodiscard]] static HRESULT s_GetDefaultConsoleId(IID& iid) noexcept; [[nodiscard]] static HRESULT s_GetDefaultTerminalId(IID& iid) noexcept; @@ -91,9 +92,9 @@ private: [[nodiscard]] static HRESULT s_GetAvailableConsoles(std::vector& consoles) noexcept; [[nodiscard]] static HRESULT s_GetAvailableTerminals(std::vector& terminals) noexcept; - [[nodiscard]] static HRESULT s_SetDefaultConsoleById(const IID& iid) noexcept; - [[nodiscard]] static HRESULT s_SetDefaultTerminalById(const IID& iid) noexcept; + [[nodiscard]] static HRESULT s_SetDefaultConsoleById(const IID& iid, const bool useRegExe) noexcept; + [[nodiscard]] static HRESULT s_SetDefaultTerminalById(const IID& iid, const bool useRegExe) noexcept; [[nodiscard]] static HRESULT s_Get(PCWSTR value, IID& iid) noexcept; - [[nodiscard]] static HRESULT s_Set(PCWSTR value, const CLSID clsid) noexcept; + [[nodiscard]] static HRESULT s_Set(PCWSTR value, const CLSID clsid, const bool useRegExe) noexcept; };