Verbindung zu ungewöhnlichen HID-Geräten herstellen

Über die WebHID API können Websites auf alternative Zusatztastaturen und exotische Gamepads zugreifen.

François Beaufort
François Beaufort

Es gibt eine ganze Reihe von Human Interface Devices (HIDs), z. B. alternative Tastaturen oder exotische Gamepads, die zu neu, zu alt oder zu ungewöhnlich sind, sodass sie nicht von den Gerätetreibern des Systems aufgerufen werden können. Die WebHID API löst dieses Problem, indem sie eine Möglichkeit bietet, gerätespezifische Logik in JavaScript zu implementieren.

Empfohlene Anwendungsfälle

Ein HID-Gerät nimmt Eingaben von Menschen an oder stellt Ausgaben für sie bereit. Beispiele für Geräte sind Tastaturen, Zeigegeräte (Mäuse, Touchscreens usw.) und Gamepads. Das HID-Protokoll ermöglicht den Zugriff auf diese Geräte über Desktop-Computer mithilfe von Betriebssystemtreibern. Die Webplattform unterstützt HID-Geräte, indem sie auf diese Treiber zurückgreift.

Die fehlende Möglichkeit, auf ungewöhnliche HID-Geräte zuzugreifen, ist besonders ärgerlich, wenn es um alternative Hilfstastaturen (z.B. Elgato Stream Deck, Jabra-Headsets, X-Keys) und exotische Gamepad-Unterstützung geht. Für Computer konzipierte Gamepads verwenden häufig HID für Gamepad-Eingaben (Tasten, Joysticks, Auslöser) und ‑Ausgaben (LEDs, Rumble). Leider sind die Eingaben und Ausgaben von Gamepads nicht gut standardisiert und Webbrowser erfordern oft benutzerdefinierte Logik für bestimmte Geräte. Das ist nicht nachhaltig und führt zu einer schlechten Unterstützung für die Long Tail-Geräte, also ältere und weniger gängige Geräte. Außerdem ist der Browser von den Eigenheiten im Verhalten bestimmter Geräte abhängig.

Terminologie

HID besteht aus zwei grundlegenden Konzepten: Berichten und Berichtsbeschreibungen. Berichte sind die Daten, die zwischen einem Gerät und einem Softwareclient ausgetauscht werden. Der Berichtsdeskriptor beschreibt das Format und die Bedeutung der Daten, die vom Gerät unterstützt werden.

Ein HID (Human Interface Device) ist ein Gerät, das Eingaben von Menschen entgegennimmt oder ihnen eine Ausgabe liefert. Es bezieht sich auch auf das HID-Protokoll, einen Standard für die bidirektionale Kommunikation zwischen einem Host und einem Gerät, der die Installation vereinfachen soll. Das HID-Protokoll wurde ursprünglich für USB-Geräte entwickelt, wurde aber inzwischen über viele andere Protokolle implementiert, einschließlich Bluetooth.

Anwendungen und HID-Geräte tauschen Binärdaten über drei Berichtstypen aus:

Berichtstyp Beschreibung
Eingabebericht Daten, die vom Gerät an die Anwendung gesendet werden (z. B. wenn eine Schaltfläche gedrückt wird)
Ausgabebericht Daten, die von der Anwendung an das Gerät gesendet werden (z. B. eine Anfrage zum Einschalten der Tastaturbeleuchtung)
Bericht zu Funktionen Daten, die in beide Richtungen gesendet werden können. Das Format ist gerätespezifisch.

Ein Berichtsdeskriptor beschreibt das Binärformat von Berichten, das vom Gerät unterstützt wird. Die Struktur ist hierarchisch und Berichte können als separate Sammlungen innerhalb der übergeordneten Sammlung gruppiert werden. Das Format des Deskriptors wird durch die HID-Spezifikation definiert.

Die HID-Nutzung ist ein numerischer Wert, der sich auf eine standardisierte Eingabe oder Ausgabe bezieht. Mithilfe von Nutzungswerten kann ein Gerät die beabsichtigte Verwendung des Geräts und den Zweck jedes Felds in seinen Berichten beschreiben. Beispielsweise ist eine für die linke Taste einer Maus definiert. Die Nutzungen sind auch in Seiten zur Nutzung unterteilt, die eine Angabe zur übergeordneten Kategorie des Geräts oder Berichts enthalten.

WebHID API verwenden

Funktionserkennung

So prüfen Sie, ob die WebHID API unterstützt wird:

if ("hid" in navigator) {
  // The WebHID API is supported.
}

HID-Verbindung öffnen

Die WebHID API ist standardmäßig asynchron, um zu verhindern, dass die Benutzeroberfläche der Website blockiert wird, wenn eine Eingabe erwartet wird. Das ist wichtig, da HID-Daten jederzeit empfangen werden können und daher eine Möglichkeit zum Abhören erforderlich ist.

Wenn Sie eine HID-Verbindung öffnen möchten, greifen Sie zuerst auf ein HIDDevice-Objekt zu. Dazu kannst du den Nutzer entweder auffordern, ein Gerät auszuwählen, indem du navigator.hid.requestDevice() aufrufst, oder eines über navigator.hid.getDevices() auswählen. Letzteres gibt eine Liste der Geräte zurück, auf die der Website bereits Zugriff gewährt wurde.

Die Funktion navigator.hid.requestDevice() verwendet ein obligatorisches Objekt, das Filter definiert. Diese dienen zum Abgleich jedes Geräts, das mit einer USB-Anbieter-ID (vendorId), einer USB-Produktkennzeichnung (productId), dem Wert der Nutzungsseite (usagePage) und einem Nutzungswert (usage) verbunden ist. Sie finden diese im USB ID Repository und im Dokument mit HID-Nutzungstabellen.

Die von dieser Funktion zurückgegebenen HIDDevice-Objekte repräsentieren mehrere HID-Schnittstellen auf demselben physischen Gerät.

// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
  {
    vendorId: 0x057e, // Nintendo Co., Ltd
    productId: 0x2006 // Joy-Con Left
  },
  {
    vendorId: 0x057e, // Nintendo Co., Ltd
    productId: 0x2007 // Joy-Con Right
  }
];

// Prompt user to select a Joy-Con device.
const [device] = await navigator.hid.requestDevice({ filters });
// Get all devices the user has previously granted the website access to.
const devices = await navigator.hid.getDevices();
Screenshot einer Aufforderung für ein HID-Gerät auf einer Website
Benutzeraufforderung zur Auswahl einer Nintendo Switch Joy-Con.

Sie können auch die optionale Taste exclusionFilters in navigator.hid.requestDevice() verwenden, um bestimmte Geräte aus der Browserauswahl auszuschließen, die beispielsweise bekanntlich nicht ordnungsgemäß funktionieren.

// Request access to a device with vendor ID 0xABCD. The device must also have
// a collection with usage page Consumer (0x000C) and usage ID Consumer
// Control (0x0001). The device with product ID 0x1234 is malfunctioning.
const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0xabcd, usagePage: 0x000c, usage: 0x0001 }],
  exclusionFilters: [{ vendorId: 0xabcd, productId: 0x1234 }],
});

Ein HIDDevice-Objekt enthält IDs von USB-Anbietern und -Produkten zur Geräteidentifikation. Das collections-Attribut wird mit einer hierarchischen Beschreibung der Berichtsformate des Geräts initialisiert.

for (let collection of device.collections) {
  // An HID collection includes usage, usage page, reports, and subcollections.
  console.log(`Usage: ${collection.usage}`);
  console.log(`Usage page: ${collection.usagePage}`);

  for (let inputReport of collection.inputReports) {
    console.log(`Input report: ${inputReport.reportId}`);
    // Loop through inputReport.items
  }

  for (let outputReport of collection.outputReports) {
    console.log(`Output report: ${outputReport.reportId}`);
    // Loop through outputReport.items
  }

  for (let featureReport of collection.featureReports) {
    console.log(`Feature report: ${featureReport.reportId}`);
    // Loop through featureReport.items
  }

  // Loop through subcollections with collection.children
}

Die HIDDevice-Geräte werden standardmäßig im Status „geschlossen“ zurückgegeben und müssen durch Aufrufen von open() geöffnet werden, bevor Daten gesendet oder empfangen werden können.

// Wait for the HID connection to open before sending/receiving data.
await device.open();

Eingabeberichte erhalten

Sobald die HID-Verbindung hergestellt wurde, kannst du eingehende Eingabeberichte verarbeiten, indem du die "inputreport"-Ereignisse vom Gerät abwartest. Diese Ereignisse enthalten die HID-Daten als DataView-Objekt (data), das zugehörige HID-Gerät (device) und die 8‑Bit-Berichts-ID, die mit dem Eingabebericht verknüpft ist (reportId).

Foto einer roten und einer blauen Nintendo Switch.
Nintendo Switch Joy-Con-Geräte

Um auf das vorherige Beispiel zurückzukommen: Der folgende Code zeigt dir, wie du erkennen kannst, welche Taste der Nutzer auf einem Joy-Con Right-Gerät gedrückt hat, damit du es hoffentlich zu Hause ausprobieren kannst.

device.addEventListener("inputreport", event => {
  const { data, device, reportId } = event;

  // Handle only the Joy-Con Right device and a specific report ID.
  if (device.productId !== 0x2007 && reportId !== 0x3f) return;

  const value = data.getUint8(0);
  if (value === 0) return;

  const someButtons = { 1: "A", 2: "X", 4: "B", 8: "Y" };
  console.log(`User pressed button ${someButtons[value]}.`);
});

Ausgabeberichte senden

Wenn Sie einen Ausgabebericht an ein HID-Gerät senden möchten, übergeben Sie die 8‑Bit-Berichts-ID, die mit dem Ausgabebericht verknüpft ist (reportId), und die Bytes als BufferSource (data) an device.sendReport(). Das zurückgegebene Promise wird aufgelöst, sobald der Bericht gesendet wurde. Wenn das HID-Gerät keine Berichts-IDs verwendet, setzen Sie reportId auf 0.

Im folgenden Beispiel wird gezeigt, wie Sie mithilfe von Ausgabeberichten ein Joy-Con-Gerät zum Vibrieren bringen.

// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));

// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));

Funktionsberichte senden und empfangen

Funktionsberichte sind die einzige Art von HID-Datenberichten, die in beide Richtungen übertragen werden können. Sie ermöglichen den Austausch nicht standardisierter HID-Daten zwischen HID-Geräten und ‑Anwendungen. Im Gegensatz zu Eingabe- und Ausgabeberichten werden Funktionsberichte nicht regelmäßig von der Anwendung empfangen oder gesendet.

Schwarz-silbernes Laptopfoto.
Laptoptastatur

Wenn du einen Funktionsbericht an ein HID-Gerät senden möchtest, übergib die mit dem Funktionsbericht verknüpfte 8-Bit-Berichts-ID (reportId) und die Byte als BufferSource (data) an device.sendFeatureReport(). Das zurückgegebene Promise wird aufgelöst, sobald der Bericht gesendet wurde. Wenn das HID-Gerät keine Berichts-IDs verwendet, setzen Sie reportId auf 0.

Im folgenden Beispiel wird die Verwendung von Funktionsberichten veranschaulicht. Sie erfahren, wie Sie eine Apple-Tastatur mit Hintergrundbeleuchtung anfordern, öffnen und zum Blinken bringen.

const waitFor = duration => new Promise(r => setTimeout(r, duration));

// Prompt user to select an Apple Keyboard Backlight device.
const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
});

// Wait for the HID connection to open.
await device.open();

// Blink!
const reportId = 1;
for (let i = 0; i < 10; i++) {
  // Turn off
  await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
  await waitFor(100);
  // Turn on
  await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
  await waitFor(100);
}

Wenn du einen Funktionsbericht von einem HID-Gerät erhalten möchtest, übergib die mit dem Funktionsbericht verknüpfte 8-Bit-Berichts-ID (reportId) an device.receiveFeatureReport(). Das zurückgegebene Versprechen wird mit einem DataView-Objekt aufgelöst, das den Inhalt des Feature-Berichts enthält. Wenn das HID-Gerät keine Berichts-IDs verwendet, setze reportId auf 0.

// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);

// Read feature report contents with dataView.getInt8(), getUint8(), etc...

Informationen zu Verbindungs- und Verbindungsabbrüchen anhören

Wenn der Website die Berechtigung zum Zugriff auf ein HID-Gerät gewährt wurde, kann sie Verbindungs- und Trennungsereignisse aktiv empfangen, indem sie auf "connect"- und "disconnect"-Ereignisse achtet.

navigator.hid.addEventListener("connect", event => {
  // Automatically open event.device or warn user a device is available.
});

navigator.hid.addEventListener("disconnect", event => {
  // Remove |event.device| from the UI.
});

Zugriff auf ein HID-Gerät widerrufen

Die Website kann Berechtigungen für den Zugriff auf ein HID-Gerät bereinigen, das nicht mehr benötigt wird, indem forget() auf die HIDDevice-Instanz aufgerufen wird. Bei einer Bildungs-Webanwendung, die auf einem gemeinsam genutzten Computer mit vielen Geräten verwendet wird, führt eine große Anzahl von nutzergenerierten Berechtigungen zu einer schlechten Nutzererfahrung.

Wenn forget() auf einer einzelnen HIDDevice-Instanz aufgerufen wird, wird der Zugriff auf alle HID-Schnittstellen auf demselben physischen Gerät widerrufen.

// Voluntarily revoke access to this HID device.
await device.forget();

Da forget() in Chrome 100 oder höher verfügbar ist, prüfen Sie, ob diese Funktion mit den folgenden Elementen unterstützt wird:

if ("hid" in navigator && "forget" in HIDDevice.prototype) {
  // forget() is supported.
}

Tipps für Entwickler

Das Entfernen von Fehlern bei HID in Chrome ist mit der internen Seite about://device-log ganz einfach. Dort finden Sie alle HID- und USB-Gerätereignisse an einem Ort.

Screenshot der internen Seite zum Debuggen von HID
Interne Seite in Chrome zur Fehlerbehebung bei HID.

Mit dem HID Explorer können Sie HID-Geräteinformationen in ein visuell lesbares Format umwandeln. Es werden Nutzungswerte für jede HID-Nutzung in Namen umgewandelt.

Auf den meisten Linux-Systemen werden HID-Geräte standardmäßig mit Lesezugriffsberechtigungen zugeordnet. Damit Chrome ein HID-Gerät öffnen kann, müssen Sie eine neue udev-Regel hinzufügen. Erstellen Sie unter /etc/udev/rules.d/50-yourdevicename.rules eine Datei mit folgendem Inhalt:

KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

In der Zeile oben ist [yourdevicevendor] 057e, wenn es sich bei Ihrem Gerät beispielsweise um eine Nintendo Switch Joy-Con handelt. Für eine spezifischere Regel kann auch ATTRS{idProduct} hinzugefügt werden. Ihr user muss Mitglied der Gruppe plugdev sein. Verbinden Sie dann einfach Ihr Gerät neu.

Unterstützte Browser

Die WebHID API ist in Chrome 89 auf allen Desktop-Plattformen (ChromeOS, Linux, macOS und Windows) verfügbar.

Demos

Einige WebHID-Demos finden Sie unter web.dev/hid-examples. Schau mal hier!

Sicherheit und Datenschutz

Die Spezifikationsautoren haben die WebHID API gemäß den unter Steuerung des Zugriffs auf leistungsstarke Webplattform-Funktionen definierten Kernprinzipien wie Nutzersteuerung, Transparenz und Ergonomie entwickelt und implementiert. Die Verwendung dieser API wird hauptsächlich durch ein Berechtigungsmodell eingeschränkt, das nur Zugriff auf ein einzelnes HID-Gerät gleichzeitig gewährt. Als Reaktion auf eine Nutzeraufforderung muss der Nutzer aktive Schritte ausführen, um ein bestimmtes HID-Gerät auszuwählen.

Informationen zu den Sicherheitsabwägungen finden Sie in der WebHID-Spezifikation im Abschnitt Sicherheits- und Datenschutzaspekte.

Außerdem prüft Chrome die Verwendung jeder Sammlung der obersten Ebene. Wenn eine Sammlung der obersten Ebene eine geschützte Verwendung hat (z. B. eine generische Tastatur oder Maus), kann eine Website keine in dieser Sammlung definierten Berichte senden und empfangen. Eine vollständige Liste der geschützten Verwendungen ist öffentlich verfügbar.

Sicherheitsrelevante HID-Geräte, z. B. FIDO-HID-Geräte, die für eine stärkere Authentifizierung verwendet werden, werden in Chrome ebenfalls blockiert. Weitere Informationen finden Sie in den Dateien USB-Sperrliste und HID-Sperrliste.

Feedback

Das Chrome-Team würde sich über Ihre Meinung und Erfahrungen mit der WebHID API freuen.

Informationen zum API-Design

Funktioniert die API nicht wie erwartet? Oder fehlen Methoden oder Eigenschaften, die Sie für die Implementierung Ihrer Idee benötigen?

Melden Sie ein Problem mit der Spezifikation im GitHub-Repository der WebHID API oder fügen Sie Ihre Anmerkungen zu einem vorhandenen Problem hinzu.

Problem mit der Implementierung melden

Haben Sie einen Fehler in der Chrome-Implementierung gefunden? Oder unterscheidet sich die Implementierung von der Spezifikation?

Weitere Informationen finden Sie unter WebHID-Fehler melden. Machen Sie dabei möglichst präzise Angaben, geben Sie eine einfache Anleitung zum Reproduzieren des Fehlers an und legen Sie Blink>HID als Komponenten fest. Glitch eignet sich hervorragend, um schnell und einfach Reproduktionen zu teilen.

Unterstützung zeigen

Möchten Sie die WebHID API verwenden? Ihre öffentliche Unterstützung hilft dem Chrome-Team, Funktionen zu priorisieren, und zeigt anderen Browseranbietern, wie wichtig es ist, diese zu unterstützen.

Senden Sie einen Tweet an @ChromiumDev mit dem Hashtag #WebHID und teilen Sie uns mit, wo und wie Sie ihn verwenden.

Nützliche Links

Danksagungen

Vielen Dank an Matt Reynolds und Joe Medley für die Überprüfung dieses Artikels. Foto der roten und blauen Nintendo Switch von Sara Kurfeß und Foto eines schwarzen und silbernen Laptops von Athul Cyriac Ajay auf Unsplash.