using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace XFEToolBox.Client.Utilities;
internal sealed class GlobalHotkeyService : IDisposable
{
private const int HotkeyId = 0x5846;
private const int WmHotkey = 0x0312;
private readonly HwndSource source;
private readonly Action action;
private bool registered;
public GlobalHotkeyService(Window window, Action action)
{
this.action = action;
var handle = new WindowInteropHelper(window).EnsureHandle();
source = HwndSource.FromHwnd(handle) ?? throw new InvalidOperationException("无法创建快捷键消息源。");
source.AddHook(WindowProc);
}
public string Status { get; private set; } = "尚未注册";
public event EventHandler? StatusChanged;
public bool Register(string gestureText)
{
Unregister();
if (!TryParse(gestureText, out var modifiers, out var key, out var normalized, out var error))
{
Status = error;
StatusChanged?.Invoke(this, EventArgs.Empty);
return false;
}
registered = RegisterHotKey(source.Handle, HotkeyId, modifiers | 0x4000, (uint)KeyInterop.VirtualKeyFromKey(key));
Status = registered ? $"已启用:{normalized}" : $"快捷键 {normalized} 已被其他应用占用";
StatusChanged?.Invoke(this, EventArgs.Empty);
return registered;
}
public void Disable()
{
Unregister();
Status = "已禁用";
StatusChanged?.Invoke(this, EventArgs.Empty);
}
public void Dispose()
{
Unregister();
source.RemoveHook(WindowProc);
}
public static bool TryNormalize(string gestureText, out string normalized, out string error)
{
var success = TryParse(gestureText, out _, out _, out normalized, out error);
return success;
}
private void Unregister()
{
if (!registered) return;
UnregisterHotKey(source.Handle, HotkeyId);
registered = false;
}
private IntPtr WindowProc(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (message != WmHotkey || wParam.ToInt32() != HotkeyId) return IntPtr.Zero;
handled = true;
action();
return IntPtr.Zero;
}
private static bool TryParse(
string gestureText,
out uint modifiers,
out Key key,
out string normalized,
out string error)
{
modifiers = 0;
key = Key.None;
normalized = string.Empty;
error = string.Empty;
var parts = (gestureText ?? string.Empty)
.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length < 2)
{
error = "快捷键必须包含至少一个修饰键和一个普通按键";
return false;
}
var labels = new List<string>();
for (var index = 0; index < parts.Length - 1; index++)
{
switch (parts[index].ToLowerInvariant())
{
case "ctrl" or "control": modifiers |= 0x0002; labels.Add("Ctrl"); break;
case "alt": modifiers |= 0x0001; labels.Add("Alt"); break;
case "shift": modifiers |= 0x0004; labels.Add("Shift"); break;
case "win" or "windows": modifiers |= 0x0008; labels.Add("Win"); break;
default:
error = $"无法识别修饰键“{parts[index]}”";
return false;
}
}
if (modifiers == 0 || !Enum.TryParse(parts[^1], true, out key) || key is Key.None or Key.System)
{
error = $"无法识别快捷键“{gestureText}”";
return false;
}
labels.Add(key == Key.Space ? "Space" : key.ToString());
normalized = string.Join('+', labels.Distinct(StringComparer.OrdinalIgnoreCase));
return true;
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool RegisterHotKey(IntPtr windowHandle, int id, uint modifiers, uint virtualKey);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnregisterHotKey(IntPtr windowHandle, int id);
}
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace XFEToolBox.Client.Utilities;
internal sealed class GlobalHotkeyService : IDisposable
{
private const int HotkeyId = 0x5846;
private const int WmHotkey = 0x0312;
private readonly HwndSource source;
private readonly Action action;
private bool registered;
public GlobalHotkeyService(Window window, Action action)
{
this.action = action;
var handle = new WindowInteropHelper(window).EnsureHandle();
source = HwndSource.FromHwnd(handle) ?? throw new InvalidOperationException("无法创建快捷键消息源。");
source.AddHook(WindowProc);
}
public string Status { get; private set; } = "尚未注册";
public event EventHandler? StatusChanged;
public bool Register(string gestureText)
{
Unregister();
if (!TryParse(gestureText, out var modifiers, out var key, out var normalized, out var error))
{
Status = error;
StatusChanged?.Invoke(this, EventArgs.Empty);
return false;
}
registered = RegisterHotKey(source.Handle, HotkeyId, modifiers | 0x4000, (uint)KeyInterop.VirtualKeyFromKey(key));
Status = registered ? $"已启用:{normalized}" : $"快捷键 {normalized} 已被其他应用占用";
StatusChanged?.Invoke(this, EventArgs.Empty);
return registered;
}
public void Disable()
{
Unregister();
Status = "已禁用";
StatusChanged?.Invoke(this, EventArgs.Empty);
}
public void Dispose()
{
Unregister();
source.RemoveHook(WindowProc);
}
public static bool TryNormalize(string gestureText, out string normalized, out string error)
{
var success = TryParse(gestureText, out _, out _, out normalized, out error);
return success;
}
private void Unregister()
{
if (!registered) return;
UnregisterHotKey(source.Handle, HotkeyId);
registered = false;
}
private IntPtr WindowProc(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (message != WmHotkey || wParam.ToInt32() != HotkeyId) return IntPtr.Zero;
handled = true;
action();
return IntPtr.Zero;
}
private static bool TryParse(
string gestureText,
out uint modifiers,
out Key key,
out string normalized,
out string error)
{
modifiers = 0;
key = Key.None;
normalized = string.Empty;
error = string.Empty;
var parts = (gestureText ?? string.Empty)
.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length < 2)
{
error = "快捷键必须包含至少一个修饰键和一个普通按键";
return false;
}
var labels = new List<string>();
for (var index = 0; index < parts.Length - 1; index++)
{
switch (parts[index].ToLowerInvariant())
{
case "ctrl" or "control": modifiers |= 0x0002; labels.Add("Ctrl"); break;
case "alt": modifiers |= 0x0001; labels.Add("Alt"); break;
case "shift": modifiers |= 0x0004; labels.Add("Shift"); break;
case "win" or "windows": modifiers |= 0x0008; labels.Add("Win"); break;
default:
error = $"无法识别修饰键“{parts[index]}”";
return false;
}
}
if (modifiers == 0 || !Enum.TryParse(parts[^1], true, out key) || key is Key.None or Key.System)
{
error = $"无法识别快捷键“{gestureText}”";
return false;
}
labels.Add(key == Key.Space ? "Space" : key.ToString());
normalized = string.Join('+', labels.Distinct(StringComparer.OrdinalIgnoreCase));
return true;
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool RegisterHotKey(IntPtr windowHandle, int id, uint modifiers, uint virtualKey);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnregisterHotKey(IntPtr windowHandle, int id);
}