התחברות למכשירי 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 (Human Interface Device) הוא סוג של מכשיר שמקבל קלט מבני אדם או מספק להם פלט. הוא מתייחס גם לפרוטוקול HID, תקן לתקשורת דו-כיוונית בין מארח למכשיר, שנועד לפשט את תהליך ההתקנה. פרוטוקול ה-HID פותח במקור למכשירי USB, אבל מאז הוא יושם בפרוטוקולים רבים אחרים, כולל Bluetooth.

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

תמונה של Nintendo Switch בצבע אדום וכחול.
מכשירי 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 Explorer כדי להציג את פרטי מכשירי ממשק אנושי (HID) בפורמט קריא לאנשים. הוא ממפה ערכים של שימוש לשמות לכל שימוש ב-HID.

ברוב מערכות Linux, מכשירי HID ממופה עם הרשאות לקריאה בלבד כברירת מחדל. כדי לאפשר ל-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. לאחר מכן, מחברים מחדש את המכשיר.

תמיכה בדפדפנים

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

הדגמות

חלק מההדגמות של WebHID מפורטות בכתובת web.dev/hid-examples. כדאי לך להיכנס ולראות.

אבטחה ופרטיות

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

כדי להבין את הפשרות באבטחה, כדאי לעיין בקטע שיקולים לגבי אבטחה ופרטיות במפרט WebHID.

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

חשוב לדעת שמכשירי HID עם רגישות אבטחה (כמו מכשירי HID של FIDO המשמשים לאימות חזק יותר) חסומים גם ב-Chrome. רשימת החסימות של USB ורשימת החסימות של HID

משוב

הצוות של Chrome ישמח לשמוע מה דעתכם לגבי השימוש ב-WebHID API.

מתארים את עיצוב ה-API

האם יש משהו ב-API שלא פועל כצפוי? או אולי חסרות שיטות או מאפיינים שדרושים לכם כדי להטמיע את הרעיון?

אתם יכולים לשלוח דיווח על בעיה במפרט במאגר GitHub של WebHID API או להוסיף את המחשבות שלכם לבעיה קיימת.

דיווח על בעיה בהטמעה

מצאת באג באופן ההטמעה של Chrome? או שההטמעה שונה מהמפרט?

איך שולחים באגים בנושא WebHID חשוב לכלול כמה שיותר פרטים, לספק הוראות פשוטות לשחזור הבאג ולהגדיר את Components כ-Blink>HID. Glitch הוא כלי מצוין לשיתוף שחזור מהיר וקל של הבעיה.

הצגת תמיכה

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

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

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

תודות

תודה לMatt Reynolds ול-Joe Medley על הביקורות שלהם על המאמר הזה. תמונה של Nintendo Switch בצבע אדום וכחול, צילום: Sara Kurfeß. תמונה של מחשב נייד בצבע שחור וכסוף, צילום: Athul Cyriac Ajay ב-Unsplash.