连接到不常见的HID设备

2020-09-17 08:04:33

WebHID API是Capability项目的一部分,目前正在开发中。这篇文章将随着实现的进展而更新。

有一长串人机接口设备(HID),如备用键盘或奇异的游戏手柄,它们太新、太旧或太不常见,无法被系统设备驱动程序访问。WebHID API通过提供在JavaScript中实现特定于设备的逻辑来解决这个问题。

HID设备从人类获取输入或向人类提供输出。设备的示例包括键盘、定点设备(鼠标、触摸屏等)和游戏手柄。HID协议使使用操作系统驱动程序访问台式计算机上的这些设备成为可能。Web平台通过依赖这些驱动程序来支持HID设备。

当涉及到替代辅助键盘(如Elgato Stream Deck、Jrapar耳机、X键)和异国情调的游戏手柄支持时,无法访问不常见的HID设备尤其令人痛苦。专为台式机设计的游戏手柄通常使用HID作为游戏手柄的输入(按钮、操纵杆、触发器)和输出(发光二极管、隆隆声)。不幸的是,游戏手柄的输入和输出没有很好地标准化,Web浏览器通常需要特定设备的自定义逻辑,这是不可持续的,并且导致对旧的和不常见的设备的长尾的支持很差。它还会导致浏览器依赖于特定设备行为中的怪癖。

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

HID(人机接口设备)是一种从人类获取输入或向人类提供输出的设备。它还指的是HID协议,这是主机和设备之间的标准双向通信,旨在简化安装过程。HID协议最初是为USB设备开发的,但后来在包括蓝牙在内的许多其他协议上实现。

从设备发送到应用程序的数据(例如,按下按钮)。

从应用程序发送到设备的数据(例如,打开键盘背光的请求)。

报告描述符描述设备支持的报告的二进制格式。它的结构是分层的,可以将报告分组为顶级集合中的不同集合。描述符的格式由HID规范定义。

HID用法是指标准化输入或输出的数字值。用法值允许设备在其报告中描述设备的预期用途和每个字段的用途。例如,为鼠标的左键定义了一个。使用情况也被组织到使用情况页面中,这些页面提供设备或报告的高级类别的指示。

要在所有桌面平台上本地试验WebHID API,而不使用原始试用令牌,请在Chrome://flag中启用#Examentative-Web-Platform-Feature标志。

WebHID API在所有桌面平台(Chrome OS、Linux、MacOS和Windows)上都可用,作为Chrome 86中的原始试用版。原版试用预计将在Chrome-89于2021年2月稳定之前结束。也可以使用标志启用API。

Origin试用版允许您尝试新功能,并就其可用性、实用性和有效性向Web标准社区提供反馈。有关详细信息,请参阅Web开发人员原版试用指南。若要注册此原版试用版或其他原版试用版,请访问注册页。

将令牌添加到您的页面。有两种方法可以做到这一点:在每个页面的头部添加一个Origin-Trial<;meta>;标记。例如,这可能类似于:<;meta http-equv=";Origin-Trial";Content=";TOKEN_GOES_HERE";>;

如果您可以配置您的服务器,您还可以使用原始试用HTTP标头添加令牌。生成的响应头应该类似于:Origin-Trial:Token_Goes_Here。

WebHID API在设计上是异步的,以防止网站UI在等待输入时阻塞。这一点很重要,因为可以随时接收HID数据,这需要一种监听方式。

若要打开HID连接,请首先访问HIDDevice对象。为此,您可以通过调用navgator.id.requestDevice()来提示用户选择设备,也可以从Navigator.id.getDevices()中选择一个设备,后者返回网站以前已被授予访问权限的设备列表。

函数的作用是:获取定义过滤器的强制对象。它们用于匹配与USB供应商标识符(VendorID)、USB产品标识符(ProductID)、使用页值(UsagePage)和使用值(Usage)连接的任何设备。您可以从USB ID存储库和HID使用表文档中获得这些信息。

此函数返回的多个HIDDevice对象表示同一物理设备上的多个HID接口。

//使用任天堂交换机Joy-Con USB供应商/产品ID过滤设备。Const Filters=[{vendorID:0x057e,//任天堂有限公司ProductID:0x2006//Joy-Con Left},{vendorID:0x057e,//任天堂有限公司ProductID:0x2007//Joy-Con Right}];//提示用户选择Joy-Con设备。Const[设备]=等待导航器.hid。RequestDevice({filter});

//获取用户之前授予网站访问权限的所有设备。Const device=等待导航器.hid。GetDevices();

HIDDevice对象包含用于设备标识的USB供应商和产品标识符。它的集合属性是用设备报告格式的分层描述初始化的。

For(Let Collection of Device.Collection){//HID集合包括使用情况、使用情况页、报告和子集合。控制台。Log(`用法:${Collection.use}`);控制台。Log(`使用页面:${Collection.usagePage}`);for(let inputReport of Collection.inputReports){控制台。Log(`输入报告:${inputReport.reportId}`);//循环遍历inputReport.item}for(let outputReport of Collection.outputReports){console。Log(`输出报告:${outputReport.reportId}`);//循环遍历outputReport.Items}for(let feature ureReport of Collection.feature ureReports){console。Log(`Feature Report:${FeatureReport.reportId}`);//循环通过Feature ureReport.Items}//循环通过集合的子集。}。

默认情况下,HIDDevice设备返回为关闭状态,并且必须通过调用open()打开,然后才能发送或接收数据。

一旦建立了HID连接,您就可以通过侦听来自设备的输入事件来处理传入的输入端口。这些事件包含作为DataView对象(Data)的HID数据、它所属的HID设备(Device)以及与输入报告关联的8位报告ID(ReportId)。

继续前面的示例,下面的代码向您展示了如何检测用户在Joy-Con Right设备上按下了哪个按钮,这样您就有希望在家里尝试一下。

设备。AddEventListener(";inputreport";,event=>;{const{data,device,reportId}=event;//仅处理Joy-Con Right设备和特定报告ID。if(device.products tId!==0x2007&;&;reportId!==0x3f)return;const value=data。GetUint8(0);if(value=0)return;const omeButton={1:";A";,2:";X";,4:";B";,8:";Y";};控制台。Log(`用户按下的按钮${omeButton[value]}。`);});

要将输出报告发送到HID设备,请将与输出报告关联的8位报告ID(ReportId)和字节作为BufferSource(Data)传递给device.sendReport()。一旦报告提交,退还的承诺就会解决。如果HID设备不使用报告ID,请将reportId设置为0。

下面的示例适用于Joy-Con设备,并向您展示如何使其具有输出报告。

//首先,发送开启振动的命令。//神奇字节来自https://github.com/mzyy94/joycon-toolweb常量enableVibrationData=[1,0,1,64,64,0,1,64,64,0x48,0x01];等待设备。SendReport(0x01,new(EnableVibrationData));//然后发送命令,让Joy-Con设备发出隆隆声。//下面的示例中有实际的字节数。Const rambleData=[/*...*/];等待设备。SendReport(0x10,new(RambleData));

功能报告是唯一可以双向传输的HID数据报告类型。它们允许HID设备和应用程序交换非标准化的HID数据。与输入和输出报告不同,应用程序不会定期接收或发送功能报告。

要将功能报告发送到HID设备,请将与功能报告关联的8位报告ID(ReportId)和字节作为BufferSource(Data)传递给device.sendFeatureReport()。报告发送后,退回的承诺即可解决。如果HID设备不使用报告ID,请将reportId设置为0。

下面的示例通过向您展示如何请求Apple键盘背光设备、打开它并使其闪烁来说明功能报告的使用。

Const waitFor=Duration=>;new(r=>;setTimeout(r,Duration));//提示用户选择Apple键盘背光设备。Const[设备]=等待导航器.hid。RequestDevice({Filters:[{vendorID:0x05ac,Usage:0x0f,usagePage:0xff00}]});//等待HID连接打开。等待设备。Open();//Blink!Const reportId=1;for(设i=0;i<;10;i++){//关闭等待设备。SendFeatureReport(reportId,Uint32Array。From([0,0]));等待waitFor(100);//打开等待设备。SendFeatureReport(reportId,Uint32Array。发自([512,0]);等待waitFor(100);}。

要从HID设备接收功能报告,请将与功能报告(ReportId)关联的8位报告ID传递给device.ReceiveFeatureReport()。返回的Promise将使用包含功能报告内容的DataView对象进行解析。如果HIDDevice不使用报告ID,请将reportId设置为0。

//请求功能报告。Const dataView=等待设备。ReceiveFeatureReport(/*reportId=*/1);//使用dataView.getInt8()、getUint8()等读取功能报表内容...。

当网站被授予访问HID设备的权限后,它可以通过侦听";CONNECT";和";DISCONNECT";事件来主动接收连接和断开事件。

导航器.HID。AddEventListener(";connect";,event=>;{//自动打开event.device或警告用户设备可用。});导航器.hid。AddEventListener(";断开";,event=>;{//从UI中删除|event.device|。});

在Chrome中调试HID很容易,通过内部页面Chrome://device-log,您可以在一个地方看到所有与HID和USB设备相关的事件。

规范作者使用控制访问功能强大的Web平台特性(包括用户控制、透明度和人体工程学)中定义的核心原则设计并实现了WebHID API。使用此API的能力主要受权限模型的限制,该模型一次仅授予对单个HID设备的访问权限。响应于用户提示,用户必须采取主动步骤来选择特定的HID设备。

要了解安全权衡,请查看WebHID规范的Security and PrivacyConsiderations部分。

最重要的是,Chrome检查每个顶级集合的使用情况,以及顶级集合是否有受保护的使用(例如,通用键盘、鼠标),则网站不能发送和接收该集合中定义的任何报告。受保护使用的完整列表是公开提供的。

请注意,安全敏感的HID设备(如用于更强身份验证的FIDO HID设备)也在Chrome中被屏蔽。请参见阻止列表文件。

Chrome团队很想听听您对WebHID API的想法和经验。

是不是API有什么地方没有按预期工作?或者这些方法或属性是实现您的想法所需的吗?

在WebHID API GitHub repo上提交规范问题,或将您的想法添加到现有问题中。

你在Chrome的实现中发现错误了吗?或者实现与规范不同?

在https://new.crbug.com.上提交漏洞。请确保包含尽可能多的详细信息,提供简单的错误再现说明,并将组件设置为闪烁&>隐藏。小故障对于快速、轻松地分享责备非常有用。

您是否计划使用WebHID API?您的公开支持有助于Chrometeam确定功能的优先顺序,并向其他浏览器供应商展示支持这些功能的重要性。

向@ChromiumDev发送一条带有#WebHID的推文,让我们知道您在哪里以及如何使用它。

感谢马特·雷诺兹(Matt Reynolds)和乔·梅德利(Joe Medley)对本文的评论。瓦伦丁·米勒(Valentin Müller)拍摄的英雄图像,萨拉库尔夫(SaraKurfe?)拍摄的红色和蓝色任天堂Switch照片,以及Athul Cyriac Ajay在Unspash上拍摄的黑色和银色笔记本电脑照片。