Skip to content

Commit 816c372

Browse files
authored
Add Raw HID device detection (#473)
1 parent 1e63e81 commit 816c372

File tree

12 files changed

+259
-135
lines changed

12 files changed

+259
-135
lines changed

macos/QMK Toolbox.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
3AAB20AB283BEEC700029ABD /* LogTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB20AA283BEEC700029ABD /* LogTextView.swift */; };
5757
3AB09F1D28B46672006CC212 /* GD32VDFUDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB09F1C28B46672006CC212 /* GD32VDFUDevice.swift */; };
5858
3AB4BC9D2495540A00204A3F /* bootloadHID in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3AB4BC9C2495540A00204A3F /* bootloadHID */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
59+
3AB657102B9EABB4007805DD /* RawDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB6570F2B9EABB4007805DD /* RawDevice.swift */; };
60+
3AB657122B9EAC34007805DD /* HIDDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB657112B9EAC34007805DD /* HIDDevice.swift */; };
5961
3AE86EF8294C9CEC00008D3E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3AE86EFA294C9CEC00008D3E /* Main.storyboard */; };
6062
9BE10718275F4CFE00C708D5 /* wb32-dfu-updater_cli in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BE10717275F4CFE00C708D5 /* wb32-dfu-updater_cli */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
6163
C93A0FF42292232E0006C88F /* reset.eep in Resources */ = {isa = PBXBuildFile; fileRef = C93A0FF32292232D0006C88F /* reset.eep */; };
@@ -147,6 +149,8 @@
147149
3AAB20AA283BEEC700029ABD /* LogTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogTextView.swift; sourceTree = "<group>"; };
148150
3AB09F1C28B46672006CC212 /* GD32VDFUDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GD32VDFUDevice.swift; sourceTree = "<group>"; };
149151
3AB4BC9C2495540A00204A3F /* bootloadHID */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = bootloadHID; sourceTree = "<group>"; };
152+
3AB6570F2B9EABB4007805DD /* RawDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawDevice.swift; sourceTree = "<group>"; };
153+
3AB657112B9EAC34007805DD /* HIDDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HIDDevice.swift; sourceTree = "<group>"; };
150154
3AE86EF9294C9CEC00008D3E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
151155
3AFD4BCF281AB83C00ADCB65 /* libhidapi.0.14.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libhidapi.0.14.0.dylib; sourceTree = "<group>"; };
152156
9BE10717275F4CFE00C708D5 /* wb32-dfu-updater_cli */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = "wb32-dfu-updater_cli"; sourceTree = "<group>"; };
@@ -217,6 +221,8 @@
217221
3A8DE019284636780012063A /* HIDConsoleDevice.swift */,
218222
3A708D99284901F500394E52 /* HIDListener.swift */,
219223
3A92873C292CF7A10015D961 /* HIDConsoleViewController.swift */,
224+
3AB657112B9EAC34007805DD /* HIDDevice.swift */,
225+
3AB6570F2B9EABB4007805DD /* RawDevice.swift */,
220226
);
221227
path = HID;
222228
sourceTree = "<group>";
@@ -371,6 +377,7 @@
371377
buildActionMask = 2147483647;
372378
files = (
373379
3A8DE01A284636780012063A /* HIDConsoleDevice.swift in Sources */,
380+
3AB657102B9EABB4007805DD /* RawDevice.swift in Sources */,
374381
3A708D9A284901F500394E52 /* HIDListener.swift in Sources */,
375382
3A92873D292CF7A10015D961 /* HIDConsoleViewController.swift in Sources */,
376383
3A37607A283E769300C19B3F /* KeyView.swift in Sources */,
@@ -390,6 +397,7 @@
390397
3A32CF5F284142D10016D7B7 /* STM32DFUDevice.swift in Sources */,
391398
3A32CF61284143990016D7B7 /* STM32DuinoDevice.swift in Sources */,
392399
3A32CF63284143EC0016D7B7 /* USBAspDevice.swift in Sources */,
400+
3AB657122B9EAC34007805DD /* HIDDevice.swift in Sources */,
393401
3A32CF652841445E0016D7B7 /* USBTinyISPDevice.swift in Sources */,
394402
3A32CF672841451D0016D7B7 /* WB32DFUDevice.swift in Sources */,
395403
3A5A916C28410F53004DD9BD /* USBDevice.swift in Sources */,

macos/QMK Toolbox/HID/HIDConsoleDevice.swift

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,14 @@ protocol HIDConsoleDeviceDelegate: AnyObject {
44
func consoleDevice(_ device: HIDConsoleDevice, didReceiveReport report: String)
55
}
66

7-
class HIDConsoleDevice: Equatable, CustomStringConvertible {
7+
class HIDConsoleDevice: HIDDevice {
88
weak var delegate: HIDConsoleDeviceDelegate?
99

1010
private var reportBuffer: UnsafeMutablePointer<UInt8>
1111

1212
private var reportBufferSize: Int = 0
1313

14-
let hidDevice: IOHIDDevice
15-
16-
var manufacturer: String? {
17-
HIDConsoleDevice.stringProperty(kIOHIDManufacturerKey, for: hidDevice)
18-
}
19-
20-
var product: String? {
21-
HIDConsoleDevice.stringProperty(kIOHIDProductKey, for: hidDevice)
22-
}
23-
24-
var vendorID: UInt16 {
25-
HIDConsoleDevice.uint16Property(kIOHIDVendorIDKey, for: hidDevice)
26-
}
27-
28-
var productID: UInt16 {
29-
HIDConsoleDevice.uint16Property(kIOHIDProductIDKey, for: hidDevice)
30-
}
31-
32-
var revisionBCD: UInt16 {
33-
HIDConsoleDevice.uint16Property(kIOHIDVersionNumberKey, for: hidDevice)
34-
}
35-
36-
init(_ device: IOHIDDevice) {
37-
hidDevice = device
14+
override init(_ device: IOHIDDevice) {
3815
reportBufferSize = IOHIDDeviceGetProperty(device, kIOHIDMaxInputReportSizeKey as CFString) as! Int
3916
reportBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: reportBufferSize)
4017

@@ -44,6 +21,8 @@ class HIDConsoleDevice: Equatable, CustomStringConvertible {
4421
device.reportReceived(reportData)
4522
}
4623

24+
super.init(device)
25+
4726
let unsafeSelf = Unmanaged.passRetained(self).toOpaque()
4827
IOHIDDeviceRegisterInputReportCallback(hidDevice, reportBuffer, reportBufferSize, inputReportCallback, unsafeSelf)
4928
}
@@ -75,20 +54,4 @@ class HIDConsoleDevice: Equatable, CustomStringConvertible {
7554
delegate?.consoleDevice(self, didReceiveReport: completedLine)
7655
}
7756
}
78-
79-
var description: String {
80-
String(format: "%@ %@ (%04X:%04X:%04X)", manufacturer ?? "", product ?? "", vendorID, productID, revisionBCD)
81-
}
82-
83-
static func == (lhs: HIDConsoleDevice, rhs: HIDConsoleDevice) -> Bool {
84-
return lhs.hidDevice === rhs.hidDevice
85-
}
86-
87-
static func stringProperty(_ propertyName: String, for device: IOHIDDevice) -> String? {
88-
return IOHIDDeviceGetProperty(device, propertyName as CFString) as! String?
89-
}
90-
91-
static func uint16Property(_ propertyName: String, for device: IOHIDDevice) -> UInt16 {
92-
return (IOHIDDeviceGetProperty(device, propertyName as CFString) as! NSNumber?)!.uint16Value
93-
}
9457
}

macos/QMK Toolbox/HID/HIDConsoleViewController.swift

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,44 @@ class HIDConsoleViewController: NSViewController, HIDListenerDelegate {
2424

2525
var hidListener: HIDListener!
2626
var lastReportedDevice: HIDConsoleDevice?
27-
28-
func hidDeviceDidConnect(_ device: HIDConsoleDevice) {
29-
lastReportedDevice = device
30-
updateConsoleList()
31-
logTextView.logHID("HID console connected: \(device)")
27+
func hidDeviceDidConnect(_ device: HIDDevice) {
28+
if device is HIDConsoleDevice {
29+
lastReportedDevice = (device as! HIDConsoleDevice)
30+
updateConsoleList()
31+
logTextView.logHID("HID console connected: \(device)")
32+
} else {
33+
logTextView.logHID("Raw HID device connected: \(device)")
34+
}
3235
}
3336

34-
func hidDeviceDidDisconnect(_ device: HIDConsoleDevice) {
35-
lastReportedDevice = nil
36-
updateConsoleList()
37-
logTextView.logHID("HID console disconnected: \(device)")
37+
func hidDeviceDidDisconnect(_ device: HIDDevice) {
38+
if device is HIDConsoleDevice {
39+
lastReportedDevice = nil
40+
updateConsoleList()
41+
logTextView.logHID("HID console disconnected: \(device)")
42+
} else {
43+
logTextView.logHID("Raw HID device disconnected: \(device)")
44+
}
3845
}
3946

4047
func consoleDevice(_ device: HIDConsoleDevice, didReceiveReport report: String) {
4148
let selectedDevice = consoleListBox.indexOfSelectedItem
42-
if selectedDevice == 0 || hidListener.devices[selectedDevice - 1] == device {
49+
let consoleDevices = hidListener.devices.filter { $0 is HIDConsoleDevice }
50+
if selectedDevice == 0 || consoleDevices[selectedDevice - 1] == device {
4351
if lastReportedDevice != device {
4452
logTextView.logHID("\(device.manufacturer ?? "") \(device.product ?? "")")
4553
lastReportedDevice = device
4654
}
55+
logTextView.logHIDOutput(report)
4756
}
48-
logTextView.logHIDOutput(report)
4957
}
5058

5159
func updateConsoleList() {
5260
let selectedItem = consoleListBox.indexOfSelectedItem >= 0 ? consoleListBox.indexOfSelectedItem : 0
5361
consoleListBox.deselectItem(at: selectedItem)
5462
consoleListBox.removeAllItems()
5563

56-
hidListener.devices.forEach { device in
64+
hidListener.devices.filter { $0 is HIDConsoleDevice }.forEach { device in
5765
consoleListBox.addItem(withObjectValue: device.description)
5866
}
5967

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import Foundation
2+
3+
class HIDDevice: Equatable, CustomStringConvertible {
4+
let hidDevice: IOHIDDevice
5+
6+
var usagePage: UInt16 {
7+
HIDDevice.uint16Property(kIOHIDDeviceUsagePageKey, for: hidDevice)!
8+
}
9+
10+
var usage: UInt16 {
11+
HIDDevice.uint16Property(kIOHIDDeviceUsageKey, for: hidDevice)!
12+
}
13+
14+
var manufacturer: String? {
15+
HIDDevice.stringProperty(kIOHIDManufacturerKey, for: hidDevice)
16+
}
17+
18+
var product: String? {
19+
HIDDevice.stringProperty(kIOHIDProductKey, for: hidDevice)
20+
}
21+
22+
var vendorID: UInt16 {
23+
HIDDevice.uint16Property(kIOHIDVendorIDKey, for: hidDevice)!
24+
}
25+
26+
var productID: UInt16 {
27+
HIDDevice.uint16Property(kIOHIDProductIDKey, for: hidDevice)!
28+
}
29+
30+
var revisionBCD: UInt16 {
31+
HIDDevice.uint16Property(kIOHIDVersionNumberKey, for: hidDevice)!
32+
}
33+
34+
init(_ device: IOHIDDevice) {
35+
hidDevice = device
36+
}
37+
38+
var description: String {
39+
String(format: "%@ %@ (%04X:%04X:%04X)", manufacturer ?? "", product ?? "", vendorID, productID, revisionBCD)
40+
}
41+
42+
static func == (lhs: HIDDevice, rhs: HIDDevice) -> Bool {
43+
return lhs.hidDevice === rhs.hidDevice
44+
}
45+
46+
static func stringProperty(_ propertyName: String, for device: IOHIDDevice) -> String? {
47+
return IOHIDDeviceGetProperty(device, propertyName as CFString) as! String?
48+
}
49+
50+
static func uint16Property(_ propertyName: String, for device: IOHIDDevice) -> UInt16? {
51+
return (IOHIDDeviceGetProperty(device, propertyName as CFString) as! NSNumber?)!.uint16Value
52+
}
53+
}

macos/QMK Toolbox/HID/HIDListener.swift

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import IOKit.hid
33

44
let CONSOLE_USAGE_PAGE: UInt16 = 0xFF31
55
let CONSOLE_USAGE: UInt16 = 0x0074
6+
let RAW_USAGE_PAGE: UInt16 = 0xFF60
7+
let RAW_USAGE: UInt16 = 0x0061
68

79
protocol HIDListenerDelegate: AnyObject {
8-
func hidDeviceDidConnect(_ device: HIDConsoleDevice)
10+
func hidDeviceDidConnect(_ device: HIDDevice)
911

10-
func hidDeviceDidDisconnect(_ device: HIDConsoleDevice)
12+
func hidDeviceDidDisconnect(_ device: HIDDevice)
1113

1214
func consoleDevice(_ device: HIDConsoleDevice, didReceiveReport report: String)
1315
}
@@ -17,12 +19,11 @@ class HIDListener: HIDConsoleDeviceDelegate {
1719

1820
private var hidManager: IOHIDManager
1921

20-
var devices: [HIDConsoleDevice] = []
22+
var devices: [HIDDevice] = []
2123

2224
init() {
2325
hidManager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
24-
let consoleMatcher = [kIOHIDDeviceUsagePageKey: CONSOLE_USAGE_PAGE, kIOHIDDeviceUsageKey: CONSOLE_USAGE]
25-
IOHIDManagerSetDeviceMatching(hidManager, consoleMatcher as CFDictionary?)
26+
IOHIDManagerSetDeviceMatching(hidManager, nil)
2627
}
2728

2829
func start() {
@@ -49,10 +50,17 @@ class HIDListener: HIDConsoleDeviceDelegate {
4950
return
5051
}
5152

52-
let consoleDevice = HIDConsoleDevice(device)
53-
consoleDevice.delegate = self
54-
devices.append(consoleDevice)
55-
delegate?.hidDeviceDidConnect(consoleDevice)
53+
guard let hidDevice = createDevice(device) else {
54+
return
55+
}
56+
57+
devices.append(hidDevice)
58+
59+
if hidDevice is HIDConsoleDevice {
60+
(hidDevice as! HIDConsoleDevice).delegate = self
61+
}
62+
63+
delegate?.hidDeviceDidConnect(hidDevice)
5664
}
5765

5866
func deviceDisconnected(_ device: IOHIDDevice) {
@@ -75,4 +83,17 @@ class HIDListener: HIDConsoleDeviceDelegate {
7583
IOHIDManagerUnscheduleFromRunLoop(hidManager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
7684
IOHIDManagerClose(hidManager, IOOptionBits(kIOHIDOptionsTypeNone))
7785
}
86+
87+
func createDevice(_ d: IOHIDDevice) -> HIDDevice? {
88+
let usagePage = HIDDevice.uint16Property(kIOHIDPrimaryUsagePageKey, for: d)
89+
let usage = HIDDevice.uint16Property(kIOHIDPrimaryUsageKey, for: d)
90+
91+
if usagePage == CONSOLE_USAGE_PAGE && usage == CONSOLE_USAGE {
92+
return HIDConsoleDevice(d)
93+
} else if usagePage == RAW_USAGE_PAGE && usage == RAW_USAGE {
94+
return RawDevice(d)
95+
}
96+
97+
return nil
98+
}
7899
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Foundation
2+
3+
class RawDevice: HIDDevice {}

macos/QMK Toolbox/USB/USBListener.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ class USBListener: BootloaderDeviceDelegate {
193193
if productID == 0xB007 {
194194
return .kiibohdDfu
195195
}
196-
break;
196+
break
197197
case 0x1EAF: // Leaflabs
198198
if productID == 0x0003 {
199199
return .stm32duino
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using HidLibrary;
2+
using System.Linq;
3+
using System.Text;
4+
5+
namespace QMK_Toolbox.Hid
6+
{
7+
public abstract class BaseHidDevice
8+
{
9+
public IHidDevice HidDevice { get; }
10+
11+
public string ManufacturerString { get; }
12+
13+
public string ProductString { get; }
14+
15+
public ushort VendorId { get; }
16+
17+
public ushort ProductId { get; }
18+
19+
public ushort RevisionBcd { get; }
20+
21+
public ushort UsagePage { get; }
22+
23+
public ushort Usage { get; }
24+
25+
public BaseHidDevice(IHidDevice device)
26+
{
27+
HidDevice = device;
28+
HidDevice.OpenDevice();
29+
30+
ManufacturerString = GetManufacturerString(HidDevice);
31+
ProductString = GetProductString(HidDevice);
32+
33+
VendorId = (ushort)HidDevice.Attributes.VendorId;
34+
ProductId = (ushort)HidDevice.Attributes.ProductId;
35+
RevisionBcd = (ushort)HidDevice.Attributes.Version;
36+
UsagePage = (ushort)HidDevice.Capabilities.UsagePage;
37+
Usage = (ushort)HidDevice.Capabilities.Usage;
38+
39+
HidDevice.CloseDevice();
40+
}
41+
42+
public override string ToString()
43+
{
44+
return $"{ManufacturerString} {ProductString} ({VendorId:X4}:{ProductId:X4}:{RevisionBcd:X4})";
45+
}
46+
47+
private static string GetManufacturerString(IHidDevice d)
48+
{
49+
if (d == null) return "";
50+
51+
d.ReadManufacturer(out var bs);
52+
return Encoding.Default.GetString(bs.Where(b => b > 0).ToArray());
53+
}
54+
55+
private static string GetProductString(IHidDevice d)
56+
{
57+
if (d == null) return "";
58+
59+
d.ReadProduct(out var bs);
60+
return Encoding.Default.GetString(bs.Where(b => b > 0).ToArray());
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)