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
}
}