sl@27: using System;
sl@27: using System.Windows.Forms;
sl@27: using System.Runtime.InteropServices;
sl@27: using System.Diagnostics;
sl@27: using System.Text;
sl@27: using Microsoft.Win32.SafeHandles;
sl@27: using Win32;
sl@27: using System.Collections.Generic;
sl@41: using System.Timers;
sl@27:
sl@27:
sl@27: namespace Hid
sl@27: {
sl@27: ///
sl@27: /// Represent a HID event.
StephaneLenclud@54: /// TODO: Rename this into HidRawInput?
sl@27: ///
StephaneLenclud@54: public class HidEvent : IDisposable
sl@27: {
sl@27: public bool IsValid { get; private set; }
StephaneLenclud@54: public bool IsForeground { get; private set; }
StephaneLenclud@54: public bool IsBackground { get { return !IsForeground; } }
sl@27: public bool IsMouse { get; private set; }
sl@27: public bool IsKeyboard { get; private set; }
sl@27: public bool IsGeneric { get; private set; }
sl@29: public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
StephaneLenclud@49: public bool IsButtonUp { get { return Usages.Count == 0; } }
sl@44: public bool IsRepeat { get { return RepeatCount != 0; } }
sl@44: public uint RepeatCount { get; private set; }
sl@27:
sl@27: public HidDevice Device { get; private set; }
sl@27:
sl@27: public ushort UsagePage { get; private set; }
sl@27: public ushort UsageCollection { get; private set; }
sl@31: public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
StephaneLenclud@49: public List Usages { get; private set; }
StephaneLenclud@54: //TODO: We need a collection of input report
StephaneLenclud@54: public byte[] InputReport { get; private set; }
StephaneLenclud@54: //
StephaneLenclud@54: public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
sl@41: public event HidEventRepeatDelegate OnHidEventRepeat;
sl@27:
sl@41: private System.Timers.Timer Timer { get; set; }
sl@44: public DateTime Time { get; private set; }
sl@44: public DateTime OriginalTime { get; private set; }
sl@41:
sl@43: //Compute repeat delay and speed based on system settings
sl@43: //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
sl@43: private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
sl@43: private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
sl@43:
sl@42: ///
sl@42: /// Tells whether this event has already been disposed of.
sl@42: ///
sl@42: public bool IsStray { get { return Timer == null; } }
sl@42:
sl@43: ///
sl@43: /// We typically dispose of events as soon as we get the corresponding key up signal.
sl@43: ///
sl@41: public void Dispose()
sl@41: {
sl@41: Timer.Enabled = false;
sl@41: Timer.Dispose();
sl@43: //Mark this event as a stray
sl@41: Timer = null;
sl@41: }
sl@27:
sl@27: ///
sl@27: /// Initialize an HidEvent from a WM_INPUT message
sl@27: ///
sl@27: /// Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice
sl@41: public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
sl@27: {
sl@44: RepeatCount = 0;
sl@27: IsValid = false;
sl@27: IsKeyboard = false;
sl@27: IsGeneric = false;
StephaneLenclud@54:
sl@27:
sl@44: Time = DateTime.Now;
sl@44: OriginalTime = DateTime.Now;
sl@41: Timer = new System.Timers.Timer();
sl@44: Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
sl@27: Usages = new List();
sl@41: OnHidEventRepeat += aRepeatDelegate;
sl@27:
sl@27: if (aMessage.Msg != Const.WM_INPUT)
sl@27: {
sl@27: //Has to be a WM_INPUT message
sl@27: return;
sl@27: }
sl@27:
sl@27: if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
sl@27: {
sl@27: IsForeground = true;
sl@27: }
sl@27: else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
sl@27: {
sl@27: IsForeground = false;
sl@27: }
sl@27:
sl@27: //Declare some pointers
sl@27: IntPtr rawInputBuffer = IntPtr.Zero;
sl@27:
sl@27: try
sl@27: {
sl@27: //Fetch raw input
sl@27: RAWINPUT rawInput = new RAWINPUT();
StephaneLenclud@60: if (!Win32.RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer))
sl@27: {
sl@27: return;
sl@27: }
sl@27:
sl@27:
sl@27: //Get various information about this HID device
StephaneLenclud@54: Device = new Hid.HidDevice(rawInput.header.hDevice);
sl@27:
StephaneLenclud@60: if (rawInput.header.dwType == Win32.RawInputDeviceType.RIM_TYPEHID) //Check that our raw input is HID
sl@27: {
sl@27: IsGeneric = true;
sl@27:
sl@27: Debug.WriteLine("WM_INPUT source device is HID.");
sl@27: //Get Usage Page and Usage
sl@27: //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
StephaneLenclud@53: UsagePage = Device.Info.hid.usUsagePage;
StephaneLenclud@53: UsageCollection = Device.Info.hid.usUsage;
sl@27:
sl@27: if (!(rawInput.hid.dwSizeHid > 1 //Make sure our HID msg size more than 1. In fact the first ushort is irrelevant to us for now
sl@27: && rawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
sl@27: {
sl@27: return;
sl@27: }
sl@27:
sl@27: //Allocate a buffer for one HID input
StephaneLenclud@54: InputReport = new byte[rawInput.hid.dwSizeHid];
sl@27:
sl@27: Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
sl@27:
sl@27: //For each HID input report in our raw input
sl@27: for (int i = 0; i < rawInput.hid.dwCount; i++)
sl@27: {
sl@27: //Compute the address from which to copy our HID input
sl@27: int hidInputOffset = 0;
sl@27: unsafe
sl@27: {
sl@27: byte* source = (byte*)rawInputBuffer;
sl@27: source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
sl@27: hidInputOffset = (int)source;
sl@27: }
sl@27:
sl@27: //Copy HID input into our buffer
StephaneLenclud@54: Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)rawInput.hid.dwSizeHid);
sl@27:
sl@27: //Print HID input report in our debug output
StephaneLenclud@49: //string hidDump = "HID input report: " + InputReportString();
StephaneLenclud@49: //Debug.WriteLine(hidDump);
sl@27:
StephaneLenclud@49: //Do proper parsing of our HID report
StephaneLenclud@54: //First query our usage count
StephaneLenclud@54: uint usageCount = 0;
StephaneLenclud@49: Win32.USAGE_AND_PAGE[] usages = null;
StephaneLenclud@54: Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
StephaneLenclud@54: if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
StephaneLenclud@54: {
StephaneLenclud@54: //Allocate a large enough buffer
StephaneLenclud@54: usages = new Win32.USAGE_AND_PAGE[usageCount];
StephaneLenclud@54: //...and fetch our usages
StephaneLenclud@52: status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
StephaneLenclud@54: if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
StephaneLenclud@54: {
StephaneLenclud@54: Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
StephaneLenclud@54: }
StephaneLenclud@54: }
StephaneLenclud@54: else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
StephaneLenclud@54: {
StephaneLenclud@54: Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
StephaneLenclud@54: }
StephaneLenclud@49:
StephaneLenclud@54: Debug.WriteLine("Usage count: " + usageCount.ToString());
StephaneLenclud@49:
StephaneLenclud@52: //Copy usages into this event
StephaneLenclud@54: if (usages != null)
StephaneLenclud@54: {
StephaneLenclud@54: foreach (USAGE_AND_PAGE up in usages)
StephaneLenclud@54: {
StephaneLenclud@54: //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
StephaneLenclud@54: //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
StephaneLenclud@54: //Add this usage to our list
StephaneLenclud@54: Usages.Add(up.Usage);
StephaneLenclud@54: }
StephaneLenclud@54: }
sl@27: }
sl@27: }
StephaneLenclud@60: else if (rawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
sl@27: {
sl@27: IsMouse = true;
sl@27:
StephaneLenclud@54: Debug.WriteLine("WM_INPUT source device is Mouse.");
sl@27: // do mouse handling...
sl@27: }
StephaneLenclud@60: else if (rawInput.header.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
sl@27: {
sl@27: IsKeyboard = true;
sl@27:
sl@27: Debug.WriteLine("WM_INPUT source device is Keyboard.");
sl@27: // do keyboard handling...
StephaneLenclud@53: Debug.WriteLine("Type: " + Device.Info.keyboard.dwType.ToString());
StephaneLenclud@53: Debug.WriteLine("SubType: " + Device.Info.keyboard.dwSubType.ToString());
StephaneLenclud@53: Debug.WriteLine("Mode: " + Device.Info.keyboard.dwKeyboardMode.ToString());
StephaneLenclud@53: Debug.WriteLine("Number of function keys: " + Device.Info.keyboard.dwNumberOfFunctionKeys.ToString());
StephaneLenclud@53: Debug.WriteLine("Number of indicators: " + Device.Info.keyboard.dwNumberOfIndicators.ToString());
StephaneLenclud@53: Debug.WriteLine("Number of keys total: " + Device.Info.keyboard.dwNumberOfKeysTotal.ToString());
sl@27: }
sl@27: }
sl@27: finally
sl@27: {
sl@27: //Always executed when leaving our try block
sl@27: Marshal.FreeHGlobal(rawInputBuffer);
sl@27: }
sl@27:
sl@47: //
sl@47: if (IsButtonDown)
sl@41: {
StephaneLenclud@54: //TODO: Make this optional
StephaneLenclud@63: //StartRepeatTimer(iRepeatDelay);
sl@41: }
StephaneLenclud@54:
sl@27: IsValid = true;
sl@27: }
sl@27:
sl@41: public void StartRepeatTimer(double aInterval)
sl@41: {
sl@41: if (Timer == null)
sl@41: {
sl@41: return;
sl@41: }
sl@41: Timer.Enabled = false;
sl@44: //Initial delay do not use auto reset
sl@44: //After our initial delay however we do setup our timer one more time using auto reset
StephaneLenclud@54: Timer.AutoReset = (RepeatCount != 0);
StephaneLenclud@54: Timer.Interval = aInterval;
StephaneLenclud@54: Timer.Enabled = true;
sl@41: }
sl@27:
sl@44: static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
sl@41: {
sl@42: if (aHidEvent.IsStray)
sl@42: {
sl@42: //Skip events if canceled
sl@42: return;
sl@42: }
sl@44:
sl@44: aHidEvent.RepeatCount++;
sl@44: aHidEvent.Time = DateTime.Now;
StephaneLenclud@54: if (aHidEvent.RepeatCount == 1)
sl@44: {
sl@44: //Re-Start our timer only after the initial delay
sl@44: aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
sl@44: }
sl@44:
sl@44: //Broadcast our repeat event
sl@44: aHidEvent.OnHidEventRepeat(aHidEvent);
sl@41: }
sl@27:
StephaneLenclud@54: ///
StephaneLenclud@54: /// Print information about this device to our debug output.
StephaneLenclud@54: ///
StephaneLenclud@54: public void DebugWrite()
StephaneLenclud@54: {
StephaneLenclud@54: if (!IsValid)
StephaneLenclud@54: {
StephaneLenclud@54: Debug.WriteLine("==== Invalid HidEvent");
StephaneLenclud@54: return;
StephaneLenclud@54: }
StephaneLenclud@54: Device.DebugWrite();
StephaneLenclud@54: if (IsGeneric) Debug.WriteLine("==== Generic");
StephaneLenclud@54: if (IsKeyboard) Debug.WriteLine("==== Keyboard");
StephaneLenclud@54: if (IsMouse) Debug.WriteLine("==== Mouse");
StephaneLenclud@54: Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
StephaneLenclud@54: Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
StephaneLenclud@54: Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
StephaneLenclud@54: Debug.WriteLine("==== InputReport: 0x" + InputReportString());
StephaneLenclud@54: foreach (ushort usage in Usages)
StephaneLenclud@54: {
StephaneLenclud@54: Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
StephaneLenclud@54: }
StephaneLenclud@54: }
StephaneLenclud@49:
StephaneLenclud@54: ///
StephaneLenclud@54: ///
StephaneLenclud@54: ///
StephaneLenclud@54: ///
StephaneLenclud@54: public string InputReportString()
StephaneLenclud@54: {
StephaneLenclud@54: string hidDump = "";
StephaneLenclud@54: foreach (byte b in InputReport)
StephaneLenclud@54: {
StephaneLenclud@54: hidDump += b.ToString("X2");
StephaneLenclud@54: }
StephaneLenclud@54: return hidDump;
StephaneLenclud@54: }
StephaneLenclud@49:
StephaneLenclud@49:
StephaneLenclud@54: ///
StephaneLenclud@54: /// Create a list view item describing this HidEvent
StephaneLenclud@54: ///
StephaneLenclud@54: ///
sl@42: public ListViewItem ToListViewItem()
sl@42: {
StephaneLenclud@49: string usageText = "";
sl@42:
StephaneLenclud@54: foreach (ushort usage in Usages)
StephaneLenclud@54: {
StephaneLenclud@54: if (usageText != "")
StephaneLenclud@54: {
StephaneLenclud@54: //Add a separator
StephaneLenclud@54: usageText += ", ";
StephaneLenclud@54: }
sl@42:
StephaneLenclud@54: UsagePage usagePage = (UsagePage)UsagePage;
StephaneLenclud@54: switch (usagePage)
StephaneLenclud@54: {
StephaneLenclud@54: case Hid.UsagePage.Consumer:
StephaneLenclud@66: usageText += ((Hid.Usage.ConsumerControl)usage).ToString();
StephaneLenclud@54: break;
sl@42:
StephaneLenclud@54: case Hid.UsagePage.WindowsMediaCenterRemoteControl:
StephaneLenclud@66: usageText += ((Hid.Usage.WindowsMediaCenterRemoteControl)usage).ToString();
StephaneLenclud@54: break;
StephaneLenclud@49:
StephaneLenclud@54: default:
StephaneLenclud@54: usageText += usage.ToString("X2");
StephaneLenclud@54: break;
StephaneLenclud@54: }
StephaneLenclud@54: }
StephaneLenclud@49:
StephaneLenclud@54: ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });
sl@42: return item;
sl@42: }
sl@42:
sl@27: }
sl@27:
sl@27: }