连接到不常用的 HID 设备

借助 WebHID API,网站可以访问替代的辅助键盘和非标准的游戏手柄。

François Beaufort
François Beaufort

发布时间:2020 年 9 月 15 日

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

Source

许多人机接口设备 (HID)(例如替代键盘或异型游戏手柄)过于新颖、过旧或过于罕见,以至于系统设备驱动程序无法访问。WebHID API 通过提供一种在 JavaScript 中实现设备专用逻辑的方式来解决此问题。

建议的应用场景

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

在涉及替代的辅助键盘(例如 Elgato Stream DeckJabra 耳机X-keys)和非标准游戏手柄支持时,无法访问非标准 HID 设备尤其令人头疼。专为桌面设备设计的游戏手柄通常使用 HID 来处理游戏手柄输入(按钮、操纵杆、扳机)和输出(LED、振动)。

遗憾的是,游戏手柄输入和输出并未实现标准化,因此 Web 浏览器通常需要针对特定设备采用自定义逻辑。这种做法不可持续,会导致对大量旧版和不常见设备的支持不佳。它还会导致浏览器依赖于特定设备的行为怪异之处。

术语

人机接口设备 (HID) 可以接收人类的输入或向人类提供输出。 HID 协议是一种用于在主机和设备之间进行双向通信的标准协议,旨在简化安装过程。

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

应用和 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();