WebHID API 可让网站访问替代的辅助键盘和独特的游戏手柄。
有一长串的人机接口设备 (HID)(如备用键盘或奇异游戏手柄)过于新、太旧或太不常见,以至于系统的设备驱动程序无法访问。WebHID API 提供了一种在 JavaScript 中实现设备专用逻辑的方法,来解决此问题。
建议的用例
HID 设备从人类接收输入或提供输出。设备示例包括键盘、指控设备(鼠标、触摸屏等)和游戏手柄。借助 HID 协议,可以使用操作系统驱动程序在台式机上访问这些设备。Web 平台依靠这些驱动程序来支持 HID 设备。
当涉及到备用的辅助键盘(例如 Elgato Stream Deck、Jabra 耳机、X 键)和支持异国情调的游戏手柄时,无法访问不常见的 HID 设备尤为令人痛苦。专为桌面设备设计的游戏手柄通常会将 HID 用于游戏手柄输入(按钮、操纵杆、触发器)和输出(LED、振动)。遗憾的是,游戏手柄输入和输出尚未完全标准化,网络浏览器通常需要特定设备的自定义逻辑。这种方式不可持续,并且会导致对较旧和不常见设备的长尾设备提供不良支持。它还会导致浏览器依赖于特定设备的行为中的怪异行为。
术语
HID 由两个基本概念组成:报告和报告描述符。报告是在设备和软件客户端之间交换的数据。 报告描述符描述了设备支持的数据的格式和含义。
HID(人机接口设备)是一种可接受输入或提供输出给人类的设备。它还指 HID 协议,这是主机和设备之间的双向通信标准,旨在简化安装过程。HID 协议最初是为 USB 设备开发的,后来已通过许多其他协议(包括蓝牙)实现。
应用和 HID 设备通过三种类型的报告交换二进制数据:
报告类型 | 说明 |
---|---|
输入报告 | 从设备发送到应用的数据(例如按下按钮)。 |
输出报告 | 从应用发送到设备的数据(例如,开启键盘背光的请求)。 |
功能报告 | 可以向任一方向发送的数据。格式因设备而异。 |
报告描述符描述了设备支持的报告的二进制格式。它的结构是分层的,可以将报告分组为顶级集合中不同的集合。描述符的格式由 HID 规范定义。
HID 用途是一个数值,表示标准化输入或输出。通过用量值,设备可以说明设备的预期用途以及报告中每个字段的用途。例如,为鼠标左键定义一个按钮。使用情况也被整理到使用情况页面中,这些页面指示了设备或报告的简要类别。
使用 WebHID API
功能检测
如需检查 WebHID API 是否受支持,请使用以下命令:
if ("hid" in navigator) {
// The WebHID API is supported.
}
打开 HID 连接
WebHID API 在设计上是异步的,以防止网站界面在等待输入时阻塞。这很重要,因为可以随时接收 HID 数据,需要通过某种方式进行监听。
如需打开 HID 连接,请先访问 HIDDevice
对象。为此,您可以调用 navigator.hid.requestDevice()
来提示用户选择设备,或者从 navigator.hid.getDevices()
中选择一个设备,该方法会返回网站先前有权访问的设备列表。
navigator.hid.requestDevice()
函数接受一个用于定义过滤条件的必需对象。这些参数可用于匹配连接了 USB 供应商标识符 (vendorId
)、USB 产品标识符 (productId
)、使用情况页面值 (usagePage
) 和使用情况值 (usage
) 的任何设备。您可以从 USB ID 代码库和 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();
您还可以在 navigator.hid.requestDevice()
中使用可选的 exclusionFilters
键,从浏览器选择器中排除某些已知故障的设备。
// 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"
事件来处理传入的输入报告。这些事件包含作为 DataView
对象 (data
) 的 HID 数据、它所属的 HID 设备 (device
) 以及与输入报告关联的 8 位报告 ID (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]}.`);
});
发送输出报告
如需将输出报告发送到 HID 设备,请将与输出报告关联的 8 位报告 ID (reportId
) 和字节作为 BufferSource
(data
) 传递给 device.sendReport()
。返回的 promise 会在报告发送后解析。如果 HID 设备不使用报告 ID,请将 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 below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));
发送和接收功能报告
特征报告是唯一可以双向传输的 HID 数据报告类型。它们允许 HID 设备和应用交换非标准化 HID 数据。与输入和输出报告不同,应用不会定期接收或发送特征报告。
如需向 HID 设备发送功能报告,请将与功能报告关联的 8 位报告 ID (reportId
) 和字节作为 BufferSource
(data
) 传递给 device.sendFeatureReport()
。返回的 promise 会在报告发送后解析。如果 HID 设备不使用报告 ID,请将 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);
}
如需从 HID 设备接收功能报告,请将与功能报告关联的 8 位报告 ID (reportId
) 传递给 device.receiveFeatureReport()
。返回的 promise 通过包含特征报告内容的 DataView
对象进行解析。如果 HID 设备不使用报告 ID,请将 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 设备的访问权限
网站可以通过对 HIDDevice
实例调用 forget()
来清理访问其不再需要保留的 HID 设备的权限。例如,对于在具有许多设备的共享计算机上使用的教育类 Web 应用,大量累积的用户生成权限会导致用户体验不佳。
在单个 HIDDevice
实例上调用 forget()
将撤消对同一实体设备上所有 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.
}
开发提示
通过内部页面 about://device-log
,您可以轻松地在 Chrome 中调试 HID。在该页面中,您可以在一个位置查看所有 HID 和 USB 设备相关事件。
如需了解如何将 HID 设备信息转储为人类可读的格式,请查看 HID 资源管理器。它会从用法值映射到每个 HID 用法的名称。
在大多数 Linux 系统中,HID 设备默认以只读权限进行映射。若要允许 Chrome 打开 HID 设备,您需要添加新的 udev 规则。使用以下内容在 /etc/udev/rules.d/50-yourdevicename.rules
上创建一个文件:
KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
在上面的一行中,如果您的设备是 Nintendo Switch Joy-Con 功能,则 [yourdevicevendor]
为 057e
。您还可以为更具体的规则添加 ATTRS{idProduct}
。请确保您的 user
是 plugdev
群组的成员。然后重新连接设备即可。
浏览器支持
在 Chrome 89 中,WebHID API 适用于所有桌面平台(ChromeOS、Linux、macOS 和 Windows)。
样本歌曲
如需查看一些 WebHID 演示,请访问 web.dev/hid-examples。去看看吧!
安全和隐私设置
规范作者根据控制对强大 Web 平台功能的访问权限中定义的核心原则(包括用户控制、透明度和工效学设计)设计和实现了 WebHID API。能否使用该 API 主要受权限模型控制,该权限模型一次仅授予对单个 HID 设备的访问权限。为响应用户提示,用户必须主动选择特定的 HID 设备。
如需了解安全方面的权衡,请参阅 WebHID 规范的安全与隐私注意事项部分。
除此之外,Chrome 还会检查每个顶级集合的使用情况,如果顶级集合有受保护的使用情况(例如通用键盘、鼠标),网站将无法发送和接收该集合中定义的任何报告。受保护的用法的完整列表已公开发布。
请注意,对安全性要求较高的 HID 设备(例如用于更严格的身份验证的 FIDO HID 设备)也会在 Chrome 中被屏蔽。请参阅 USB 屏蔽名单和 HID 屏蔽名单文件。
反馈
Chrome 团队希望了解您对 WebHID API 的想法和体验。
向我们介绍 API 设计
是否存在 API 无法正常运行的问题?或者,您是否需要缺少一些方法或属性来实现您的想法?
在 WebHID API GitHub 代码库上提交规范问题,或将您的想法添加到现有问题中。
报告实施方面的问题
您是否发现了 Chrome 实现方面的错误?或者实现方式是否不同于规范?
请查看如何提交 WebHID 错误。请务必提供尽可能多的详细信息,提供重现 bug 的简单说明,并将组件设置为 Blink>HID
。Glitch 非常适合用于快速轻松地分享重现的视频。
表达支持
您打算使用 WebHID API 吗?您的公开支持有助于 Chrome 团队确定各项功能的优先级,还能向其他浏览器供应商表明支持这些功能的重要性。
请使用 # 标签 #WebHID
向 @ChromiumDev 发送一条推文,告诉我们您使用该产品的位置和方式。
实用链接
- 规格
- 跟踪 bug
- ChromeStatus.com 条目
- Blink 组件:
Blink>HID
致谢
感谢 Matt Reynolds 和 Joe Medley 审核本文。 红蓝配色的 Nintendo Switch 照片由 Sara Kurfeß 拍摄,黑色和银色笔记本电脑 照片由 Athul Cyriac Ajay 拍摄。