From 3007f046d38bce0bfde6b35a498367bdef3a1be7 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 3 Apr 2025 14:08:55 -0500 Subject: [PATCH 1/6] for felipe and lauren --- .../Pages/SampleListPage.cs | 28 +++++++++++++++++++ .../SamplePagesExtension.csproj | 6 ++++ .../SamplePagesExtension/NativeMethods.txt | 3 ++ 3 files changed, 37 insertions(+) create mode 100644 src/modules/cmdpal/exts/SamplePagesExtension/NativeMethods.txt diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs index 3f65bef9429b..229676c305d5 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs @@ -4,6 +4,7 @@ using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.Win32; namespace SamplePagesExtension; @@ -91,6 +92,33 @@ public override IListItem[] GetItems() }) { Title = "Confirm twice before doing something", + }, + new ListItem( + new AnonymousCommand(() => + { + var fg = PInvoke.GetForegroundWindow(); + var bufferSize = PInvoke.GetWindowTextLength(fg) + 1; + unsafe + { + fixed (char* windowNameChars = new char[bufferSize]) + { + if (PInvoke.GetWindowText(fg, windowNameChars, bufferSize) == 0) + { + var emptyToast = new ToastStatusMessage(new StatusMessage() { Message = "FG Window didn't have a title", State = MessageState.Warning }); + emptyToast.Show(); + } + + var windowName = new string(windowNameChars); + var nameToast = new ToastStatusMessage(new StatusMessage() { Message = $"FG Window is {windowName}", State = MessageState.Success }); + nameToast.Show(); + } + } + }) + { + Result = CommandResult.KeepOpen(), + }) + { + Title = "Get the name of the Foreground window", } ]; } diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesExtension.csproj b/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesExtension.csproj index c00df9b84e13..e1a9cc20dd16 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesExtension.csproj +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesExtension.csproj @@ -33,6 +33,12 @@ + + + all + runtime; build; native; contentfiles; analyzers + + + Text="{x:Bind RequestedShortcut, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" /> diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs index f41ac98ea3e0..68a5998d1e50 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs @@ -8,6 +8,8 @@ using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.Views; +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.UI.Dispatching; using Microsoft.UI.Input; using Microsoft.UI.Xaml; @@ -21,6 +23,7 @@ namespace Microsoft.CmdPal.UI.Controls; public sealed partial class SearchBar : UserControl, IRecipient, IRecipient, + IRecipient, ICurrentPageAware { private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread(); @@ -31,6 +34,8 @@ public sealed partial class SearchBar : UserControl, private readonly DispatcherQueueTimer _debounceTimer = DispatcherQueue.GetForCurrentThread().CreateTimer(); private bool _isBackspaceHeld; + private Dictionary? _keybinds; + public PageViewModel? CurrentPageViewModel { get => (PageViewModel?)GetValue(CurrentPageViewModelProperty); @@ -69,6 +74,7 @@ public SearchBar() this.InitializeComponent(); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); + WeakReferenceMessenger.Default.Register(this); } public void ClearSearch() @@ -105,7 +111,9 @@ private void FilterBox_KeyDown(object sender, KeyRoutedEventArgs e) var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down); var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down); - + var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down); + var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) || + InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down); if (ctrlPressed && e.Key == VirtualKey.Enter) { // ctrl+enter @@ -164,6 +172,19 @@ private void FilterBox_KeyDown(object sender, KeyRoutedEventArgs e) { WeakReferenceMessenger.Default.Send(new()); } + + if (_keybinds != null) + { + // Does the pressecd key match any of the keybinds? + var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrlPressed, altPressed, shiftPressed, winPressed, (int)e.Key, 0); + if (_keybinds.TryGetValue(pressedKeyChord, out var item)) + { + // TODO GH #245: This is a bit of a hack, but we need to make sure that the keybinds are updated before we send the message + // so that the correct item is activated. + WeakReferenceMessenger.Default.Send(new(item)); + e.Handled = true; + } + } } private void FilterBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) @@ -269,4 +290,9 @@ private void Page_PropertyChanged(object? sender, System.ComponentModel.Property public void Receive(GoHomeMessage message) => ClearSearch(); public void Receive(FocusSearchBoxMessage message) => this.Focus(Microsoft.UI.Xaml.FocusState.Programmatic); + + public void Receive(UpdateItemKeybindsMessage message) + { + _keybinds = message.Keys; + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs index 36c9394ece5c..8ed69dc4bf4d 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs @@ -187,6 +187,8 @@ private void PerformCommand(PerformCommandMessage message) WeakReferenceMessenger.Default.Send(new(null)); + WeakReferenceMessenger.Default.Send(new(null)); + var isMainPage = command is MainListPage; // Construct our ViewModel of the appropriate type and pass it the UI Thread context. @@ -427,8 +429,6 @@ public void Receive(OpenSettingsMessage message) } _settingsWindow.Activate(); - - WeakReferenceMessenger.Default.Send(new(null)); }); } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs index 6c6a0ac5c51a..c6b73b670f85 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs @@ -2,14 +2,19 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Windows.Foundation; using Windows.System; namespace Microsoft.CommandPalette.Extensions.Toolkit; public partial class KeyChordHelpers { - public static KeyChord FromModifiers(bool ctrl, bool alt, bool shift, bool win, int vkey, int scanCode) + public static KeyChord FromModifiers( + bool ctrl = false, + bool alt = false, + bool shift = false, + bool win = false, + int vkey = 0, + int scanCode = 0) { var modifiers = (ctrl ? VirtualKeyModifiers.Control : VirtualKeyModifiers.None) | (alt ? VirtualKeyModifiers.Menu : VirtualKeyModifiers.None) From b53cc6fdb778da7102d55817886f3df4990d339e Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 4 Apr 2025 08:27:25 -0500 Subject: [PATCH 3/6] add samples --- .../Pages/SampleListPage.cs | 65 ++++++++++++++++++- .../KeyChordHelpers.cs | 11 ++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs index 229676c305d5..7d8716b5b89d 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs @@ -4,6 +4,7 @@ using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.System; using Windows.Win32; namespace SamplePagesExtension; @@ -66,6 +67,67 @@ public override IListItem[] GetItems() Subtitle = "and I'll take you to a page with markdown content", Tags = [new Tag("Sample Tag")], }, + + new ListItem( + new AnonymousCommand(() => + { + var t = new ToastStatusMessage(new StatusMessage() + { + Message = "Primary command invoked", + State = MessageState.Info, + }); + t.Show(); + }) + { + Result = CommandResult.KeepOpen(), + }) + { + Title = "You can add context menu items too. Press Ctrl+k", + Subtitle = "Try pressing Ctrl+1 with me selected", + Icon = new IconInfo("\uE712"), + MoreCommands = [ + new CommandContextItem( + new AnonymousCommand(() => + { + var t = new ToastStatusMessage(new StatusMessage() + { + Message = "Secondary command invoked", + State = MessageState.Warning, + }); + t.Show(); + }) + { + Name = "Secondary command", + Icon = new IconInfo("\uF147"), // Dial 2 + Result = CommandResult.KeepOpen(), + }) + { + Title = "I'm a second command", + RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1), + }, + new CommandContextItem( + new AnonymousCommand(() => + { + var t = new ToastStatusMessage(new StatusMessage() + { + Message = "Third command invoked", + State = MessageState.Error, + }); + t.Show(); + }) + { + Name = "Do it", + Icon = new IconInfo("\uF148"), // dial 3 + Result = CommandResult.KeepOpen(), + }) + { + Title = "A third command too", + Icon = new IconInfo("\uF148"), + RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number2), + } + ], + }, + new ListItem(new SendMessageCommand()) { Title = "I send lots of messages", @@ -119,7 +181,8 @@ public override IListItem[] GetItems() }) { Title = "Get the name of the Foreground window", - } + }, + ]; } } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs index c6b73b670f85..b037941da4c5 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs @@ -23,4 +23,15 @@ public static KeyChord FromModifiers( ; return new(modifiers, vkey, scanCode); } + + public static KeyChord FromModifiers( + bool ctrl = false, + bool alt = false, + bool shift = false, + bool win = false, + VirtualKey vkey = VirtualKey.None, + int scanCode = 0) + { + return FromModifiers(ctrl, alt, shift, win, (int)vkey, scanCode); + } } From 65f1d991b9447e377c097a3d2f56431ff75e08f7 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 4 Apr 2025 14:26:59 -0500 Subject: [PATCH 4/6] context menu too --- .../Pages/SampleListPage.cs | 1 + .../CommandBarViewModel.cs | 29 ++++++++++++++++++- .../ListViewModel.cs | 2 +- ...age.cs => UpdateItemKeybindingsMessage.cs} | 2 +- .../Controls/CommandBar.xaml | 1 + .../Controls/CommandBar.xaml.cs | 22 ++++++++++++++ .../Controls/SearchBar.xaml.cs | 6 ++-- .../Pages/ShellPage.xaml.cs | 2 +- 8 files changed, 58 insertions(+), 7 deletions(-) rename src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/{UpdateItemKeybindsMessage.cs => UpdateItemKeybindingsMessage.cs} (73%) diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs index 7d8716b5b89d..954a79ce04aa 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs @@ -80,6 +80,7 @@ public override IListItem[] GetItems() }) { Result = CommandResult.KeepOpen(), + Icon = new IconInfo("\uE712"), }) { Title = "You can add context menu items too. Press Ctrl+k", diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs index 9b5be8a973e0..baaafcd57cc6 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs @@ -7,11 +7,15 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.UI.ViewModels.Messages; +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.System; namespace Microsoft.CmdPal.UI.ViewModels; public partial class CommandBarViewModel : ObservableObject, - IRecipient + IRecipient, + IRecipient { public ICommandBarContext? SelectedItem { @@ -49,13 +53,18 @@ public ICommandBarContext? SelectedItem [ObservableProperty] public partial ObservableCollection ContextCommands { get; set; } = []; + private Dictionary? _contextKeybinds; + public CommandBarViewModel() { WeakReferenceMessenger.Default.Register(this); + WeakReferenceMessenger.Default.Register(this); } public void Receive(UpdateCommandBarMessage message) => SelectedItem = message.ViewModel; + public void Receive(UpdateItemKeybindingsMessage message) => _contextKeybinds = message.Keys; + private void SetSelectedItem(ICommandBarContext? value) { if (value != null) @@ -131,4 +140,22 @@ public void InvokeSecondaryCommand() WeakReferenceMessenger.Default.Send(new(SecondaryCommand.Command.Model, SecondaryCommand.Model)); } } + + public bool CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key) + { + if (_contextKeybinds != null) + { + // Does the pressecd key match any of the keybinds? + var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0); + if (_contextKeybinds.TryGetValue(pressedKeyChord, out var item)) + { + // TODO GH #245: This is a bit of a hack, but we need to make sure that the keybinds are updated before we send the message + // so that the correct item is activated. + WeakReferenceMessenger.Default.Send(new(item)); + return true; + } + } + + return false; + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs index f7617528ee3e..c728a434fb86 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs @@ -342,7 +342,7 @@ private void UpdateSelectedItem(ListItemViewModel item) { WeakReferenceMessenger.Default.Send(new(item)); - WeakReferenceMessenger.Default.Send(new(item.Keybindings())); + WeakReferenceMessenger.Default.Send(new(item.Keybindings())); if (ShowDetails && item.HasDetails) { diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindsMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs similarity index 73% rename from src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindsMessage.cs rename to src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs index 885fe8186b4b..2054d3d8fd5f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindsMessage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs @@ -6,4 +6,4 @@ namespace Microsoft.CmdPal.UI.ViewModels.Messages; -public record UpdateItemKeybindsMessage(Dictionary? Keys); +public record UpdateItemKeybindingsMessage(Dictionary? Keys); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml index ac7c20aa9249..4a692fcc20fe 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml @@ -263,6 +263,7 @@ ItemClick="CommandsDropdown_ItemClick" ItemTemplate="{StaticResource ContextMenuViewModelTemplate}" ItemsSource="{x:Bind ViewModel.ContextCommands, Mode=OneWay}" + KeyDown="CommandsDropdown_KeyDown" SelectionMode="None">