From 43e9959df4112bceea0c9df5fab814adccbcc224 Mon Sep 17 00:00:00 2001 From: Noraa Junker Date: Tue, 2 Dec 2025 21:19:19 +0100 Subject: [PATCH] Fix ctrl alt key in Keyboard Hook and Advanced Paste custom actions --- .../Helpers/CentralizedKeyboardHookManager.cs | 76 +++++++++++++++---- .../AdvancedPasteModuleInterface.cs | 26 +++++-- .../AdvancedPasteXAML/App.xaml.cs | 12 +-- 3 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/RunnerV2/RunnerV2/Helpers/CentralizedKeyboardHookManager.cs b/src/RunnerV2/RunnerV2/Helpers/CentralizedKeyboardHookManager.cs index d56a7c53a1..6016d6284a 100644 --- a/src/RunnerV2/RunnerV2/Helpers/CentralizedKeyboardHookManager.cs +++ b/src/RunnerV2/RunnerV2/Helpers/CentralizedKeyboardHookManager.cs @@ -21,6 +21,11 @@ namespace RunnerV2.Helpers private static void OnKeyDown(int key) { + if ((VirtualKey)key == VirtualKey.RightMenu && _ctrlState) + { + _ctrlAltState = true; + } + switch ((VirtualKey)key) { case VirtualKey.Control: @@ -41,6 +46,20 @@ namespace RunnerV2.Helpers case VirtualKey.LeftWindows: case VirtualKey.RightWindows: _winState = true; + break; + default: + if (OnKeyboardEvent(new HotkeySettings + { + Code = key, + Ctrl = _ctrlState, + Alt = _altState, + Shift = _shiftState, + Win = _winState, + })) + { + return; + } + break; } @@ -70,16 +89,15 @@ namespace RunnerV2.Helpers case VirtualKey.RightWindows: _winState = false; break; - default: - OnKeyboardEvent(new HotkeySettings - { - Code = key, - Ctrl = _ctrlState, - Alt = _altState, - Shift = _shiftState, - Win = _winState, - }); - break; + } + + // Correctly release Ctrl key if Ctrl+Alt (AltGr) was used. + if (_ctrlAltState && (VirtualKey)key == VirtualKey.RightMenu) + { + _ctrlAltState = false; + _ctrlState = false; + + SendSingleKeyboardInput((short)VirtualKey.LeftControl, (uint)NativeKeyboardHelper.KeyEventF.KeyUp); } SendSingleKeyboardInput((short)key, (uint)NativeKeyboardHelper.KeyEventF.KeyUp); @@ -89,6 +107,7 @@ namespace RunnerV2.Helpers private static bool _altState; private static bool _shiftState; private static bool _winState; + private static bool _ctrlAltState; private static bool _isActive; @@ -114,8 +133,10 @@ namespace RunnerV2.Helpers _keyboardHooks.Remove(moduleName); } - private static void OnKeyboardEvent(HotkeySettings pressedHotkey) + private static bool OnKeyboardEvent(HotkeySettings pressedHotkey) { + bool shortcutHandled = false; + foreach (var moduleHooks in _keyboardHooks.Values) { foreach (var (hotkeySettings, action) in moduleHooks) @@ -123,9 +144,12 @@ namespace RunnerV2.Helpers if (hotkeySettings == pressedHotkey) { action(); + shortcutHandled = true; } } } + + return shortcutHandled; } public static void Start() @@ -147,7 +171,12 @@ namespace RunnerV2.Helpers // Function to send a single key event to the system which would be ignored by the hotkey control. private static void SendSingleKeyboardInput(short keyCode, uint keyStatus) { - NativeKeyboardHelper.INPUT inputShift = new() + if (IsExtendedVirtualKey(keyCode)) + { + keyStatus |= (uint)NativeKeyboardHelper.KeyEventF.ExtendedKey; + } + + NativeKeyboardHelper.INPUT input = new() { type = NativeKeyboardHelper.INPUTTYPE.INPUT_KEYBOARD, data = new NativeKeyboardHelper.InputUnion @@ -161,9 +190,30 @@ namespace RunnerV2.Helpers }, }; - NativeKeyboardHelper.INPUT[] inputs = [inputShift]; + NativeKeyboardHelper.INPUT[] inputs = [input]; _ = NativeMethods.SendInput(1, inputs, NativeKeyboardHelper.INPUT.Size); } + + private static bool IsExtendedVirtualKey(short vk) + { + return vk switch + { + 0xA5 => true, // VK_RMENU (Right Alt - AltGr) + 0xA3 => true, // VK_RCONTROL + 0x2D => true, // VK_INSERT + 0x2E => true, // VK_DELETE + 0x23 => true, // VK_END + 0x24 => true, // VK_HOME + 0x21 => true, // VK_PRIOR (Page Up) + 0x22 => true, // VK_NEXT (Page Down) + 0x25 => true, // VK_LEFT + 0x26 => true, // VK_UP + 0x27 => true, // VK_RIGHT + 0x28 => true, // VK_DOWN + 0x90 => true, // VK_NUMLOCK + _ => false, + }; + } } } diff --git a/src/RunnerV2/RunnerV2/ModuleInterfaces/AdvancedPasteModuleInterface.cs b/src/RunnerV2/RunnerV2/ModuleInterfaces/AdvancedPasteModuleInterface.cs index e5d5942f51..df2d8c6ef4 100644 --- a/src/RunnerV2/RunnerV2/ModuleInterfaces/AdvancedPasteModuleInterface.cs +++ b/src/RunnerV2/RunnerV2/ModuleInterfaces/AdvancedPasteModuleInterface.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; +using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using System.Windows.Forms; -using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Helpers; using PowerToys.GPOWrapper; @@ -64,18 +64,21 @@ namespace RunnerV2.ModuleInterfaces Process.Start("WinUI3Apps\\PowerToys.AdvancedPaste.exe", $"{Environment.ProcessId} {ipcName}"); } - public void OnSettingsChanged() + public void OnSettingsChanged(string settingsKind, JsonElement jsonProperties) { PopulateShortcuts(); } public void PopulateShortcuts() { - ArgumentNullException.ThrowIfNull(_ipc); + if (_ipc is null) + { + _ipc = new TwoWayPipeMessageIPCManaged(string.Empty, @"\\.\pipe\PowerToys.AdvancedPaste", (_) => { }); + } Shortcuts.Clear(); - AdvancedPasteSettings settings = new SettingsUtils().GetSettings(); + AdvancedPasteSettings settings = new SettingsUtils().GetSettingsOrDefault(Name); Shortcuts.Add((settings.Properties.AdvancedPasteUIShortcut, () => _ipc.Send("ShowUI") )); @@ -84,10 +87,17 @@ namespace RunnerV2.ModuleInterfaces Shortcuts.Add((settings.Properties.PasteAsJsonShortcut, () => _ipc.Send("PasteJson"))); HotkeyAccessor[] hotkeyAccessors = settings.GetAllHotkeyAccessors(); - for (int i = 4; i < hotkeyAccessors.Length; i++) + int additionalActionsCount = settings.Properties.AdditionalActions.GetAllActions().Count() - 2; + for (int i = 0; i < additionalActionsCount; i++) { - HotkeyAccessor hotkeyAccessor = hotkeyAccessors[i]; - Shortcuts.Add((hotkeyAccessor.Value, () => _ipc.Send($"CustomPaste {i}"))); + int scopedI = i; + Shortcuts.Add((hotkeyAccessors[4 + i].Value, () => _ipc.Send("AdditionalAction " + (3 + scopedI)))); + } + + for (int i = 4 + additionalActionsCount; i < hotkeyAccessors.Length; i++) + { + int scopedI = i; + Shortcuts.Add((hotkeyAccessors[i].Value, () => _ipc.Send("CustomAction " + (scopedI - 5 - additionalActionsCount)))); } } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs index 9dcd68c365..e6a0142758 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs @@ -25,6 +25,7 @@ using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using PowerToys.Interop; using Windows.Graphics; +using WinRT; using WinUIEx; using static AdvancedPaste.Helpers.NativeMethods; @@ -44,13 +45,6 @@ namespace AdvancedPaste public ETWTrace EtwTrace { get; private set; } = new ETWTrace(); - private static readonly Dictionary AdditionalActionIPCKeys = - typeof(PasteFormats).GetFields() - .Where(field => field.IsLiteral) - .Select(field => (Format: (PasteFormats)field.GetRawConstantValue(), field.GetCustomAttribute().IPCKey)) - .Where(field => field.IPCKey != null) - .ToDictionary(field => field.IPCKey, field => field.Format); - private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); private readonly OptionsViewModel viewModel; @@ -187,14 +181,14 @@ namespace AdvancedPaste } else { - if (!AdditionalActionIPCKeys.TryGetValue(messageParts[1], out PasteFormats pasteFormat)) + if (!int.TryParse(messageParts[1], CultureInfo.InvariantCulture, out int customActionId)) { Logger.LogWarning($"Unexpected additional action type {messageParts[1]}"); } else { await ShowWindow(); - await viewModel.ExecutePasteFormatAsync(pasteFormat, PasteActionSource.GlobalKeyboardShortcut); + await viewModel.ExecutePasteFormatAsync((PasteFormats)customActionId, PasteActionSource.GlobalKeyboardShortcut); } } }