mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
Implement Keyboard hook to get the user input of remappings
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user