Moving pre-parsed data to our device class.
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.
17 public class HidEvent: IDisposable
19 public bool IsValid { get; private set; }
20 public bool IsForeground { get; private set; }
21 public bool IsBackground { get{return !IsForeground;} }
22 public bool IsMouse { get; private set; }
23 public bool IsKeyboard { get; private set; }
24 public bool IsGeneric { get; private set; }
25 public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
26 public bool IsButtonUp { get { return Usages.Count == 0; } }
27 public bool IsRepeat { get { return RepeatCount != 0; } }
28 public uint RepeatCount { get; private set; }
30 public HidDevice Device { get; private set; }
32 public ushort UsagePage { get; private set; }
33 public ushort UsageCollection { get; private set; }
34 public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
35 public List<ushort> Usages { get; private set; }
36 public byte[] InputReport { get; private set; }
38 public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
39 public event HidEventRepeatDelegate OnHidEventRepeat;
41 private System.Timers.Timer Timer { get; set; }
42 public DateTime Time { get; private set; }
43 public DateTime OriginalTime { get; private set; }
45 //Compute repeat delay and speed based on system settings
46 //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
47 private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
48 private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
51 /// Tells whether this event has already been disposed of.
53 public bool IsStray { get { return Timer == null; } }
56 /// We typically dispose of events as soon as we get the corresponding key up signal.
60 Timer.Enabled = false;
62 //Mark this event as a stray
67 /// Initialize an HidEvent from a WM_INPUT message
69 /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
70 public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
79 OriginalTime = DateTime.Now;
80 Timer = new System.Timers.Timer();
81 Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
82 Usages = new List<ushort>();
83 OnHidEventRepeat += aRepeatDelegate;
85 if (aMessage.Msg != Const.WM_INPUT)
87 //Has to be a WM_INPUT message
91 if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
95 else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
100 //Declare some pointers
101 IntPtr rawInputBuffer = IntPtr.Zero;
106 RAWINPUT rawInput = new RAWINPUT();
107 if (!Win32.Utils.RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer))
112 //TODO: move this into our device class
114 RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
115 if (!Win32.Utils.RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
120 //Get various information about this HID device
121 Device = new Hid.HidDevice(rawInput.header.hDevice);
123 if (rawInput.header.dwType == Const.RIM_TYPEHID) //Check that our raw input is HID
127 Debug.WriteLine("WM_INPUT source device is HID.");
128 //Get Usage Page and Usage
129 //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
130 UsagePage = deviceInfo.hid.usUsagePage;
131 UsageCollection = deviceInfo.hid.usUsage;
133 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
134 && rawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
139 //Allocate a buffer for one HID input
140 InputReport = new byte[rawInput.hid.dwSizeHid];
142 Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
144 //For each HID input report in our raw input
145 for (int i = 0; i < rawInput.hid.dwCount; i++)
147 //Compute the address from which to copy our HID input
148 int hidInputOffset = 0;
151 byte* source = (byte*)rawInputBuffer;
152 source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
153 hidInputOffset = (int)source;
156 //Copy HID input into our buffer
157 Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)rawInput.hid.dwSizeHid);
159 //Print HID input report in our debug output
160 //string hidDump = "HID input report: " + InputReportString();
161 //Debug.WriteLine(hidDump);
163 //Do proper parsing of our HID report
164 //First query our usage count
166 Win32.USAGE_AND_PAGE[] usages = null;
167 Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
168 if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
170 //Allocate a large enough buffer
171 usages = new Win32.USAGE_AND_PAGE[usageCount];
172 //...and fetch our usages
173 status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
174 if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
176 Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
179 else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
181 Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
184 Debug.WriteLine("Usage count: " + usageCount.ToString());
186 //Copy usages into this event
189 foreach (USAGE_AND_PAGE up in usages)
191 //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
192 //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
193 //Add this usage to our list
194 Usages.Add(up.Usage);
199 else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
203 Debug.WriteLine("WM_INPUT source device is Mouse.");
204 // do mouse handling...
206 else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
210 Debug.WriteLine("WM_INPUT source device is Keyboard.");
211 // do keyboard handling...
212 Debug.WriteLine("Type: " + deviceInfo.keyboard.dwType.ToString());
213 Debug.WriteLine("SubType: " + deviceInfo.keyboard.dwSubType.ToString());
214 Debug.WriteLine("Mode: " + deviceInfo.keyboard.dwKeyboardMode.ToString());
215 Debug.WriteLine("Number of function keys: " + deviceInfo.keyboard.dwNumberOfFunctionKeys.ToString());
216 Debug.WriteLine("Number of indicators: " + deviceInfo.keyboard.dwNumberOfIndicators.ToString());
217 Debug.WriteLine("Number of keys total: " + deviceInfo.keyboard.dwNumberOfKeysTotal.ToString());
222 //Always executed when leaving our try block
223 Marshal.FreeHGlobal(rawInputBuffer);
229 //TODO: Make this optional
230 StartRepeatTimer(iRepeatDelay);
236 public void StartRepeatTimer(double aInterval)
242 Timer.Enabled = false;
243 //Initial delay do not use auto reset
244 //After our initial delay however we do setup our timer one more time using auto reset
245 Timer.AutoReset = (RepeatCount!=0);
246 Timer.Interval = aInterval;
247 Timer.Enabled = true;
250 static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
252 if (aHidEvent.IsStray)
254 //Skip events if canceled
258 aHidEvent.RepeatCount++;
259 aHidEvent.Time = DateTime.Now;
260 if (aHidEvent.RepeatCount==1)
262 //Re-Start our timer only after the initial delay
263 aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
266 //Broadcast our repeat event
267 aHidEvent.OnHidEventRepeat(aHidEvent);
271 /// Print information about this device to our debug output.
273 public void DebugWrite()
277 Debug.WriteLine("==== Invalid HidEvent");
281 if (IsGeneric) Debug.WriteLine("==== Generic");
282 if (IsKeyboard) Debug.WriteLine("==== Keyboard");
283 if (IsMouse) Debug.WriteLine("==== Mouse");
284 Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
285 Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
286 Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
287 Debug.WriteLine("==== InputReport: 0x" + InputReportString());
288 foreach (ushort usage in Usages)
290 Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
297 /// <returns></returns>
298 public string InputReportString()
301 foreach (byte b in InputReport)
303 hidDump += b.ToString("X2");
310 /// Create a list view item describing this HidEvent
312 /// <returns></returns>
313 public ListViewItem ToListViewItem()
315 string usageText = "";
317 foreach (ushort usage in Usages)
325 UsagePage usagePage = (UsagePage)UsagePage;
328 case Hid.UsagePage.Consumer:
329 usageText += ((Hid.UsageTables.ConsumerControl)usage).ToString();
332 case Hid.UsagePage.WindowsMediaCenterRemoteControl:
333 usageText += ((Hid.UsageTables.WindowsMediaCenterRemoteControl)usage).ToString();
337 usageText += usage.ToString("X2");
342 ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });