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

LogiBoardToolBox

【WinUI】罗技鼠标板载驱动程序

公开
关注 0 Fork 0 Star 0
UTF-8
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;

namespace LogiBoardToolBox.Core.Windows;

public sealed record RawMouseDevice(IntPtr Handle, string Path);
public sealed record RawMousePacket(IntPtr Device, string Path, ushort Buttons, int X, int Y, bool Absolute, long Timestamp);

/// <summary>A message-only INPUTSINK window observes physical mouse reports without focus or injected input.</summary>
[SupportedOSPlatform("windows")]
public sealed class RawMouseMonitor : IDisposable
{
    private readonly Thread thread;
    private readonly WndProc callback;
    private readonly TaskCompletionSource ready = new(TaskCreationOptions.RunContinuationsAsynchronously);
    private readonly Dictionary<IntPtr, string> paths = [];
    private uint threadId;
    private IntPtr window;
    private bool disposed;
    public event EventHandler<RawMousePacket>? Packet;
    public Task Ready => ready.Task;
    public RawMouseMonitor()
    {
        callback = ProcessMessage;
        thread = new(Run) { IsBackground = true, Name = "Logitech physical mouse input monitor" };
        thread.Start();
    }
    public static IReadOnlyList<RawMouseDevice> Devices()
    {
        uint count = 0; var size = (uint)Marshal.SizeOf<DeviceList>();
        if (GetRawInputDeviceList(null, ref count, size) == uint.MaxValue) throw new Win32Exception();
        var list = new DeviceList[count];
        if (GetRawInputDeviceList(list, ref count, size) == uint.MaxValue) throw new Win32Exception();
        return list.Take((int)count).Where(d => d.Type == 0).Select(d => new RawMouseDevice(d.Device, DevicePath(d.Device))).ToArray();
    }
    private void Run()
    {
        var className = "LogiBoardRawInput-" + Guid.NewGuid().ToString("N"); var instance = GetModuleHandle(null);
        try
        {
            threadId = GetCurrentThreadId();
            var wc = new WindowClass { Size = (uint)Marshal.SizeOf<WindowClass>(), Callback = callback, Instance = instance, Name = className };
            if (RegisterClassEx(ref wc) == 0) throw new Win32Exception();
            window = CreateWindowEx(0, className, "", 0, 0, 0, 0, 0, new(-3), IntPtr.Zero, instance, IntPtr.Zero);
            if (window == IntPtr.Zero) throw new Win32Exception();
            if (!RegisterRawInputDevices([new() { Page = 1, Usage = 2, Flags = 0x100, Target = window }], 1, (uint)Marshal.SizeOf<RawDevice>())) throw new Win32Exception();
            ready.TrySetResult();
            while (GetMessage(out var message, IntPtr.Zero, 0, 0) > 0) { TranslateMessage(ref message); DispatchMessage(ref message); }
        }
        catch (Exception error) { ready.TrySetException(error); }
        finally
        {
            if (window != IntPtr.Zero) { DestroyWindow(window); window = IntPtr.Zero; }
            UnregisterClass(className, instance); GC.KeepAlive(callback);
        }
    }
    private IntPtr ProcessMessage(IntPtr hwnd, uint message, IntPtr wparam, IntPtr lparam)
    {
        if (message == 0x00FF)
        {
            uint size = 0; var headerSize = (uint)(IntPtr.Size == 8 ? 24 : 16);
            if (GetRawInputData(lparam, 0x10000003, IntPtr.Zero, ref size, headerSize) == 0 && size is >= 40 and <= 1024)
            {
                var data = Marshal.AllocHGlobal((int)size);
                try
                {
                    if (GetRawInputData(lparam, 0x10000003, data, ref size, headerSize) == size && Marshal.ReadInt32(data) == 0)
                    {
                        var device = Marshal.ReadIntPtr(data, 8); var mouse = data + (int)headerSize;
                        if (!paths.TryGetValue(device, out var path)) paths[device] = path = DevicePath(device);
                        var packet = new RawMousePacket(device, path, (ushort)Marshal.ReadInt16(mouse, 4), Marshal.ReadInt32(mouse, 12), Marshal.ReadInt32(mouse, 16), (Marshal.ReadInt16(mouse) & 1) != 0, System.Diagnostics.Stopwatch.GetTimestamp());
                        try { Packet?.Invoke(this, packet); } catch (Exception error) { System.Diagnostics.Debug.WriteLine(error); }
                    }
                }
                finally { Marshal.FreeHGlobal(data); }
            }
        }
        return DefWindowProc(hwnd, message, wparam, lparam);
    }
    private static string DevicePath(IntPtr device)
    {
        uint size = 0; GetRawInputDeviceInfo(device, 0x20000007, null, ref size);
        if (size == 0 || size > 32768) return "";
        var text = new StringBuilder((int)size + 1);
        return GetRawInputDeviceInfo(device, 0x20000007, text, ref size) == uint.MaxValue ? "" : text.ToString();
    }
    public void Dispose()
    {
        if (disposed) return; disposed = true;
        if (threadId != 0) PostThreadMessage(threadId, 0x0012, IntPtr.Zero, IntPtr.Zero);
        if (Thread.CurrentThread != thread) thread.Join(1000);
    }
    [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate IntPtr WndProc(IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WindowClass { public uint Size, Style; public WndProc Callback; public int ClassExtra, WindowExtra; public IntPtr Instance, Icon, Cursor, Background; public string? Menu, Name; public IntPtr SmallIcon; }
    [StructLayout(LayoutKind.Sequential)] private struct DeviceList { public IntPtr Device; public uint Type; }
    [StructLayout(LayoutKind.Sequential)] private struct RawDevice { public ushort Page, Usage; public uint Flags; public IntPtr Target; }
    [StructLayout(LayoutKind.Sequential)] private struct Message { public IntPtr Hwnd; public uint Id; public IntPtr Wparam, Lparam; public uint Time; public int X, Y; public uint Private; }
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern ushort RegisterClassEx(ref WindowClass wc);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool UnregisterClass(string name, IntPtr instance);
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CreateWindowEx(uint ex, string cls, string title, uint style, int x, int y, int w, int h, IntPtr parent, IntPtr menu, IntPtr instance, IntPtr data);
    [DllImport("user32.dll")] private static extern bool DestroyWindow(IntPtr hwnd);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr DefWindowProc(IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetMessage(out Message message, IntPtr hwnd, uint min, uint max);
    [DllImport("user32.dll")] private static extern bool TranslateMessage(ref Message message);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr DispatchMessage(ref Message message);
    [DllImport("user32.dll", SetLastError = true)] private static extern bool RegisterRawInputDevices(RawDevice[] devices, uint count, uint size);
    [DllImport("user32.dll")] private static extern uint GetRawInputData(IntPtr raw, uint command, IntPtr data, ref uint size, uint headerSize);
    [DllImport("user32.dll", SetLastError = true)] private static extern uint GetRawInputDeviceList([In, Out] DeviceList[]? devices, ref uint count, uint size);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern uint GetRawInputDeviceInfo(IntPtr device, uint command, StringBuilder? data, ref uint size);
    [DllImport("user32.dll")] private static extern bool PostThreadMessage(uint thread, uint msg, IntPtr wparam, IntPtr lparam);
    [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId();
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string? name);
}