From a4869646eb55e9107c02c0c413faa8c9690902d5 Mon Sep 17 00:00:00 2001 From: Tsuyumi25 Date: Wed, 9 Sep 2026 19:18:03 +0800 Subject: [PATCH 1/2] Linux: Enumerate HID devices without USB parents --- HidSharp/Platform/Linux/LinuxHidDevice.cs | 51 +++++++++++++++++-- .../Platform/Linux/NativeMethodsLibudev.cs | 2 + .../Platform/Linux/NativeMethodsLibudev0.cs | 9 ++++ .../Platform/Linux/NativeMethodsLibudev1.cs | 9 ++++ 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/HidSharp/Platform/Linux/LinuxHidDevice.cs b/HidSharp/Platform/Linux/LinuxHidDevice.cs index d7dc664..1a1d3a8 100644 --- a/HidSharp/Platform/Linux/LinuxHidDevice.cs +++ b/HidSharp/Platform/Linux/LinuxHidDevice.cs @@ -16,6 +16,7 @@ under the License. */ #endregion using System; +using System.Globalization; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; @@ -90,6 +91,26 @@ internal static LinuxHidDevice TryCreate(string path) return d; } } + + // Devices without USB parents expose their identity through HID udev properties. + IntPtr hidParent = NativeMethodsLibudev.Instance.udev_device_get_parent_with_subsystem_devtype(device, "hid", null); + if (IntPtr.Zero != hidParent) + { + string hidId = NativeMethodsLibudev.Instance.udev_device_get_property_value(hidParent, "HID_ID"); + + int vid, pid; + if (TryParseHidId(hidId, out vid, out pid)) + { + d._vid = vid; + d._pid = pid; + d._version = 0; + d._manufacturer = null; + d._productName = NativeMethodsLibudev.Instance.udev_device_get_property_value(hidParent, "HID_NAME"); + d._serialNumber = null; + + return d; + } + } } } } @@ -108,6 +129,26 @@ internal static LinuxHidDevice TryCreate(string path) return null; } + static bool TryParseHidId(string hidId, out int vid, out int pid) + { + vid = 0; + pid = 0; + + if (hidId == null) { return false; } + + int vendorStart = hidId.IndexOf(':'); + if (vendorStart < 0) { return false; } + vendorStart++; + + int productStart = hidId.IndexOf(':', vendorStart); + if (productStart < 0 || productStart == hidId.Length - 1) { return false; } + + return int.TryParse(hidId.AsSpan(vendorStart, productStart - vendorStart), + NumberStyles.HexNumber, CultureInfo.InvariantCulture, out vid) && + int.TryParse(hidId.AsSpan(productStart + 1), + NumberStyles.HexNumber, CultureInfo.InvariantCulture, out pid); + } + protected override DeviceStream OpenDeviceDirectly(OpenConfiguration openConfig) { RequiresGetInfo(); @@ -282,10 +323,14 @@ unsafe string GetUsbPath() { using (var udev = new SafeUdevHandle(NativeMethodsLibudev.Instance.udev_new())) { - var handle = NativeMethodsLibudev.Instance.udev_device_new_from_syspath(udev.DangerousGetHandle(), _path); - using (var parent = new SafeUdevDeviceHandle(NativeMethodsLibudev.Instance.udev_device_get_parent_with_subsystem_devtype(handle, "usb", "usb_device"))) + var devicePtr = NativeMethodsLibudev.Instance.udev_device_new_from_syspath(udev.DangerousGetHandle(), _path); + using (var device = new SafeUdevDeviceHandle(devicePtr)) { - var parentPtr = parent.DangerousGetHandle(); + if (device.IsInvalid) { throw DeviceException.CreateIOException(this, "Failed to find device in udev."); } + + var parentPtr = NativeMethodsLibudev.Instance.udev_device_get_parent_with_subsystem_devtype( + device.DangerousGetHandle(), "usb", "usb_device"); + if (parentPtr == IntPtr.Zero) { throw DeviceException.CreateIOException(this, "Device has no USB parent."); } string devNum = NativeMethodsLibudev.Instance.udev_device_get_sysattr_value(parentPtr, "devnum"); string busNum = NativeMethodsLibudev.Instance.udev_device_get_sysattr_value(parentPtr, "busnum"); diff --git a/HidSharp/Platform/Linux/NativeMethodsLibudev.cs b/HidSharp/Platform/Linux/NativeMethodsLibudev.cs index 3759a38..a80690d 100644 --- a/HidSharp/Platform/Linux/NativeMethodsLibudev.cs +++ b/HidSharp/Platform/Linux/NativeMethodsLibudev.cs @@ -101,5 +101,7 @@ public abstract string FriendlyName public abstract string udev_device_get_sysattr_value(IntPtr device, string sysattr); public abstract int udev_device_get_is_initialized(IntPtr device); + + public abstract string udev_device_get_property_value(IntPtr device, string key); } } diff --git a/HidSharp/Platform/Linux/NativeMethodsLibudev0.cs b/HidSharp/Platform/Linux/NativeMethodsLibudev0.cs index b4167c4..25e3da9 100644 --- a/HidSharp/Platform/Linux/NativeMethodsLibudev0.cs +++ b/HidSharp/Platform/Linux/NativeMethodsLibudev0.cs @@ -215,5 +215,14 @@ public override int udev_device_get_is_initialized(IntPtr device) { return native_udev_device_get_is_initialized(device); } + + [DllImport(libudev, EntryPoint = "udev_device_get_property_value")] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] + static extern string native_udev_device_get_property_value(IntPtr device, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string key); + public override string udev_device_get_property_value(IntPtr device, string key) + { + return native_udev_device_get_property_value(device, key); + } } } diff --git a/HidSharp/Platform/Linux/NativeMethodsLibudev1.cs b/HidSharp/Platform/Linux/NativeMethodsLibudev1.cs index 484db01..1e136c1 100644 --- a/HidSharp/Platform/Linux/NativeMethodsLibudev1.cs +++ b/HidSharp/Platform/Linux/NativeMethodsLibudev1.cs @@ -215,5 +215,14 @@ public override int udev_device_get_is_initialized(IntPtr device) { return native_udev_device_get_is_initialized(device); } + + [DllImport(libudev, EntryPoint = "udev_device_get_property_value")] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] + static extern string native_udev_device_get_property_value(IntPtr device, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string key); + public override string udev_device_get_property_value(IntPtr device, string key) + { + return native_udev_device_get_property_value(device, key); + } } } From 34667a671f96e52a2cf2c93c9ac263634085a368 Mon Sep 17 00:00:00 2001 From: Tsuyumi25 Date: Thu, 10 Sep 2026 18:48:43 +0800 Subject: [PATCH 2/2] Simplify HID_ID parsing --- HidSharp/Platform/Linux/LinuxHidDevice.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/HidSharp/Platform/Linux/LinuxHidDevice.cs b/HidSharp/Platform/Linux/LinuxHidDevice.cs index 1a1d3a8..cb26f65 100644 --- a/HidSharp/Platform/Linux/LinuxHidDevice.cs +++ b/HidSharp/Platform/Linux/LinuxHidDevice.cs @@ -136,16 +136,13 @@ static bool TryParseHidId(string hidId, out int vid, out int pid) if (hidId == null) { return false; } - int vendorStart = hidId.IndexOf(':'); - if (vendorStart < 0) { return false; } - vendorStart++; + // HID_ID is BUS:VID:PID. + string[] fields = hidId.Split(':'); + if (fields.Length != 3) { return false; } - int productStart = hidId.IndexOf(':', vendorStart); - if (productStart < 0 || productStart == hidId.Length - 1) { return false; } - - return int.TryParse(hidId.AsSpan(vendorStart, productStart - vendorStart), + return int.TryParse(fields[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out vid) && - int.TryParse(hidId.AsSpan(productStart + 1), + int.TryParse(fields[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out pid); }