using System;
using System.Runtime.InteropServices;
namespace Hua.Todo.Avalonia.Services.Platforms;
///
/// Windows 全局键盘事件处理器。
/// 用于监听按键并向上层暴露事件(例如 Esc),以保证在 WebView 获得焦点时依旧可触发窗口级交互。
///
public sealed class WindowsKeyboardHandler : IDisposable
{
private readonly KeyboardHook _keyboardHook;
private bool _isDisposed;
///
/// 当检测到 Esc 键抬起时触发。
///
public event EventHandler? EscKeyPressed;
///
/// 创建 。
///
public WindowsKeyboardHandler()
{
_keyboardHook = new KeyboardHook();
_keyboardHook.KeyPressed += OnKeyPressed;
}
///
/// 开始监听键盘事件。
///
public void Start()
{
_keyboardHook.Hook();
}
private void OnKeyPressed(object? sender, KeyPressedEventArgs e)
{
if (e.Key == 0x1B && !e.IsKeyDown)
{
EscKeyPressed?.Invoke(this, EventArgs.Empty);
}
}
///
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
_keyboardHook.Unhook();
_keyboardHook.KeyPressed -= OnKeyPressed;
}
private sealed class KeyboardHook : IDisposable
{
private const int WhKeyboardLl = 13;
private const int WmKeyDown = 0x0100;
private const int WmKeyUp = 0x0101;
private const int WmSysKeyDown = 0x0104;
private const int WmSysKeyUp = 0x0105;
private readonly LowLevelKeyboardProc _proc;
private IntPtr _hookId = IntPtr.Zero;
private bool _isDisposed;
public event EventHandler? KeyPressed;
public KeyboardHook()
{
_proc = HookCallback;
}
public void Hook()
{
if (_hookId != IntPtr.Zero) return;
_hookId = SetWindowsHookEx(WhKeyboardLl, _proc, GetModuleHandle(null), 0);
}
public void Unhook()
{
if (_hookId == IntPtr.Zero) return;
UnhookWindowsHookEx(_hookId);
_hookId = IntPtr.Zero;
}
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var vkCode = Marshal.ReadInt32(lParam);
var isKeyDown = wParam == (IntPtr)WmKeyDown || wParam == (IntPtr)WmSysKeyDown;
var isKeyUp = wParam == (IntPtr)WmKeyUp || wParam == (IntPtr)WmSysKeyUp;
if (isKeyDown || isKeyUp)
{
KeyPressed?.Invoke(this, new KeyPressedEventArgs(vkCode, isKeyDown));
}
}
return CallNextHookEx(_hookId, nCode, wParam, lParam);
}
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
Unhook();
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string? lpModuleName);
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private sealed class KeyPressedEventArgs : EventArgs
{
public int Key { get; }
public bool IsKeyDown { get; }
public KeyPressedEventArgs(int key, bool isKeyDown)
{
Key = key;
IsKeyDown = isKeyDown;
}
}
}