using System; using System.Runtime.InteropServices; using System.Windows.Input; using System.Windows.Interop; namespace TodoList.Services { public class GlobalShortcutService : IDisposable { private const int HOTKEY_ID = 9000; public const uint MOD_ALT = 0x0001; public const uint MOD_CONTROL = 0x0002; public const uint MOD_SHIFT = 0x0004; public const uint MOD_WIN = 0x0008; [DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); [DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id); private IntPtr _windowHandle; private HwndSource _source; private Action _onHotKeyPressed; private bool _isRegistered; public void Register(IntPtr windowHandle, Action onHotKeyPressed, uint modifiers, uint key) { // If already registered, unregister first (to support updating) if (_isRegistered) { UnregisterHotKey(_windowHandle, HOTKEY_ID); _source?.RemoveHook(HwndHook); _isRegistered = false; } _windowHandle = windowHandle; _onHotKeyPressed = onHotKeyPressed; _source = HwndSource.FromHwnd(_windowHandle); if (_source == null) return; // Should not happen if handle is valid _source.AddHook(HwndHook); if (RegisterHotKey(_windowHandle, HOTKEY_ID, modifiers, key)) { _isRegistered = true; } else { System.Diagnostics.Debug.WriteLine("Failed to register hotkey."); } } public void UpdateShortcut(uint modifiers, uint key) { if (_windowHandle != IntPtr.Zero && _onHotKeyPressed != null) { // Re-register with new keys Register(_windowHandle, _onHotKeyPressed, modifiers, key); } } private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { const int WM_HOTKEY = 0x0312; if (msg == WM_HOTKEY) { if (wParam.ToInt32() == HOTKEY_ID) { _onHotKeyPressed?.Invoke(); handled = true; } } return IntPtr.Zero; } public void Unregister() { if (_isRegistered) { _source?.RemoveHook(HwndHook); UnregisterHotKey(_windowHandle, HOTKEY_ID); _isRegistered = false; } } public void Dispose() { Unregister(); } public static uint GetModifier(string modifiers) { uint mod = 0; if (string.IsNullOrEmpty(modifiers)) return mod; var parts = modifiers.Split(','); foreach (var part in parts) { var p = part.Trim(); if (p.Equals("Control", StringComparison.OrdinalIgnoreCase)) mod |= MOD_CONTROL; if (p.Equals("Alt", StringComparison.OrdinalIgnoreCase)) mod |= MOD_ALT; if (p.Equals("Shift", StringComparison.OrdinalIgnoreCase)) mod |= MOD_SHIFT; if (p.Equals("Windows", StringComparison.OrdinalIgnoreCase)) mod |= MOD_WIN; } return mod; } public static uint GetKey(string key) { if (Enum.TryParse(key, out var k)) { return (uint)KeyInterop.VirtualKeyFromKey(k); } // Fallback for simple letters if Key enum doesn't match directly (though it should for A-Z) if (key.Length == 1) { char c = char.ToUpper(key[0]); if (c >= 'A' && c <= 'Z') return (uint)c; if (c >= '0' && c <= '9') return (uint)c; } return 0x41; // Default 'A' } } }