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();
}