الاتصال بأجهزة HID غير مألوفة

تسمح واجهة برمجة التطبيقات WebHID API للمواقع الإلكترونية بالوصول إلى لوحات مفاتيح إضافية بديلة ولوحات ألعاب غير مميّزة.

François Beaufort
François Beaufort

هناك عدد أقل من الأجهزة ذات الواجهة البشرية (HID)، مثل لوحات المفاتيح البديلة أو لوحات الألعاب الغريبة، التي تكون حديثة جدًا أو قديمة جدًا أو يصعب الوصول إليها من قِبل برامج تشغيل الأجهزة. يمكن لواجهة WebHID API حل هذه المشكلة من خلال توفير طريقة لتنفيذ منطق خاص بالجهاز في JavaScript.

حالات الاستخدام المقترَحة

يأخذ جهاز HID مدخلات من المستخدمين أو يقدّم لهم الإخراج. ومن أمثلة الأجهزة لوحات المفاتيح وأجهزة التأشير (أجهزة الماوس والشاشات التي تعمل باللمس وما إلى ذلك) وأجهزة الألعاب. يتيح بروتوكول HID الوصول إلى هذه الأجهزة على أجهزة كمبيوتر مكتبية باستخدام برامج تشغيل نظام التشغيل. ويدعم نظام الويب الأساسي أجهزة HID من خلال الاعتماد على برامج التشغيل هذه.

وتزداد صعوبة الوصول إلى أجهزة HID غير المألوفة عند استخدام لوحات مفاتيح إضافية بديلة (مثل Elgato Stream Deck وسماعات Jabra وX-key) فضلاً عن توافقها مع جهاز تحكُّم غير تقليدي. غالبًا ما تستخدم لوحات الألعاب المصمّمة لأجهزة الكمبيوتر المكتبي واجهة بشرية (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 غير متزامنة بطبيعتها لمنع حظر واجهة مستخدم موقع الويب أثناء انتظار الإدخال. هذه الخطوة مهمة لأنّه يمكن تلقّي بيانات 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();
لقطة شاشة لطلب جهاز 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، مرِّر رقم تعريف التقرير المكوّن من 8 بت والمرتبط بتقرير الميزة (reportId) إلى device.receiveFeatureReport(). يتم تنفيذ الوعد الذي يتم عرضه باستخدام العنصر 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 بتنسيق يمكن للمستخدمين قراءته. فهو يحدد من قيم الاستخدام إلى الأسماء لكل استخدام من استخدامات 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. بعد ذلك، ما عليك سوى إعادة توصيل جهازك.

المتصفحات المتوافقة

تتوفّر واجهة برمجة التطبيقات WebHID API على جميع الأنظمة الأساسية لأجهزة الكمبيوتر المكتبي (ChromeOS وLinux وmacOS وWindows) في الإصدار Chrome 89.

إصدارات تجريبية

يمكن الاطّلاع على بعض عروض WebHID التجريبية على web.dev/hid-examples. ألقِ نظرة!

الأمان والخصوصية

صمّم مؤلفو المواصفات واجهة برمجة التطبيقات WebHID API ونفّذوها باستخدام المبادئ الأساسية المحدّدة في التحكّم في الوصول إلى الميزات الفعّالة لمنصة الويب، بما في ذلك التحكّم في المستخدم والشفافية وهندسة العمل. وتعتمد إمكانية استخدام واجهة برمجة التطبيقات هذه بشكل أساسي على نموذج إذن يمنح إمكانية الوصول إلى جهاز HID واحد فقط في كل مرة. استجابةً لطلب المستخدم، على المستخدم اتّخاذ خطوات نشطة لاختيار جهاز HID معيّن.

للتعرّف على مُفاضلات الأمان، يُرجى الاطّلاع على قسم اعتبارات الأمان والخصوصية في مواصفات WebHID.

علاوة على ذلك، يفحص Chrome استخدام كل مجموعة من المستوى الأعلى، وما إذا كانت المجموعة ذات المستوى الأعلى محمية باستخدام (مثل لوحة المفاتيح العامة والماوس)، فلن يتمكن موقع الويب من إرسال أو تلقي أي تقارير محددة في هذه المجموعة. القائمة الكاملة للاستخدامات المحمية متاحة للجميع.

يُرجى ملاحظة أنّه يتم أيضًا حظر أجهزة HID الحساسة للأمان (مثل أجهزة FIDO HID المستخدمة لمصادقة أقوى) في Chrome. يمكنك الاطّلاع على ملفات القائمة المحظورة لأجهزة USB وقائمة حظر أجهزة HID.

إضافة ملاحظات

يود فريق Chrome معرفة رأيك وتجاربك في استخدام WebHID API.

أخبرنا عن تصميم واجهة برمجة التطبيقات

هل هناك أي مشكلة في واجهة برمجة التطبيقات لا تعمل كما هو متوقع؟ أو هل هناك طرق أو خصائص مفقودة تحتاج إلى تنفيذ فكرتك؟

يمكنك الإبلاغ عن مشكلة في المواصفات في مستودع WebHID API GitHub أو إضافة أفكارك إلى مشكلة حالية.

الإبلاغ عن مشكلة في التنفيذ

هل واجهت خطأً في تنفيذ Chrome؟ أم أن التنفيذ مختلف عن المواصفات؟

اطّلِع على المقالة كيفية الإبلاغ عن أخطاء WebHID. احرص على تضمين أكبر قدر ممكن من التفاصيل، وتوفير تعليمات بسيطة لإعادة إنتاج الخطأ، وضبط المكوّنات على Blink>HID. تعمل Glitch بشكل رائع لمشاركة عمليات إعادة الإنشاء السريعة والسهلة.

إظهار الدعم

هل تخطّط لاستخدام WebHID API؟ يساعد الدعم المتاح للجميع فريق Chrome في إعطاء الأولوية للميزات، كما يوضّح لمورّدي المتصفّحات الآخرين مدى أهمية دعمهم لهذه الميزات.

يمكنك إرسال تغريدة إلى @ChromiumDev باستخدام الهاشتاغ #WebHID وإخبارنا بمكان استخدامك لها وطريقة استخدامك لها.

روابط مفيدة

شكر وتقدير

شكرًا لكل من مات رينولدز وجو ميدلي على مراجعاتهما لهذه المقالة. صورة من Nintendo Switch باللونَين الأحمر والأزرق من إعداد سارة كورفيس، وصورة كمبيوتر محمول باللونَين الأسود والفضي من أثول سيرياك أجاي على Unسبل