iToverDose/Software· 1 JUNI 2026 · 08:01

USB-HID-Geräte unter macOS steuern: IOKit richtig nutzen

Wie Sie mit IOKit auf USB-HID-Geräte zugreifen und Feature-Reports für Einstellungen wie DPI oder RGB nutzen – ohne Kernel-Erweiterungen. Eine Schritt-für-Schritt-Anleitung für Entwickler.

DEV Community4 min0 Kommentare

Die Kommunikation zwischen USB-HID-Geräten wie Mäusen, Tastaturen oder Gamecontrollern und dem Betriebssystem erfolgt meist unsichtbar. Das macOS-Betriebssystem übernimmt die Abstraktion, sodass Anwendungen lediglich standardisierte Eingabeereignisse empfangen. Doch viele Geräte bieten zusätzliche Funktionen, die über Feature-Reports gesteuert werden können – etwa die Konfiguration von DPI-Werten oder RGB-Beleuchtung.

Hersteller wie Razer nutzen diese Reports, um ihre Geräte über Tools wie Razer Synapse zu konfigurieren. Doch was, wenn Sie eine eigene Lösung entwickeln möchten? Auf macOS steht dafür die IOKit-Bibliothek mit der Klasse `IOHIDManager` zur Verfügung, die den Zugriff ermöglicht – ohne dass eine Kernel-Erweiterung erforderlich ist.

Projektvorbereitung: Entwicklungsumgebung einrichten

Für die Implementierung benötigen Sie eine macOS-Umgebung mit Xcode und einem Command-Line-Tool-Projekt. Achten Sie darauf, dass die App Sandbox deaktiviert ist, da diese den Zugriff auf HID-Geräte blockiert. Entfernen Sie den Eintrag im Tab Signing & Capabilities oder stellen Sie sicher, dass er fehlt. Anschließend müssen Sie das `IOKit.framework` in Ihre Projektkonfiguration einbinden.

Nachfolgend der grundlegende Import und die Initialisierung des IOHIDManager:

import Foundation
import IOKit
import IOKit.hid

let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))

Geräteerkennung: Vendor- und Product-ID identifizieren

Um ein bestimmtes USB-HID-Gerät anzusprechen, müssen Sie dessen Vendor-ID (VID) und Product-ID (PID) kennen. Diese Informationen finden Sie in der Regel in der Dokumentation des Herstellers oder durch Auslesen der Geräteattribute. Die IDs werden im HID-Matching-Dictionary übergeben.

IOHIDManagerSetDeviceMatching(
    manager,
    [
        kIOHIDVendorIDKey: 0x1532,
        kIOHIDProductIDKey: 0x0084
    ] as CFDictionary
)

Falls die PID unbekannt ist, können Sie zunächst nur die VID filtern und alle verfügbaren Geräte auflisten:

IOHIDManagerSetDeviceMatching(manager, [kIOHIDVendorIDKey: 0x1532] as CFDictionary)

// Geräte öffnen und Metadaten auslesen
IOHIDManagerOpen(manager, IOOptionBits(kIOHIDOptionsTypeNone))
let devices = IOHIDManagerCopyDevices(manager) as? Set<IOHIDDevice> ?? []

for device in devices {
    let name = IOHIDDeviceGetProperty(device, kIOHIDProductKey as CFString) as? String ?? "Unbekannt"
    let pid = IOHIDDeviceGetProperty(device, kIOHIDProductIDKey as CFString) as? Int ?? 0
    print("Gerät: \(name) | PID: 0x\(String(format: "%04X", pid))")
}

Das Multi-Interface-Problem: Die richtige Schnittstelle finden

Ein physisches USB-Gerät wird oft als mehrere HID-Schnittstellen erkannt. Beispielsweise listet eine Razer DeathAdder V2 vier separate Interfaces auf, von denen nur eines für die Konfiguration relevant ist. Die Auswahl des falschen Interfaces führt dazu, dass Ihre Befehle ignoriert werden – selbst wenn IOHIDDeviceSetReport einen Erfolg zurückmeldet.

Um die korrekte Schnittstelle zu identifizieren, prüfen Sie die `maxFeatureReport`-Größe. Bei Razer-Geräten verwenden wir das Interface mit einer Report-Größe von 90 Bytes. Filtern Sie die Geräteliste entsprechend:

let controlDevice = devices.first { device in
    (IOHIDDeviceGetProperty(device, kIOHIDMaxFeatureReportSizeKey as CFString) as? Int ?? 0) == 90
}

if controlDevice == nil {
    print("Steuerschnittstelle nicht gefunden")
    exit(1)
}

Für andere Geräte kann die richtige Schnittstelle variieren. Ein guter Indikator ist jedoch stets die maxFeatureReport-Größe, die oft im Geräteprotokoll dokumentiert ist.

Daten senden und empfangen: Feature-Reports nutzen

Nach der Auswahl der korrekten Schnittstelle müssen Sie diese öffnen und einen Feature-Report senden. Ein grundlegendes Beispiel für das Senden eines 90-Byte-Reports:

// Gerät öffnen
if IOHIDDeviceOpen(controlDevice, IOOptionBits(kIOHIDOptionsTypeNone)) != kIOReturnSuccess {
    print("Fehler: Gerät konnte nicht geöffnet werden")
    exit(1)
}

// Report vorbereiten (90 Bytes)
var report = UInt8
// Hier die gewünschten Bytes einfügen (z. B. für DPI- oder RGB-Einstellungen)

// Report senden (Report-ID ist 0)
let sendResult = IOHIDDeviceSetReport(
    controlDevice,
    kIOHIDReportTypeFeature,
    0,
    &report,
    report.count
)

if sendResult != kIOReturnSuccess {
    print("Fehler beim Senden des Reports: \(sendResult)")
}

Wichtige Hinweise zur Implementierung

  • Puffergröße: Der Report-Puffer darf genau der `maxFeatureReport`-Größe entsprechen (hier 90 Bytes). Eine falsche Größe führt zu Fehlern wie -536850432 (0xE0005000).
  • Report-ID: Die ID wird als separater Parameter übergeben und sollte nicht im Puffer enthalten sein.
  • Verzögerung: Nach dem Senden eines Reports benötigt das Gerät eine kurze Pause, bevor die Antwort ausgelesen wird. Ein Warteintervall von 200–300 ms hat sich in Tests als zuverlässig erwiesen.
// Kurze Verzögerung vor dem Auslesen
Thread.sleep(forTimeInterval: 0.3)

// Antwort empfangen
var response = UInt8
var responseLength = CFIndex(90)

let getResult = IOHIDDeviceGetReport(
    controlDevice,
    kIOHIDReportTypeFeature,
    0,
    &response,
    &responseLength
)

if getResult == kIOReturnSuccess {
    print("Empfangene Antwort: \(response)")
}

Antworten interpretieren: Statuscodes verstehen

Die ersten Bytes der Antwort enthalten oft Statuscodes, die Aufschluss über den Erfolg der Operation geben. Bei Razer-Geräten gelten folgende Bedeutungen:

  • `0x00`: Keine Antwort oder veraltete Daten (z. B. wenn zu schnell gelesen wurde).
  • `0x01`: Gerät ist beschäftigt und verarbeitet eine andere Anfrage.
  • `0x02`: Befehl wurde erfolgreich angenommen.
  • `0x05`: Befehl nicht erkannt – oft zurückzuführen auf falsche Report-Bytes oder eine inkompatible Transaktions-ID.

Falls Sie eine vollständig leere Antwort (0x00 in allen Bytes) erhalten, überprüfen Sie:

  • Ob die Verzögerung zwischen Senden und Empfangen ausreichend war.
  • Ob die korrekte Schnittstelle ausgewählt wurde.
  • Ob die Puffergröße korrekt ist.

Ausblick: Razer-Paketformat und CRC-Berechnung

In einem nächsten Schritt geht es um die genaue Struktur der 90-Byte-Reports bei Razer-Geräten. Dazu gehören:

  • Die Positionierung der Transaktions-ID im Paket.
  • Die Berechnung des CRC-Checksums zur Validierung.
  • Die logische Abfolge der Befehle für spezifische Funktionen wie DPI-Anpassung oder RGB-Steuerung.

Mit diesem Wissen können Entwickler eigene Tools erstellen, die unabhängig von proprietären Lösungen wie Razer Synapse funktionieren – und so mehr Kontrolle über ihre Hardware gewinnen.

Die Integration von USB-HID auf macOS über IOKit eröffnet Entwicklern neue Möglichkeiten, ohne auf Kernel-Programmierung angewiesen zu sein. Wer die Herausforderungen wie Multi-Interfaces und Puffergrößen meistert, kann robuste und plattformspezifische Lösungen für die Hardware-Konfiguration schaffen.

KI-Zusammenfassung

Learn to send custom configuration commands to USB HID devices like gaming mice on macOS using IOKit and Swift. Master feature reports and avoid common pitfalls.

Kommentare

00
KOMMENTAR SCHREIBEN
ID #L40IP8

0 / 1200 ZEICHEN

Menschen-Check

7 + 8 = ?

Erscheint nach redaktioneller Prüfung

Moderation · Spam-Schutz aktiv

Noch keine Kommentare. Sei der erste.