התחברות למכשירי HID לא נפוצים

ה-WebHID API מאפשר לאתרים לגשת למקלדות עזר חלופיות ולגיימפאד אקזוטי.

François Beaufort
François Beaufort

יש "זנב ארוך" של מכשירים עם ממשק אנושי (HID), כמו מקלדות חלופיות או גיימפאד אקזוטי, שהם חדשים מדי, ישנים מדי או לא נפוצים מדי למנהלי המכשירים של המערכות שלנו. ה-WebHID API פותר את הבעיה באמצעות שיטה להטמעת לוגיקה ספציפית למכשיר ב-JavaScript.

תרחישים לדוגמה

מכשיר ממשק אנושי (HID) מקבל קלט מבן אדם או מספק פלט לגביו. דוגמאות למכשירים כוללות מקלדות, התקני הצבעה (עכברים, מסכי מגע וכו') ופקדי משחקים. פרוטוקול HID מאפשר לגשת למכשירים האלה במחשבים שולחניים באמצעות מנהלי התקנים של מערכת ההפעלה. פלטפורמת האינטרנט תומכת במכשירי ממשק אנושי (HID) על ידי הסתמכות על מנהלי ההתקנים האלה.

חוסר הגישה למכשירי ממשק אנושי (HID) שאינם נפוצים מכאיב במיוחד כשמוסיפים אותם למקלדות עזר חלופיות (למשל Elgato Stream Deck, אוזניות Jabra ו-X-keys) ותמיכה בגיימפאד אקזוטי. בגיימפאדים שמיועדים למחשבים שולחניים משתמשים בדרך כלל במכשיר ממשק אנושי (HID) לקלט של גיימפאד (לחצנים, ג'ויסטיק, ג'ויסטיק, טריגרים) וליציאות (LED, רעשן). לצערנו, נתוני הקלט והפלט של הגיימפאד לא סטנדרטיים, ולרוב לדפדפני אינטרנט נדרשת לוגיקה מותאמת אישית למכשירים ספציפיים. המצב הזה לא בר קיימא וגורם לתמיכה גרועה בזנב הארוך של מכשירים ישנים ולא נפוצים. זה גם גורם לדפדפן להסתמך על תכונות מוזרות בהתנהגות של מכשירים מסוימים.

הסברים על המונחים

ממשק אנושי (HID) מורכב משני מושגים בסיסיים: דוחות ותיאורי דוחות. דוחות הם הנתונים שמועברים בין מכשיר ללקוח תוכנה. מתאר הדוח מתאר את הפורמט והמשמעות של הנתונים שנתמכים במכשיר.

מכשיר ממשק אנושי (HID) הוא סוג של מכשיר שמקבל קלט או מספק פלט לבני אדם. הוא מתייחס גם לפרוטוקול HID, תקן לתקשורת דו-כיוונית בין מארח למכשיר שנועד לפשט את תהליך ההתקנה. פרוטוקול HID פותח במקור להתקני USB, אבל מאז הוטמע בפרוטוקולים רבים אחרים, כולל Bluetooth.

אפליקציות ומכשירי ממשק אנושי מחליפים נתונים בינאריים באמצעות שלושה סוגי דוחות:

סוג הדוח תיאור
דוח קלט נתונים שנשלחים מהמכשיר אל האפליקציה (למשל, לחיצה על לחצן).
דוח פלט נתונים שנשלחים מהאפליקציה למכשיר (למשל, בקשה להפעלת התאורה האחורית של המקלדת).
דוח תכונות נתונים שעשויים להישלח בכל כיוון. הפורמט הוא ספציפי למכשיר.

מתאר דוח מתאר את הפורמט הבינארי של דוחות שנתמכים במכשיר. המבנה שלו היררכי, ויכול לקבץ דוחות יחד כאוספים נפרדים בתוך האוסף ברמה העליונה. הפורמט של התיאור מוגדר במפרט ה-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 ומהמסמך של טבלאות השימוש ב-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();
צילום מסך של הנחיה לגבי מכשיר ממשק אנושי באתר.
הודעה למשתמש לבחירת 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 ביט שמשויך לדוח הקלט (reportId).

החלפת תמונה של nintendo בצבע אדום וכחול.
מכשירי Nintendo Switch Joy-Con.

בהמשך לדוגמה הקודמת, הקוד הבא מראה איך לזהות על איזה לחצן המשתמש לחץ במכשיר Joy-ConRight, כדי שתוכלו בתקווה לנסות אותו בבית.

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 ביט שמשויך לדוח הפלט (reportId) ובייטים בתור BufferSource (data) אל device.sendReport(). ההבטחה שמוחזרת מסתיימת אחרי שהדוח נשלח. אם מכשיר ממשק אנושי (HID) לא משתמש במזהי דוחות, צריך להגדיר את 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 ביט שמשויך לדוח התכונות (reportId) ובייטים בתור BufferSource (data) אל device.sendFeatureReport(). ההבטחה שמוחזרת מסתיימת אחרי שהדוח נשלח. אם מכשיר ממשק אנושי (HID) לא משתמש במזהי דוחות, צריך להגדיר את 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, צריך להעביר אל device.receiveFeatureReport() את מזהה הדוח ב-8 ביט שמשויך לדוח התכונות (reportId). ההבטחה שמוחזרת מסתיימת עם אובייקט DataView שמכיל את התוכן של דוח התכונות. אם מכשיר ה-HID לא משתמש במזהי דוחות, צריך להגדיר את 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) שהוא כבר לא מעוניין לשמור על ידי קריאה ל-forget() במכונה של HIDDevice. לדוגמה, באפליקציית אינטרנט חינוכית שפועלת במחשב משותף עם מכשירים רבים, כמות גדולה של הרשאות שנוצרו על ידי משתמשים יוצרת חוויית משתמש גרועה.

הפעלת הפקודה forget() במכונה אחת של HIDDevice תבטל את הגישה לכל ממשקי ה-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.
}

טיפים למפתחים

קל לנפות באגים ב-HID ב-Chrome באמצעות הדף הפנימי, about://device-log שבו אפשר לראות את כל האירועים שקשורים ל-HID ולהתקן USB במקום אחד.

צילום מסך של הדף הפנימי לניפוי באגים ב-HID.
דף פנימי ב-Chrome לניפוי באגים ב-HID.

לייצא את המידע של מכשיר ה-HID לפורמט קריא (לבני אדם). הוא ממופה מערכי השימוש לשמות של כל שימוש ב-HID.

ברוב מערכות Linux, מכשירי ממשק אנושי ממופים עם הרשאות לקריאה בלבד כברירת מחדל. כדי לאפשר ל-Chrome לפתוח מכשיר ממשק אנושי (HID), צריך להוסיף כלל udev חדש. יוצרים קובץ ב-/etc/udev/rules.d/50-yourdevicename.rules עם התוכן הבא:

KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

בשורה שלמעלה, [yourdevicevendor] הוא 057e אם המכשיר שלכם הוא Nintendo Switch Joy-Con. אפשר להוסיף את ATTRS{idProduct} גם לכלל ספציפי יותר. צריך לוודא ש-user שלך חבר בקבוצה plugdev. לאחר מכן פשוט מחברים מחדש את המכשיר.

תמיכת דפדפן

WebHID API זמין בכל הפלטפורמות למחשבים (ChromeOS, Linux, macOS ו-Windows) ב-Chrome 89.

הדגמות

חלק מההדגמות של 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. גליץ' הוא כלי מעולה לשיתוף גיבויים מהירים וקלים.

הבעת תמיכה

בכוונתך להשתמש ב-WebHID API? התמיכה הציבורית עוזרת לצוות של Chrome לתעדף תכונות, ומראה לספקי דפדפנים אחרים עד כמה חשוב התמיכה בהן.

שלח ציוץ אל @ChromiumDev באמצעות ה-hashtag #WebHID וספר לנו איפה ואיך אתם משתמשים בו.

קישורים שימושיים

אישורים

תודה למאט ריינולדס ולג'ו מדלי על הביקורות שלהם על המאמר הזה. תמונה של נינטנדו סוויץ' בצבעי אדום וכחול מאת שרה קורס, ותמונה של מחשב נייד בשחור וכסף מאת אתול סיריאק אג'יי ב-UnFlood.