using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace grapher.Models.Mouse { public class MouseWatcher { #region External /// /// Enumeration containing HID usage page flags. /// public enum HIDUsagePage : ushort { /// Unknown usage page. Undefined = 0x00, /// Generic desktop controls. Generic = 0x01, /// Simulation controls. Simulation = 0x02, /// Virtual reality controls. VR = 0x03, /// Sports controls. Sport = 0x04, /// Games controls. Game = 0x05, /// Keyboard controls. Keyboard = 0x07, /// LED controls. LED = 0x08, /// Button. Button = 0x09, /// Ordinal. Ordinal = 0x0A, /// Telephony. Telephony = 0x0B, /// Consumer. Consumer = 0x0C, /// Digitizer. Digitizer = 0x0D, /// Physical interface device. PID = 0x0F, /// Unicode. Unicode = 0x10, /// Alphanumeric display. AlphaNumeric = 0x14, /// Medical instruments. Medical = 0x40, /// Monitor page 0. MonitorPage0 = 0x80, /// Monitor page 1. MonitorPage1 = 0x81, /// Monitor page 2. MonitorPage2 = 0x82, /// Monitor page 3. MonitorPage3 = 0x83, /// Power page 0. PowerPage0 = 0x84, /// Power page 1. PowerPage1 = 0x85, /// Power page 2. PowerPage2 = 0x86, /// Power page 3. PowerPage3 = 0x87, /// Bar code scanner. BarCode = 0x8C, /// Scale page. Scale = 0x8D, /// Magnetic strip reading devices. MSR = 0x8E } /// /// Enumeration containing the HID usage values. /// public enum HIDUsage : ushort { /// Pointer = 0x01, /// Mouse = 0x02, /// Joystick = 0x04, /// Gamepad = 0x05, /// Keyboard = 0x06, /// Keypad = 0x07, /// SystemControl = 0x80, /// X = 0x30, /// Y = 0x31, /// Z = 0x32, /// RelativeX = 0x33, /// RelativeY = 0x34, /// RelativeZ = 0x35, /// Slider = 0x36, /// Dial = 0x37, /// Wheel = 0x38, /// HatSwitch = 0x39, /// CountedBuffer = 0x3A, /// ByteCount = 0x3B, /// MotionWakeup = 0x3C, /// VX = 0x40, /// VY = 0x41, /// VZ = 0x42, /// VBRX = 0x43, /// VBRY = 0x44, /// VBRZ = 0x45, /// VNO = 0x46, /// SystemControlPower = 0x81, /// SystemControlSleep = 0x82, /// SystemControlWake = 0x83, /// SystemControlContextMenu = 0x84, /// SystemControlMainMenu = 0x85, /// SystemControlApplicationMenu = 0x86, /// SystemControlHelpMenu = 0x87, /// SystemControlMenuExit = 0x88, /// SystemControlMenuSelect = 0x89, /// SystemControlMenuRight = 0x8A, /// SystemControlMenuLeft = 0x8B, /// SystemControlMenuUp = 0x8C, /// SystemControlMenuDown = 0x8D, /// KeyboardNoEvent = 0x00, /// KeyboardRollover = 0x01, /// KeyboardPostFail = 0x02, /// KeyboardUndefined = 0x03, /// KeyboardaA = 0x04, /// KeyboardzZ = 0x1D, /// Keyboard1 = 0x1E, /// Keyboard0 = 0x27, /// KeyboardLeftControl = 0xE0, /// KeyboardLeftShift = 0xE1, /// KeyboardLeftALT = 0xE2, /// KeyboardLeftGUI = 0xE3, /// KeyboardRightControl = 0xE4, /// KeyboardRightShift = 0xE5, /// KeyboardRightALT = 0xE6, /// KeyboardRightGUI = 0xE7, /// KeyboardScrollLock = 0x47, /// KeyboardNumLock = 0x53, /// KeyboardCapsLock = 0x39, /// KeyboardF1 = 0x3A, /// KeyboardF12 = 0x45, /// KeyboardReturn = 0x28, /// KeyboardEscape = 0x29, /// KeyboardDelete = 0x2A, /// KeyboardPrintScreen = 0x46, /// LEDNumLock = 0x01, /// LEDCapsLock = 0x02, /// LEDScrollLock = 0x03, /// LEDCompose = 0x04, /// LEDKana = 0x05, /// LEDPower = 0x06, /// LEDShift = 0x07, /// LEDDoNotDisturb = 0x08, /// LEDMute = 0x09, /// LEDToneEnable = 0x0A, /// LEDHighCutFilter = 0x0B, /// LEDLowCutFilter = 0x0C, /// LEDEqualizerEnable = 0x0D, /// LEDSoundFieldOn = 0x0E, /// LEDSurroundFieldOn = 0x0F, /// LEDRepeat = 0x10, /// LEDStereo = 0x11, /// LEDSamplingRateDirect = 0x12, /// LEDSpinning = 0x13, /// LEDCAV = 0x14, /// LEDCLV = 0x15, /// LEDRecordingFormatDet = 0x16, /// LEDOffHook = 0x17, /// LEDRing = 0x18, /// LEDMessageWaiting = 0x19, /// LEDDataMode = 0x1A, /// LEDBatteryOperation = 0x1B, /// LEDBatteryOK = 0x1C, /// LEDBatteryLow = 0x1D, /// LEDSpeaker = 0x1E, /// LEDHeadset = 0x1F, /// LEDHold = 0x20, /// LEDMicrophone = 0x21, /// LEDCoverage = 0x22, /// LEDNightMode = 0x23, /// LEDSendCalls = 0x24, /// LEDCallPickup = 0x25, /// LEDConference = 0x26, /// LEDStandBy = 0x27, /// LEDCameraOn = 0x28, /// LEDCameraOff = 0x29, /// LEDOnLine = 0x2A, /// LEDOffLine = 0x2B, /// LEDBusy = 0x2C, /// LEDReady = 0x2D, /// LEDPaperOut = 0x2E, /// LEDPaperJam = 0x2F, /// LEDRemote = 0x30, /// LEDForward = 0x31, /// LEDReverse = 0x32, /// LEDStop = 0x33, /// LEDRewind = 0x34, /// LEDFastForward = 0x35, /// LEDPlay = 0x36, /// LEDPause = 0x37, /// LEDRecord = 0x38, /// LEDError = 0x39, /// LEDSelectedIndicator = 0x3A, /// LEDInUseIndicator = 0x3B, /// LEDMultiModeIndicator = 0x3C, /// LEDIndicatorOn = 0x3D, /// LEDIndicatorFlash = 0x3E, /// LEDIndicatorSlowBlink = 0x3F, /// LEDIndicatorFastBlink = 0x40, /// LEDIndicatorOff = 0x41, /// LEDFlashOnTime = 0x42, /// LEDSlowBlinkOnTime = 0x43, /// LEDSlowBlinkOffTime = 0x44, /// LEDFastBlinkOnTime = 0x45, /// LEDFastBlinkOffTime = 0x46, /// LEDIndicatorColor = 0x47, /// LEDRed = 0x48, /// LEDGreen = 0x49, /// LEDAmber = 0x4A, /// LEDGenericIndicator = 0x3B, /// TelephonyPhone = 0x01, /// TelephonyAnsweringMachine = 0x02, /// TelephonyMessageControls = 0x03, /// TelephonyHandset = 0x04, /// TelephonyHeadset = 0x05, /// TelephonyKeypad = 0x06, /// TelephonyProgrammableButton = 0x07, /// SimulationRudder = 0xBA, /// SimulationThrottle = 0xBB } /// Enumeration containing flags for a raw input device. [Flags()] public enum RawInputDeviceFlags { /// No flags. None = 0, /// If set, this removes the top level collection from the inclusion list. This tells the operating system to stop reading from a device which matches the top level collection. Remove = 0x00000001, /// If set, this specifies the top level collections to exclude when reading a complete usage page. This flag only affects a TLC whose usage page is already specified with PageOnly. Exclude = 0x00000010, /// If set, this specifies all devices whose top level collection is from the specified usUsagePage. Note that Usage must be zero. To exclude a particular top level collection, use Exclude. PageOnly = 0x00000020, /// If set, this prevents any devices specified by UsagePage or Usage from generating legacy messages. This is only for the mouse and keyboard. NoLegacy = 0x00000030, /// If set, this enables the caller to receive the input even when the caller is not in the foreground. Note that WindowHandle must be specified. InputSink = 0x00000100, /// If set, the mouse button click does not activate the other window. CaptureMouse = 0x00000200, /// If set, the application-defined keyboard device hotkeys are not handled. However, the system hotkeys; for example, ALT+TAB and CTRL+ALT+DEL, are still handled. By default, all keyboard hotkeys are handled. NoHotKeys can be specified even if NoLegacy is not specified and WindowHandle is NULL. NoHotKeys = 0x00000200, /// If set, application keys are handled. NoLegacy must be specified. Keyboard only. AppKeys = 0x00000400 } /// Value type for raw input devices. [StructLayout(LayoutKind.Sequential)] public struct RAWINPUTDEVICE { /// Top level collection Usage page for the raw input device. public HIDUsagePage UsagePage; /// Top level collection Usage for the raw input device. public HIDUsage Usage; /// Mode flag that specifies how to interpret the information provided by UsagePage and Usage. public RawInputDeviceFlags Flags; /// Handle to the target device. If NULL, it follows the keyboard focus. public IntPtr WindowHandle; } /// /// Contains the raw input from a device. /// [StructLayout(LayoutKind.Sequential)] public struct RawInput { /// /// Header for the data. /// public RAWINPUTHEADER Header; public Union Data; [StructLayout(LayoutKind.Explicit)] public struct Union { /// /// Mouse raw input data. /// [FieldOffset(0)] public RawMouse Mouse; /// /// Keyboard raw input data. /// [FieldOffset(0)] public RawKeyboard Keyboard; /// /// HID raw input data. /// [FieldOffset(0)] public RawHID HID; } } /// /// Value type for raw input from a HID. /// [StructLayout(LayoutKind.Sequential)] public struct RawHID { /// Size of the HID data in bytes. public int Size; /// Number of HID in Data. public int Count; /// Data for the HID. public IntPtr Data; } /// /// Value type for a raw input header. /// [StructLayout(LayoutKind.Sequential)] public struct RAWINPUTHEADER { /// Type of device the input is coming from. public RawInputType Type; /// Size of the packet of data. public int Size; /// Handle to the device sending the data. public IntPtr Device; /// wParam from the window message. public IntPtr wParam; } /// /// Enumeration containing the type device the raw input is coming from. /// public enum RawInputType { /// /// Mouse input. /// Mouse = 0, /// /// Keyboard input. /// Keyboard = 1, /// /// Human interface device input. /// HID = 2, /// /// Another device that is not the keyboard or the mouse. /// Other = 3 } /// /// Contains information about the state of the mouse. /// [StructLayout(LayoutKind.Sequential)] public struct RawMouse { /// /// The mouse state. /// public RawMouseFlags Flags; [StructLayout(LayoutKind.Explicit)] public struct Data { [FieldOffset(0)] public uint Buttons; /// /// If the mouse wheel is moved, this will contain the delta amount. /// [FieldOffset(2)] public ushort ButtonData; /// /// Flags for the event. /// [FieldOffset(0)] public RawMouseButtons ButtonFlags; } public Data _Data; /// /// Raw button data. /// public uint RawButtons; /// /// The motion in the X direction. This is signed relative motion or /// absolute motion, depending on the value of usFlags. /// public int LastX; /// /// The motion in the Y direction. This is signed relative motion or absolute motion, /// depending on the value of usFlags. /// public int LastY; /// /// The device-specific additional information for the event. /// public uint ExtraInformation; } /// /// Enumeration containing the flags for raw mouse data. /// [Flags()] public enum RawMouseFlags : ushort { /// Relative to the last position. MoveRelative = 0, /// Absolute positioning. MoveAbsolute = 1, /// Coordinate data is mapped to a virtual desktop. VirtualDesktop = 2, /// Attributes for the mouse have changed. AttributesChanged = 4 } /// /// Enumeration containing the button data for raw mouse input. /// [Flags()] public enum RawMouseButtons : ushort { /// No button. None = 0, /// Left (button 1) down. LeftDown = 0x0001, /// Left (button 1) up. LeftUp = 0x0002, /// Right (button 2) down. RightDown = 0x0004, /// Right (button 2) up. RightUp = 0x0008, /// Middle (button 3) down. MiddleDown = 0x0010, /// Middle (button 3) up. MiddleUp = 0x0020, /// Button 4 down. Button4Down = 0x0040, /// Button 4 up. Button4Up = 0x0080, /// Button 5 down. Button5Down = 0x0100, /// Button 5 up. Button5Up = 0x0200, /// Mouse wheel moved. MouseWheel = 0x0400 } /// /// Value type for raw input from a keyboard. /// [StructLayout(LayoutKind.Sequential)] public struct RawKeyboard { /// Scan code for key depression. public short MakeCode; /// Scan code information. public RawKeyboardFlags Flags; /// Reserved. public short Reserved; /// Virtual key code. public ushort VirtualKey; /// Corresponding window message. public uint Message; /// Extra information. public int ExtraInformation; } /// /// Enumeration containing flags for raw keyboard input. /// [Flags] public enum RawKeyboardFlags : ushort { /// KeyMake = 0, /// KeyBreak = 1, /// KeyE0 = 2, /// KeyE1 = 4, /// TerminalServerSetLED = 8, /// TerminalServerShadow = 0x10, /// TerminalServerVKPACKET = 0x20 } /// /// Enumeration contanining the command types to issue. /// public enum RawInputCommand { /// /// Get input data. /// Input = 0x10000003, /// /// Get header data. /// Header = 0x10000005 } [DllImport("user32.dll")] public static extern bool RegisterRawInputDevices([MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] RAWINPUTDEVICE[] pRawInputDevices, int uiNumDevices, int cbSize); /// /// Function to retrieve raw input data. /// /// Handle to the raw input. /// Command to issue when retrieving data. /// Raw input data. /// Number of bytes in the array. /// Size of the header. /// 0 if successful if pData is null, otherwise number of bytes if pData is not null. [DllImport("user32.dll")] public static extern int GetRawInputData(IntPtr hRawInput, RawInputCommand uiCommand, out RawInput pData, ref int pcbSize, int cbSizeHeader); #endregion External #region Constructors public MouseWatcher(Form containingForm, Label display, AccelCharts accelCharts, Field pollRate) { ContainingForm = containingForm; Display = display; AccelCharts = accelCharts; PollRateField = pollRate; MouseData = new MouseData(); RAWINPUTDEVICE device = new RAWINPUTDEVICE(); device.WindowHandle = ContainingForm.Handle; device.UsagePage = HIDUsagePage.Generic; device.Usage = HIDUsage.Mouse; device.Flags = RawInputDeviceFlags.InputSink; RAWINPUTDEVICE[] devices = new RAWINPUTDEVICE[1]; devices[0] = device; RegisterRawInputDevices(devices, 1, Marshal.SizeOf(typeof(RAWINPUTDEVICE))); PollTime = 1; PollRate = 1000; } #endregion Constructors #region Properties private Form ContainingForm { get; } private Label Display { get; } private AccelCharts AccelCharts { get; } private Field PollRateField { get; set; } private MouseData MouseData { get; } private double PollRate { get; set; } private double PollTime { get; set; } #endregion Properties #region Methods public void OnMouseMove(int x, int y, double timeInMs) { MouseData.Set(x,y); AccelCharts.MakeDots(x, y, timeInMs); } public void UpdateLastMove() { MouseData.Get(out var x, out var y); Display.Text = $"Last (x, y): ({x}, {y})"; } public void ReadMouseMove(Message message) { RawInput rawInput = new RawInput(); int outSize = 0; int size = Marshal.SizeOf(typeof(RawInput)); outSize = GetRawInputData((IntPtr)message.LParam, RawInputCommand.Input, out rawInput, ref size, Marshal.SizeOf(typeof(RAWINPUTHEADER))); if (PollRateField.Data != PollRate) { PollRate = PollRateField.Data; PollTime = 1000 / PollRate; } if (rawInput.Data.Mouse.LastX != 0 || rawInput.Data.Mouse.LastY != 0) { OnMouseMove(rawInput.Data.Mouse.LastX, rawInput.Data.Mouse.LastY, PollTime); } } #endregion Methods } }