API WebHID позволяет веб-сайтам получать доступ к альтернативным вспомогательным клавиатурам и экзотическим геймпадам.
Опубликовано: 15 сентября 2020 г.
Существует множество устройств пользовательского интерфейса (HID), таких как альтернативные клавиатуры или экзотические геймпады, которые слишком новые, слишком старые или слишком редкие, чтобы быть доступными для драйверов устройств системы. API WebHID решает эту проблему, предоставляя способ реализации специфичной для устройства логики на JavaScript.
Предлагаемые варианты использования
HID-устройство принимает ввод от человека или выдает ему вывод. Примерами таких устройств являются клавиатуры, указывающие устройства (мыши, сенсорные экраны и т. д.) и геймпады. Протокол HID позволяет получать доступ к этим устройствам на настольных компьютерах с помощью драйверов операционной системы. Веб-платформа поддерживает HID-устройства, используя эти драйверы.
Невозможность доступа к редким HID-устройствам особенно неприятна, когда речь идёт об альтернативных вспомогательных клавиатурах (таких как Elgato Stream Deck , гарнитуры Jabra , X-keys ) и поддержке экзотических геймпадов. Геймпады, разработанные для настольных компьютеров, часто используют HID для ввода (кнопки, джойстики, триггеры) и вывода (светодиоды, вибрация).
К сожалению, входы и выходы геймпадов плохо стандартизированы, и веб-браузеры часто требуют пользовательской логики для конкретных устройств. Это нецелесообразно и приводит к плохой поддержке большого количества старых и редких устройств. Кроме того, это заставляет браузер зависеть от особенностей поведения конкретных устройств.
Терминология
Устройство человеко-машинного интерфейса (HID) может принимать входные данные или выдавать выходные данные человеку. Существует протокол HID — стандарт для двусторонней связи между хостом и устройством, разработанный для упрощения процедуры установки.
HID состоит из двух основных концепций: отчетов и дескрипторов отчетов. Отчеты — это данные, которыми обмениваются устройство и программный клиент. Дескриптор отчета описывает формат и значение данных, поддерживаемых устройством.
Приложения и HID-устройства обмениваются двоичными данными посредством трех типов отчетов:
| Тип отчета | Описание |
|---|---|
| Ввод отчета | Данные, передаваемые с устройства в приложение (например, нажатие кнопки). |
| Отчет о результатах | Данные, отправляемые из приложения на устройство (например, запрос на включение подсветки клавиатуры). |
| Отчет о функциях | Данные могут передаваться в любом направлении. Формат зависит от устройства. |
Дескриптор отчета описывает двоичный формат отчетов, поддерживаемых устройством. Его структура иерархична и позволяет группировать отчеты в отдельные коллекции внутри коллекции верхнего уровня. Формат дескриптора определяется спецификацией HID.
Значение использования HID — это числовое значение, относящееся к стандартизированному входному или выходному параметру. Значения использования позволяют устройству описывать предполагаемое назначение устройства и цель каждого поля в своих отчетах. Например, одно из них определено для левой кнопки мыши. Значения использования также организованы в страницы использования, которые указывают на общую категорию устройства или отчета.
Используйте API WebHID
Чтобы проверить, поддерживается ли API WebHID, используйте:
if ("hid" in navigator) {
// The WebHID API is supported.
}
Откройте соединение HID
API WebHID по своей конструкции является асинхронным, чтобы предотвратить блокировку пользовательского интерфейса веб-сайта в ожидании ввода. Это важно, поскольку данные HID могут быть получены в любое время, поэтому необходим способ их прослушивания.
Для установления HID-соединения сначала необходимо получить доступ к объекту HIDDevice . Для этого можно либо предложить пользователю выбрать устройство, вызвав метод navigator.hid.requestDevice() , либо выбрать его из navigator.hid.getDevices() , который возвращает список устройств, к которым веб-сайту ранее был предоставлен доступ.
Функция navigator.hid.requestDevice() принимает обязательный объект, определяющий фильтры. Они используются для сопоставления любого подключенного устройства с идентификатором производителя USB ( vendorId ), идентификатором продукта USB ( productId ), значением страницы использования ( usagePage ) и значением использования ( usage ). Эти данные можно получить из репозитория идентификаторов USB и документа таблиц использования HID .
Множество объектов HIDDevice , возвращаемых этой функцией, представляют собой несколько HID-интерфейсов на одном и том же физическом устройстве.
// 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();

Также можно использовать необязательный ключ exclusionFilters в navigator.hid.requestDevice() чтобы исключить из выбора устройств в браузере те, которые, как известно, неисправны.
// 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 }],
});
Объект HIDDevice содержит идентификаторы производителя и продукта USB для идентификации устройства. Его атрибут collections инициализируется иерархическим описанием форматов отчетов устройства.
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
}
Устройства HIDDevice по умолчанию возвращаются в "закрытом" состоянии и должны быть открыты путем вызова функции open() прежде чем можно будет отправлять или получать данные.
// Wait for the HID connection to open before sending/receiving data.
await device.open();
Получение входных отчетов

После установления HID-соединения вы можете обрабатывать входящие отчеты, прослушивая события "inputreport" от устройства. Эти события содержат данные HID в виде объекта DataView ( data ), HID-устройство, к которому они относятся ( device ), и 8-битный идентификатор отчета, связанный с входным отчетом ( reportId ).
Продолжая предыдущий пример, этот код поможет вам определить, какую кнопку нажал пользователь на устройстве Joy-Con Right, чтобы вы могли проверить это дома.
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]}.`);
});
Посмотрите демонстрацию на CodePen .
Отправка отчетов о результатах
Для отправки отчета на HID-устройство передайте 8-битный идентификатор отчета ( reportId ) и байты в качестве BufferSource ( data ) в device.sendReport() . Возвращаемое обещание выполняется после отправки отчета. Если HID-устройство не использует идентификаторы отчетов, установите reportId равным 0.
Следующий пример относится к устройству Joy-Con и показывает, как заставить его вибрировать с помощью отчетов о результатах.
// 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.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));
Посмотрите демонстрацию на CodePen .
Отправляйте и получайте отчеты о функциях.

Отчеты о характеристиках — это единственный тип отчетов о данных HID, который может передаваться в обоих направлениях. Они позволяют устройствам HID и приложениям обмениваться нестандартизированными данными HID. В отличие от отчетов о входе и выходе, отчеты о характеристиках не принимаются и не отправляются приложением на регулярной основе.
Для отправки отчета о характеристиках на HID-устройство передайте 8-битный идентификатор отчета ( reportId ) и байты в качестве BufferSource ( data ) в device.sendFeatureReport() . Возвращаемое обещание выполняется после отправки отчета. Если HID-устройство не использует идентификаторы отчетов, установите reportId равным 0.
Этот пример иллюстрирует использование отчетов о функциях, показывая, как запросить устройство подсветки клавиатуры Apple, открыть его и заставить мигать.
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);
}
Посмотрите демонстрацию на CodePen .
Чтобы получить отчет о характеристиках от HID-устройства, передайте 8-битный идентификатор отчета ( reportId ), связанный с отчетом (reportId), в device.receiveFeatureReport() . Возвращаемое обещание разрешается с объектом DataView , содержащим содержимое отчета о характеристиках. Если HID-устройство не использует идентификаторы отчетов, установите reportId равным 0.
// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);
// Read feature report contents with dataView.getInt8(), getUint8(), etc...
Прислушайтесь к связи и разобщенности
Получив разрешение на доступ к HID-устройству, веб-сайт может активно получать события подключения и отключения, отслеживая события "connect" и "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.
});
Отозвать доступ к устройству HID
Веб-сайт может очистить разрешения доступа к HID-устройству, которое ему больше не нужно, вызвав метод forget() для экземпляра HIDDevice . Например, для образовательного веб-приложения, используемого на компьютере, которым совместно пользуются многие устройства, большое количество накопленных разрешений, созданных пользователями, приводит к ухудшению пользовательского опыта.
Вызов метода forget() для отдельного экземпляра HIDDevice отменит доступ ко всем HID-интерфейсам на том же физическом устройстве.
// Voluntarily revoke access to this HID device.
await device.forget();
Поскольку forget() доступна в Chrome версии 100 и более поздних, проверьте, поддерживается ли она следующим образом:
if ("hid" in navigator && "forget" in HIDDevice.prototype) {
// forget() is supported.
}
Советы для разработчиков

Отладка HID в Chrome осуществляется с помощью внутренней страницы about://device-log , где вы можете увидеть все события, связанные с HID и USB-устройствами, в одном месте.
Воспользуйтесь инструментом HID Explorer для вывода информации об HID-устройствах в удобочитаемом формате. Он сопоставляет значения использования с именами для каждого использования HID-устройства.
В большинстве систем Linux устройства HID по умолчанию имеют права только для чтения. Чтобы разрешить Chrome открывать устройства HID, необходимо добавить новое правило udev . Создайте файл по адресу /etc/udev/rules.d/50-yourdevicename.rules со следующим содержимым:
KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
В этом коде [yourdevicevendor] — это, например, 057e , если ваше устройство — это контроллеры Joy-Con для Nintendo Switch. Для более точного правила можно добавить ATTRS{idProduct} . Убедитесь, что ваш user является членом группы plugdev . Затем просто переподключите ваше устройство.
Демонстрации
Некоторые примеры использования WebHID можно найти по адресу web.dev/hid-examples .
Безопасность и конфиденциальность
Авторы спецификации разработали и реализовали API WebHID, используя основные принципы, изложенные в документе «Контроль доступа к мощным функциям веб-платформы» , включая пользовательский контроль, прозрачность и эргономику. Возможность использования этого API в основном ограничена моделью разрешений, которая предоставляет доступ только к одному HID-устройству одновременно. В ответ на запрос пользователя, он должен предпринять активные действия для выбора конкретного HID-устройства.
Чтобы понять компромиссы в вопросах безопасности, ознакомьтесь с разделом «Вопросы безопасности и конфиденциальности» спецификации WebHID.
Кроме того, Chrome проверяет использование каждой коллекции верхнего уровня, и если коллекция верхнего уровня имеет защищенное использование (например, «общая клавиатура», «мышь»), то веб-сайт не сможет отправлять и получать отчеты, определенные в этой коллекции. Полный список защищенных использований находится в открытом доступе .
Обратите внимание, что устройства HID, чувствительные к вопросам безопасности (например, устройства FIDO HID, используемые для более надежной аутентификации), также блокируются в Chrome. См. файлы списков блокировки USB и HID .
Обратная связь
Команда Chrome будет рада узнать ваше мнение и опыт использования API WebHID.
Расскажите о проектировании API.
Есть ли какие-то особенности API, которые работают не так, как ожидалось? Или отсутствуют методы или свойства, необходимые для реализации вашей идеи?
Создайте заявку в репозитории WebHID API на GitHub или поделитесь своими мыслями в уже существующей заявке.
Сообщить о проблеме с реализацией
Вы обнаружили ошибку в реализации Chrome? Или реализация отличается от спецификации?
Ознакомьтесь с инструкцией «Как сообщать об ошибках WebHID» . Обязательно укажите как можно больше подробностей, предоставьте инструкции по воспроизведению ошибки и установите для компонентов значение Blink>HID .
Полезные ссылки
- Спецификация
- Отслеживание ошибки
- Запись на ChromeStatus.com
- Компонент Blink:
Blink>HID
Благодарности
Благодарим Мэтта Рейнольдса и Джо Медли за их отзывы.