Verbindung zu ungewöhnlichen HID-Geräten herstellen

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

François Beaufort
François Beaufort

Es gibt viele verschiedene Human Interface Devices (HIDs) wie alternative Tastaturen oder exotische Gamepads, die zu neu, zu alt oder zu ungewöhnlich sind, um für die Gerätetreiber des Systems nicht zugänglich zu sein. 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 liefert sie an Menschen aus. Beispiele hierfür sind Tastaturen, Zeigegeräte (Mäuse, Touchscreens usw.) und Gamepads. Das HID-Protokoll ermöglicht den Zugriff auf diese Geräte über Desktopcomputer mithilfe von Betriebssystemtreibern. Die Webplattform unterstützt HID-Geräte, indem sie diese Treiber nutzt.

Besonders schwierig ist es, wenn auf ungewöhnliche HID-Geräte nicht zugegriffen werden kann, wenn alternative Hilfstastaturen (z.B. Elgato Stream Deck, Jabra-Headsets, X-keys) und exotische Gamepads unterstützt werden. Gamepads, die für Desktop-Computer entwickelt wurden, verwenden häufig HID für Gamepad-Eingaben (Tasten, Joysticks, Trigger) und Ausgaben (LEDs, Rumble). Leider sind die Ein- und Ausgaben des Gamepads nicht gut standardisiert und Webbrowser erfordern oft eine benutzerdefinierte Logik für bestimmte Geräte. Dies ist nicht rentabel und führt zu einer schlechten Unterstützung für die Longtail-Version älterer und ungewöhnlicher Geräte. Dies führt auch dazu, dass der Browser von Eigenverhalten im Verhalten bestimmter Geräte abhängig ist.

Terminologie

HID besteht aus zwei grundlegenden Konzepten: Berichten und Berichtsdeskriptoren. 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ätetyp, der Eingaben von Menschen entgegennimmt oder ihnen 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 den Installationsvorgang vereinfacht. Das HID-Protokoll wurde ursprünglich für USB-Geräte entwickelt, wird inzwischen aber über viele andere Protokolle, einschließlich Bluetooth, implementiert.

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

Berichtstyp Beschreibung
Eingabebericht Daten, die vom Gerät an die App gesendet werden (z. B. wenn eine Taste gedrückt wird)
Ausgabebericht Daten, die von der Anwendung an das Gerät gesendet werden (z.B. eine Anfrage zum Einschalten der Tastaturbeleuchtung).
Funktionsbericht 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. Sie ist hierarchisch aufgebaut und kann Berichte in der übergeordneten Sammlung als separate Sammlungen gruppieren. Das Format des Deskriptors wird durch die HID-Spezifikation definiert.

Eine HID-Nutzung ist ein numerischer Wert, der sich auf eine standardisierte Eingabe oder Ausgabe bezieht. Mithilfe von Nutzungswerten können die beabsichtigte Verwendung des Geräts und der Zweck der einzelnen Felder in seinen Berichten beschrieben werden. Beispielsweise wird eine für die linke Taste der Maus definiert. Die Nutzungen werden auch auf Nutzungsseiten organisiert, auf denen die allgemeine Kategorie des Geräts oder Berichts angegeben ist.

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 herstellen

Die WebHID API ist asynchron, um zu verhindern, dass die Website-UI blockiert wird, wenn sie auf Eingaben wartet. Das ist wichtig, da HID-Daten jederzeit empfangen werden können und 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 durch Aufrufen von navigator.hid.requestDevice() auszuwählen, oder ein Gerät aus navigator.hid.getDevices() auswählen. Daraufhin wird eine Liste der Geräte zurückgegeben, auf die die Website bereits Zugriff gewährt hat.

Für die Funktion navigator.hid.requestDevice() wird ein obligatorisches Objekt verwendet, das Filter definiert. Sie werden verwendet, um alle Geräte abzugleichen, die mit einer USB-Anbieter-ID (vendorId), einer USB-Produkt-ID (productId), einem Wert für die Nutzungsseite (usagePage) und einem Nutzungswert (usage) verbunden sind. Sie finden diese aus dem USB-ID-Repository und dem Dokument mit HID-Nutzungstabellen.

Die verschiedenen HIDDevice-Objekte, die von dieser Funktion zurückgegeben werden, stellen mehrere HID-Schnittstellen auf demselben physischen Gerät dar.

// 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 HID-Geräteaufforderung auf einer Website.
Nutzeraufforderung zur Auswahl einer Nintendo Switch Joy-Con

Sie können auch den optionalen Schlüssel exclusionFilters in navigator.hid.requestDevice() verwenden, um einige Geräte aus der Browserauswahl auszuschließen, bei denen bekannt ist, dass sie z. B. nicht richtig 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 USB-Anbieter- und Produktkennzeichnungen für die Geräteidentifikation. Das Attribut collections 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, können Sie eingehende Eingabeberichte verarbeiten, indem Sie die "inputreport"-Ereignisse des Geräts beobachten. Diese Ereignisse enthalten die HID-Daten als DataView-Objekt (data), das HID-Gerät, zu dem es gehört (device), und die mit dem Eingabebericht verknüpfte 8-Bit-Berichts-ID (reportId).

Roter und blauer Nintendo Switch Foto.
Nintendo Switch Joy-Con-Geräte

Wenn wir mit dem vorherigen Beispiel fortfahren, zeigt der folgende Code, 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 mit dem Ausgabebericht verknüpfte 8-Bit-Berichts-ID (reportId) und die zugehörigen Byte 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, setze reportId auf „0“.

Das folgende Beispiel bezieht sich auf ein Joy-Con-Gerät und zeigt Ihnen, wie Sie es mit Ausgabeberichten durcheinanderbringen können.

// 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

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

Schwarz-silbernes Foto auf einem Laptop-Computer
Laptoptastatur

Übergeben Sie zum Senden eines Funktionsberichts an ein HID-Gerät 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, setze reportId auf „0“.

Im folgenden Beispiel wird gezeigt, wie Sie Funktionsberichte verwenden, indem Sie eine Apple-Tastatur mit Hintergrundbeleuchtung anfordern, öffnen und blinken lassen.

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 Sie einen Funktionsbericht von einem HID-Gerät erhalten möchten, müssen Sie die mit dem Funktionsbericht verknüpfte 8-Bit-Berichts-ID (reportId) an device.receiveFeatureReport() übergeben. Das zurückgegebene Promise wird mit einem DataView-Objekt aufgelöst, das den Inhalt des Featureberichts enthält. Wenn das HID-Gerät keine Berichts-IDs verwendet, setzen Sie reportId auf 0.

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

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

Hören, wenn Verbindung unterbrochen wird oder getrennt wird

Wenn der Website die Berechtigung für den Zugriff auf ein HID-Gerät gewährt wurde, kann sie aktiv Verbindungs- und Trennereignisse empfangen, indem sie "connect"- und "disconnect"-Ereignisse überwacht.

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, das nicht mehr beibehalten werden soll, durch Aufrufen von forget() auf der Instanz HIDDevice bereinigen. Bei einer Webanwendung für Bildungseinrichtungen, die auf einem gemeinsam genutzten Computer mit vielen Geräten verwendet wird, beeinträchtigt eine große Anzahl von nutzergenerierten Berechtigungen die Nutzerfreundlichkeit.

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();

Wenn forget() in Chrome 100 oder höher verfügbar ist, prüfen Sie folgendermaßen, ob diese Funktion unterstützt wird:

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

Entwicklertipps

Das Debugging von HID in Chrome ist auf der internen Seite about://device-log ganz einfach. Hier kannst du alle HID- und USB-Geräteereignisse an einem zentralen Ort sehen.

Screenshot der internen Seite für die HID-Fehlerbehebung.
Interne Seite in Chrome zum Debuggen von HID.

Im HID Explorer können Sie HID-Geräteinformationen in ein visuell lesbares Format speichern. Für jede HID-Nutzung werden Nutzungswerte den Namen zugeordnet.

Auf den meisten Linux-Systemen erhalten HID-Geräte standardmäßig schreibgeschützte Berechtigungen. 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 beispielsweise um eine Nintendo Switch Joy-Con handelt. ATTRS{idProduct} kann auch für eine spezifischere Regel hinzugefügt werden. Achte darauf, dass dein user ein Mitglied der Gruppe plugdev ist. Verbinde dein Gerät dann einfach wieder.

Unterstützte Browser

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

Demos

Einige WebHID-Demos sind unter web.dev/hid-examples aufgeführt. Sieh sie dir an!

Sicherheit und Datenschutz

Die Spezifikationsautoren haben die WebHID API nach den unter Zugriff auf leistungsstarke Webplattformfunktionen steuern definierten Kernprinzipien wie Nutzersteuerung, Transparenz und Ergonomie entworfen und implementiert. Diese API wird hauptsächlich durch ein Berechtigungsmodell gesteuert, das jeweils nur einem einzigen HID-Gerät Zugriff gewährt. Auf eine Nutzeraufforderung muss der Nutzer aktive Schritte unternehmen, um ein bestimmtes HID-Gerät auszuwählen.

Informationen zu den Vor- und Nachteilen der Sicherheit findest du im Abschnitt Überlegungen zu Sicherheit und Datenschutz der WebHID-Spezifikation.

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

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

Feedback

Das Chrome-Team freut sich über Ihr Feedback zur WebHID API.

Informationen zum API-Design

Gibt es etwas an der API, das nicht wie erwartet funktioniert? Oder fehlen Methoden oder Eigenschaften, die Sie zur Implementierung Ihrer Idee benötigen?

Melden Sie ein Spezifikationsproblem im WebHID API-GitHub-Repository oder fügen Sie Ihre Gedanken zu einem vorhandenen Problem hinzu.

Problem mit der Implementierung melden

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

Weitere Informationen finden Sie unter WebHID-Fehler melden. Geben Sie so viele Details wie möglich an, stellen Sie eine einfache Anleitung zum Reproduzieren des Fehlers bereit und setzen Sie Components (Komponenten) auf Blink>HID. Mit Glitch lassen sich schnelle und einfache Reproduktionen teilen.

Support 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, sie zu unterstützen.

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

Nützliche Links

Danksagungen

Wir danken Matt Reynolds und Joe Medley für die Rezensionen zu diesem Artikel. Rot-blaues Nintendo Switch-Foto von Sara Kurfeß und schwarz-silbernes Laptop-Computerfoto von Athul Cyriac Ajay auf Unsplash.