Updated Write output
This commit is contained in:
parent
fe283384af
commit
19e713eb21
|
@ -40,9 +40,9 @@ namespace EonaCat.HID.Analyzer
|
|||
_deviceManager.OnDeviceRemoved += Hid_Removed;
|
||||
}
|
||||
|
||||
public void RefreshDevices(ushort? vendorId = null, ushort? productId = null)
|
||||
public async Task RefreshDevicesAsync(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
_deviceList = _deviceManager.Enumerate(vendorId, productId);
|
||||
_deviceList = await _deviceManager.EnumerateAsync(vendorId, productId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void MainForm_Load(object sender, System.EventArgs e)
|
||||
|
@ -61,7 +61,7 @@ namespace EonaCat.HID.Analyzer
|
|||
{
|
||||
try
|
||||
{
|
||||
RefreshDevices();
|
||||
RefreshDevicesAsync();
|
||||
UpdateDeviceList();
|
||||
toolStripStatusLabel1.Text = "Please select device and click open to start.";
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ namespace EonaCat.HID.Analyzer
|
|||
{
|
||||
try
|
||||
{
|
||||
RefreshDevices();
|
||||
RefreshDevicesAsync();
|
||||
UpdateDeviceList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -203,7 +203,7 @@ namespace EonaCat.HID.Analyzer
|
|||
vid = ushort.Parse(str[0], NumberStyles.AllowHexSpecifier);
|
||||
pid = ushort.Parse(str[1], NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
RefreshDevices(vid, pid);
|
||||
RefreshDevicesAsync(vid, pid);
|
||||
UpdateDeviceList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace EonaCat.HID.Example
|
|||
Console.WriteLine($"Removed Device --> VID: {e.Device.VendorId:X4}, PID: {e.Device.ProductId:X4}");
|
||||
};
|
||||
|
||||
RefreshDevices();
|
||||
RefreshDevicesAsync();
|
||||
|
||||
if (!_deviceList.Any())
|
||||
{
|
||||
|
@ -83,9 +83,9 @@ namespace EonaCat.HID.Example
|
|||
}
|
||||
}
|
||||
|
||||
static void RefreshDevices(ushort? vendorId = null, ushort? productId = null)
|
||||
static async Task RefreshDevicesAsync(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
_deviceList = _deviceManager.Enumerate(vendorId, productId);
|
||||
_deviceList = await _deviceManager.EnumerateAsync(vendorId, productId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
static void DisplayDevices()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<Copyright>Copyright 2024 EonaCat (Jeroen Saey)</Copyright>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<PackageId>EonaCat.HID</PackageId>
|
||||
<Version>1.0.4</Version>
|
||||
<Version>1.0.5</Version>
|
||||
<Title>EonaCat.HID</Title>
|
||||
<Authors>EonaCat (Jeroen Saey)</Authors>
|
||||
<Description>HID Devices</Description>
|
||||
|
|
|
@ -228,41 +228,38 @@ namespace EonaCat.HID
|
|||
|
||||
public async Task WriteOutputReportAsync(HidReport report)
|
||||
{
|
||||
if (!_isOpen)
|
||||
if (!_isOpen || _deviceStream == null)
|
||||
{
|
||||
OnError?.Invoke(this, new HidErrorEventArgs(this, new InvalidOperationException("Device not open")));
|
||||
OnError?.Invoke(this, new HidErrorEventArgs(this, new InvalidOperationException("Device not open or stream is null")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (report == null)
|
||||
if (report?.Data == null || report.Data.Length == 0)
|
||||
{
|
||||
OnError?.Invoke(this, new HidErrorEventArgs(this, new ArgumentNullException(nameof(report))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (report.Data == null || report.Data.Length == 0)
|
||||
{
|
||||
OnError?.Invoke(this, new HidErrorEventArgs(this, new ArgumentException("Data cannot be null or empty", nameof(report))));
|
||||
OnError?.Invoke(this, new HidErrorEventArgs(this, new ArgumentException("Invalid report or data")));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Combine reportId and data into one buffer for sending
|
||||
var buffer = new byte[1 + report.Data.Length];
|
||||
int reportLength = report.Data.Length + 1;
|
||||
var buffer = new byte[reportLength];
|
||||
buffer[0] = report.ReportId;
|
||||
Array.Copy(report.Data, 0, buffer, 1, report.Data.Length);
|
||||
|
||||
await _deviceStream.WriteAsync(buffer, 0, buffer.Length);
|
||||
await _deviceStream.FlushAsync();
|
||||
}
|
||||
catch (IOException ioEx)
|
||||
{
|
||||
OnError?.Invoke(this, new HidErrorEventArgs(this, ioEx));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<HidReport> ReadInputReportAsync()
|
||||
{
|
||||
if (!_isOpen)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using EonaCat.HID.EventArguments;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.HID
|
||||
{
|
||||
|
@ -15,7 +16,7 @@ namespace EonaCat.HID
|
|||
/// <summary>
|
||||
/// Enumerate all connected HID devices matching optional VendorId/ProductId filters
|
||||
/// </summary>
|
||||
IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null);
|
||||
Task<IEnumerable<IHid>> EnumerateAsync(ushort? vendorId = null, ushort? productId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Event is raised when a HID device is inserted
|
||||
|
|
|
@ -18,7 +18,12 @@ namespace EonaCat.HID.Managers.Linux
|
|||
public event EventHandler<HidEventArgs> OnDeviceRemoved;
|
||||
public event EventHandler<string> OnDeviceError;
|
||||
|
||||
public IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null)
|
||||
public Task<IEnumerable<IHid>> EnumerateAsync(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
return Task.Run(() => Enumerate(vendorId, productId));
|
||||
}
|
||||
|
||||
private IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
var hidrawDir = "/sys/class/hidraw/";
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using static EonaCat.HID.Managers.Mac.NativeMethods;
|
||||
|
||||
namespace EonaCat.HID.Managers.Mac
|
||||
|
@ -48,7 +49,12 @@ namespace EonaCat.HID.Managers.Mac
|
|||
IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, _deviceRemovedCallback, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null)
|
||||
public async Task<IEnumerable<IHid>> EnumerateAsync(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
return await Task.Run(() => Enumerate(vendorId, productId));
|
||||
}
|
||||
|
||||
private IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
var devices = new List<IHid>();
|
||||
IntPtr cfSet = IOHIDManagerCopyDevices(_hidManager);
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using static EonaCat.HID.Managers.Windows.NativeMethods;
|
||||
|
||||
namespace EonaCat.HID.Managers.Windows
|
||||
|
@ -113,12 +114,8 @@ namespace EonaCat.HID.Managers.Windows
|
|||
if (hdr.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
|
||||
{
|
||||
var devInterface = Marshal.PtrToStructure<DEV_BROADCAST_DEVICEINTERFACE>(lParam);
|
||||
string devicePath = Marshal.PtrToStringUni((IntPtr)((long)lParam + Marshal.OffsetOf<DEV_BROADCAST_DEVICEINTERFACE>("dbcc_classguid").ToInt64() + Marshal.SizeOf<Guid>()));
|
||||
|
||||
// Calculate pointer to string
|
||||
IntPtr stringPtr = IntPtr.Add(lParam, Marshal.SizeOf<DEV_BROADCAST_DEVICEINTERFACE>());
|
||||
|
||||
// Read null-terminated string from unmanaged memory
|
||||
string devicePath = Marshal.PtrToStringUni(stringPtr);
|
||||
if (!string.IsNullOrEmpty(devicePath))
|
||||
{
|
||||
try
|
||||
|
@ -175,19 +172,23 @@ namespace EonaCat.HID.Managers.Windows
|
|||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null)
|
||||
public Task<IEnumerable<IHid>> EnumerateAsync(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
return Task.Run(() => Enumerate(vendorId, productId));
|
||||
}
|
||||
|
||||
private IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null)
|
||||
{
|
||||
var list = new List<IHid>();
|
||||
|
||||
IntPtr devInfo = SetupDiGetClassDevs(
|
||||
ref GUID_DEVINTERFACE_HID,
|
||||
null,
|
||||
IntPtr.Zero,
|
||||
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
|
||||
if (devInfo == IntPtr.Zero || devInfo.ToInt64() == -1)
|
||||
{
|
||||
if (devInfo == IntPtr.Zero || devInfo == new IntPtr(-1))
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "SetupDiGetClassDevs failed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -198,92 +199,59 @@ namespace EonaCat.HID.Managers.Windows
|
|||
|
||||
for (uint index = 0; ; index++)
|
||||
{
|
||||
bool ok = SetupDiEnumDeviceInterfaces(
|
||||
devInfo, IntPtr.Zero, ref GUID_DEVINTERFACE_HID, index, ref iface);
|
||||
|
||||
if (!ok)
|
||||
if (!SetupDiEnumDeviceInterfaces(devInfo, IntPtr.Zero, ref GUID_DEVINTERFACE_HID, index, ref iface))
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
if (error == ERROR_NO_MORE_ITEMS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Win32Exception(error, "SetupDiEnumDeviceInterfaces failed");
|
||||
}
|
||||
|
||||
// Step 1: Get required size
|
||||
uint requiredSize = 0;
|
||||
bool sizeResult = SetupDiGetDeviceInterfaceDetail(
|
||||
devInfo,
|
||||
ref iface,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
ref requiredSize,
|
||||
IntPtr.Zero);
|
||||
|
||||
// This call should fail with ERROR_INSUFFICIENT_BUFFER
|
||||
int sizeError = Marshal.GetLastWin32Error();
|
||||
if (sizeError != ERROR_INSUFFICIENT_BUFFER || requiredSize == 0)
|
||||
{
|
||||
SetupDiGetDeviceInterfaceDetail(devInfo, ref iface, IntPtr.Zero, 0, ref requiredSize, IntPtr.Zero);
|
||||
if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER || requiredSize == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 2: Allocate buffer for detail data
|
||||
IntPtr detailDataBuffer = Marshal.AllocHGlobal((int)requiredSize);
|
||||
try
|
||||
{
|
||||
// Step 3: Set cbSize at start of allocated memory
|
||||
// CRITICAL: cbSize must be size of SP_DEVICE_INTERFACE_DETAIL_DATA structure
|
||||
// On x86: 6 bytes (4 for cbSize + 2 for alignment)
|
||||
// On x64: 8 bytes (4 for cbSize + 4 for alignment)
|
||||
int cbSize = IntPtr.Size == 8 ? 8 : 6;
|
||||
Marshal.WriteInt32(detailDataBuffer, cbSize);
|
||||
|
||||
// Step 4: Now get the device interface detail
|
||||
bool success = SetupDiGetDeviceInterfaceDetail(
|
||||
devInfo,
|
||||
ref iface,
|
||||
detailDataBuffer,
|
||||
requiredSize,
|
||||
ref requiredSize,
|
||||
IntPtr.Zero);
|
||||
|
||||
if (!success)
|
||||
if (!SetupDiGetDeviceInterfaceDetail(devInfo, ref iface, detailDataBuffer, requiredSize, ref requiredSize, IntPtr.Zero))
|
||||
{
|
||||
int detailError = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(detailError, $"SetupDiGetDeviceInterfaceDetail failed with error {detailError}");
|
||||
System.Diagnostics.Debug.WriteLine($"Detail fetch failed: {Marshal.GetLastWin32Error()}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 5: Read device path string (starts at offset 4)
|
||||
// The DevicePath is a null-terminated string that starts at offset 4
|
||||
IntPtr pDevicePathName = IntPtr.Add(detailDataBuffer, 4);
|
||||
string devicePath = Marshal.PtrToStringAuto(pDevicePathName);
|
||||
|
||||
if (string.IsNullOrEmpty(devicePath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(devicePath))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 6: Create device, filter and add
|
||||
try
|
||||
{
|
||||
// First try to open the device with minimal access to check if it's accessible
|
||||
using (var testHandle = CreateFile(devicePath, 0, // No access requested
|
||||
FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
|
||||
// Try to open with zero access to ensure it’s reachable
|
||||
using (var testHandle = CreateFile(devicePath, 0, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
|
||||
{
|
||||
if (testHandle.IsInvalid)
|
||||
{
|
||||
// Device not accessible, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
HidWindows device;
|
||||
try
|
||||
{
|
||||
device = new HidWindows(devicePath);
|
||||
device.Setup();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Skipping inaccessible HID device {devicePath}: {ex.Message}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var device = new HidWindows(devicePath);
|
||||
device.Setup();
|
||||
|
||||
if (vendorId.HasValue && device.VendorId != vendorId.Value ||
|
||||
productId.HasValue && device.ProductId != productId.Value)
|
||||
if ((vendorId.HasValue && device.VendorId != vendorId.Value) ||
|
||||
(productId.HasValue && device.ProductId != productId.Value))
|
||||
{
|
||||
device.Dispose();
|
||||
continue;
|
||||
|
@ -291,26 +259,6 @@ namespace EonaCat.HID.Managers.Windows
|
|||
|
||||
list.Add(device);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// Device is in use or access denied - skip silently
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex) when (ex.Message.Contains("HidP_GetCaps") ||
|
||||
ex.Message.Contains("The parameter is incorrect") ||
|
||||
ex.Message.Contains("Access is denied"))
|
||||
{
|
||||
// Common HID access failures - skip these devices
|
||||
System.Diagnostics.Debug.WriteLine($"Skipping inaccessible HID device {devicePath}: {ex.Message}");
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Other unexpected errors - log but continue
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to create device for path {devicePath}: {ex.Message}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(detailDataBuffer);
|
||||
|
@ -325,7 +273,10 @@ namespace EonaCat.HID.Managers.Windows
|
|||
return list;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_deviceNotificationHandle != IntPtr.Zero)
|
||||
{
|
||||
|
@ -338,7 +289,16 @@ namespace EonaCat.HID.Managers.Windows
|
|||
DestroyWindow(_messageWindowHandle);
|
||||
_messageWindowHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
foreach (var device in _knownDevices.Values)
|
||||
{
|
||||
device.Dispose();
|
||||
}
|
||||
|
||||
_knownDevices.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static class NativeMethods
|
||||
|
|
Loading…
Reference in New Issue