using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; using ToolboxApp.Models; namespace ToolboxApp.Services; public sealed class HotkeyService : IDisposable { private const int WmHotkey = 0x0312; private readonly Dictionary _registeredHotkeys = new(); private HwndSource? _source; private Func? _onTriggered; private Action? _log; private int _nextId = 1000; private nint _handle; public void RegisterAll( Window window, IEnumerable tools, bool enabled, Func onTriggered, Action log) { UnregisterAll(); _onTriggered = onTriggered; _log = log; var helper = new WindowInteropHelper(window); _handle = helper.Handle; if (_handle == 0) { return; } _source ??= HwndSource.FromHwnd(_handle); _source?.RemoveHook(WndProc); _source?.AddHook(WndProc); foreach (var tool in tools) { tool.HotkeyStatus = string.IsNullOrWhiteSpace(tool.Hotkey) ? "未设置" : "等待注册"; } if (!enabled) { foreach (var tool in tools.Where(tool => !string.IsNullOrWhiteSpace(tool.Hotkey))) { tool.HotkeyStatus = "已禁用"; } return; } var activeTools = tools .Where(tool => !tool.IsDeleted && !string.IsNullOrWhiteSpace(tool.Hotkey)) .ToList(); var conflicts = activeTools .GroupBy(tool => HotkeyParser.Normalize(tool.Hotkey!)) .Where(group => group.Count() > 1) .SelectMany(group => group) .ToHashSet(); foreach (var conflict in conflicts) { conflict.HotkeyStatus = "内部冲突"; log(CreateLog(LogLevel.Warning, $"快捷键内部冲突:{conflict.Hotkey},{conflict.Name}")); } foreach (var tool in activeTools.Where(tool => !conflicts.Contains(tool))) { if (!HotkeyParser.TryParse(tool.Hotkey, out var modifiers, out var key)) { tool.HotkeyStatus = "格式无效"; log(CreateLog(LogLevel.Warning, $"快捷键格式无效:{tool.Name},{tool.Hotkey}")); continue; } var id = _nextId++; if (RegisterHotKey(_handle, id, modifiers, key)) { _registeredHotkeys[id] = tool.Id; tool.HotkeyStatus = "正常"; } else { tool.HotkeyStatus = "系统注册失败"; log(CreateLog(LogLevel.Warning, $"快捷键注册失败:{tool.Name},{tool.Hotkey}")); } } } public void UnregisterAll() { if (_handle != 0) { foreach (var id in _registeredHotkeys.Keys.ToList()) { UnregisterHotKey(_handle, id); } } _registeredHotkeys.Clear(); } public void Dispose() { UnregisterAll(); _source?.RemoveHook(WndProc); } private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) { if (msg == WmHotkey && _registeredHotkeys.TryGetValue(wParam.ToInt32(), out var toolId)) { handled = true; _ = TriggerAsync(toolId); } return 0; } private async Task TriggerAsync(string toolId) { if (_onTriggered is null) { return; } try { await _onTriggered(toolId); } catch (Exception ex) { _log?.Invoke(CreateLog(LogLevel.Error, $"快捷键触发失败:{ex.Message}")); } } private static LogMessage CreateLog(LogLevel level, string message) { return new LogMessage { Level = level, Message = message }; } [DllImport("user32.dll", SetLastError = true)] private static extern bool RegisterHotKey(nint hWnd, int id, uint fsModifiers, uint vk); [DllImport("user32.dll", SetLastError = true)] private static extern bool UnregisterHotKey(nint hWnd, int id); } public static class HotkeyParser { private const uint ModAlt = 0x0001; private const uint ModControl = 0x0002; private const uint ModShift = 0x0004; private const uint ModWin = 0x0008; public static string Normalize(string hotkey) { return string.Join("+", hotkey.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(NormalizePart) .Where(part => part.Length > 0) .OrderBy(GetPartOrder) .ThenBy(part => part) .ToArray()); } public static bool TryParse(string? hotkey, out uint modifiers, out uint key) { modifiers = 0; key = 0; if (string.IsNullOrWhiteSpace(hotkey)) { return false; } var keyPart = ""; foreach (var rawPart in hotkey.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { var part = NormalizePart(rawPart); switch (part) { case "Ctrl": modifiers |= ModControl; break; case "Alt": modifiers |= ModAlt; break; case "Shift": modifiers |= ModShift; break; case "Win": modifiers |= ModWin; break; default: keyPart = part; break; } } if (modifiers == 0 || string.IsNullOrWhiteSpace(keyPart)) { return false; } key = KeyToVirtualKey(keyPart); return key != 0; } private static string NormalizePart(string value) { var part = value.Trim(); return part.ToLowerInvariant() switch { "control" or "ctrl" => "Ctrl", "menu" or "alt" => "Alt", "shift" => "Shift", "windows" or "win" => "Win", _ => part.Length == 1 ? part.ToUpperInvariant() : part.ToUpperInvariant().StartsWith("F", StringComparison.Ordinal) ? part.ToUpperInvariant() : part }; } private static int GetPartOrder(string part) { return part switch { "Ctrl" => 0, "Alt" => 1, "Shift" => 2, "Win" => 3, _ => 4 }; } private static uint KeyToVirtualKey(string keyPart) { if (keyPart.Length == 1) { var character = keyPart[0]; if (character is >= 'A' and <= 'Z') { return character; } if (character is >= '0' and <= '9') { return character; } } if (keyPart.StartsWith("F", StringComparison.OrdinalIgnoreCase) && int.TryParse(keyPart[1..], out var functionKey) && functionKey is >= 1 and <= 24) { return (uint)(0x70 + functionKey - 1); } return keyPart.ToLowerInvariant() switch { "escape" or "esc" => 0x1B, "tab" => 0x09, "space" => 0x20, "enter" => 0x0D, "backspace" => 0x08, "delete" => 0x2E, "insert" => 0x2D, "home" => 0x24, "end" => 0x23, "pageup" => 0x21, "pagedown" => 0x22, "left" => 0x25, "up" => 0x26, "right" => 0x27, "down" => 0x28, _ => 0 }; } }