using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; namespace Hua.Todo.Avalonia.Services.Platforms; /// /// Windows 平台全局热键服务实现。 /// 通过 RegisterHotKey 将热键注册到专用线程的消息队列中,避免依赖窗口句柄与 WndProc Hook。 /// public sealed class WindowsGlobalHotKeyService : IGlobalHotKeyService, IDisposable { private const int HotKeyId = 9000; private const uint WmHotKey = 0x0312; private const uint WmQuit = 0x0012; private const uint WmAppExecute = 0x8001; public const uint ModAlt = 0x0001; public const uint ModControl = 0x0002; public const uint ModShift = 0x0004; public const uint ModWin = 0x0008; private readonly ConcurrentDictionary _actions = new(); private int _actionId; private Thread? _thread; private uint _threadId; private ManualResetEventSlim? _threadStarted; private Action? _callback; private bool _isRegistered; private uint _currentModifiers; private uint _currentKey; private bool _isDisposed; /// public bool IsSupported => OperatingSystem.IsWindows(); /// public void RegisterHotKey(string modifiers, string key, Action callback) { if (!IsSupported) return; _callback = callback; _currentModifiers = ParseModifiers(modifiers); _currentKey = ParseKey(key); StartThreadIfNeeded(); InvokeOnHotKeyThread(() => { UnregisterHotKeyInternal(); if (_currentKey == 0) { return; } if (RegisterHotKey(IntPtr.Zero, HotKeyId, _currentModifiers, _currentKey)) { _isRegistered = true; } else { Debug.WriteLine("[WindowsGlobalHotKeyService] Failed to register hotkey."); } }); } /// public void UnregisterHotKey() { if (!IsSupported) return; StartThreadIfNeeded(); InvokeOnHotKeyThread(UnregisterHotKeyInternal); } /// public void UpdateHotKey(string modifiers, string key) { if (_callback == null) return; RegisterHotKey(modifiers, key, _callback); } private void UnregisterHotKeyInternal() { if (_isRegistered) { UnregisterHotKey(IntPtr.Zero, HotKeyId); _isRegistered = false; } } private void StartThreadIfNeeded() { if (_thread != null) return; _threadStarted = new ManualResetEventSlim(false); _thread = new Thread(MessageLoop) { IsBackground = true, Name = "Hua.Todo HotKey Thread" }; if (OperatingSystem.IsWindows()) { _thread.SetApartmentState(ApartmentState.STA); } _thread.Start(); _threadStarted.Wait(); } private void MessageLoop() { _threadId = GetCurrentThreadId(); _threadStarted?.Set(); while (GetMessage(out var msg, IntPtr.Zero, 0, 0) != 0) { if (msg.message == WmHotKey && msg.wParam == (UIntPtr)HotKeyId) { try { _callback?.Invoke(); } catch (Exception ex) { Debug.WriteLine($"[WindowsGlobalHotKeyService] Hotkey callback failed: {ex}"); } continue; } if (msg.message == WmAppExecute) { var id = unchecked((int)msg.wParam); if (_actions.TryRemove(id, out var action)) { try { action(); } catch (Exception ex) { Debug.WriteLine($"[WindowsGlobalHotKeyService] Action execution failed: {ex}"); } } continue; } if (msg.message == WmQuit) { break; } TranslateMessage(ref msg); DispatchMessage(ref msg); } _threadStarted?.Dispose(); _threadStarted = null; } private void InvokeOnHotKeyThread(Action action) { var id = Interlocked.Increment(ref _actionId); _actions[id] = action; if (!PostThreadMessage(_threadId, WmAppExecute, (UIntPtr)id, IntPtr.Zero)) { _actions.TryRemove(id, out _); } } private static uint ParseModifiers(string modifiers) { uint mod = 0; if (string.IsNullOrWhiteSpace(modifiers)) return mod; var parts = modifiers.Split(','); foreach (var part in parts) { var p = part.Trim(); if (p.Equals("Control", StringComparison.OrdinalIgnoreCase)) mod |= ModControl; if (p.Equals("Alt", StringComparison.OrdinalIgnoreCase)) mod |= ModAlt; if (p.Equals("Shift", StringComparison.OrdinalIgnoreCase)) mod |= ModShift; if (p.Equals("Windows", StringComparison.OrdinalIgnoreCase)) mod |= ModWin; } return mod; } private static uint ParseKey(string key) { if (string.IsNullOrWhiteSpace(key)) return 0; if (key.Length == 1) { var c = char.ToUpperInvariant(key[0]); if (c is >= 'A' and <= 'Z') return c; if (c is >= '0' and <= '9') return c; } return 0x58; // Default 'X' } /// public void Dispose() { if (_isDisposed) return; _isDisposed = true; try { UnregisterHotKey(); } catch { } if (_threadId != 0) { PostThreadMessage(_threadId, WmQuit, UIntPtr.Zero, IntPtr.Zero); } } [StructLayout(LayoutKind.Sequential)] private struct Msg { public IntPtr hwnd; public uint message; public UIntPtr wParam; public IntPtr lParam; public uint time; public POINT pt; } [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x; public int y; } [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); [DllImport("user32.dll")] private static extern sbyte GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); [DllImport("user32.dll")] private static extern bool TranslateMessage(ref Msg lpMsg); [DllImport("user32.dll")] private static extern IntPtr DispatchMessage(ref Msg lpMsg); [DllImport("user32.dll", SetLastError = true)] private static extern bool PostThreadMessage(uint idThread, uint msg, UIntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); }