mirror of https://github.com/duggerd/KFDtool.git
648 lines
23 KiB
C#
648 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace HidLibrary
|
|
{
|
|
public class HidDevice : IHidDevice, IDisposable
|
|
{
|
|
protected delegate HidDeviceData ReadDelegate();
|
|
|
|
protected delegate HidReport ReadReportDelegate();
|
|
|
|
private delegate bool WriteDelegate(byte[] data);
|
|
|
|
private delegate bool WriteReportDelegate(HidReport report);
|
|
|
|
private readonly string _description;
|
|
|
|
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;
|
|
|
|
public IntPtr ReadHandle
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public IntPtr WriteHandle
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public bool IsOpen
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public bool IsConnected => HidDevices.IsConnected(_devicePath);
|
|
|
|
public string Description => _description;
|
|
|
|
public HidDeviceCapabilities Capabilities => _deviceCapabilities;
|
|
|
|
public HidDeviceAttributes Attributes => _deviceAttributes;
|
|
|
|
public string DevicePath => _devicePath;
|
|
|
|
public bool MonitorDeviceEvents
|
|
{
|
|
get
|
|
{
|
|
return _monitorDeviceEvents;
|
|
}
|
|
set
|
|
{
|
|
if (value & !_monitorDeviceEvents)
|
|
{
|
|
_deviceEventMonitor.Init();
|
|
}
|
|
_monitorDeviceEvents = value;
|
|
}
|
|
}
|
|
|
|
public event InsertedEventHandler Inserted;
|
|
|
|
public event RemovedEventHandler Removed;
|
|
|
|
internal HidDevice(string devicePath, string description = null)
|
|
{
|
|
_deviceEventMonitor = new HidDeviceEventMonitor(this);
|
|
_deviceEventMonitor.Inserted += DeviceEventMonitorInserted;
|
|
_deviceEventMonitor.Removed += DeviceEventMonitorRemoved;
|
|
_devicePath = devicePath;
|
|
_description = description;
|
|
try
|
|
{
|
|
IntPtr intPtr = OpenDeviceIO(_devicePath, 0u);
|
|
_deviceAttributes = GetDeviceAttributes(intPtr);
|
|
_deviceCapabilities = GetDeviceCapabilities(intPtr);
|
|
CloseDeviceIO(intPtr);
|
|
}
|
|
catch (Exception innerException)
|
|
{
|
|
throw new Exception($"Error querying HID device '{devicePath}'.", innerException);
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"VendorID={_deviceAttributes.VendorHexId}, ProductID={_deviceAttributes.ProductHexId}, Version={_deviceAttributes.Version}, DevicePath={_devicePath}";
|
|
}
|
|
|
|
public void OpenDevice()
|
|
{
|
|
OpenDevice(DeviceMode.NonOverlapped, DeviceMode.NonOverlapped);
|
|
}
|
|
|
|
public void OpenDevice(DeviceMode readMode, DeviceMode writeMode)
|
|
{
|
|
if (!IsOpen)
|
|
{
|
|
_deviceReadMode = readMode;
|
|
_deviceWriteMode = writeMode;
|
|
try
|
|
{
|
|
ReadHandle = OpenDeviceIO(_devicePath, readMode, 2147483648u);
|
|
WriteHandle = OpenDeviceIO(_devicePath, writeMode, 1073741824u);
|
|
}
|
|
catch (Exception innerException)
|
|
{
|
|
IsOpen = false;
|
|
throw new Exception("Error opening HID device.", innerException);
|
|
}
|
|
IntPtr intPtr = ReadHandle;
|
|
bool num = intPtr.ToInt32() != -1;
|
|
intPtr = WriteHandle;
|
|
IsOpen = (num & (intPtr.ToInt32() != -1));
|
|
}
|
|
}
|
|
|
|
public void CloseDevice()
|
|
{
|
|
if (IsOpen)
|
|
{
|
|
CloseDeviceIO(ReadHandle);
|
|
CloseDeviceIO(WriteHandle);
|
|
IsOpen = false;
|
|
}
|
|
}
|
|
|
|
public HidDeviceData Read()
|
|
{
|
|
return Read(0);
|
|
}
|
|
|
|
public void Read(ReadCallback callback)
|
|
{
|
|
ReadDelegate readDelegate = Read;
|
|
HidAsyncState @object = new HidAsyncState(readDelegate, callback);
|
|
readDelegate.BeginInvoke(EndRead, @object);
|
|
}
|
|
|
|
public HidDeviceData Read(int timeout)
|
|
{
|
|
if (IsConnected)
|
|
{
|
|
if (!IsOpen)
|
|
{
|
|
OpenDevice();
|
|
}
|
|
try
|
|
{
|
|
return ReadData(timeout);
|
|
}
|
|
catch
|
|
{
|
|
return new HidDeviceData(HidDeviceData.ReadStatus.ReadError);
|
|
}
|
|
}
|
|
return new HidDeviceData(HidDeviceData.ReadStatus.NotConnected);
|
|
}
|
|
|
|
public void ReadReport(ReadReportCallback callback)
|
|
{
|
|
ReadReportDelegate readReportDelegate = ReadReport;
|
|
HidAsyncState @object = new HidAsyncState(readReportDelegate, callback);
|
|
readReportDelegate.BeginInvoke(EndReadReport, @object);
|
|
}
|
|
|
|
public HidReport ReadReport(int timeout)
|
|
{
|
|
return new HidReport(Capabilities.InputReportByteLength, Read(timeout));
|
|
}
|
|
|
|
public HidReport ReadReport()
|
|
{
|
|
return ReadReport(0);
|
|
}
|
|
|
|
public bool ReadFeatureData(out byte[] data, byte reportId = 0)
|
|
{
|
|
if (_deviceCapabilities.FeatureReportByteLength > 0)
|
|
{
|
|
data = new byte[_deviceCapabilities.FeatureReportByteLength];
|
|
byte[] array = CreateFeatureOutputBuffer();
|
|
array[0] = reportId;
|
|
IntPtr intPtr = IntPtr.Zero;
|
|
bool flag = false;
|
|
try
|
|
{
|
|
intPtr = OpenDeviceIO(_devicePath, 0u);
|
|
flag = NativeMethods.HidD_GetFeature(intPtr, array, array.Length);
|
|
if (flag)
|
|
{
|
|
Array.Copy(array, 0, data, 0, Math.Min(data.Length, _deviceCapabilities.FeatureReportByteLength));
|
|
}
|
|
}
|
|
catch (Exception innerException)
|
|
{
|
|
throw new Exception($"Error accessing HID device '{_devicePath}'.", innerException);
|
|
}
|
|
finally
|
|
{
|
|
if (intPtr != IntPtr.Zero)
|
|
{
|
|
CloseDeviceIO(intPtr);
|
|
}
|
|
}
|
|
return flag;
|
|
}
|
|
data = new byte[0];
|
|
return false;
|
|
}
|
|
|
|
public bool ReadProduct(out byte[] data)
|
|
{
|
|
data = new byte[64];
|
|
IntPtr intPtr = IntPtr.Zero;
|
|
bool result = false;
|
|
try
|
|
{
|
|
intPtr = OpenDeviceIO(_devicePath, 0u);
|
|
result = NativeMethods.HidD_GetProductString(intPtr, ref data[0], data.Length);
|
|
}
|
|
catch (Exception innerException)
|
|
{
|
|
throw new Exception($"Error accessing HID device '{_devicePath}'.", innerException);
|
|
}
|
|
finally
|
|
{
|
|
if (intPtr != IntPtr.Zero)
|
|
{
|
|
CloseDeviceIO(intPtr);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public bool ReadManufacturer(out byte[] data)
|
|
{
|
|
data = new byte[64];
|
|
IntPtr intPtr = IntPtr.Zero;
|
|
bool result = false;
|
|
try
|
|
{
|
|
intPtr = OpenDeviceIO(_devicePath, 0u);
|
|
result = NativeMethods.HidD_GetManufacturerString(intPtr, ref data[0], data.Length);
|
|
}
|
|
catch (Exception innerException)
|
|
{
|
|
throw new Exception($"Error accessing HID device '{_devicePath}'.", innerException);
|
|
}
|
|
finally
|
|
{
|
|
if (intPtr != IntPtr.Zero)
|
|
{
|
|
CloseDeviceIO(intPtr);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public bool ReadSerialNumber(out byte[] data)
|
|
{
|
|
data = new byte[64];
|
|
IntPtr intPtr = IntPtr.Zero;
|
|
bool result = false;
|
|
try
|
|
{
|
|
intPtr = OpenDeviceIO(_devicePath, 0u);
|
|
result = NativeMethods.HidD_GetSerialNumberString(intPtr, ref data[0], data.Length);
|
|
}
|
|
catch (Exception innerException)
|
|
{
|
|
throw new Exception($"Error accessing HID device '{_devicePath}'.", innerException);
|
|
}
|
|
finally
|
|
{
|
|
if (intPtr != IntPtr.Zero)
|
|
{
|
|
CloseDeviceIO(intPtr);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void Write(byte[] data, WriteCallback callback)
|
|
{
|
|
WriteDelegate writeDelegate = Write;
|
|
HidAsyncState @object = new HidAsyncState(writeDelegate, callback);
|
|
writeDelegate.BeginInvoke(data, EndWrite, @object);
|
|
}
|
|
|
|
public bool Write(byte[] data)
|
|
{
|
|
return Write(data, 0);
|
|
}
|
|
|
|
public bool Write(byte[] data, int timeout)
|
|
{
|
|
if (IsConnected)
|
|
{
|
|
if (!IsOpen)
|
|
{
|
|
OpenDevice();
|
|
}
|
|
try
|
|
{
|
|
return WriteData(data, timeout);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void WriteReport(HidReport report, WriteCallback callback)
|
|
{
|
|
WriteReportDelegate writeReportDelegate = WriteReport;
|
|
HidAsyncState @object = new HidAsyncState(writeReportDelegate, callback);
|
|
writeReportDelegate.BeginInvoke(report, EndWriteReport, @object);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public bool WriteFeatureData(byte[] data)
|
|
{
|
|
if (_deviceCapabilities.FeatureReportByteLength > 0)
|
|
{
|
|
byte[] array = CreateFeatureOutputBuffer();
|
|
Array.Copy(data, 0, array, 0, Math.Min(data.Length, _deviceCapabilities.FeatureReportByteLength));
|
|
IntPtr intPtr = IntPtr.Zero;
|
|
bool result = false;
|
|
try
|
|
{
|
|
intPtr = OpenDeviceIO(_devicePath, 0u);
|
|
result = NativeMethods.HidD_SetFeature(intPtr, array, array.Length);
|
|
}
|
|
catch (Exception innerException)
|
|
{
|
|
throw new Exception($"Error accessing HID device '{_devicePath}'.", innerException);
|
|
}
|
|
finally
|
|
{
|
|
if (intPtr != IntPtr.Zero)
|
|
{
|
|
CloseDeviceIO(intPtr);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected static void EndRead(IAsyncResult ar)
|
|
{
|
|
HidAsyncState hidAsyncState = (HidAsyncState)ar.AsyncState;
|
|
ReadDelegate readDelegate = (ReadDelegate)hidAsyncState.CallerDelegate;
|
|
ReadCallback readCallback = (ReadCallback)hidAsyncState.CallbackDelegate;
|
|
HidDeviceData data = readDelegate.EndInvoke(ar);
|
|
readCallback?.Invoke(data);
|
|
}
|
|
|
|
protected static void EndReadReport(IAsyncResult ar)
|
|
{
|
|
HidAsyncState hidAsyncState = (HidAsyncState)ar.AsyncState;
|
|
ReadReportDelegate readReportDelegate = (ReadReportDelegate)hidAsyncState.CallerDelegate;
|
|
ReadReportCallback readReportCallback = (ReadReportCallback)hidAsyncState.CallbackDelegate;
|
|
HidReport report = readReportDelegate.EndInvoke(ar);
|
|
readReportCallback?.Invoke(report);
|
|
}
|
|
|
|
private static void EndWrite(IAsyncResult ar)
|
|
{
|
|
HidAsyncState hidAsyncState = (HidAsyncState)ar.AsyncState;
|
|
WriteDelegate writeDelegate = (WriteDelegate)hidAsyncState.CallerDelegate;
|
|
WriteCallback writeCallback = (WriteCallback)hidAsyncState.CallbackDelegate;
|
|
bool success = writeDelegate.EndInvoke(ar);
|
|
writeCallback?.Invoke(success);
|
|
}
|
|
|
|
private static void EndWriteReport(IAsyncResult ar)
|
|
{
|
|
HidAsyncState hidAsyncState = (HidAsyncState)ar.AsyncState;
|
|
WriteReportDelegate writeReportDelegate = (WriteReportDelegate)hidAsyncState.CallerDelegate;
|
|
WriteCallback writeCallback = (WriteCallback)hidAsyncState.CallbackDelegate;
|
|
bool success = writeReportDelegate.EndInvoke(ar);
|
|
writeCallback?.Invoke(success);
|
|
}
|
|
|
|
private byte[] CreateInputBuffer()
|
|
{
|
|
return CreateBuffer(Capabilities.InputReportByteLength - 1);
|
|
}
|
|
|
|
private byte[] CreateOutputBuffer()
|
|
{
|
|
return CreateBuffer(Capabilities.OutputReportByteLength - 1);
|
|
}
|
|
|
|
private byte[] CreateFeatureOutputBuffer()
|
|
{
|
|
return CreateBuffer(Capabilities.FeatureReportByteLength - 1);
|
|
}
|
|
|
|
private static byte[] CreateBuffer(int length)
|
|
{
|
|
byte[] array = null;
|
|
Array.Resize(ref array, length + 1);
|
|
return array;
|
|
}
|
|
|
|
private static HidDeviceAttributes GetDeviceAttributes(IntPtr hidHandle)
|
|
{
|
|
NativeMethods.HIDD_ATTRIBUTES attributes = default(NativeMethods.HIDD_ATTRIBUTES);
|
|
attributes.Size = Marshal.SizeOf((object)attributes);
|
|
NativeMethods.HidD_GetAttributes(hidHandle, ref attributes);
|
|
return new HidDeviceAttributes(attributes);
|
|
}
|
|
|
|
private static HidDeviceCapabilities GetDeviceCapabilities(IntPtr hidHandle)
|
|
{
|
|
NativeMethods.HIDP_CAPS capabilities = default(NativeMethods.HIDP_CAPS);
|
|
IntPtr preparsedData = default(IntPtr);
|
|
if (NativeMethods.HidD_GetPreparsedData(hidHandle, ref preparsedData))
|
|
{
|
|
NativeMethods.HidP_GetCaps(preparsedData, ref capabilities);
|
|
NativeMethods.HidD_FreePreparsedData(preparsedData);
|
|
}
|
|
return new HidDeviceCapabilities(capabilities);
|
|
}
|
|
|
|
private bool WriteData(byte[] data, int timeout)
|
|
{
|
|
if (_deviceCapabilities.OutputReportByteLength > 0)
|
|
{
|
|
byte[] array = CreateOutputBuffer();
|
|
uint lpNumberOfBytesWritten = 0u;
|
|
Array.Copy(data, 0, array, 0, Math.Min(data.Length, _deviceCapabilities.OutputReportByteLength));
|
|
if (_deviceWriteMode != DeviceMode.Overlapped)
|
|
{
|
|
try
|
|
{
|
|
NativeOverlapped lpOverlapped = default(NativeOverlapped);
|
|
return NativeMethods.WriteFile(WriteHandle, array, (uint)array.Length, out lpNumberOfBytesWritten, ref lpOverlapped);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
NativeMethods.SECURITY_ATTRIBUTES securityAttributes = default(NativeMethods.SECURITY_ATTRIBUTES);
|
|
NativeOverlapped lpOverlapped2 = default(NativeOverlapped);
|
|
int dwMilliseconds = (timeout <= 0) ? 65535 : timeout;
|
|
securityAttributes.lpSecurityDescriptor = IntPtr.Zero;
|
|
securityAttributes.bInheritHandle = true;
|
|
securityAttributes.nLength = Marshal.SizeOf((object)securityAttributes);
|
|
lpOverlapped2.OffsetLow = 0;
|
|
lpOverlapped2.OffsetHigh = 0;
|
|
lpOverlapped2.EventHandle = NativeMethods.CreateEvent(ref securityAttributes, Convert.ToInt32(false), Convert.ToInt32(true), "");
|
|
try
|
|
{
|
|
NativeMethods.WriteFile(WriteHandle, array, (uint)array.Length, out lpNumberOfBytesWritten, ref lpOverlapped2);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
switch (NativeMethods.WaitForSingleObject(lpOverlapped2.EventHandle, dwMilliseconds))
|
|
{
|
|
case 0u:
|
|
return true;
|
|
case 258u:
|
|
return false;
|
|
case uint.MaxValue:
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected HidDeviceData ReadData(int timeout)
|
|
{
|
|
byte[] array = new byte[0];
|
|
HidDeviceData.ReadStatus status = HidDeviceData.ReadStatus.NoDataRead;
|
|
if (_deviceCapabilities.InputReportByteLength > 0)
|
|
{
|
|
uint lpNumberOfBytesRead = 0u;
|
|
array = CreateInputBuffer();
|
|
if (_deviceReadMode != DeviceMode.Overlapped)
|
|
{
|
|
try
|
|
{
|
|
NativeOverlapped lpOverlapped = default(NativeOverlapped);
|
|
NativeMethods.ReadFile(ReadHandle, array, (uint)array.Length, out lpNumberOfBytesRead, ref lpOverlapped);
|
|
status = HidDeviceData.ReadStatus.Success;
|
|
}
|
|
catch
|
|
{
|
|
status = HidDeviceData.ReadStatus.ReadError;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NativeMethods.SECURITY_ATTRIBUTES securityAttributes = default(NativeMethods.SECURITY_ATTRIBUTES);
|
|
NativeOverlapped lpOverlapped2 = default(NativeOverlapped);
|
|
int dwMilliseconds = (timeout <= 0) ? 65535 : timeout;
|
|
securityAttributes.lpSecurityDescriptor = IntPtr.Zero;
|
|
securityAttributes.bInheritHandle = true;
|
|
securityAttributes.nLength = Marshal.SizeOf((object)securityAttributes);
|
|
lpOverlapped2.OffsetLow = 0;
|
|
lpOverlapped2.OffsetHigh = 0;
|
|
lpOverlapped2.EventHandle = NativeMethods.CreateEvent(ref securityAttributes, Convert.ToInt32(false), Convert.ToInt32(true), string.Empty);
|
|
try
|
|
{
|
|
NativeMethods.ReadFile(ReadHandle, array, (uint)array.Length, out lpNumberOfBytesRead, ref lpOverlapped2);
|
|
switch (NativeMethods.WaitForSingleObject(lpOverlapped2.EventHandle, dwMilliseconds))
|
|
{
|
|
case 0u:
|
|
status = HidDeviceData.ReadStatus.Success;
|
|
break;
|
|
case 258u:
|
|
status = HidDeviceData.ReadStatus.WaitTimedOut;
|
|
array = new byte[0];
|
|
break;
|
|
case uint.MaxValue:
|
|
status = HidDeviceData.ReadStatus.WaitFail;
|
|
array = new byte[0];
|
|
break;
|
|
default:
|
|
status = HidDeviceData.ReadStatus.NoDataRead;
|
|
array = new byte[0];
|
|
break;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
status = HidDeviceData.ReadStatus.ReadError;
|
|
}
|
|
finally
|
|
{
|
|
CloseDeviceIO(lpOverlapped2.EventHandle);
|
|
}
|
|
}
|
|
}
|
|
return new HidDeviceData(array, 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)
|
|
{
|
|
NativeMethods.SECURITY_ATTRIBUTES lpSecurityAttributes = default(NativeMethods.SECURITY_ATTRIBUTES);
|
|
int dwFlagsAndAttributes = 0;
|
|
if (deviceMode == DeviceMode.Overlapped)
|
|
{
|
|
dwFlagsAndAttributes = 1073741824;
|
|
}
|
|
lpSecurityAttributes.lpSecurityDescriptor = IntPtr.Zero;
|
|
lpSecurityAttributes.bInheritHandle = true;
|
|
lpSecurityAttributes.nLength = Marshal.SizeOf((object)lpSecurityAttributes);
|
|
return NativeMethods.CreateFile(devicePath, deviceAccess, 3, ref lpSecurityAttributes, 3, dwFlagsAndAttributes, 0);
|
|
}
|
|
|
|
private static void CloseDeviceIO(IntPtr handle)
|
|
{
|
|
if (Environment.OSVersion.Version.Major > 5)
|
|
{
|
|
NativeMethods.CancelIoEx(handle, IntPtr.Zero);
|
|
}
|
|
NativeMethods.CloseHandle(handle);
|
|
}
|
|
|
|
private void DeviceEventMonitorInserted()
|
|
{
|
|
if (IsOpen)
|
|
{
|
|
OpenDevice();
|
|
}
|
|
if (this.Inserted != null)
|
|
{
|
|
this.Inserted();
|
|
}
|
|
}
|
|
|
|
private void DeviceEventMonitorRemoved()
|
|
{
|
|
if (IsOpen)
|
|
{
|
|
CloseDevice();
|
|
}
|
|
if (this.Removed != null)
|
|
{
|
|
this.Removed();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (MonitorDeviceEvents)
|
|
{
|
|
MonitorDeviceEvents = false;
|
|
}
|
|
if (IsOpen)
|
|
{
|
|
CloseDevice();
|
|
}
|
|
}
|
|
}
|
|
}
|