Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multithreaded, real-time recording and analysis #25

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

mat1jaczyyy
Copy link
Owner

Fixes #12

  • Merge master into realtime
  • Don't spawn a second WinForms thread, rather create a window manually
  • Make sure default sorting is by device (group together same device name next to each other)
  • Reimplement Freeze
  • Perf: Measure how long it takes to record a single event coming in

@mat1jaczyyy mat1jaczyyy self-assigned this Dec 16, 2023
@mat1jaczyyy mat1jaczyyy added feature New feature or request ui User interface recording Recording data from device analysis Input data analysis and removed analysis Input data analysis labels Dec 16, 2023
@Missmerel
Copy link

no

@mat1jaczyyy
Copy link
Owner Author

@Missmerel huh

@Missmerel
Copy link

Missmerel commented Feb 1, 2024 via email

@Thandylol
Copy link

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

namespace RawInputProcessor
{
public class ListenerWindow : IDisposable
{
private const int WM_CREATE = 0x0001;
private const int WM_DESTROY = 0x0002;
private const int WM_INPUT = 0x00FF;
private const int RID_INPUT = 0x10000003;
private const int RIDEV_INPUTSINK = 0x00000100;
private const int RIM_TYPEMOUSE = 0;
private const int RIM_TYPEKEYBOARD = 1;

    private IntPtr _hwnd;
    private readonly Dictionary<string, DeviceInfo> _devices = new();
    private readonly object _deviceLock = new();
    private bool _isFrozen;
    private readonly Stopwatch _eventStopwatch = new();
    
    private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    private readonly WndProcDelegate _wndProc;

    public event EventHandler<RawInputEventArgs> InputReceived;

    public ListenerWindow()
    {
        _wndProc = WndProc;
        CreateWindow();
        RegisterRawInputDevices();
    }

    private void CreateWindow()
    {
        // Register window class
        var wndClass = new WNDCLASSEX
        {
            cbSize = Marshal.SizeOf<WNDCLASSEX>(),
            lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProc),
            hInstance = GetModuleHandle(null),
            lpszClassName = "RawInputListener"
        };

        var atom = RegisterClassEx(ref wndClass);
        if (atom == 0)
            throw new Exception($"Failed to register window class. Error: {Marshal.GetLastWin32Error()}");

        // Create window
        _hwnd = CreateWindowEx(
            0,
            "RawInputListener",
            "Raw Input Listener",
            0,
            0, 0, 0, 0,
            IntPtr.Zero,
            IntPtr.Zero,
            wndClass.hInstance,
            IntPtr.Zero
        );

        if (_hwnd == IntPtr.Zero)
            throw new Exception($"Failed to create window. Error: {Marshal.GetLastWin32Error()}");
    }

    private void RegisterRawInputDevices()
    {
        var devices = new RAWINPUTDEVICE[]
        {
            new() {
                usUsagePage = 0x01,
                usUsage = 0x06,  // Keyboard
                dwFlags = RIDEV_INPUTSINK,
                hwndTarget = _hwnd
            },
            new() {
                usUsagePage = 0x01,
                usUsage = 0x02,  // Mouse
                dwFlags = RIDEV_INPUTSINK,
                hwndTarget = _hwnd
            }
        };

        if (!RegisterRawInputDevices(devices, devices.Length, Marshal.SizeOf<RAWINPUTDEVICE>()))
            throw new Exception($"Failed to register raw input devices. Error: {Marshal.GetLastWin32Error()}");
    }

    private IntPtr WndProc(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        switch (msg)
        {
            case WM_INPUT:
                if (!_isFrozen)
                    ProcessRawInput(lParam);
                break;

            case WM_DESTROY:
                PostQuitMessage(0);
                break;
        }

        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    private void ProcessRawInput(IntPtr lParam)
    {
        _eventStopwatch.Restart();

        // Get required buffer size
        uint dataSize = 0;
        GetRawInputData(lParam, RID_INPUT, IntPtr.Zero, ref dataSize, Marshal.SizeOf<RAWINPUTHEADER>());

        if (dataSize == 0)
            return;

        // Allocate buffer and get data
        var buffer = Marshal.AllocHGlobal((int)dataSize);
        try
        {
            uint result = GetRawInputData(lParam, RID_INPUT, buffer, ref dataSize, Marshal.SizeOf<RAWINPUTHEADER>());
            if (result != dataSize)
                return;

            var rawInput = Marshal.PtrToStructure<RAWINPUT>(buffer);
            ProcessInputData(rawInput);
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }

        _eventStopwatch.Stop();
        Debug.WriteLine($"Event processing time: {_eventStopwatch.ElapsedMilliseconds}ms");
    }

    private void ProcessInputData(RAWINPUT rawInput)
    {
        string deviceName = GetDeviceName(rawInput.header.hDevice);
        
        lock (_deviceLock)
        {
            if (!_devices.TryGetValue(deviceName, out var deviceInfo))
            {
                deviceInfo = new DeviceInfo { Name = deviceName };
                _devices.Add(deviceName, deviceInfo);
            }

            var eventArgs = new RawInputEventArgs
            {
                DeviceName = deviceName,
                TimeStamp = DateTime.Now,
                Type = rawInput.header.dwType switch
                {
                    RIM_TYPEKEYBOARD => InputType.Keyboard,
                    RIM_TYPEMOUSE => InputType.Mouse,
                    _ => InputType.Unknown
                }
            };

            InputReceived?.Invoke(this, eventArgs);
        }
    }

    private string GetDeviceName(IntPtr hDevice)
    {
        uint size = 0;
        GetRawInputDeviceInfo(hDevice, 0x20000007, IntPtr.Zero, ref size);

        if (size == 0)
            return "Unknown Device";

        var nameBuffer = new char[size];
        if (GetRawInputDeviceInfo(hDevice, 0x20000007, nameBuffer, ref size) != -1)
        {
            return new string(nameBuffer).Trim('\0');
        }

        return "Unknown Device";
    }

    public void ToggleFreeze()
    {
        _isFrozen = !_isFrozen;
    }

    public IEnumerable<DeviceInfo> GetDevices()
    {
        lock (_deviceLock)
        {
            return new List<DeviceInfo>(_devices.Values);
        }
    }

    public void Dispose()
    {
        if (_hwnd != IntPtr.Zero)
        {
            DestroyWindow(_hwnd);
            _hwnd = IntPtr.Zero;
        }
    }

    #region Win32 API Declarations

    [StructLayout(LayoutKind.Sequential)]
    private struct WNDCLASSEX
    {
        public int cbSize;
        public int style;
        public IntPtr lpfnWndProc;
        public int cbClsExtra;
        public int cbWndExtra;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public IntPtr hCursor;
        public IntPtr hbrBackground;
        public string lpszMenuName;
        public string lpszClassName;
        public IntPtr hIconSm;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RAWINPUTDEVICE
    {
        public ushort usUsagePage;
        public ushort usUsage;
        public uint dwFlags;
        public IntPtr hwndTarget;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RAWINPUTHEADER
    {
        public uint dwType;
        public uint dwSize;
        public IntPtr hDevice;
        public IntPtr wParam;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RAWINPUT
    {
        public RAWINPUTHEADER header;
        // Union of mouse, keyboard, and HID data follows
        // We'll process the raw data based on header.dwType
    }

    [DllImport("user32.dll")]
    private static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx);

    [DllImport("user32.dll")]
    private static extern IntPtr CreateWindowEx(
        uint dwExStyle,
        string lpClassName,
        string lpWindowName,
        uint dwStyle,
        int x,
        int y,
        int nWidth,
        int nHeight,
        IntPtr hWndParent,
        IntPtr hMenu,
        IntPtr hInstance,
        IntPtr lpParam);

    [DllImport("user32.dll")]
    private static extern bool DestroyWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern void PostQuitMessage(int nExitCode);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("user32.dll")]
    private static extern bool RegisterRawInputDevices(
        RAWINPUTDEVICE[] pRawInputDevices,
        uint uiNumDevices,
        int cbSize);

    [DllImport("user32.dll")]
    private static extern uint GetRawInputData(
        IntPtr hRawInput,
        uint uiCommand,
        IntPtr pData,
        ref uint pcbSize,
        int cbSizeHeader);

    [DllImport("user32.dll")]
    private static extern uint GetRawInputDeviceInfo(
        IntPtr hDevice,
        uint uiCommand,
        IntPtr pData,
        ref uint pcbSize);

    [DllImport("user32.dll")]
    private static extern uint GetRawInputDeviceInfo(
        IntPtr hDevice,
        uint uiCommand,
        char[] pData,
        ref uint pcbSize);

    #endregion
}

public class DeviceInfo
{
    public string Name { get; set; }
    public DateTime LastEventTime { get; set; }
    public int EventCount { get; set; }
}

public class RawInputEventArgs : EventArgs
{
    public string DeviceName { get; set; }
    public DateTime TimeStamp { get; set; }
    public InputType Type { get; set; }
}

public enum InputType
{
    Unknown,
    Keyboard,
    Mouse
}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request recording Recording data from device ui User interface
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Real-time Analysis
3 participants