-
Notifications
You must be signed in to change notification settings - Fork 31
/
HidDevice.cs
479 lines (395 loc) · 17.4 KB
/
HidDevice.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
/*
Copyright (c) 2010 Ultraviolet Catastrophe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Runtime.InteropServices;
namespace HidLibrary
{
public class HidDevice : IDisposable
{
public event InsertedEventHandler Inserted;
public event RemovedEventHandler Removed;
public delegate void InsertedEventHandler();
public delegate void RemovedEventHandler();
public enum DeviceMode
{
NonOverlapped = 0,
Overlapped = 1
}
private readonly string _devicePath;
private readonly HidDeviceAttributes _deviceAttributes;
private readonly HidDeviceCapabilities _deviceCapabilities;
private DeviceMode _deviceReadMode = DeviceMode.NonOverlapped;
private DeviceMode _deviceWriteMode = DeviceMode.NonOverlapped;
private readonly HidDeviceEventMonitor _deviceEventMonitor;
private bool _monitorDeviceEvents;
private delegate HidDeviceData ReadDelegate();
private delegate HidReport ReadReportDelegate();
private delegate bool WriteDelegate(byte[] data);
private delegate bool WriteReportDelegate(HidReport report);
public delegate void ReadCallback(HidDeviceData data);
public delegate void ReadReportCallback(HidReport report);
public delegate void WriteCallback(bool success);
internal HidDevice(string devicePath)
{
_deviceEventMonitor = new HidDeviceEventMonitor(this);
_deviceEventMonitor.Inserted += DeviceEventMonitorInserted;
_deviceEventMonitor.Removed += DeviceEventMonitorRemoved;
_devicePath = devicePath;
try
{
var hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
_deviceAttributes = GetDeviceAttributes(hidHandle);
_deviceCapabilities = GetDeviceCapabilities(hidHandle);
CloseDeviceIO(hidHandle);
}
catch (Exception exception)
{
throw new Exception(string.Format("Error querying HID device '{0}'.", devicePath), exception);
}
}
public IntPtr ReadHandle { get; private set; }
public IntPtr WriteHandle { get; private set; }
public bool IsOpen { get; private set; }
public bool IsConnected { get { return HidDevices.IsConnected(_devicePath); } }
public string Description { get { return ToString(); } }
public HidDeviceCapabilities Capabilities { get { return _deviceCapabilities; } }
public HidDeviceAttributes Attributes { get { return _deviceAttributes; } }
public string DevicePath { get { return _devicePath; } }
public bool MonitorDeviceEvents
{
get { return _monitorDeviceEvents; }
set
{
if (value & _monitorDeviceEvents == false) _deviceEventMonitor.Init();
_monitorDeviceEvents = value;
}
}
public override string ToString()
{
return string.Format("VendorID={0}, ProductID={1}, Version={2}, DevicePath={3}",
_deviceAttributes.VendorHexId,
_deviceAttributes.ProductHexId,
_deviceAttributes.Version,
_devicePath);
}
public void OpenDevice()
{
OpenDevice(DeviceMode.NonOverlapped, DeviceMode.NonOverlapped);
}
public void OpenDevice(DeviceMode readMode, DeviceMode writeMode)
{
if (IsOpen) return;
_deviceReadMode = readMode;
_deviceWriteMode = writeMode;
try
{
ReadHandle = OpenDeviceIO(_devicePath, readMode, NativeMethods.GENERIC_READ);
WriteHandle = OpenDeviceIO(_devicePath, writeMode, NativeMethods.GENERIC_WRITE);
}
catch (Exception exception)
{
IsOpen = false;
throw new Exception("Error opening HID device.", exception);
}
IsOpen = ReadHandle.ToInt32() != NativeMethods.INVALID_HANDLE_VALUE & WriteHandle.ToInt32() != NativeMethods.INVALID_HANDLE_VALUE;
}
public void CloseDevice()
{
if (!IsOpen) return;
CloseDeviceIO(ReadHandle);
CloseDeviceIO(WriteHandle);
IsOpen = false;
}
public HidDeviceData Read()
{
return Read(0);
}
public void Read(ReadCallback callback)
{
var readDelegate = new ReadDelegate(Read);
var asyncState = new HidAsyncState(readDelegate, callback);
readDelegate.BeginInvoke(EndRead, asyncState);
}
public HidDeviceData Read(int timeout)
{
if (IsConnected)
{
if (IsOpen == false) OpenDevice();
try
{
return ReadData(timeout);
}
catch
{
return new HidDeviceData(HidDeviceData.ReadStatus.ReadError);
}
}
return new HidDeviceData(HidDeviceData.ReadStatus.NotConnected);
}
public void ReadReport(ReadReportCallback callback)
{
var readReportDelegate = new ReadReportDelegate(ReadReport);
var asyncState = new HidAsyncState(readReportDelegate, callback);
readReportDelegate.BeginInvoke(EndReadReport, asyncState);
}
public HidReport ReadReport(int timeout)
{
return new HidReport(Capabilities.InputReportByteLength, Read(timeout));
}
public HidReport ReadReport()
{
return ReadReport(0);
}
public void Write(byte[] data, WriteCallback callback)
{
var writeDelegate = new WriteDelegate(Write);
var asyncState = new HidAsyncState(writeDelegate, callback);
writeDelegate.BeginInvoke(data, EndWrite, asyncState);
}
public bool Write(byte[] data)
{
return Write(data, 0);
}
public bool Write(byte[] data, int timeout)
{
if (IsConnected)
{
if (IsOpen == false) OpenDevice();
try
{
return WriteData(data, timeout);
}
catch
{
return false;
}
}
return false;
}
public void WriteReport(HidReport report, WriteCallback callback)
{
var writeReportDelegate = new WriteReportDelegate(WriteReport);
var asyncState = new HidAsyncState(writeReportDelegate, callback);
writeReportDelegate.BeginInvoke(report, EndWriteReport, asyncState);
}
public bool WriteReport(HidReport report)
{
return WriteReport(report, 0);
}
public bool WriteReport(HidReport report, int timeout)
{
return Write(report.GetBytes(), timeout);
}
public HidReport CreateReport()
{
return new HidReport(Capabilities.OutputReportByteLength);
}
private static void EndRead(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (ReadDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (ReadCallback)hidAsyncState.CallbackDelegate;
var data = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(data);
}
private static void EndReadReport(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (ReadReportDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (ReadReportCallback)hidAsyncState.CallbackDelegate;
var report = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(report);
}
private static void EndWrite(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (WriteDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (WriteCallback)hidAsyncState.CallbackDelegate;
var result = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(result);
}
private static void EndWriteReport(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (WriteReportDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (WriteCallback)hidAsyncState.CallbackDelegate;
var result = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(result);
}
private byte[] CreateInputBuffer()
{
return CreateBuffer(Capabilities.InputReportByteLength - 1);
}
private byte[] CreateOutputBuffer()
{
return CreateBuffer(Capabilities.OutputReportByteLength - 1);
}
private static byte[] CreateBuffer(int length)
{
byte[] buffer = null;
Array.Resize(ref buffer, length + 1);
return buffer;
}
private static HidDeviceAttributes GetDeviceAttributes(IntPtr hidHandle)
{
var deviceAttributes = default(NativeMethods.HIDD_ATTRIBUTES);
deviceAttributes.Size = Marshal.SizeOf(deviceAttributes);
NativeMethods.HidD_GetAttributes(hidHandle, ref deviceAttributes);
return new HidDeviceAttributes(deviceAttributes);
}
private static HidDeviceCapabilities GetDeviceCapabilities(IntPtr hidHandle)
{
var capabilities = default(NativeMethods.HIDP_CAPS);
var preparsedDataPointer = default(IntPtr);
if (NativeMethods.HidD_GetPreparsedData(hidHandle, ref preparsedDataPointer))
{
NativeMethods.HidP_GetCaps(preparsedDataPointer, ref capabilities);
NativeMethods.HidD_FreePreparsedData(preparsedDataPointer);
}
return new HidDeviceCapabilities(capabilities);
}
private bool WriteData(byte[] data, int timeout)
{
if (_deviceCapabilities.OutputReportByteLength <= 0) return false;
var buffer = CreateOutputBuffer();
var bytesWritten = 0;
Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, _deviceCapabilities.OutputReportByteLength));
if (_deviceWriteMode == DeviceMode.Overlapped)
{
var security = new NativeMethods.SECURITY_ATTRIBUTES();
var overlapped = new NativeMethods.OVERLAPPED();
var overlapTimeout = timeout <= 0 ? NativeMethods.WAIT_INFINITE : timeout;
security.lpSecurityDescriptor = IntPtr.Zero;
security.bInheritHandle = true;
security.nLength = Marshal.SizeOf(security);
overlapped.Offset = 0;
overlapped.OffsetHigh = 0;
overlapped.hEvent = NativeMethods.CreateEvent(ref security, Convert.ToInt32(false), Convert.ToInt32(true), "");
try
{
NativeMethods.WriteFileOverlapped(WriteHandle, ref buffer[0], buffer.Length, ref bytesWritten, ref overlapped);
}
catch { return false; }
var result = NativeMethods.WaitForSingleObject(overlapped.hEvent, overlapTimeout);
switch (result)
{
case NativeMethods.WAIT_OBJECT_0:
return true;
case NativeMethods.WAIT_TIMEOUT:
return false;
case NativeMethods.WAIT_FAILED:
return false;
default:
return false;
}
}
try
{
return NativeMethods.WriteFile(WriteHandle, ref buffer[0], buffer.Length, ref bytesWritten, 0);
}
catch { return false; }
}
private HidDeviceData ReadData(int timeout)
{
var buffer = new byte[] { };
var status = HidDeviceData.ReadStatus.NoDataRead;
if (_deviceCapabilities.InputReportByteLength > 0)
{
var bytesRead = 0;
buffer = CreateInputBuffer();
if (_deviceReadMode == DeviceMode.Overlapped)
{
var security = new NativeMethods.SECURITY_ATTRIBUTES();
var overlapped = new NativeMethods.OVERLAPPED();
var overlapTimeout = timeout <= 0 ? NativeMethods.WAIT_INFINITE : timeout;
security.lpSecurityDescriptor = IntPtr.Zero;
security.bInheritHandle = true;
security.nLength = Marshal.SizeOf(security);
overlapped.Offset = 0;
overlapped.OffsetHigh = 0;
overlapped.hEvent = NativeMethods.CreateEvent(ref security, Convert.ToInt32(false), Convert.ToInt32(true), string.Empty);
try
{
NativeMethods.ReadFileOverlapped(ReadHandle, ref buffer[0], buffer.Length, ref bytesRead, ref overlapped);
var result = NativeMethods.WaitForSingleObject(overlapped.hEvent, overlapTimeout);
switch (result)
{
case NativeMethods.WAIT_OBJECT_0: status = HidDeviceData.ReadStatus.Success; break;
case NativeMethods.WAIT_TIMEOUT:
status = HidDeviceData.ReadStatus.WaitTimedOut;
buffer = new byte[] {};
break;
case NativeMethods.WAIT_FAILED:
status = HidDeviceData.ReadStatus.WaitFail;
buffer = new byte[] { };
break;
default:
status = HidDeviceData.ReadStatus.NoDataRead;
buffer = new byte[] { };
break;
}
}
catch { status = HidDeviceData.ReadStatus.ReadError; }
}
else
{
try
{
NativeMethods.ReadFile(ReadHandle, ref buffer[0], buffer.Length, ref bytesRead, IntPtr.Zero);
status = HidDeviceData.ReadStatus.Success;
}
catch { status = HidDeviceData.ReadStatus.ReadError; }
}
}
return new HidDeviceData(buffer, status);
}
private static IntPtr OpenDeviceIO(string devicePath, uint deviceAccess)
{
return OpenDeviceIO(devicePath, DeviceMode.NonOverlapped, deviceAccess);
}
private static IntPtr OpenDeviceIO(string devicePath, DeviceMode deviceMode, uint deviceAccess)
{
var security = new NativeMethods.SECURITY_ATTRIBUTES();
var flags = 0;
if (deviceMode == DeviceMode.Overlapped) flags = NativeMethods.FILE_FLAG_OVERLAPPED;
security.lpSecurityDescriptor = IntPtr.Zero;
security.bInheritHandle = true;
security.nLength = Marshal.SizeOf(security);
return NativeMethods.CreateFile(devicePath, deviceAccess, NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, ref security, NativeMethods.OPEN_EXISTING, flags, 0);
}
private static void CloseDeviceIO(IntPtr handle)
{
NativeMethods.CloseHandle(handle);
}
private void DeviceEventMonitorInserted()
{
if (IsOpen) OpenDevice();
if (Inserted != null) Inserted();
}
private void DeviceEventMonitorRemoved()
{
if (IsOpen) CloseDevice();
if (Removed != null) Removed();
}
public void Dispose()
{
if (MonitorDeviceEvents) MonitorDeviceEvents = false;
if (IsOpen) CloseDevice();
}
}
}