Connexion à des périphériques HID peu courants

L'API WebHID permet aux sites Web d'accéder à d'autres claviers auxiliaires et à des manettes de jeu exotiques.

François Beaufort
François Beaufort

Il existe une longue traîne de périphériques d'interface humaine (HID), tels que des claviers alternatifs ou des manettes de jeu exotiques, qui sont trop récents, trop anciens ou trop inhabituels pour être accessibles par les pilotes d'appareils des systèmes. L'API WebHID résout ce problème en fournissant un moyen d'implémenter une logique propre à l'appareil en JavaScript.

Suggestions de cas d'utilisation

Un appareil HID reçoit des entrées ou fournit des sorties à des utilisateurs. Les claviers, les dispositifs de pointage (souris, écrans tactiles, etc.) et les manettes de jeu sont des exemples d'appareils. Le protocole HID permet d'accéder à ces appareils sur les ordinateurs de bureau à l'aide de pilotes de système d'exploitation. La plate-forme Web est compatible avec les appareils HID en s'appuyant sur ces pilotes.

L'impossibilité d'accéder aux périphériques HID inhabituels est particulièrement pénible lorsqu'il s'agit d'autres claviers auxiliaires (par exemple, Elgato Stream Deck, casques Jabra, X-keys) et de la prise en charge de manettes de jeu exotiques. Les manettes de jeu conçues pour les ordinateurs de bureau utilisent souvent HID pour les entrées (boutons, joysticks, déclencheurs) et les sorties (voyants, retour haptique) de la manette. Malheureusement, les entrées et sorties des manettes de jeu ne sont pas bien standardisées, et les navigateurs Web nécessitent souvent une logique personnalisée pour des appareils spécifiques. Cette situation n'est pas durable et entraîne une mauvaise compatibilité avec la longue traîne des appareils plus anciens et inhabituels. Le navigateur dépend également des particularités du comportement d'appareils spécifiques.

Terminologie

HID se compose de deux concepts fondamentaux: les rapports et les descripteurs de rapports. Les rapports désignent les données échangées entre un appareil et un client logiciel. Le descripteur de rapport décrit le format et la signification des données compatibles avec l'appareil.

Un appareil HID (Human Interface Device) est un type d'appareil qui reçoit des entrées ou fournit des sorties à des humains. Il fait également référence au protocole HID, une norme de communication bidirectionnelle entre un hôte et un appareil conçue pour simplifier la procédure d'installation. Le protocole HID a été développé à l'origine pour les appareils USB, mais il a depuis été implémenté sur de nombreux autres protocoles, y compris Bluetooth.

Les applications et les appareils HID échangent des données binaires via trois types de rapports:

Type de rapport Description
Rapport sur les entrées Données envoyées de l'appareil à l'application (par exemple, un bouton est enfoncé).
Rapport sur les résultats Données envoyées de l'application à l'appareil (par exemple, une requête pour allumer le rétroéclairage du clavier).
Rapport sur les fonctionnalités Données pouvant être envoyées dans les deux sens. Le format est spécifique à l'appareil.

Un descripteur de rapport décrit le format binaire des rapports compatibles avec l'appareil. Sa structure est hiérarchique et peut regrouper des rapports en collections distinctes au sein de la collection de niveau supérieur. Le format du descripteur est défini par la spécification HID.

L'utilisation d'un HID est une valeur numérique faisant référence à une entrée ou à une sortie standardisées. Les valeurs d'utilisation permettent à un appareil de décrire l'utilisation prévue de l'appareil et l'objectif de chaque champ dans ses rapports. Par exemple, un est défini pour le bouton gauche d'une souris. Les utilisations sont également organisées en pages d'utilisation, qui indiquent la catégorie de haut niveau de l'appareil ou du rapport.

Utiliser l'API WebHID

Détection de fonctionnalités

Pour vérifier si l'API WebHID est prise en charge, utilisez:

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

Ouvrir une connexion HID

L'API WebHID est asynchrone par conception pour éviter que l'interface utilisateur du site Web ne se bloque en attente d'entrée. Ceci est important, car les données HID peuvent être reçues à tout moment, ce qui nécessite un moyen de les écouter.

Pour ouvrir une connexion HID, accédez d'abord à un objet HIDDevice. Pour ce faire, vous pouvez demander à l'utilisateur de sélectionner un appareil en appelant navigator.hid.requestDevice() ou en en sélectionnant un dans navigator.hid.getDevices(), qui renvoie une liste des appareils auxquels le site Web a déjà été autorisé à accéder.

La fonction navigator.hid.requestDevice() utilise un objet obligatoire qui définit des filtres. Ils permettent de faire correspondre n'importe quel appareil connecté à un identifiant de fournisseur USB (vendorId), un identifiant de produit USB (productId), une valeur de page d'utilisation (usagePage) et une valeur d'utilisation (usage). Vous pouvez les obtenir à partir du répertoire d'ID USB et du document sur les tables d'utilisation des appareils HID.

Les multiples objets HIDDevice renvoyés par cette fonction représentent plusieurs interfaces HID sur le même appareil physique.

// 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();
Capture d'écran d'une invite concernant un appareil HID sur un site Web.
Invite à l'utilisateur de sélectionner un Joy-Con Nintendo Switch.

Vous pouvez également utiliser la clé facultative exclusionFilters dans navigator.hid.requestDevice() pour exclure du sélecteur de navigateur certains appareils connus pour fonctionner en panne, par exemple.

// 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 }],
});

Un objet HIDDevice contient des identifiants de fournisseur et de produit USB pour l'identification de l'appareil. Son attribut collections est initialisé avec une description hiérarchique des formats de rapport de l'appareil.

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
}

Par défaut, les appareils HIDDevice sont renvoyés dans un état "fermé" et doivent être ouverts en appelant open() avant que des données puissent être envoyées ou reçues.

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

Recevoir des rapports sur les entrées

Une fois la connexion HID établie, vous pouvez gérer les rapports d'entrée entrants en écoutant les événements "inputreport" de l'appareil. Ces événements contiennent les données HID sous la forme d'un objet DataView (data), de l'appareil HID auquel il appartient (device) et de l'ID de rapport 8 bits associé au rapport d'entrée (reportId).

Photo de la Nintendo Switch rouge et bleue.
Nintendo Switch Joy-Con

En reprenant l'exemple précédent, le code ci-dessous vous montre comment détecter le bouton sur lequel l'utilisateur a appuyé sur un appareil Joy-Con de droite, afin de pouvoir l'essayer chez vous.

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]}.`);
});

Envoyer des rapports de sortie

Pour envoyer un rapport de sortie à un appareil HID, transmettez l'ID de rapport 8 bits associé au rapport de sortie (reportId) et les octets en tant que BufferSource (data) à device.sendReport(). La promesse renvoyée est résolue une fois le rapport envoyé. Si l'appareil HID n'utilise pas d'ID de rapport, définissez reportId sur 0.

L'exemple ci-dessous s'applique à un appareil Joy-Con et vous montre comment le faire vibrer à l'aide de rapports de sortie.

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

Envoyer et recevoir des rapports sur les fonctionnalités

Les rapports sur les éléments géographiques sont le seul type de rapports sur les données HID pouvant circuler dans les deux sens. Ils permettent aux appareils et applications HID d'échanger des données HID non standardisées. Contrairement aux rapports d'entrée et de sortie, les rapports sur les fonctionnalités ne sont pas reçus ni envoyés régulièrement par l'application.

Photo d'un ordinateur portable noir et argenté.
Clavier d'ordinateur portable

Pour envoyer un rapport de fonctionnalité à un appareil HID, transmettez l'ID de rapport 8 bits associé au rapport de fonctionnalité (reportId) et les octets en tant que BufferSource (data) à device.sendFeatureReport(). La promesse renvoyée est résolue une fois le rapport envoyé. Si l'appareil HID n'utilise pas d'ID de rapport, définissez reportId sur 0.

L'exemple ci-dessous illustre l'utilisation des rapports sur les fonctionnalités en vous montrant comment demander un appareil de rétroéclairage de clavier Apple, l'ouvrir et le faire clignoter.

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

Pour recevoir un rapport de fonctionnalités à partir d'un appareil HID, transmettez l'ID de rapport 8 bits associé au rapport de fonctionnalités (reportId) à device.receiveFeatureReport(). La promesse renvoyée se résout avec un objet DataView contenant le contenu du rapport sur les éléments géographiques. Si l'appareil HID n'utilise pas d'ID de rapport, définissez reportId sur 0.

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

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

Écouter la connexion et la déconnexion

Lorsque le site Web est autorisé à accéder à un appareil HID, il peut recevoir activement des événements de connexion et de déconnexion en écoutant les événements "connect" et "disconnect".

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.
});

Révoquer l'accès à un appareil HID

Le site Web peut nettoyer les autorisations d'accès à un appareil HID qu'il ne souhaite plus conserver en appelant forget() sur l'instance HIDDevice. Par exemple, pour une application Web éducative utilisée sur un ordinateur partagé avec de nombreux appareils, un grand nombre d'autorisations accumulées par l'utilisateur nuit à l'expérience utilisateur.

L'appel de forget() sur une seule instance HIDDevice révoque l'accès à toutes les interfaces HID du même appareil physique.

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

Comme forget() est disponible dans Chrome 100 ou version ultérieure, vérifiez si cette fonctionnalité est compatible avec les éléments suivants:

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

Conseils pour les développeurs

Le débogage des appareils HID dans Chrome est facile grâce à la page interne about://device-log, où vous pouvez consulter tous les événements liés aux appareils HID et USB au même endroit.

Capture d&#39;écran de la page interne permettant de déboguer HID.
Page interne de Chrome pour déboguer les périphériques HID.

Consultez l'explorateur HID pour extraire des informations sur les appareils HID dans un format lisible par l'humain. Il fait correspondre les valeurs d'utilisation aux noms pour chaque utilisation d'HID.

Sur la plupart des systèmes Linux, les périphériques HID sont mappés avec des autorisations de lecture seule par défaut. Pour autoriser Chrome à ouvrir un appareil HID, vous devez ajouter une règle udev. Créez un fichier à l'emplacement /etc/udev/rules.d/50-yourdevicename.rules avec le contenu suivant:

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

Dans la ligne ci-dessus, [yourdevicevendor] est 057e si votre appareil est un Joy-Con de Nintendo Switch, par exemple. Vous pouvez également ajouter ATTRS{idProduct} pour une règle plus spécifique. Assurez-vous que votre user est membre du groupe plugdev. Ensuite, reconnectez votre appareil.

Prise en charge des navigateurs

L'API WebHID est disponible sur toutes les plates-formes de bureau (ChromeOS, Linux, macOS et Windows) dans Chrome 89.

Démonstrations

Vous trouverez des démonstrations WebHID sur web.dev/hid-examples. Allez y jeter un œil !

Sécurité et confidentialité

Les auteurs de la spécification ont conçu et implémenté l'API WebHID en utilisant les principes fondamentaux définis dans Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web, y compris le contrôle utilisateur, la transparence et l'ergonomie. La possibilité d'utiliser cette API est principalement limitée par un modèle d'autorisation qui n'accorde l'accès qu'à un seul appareil HID à la fois. En réponse à une invite de l'utilisateur, celui-ci doit prendre des mesures actives pour sélectionner un appareil HID particulier.

Pour comprendre les compromis de sécurité, consultez la section Considérations sur la sécurité et la confidentialité de la spécification WebHID.

De plus, Chrome inspecte l'utilisation de chaque collection de niveau supérieur. Si une collection de niveau supérieur a une utilisation protégée (par exemple, un clavier ou une souris génériques), un site Web ne pourra pas envoyer ni recevoir de rapports définis dans cette collection. La liste complète des utilisations protégées est accessible au public.

Notez que les appareils HID sensibles à la sécurité (tels que les appareils HID FIDO utilisés pour une authentification plus forte) sont également bloqués dans Chrome. Consultez les fichiers Liste de blocage USB et Liste de blocage HID.

Commentaires

L'équipe Chrome aimerait connaître votre avis et votre expérience avec l'API WebHID.

Parlez-nous de la conception de l'API

Y a-t-il un aspect de l'API qui ne fonctionne pas comme prévu ? Ou manque-t-il des méthodes ou des propriétés dont vous avez besoin pour implémenter votre idée ?

Signalez un problème de spécification dans le dépôt GitHub de l'API WebHID ou ajoutez vos commentaires à un problème existant.

Signaler un problème d'implémentation

Avez-vous détecté un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente de la spécification ?

Consultez Signaler des bugs WebHID. Veillez à inclure autant de détails que possible, à fournir des instructions simples pour reproduire le bug et à définir Composants sur Blink>HID. Glitch est idéal pour partager des répétitions rapidement et facilement.

Montrer votre soutien

Comptez-vous utiliser l'API WebHID ? Votre soutien public aide l'équipe Chrome à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge.

Envoyez un tweet à @ChromiumDev en utilisant le hashtag #WebHID et indiquez-nous où et comment vous l'utilisez.

Liens utiles

Remerciements

Merci à Matt Reynolds et Joe Medley pour leurs commentaires sur cet article. Photo d'une Nintendo Switch rouge et bleue par Sara Kurfeß, et photo d'un ordinateur portable noir et argenté par Athul Cyriac Ajay sur Unsplash.