Implement Keyboard hook to get the user input of remappings

This commit is contained in:
Hao Liu
2025-03-25 19:04:16 +08:00
parent fc5fb5e80f
commit 09850306aa
3 changed files with 185 additions and 111 deletions

View File

@@ -155,10 +155,14 @@ namespace KeyboardManagerEditorUI.Pages
RemappingControl.SetRemappedKeys(new List<string>());
RemappingControl.SetApp(false, string.Empty);
RemappingControl.SetKeyboardHook();
// Show the dialog to add a new remapping
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
RemappingControl.CleanupKeyboardHook();
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
@@ -175,9 +179,13 @@ namespace KeyboardManagerEditorUI.Pages
RemappingControl.SetRemappedKeys(selectedRemapping.RemappedKeys);
RemappingControl.SetApp(!selectedRemapping.IsAllApps, selectedRemapping.AppName);
RemappingControl.SetKeyboardHook();
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
RemappingControl.CleanupKeyboardHook();
}
}

View File

@@ -51,14 +51,6 @@
</ToggleButton>
</Grid>
<Grid Grid.Row="1" Margin="0,8,0,0">
<StackPanel
Name="KeyStackPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal" />
</Grid>
<TextBlock
Grid.Row="1"
Grid.Column="1"
@@ -94,17 +86,6 @@
</ToggleButton.Content>
</ToggleButton>
</Grid>
<Grid
Grid.Row="1"
Grid.Column="2"
Margin="0,8,0,0">
<StackPanel
Name="NewKeyStackPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal" />
</Grid>
</Grid>
<CheckBox

View File

@@ -3,18 +3,32 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using KeyboardManagerEditorUI.Interop;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Windows.System;
namespace KeyboardManagerEditorUI.Styles
{
public sealed partial class InputControl : UserControl
public sealed partial class InputControl : UserControl, IDisposable
{
private List<string> pressedKeys = new List<string>();
private List<string> newPressedKeys = new List<string>();
// List to store pressed keys
private HashSet<VirtualKey> _currentlyPressedKeys = new HashSet<VirtualKey>();
// List to store order of remapped keys
private List<VirtualKey> _keyPressOrder = new List<VirtualKey>();
// Collection to store original and remapped keys
private ObservableCollection<string> _originalKeys = new ObservableCollection<string>();
private ObservableCollection<string> _remappedKeys = new ObservableCollection<string>();
private HotkeySettingsControlHook? _keyboardHook;
private bool _disposed;
// Define newMode as a DependencyProperty for binding
public static readonly DependencyProperty NewModeProperty =
@@ -33,127 +47,192 @@ namespace KeyboardManagerEditorUI.Styles
public InputControl()
{
this.InitializeComponent();
this.KeyDown += (sender, e) => InputControl_KeyDown(sender, e, NewMode);
this.KeyUp += (sender, e) => InputControl_KeyUp(sender, e, NewMode);
this.OriginalKeys.ItemsSource = _originalKeys;
this.RemappedKeys.ItemsSource = _remappedKeys;
}
private void InputControl_KeyDown(int key)
{
// if no keys are pressed, clear the lists when a new key is pressed
if (_currentlyPressedKeys.Count == 0)
{
if (NewMode)
{
_remappedKeys.Clear();
}
else
{
_originalKeys.Clear();
}
_keyPressOrder.Clear();
}
VirtualKey virtualKey = (VirtualKey)key;
if (_currentlyPressedKeys.Add(virtualKey))
{
_keyPressOrder.Add(virtualKey);
UpdateKeysDisplay();
}
}
private void InputControl_KeyUp(int key)
{
VirtualKey virtualKey = (VirtualKey)key;
if (_currentlyPressedKeys.Remove(virtualKey))
{
_keyPressOrder.Remove(virtualKey);
}
}
private static void OnNewModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as InputControl;
if (control != null)
if (d is InputControl control)
{
bool newMode = (bool)e.NewValue;
control.UpdateKeyDisplay(newMode);
// Clear the lists when the mode changes
control._currentlyPressedKeys.Clear();
control._keyPressOrder.Clear();
}
}
private void InputControl_KeyDown(object sender, KeyRoutedEventArgs e, bool newMode)
public void SetKeyboardHook()
{
// Get the key name and add it to the list if it's not already there
string keyName = e.Key.ToString();
keyName = NormalizeKeyName(keyName);
CleanupKeyboardHook();
var currentKeyList = newMode ? newPressedKeys : pressedKeys;
_keyboardHook = new HotkeySettingsControlHook(
InputControl_KeyDown,
InputControl_KeyUp,
() => true,
(key, extraInfo) => true);
}
if (!currentKeyList.Contains(keyName))
public void CleanupKeyboardHook()
{
if (_keyboardHook != null)
{
var allowedModifiers = new[] { "Shift", "Ctrl", "LWin", "RWin", "Alt" };
if (currentKeyList.All(k => allowedModifiers.Contains(k)) && pressedKeys.Count < 4)
_keyboardHook.Dispose();
_keyboardHook = null;
}
}
private void UpdateKeysDisplay()
{
var formattedKeys = GetFormattedKeyList();
if (NewMode)
{
_remappedKeys.Clear();
foreach (var key in formattedKeys)
{
currentKeyList.Add(keyName);
UpdateKeyDisplay(newMode);
_remappedKeys.Add(key);
}
}
else
{
_originalKeys.Clear();
foreach (var key in formattedKeys)
{
_originalKeys.Add(key);
}
}
}
private void InputControl_KeyUp(object sender, KeyRoutedEventArgs e, bool newMode)
private List<string> GetFormattedKeyList()
{
// Console.WriteLine(newMode);
string keyName = e.Key.ToString();
var currentKeyList = newMode ? newPressedKeys : pressedKeys;
List<string> keyList = new List<string>();
List<VirtualKey> modifierKeys = new List<VirtualKey>();
VirtualKey? actionKey = null;
if (!currentKeyList.Contains(keyName))
foreach (var key in _keyPressOrder)
{
return;
}
// Remove the key name from the list when the key is released
// currentKeyList.Remove(keyName);
UpdateKeyDisplay(newMode);
}
private void UpdateKeyDisplay(bool newMode)
{
// Clear current UI elements
if (newMode)
{
NewKeyStackPanel.Children.Clear();
} // Assuming keyPanel2 is your second stack panel
else
{
KeyStackPanel.Children.Clear();
}
var currentKeyList = newMode ? newPressedKeys : pressedKeys;
// Add each pressed key as a TextBlock in the StackPanel
foreach (var key in currentKeyList)
{
Border keyBlockContainer = new Border
if (!_currentlyPressedKeys.Contains(key))
{
Background = new SolidColorBrush(Microsoft.UI.Colors.White),
Padding = new Thickness(10),
Margin = new Thickness(1),
CornerRadius = new CornerRadius(3),
BorderBrush = new SolidColorBrush(Microsoft.UI.Colors.Black),
BorderThickness = new Thickness(1),
};
continue;
}
TextBlock keyBlock = new TextBlock
if (IsModifierKey(key))
{
Text = key,
FontSize = 12,
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
Foreground = new SolidColorBrush(Microsoft.UI.Colors.Black),
};
// Add TextBlock inside the Border container
keyBlockContainer.Child = keyBlock;
// Add Border to StackPanel
if (newMode)
{
keyBlockContainer.Background = Application.Current.Resources["AccentButtonBackgroundPressed"] as SolidColorBrush;
keyBlockContainer.BorderBrush = Application.Current.Resources["AccentButtonBackgroundPressed"] as SolidColorBrush;
keyBlock.Foreground = new SolidColorBrush(Microsoft.UI.Colors.White);
NewKeyStackPanel.Children.Add(keyBlockContainer); // For remapping keys
if (!modifierKeys.Contains(key))
{
modifierKeys.Add(key);
}
}
else
{
KeyStackPanel.Children.Add(keyBlockContainer); // For normal keys
actionKey = key;
}
}
foreach (var key in modifierKeys)
{
keyList.Add(GetKeyDisplayName((int)key));
}
if (actionKey.HasValue)
{
keyList.Add(GetKeyDisplayName((int)actionKey.Value));
}
return keyList;
}
private string GetKeyDisplayName(int keyCode)
{
var keyName = new System.Text.StringBuilder(64);
KeyboardManagerInterop.GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
return keyName.ToString();
}
private bool IsModifierKey(VirtualKey key)
{
return key == VirtualKey.Control
|| key == VirtualKey.LeftControl
|| key == VirtualKey.RightControl
|| key == VirtualKey.Menu
|| key == VirtualKey.LeftMenu
|| key == VirtualKey.RightMenu
|| key == VirtualKey.Shift
|| key == VirtualKey.LeftShift
|| key == VirtualKey.RightShift
|| key == VirtualKey.LeftWindows
|| key == VirtualKey.RightWindows;
}
public void SetRemappedKeys(List<string> keys)
{
RemappedKeys.ItemsSource = keys;
_remappedKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_remappedKeys.Add(key);
}
}
}
public void SetOriginalKeys(List<string> keys)
{
OriginalKeys.ItemsSource = keys;
_originalKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_originalKeys.Add(key);
}
}
}
public List<string> GetOriginalKeys()
{
return pressedKeys as List<string> ?? new List<string>();
return _originalKeys.ToList();
}
public List<string> GetRemappedKeys()
{
return newPressedKeys as List<string> ?? new List<string>();
return _remappedKeys.ToList();
}
public bool GetIsAppSpecific()
@@ -170,12 +249,16 @@ namespace KeyboardManagerEditorUI.Styles
{
NewMode = true;
RemappedToggleBtn.IsChecked = false;
this.Focus(FocusState.Programmatic);
}
private void OriginalToggleBtn_Checked(object sender, RoutedEventArgs e)
{
NewMode = false;
OriginalToggleBtn.IsChecked = false;
this.Focus(FocusState.Programmatic);
}
public void SetApp(bool isSpecificApp, string appName)
@@ -209,20 +292,22 @@ namespace KeyboardManagerEditorUI.Styles
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Unchecked;
}
private string NormalizeKeyName(string keyName)
public void Dispose()
{
switch (keyName)
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
case "Control":
return "Ctrl";
case "Menu":
return "Alt";
case "LeftWindows":
return "LWin";
case "RightWindows":
return "RWin";
default:
return keyName;
if (disposing)
{
CleanupKeyboardHook();
}
_disposed = true;
}
}
}