sl@27
|
1 |
using System;
|
sl@27
|
2 |
using System.Windows.Forms;
|
sl@27
|
3 |
using System.Runtime.InteropServices;
|
sl@27
|
4 |
using System.Diagnostics;
|
sl@27
|
5 |
using System.Text;
|
sl@27
|
6 |
using Microsoft.Win32.SafeHandles;
|
sl@27
|
7 |
using Win32;
|
sl@27
|
8 |
using System.Collections.Generic;
|
sl@41
|
9 |
using System.Timers;
|
sl@27
|
10 |
|
sl@27
|
11 |
|
sl@27
|
12 |
namespace Hid
|
sl@27
|
13 |
{
|
sl@27
|
14 |
/// <summary>
|
sl@27
|
15 |
/// Represent a HID event.
|
sl@27
|
16 |
/// </summary>
|
sl@41
|
17 |
public class HidEvent: IDisposable
|
sl@27
|
18 |
{
|
sl@27
|
19 |
public bool IsValid { get; private set; }
|
sl@42
|
20 |
public bool IsForeground { get; private set; }
|
sl@27
|
21 |
public bool IsBackground { get{return !IsForeground;} }
|
sl@27
|
22 |
public bool IsMouse { get; private set; }
|
sl@27
|
23 |
public bool IsKeyboard { get; private set; }
|
sl@27
|
24 |
public bool IsGeneric { get; private set; }
|
sl@29
|
25 |
public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
|
sl@29
|
26 |
public bool IsButtonUp { get { return Usages.Count == 1 && Usages[0] == 0; } }
|
sl@44
|
27 |
public bool IsRepeat { get { return RepeatCount != 0; } }
|
sl@44
|
28 |
public uint RepeatCount { get; private set; }
|
sl@27
|
29 |
|
sl@27
|
30 |
public HidDevice Device { get; private set; }
|
sl@27
|
31 |
|
sl@27
|
32 |
public ushort UsagePage { get; private set; }
|
sl@27
|
33 |
public ushort UsageCollection { get; private set; }
|
sl@31
|
34 |
public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
|
sl@41
|
35 |
public List<ushort> Usages { get; private set; }
|
sl@41
|
36 |
public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
|
sl@41
|
37 |
public event HidEventRepeatDelegate OnHidEventRepeat;
|
sl@27
|
38 |
|
sl@41
|
39 |
private System.Timers.Timer Timer { get; set; }
|
sl@44
|
40 |
public DateTime Time { get; private set; }
|
sl@44
|
41 |
public DateTime OriginalTime { get; private set; }
|
sl@41
|
42 |
|
sl@43
|
43 |
//Compute repeat delay and speed based on system settings
|
sl@43
|
44 |
//Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
|
sl@43
|
45 |
private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
|
sl@43
|
46 |
private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
|
sl@43
|
47 |
|
sl@42
|
48 |
/// <summary>
|
sl@42
|
49 |
/// Tells whether this event has already been disposed of.
|
sl@42
|
50 |
/// </summary>
|
sl@42
|
51 |
public bool IsStray { get { return Timer == null; } }
|
sl@42
|
52 |
|
sl@43
|
53 |
/// <summary>
|
sl@43
|
54 |
/// We typically dispose of events as soon as we get the corresponding key up signal.
|
sl@43
|
55 |
/// </summary>
|
sl@41
|
56 |
public void Dispose()
|
sl@41
|
57 |
{
|
sl@41
|
58 |
Timer.Enabled = false;
|
sl@41
|
59 |
Timer.Dispose();
|
sl@43
|
60 |
//Mark this event as a stray
|
sl@41
|
61 |
Timer = null;
|
sl@41
|
62 |
}
|
sl@27
|
63 |
|
sl@27
|
64 |
/// <summary>
|
sl@27
|
65 |
/// Initialize an HidEvent from a WM_INPUT message
|
sl@27
|
66 |
/// </summary>
|
sl@27
|
67 |
/// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
|
sl@41
|
68 |
public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
|
sl@27
|
69 |
{
|
sl@44
|
70 |
RepeatCount = 0;
|
sl@27
|
71 |
IsValid = false;
|
sl@27
|
72 |
IsKeyboard = false;
|
sl@27
|
73 |
IsGeneric = false;
|
sl@44
|
74 |
|
sl@27
|
75 |
|
sl@44
|
76 |
Time = DateTime.Now;
|
sl@44
|
77 |
OriginalTime = DateTime.Now;
|
sl@41
|
78 |
Timer = new System.Timers.Timer();
|
sl@44
|
79 |
Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
|
sl@27
|
80 |
Usages = new List<ushort>();
|
sl@41
|
81 |
OnHidEventRepeat += aRepeatDelegate;
|
sl@27
|
82 |
|
sl@27
|
83 |
if (aMessage.Msg != Const.WM_INPUT)
|
sl@27
|
84 |
{
|
sl@27
|
85 |
//Has to be a WM_INPUT message
|
sl@27
|
86 |
return;
|
sl@27
|
87 |
}
|
sl@27
|
88 |
|
sl@27
|
89 |
if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
|
sl@27
|
90 |
{
|
sl@27
|
91 |
IsForeground = true;
|
sl@27
|
92 |
}
|
sl@27
|
93 |
else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
|
sl@27
|
94 |
{
|
sl@27
|
95 |
IsForeground = false;
|
sl@27
|
96 |
}
|
sl@27
|
97 |
|
sl@27
|
98 |
//Declare some pointers
|
sl@27
|
99 |
IntPtr rawInputBuffer = IntPtr.Zero;
|
sl@27
|
100 |
//My understanding is that this is basically our HID descriptor
|
sl@27
|
101 |
IntPtr preParsedData = IntPtr.Zero;
|
sl@27
|
102 |
|
sl@27
|
103 |
try
|
sl@27
|
104 |
{
|
sl@27
|
105 |
//Fetch raw input
|
sl@27
|
106 |
RAWINPUT rawInput = new RAWINPUT();
|
sl@47
|
107 |
if (!Win32.Utils.RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer))
|
sl@27
|
108 |
{
|
sl@27
|
109 |
return;
|
sl@27
|
110 |
}
|
sl@27
|
111 |
|
sl@27
|
112 |
//Fetch device info
|
sl@27
|
113 |
RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
|
sl@47
|
114 |
if (!Win32.Utils.RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
|
sl@27
|
115 |
{
|
sl@27
|
116 |
return;
|
sl@27
|
117 |
}
|
sl@27
|
118 |
|
sl@27
|
119 |
//Get various information about this HID device
|
sl@27
|
120 |
Device = new Hid.HidDevice(rawInput.header.hDevice);
|
sl@27
|
121 |
|
sl@27
|
122 |
if (rawInput.header.dwType == Const.RIM_TYPEHID) //Check that our raw input is HID
|
sl@27
|
123 |
{
|
sl@27
|
124 |
IsGeneric = true;
|
sl@27
|
125 |
|
sl@27
|
126 |
Debug.WriteLine("WM_INPUT source device is HID.");
|
sl@27
|
127 |
//Get Usage Page and Usage
|
sl@27
|
128 |
//Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
|
sl@27
|
129 |
UsagePage = deviceInfo.hid.usUsagePage;
|
sl@27
|
130 |
UsageCollection = deviceInfo.hid.usUsage;
|
sl@27
|
131 |
|
sl@47
|
132 |
preParsedData = Win32.Utils.RawInput.GetPreParsedData(rawInput.header.hDevice);
|
sl@27
|
133 |
|
sl@27
|
134 |
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
|
135 |
&& rawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
|
sl@27
|
136 |
{
|
sl@27
|
137 |
return;
|
sl@27
|
138 |
}
|
sl@27
|
139 |
|
sl@27
|
140 |
//Allocate a buffer for one HID input
|
sl@27
|
141 |
byte[] hidInputReport = new byte[rawInput.hid.dwSizeHid];
|
sl@27
|
142 |
|
sl@27
|
143 |
Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
|
sl@27
|
144 |
|
sl@27
|
145 |
//For each HID input report in our raw input
|
sl@27
|
146 |
for (int i = 0; i < rawInput.hid.dwCount; i++)
|
sl@27
|
147 |
{
|
sl@27
|
148 |
//Compute the address from which to copy our HID input
|
sl@27
|
149 |
int hidInputOffset = 0;
|
sl@27
|
150 |
unsafe
|
sl@27
|
151 |
{
|
sl@27
|
152 |
byte* source = (byte*)rawInputBuffer;
|
sl@27
|
153 |
source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
|
sl@27
|
154 |
hidInputOffset = (int)source;
|
sl@27
|
155 |
}
|
sl@27
|
156 |
|
sl@27
|
157 |
//Copy HID input into our buffer
|
sl@27
|
158 |
Marshal.Copy(new IntPtr(hidInputOffset), hidInputReport, 0, (int)rawInput.hid.dwSizeHid);
|
sl@27
|
159 |
|
sl@27
|
160 |
//Print HID input report in our debug output
|
sl@27
|
161 |
string hidDump = "HID input report: ";
|
sl@27
|
162 |
foreach (byte b in hidInputReport)
|
sl@27
|
163 |
{
|
sl@27
|
164 |
hidDump += b.ToString("X2");
|
sl@27
|
165 |
}
|
sl@27
|
166 |
Debug.WriteLine(hidDump);
|
sl@27
|
167 |
|
sl@27
|
168 |
//Proper parsing now
|
sl@27
|
169 |
uint usageCount = 1; //Assuming a single usage per input report. Is that correct?
|
sl@27
|
170 |
Win32.USAGE_AND_PAGE[] usages = new Win32.USAGE_AND_PAGE[usageCount];
|
sl@27
|
171 |
Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, hidInputReport, (uint)hidInputReport.Length);
|
sl@27
|
172 |
if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
|
sl@27
|
173 |
{
|
sl@27
|
174 |
Debug.WriteLine("Could not parse HID data!");
|
sl@27
|
175 |
}
|
sl@27
|
176 |
else
|
sl@27
|
177 |
{
|
sl@27
|
178 |
//Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
|
sl@27
|
179 |
//Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
|
sl@27
|
180 |
//Add this usage to our list
|
sl@27
|
181 |
Usages.Add(usages[0].Usage);
|
sl@27
|
182 |
}
|
sl@27
|
183 |
}
|
sl@27
|
184 |
|
sl@27
|
185 |
}
|
sl@27
|
186 |
else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
|
sl@27
|
187 |
{
|
sl@27
|
188 |
IsMouse = true;
|
sl@27
|
189 |
|
sl@27
|
190 |
Debug.WriteLine("WM_INPUT source device is Mouse.");
|
sl@27
|
191 |
// do mouse handling...
|
sl@27
|
192 |
}
|
sl@27
|
193 |
else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
|
sl@27
|
194 |
{
|
sl@27
|
195 |
IsKeyboard = true;
|
sl@27
|
196 |
|
sl@27
|
197 |
Debug.WriteLine("WM_INPUT source device is Keyboard.");
|
sl@27
|
198 |
// do keyboard handling...
|
sl@27
|
199 |
Debug.WriteLine("Type: " + deviceInfo.keyboard.dwType.ToString());
|
sl@27
|
200 |
Debug.WriteLine("SubType: " + deviceInfo.keyboard.dwSubType.ToString());
|
sl@27
|
201 |
Debug.WriteLine("Mode: " + deviceInfo.keyboard.dwKeyboardMode.ToString());
|
sl@27
|
202 |
Debug.WriteLine("Number of function keys: " + deviceInfo.keyboard.dwNumberOfFunctionKeys.ToString());
|
sl@27
|
203 |
Debug.WriteLine("Number of indicators: " + deviceInfo.keyboard.dwNumberOfIndicators.ToString());
|
sl@27
|
204 |
Debug.WriteLine("Number of keys total: " + deviceInfo.keyboard.dwNumberOfKeysTotal.ToString());
|
sl@27
|
205 |
}
|
sl@27
|
206 |
}
|
sl@27
|
207 |
finally
|
sl@27
|
208 |
{
|
sl@27
|
209 |
//Always executed when leaving our try block
|
sl@27
|
210 |
Marshal.FreeHGlobal(rawInputBuffer);
|
sl@27
|
211 |
Marshal.FreeHGlobal(preParsedData);
|
sl@27
|
212 |
}
|
sl@27
|
213 |
|
sl@47
|
214 |
//
|
sl@47
|
215 |
if (IsButtonDown)
|
sl@41
|
216 |
{
|
sl@43
|
217 |
StartRepeatTimer(iRepeatDelay);
|
sl@41
|
218 |
}
|
sl@41
|
219 |
|
sl@27
|
220 |
IsValid = true;
|
sl@27
|
221 |
}
|
sl@27
|
222 |
|
sl@27
|
223 |
/// <summary>
|
sl@27
|
224 |
/// Print information about this device to our debug output.
|
sl@27
|
225 |
/// </summary>
|
sl@27
|
226 |
public void DebugWrite()
|
sl@27
|
227 |
{
|
sl@27
|
228 |
if (!IsValid)
|
sl@27
|
229 |
{
|
sl@27
|
230 |
Debug.WriteLine("==== Invalid HidEvent");
|
sl@27
|
231 |
return;
|
sl@27
|
232 |
}
|
sl@27
|
233 |
Device.DebugWrite();
|
sl@27
|
234 |
if (IsGeneric) Debug.WriteLine("==== Generic");
|
sl@27
|
235 |
if (IsKeyboard) Debug.WriteLine("==== Keyboard");
|
sl@27
|
236 |
if (IsMouse) Debug.WriteLine("==== Mouse");
|
sl@27
|
237 |
Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
|
sl@27
|
238 |
Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
|
sl@27
|
239 |
Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
|
sl@27
|
240 |
foreach (ushort usage in Usages)
|
sl@27
|
241 |
{
|
sl@27
|
242 |
Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
|
sl@27
|
243 |
}
|
sl@27
|
244 |
}
|
sl@27
|
245 |
|
sl@41
|
246 |
public void StartRepeatTimer(double aInterval)
|
sl@41
|
247 |
{
|
sl@41
|
248 |
if (Timer == null)
|
sl@41
|
249 |
{
|
sl@41
|
250 |
return;
|
sl@41
|
251 |
}
|
sl@41
|
252 |
Timer.Enabled = false;
|
sl@44
|
253 |
//Initial delay do not use auto reset
|
sl@44
|
254 |
//After our initial delay however we do setup our timer one more time using auto reset
|
sl@44
|
255 |
Timer.AutoReset = (RepeatCount!=0);
|
sl@44
|
256 |
Timer.Interval = aInterval;
|
sl@44
|
257 |
Timer.Enabled = true;
|
sl@41
|
258 |
}
|
sl@27
|
259 |
|
sl@44
|
260 |
static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
|
sl@41
|
261 |
{
|
sl@42
|
262 |
if (aHidEvent.IsStray)
|
sl@42
|
263 |
{
|
sl@42
|
264 |
//Skip events if canceled
|
sl@42
|
265 |
return;
|
sl@42
|
266 |
}
|
sl@44
|
267 |
|
sl@44
|
268 |
aHidEvent.RepeatCount++;
|
sl@44
|
269 |
aHidEvent.Time = DateTime.Now;
|
sl@44
|
270 |
if (aHidEvent.RepeatCount==1)
|
sl@44
|
271 |
{
|
sl@44
|
272 |
//Re-Start our timer only after the initial delay
|
sl@44
|
273 |
aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
|
sl@44
|
274 |
}
|
sl@44
|
275 |
|
sl@44
|
276 |
//Broadcast our repeat event
|
sl@44
|
277 |
aHidEvent.OnHidEventRepeat(aHidEvent);
|
sl@41
|
278 |
}
|
sl@27
|
279 |
|
sl@42
|
280 |
public ListViewItem ToListViewItem()
|
sl@42
|
281 |
{
|
sl@42
|
282 |
//TODO: What to do with multiple usage
|
sl@42
|
283 |
string usage = "";
|
sl@42
|
284 |
UsagePage usagePage = (UsagePage)UsagePage;
|
sl@42
|
285 |
switch (usagePage)
|
sl@42
|
286 |
{
|
sl@42
|
287 |
case Hid.UsagePage.Consumer:
|
sl@42
|
288 |
usage = ((Hid.UsageTables.ConsumerControl)Usages[0]).ToString();
|
sl@42
|
289 |
break;
|
sl@42
|
290 |
|
sl@42
|
291 |
case Hid.UsagePage.WindowsMediaCenterRemoteControl:
|
sl@42
|
292 |
usage = ((Hid.UsageTables.WindowsMediaCenterRemoteControl)Usages[0]).ToString();
|
sl@42
|
293 |
break;
|
sl@42
|
294 |
|
sl@42
|
295 |
}
|
sl@42
|
296 |
|
sl@44
|
297 |
ListViewItem item = new ListViewItem(new[] { usage, UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });
|
sl@42
|
298 |
return item;
|
sl@42
|
299 |
}
|
sl@42
|
300 |
|
sl@27
|
301 |
}
|
sl@27
|
302 |
|
sl@27
|
303 |
} |