Fixing repeat delay and speed computation.
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 == 1 && Usages[0] == 0; } }
27 public bool IsRepeat { get; private set; }
29 public HidDevice Device { get; private set; }
31 public ushort UsagePage { get; private set; }
32 public ushort UsageCollection { get; private set; }
33 public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
34 public List<ushort> Usages { get; private set; }
35 public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
36 public event HidEventRepeatDelegate OnHidEventRepeat;
38 private System.Timers.Timer Timer { get; set; }
40 //Compute repeat delay and speed based on system settings
41 //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
42 private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
43 private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
46 /// Tells whether this event has already been disposed of.
48 public bool IsStray { get { return Timer == null; } }
51 /// We typically dispose of events as soon as we get the corresponding key up signal.
55 Timer.Enabled = false;
57 //Mark this event as a stray
62 /// Initialize an HidEvent from a WM_INPUT message
64 /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
65 public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
72 Timer = new System.Timers.Timer();
73 Usages = new List<ushort>();
74 OnHidEventRepeat += aRepeatDelegate;
76 if (aMessage.Msg != Const.WM_INPUT)
78 //Has to be a WM_INPUT message
82 if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
86 else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
91 //Declare some pointers
92 IntPtr rawInputBuffer = IntPtr.Zero;
93 //My understanding is that this is basically our HID descriptor
94 IntPtr preParsedData = IntPtr.Zero;
99 RAWINPUT rawInput = new RAWINPUT();
100 if (!RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer))
106 RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
107 if (!RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
112 //Get various information about this HID device
113 Device = new Hid.HidDevice(rawInput.header.hDevice);
115 if (rawInput.header.dwType == Const.RIM_TYPEHID) //Check that our raw input is HID
119 Debug.WriteLine("WM_INPUT source device is HID.");
120 //Get Usage Page and Usage
121 //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
122 UsagePage = deviceInfo.hid.usUsagePage;
123 UsageCollection = deviceInfo.hid.usUsage;
125 preParsedData = RawInput.GetPreParsedData(rawInput.header.hDevice);
127 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
128 && rawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
133 //Allocate a buffer for one HID input
134 byte[] hidInputReport = new byte[rawInput.hid.dwSizeHid];
136 Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
138 //For each HID input report in our raw input
139 for (int i = 0; i < rawInput.hid.dwCount; i++)
141 //Compute the address from which to copy our HID input
142 int hidInputOffset = 0;
145 byte* source = (byte*)rawInputBuffer;
146 source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
147 hidInputOffset = (int)source;
150 //Copy HID input into our buffer
151 Marshal.Copy(new IntPtr(hidInputOffset), hidInputReport, 0, (int)rawInput.hid.dwSizeHid);
153 //Print HID input report in our debug output
154 string hidDump = "HID input report: ";
155 foreach (byte b in hidInputReport)
157 hidDump += b.ToString("X2");
159 Debug.WriteLine(hidDump);
162 uint usageCount = 1; //Assuming a single usage per input report. Is that correct?
163 Win32.USAGE_AND_PAGE[] usages = new Win32.USAGE_AND_PAGE[usageCount];
164 Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, hidInputReport, (uint)hidInputReport.Length);
165 if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
167 Debug.WriteLine("Could not parse HID data!");
171 //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
172 //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
173 //Add this usage to our list
174 Usages.Add(usages[0].Usage);
179 else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
183 Debug.WriteLine("WM_INPUT source device is Mouse.");
184 // do mouse handling...
186 else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
190 Debug.WriteLine("WM_INPUT source device is Keyboard.");
191 // do keyboard handling...
192 Debug.WriteLine("Type: " + deviceInfo.keyboard.dwType.ToString());
193 Debug.WriteLine("SubType: " + deviceInfo.keyboard.dwSubType.ToString());
194 Debug.WriteLine("Mode: " + deviceInfo.keyboard.dwKeyboardMode.ToString());
195 Debug.WriteLine("Number of function keys: " + deviceInfo.keyboard.dwNumberOfFunctionKeys.ToString());
196 Debug.WriteLine("Number of indicators: " + deviceInfo.keyboard.dwNumberOfIndicators.ToString());
197 Debug.WriteLine("Number of keys total: " + deviceInfo.keyboard.dwNumberOfKeysTotal.ToString());
202 //Always executed when leaving our try block
203 Marshal.FreeHGlobal(rawInputBuffer);
204 Marshal.FreeHGlobal(preParsedData);
209 StartRepeatTimer(iRepeatDelay);
216 /// Print information about this device to our debug output.
218 public void DebugWrite()
222 Debug.WriteLine("==== Invalid HidEvent");
226 if (IsGeneric) Debug.WriteLine("==== Generic");
227 if (IsKeyboard) Debug.WriteLine("==== Keyboard");
228 if (IsMouse) Debug.WriteLine("==== Mouse");
229 Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
230 Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
231 Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
232 foreach (ushort usage in Usages)
234 Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
238 public void StartRepeatTimer(double aInterval)
244 Timer.Enabled = false;
245 Timer.AutoReset = false;
246 Timer.Interval = aInterval;
247 Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
248 Timer.Enabled = true;
251 private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
253 if (aHidEvent.IsStray)
255 //Skip events if canceled
258 aHidEvent.IsRepeat = true;
259 StartRepeatTimer(iRepeatSpeed);
260 OnHidEventRepeat(aHidEvent);
263 public ListViewItem ToListViewItem()
265 //TODO: What to do with multiple usage
267 UsagePage usagePage = (UsagePage)UsagePage;
270 case Hid.UsagePage.Consumer:
271 usage = ((Hid.UsageTables.ConsumerControl)Usages[0]).ToString();
274 case Hid.UsagePage.WindowsMediaCenterRemoteControl:
275 usage = ((Hid.UsageTables.WindowsMediaCenterRemoteControl)Usages[0]).ToString();
280 ListViewItem item = new ListViewItem(new[] { usage, UsagePage.ToString("X2"), UsageCollection.ToString("X2"), IsRepeat.ToString() });