mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
CmdPal: Fix SUI crash ; Allow extensions to be disabled (#38040)
Both `TopLevelCommandItemWrapper` and `TopLevelViewModel` were really the same thing. The latter was from an earlier prototype, and the former is a more correct, safer abstraction. We really should have only ever used the former, but alas, we only used it for the SUI, and it piggy-backed off the latter, and that meant the latter's bugs became the former's. tldr: I made the icon access safe in the SUI. And while I was doing this, because we now have a cleaner VM abstraction here in the host, we can actually cleanly disable extensions, because the `CommandProviderWrapper` knows which `ViewModel`s it made. Closes https://github.com/zadjii-msft/PowerToys/issues/426 Closes https://github.com/zadjii-msft/PowerToys/issues/478 Closes https://github.com/zadjii-msft/PowerToys/issues/577
This commit is contained in:
@@ -2,13 +2,11 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.UI.Deferred;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
@@ -39,7 +37,14 @@ public partial class ContentIcon : FontIcon
|
||||
{
|
||||
if (this.FindDescendants().OfType<Grid>().FirstOrDefault() is Grid grid && Content is not null)
|
||||
{
|
||||
grid.Children.Add(Content);
|
||||
try
|
||||
{
|
||||
grid.Children.Add(Content);
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
Logger.LogError(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,18 +123,25 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
// Or the command may be a stub. Future us problem.
|
||||
try
|
||||
{
|
||||
var host = ViewModel.CurrentPage?.ExtensionHost ?? CommandPaletteHost.Instance;
|
||||
var pageHost = ViewModel.CurrentPage?.ExtensionHost;
|
||||
var messageHost = message.ExtensionHost;
|
||||
|
||||
if (command is TopLevelCommandWrapper wrapper)
|
||||
// Use the host from the current page if it has one, else use the
|
||||
// one specified in the PerformMessage for a top-level command,
|
||||
// else just use the global one.
|
||||
var host = pageHost ?? messageHost ?? CommandPaletteHost.Instance;
|
||||
extension = pageHost?.Extension ?? messageHost?.Extension ?? null;
|
||||
|
||||
if (extension != null)
|
||||
{
|
||||
var tlc = wrapper;
|
||||
command = wrapper.Command;
|
||||
host = tlc.ExtensionHost != null ? tlc.ExtensionHost! : host;
|
||||
extension = tlc.ExtensionHost?.Extension;
|
||||
if (extension != null)
|
||||
if (messageHost != null)
|
||||
{
|
||||
Logger.LogDebug($"Activated top-level command from {extension.ExtensionDisplayName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug($"Activated command from {extension.ExtensionDisplayName}");
|
||||
}
|
||||
}
|
||||
|
||||
ViewModel.SetActiveExtension(extension);
|
||||
@@ -476,8 +483,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
var topLevelCommand = tlcManager.LookupCommand(commandId);
|
||||
if (topLevelCommand != null)
|
||||
{
|
||||
var command = topLevelCommand.Command;
|
||||
var isPage = command is TopLevelCommandWrapper wrapper && wrapper.Command is not IInvokableCommand;
|
||||
var command = topLevelCommand.CommandViewModel.Model.Unsafe;
|
||||
var isPage = command is not IInvokableCommand;
|
||||
|
||||
// If the bound command is an invokable command, then
|
||||
// we don't want to open the window at all - we want to
|
||||
|
||||
@@ -48,75 +48,107 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<TextBlock x:Uid="ExtensionCommandsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<ItemsRepeater ItemsSource="{x:Bind ViewModel.TopLevelCommands, Mode=OneWay}" Layout="{StaticResource VerticalStackLayout}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:TopLevelViewModel">
|
||||
<controls:SettingsExpander
|
||||
DataContext="{x:Bind}"
|
||||
Description="{x:Bind Subtitle, Mode=OneWay}"
|
||||
Header="{x:Bind Title, Mode=OneWay}">
|
||||
<controls:SettingsExpander.HeaderIcon>
|
||||
<cpcontrols:ContentIcon>
|
||||
<cpcontrols:ContentIcon.Content>
|
||||
<cpcontrols:IconBox
|
||||
Width="20"
|
||||
Height="20"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind helpers:IconCacheProvider.SourceRequested}" />
|
||||
</cpcontrols:ContentIcon.Content>
|
||||
</cpcontrols:ContentIcon>
|
||||
</controls:SettingsExpander.HeaderIcon>
|
||||
|
||||
<!-- Content goes here -->
|
||||
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="Settings_ExtensionPage_GlobalHotkey_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<cpcontrols:ShortcutControl HotkeySettings="{x:Bind Hotkey, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_ExtensionPage_Alias_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBox Text="{x:Bind AliasText, Mode=TwoWay}" />
|
||||
<ToggleSwitch
|
||||
IsEnabled="{x:Bind AliasText, Converter={StaticResource StringEmptyToBoolConverter}, Mode=OneWay}"
|
||||
IsOn="{x:Bind IsDirectAlias, Mode=TwoWay}"
|
||||
OffContent="Indirect"
|
||||
OnContent="Direct" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
|
||||
<TextBlock
|
||||
x:Uid="ExtensionSettingsHeader"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Visibility="{x:Bind ViewModel.HasSettings}" />
|
||||
|
||||
<Frame x:Name="SettingsFrame" Visibility="{x:Bind ViewModel.HasSettings}">
|
||||
<cmdpalUI:ContentPage ViewModel="{x:Bind ViewModel.SettingsPage, Mode=OneWay}" />
|
||||
</Frame>
|
||||
|
||||
<TextBlock x:Uid="ExtensionAboutHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard
|
||||
Description="{x:Bind ViewModel.Extension.Publisher, Mode=OneWay}"
|
||||
Header="{x:Bind ViewModel.Extension.PackageDisplayName, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.IsFromExtension, Mode=OneWay}">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind ViewModel.ExtensionVersion}" />
|
||||
<controls:SettingsCard x:Uid="ExtensionEnableCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SwitchPresenter
|
||||
HorizontalAlignment="Stretch"
|
||||
TargetType="x:Boolean"
|
||||
Value="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
||||
<controls:Case Value="True">
|
||||
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<TextBlock x:Uid="ExtensionCommandsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<ItemsRepeater ItemsSource="{x:Bind ViewModel.TopLevelCommands, Mode=OneWay}" Layout="{StaticResource VerticalStackLayout}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:TopLevelViewModel">
|
||||
<controls:SettingsExpander
|
||||
DataContext="{x:Bind}"
|
||||
Description="{x:Bind Subtitle, Mode=OneWay}"
|
||||
Header="{x:Bind Title, Mode=OneWay}">
|
||||
<controls:SettingsExpander.HeaderIcon>
|
||||
<cpcontrols:ContentIcon>
|
||||
<cpcontrols:ContentIcon.Content>
|
||||
<cpcontrols:IconBox
|
||||
Width="20"
|
||||
Height="20"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind helpers:IconCacheProvider.SourceRequested}" />
|
||||
</cpcontrols:ContentIcon.Content>
|
||||
</cpcontrols:ContentIcon>
|
||||
</controls:SettingsExpander.HeaderIcon>
|
||||
|
||||
<!-- Content goes here -->
|
||||
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="Settings_ExtensionPage_GlobalHotkey_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<cpcontrols:ShortcutControl HotkeySettings="{x:Bind Hotkey, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_ExtensionPage_Alias_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBox Text="{x:Bind AliasText, Mode=TwoWay}" />
|
||||
<ToggleSwitch
|
||||
IsEnabled="{x:Bind AliasText, Converter={StaticResource StringEmptyToBoolConverter}, Mode=OneWay}"
|
||||
IsOn="{x:Bind IsDirectAlias, Mode=TwoWay}"
|
||||
OffContent="Indirect"
|
||||
OnContent="Direct" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
|
||||
<TextBlock
|
||||
x:Uid="ExtensionSettingsHeader"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Visibility="{x:Bind ViewModel.HasSettings}" />
|
||||
|
||||
<Frame x:Name="SettingsFrame" Visibility="{x:Bind ViewModel.HasSettings}">
|
||||
<cmdpalUI:ContentPage ViewModel="{x:Bind ViewModel.SettingsPage, Mode=OneWay}" />
|
||||
</Frame>
|
||||
|
||||
<TextBlock
|
||||
x:Uid="ExtensionAboutHeader"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Visibility="{x:Bind ViewModel.IsFromExtension, Mode=OneWay}" />
|
||||
|
||||
<controls:SettingsCard
|
||||
Description="{x:Bind ViewModel.Extension.Publisher, Mode=OneWay}"
|
||||
Header="{x:Bind ViewModel.Extension.PackageDisplayName, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.IsFromExtension, Mode=OneWay}">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind ViewModel.ExtensionVersion}" />
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="False">
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="ExtensionDisabledHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
<TextBlock x:Uid="ExtensionDisabledDetails" />
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
|
||||
<TextBlock
|
||||
x:Uid="ExtensionAboutHeader"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Visibility="{x:Bind ViewModel.IsFromExtension, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
<controls:SettingsCard x:Uid="Settings_ExtensionPage_Builtin_SettingsCard" Visibility="{x:Bind ViewModel.IsFromExtension, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -48,8 +48,7 @@
|
||||
</cpcontrols:ContentIcon>
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
|
||||
<!-- In the near future: add a toggle to actually expose if a extension is enabled -->
|
||||
<!-- <ToggleSwitch IsOn="{x:Bind IsEnabled, Mode=TwoWay}" /> -->
|
||||
<ToggleSwitch IsOn="{x:Bind IsEnabled, Mode=TwoWay}" />
|
||||
|
||||
</controls:SettingsCard>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Vertical">
|
||||
<HyperlinkButton x:Uid="Settings_GeneralPage_About_GithubLink_Hyperlink" NavigateUri="https://github.com/zadjii-msft/PowerToys" />
|
||||
<HyperlinkButton x:Uid="Settings_GeneralPage_About_SDKDocs_Hyperlink" NavigateUri="https://github.com/zadjii-msft/PowerToys/blob/main/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md" />
|
||||
<HyperlinkButton x:Uid="Settings_GeneralPage_About_GithubLink_Hyperlink" NavigateUri="https://go.microsoft.com/fwlink/?linkid=2310837" />
|
||||
<HyperlinkButton x:Uid="Settings_GeneralPage_About_SDKDocs_Hyperlink" NavigateUri="https://go.microsoft.com/fwlink/?linkid=2310639" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
@@ -90,7 +90,7 @@
|
||||
<HyperlinkButton
|
||||
x:Uid="Settings_GeneralPage_SendFeedback_Hyperlink"
|
||||
Margin="0,8,0,0"
|
||||
NavigateUri="https://github.com/zadjii-msft/PowerToys/issues/new" />
|
||||
NavigateUri="https://go.microsoft.com/fwlink/?linkid=2310638" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -42,7 +42,31 @@
|
||||
<NavigationView.Resources>
|
||||
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="NavigationViewContentGridBorderBrush" Color="Transparent" />
|
||||
<Thickness x:Key="NavigationViewHeaderMargin">15,0,0,0</Thickness>
|
||||
</NavigationView.Resources>
|
||||
|
||||
<NavigationView.Header>
|
||||
<BreadcrumbBar
|
||||
x:Name="NavigationBreadcrumbBar"
|
||||
MaxWidth="1000"
|
||||
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
|
||||
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
|
||||
<BreadcrumbBar.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:Crumb">
|
||||
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</BreadcrumbBar.ItemTemplate>
|
||||
<BreadcrumbBar.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">28</x:Double>
|
||||
<Thickness x:Key="BreadcrumbBarChevronPadding">7,4,8,0</Thickness>
|
||||
<FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</FontWeight>
|
||||
<x:Double x:Key="BreadcrumbBarChevronFontSize">16</x:Double>
|
||||
</ResourceDictionary>
|
||||
</BreadcrumbBar.Resources>
|
||||
</BreadcrumbBar>
|
||||
|
||||
</NavigationView.Header>
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_General"
|
||||
@@ -60,17 +84,6 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<BreadcrumbBar
|
||||
x:Name="NavigationBreadcrumbBar"
|
||||
MaxWidth="1000"
|
||||
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
|
||||
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
|
||||
<BreadcrumbBar.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:Crumb">
|
||||
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind Label, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</BreadcrumbBar.ItemTemplate>
|
||||
</BreadcrumbBar>
|
||||
<Frame x:Name="NavFrame" Grid.Row="1" />
|
||||
</Grid>
|
||||
</NavigationView>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.UI.Pages;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -15,7 +15,8 @@ using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
|
||||
namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
public sealed partial class SettingsWindow : Window,
|
||||
IRecipient<NavigateToExtensionSettingsMessage>
|
||||
IRecipient<NavigateToExtensionSettingsMessage>,
|
||||
IRecipient<QuitMessage>
|
||||
{
|
||||
public ObservableCollection<Crumb> BreadCrumbs { get; } = [];
|
||||
|
||||
@@ -27,7 +28,9 @@ public sealed partial class SettingsWindow : Window,
|
||||
this.AppWindow.Title = RS_.GetString("SettingsWindowTitle");
|
||||
this.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
|
||||
PositionCentered();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<NavigateToExtensionSettingsMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
}
|
||||
|
||||
private void NavView_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -101,6 +104,12 @@ public sealed partial class SettingsWindow : Window,
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<WindowActivatedEventArgs>(args);
|
||||
}
|
||||
|
||||
public void Receive(QuitMessage message)
|
||||
{
|
||||
// This might come in on a background thread
|
||||
DispatcherQueue.TryEnqueue(() => Close());
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Crumb
|
||||
|
||||
@@ -241,6 +241,26 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<value>Commands</value>
|
||||
<comment>A section header for information about the app</comment>
|
||||
</data>
|
||||
<data name="ExtensionDisabledHeader.Text" xml:space="preserve">
|
||||
<value>This extension is disabled</value>
|
||||
<comment>A header to inform the user that an extension is not currently active</comment>
|
||||
</data>
|
||||
<data name="ExtensionDisabledDetails.Text" xml:space="preserve">
|
||||
<value>Enable this extension to view commands and settings</value>
|
||||
<comment>Additional details for when an extension is disabled. Displayed under ExtensionDisabledHeader.Text</comment>
|
||||
</data>
|
||||
<data name="ExtensionDisabledText" xml:space="preserve">
|
||||
<value>Disabled</value>
|
||||
<comment>Displayed when an extension is disabled</comment>
|
||||
</data>
|
||||
<data name="ExtensionEnableCard.Header" xml:space="preserve">
|
||||
<value>Enable this extension</value>
|
||||
<comment>Displayed on a toggle controlling the extension's enabled / disabled state</comment>
|
||||
</data>
|
||||
<data name="ExtensionEnableCard.Description" xml:space="preserve">
|
||||
<value>Load commands and settings from this extension</value>
|
||||
<comment>Displayed on a toggle controlling the extension's enabled / disabled state</comment>
|
||||
</data>
|
||||
<data name="SettingsWindowTitle" xml:space="preserve">
|
||||
<value>Command Palette Settings</value>
|
||||
<comment>The title of the settings window for the app</comment>
|
||||
|
||||
Reference in New Issue
Block a user