Adding button count property to HID device.
Adding device list refresh button.
Better reporting of input value capabilities description.
2 using System.Windows.Forms;
3 using System.Runtime.InteropServices;
4 using System.Diagnostics;
6 using Microsoft.Win32.SafeHandles;
8 using System.Collections.Generic;
15 /// Represent a HID event.
16 /// TODO: Rename this into HidRawInput?
18 public class HidEvent : IDisposable
20 public bool IsValid { get; private set; }
21 public bool IsForeground { get; private set; }
22 public bool IsBackground { get { return !IsForeground; } }
23 public bool IsMouse { get; private set; }
24 public bool IsKeyboard { get; private set; }
26 /// If this not a mouse or keyboard event then it's a generic HID event.
28 public bool IsGeneric { get; private set; }
29 public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
30 public bool IsButtonUp { get { return Usages.Count == 0; } }
31 public bool IsRepeat { get { return RepeatCount != 0; } }
32 public uint RepeatCount { get; private set; }
34 public HidDevice Device { get; private set; }
35 public RAWINPUT RawInput { get {return iRawInput;} }
36 private RAWINPUT iRawInput;
38 public ushort UsagePage { get; private set; }
39 public ushort UsageCollection { get; private set; }
40 public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
41 public List<ushort> Usages { get; private set; }
42 //TODO: We need a collection of input report
43 public byte[] InputReport { get; private set; }
45 public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
46 public event HidEventRepeatDelegate OnHidEventRepeat;
48 private System.Timers.Timer Timer { get; set; }
49 public DateTime Time { get; private set; }
50 public DateTime OriginalTime { get; private set; }
52 //Compute repeat delay and speed based on system settings
53 //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
54 private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
55 private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
58 /// Tells whether this event has already been disposed of.
60 public bool IsStray { get { return Timer == null; } }
63 /// We typically dispose of events as soon as we get the corresponding key up signal.
67 Timer.Enabled = false;
69 //Mark this event as a stray
74 /// Initialize an HidEvent from a WM_INPUT message
76 /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
77 public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
85 OriginalTime = DateTime.Now;
86 Timer = new System.Timers.Timer();
87 Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
88 Usages = new List<ushort>();
89 OnHidEventRepeat += aRepeatDelegate;
91 if (aMessage.Msg != Const.WM_INPUT)
93 //Has to be a WM_INPUT message
97 if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
101 else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
103 IsForeground = false;
106 //Declare some pointers
107 IntPtr rawInputBuffer = IntPtr.Zero;
112 iRawInput = new RAWINPUT();
113 if (!Win32.RawInput.GetRawInputData(aMessage.LParam, ref iRawInput, ref rawInputBuffer))
115 Debug.WriteLine("GetRawInputData failed!");
119 //Our device can actually be null.
120 //This is notably happening for some keyboard events
121 if (RawInput.header.hDevice != IntPtr.Zero)
123 //Get various information about this HID device
124 Device = new Hid.HidDevice(RawInput.header.hDevice);
127 if (RawInput.header.dwType == Win32.RawInputDeviceType.RIM_TYPEHID) //Check that our raw input is HID
131 Debug.WriteLine("WM_INPUT source device is HID.");
132 //Get Usage Page and Usage
133 //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
134 UsagePage = Device.Info.hid.usUsagePage;
135 UsageCollection = Device.Info.hid.usUsage;
137 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
138 && RawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
143 //Allocate a buffer for one HID input
144 InputReport = new byte[RawInput.hid.dwSizeHid];
146 Debug.WriteLine("Raw input contains " + RawInput.hid.dwCount + " HID input report(s)");
148 //For each HID input report in our raw input
149 for (int i = 0; i < RawInput.hid.dwCount; i++)
151 //Compute the address from which to copy our HID input
152 int hidInputOffset = 0;
155 byte* source = (byte*)rawInputBuffer;
156 source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (RawInput.hid.dwSizeHid * i);
157 hidInputOffset = (int)source;
160 //Copy HID input into our buffer
161 Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)RawInput.hid.dwSizeHid);
163 //Print HID input report in our debug output
164 //string hidDump = "HID input report: " + InputReportString();
165 //Debug.WriteLine(hidDump);
167 //Do proper parsing of our HID report
168 //First query our usage count
170 Win32.USAGE_AND_PAGE[] usages = null;
171 Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
172 if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
174 //Allocate a large enough buffer
175 usages = new Win32.USAGE_AND_PAGE[usageCount];
176 //...and fetch our usages
177 status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
178 if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
180 Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
183 else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
185 Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
188 Debug.WriteLine("Usage count: " + usageCount.ToString());
190 //Copy usages into this event
193 foreach (USAGE_AND_PAGE up in usages)
195 //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
196 //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
197 //Add this usage to our list
198 Usages.Add(up.Usage);
203 else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
207 Debug.WriteLine("WM_INPUT source device is Mouse.");
208 // do mouse handling...
210 else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
214 Debug.WriteLine("WM_INPUT source device is Keyboard.");
215 // do keyboard handling...
218 Debug.WriteLine("Type: " + Device.Info.keyboard.dwType.ToString());
219 Debug.WriteLine("SubType: " + Device.Info.keyboard.dwSubType.ToString());
220 Debug.WriteLine("Mode: " + Device.Info.keyboard.dwKeyboardMode.ToString());
221 Debug.WriteLine("Number of function keys: " + Device.Info.keyboard.dwNumberOfFunctionKeys.ToString());
222 Debug.WriteLine("Number of indicators: " + Device.Info.keyboard.dwNumberOfIndicators.ToString());
223 Debug.WriteLine("Number of keys total: " + Device.Info.keyboard.dwNumberOfKeysTotal.ToString());
229 //Always executed when leaving our try block
230 Marshal.FreeHGlobal(rawInputBuffer);
236 //TODO: Make this optional
237 //StartRepeatTimer(iRepeatDelay);
243 public void StartRepeatTimer(double aInterval)
249 Timer.Enabled = false;
250 //Initial delay do not use auto reset
251 //After our initial delay however we do setup our timer one more time using auto reset
252 Timer.AutoReset = (RepeatCount != 0);
253 Timer.Interval = aInterval;
254 Timer.Enabled = true;
257 static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
259 if (aHidEvent.IsStray)
261 //Skip events if canceled
265 aHidEvent.RepeatCount++;
266 aHidEvent.Time = DateTime.Now;
267 if (aHidEvent.RepeatCount == 1)
269 //Re-Start our timer only after the initial delay
270 aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
273 //Broadcast our repeat event
274 aHidEvent.OnHidEventRepeat(aHidEvent);
278 /// Print information about this device to our debug output.
280 public void DebugWrite()
284 Debug.WriteLine("==== Invalid HidEvent");
293 if (IsGeneric) Debug.WriteLine("==== Generic");
294 if (IsKeyboard) Debug.WriteLine("==== Keyboard");
295 if (IsMouse) Debug.WriteLine("==== Mouse");
296 Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
297 Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
298 Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
299 Debug.WriteLine("==== InputReport: 0x" + InputReportString());
300 foreach (ushort usage in Usages)
302 Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
309 /// <returns></returns>
310 public string InputReportString()
312 if (InputReport == null)
318 foreach (byte b in InputReport)
320 hidDump += b.ToString("X2");
327 /// Create a list view item describing this HidEvent
329 /// <returns></returns>
330 public ListViewItem ToListViewItem()
332 string usageText = "";
334 foreach (ushort usage in Usages)
342 UsagePage usagePage = (UsagePage)UsagePage;
345 case Hid.UsagePage.Consumer:
346 usageText += ((Hid.Usage.ConsumerControl)usage).ToString();
349 case Hid.UsagePage.WindowsMediaCenterRemoteControl:
350 usageText += ((Hid.Usage.WindowsMediaCenterRemoteControl)usage).ToString();
354 usageText += usage.ToString("X2");
359 ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });