الاتصال بأجهزة 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، ولكن تم تنفيذه منذ ذلك الحين على العديد من البروتوكولات الأخرى، بما في ذلك البلوتوث.

تتبادل التطبيقات وأجهزة 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) على موقع إلكتروني
طلب من المستخدم اختيار وحدة تحكّم Joy-Con من Nintendo Switch

يمكنك أيضًا استخدام المفتاح الاختياري 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-Con Right، حتى تتمكّن من تجربة ذلك في المنزل.

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]}.`);
});

يمكنك الاطّلاع على العرض التوضيحي webhid-joycon-button الخاص بالقلم.

إرسال تقارير النتائج

لإرسال تقرير إخراج إلى جهاز 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));

يمكنك الاطّلاع على العرض التوضيحي webhid-joycon-rumble الخاص بالقلم.

إرسال تقارير عن الميزات وتلقّيها

تقارير الميزات هي النوع الوحيد من تقارير بيانات 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);
}

يمكنك الاطّلاع على العرض التوضيحي webhid-apple-keyboard-backlight الخاص بالقلم.

لتلقّي تقرير ميزة من جهاز 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() يتوفّر في الإصدار 100 من Chrome أو الإصدارات الأحدث، تحقَّق مما إذا كانت هذه الميزة متوافقة مع ما يلي:

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 إذا كان جهازك هو وحدة تحكّم Joy-Con من Nintendo Switch، على سبيل المثال. يمكن أيضًا إضافة ATTRS{idProduct} لقاعدة أكثر تحديدًا. تأكَّد من أنّ user هو عضو في مجموعة plugdev. بعد ذلك، ما عليك سوى إعادة توصيل جهازك.

دعم المتصفح

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

العروض التوضيحية

يمكنك الاطّلاع على بعض عروض 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.

إظهار الدعم

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

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

روابط مفيدة

الإقرارات

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