XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.InteropServices;
using XFEEDToolBox.Core.Session;
namespace XFEEDToolBox.Services;

public sealed class WindowsGlobalHotkeys : IHotkeyRegistry, IDisposable
{
    private readonly nint window;
    private readonly SubclassProc callback;
    private readonly GlobalHotkeyBindings bindings;
    private readonly Action<SessionOperation> action;
    private bool disposed;
    private static int nextSubclass = 0x584600;
    private readonly nuint subclassId = (nuint)Interlocked.Increment(ref nextSubclass);
    public WindowsGlobalHotkeys(nint window, Action<SessionOperation> action)
    {
        this.window = window; this.action = action; bindings = new(this); callback = OnMessage;
        if (!SetWindowSubclass(window, callback, subclassId, 0)) throw new InvalidOperationException("无法在工具箱窗口接收全局快捷键。");
    }
    public HotkeySettings Settings => bindings.Settings;
    public void Apply(HotkeySettings settings) { ObjectDisposedException.ThrowIf(disposed, this); bindings.Apply(settings); }
    void IHotkeyRegistry.Register(int id, HotkeyGesture gesture)
    { if (!RegisterHotKey(window, id, gesture.Modifiers | 0x4000, gesture.Key)) throw new InvalidOperationException($"快捷键 {gesture} 无法注册,可能已被其他程序占用({Marshal.GetLastWin32Error()})。"); }
    void IHotkeyRegistry.Unregister(int id) => UnregisterHotKey(window, id);
    private nint OnMessage(nint hwnd, uint message, nuint wParam, nint lParam, nuint subclass, nuint data)
    {
        if (message == 0x0312 && !disposed && bindings.Resolve(unchecked((int)wParam)) is { } operation) { action(operation); return 0; }
        return DefSubclassProc(hwnd, message, wParam, lParam);
    }
    public void Dispose()
    {
        if (disposed) return;
        disposed = true; bindings.Dispose(); RemoveWindowSubclass(window, callback, subclassId); GC.KeepAlive(callback);
    }
    /// <summary>Hidden-window QA: posts only to our own window and uses a counting callback, never a game command.</summary>
    public static async Task<object> ProbeAsync(nint window)
    {
        var received = new TaskCompletionSource<SessionOperation>(TaskCreationOptions.RunContinuationsAsynchronously);
        var gesture = HotkeyGesture.Parse("Ctrl+Alt+Shift+F24");
        using var probe = new WindowsGlobalHotkeys(window, operation => received.TrySetResult(operation));
        try
        {
            probe.Apply(new(true, Restart: gesture.ToString()));
            var id = probe.bindings.IdFor(SessionOperation.Restart)!.Value;
            if (!PostMessage(window, 0x0312, (nuint)id, (nint)((gesture.Key << 16) | gesture.Modifiers))) throw new InvalidOperationException("无法向工具箱测试窗口投递快捷键消息。");
            var action = await received.Task.WaitAsync(TimeSpan.FromSeconds(2));
            probe.Dispose();
            var released = RegisterHotKey(window, 0x5FFD, gesture.Modifiers | 0x4000, gesture.Key);
            if (released) UnregisterHotKey(window, 0x5FFD);
            return new { registered = true, messageDispatched = action == SessionOperation.Restart, released, gameActionInvoked = false };
        }
        catch (Exception e) { return new { registered = false, error = e.Message, gameActionInvoked = false }; }
    }
    private delegate nint SubclassProc(nint hwnd, uint msg, nuint wParam, nint lParam, nuint id, nuint data);
    [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool RegisterHotKey(nint hwnd, int id, uint modifiers, uint key);
    [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnregisterHotKey(nint hwnd, int id);
    [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool PostMessage(nint hwnd, uint msg, nuint wParam, nint lParam);
    [DllImport("comctl32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetWindowSubclass(nint hwnd, SubclassProc proc, nuint id, nuint data);
    [DllImport("comctl32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool RemoveWindowSubclass(nint hwnd, SubclassProc proc, nuint id);
    [DllImport("comctl32.dll")] private static extern nint DefSubclassProc(nint hwnd, uint msg, nuint wParam, nint lParam);
}