连接到不常见的 HID 设备

借助 WebHID API,网站可以访问备用辅助键盘和独特的游戏手柄。

François Beaufort
François Beaufort

存在长尾人机接口设备 (HID),例如备用设备 太新、太旧或不常见 供系统访问的设备驱动程序。WebHID API 通过提供 在 JavaScript 中实现设备专用逻辑的方法。

建议的用例

HID 设备接收来自人类的输入或提供输出。设备示例 包括键盘、指控设备(鼠标、触摸屏等)和游戏手柄。 借助 HID 协议,您可以在桌面设备上访问这些设备 计算机使用操作系统驱动程序。Web 平台支持 HID 设备 依靠这些驱动程序。

无法访问不常见 HID 设备的情况尤为严重, 涉及其他辅助键盘(例如 Elgato Stream DeckJabra) 耳机X 键)和独特的游戏手柄支持。专为桌面设备设计的游戏手柄 通常针对游戏手柄输入(按钮、操纵杆、触发器)和输出使用 HID (LED、隆隆声)。遗憾的是,游戏手柄的输入和输出 标准化的网络浏览器和网络浏览器通常需要针对特定设备的自定义逻辑。 这是不可持续的,并且会导致无法持续支持老旧的和 设备。这还会导致浏览器依赖行为中的怪异行为 特定设备

术语

HID 包含两个基本概念:报告和报告描述符。 报告是在设备和软件客户端之间交换的数据。 报告描述符用于描述设备所存储的数据的格式和含义。 支持。

HID(人机接口设备)是一种从以下设备获取输入的设备: 提供输出。它还指 HID 协议, 主机与设备之间的双向通信 从而简化安装过程。HID 协议最初是开发出来的 但后来在许多其他协议上得到了实现, 包括蓝牙。

应用和 HID 设备通过三种报告类型交换二进制数据:

报告类型 说明
输入报告 从设备发送到应用的数据(例如,用户按下按钮)。
输出报告 从应用发送到设备的数据(例如,开启键盘背光的请求)。
功能报告 可按任一方向发送的数据。格式因设备而异。

报告描述符用于描述受 设备。其结构是分层的,可以将报告分组为不同的报告 位于顶级集合中。描述符的格式为 由 HID 规范定义。

HID 用法是表示标准化输入或输出的数值。 利用用法值,设备可以描述其预期用途, 每个字段的用途例如,我们针对左边的 键。使用情况也被整理成了使用情况页面,该页面提供了 表示设备或报告的概要类别。

使用 WebHID API

功能检测

如需检查 WebHID API 是否受支持,请使用以下命令:

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

打开 HID 连接

WebHID API 在设计上是异步的,以防止网站界面 在等待输入时阻塞这很重要,因为 因此需要一种聆听方式

如需打开 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();
网站上的 HID 设备提示屏幕截图。
用户提示选择 Nintendo Switch Joy-Con。

您也可以使用可选的 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 位报告 ID (reportId)。

<ph type="x-smartling-placeholder">
</ph> 任天堂红蓝两色的切换照片。
Nintendo Switch Joy-Con 设备。

接着前面的示例来讲,以下代码展示了如何检测 用户在 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 设备发送输出报告,请将与 输出报告 (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 数据。与输入和输出报告不同,Google 不会接收或 定期发送

<ph type="x-smartling-placeholder">
</ph> 黑色和银色笔记本电脑照片。
笔记本电脑键盘

要向 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 设备的访问权限

网站可以清理不再使用的 HID 设备的访问权限 有兴趣通过对 HIDDevice 实例调用 forget() 来保留数据。对于 例如,对于在共享计算机上使用的教育 Web 应用,该应用具有许多 累积的大量用户生成权限会导致 用户体验。

对单个 HIDDevice 实例调用 forget() 将撤消对所有用户的访问权限 HID 接口。

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

由于 Chrome 100 或更高版本支持forget(),因此请检查此功能是否 支持以下各项:

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

开发提示

借助内部页面 about://device-log,您可以轻松地在 Chrome 中调试 HID 让您可以在一个地方集中查看所有与 HID 和 USB 设备相关的事件。

<ph type="x-smartling-placeholder">
</ph> 用于调试 HID 的内部页面的屏幕截图。
Chrome 中用于调试 HID 的内部页面。

查看 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"

在上面一行中,如果您的设备是 Nintendo Switch,则 [yourdevicevendor]057e 以 Joy-Con 为例。也可以添加ATTRS{idProduct} 规则。确保您的 userplugdev 群组的成员。然后,只需 重新连接您的设备。

浏览器支持

WebHID API 适用于所有桌面平台(ChromeOS、Linux、macOS、 和 Windows)。

演示

web.dev/hid-examples 列出了一些 WebHID 演示。去看看吧!

安全和隐私设置

规范作者通过 控制对强大的网络平台功能的访问权限中定义的原则, 包括用户控制、透明度和人体工程学。使用 API 主要由权限模型控制,该模型仅授予对单个 API 的访问权限 同时支持多个 HID 设备。为了响应用户提示,用户必须主动 选择特定 HID 设备的步骤。

要了解在安全性方面需要权衡,请参阅安全和隐私权 注意事项部分。

除此之外,Chrome 还会检查每个顶级集合的使用情况, 顶级集合具有受保护的用法(例如,通用键盘、鼠标),则 网站将无法发送和接收 。受保护的使用情况的完整列表已公开发布

请注意,对安全敏感的 HID 设备(例如用于 更强大的身份验证)也会在 Chrome 中遭到屏蔽。请参阅 USB 屏蔽名单HID 屏蔽名单文件。

反馈

Chrome 团队非常希望了解您对 WebHID API。

向我们介绍 API 设计

API 是否存在无法按预期运行的地方?或者,在那里 缺少实现想法所需的方法或属性?

WebHID API GitHub 代码库中提交规范问题或添加您的想法 现有问题。

报告实现存在的问题

您在 Chrome 的实现过程中是否发现了错误?还是 与规范不同?

请参阅如何提交 WebHID bug。务必尽可能多添加一些 提供重现错误的简单说明 将 Components 设置为 Blink>HIDGlitch 非常适用于以下情况: 轻松快速的重现问题

表示支持

您打算使用 WebHID API 吗?您的公开支持对 Chrome 很有帮助 团队确定各项功能的优先级,并向其他浏览器供应商展示 支持他们。

使用 # 标签向 @ChromiumDev 发送推文 #WebHID并告知我们 您使用它的具体位置和使用方式。

实用链接

致谢

感谢 Matt ReynoldsJoe Medley 对本文的评价。 Sara Kurfeß 拍摄的红蓝 Nintendo Switch 照片以及黑银笔记本电脑 计算机照片,由 Athul Cyriac Ajay 提供,由 Un 创立网站提供。