Skip to content

Commit

Permalink
Add Raw HID device detection (#473)
Browse files Browse the repository at this point in the history
  • Loading branch information
fauxpark authored May 15, 2024
1 parent 1e63e81 commit 816c372
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 135 deletions.
8 changes: 8 additions & 0 deletions macos/QMK Toolbox.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
3AAB20AB283BEEC700029ABD /* LogTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB20AA283BEEC700029ABD /* LogTextView.swift */; };
3AB09F1D28B46672006CC212 /* GD32VDFUDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB09F1C28B46672006CC212 /* GD32VDFUDevice.swift */; };
3AB4BC9D2495540A00204A3F /* bootloadHID in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3AB4BC9C2495540A00204A3F /* bootloadHID */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
3AB657102B9EABB4007805DD /* RawDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB6570F2B9EABB4007805DD /* RawDevice.swift */; };
3AB657122B9EAC34007805DD /* HIDDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB657112B9EAC34007805DD /* HIDDevice.swift */; };
3AE86EF8294C9CEC00008D3E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3AE86EFA294C9CEC00008D3E /* Main.storyboard */; };
9BE10718275F4CFE00C708D5 /* wb32-dfu-updater_cli in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BE10717275F4CFE00C708D5 /* wb32-dfu-updater_cli */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
C93A0FF42292232E0006C88F /* reset.eep in Resources */ = {isa = PBXBuildFile; fileRef = C93A0FF32292232D0006C88F /* reset.eep */; };
Expand Down Expand Up @@ -147,6 +149,8 @@
3AAB20AA283BEEC700029ABD /* LogTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogTextView.swift; sourceTree = "<group>"; };
3AB09F1C28B46672006CC212 /* GD32VDFUDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GD32VDFUDevice.swift; sourceTree = "<group>"; };
3AB4BC9C2495540A00204A3F /* bootloadHID */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = bootloadHID; sourceTree = "<group>"; };
3AB6570F2B9EABB4007805DD /* RawDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawDevice.swift; sourceTree = "<group>"; };
3AB657112B9EAC34007805DD /* HIDDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HIDDevice.swift; sourceTree = "<group>"; };
3AE86EF9294C9CEC00008D3E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
3AFD4BCF281AB83C00ADCB65 /* libhidapi.0.14.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libhidapi.0.14.0.dylib; sourceTree = "<group>"; };
9BE10717275F4CFE00C708D5 /* wb32-dfu-updater_cli */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = "wb32-dfu-updater_cli"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -217,6 +221,8 @@
3A8DE019284636780012063A /* HIDConsoleDevice.swift */,
3A708D99284901F500394E52 /* HIDListener.swift */,
3A92873C292CF7A10015D961 /* HIDConsoleViewController.swift */,
3AB657112B9EAC34007805DD /* HIDDevice.swift */,
3AB6570F2B9EABB4007805DD /* RawDevice.swift */,
);
path = HID;
sourceTree = "<group>";
Expand Down Expand Up @@ -371,6 +377,7 @@
buildActionMask = 2147483647;
files = (
3A8DE01A284636780012063A /* HIDConsoleDevice.swift in Sources */,
3AB657102B9EABB4007805DD /* RawDevice.swift in Sources */,
3A708D9A284901F500394E52 /* HIDListener.swift in Sources */,
3A92873D292CF7A10015D961 /* HIDConsoleViewController.swift in Sources */,
3A37607A283E769300C19B3F /* KeyView.swift in Sources */,
Expand All @@ -390,6 +397,7 @@
3A32CF5F284142D10016D7B7 /* STM32DFUDevice.swift in Sources */,
3A32CF61284143990016D7B7 /* STM32DuinoDevice.swift in Sources */,
3A32CF63284143EC0016D7B7 /* USBAspDevice.swift in Sources */,
3AB657122B9EAC34007805DD /* HIDDevice.swift in Sources */,
3A32CF652841445E0016D7B7 /* USBTinyISPDevice.swift in Sources */,
3A32CF672841451D0016D7B7 /* WB32DFUDevice.swift in Sources */,
3A5A916C28410F53004DD9BD /* USBDevice.swift in Sources */,
Expand Down
45 changes: 4 additions & 41 deletions macos/QMK Toolbox/HID/HIDConsoleDevice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,14 @@ protocol HIDConsoleDeviceDelegate: AnyObject {
func consoleDevice(_ device: HIDConsoleDevice, didReceiveReport report: String)
}

class HIDConsoleDevice: Equatable, CustomStringConvertible {
class HIDConsoleDevice: HIDDevice {
weak var delegate: HIDConsoleDeviceDelegate?

private var reportBuffer: UnsafeMutablePointer<UInt8>

private var reportBufferSize: Int = 0

let hidDevice: IOHIDDevice

var manufacturer: String? {
HIDConsoleDevice.stringProperty(kIOHIDManufacturerKey, for: hidDevice)
}

var product: String? {
HIDConsoleDevice.stringProperty(kIOHIDProductKey, for: hidDevice)
}

var vendorID: UInt16 {
HIDConsoleDevice.uint16Property(kIOHIDVendorIDKey, for: hidDevice)
}

var productID: UInt16 {
HIDConsoleDevice.uint16Property(kIOHIDProductIDKey, for: hidDevice)
}

var revisionBCD: UInt16 {
HIDConsoleDevice.uint16Property(kIOHIDVersionNumberKey, for: hidDevice)
}

init(_ device: IOHIDDevice) {
hidDevice = device
override init(_ device: IOHIDDevice) {
reportBufferSize = IOHIDDeviceGetProperty(device, kIOHIDMaxInputReportSizeKey as CFString) as! Int
reportBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: reportBufferSize)

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

super.init(device)

let unsafeSelf = Unmanaged.passRetained(self).toOpaque()
IOHIDDeviceRegisterInputReportCallback(hidDevice, reportBuffer, reportBufferSize, inputReportCallback, unsafeSelf)
}
Expand Down Expand Up @@ -75,20 +54,4 @@ class HIDConsoleDevice: Equatable, CustomStringConvertible {
delegate?.consoleDevice(self, didReceiveReport: completedLine)
}
}

var description: String {
String(format: "%@ %@ (%04X:%04X:%04X)", manufacturer ?? "", product ?? "", vendorID, productID, revisionBCD)
}

static func == (lhs: HIDConsoleDevice, rhs: HIDConsoleDevice) -> Bool {
return lhs.hidDevice === rhs.hidDevice
}

static func stringProperty(_ propertyName: String, for device: IOHIDDevice) -> String? {
return IOHIDDeviceGetProperty(device, propertyName as CFString) as! String?
}

static func uint16Property(_ propertyName: String, for device: IOHIDDevice) -> UInt16 {
return (IOHIDDeviceGetProperty(device, propertyName as CFString) as! NSNumber?)!.uint16Value
}
}
32 changes: 20 additions & 12 deletions macos/QMK Toolbox/HID/HIDConsoleViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,44 @@ class HIDConsoleViewController: NSViewController, HIDListenerDelegate {

var hidListener: HIDListener!
var lastReportedDevice: HIDConsoleDevice?

func hidDeviceDidConnect(_ device: HIDConsoleDevice) {
lastReportedDevice = device
updateConsoleList()
logTextView.logHID("HID console connected: \(device)")
func hidDeviceDidConnect(_ device: HIDDevice) {
if device is HIDConsoleDevice {
lastReportedDevice = (device as! HIDConsoleDevice)
updateConsoleList()
logTextView.logHID("HID console connected: \(device)")
} else {
logTextView.logHID("Raw HID device connected: \(device)")
}
}

func hidDeviceDidDisconnect(_ device: HIDConsoleDevice) {
lastReportedDevice = nil
updateConsoleList()
logTextView.logHID("HID console disconnected: \(device)")
func hidDeviceDidDisconnect(_ device: HIDDevice) {
if device is HIDConsoleDevice {
lastReportedDevice = nil
updateConsoleList()
logTextView.logHID("HID console disconnected: \(device)")
} else {
logTextView.logHID("Raw HID device disconnected: \(device)")
}
}

func consoleDevice(_ device: HIDConsoleDevice, didReceiveReport report: String) {
let selectedDevice = consoleListBox.indexOfSelectedItem
if selectedDevice == 0 || hidListener.devices[selectedDevice - 1] == device {
let consoleDevices = hidListener.devices.filter { $0 is HIDConsoleDevice }
if selectedDevice == 0 || consoleDevices[selectedDevice - 1] == device {
if lastReportedDevice != device {
logTextView.logHID("\(device.manufacturer ?? "") \(device.product ?? "")")
lastReportedDevice = device
}
logTextView.logHIDOutput(report)
}
logTextView.logHIDOutput(report)
}

func updateConsoleList() {
let selectedItem = consoleListBox.indexOfSelectedItem >= 0 ? consoleListBox.indexOfSelectedItem : 0
consoleListBox.deselectItem(at: selectedItem)
consoleListBox.removeAllItems()

hidListener.devices.forEach { device in
hidListener.devices.filter { $0 is HIDConsoleDevice }.forEach { device in
consoleListBox.addItem(withObjectValue: device.description)
}

Expand Down
53 changes: 53 additions & 0 deletions macos/QMK Toolbox/HID/HIDDevice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation

class HIDDevice: Equatable, CustomStringConvertible {
let hidDevice: IOHIDDevice

var usagePage: UInt16 {
HIDDevice.uint16Property(kIOHIDDeviceUsagePageKey, for: hidDevice)!
}

var usage: UInt16 {
HIDDevice.uint16Property(kIOHIDDeviceUsageKey, for: hidDevice)!
}

var manufacturer: String? {
HIDDevice.stringProperty(kIOHIDManufacturerKey, for: hidDevice)
}

var product: String? {
HIDDevice.stringProperty(kIOHIDProductKey, for: hidDevice)
}

var vendorID: UInt16 {
HIDDevice.uint16Property(kIOHIDVendorIDKey, for: hidDevice)!
}

var productID: UInt16 {
HIDDevice.uint16Property(kIOHIDProductIDKey, for: hidDevice)!
}

var revisionBCD: UInt16 {
HIDDevice.uint16Property(kIOHIDVersionNumberKey, for: hidDevice)!
}

init(_ device: IOHIDDevice) {
hidDevice = device
}

var description: String {
String(format: "%@ %@ (%04X:%04X:%04X)", manufacturer ?? "", product ?? "", vendorID, productID, revisionBCD)
}

static func == (lhs: HIDDevice, rhs: HIDDevice) -> Bool {
return lhs.hidDevice === rhs.hidDevice
}

static func stringProperty(_ propertyName: String, for device: IOHIDDevice) -> String? {
return IOHIDDeviceGetProperty(device, propertyName as CFString) as! String?
}

static func uint16Property(_ propertyName: String, for device: IOHIDDevice) -> UInt16? {
return (IOHIDDeviceGetProperty(device, propertyName as CFString) as! NSNumber?)!.uint16Value
}
}
39 changes: 30 additions & 9 deletions macos/QMK Toolbox/HID/HIDListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import IOKit.hid

let CONSOLE_USAGE_PAGE: UInt16 = 0xFF31
let CONSOLE_USAGE: UInt16 = 0x0074
let RAW_USAGE_PAGE: UInt16 = 0xFF60
let RAW_USAGE: UInt16 = 0x0061

protocol HIDListenerDelegate: AnyObject {
func hidDeviceDidConnect(_ device: HIDConsoleDevice)
func hidDeviceDidConnect(_ device: HIDDevice)

func hidDeviceDidDisconnect(_ device: HIDConsoleDevice)
func hidDeviceDidDisconnect(_ device: HIDDevice)

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

private var hidManager: IOHIDManager

var devices: [HIDConsoleDevice] = []
var devices: [HIDDevice] = []

init() {
hidManager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
let consoleMatcher = [kIOHIDDeviceUsagePageKey: CONSOLE_USAGE_PAGE, kIOHIDDeviceUsageKey: CONSOLE_USAGE]
IOHIDManagerSetDeviceMatching(hidManager, consoleMatcher as CFDictionary?)
IOHIDManagerSetDeviceMatching(hidManager, nil)
}

func start() {
Expand All @@ -49,10 +50,17 @@ class HIDListener: HIDConsoleDeviceDelegate {
return
}

let consoleDevice = HIDConsoleDevice(device)
consoleDevice.delegate = self
devices.append(consoleDevice)
delegate?.hidDeviceDidConnect(consoleDevice)
guard let hidDevice = createDevice(device) else {
return
}

devices.append(hidDevice)

if hidDevice is HIDConsoleDevice {
(hidDevice as! HIDConsoleDevice).delegate = self
}

delegate?.hidDeviceDidConnect(hidDevice)
}

func deviceDisconnected(_ device: IOHIDDevice) {
Expand All @@ -75,4 +83,17 @@ class HIDListener: HIDConsoleDeviceDelegate {
IOHIDManagerUnscheduleFromRunLoop(hidManager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
IOHIDManagerClose(hidManager, IOOptionBits(kIOHIDOptionsTypeNone))
}

func createDevice(_ d: IOHIDDevice) -> HIDDevice? {
let usagePage = HIDDevice.uint16Property(kIOHIDPrimaryUsagePageKey, for: d)
let usage = HIDDevice.uint16Property(kIOHIDPrimaryUsageKey, for: d)

if usagePage == CONSOLE_USAGE_PAGE && usage == CONSOLE_USAGE {
return HIDConsoleDevice(d)
} else if usagePage == RAW_USAGE_PAGE && usage == RAW_USAGE {
return RawDevice(d)
}

return nil
}
}
3 changes: 3 additions & 0 deletions macos/QMK Toolbox/HID/RawDevice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Foundation

class RawDevice: HIDDevice {}
2 changes: 1 addition & 1 deletion macos/QMK Toolbox/USB/USBListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class USBListener: BootloaderDeviceDelegate {
if productID == 0xB007 {
return .kiibohdDfu
}
break;
break
case 0x1EAF: // Leaflabs
if productID == 0x0003 {
return .stm32duino
Expand Down
63 changes: 63 additions & 0 deletions windows/QMK Toolbox/Hid/BaseHidDevice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using HidLibrary;
using System.Linq;
using System.Text;

namespace QMK_Toolbox.Hid
{
public abstract class BaseHidDevice
{
public IHidDevice HidDevice { get; }

public string ManufacturerString { get; }

public string ProductString { get; }

public ushort VendorId { get; }

public ushort ProductId { get; }

public ushort RevisionBcd { get; }

public ushort UsagePage { get; }

public ushort Usage { get; }

public BaseHidDevice(IHidDevice device)
{
HidDevice = device;
HidDevice.OpenDevice();

ManufacturerString = GetManufacturerString(HidDevice);
ProductString = GetProductString(HidDevice);

VendorId = (ushort)HidDevice.Attributes.VendorId;
ProductId = (ushort)HidDevice.Attributes.ProductId;
RevisionBcd = (ushort)HidDevice.Attributes.Version;
UsagePage = (ushort)HidDevice.Capabilities.UsagePage;
Usage = (ushort)HidDevice.Capabilities.Usage;

HidDevice.CloseDevice();
}

public override string ToString()
{
return $"{ManufacturerString} {ProductString} ({VendorId:X4}:{ProductId:X4}:{RevisionBcd:X4})";
}

private static string GetManufacturerString(IHidDevice d)
{
if (d == null) return "";

d.ReadManufacturer(out var bs);
return Encoding.Default.GetString(bs.Where(b => b > 0).ToArray());
}

private static string GetProductString(IHidDevice d)
{
if (d == null) return "";

d.ReadProduct(out var bs);
return Encoding.Default.GetString(bs.Where(b => b > 0).ToArray());
}
}
}
Loading

0 comments on commit 816c372

Please sign in to comment.