Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Raw HID device detection #473

Merged
merged 3 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions macos/QMK Toolbox.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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 @@ -150,6 +152,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 @@ -220,6 +224,8 @@
3A8DE019284636780012063A /* HIDConsoleDevice.swift */,
3A708D99284901F500394E52 /* HIDListener.swift */,
3A92873C292CF7A10015D961 /* HIDConsoleViewController.swift */,
3AB657112B9EAC34007805DD /* HIDDevice.swift */,
3AB6570F2B9EABB4007805DD /* RawDevice.swift */,
);
path = HID;
sourceTree = "<group>";
Expand Down Expand Up @@ -375,6 +381,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 @@ -394,6 +401,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