/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/KeyboardHookHelper.cs
304 строки
10 KB
Aryan gupta
[Keyboard Manager] Fix shortcut modifier display order in the new editor (#49707)
06 авг 2026, 12:44
Не верифицирован
06 авг 2026, 12:44
6fcbde5
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation // 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; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using KeyboardManagerEditorUI.Interop; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; using Windows.System; namespace KeyboardManagerEditorUI.Helpers { public class KeyboardHookHelper : IDisposable { private static KeyboardHookHelper? _instance; public static KeyboardHookHelper Instance => _instance ??= new KeyboardHookHelper(); private KeyboardMappingService? _mappingService; private HotkeySettingsControlHook? _keyboardHook; // The active page using this keyboard hook private IKeyboardHookTarget? _activeTarget; private HashSet<VirtualKey> _currentlyPressedKeys = new(); private List<VirtualKey> _keyPressOrder = new(); private bool _disposed; // Singleton to make sure only one instance of the hook is active private KeyboardHookHelper() { try { _mappingService = new KeyboardMappingService(); } catch (Exception ex) { Logger.LogWarning($"Native KBM library unavailable for keyboard hook: {ex.Message}"); } } public void ActivateHook(IKeyboardHookTarget target) { CleanupHook(); _activeTarget = target; _currentlyPressedKeys.Clear(); _keyPressOrder.Clear(); try { _keyboardHook = new HotkeySettingsControlHook( KeyDown, KeyUp, () => true, (key, extraInfo) => true); } catch (Exception ex) { Logger.LogWarning($"Keyboard hook unavailable: {ex.Message}"); } } public void CleanupHook() { if (_keyboardHook != null) { _keyboardHook.Dispose(); _keyboardHook = null; } _currentlyPressedKeys.Clear(); _keyPressOrder.Clear(); _activeTarget = null; } private void KeyDown(int key) { if (_activeTarget == null) { return; } VirtualKey virtualKey = (VirtualKey)key; if (_currentlyPressedKeys.Contains(virtualKey)) { return; } // if no keys are pressed, clear the lists when a new key is pressed if (_currentlyPressedKeys.Count == 0) { _activeTarget.ClearKeys(); _keyPressOrder.Clear(); } // Count current modifiers int modifierCount = _currentlyPressedKeys.Count(k => RemappingHelper.IsModifierKey(k)); // If adding this key would exceed the limits (4 modifiers + 1 action key), don't add it and show notification if ((RemappingHelper.IsModifierKey(virtualKey) && modifierCount >= 4) || (!RemappingHelper.IsModifierKey(virtualKey) && _currentlyPressedKeys.Count >= 5)) { _activeTarget.OnInputLimitReached(); return; } // Check if this is a different variant of a modifier key already pressed if (RemappingHelper.IsModifierKey(virtualKey)) { // Remove existing variant of this modifier key if a new one is pressed // This is to ensure that only one variant of a modifier key is displayed at a time RemoveExistingModifierVariant(virtualKey); } if (_currentlyPressedKeys.Add(virtualKey)) { _keyPressOrder.Add(virtualKey); // When building chords, cap at 2 action keys: if a third action key arrives, // remove the oldest (shift behavior matching old editor). if (_activeTarget.AllowChords && !RemappingHelper.IsModifierKey(virtualKey)) { var actionKeysInOrder = _keyPressOrder.Where(k => !RemappingHelper.IsModifierKey(k)).ToList(); if (actionKeysInOrder.Count > 2) { _keyPressOrder.Remove(actionKeysInOrder[0]); } } // Notify the target page _activeTarget.OnKeyDown(virtualKey, GetFormattedKeyList()); } } private void KeyUp(int key) { if (_activeTarget == null) { return; } VirtualKey virtualKey = (VirtualKey)key; if (_currentlyPressedKeys.Remove(virtualKey)) { // When building chords, preserve released action keys in _keyPressOrder // so the next action key press is recognized as the chord's second key. // Only modifier releases clear from _keyPressOrder (matching old editor behavior). if (!_activeTarget.AllowChords || RemappingHelper.IsModifierKey(virtualKey)) { _keyPressOrder.Remove(virtualKey); } _activeTarget.OnKeyUp(virtualKey, GetFormattedKeyList()); } } // Display the modifier keys and the action key in order, e.g. "Ctrl + Alt + A" private List<string> GetFormattedKeyList() { if (_activeTarget == null) { return new List<string>(); } if (_mappingService is null) { return new List<string>(); } List<string> keyList = new List<string>(); List<VirtualKey> modifierKeys = new List<VirtualKey>(); VirtualKey? actionKey = null; VirtualKey? actionKeyChord = null; foreach (var key in _keyPressOrder) { // For modifiers, only include if currently pressed. // For action keys when building chords, also include released keys // so the chord's first key stays visible while waiting for the second. if (!_currentlyPressedKeys.Contains(key)) { if (RemappingHelper.IsModifierKey(key) || !_activeTarget.AllowChords) { continue; } } if (RemappingHelper.IsModifierKey(key)) { if (!modifierKeys.Contains(key)) { modifierKeys.Add(key); } } else if (actionKey.HasValue && _activeTarget.AllowChords) { actionKeyChord = key; } else { actionKey = key; } } // Sort modifier keys by the standard display order: Win → Ctrl → Alt → Shift // This matches the old C++ GetKeyVector behavior where keys are always shown in // canonical order regardless of the order the user pressed them. RemappingHelper.SortModifierKeys(modifierKeys); foreach (var key in modifierKeys) { keyList.Add(_mappingService.GetKeyDisplayName((int)key)); } if (actionKey.HasValue) { keyList.Add(_mappingService.GetKeyDisplayName((int)actionKey.Value)); } if (actionKeyChord.HasValue && _activeTarget.AllowChords) { keyList.Add(_mappingService.GetKeyDisplayName((int)actionKeyChord.Value)); } return keyList; } private void RemoveExistingModifierVariant(VirtualKey key) { if (_mappingService is null) { return; } KeyType keyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)key); // No need to remove if the key is an action key if (keyType == KeyType.Action) { return; } foreach (var existingKey in _currentlyPressedKeys.ToList()) { if (existingKey != key) { KeyType existingKeyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)existingKey); // Remove the existing key if it is a modifier key and has the same type as the new key if (existingKeyType == keyType) { _currentlyPressedKeys.Remove(existingKey); _keyPressOrder.Remove(existingKey); } } } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!_disposed) { if (disposing) { CleanupHook(); _mappingService?.Dispose(); } _disposed = true; } } } public interface IKeyboardHookTarget { bool AllowChords { get; } void OnKeyDown(VirtualKey key, List<string> formattedKeys); void OnKeyUp(VirtualKey key, List<string> formattedKeys) { } void ClearKeys(); void OnInputLimitReached(); } }