using System; using System.Runtime.InteropServices; using System.Windows.Input; namespace Paca.App.Services; public sealed class GlobalHotkeyService : IDisposable { public const uint MOD_ALT = 0x0001; public const uint MOD_CONTROL = 0x0002; public const uint MOD_SHIFT = 0x0004; public const uint MOD_WIN = 0x0008; public const uint MOD_NOREPEAT = 0x4000; public const int WM_HOTKEY = 0x0312; [DllImport("user32.dll", SetLastError = true)] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); [DllImport("user32.dll", SetLastError = true)] private static extern bool UnregisterHotKey(IntPtr hWnd, int id); private IntPtr _hWnd; private int _currentId; private bool _isRegistered; public bool IsRegistered => _isRegistered; public event Action? HotkeyPressed; public bool Register(IntPtr hWnd, int id, string hotkeyStr) { Unregister(); if (!TryParseHotkey(hotkeyStr, out uint modifiers, out uint vk)) { return false; } _hWnd = hWnd; _currentId = id; _isRegistered = RegisterHotKey(_hWnd, _currentId, modifiers | MOD_NOREPEAT, vk); return _isRegistered; } public void Unregister() { if (_isRegistered && _hWnd != IntPtr.Zero) { UnregisterHotKey(_hWnd, _currentId); _isRegistered = false; } } public IntPtr HandleWindowMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_HOTKEY && wParam.ToInt32() == _currentId) { HotkeyPressed?.Invoke(); handled = true; } return IntPtr.Zero; } public static bool TryParseHotkey(string? hotkeyStr, out uint fsModifiers, out uint vk) { fsModifiers = 0; vk = 0; if (string.IsNullOrWhiteSpace(hotkeyStr)) return false; var parts = hotkeyStr.Split('+', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) return false; for (int i = 0; i < parts.Length - 1; i++) { var part = parts[i].ToLowerInvariant(); if (part is "ctrl" or "control") fsModifiers |= MOD_CONTROL; else if (part is "alt") fsModifiers |= MOD_ALT; else if (part is "shift") fsModifiers |= MOD_SHIFT; else if (part is "win" or "windows") fsModifiers |= MOD_WIN; else return false; } var keyPart = parts[^1].ToUpperInvariant(); if (keyPart is "SPACE") vk = 0x20; else if (keyPart is "RETURN" or "ENTER") vk = 0x0D; else if (keyPart is "ESCAPE" or "ESC") vk = 0x1B; else if (keyPart.Length == 1 && keyPart[0] >= 'A' && keyPart[0] <= 'Z') vk = (uint)keyPart[0]; else if (keyPart.Length == 1 && keyPart[0] >= '0' && keyPart[0] <= '9') vk = (uint)keyPart[0]; else if (keyPart.StartsWith("F") && int.TryParse(keyPart[1..], out int fNum) && fNum >= 1 && fNum <= 24) { vk = (uint)(0x70 + (fNum - 1)); // VK_F1 = 0x70 } else { return false; } return true; } public void Dispose() { Unregister(); } }