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
This commit is contained in:
Michael Niksa 2021-04-28 03:43:30 -07:00 committed by GitHub
parent 810ce6911b
commit b7fa32881d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 474 additions and 89 deletions

View file

@ -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

View file

@ -43,8 +43,10 @@ systemroot
taskkill
tasklist
tdbuildteamid
unvirtualized
VCRT
vcruntime
Virtualization
visualstudio
vscode
VSTHRD

View file

@ -170,6 +170,7 @@ blog
Blt
BLUESCROLL
bmp
BODGY
BOLDFONT
BOOLIFY
bools
@ -676,6 +677,7 @@ dwmapi
dword
dwrite
dwriteglyphrundescriptionclustermap
dwl
dxgi
dxgidwm
dxinterop

View file

@ -55,6 +55,7 @@
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\Microsoft.Terminal.Settings.ModelLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\propslib\propslib.vcxproj" />
<!-- If you don't reference these projects here, the
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->

View file

@ -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<Editor::LaunchPageNavigationState>();
_State.Settings().RefreshDefaultTerminals();
}
IInspectable Launch::CurrentDefaultProfile()
@ -37,21 +47,4 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto profile{ winrt::unbox_value<Model::Profile>(value) };
_State.Settings().GlobalSettings().DefaultProfile(profile.Guid());
}
// TODO GH#9463 - Complete hookup of Terminal UX to choose defapp.
Windows::Foundation::Collections::IObservableVector<IInspectable> Launch::DefaultTerminals()
{
Windows::Foundation::Collections::IObservableVector<IInspectable> vec;
return vec;
}
IInspectable Launch::CurrentDefaultTerminal()
{
return nullptr;
}
void Launch::CurrentDefaultTerminal(const IInspectable& value)
{
value;
}
}

View file

@ -28,10 +28,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
IInspectable CurrentDefaultProfile();
void CurrentDefaultProfile(const IInspectable& value);
Windows::Foundation::Collections::IObservableVector<IInspectable> 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);

View file

@ -17,9 +17,6 @@ namespace Microsoft.Terminal.Settings.Editor
IInspectable CurrentDefaultProfile;
IInspectable CurrentDefaultTerminal;
IObservableVector<IInspectable> DefaultTerminals { get; };
IInspectable CurrentLaunchMode;
IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> LaunchModeList { get; };

View file

@ -70,16 +70,63 @@
</local:SettingContainer>
<!-- Default Terminal -->
<!-- TODO GH#9463 - Complete hookup of Terminal UX to choose defapp. -->
<!--
<local:SettingContainer x:Uid="Globals_DefaultTerminal">
<local:SettingContainer x:Name="DefaultTerminalDropdown"
x:Uid="Globals_DefaultTerminal"
x:Load="false">
<ComboBox x:Name="DefaultTerminal"
x:Load="False"
ItemsSource="{x:Bind DefaultTerminals, Mode=OneWay}"
SelectedItem="{x:Bind CurrentDefaultTerminal, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
ItemsSource="{x:Bind State.Settings.DefaultTerminals, Mode=OneWay}"
SelectedItem="{x:Bind State.Settings.CurrentDefaultTerminal, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="SettingsModel:DefaultTerminal">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<!-- icon -->
<ColumnDefinition Width="24" />
<!-- profile name and author -->
<ColumnDefinition Width="*" />
<!-- version -->
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!-- profile name -->
<RowDefinition Height="*" />
<!-- author and version -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<IconSourceElement Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Width="24"
Height="24"
VerticalAlignment="Center"
IconSource="{x:Bind Icon, Mode=OneWay, Converter={StaticResource IconSourceConverter}}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Grid.ColumnSpan="2"
Text="{x:Bind Name}" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Foreground="{StaticResource SystemBaseMediumColor}"
Text="{x:Bind Author}" />
<TextBlock Grid.Row="1"
Grid.Column="2"
Foreground="{StaticResource SystemBaseMediumColor}"
Text="{x:Bind Version}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</local:SettingContainer>
-->
<!-- Start on User Login -->
<local:SettingContainer x:Uid="Globals_StartOnUserLogin">

View file

@ -226,7 +226,7 @@
<comment>A description for what the default profile is and when it's used.</comment>
</data>
<data name="Globals_DefaultTerminal.Header" xml:space="preserve">
<value>Default terminal</value>
<value>Default terminal application</value>
<comment>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.</comment>
</data>
<data name="Globals_DefaultTerminal.HelpText" xml:space="preserve">

View file

@ -48,7 +48,9 @@ CascadiaSettings::CascadiaSettings(const bool addDynamicProfiles) :
_allProfiles{ winrt::single_threaded_observable_vector<Model::Profile>() },
_activeProfiles{ winrt::single_threaded_observable_vector<Model::Profile>() },
_warnings{ winrt::single_threaded_vector<SettingsLoadWarnings>() },
_deserializationErrorMessage{ L"" }
_deserializationErrorMessage{ L"" },
_defaultTerminals{ winrt::single_threaded_observable_vector<Model::DefaultTerminal>() },
_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:
// - <none>
// Return Value:
// - <none> - 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:
// - <none>
// 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:
// - <none>
// Return Value:
// - an iterable collection of all available terminals that could be the default.
IObservableVector<winrt::Microsoft::Terminal::Settings::Model::DefaultTerminal> CascadiaSettings::DefaultTerminals() const noexcept
{
return _defaultTerminals;
}
// Method Description:
// - Returns the currently selected default terminal application
// Arguments:
// - <none>
// 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:
// - <none>
void CascadiaSettings::CurrentDefaultTerminal(winrt::Microsoft::Terminal::Settings::Model::DefaultTerminal terminal)
{
_currentDefaultTerminal = terminal;
}

View file

@ -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<Model::DefaultTerminal> DefaultTerminals() const noexcept;
Model::DefaultTerminal CurrentDefaultTerminal() const noexcept;
void CurrentDefaultTerminal(Model::DefaultTerminal terminal);
private:
com_ptr<GlobalAppSettings> _globals;
Windows::Foundation::Collections::IObservableVector<Model::Profile> _allProfiles;
@ -106,6 +113,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Windows::Foundation::IReference<SettingsLoadErrors> _loadError;
hstring _deserializationErrorMessage;
Windows::Foundation::Collections::IObservableVector<Model::DefaultTerminal> _defaultTerminals;
Model::DefaultTerminal _currentDefaultTerminal;
std::vector<std::unique_ptr<::Microsoft::Terminal::Settings::Model::IDynamicProfileGenerator>> _profileGenerators;
std::string _userSettingsString;

View file

@ -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<DefaultTerminal> DefaultTerminals { get; };
DefaultTerminal CurrentDefaultTerminal;
}
}

View file

@ -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:
// - <none>
// 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:

View file

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "DefaultTerminal.h"
#include "DefaultTerminal.g.cpp"
#include <LibraryResources.h>
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
winrt::Windows::Foundation::Collections::IVector<Model::DefaultTerminal> DefaultTerminal::_available = winrt::single_threaded_vector<Model::DefaultTerminal>();
std::optional<Model::DefaultTerminal> 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<DelegationConfig::DelegationPackage> allPackages;
DelegationConfig::DelegationPackage currentPackage;
LOG_IF_FAILED(DelegationConfig::s_GetAvailablePackages(allPackages, currentPackage));
_available.Clear();
for (const auto& pkg : allPackages)
{
auto p = winrt::make<DefaultTerminal>(pkg);
_available.Append(p);
if (pkg == currentPackage)
{
_current = p;
}
}
}
winrt::Windows::Foundation::Collections::IVectorView<Model::DefaultTerminal> 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<DefaultTerminal>(term)->_pkg, true));
_current = term;
}

View file

@ -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 <miniksa> - 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>
{
DefaultTerminal(DelegationConfig::DelegationPackage pkg);
hstring Name() const;
hstring Author() const;
hstring Version() const;
hstring Icon() const;
static void Refresh();
static Windows::Foundation::Collections::IVectorView<Model::DefaultTerminal> Available();
static Model::DefaultTerminal Current();
static void Current(const Model::DefaultTerminal& term);
private:
DelegationConfig::DelegationPackage _pkg;
static Windows::Foundation::Collections::IVector<Model::DefaultTerminal> _available;
static std::optional<Model::DefaultTerminal> _current;
};
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
{
BASIC_FACTORY(DefaultTerminal);
}

View file

@ -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<DefaultTerminal> Available { get; };
}
}

View file

@ -16,6 +16,9 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="DefaultTerminal.h" >
<DependentUpon>DefaultTerminal.idl</DependentUpon>
</ClInclude>
<ClInclude Include="IconPathConverter.h">
<DependentUpon>IconPathConverter.idl</DependentUpon>
</ClInclude>
@ -71,6 +74,9 @@
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="DefaultTerminal.cpp">
<DependentUpon>DefaultTerminal.idl</DependentUpon>
</ClCompile>
<ClCompile Include="IconPathConverter.cpp">
<DependentUpon>IconPathConverter.idl</DependentUpon>
</ClCompile>
@ -137,6 +143,7 @@
<Midl Include="CascadiaSettings.idl" />
<Midl Include="ColorScheme.idl" />
<Midl Include="Command.idl" />
<Midl Include="DefaultTerminal.idl" />
<Midl Include="GlobalAppSettings.idl" />
<Midl Include="IconPathConverter.idl" />
<Midl Include="KeyMapping.idl" />
@ -233,6 +240,6 @@
<Target Name="_TerminalAppGenerateUserSettingsH" Inputs="userDefaults.json" Outputs="Generated Files\userDefaults.h" BeforeTargets="BeforeClCompile">
<Exec Command="powershell.exe -noprofile ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile userDefaults.json -OutPath '&quot;Generated Files\userDefaults.h&quot;' -VariableName UserSettingsJson" />
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>

View file

@ -33,6 +33,7 @@
</ClCompile>
<ClCompile Include="init.cpp" />
<ClCompile Include="IconPathConverter.cpp" />
<ClCompile Include="DefaultTerminal.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@ -64,6 +65,7 @@
</ClInclude>
<ClInclude Include="IInheritable.h" />
<ClInclude Include="IconPathConverter.h" />
<ClInclude Include="DefaultTerminal.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="ActionArgs.idl" />
@ -77,6 +79,10 @@
<Midl Include="KeyChordSerialization.idl" />
<Midl Include="EnumMappings.idl" />
<Midl Include="IconPathConverter.idl" />
<Midl Include="TerminalSettings.idl" />
<Midl Include="AppearanceConfig.idl" />
<Midl Include="IAppearanceConfig.idl" />
<Midl Include="DefaultTerminal.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@ -89,4 +95,4 @@
<UniqueIdentifier>{81a6314f-aa5b-4533-a499-13bc3a5c4af0}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>
</Project>

View file

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -391,4 +391,12 @@
<data name="OpenWindowRenamerCommandKey" xml:space="preserve">
<value>Rename window...</value>
</data>
</root>
<data name="InboxWindowsConsoleAuthor" xml:space="preserve">
<value>Microsoft Corporation</value>
<comment>Paired with `InboxWindowsConsoleName`, this is the application author... which is us: Microsoft.</comment>
</data>
<data name="InboxWindowsConsoleName" xml:space="preserve">
<value>Windows Console Host</value>
<comment>Name describing the usage of the classic windows console as the terminal UI. (`conhost.exe`)</comment>
</data>
</root>

View file

@ -52,6 +52,9 @@
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\propslib\propslib.vcxproj">
<Project>{345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}</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) -->

View file

@ -52,6 +52,7 @@
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\TerminalAppLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\Microsoft.Terminal.Settings.ModelLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\propslib\propslib.vcxproj" />
</ItemGroup>
<!-- ========================= Globals ======================== -->

View file

@ -84,6 +84,19 @@ HRESULT _lookupCatalog(PCWSTR extensionName, std::vector<T>& 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<IUriRuntimeClass> 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(&currentUserKey, &consoleKey));
auto regExePath = wil::ExpandEnvironmentStringsW<std::wstring>(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<std::wstring>(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<BYTE*>(str.get()), gsl::narrow<DWORD>(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(&currentUserKey, &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<BYTE*>(str.get()), gsl::narrow<DWORD>(wcslen(str.get()) * sizeof(wchar_t))));
return S_OK;
}
}
CATCH_RETURN()

View file

@ -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<DelegationPackage>& packages, DelegationPackage& default) noexcept;
[[nodiscard]] static HRESULT s_GetAvailablePackages(std::vector<DelegationPackage>& 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<DelegationConsole>& consoles) noexcept;
[[nodiscard]] static HRESULT s_GetAvailableTerminals(std::vector<DelegationTerminal>& 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;
};