WebHID API 可讓網站存取其他輔助鍵盤和特殊控制器。
人類介面裝置 (HID) 長尾 (例如替代鍵盤或奇特的遊戲墊) 太新、太舊,或系統裝置驅動程式不常見。透過 WebHID API,您可以設法在 JavaScript 中實作裝置專屬的邏輯,藉此解決這個問題。
建議用途
HID 裝置會接收輸入內容或提供輸出內容,裝置範例包括鍵盤、指標裝置 (滑鼠、觸控螢幕等) 和遊戲控制器。透過 HID 通訊協定,您可以使用作業系統驅動程式,在電腦上存取這些裝置。網頁平台會依賴這些驅動程式支援 HID 裝置。
無法存取非標準 HID 裝置,對於替代輔助鍵盤 (例如 Elgato Stream Deck、Jabra 耳機、X-keys) 和特殊遊戲控制器的支援來說,特別令人頭痛。專為電腦設計的遊戲手把通常會使用 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 的設計為非同步,可避免網站 UI 在等待輸入時發生阻斷。這一點相當重要,因為 HID 資料可隨時接收,因此需要有收聽方式。
如要開啟 HID 連線,請先存取 HIDDevice
物件。為此,您可以呼叫 navigator.hid.requestDevice()
來提示使用者選取裝置,或是從 navigator.hid.getDevices()
中選取裝置,該函式會傳回網站先前已授予存取權的裝置清單。
navigator.hid.requestDevice()
函式會使用定義篩選器的必要物件。這些值可用於比對任何已連結 USB 供應商 ID (vendorId
)、USB 產品 ID (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 供應商和產品 ID,用於裝置識別。其 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
),以及與輸入報表 (reportId
) 相關聯的 8 位元報表 ID。
延續上一個範例,以下程式碼會說明如何偵測使用者按下 Joy-Con 右側裝置的哪個按鈕,方便您在家中試試。
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
) 相關聯的 8 位元報表 ID 和位元組,以 BufferSource
(data
) 的形式傳送至 device.sendReport()
。送出報告後,傳回的承諾就會解決。如果 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 裝置,請將與功能報告 (reportId
) 相關聯的 8 位元報告 ID 和位元組,以 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 裝置的功能報告,請將與功能報告 (reportId
) 相關聯的 8 位元報告 ID 傳遞至 device.receiveFeatureReport()
。傳回的承諾會解析包含地圖報告內容的 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 裝置存取權限,因為網站不再需要保留這些權限。舉例來說,如果在有多台裝置共用的電腦上使用教育用途的網路應用程式,大量累積的使用者產生權限會導致使用者體驗不佳。
在單一 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 版適用於所有電腦平台 (ChromeOS、Linux、macOS 和 Windows)。
示範
如要查看部分 WebHID 示範,請前往 web.dev/hid-examples。去看看吧!
安全性和隱私權
規格作者根據「控管強大網路平台功能的存取權」一文中定義的核心原則,設計並實作 WebHID API,包括使用者控制、透明度和人體工學。這個 API 的使用方式主要取決於權限模型,一次只會授予單一 HID 裝置的存取權。使用者必須採取主動步驟,才能選取特定 HID 裝置,回應使用者提示。
如要瞭解安全性取捨,請參閱 WebHID 規格的「安全性和隱私權注意事項」一節。
此外,Chrome 會檢查每個頂層集合的使用情況,如果頂層集合是否具有受保護的用途 (例如一般鍵盤、滑鼠),那麼網站將無法傳送及接收該集合中定義的任何報表。完整的受保護用途清單可供大眾查閱。
請注意,安全性敏感的 HID 裝置 (例如用於更強驗證功能的 FIDO HID 裝置) 也會在 Chrome 中遭到封鎖。請參閱 USB 封鎖清單和 HID 封鎖清單檔案。
意見回饋
Chrome 團隊很樂意聽取你對 WebHID API 的想法和使用體驗。
請說明 API 設計
API 是否有任何功能無法正常運作?或者,您是否缺少實作想法所需的方法或屬性?
在 WebHID API GitHub 存放區中提出規格問題,或在現有問題中加入您的想法。
回報實作問題
您發現 Chrome 實作錯誤嗎?還是實作方式與規格不同?
請參閱「如何回報 WebHID 錯誤」一文。請務必盡可能提供詳細資訊,提供重現錯誤的簡單操作說明,並將「元件」設為 Blink>HID
。Glitch 非常適合用來快速輕鬆地提出重新提案。
顯示支援
您打算使用 WebHID API 嗎?您的公開支援有助於 Chrome 團隊決定功能的優先順序,並向其他瀏覽器供應商顯示支援這些功能的重要性。
使用主題標記 #WebHID
將推文傳送到 @ChromiumDev,並告知我們您使用了這些標記的位置和方式。
實用連結
- 規格
- 追蹤錯誤
- ChromeStatus.com 項目
- Blink 元件:
Blink>HID
特別銘謝
感謝 Matt Reynolds 和 Joe Medley 審查本文。紅色和藍色的 Nintendo Switch 相片由 Sara Kurfeß 提供,黑色和銀色的筆記型電腦相片由 Athul Cyriac Ajay 提供,並在 Unsplash 上發布。