असामान्य एचआईडी डिवाइसों से कनेक्ट करना

WebHID API, वेबसाइटों को सहायक कीबोर्ड और अलग तरह के गेमपैड का ऐक्सेस देता है.

François Beaufort
François Beaufort

लोगों के इंटरफ़ेस वाले कई डिवाइस (एचआईडी) बहुत लंबे होते हैं. जैसे, वैकल्पिक कीबोर्ड या अलग तरह के गेमपैड. ये बहुत नए, बहुत पुराने या बहुत असामान्य होते हैं, जिन्हें सिस्टम के डिवाइस ड्राइवर ऐक्सेस नहीं कर पाते. WebHID एपीआई, JavaScript में डिवाइस के हिसाब से बने लॉजिक को लागू करने का तरीका उपलब्ध कराकर इसे ठीक करता है.

इस्तेमाल के सुझाए गए उदाहरण

एचआईडी डिवाइस, इंसानों से इनपुट लेता है या लोगों को आउटपुट देता है. डिवाइसों के उदाहरण में कीबोर्ड, पॉइंटिंग डिवाइस (माइस, टचस्क्रीन वगैरह), और गेमपैड शामिल हैं. एचआईडी प्रोटोकॉल की मदद से, ऑपरेटिंग सिस्टम ड्राइवर का इस्तेमाल करके, इन डिवाइसों को डेस्कटॉप कंप्यूटर पर ऐक्सेस किया जा सकता है. वेब प्लैटफ़ॉर्म इन ड्राइवर पर भरोसा करके एचआईडी डिवाइसों पर काम करता है.

आम तौर पर इस्तेमाल न होने वाले एचआईडी डिवाइसों को ऐक्सेस न कर पाना, खास तौर पर तब तकलीफ़ देने वाला होता है, जब दूसरे सहायक कीबोर्ड (जैसे, Elgato Stream Deck, Jabra हेडसेट, X-keys) और अनोखे गेमपैड का इस्तेमाल किया जाता है. डेस्कटॉप के लिए डिज़ाइन किए गए गेमपैड अक्सर गेमपैड इनपुट (बटन, जॉयस्टिक, ट्रिगर) और आउटपुट (एलईडी, रंबल) के लिए एचआईडी का इस्तेमाल करते हैं. माफ़ करें, गेमपैड इनपुट और आउटपुट का सही तरीके से इस्तेमाल नहीं किया गया है. साथ ही, वेब ब्राउज़र को कुछ खास डिवाइसों के लिए अक्सर कस्टम लॉजिक की ज़रूरत होती है. यह लंबे समय तक नहीं बना रहता है और इस वजह से पुराने और असामान्य डिवाइसों की लंबी टेल ठीक से काम नहीं करती. साथ ही, इससे ब्राउज़र कुछ खास डिवाइसों के व्यवहार की बारीकियों पर भी निर्भर हो जाता है.

शब्दावली

एचआईडी में दो बुनियादी सिद्धांत होते हैं: रिपोर्ट और रिपोर्ट डिस्क्रिप्टर. रिपोर्ट वह डेटा होता है जो डिवाइस और सॉफ़्टवेयर क्लाइंट के बीच शेयर होता है. रिपोर्ट डिस्क्रिप्टर, डिवाइस पर काम करने वाले डेटा के फ़ॉर्मैट और मतलब के बारे में बताता है.

एचआईडी (ह्यूमन इंटरफ़ेस डिवाइस) एक ऐसा डिवाइस है जो इंसानों से इनपुट लेता है या आउटपुट देता है. इसका मतलब एचआईडी प्रोटोकॉल भी है. यह होस्ट और डिवाइस के बीच दो-तरफ़ा कम्यूनिकेशन के लिए एक स्टैंडर्ड है. इसे इंस्टॉल करने की प्रोसेस को आसान बनाने के लिए डिज़ाइन किया गया है. एचआईडी प्रोटोकॉल को मूल रूप से यूएसबी डिवाइसों के लिए बनाया गया था. हालांकि, इसे ब्लूटूथ जैसे कई अन्य प्रोटोकॉल पर लागू किया जा चुका है.

ऐप्लिकेशन और एचआईडी डिवाइस, तीन तरह की रिपोर्ट के ज़रिए बाइनरी डेटा का लेन-देन करते हैं:

रिपोर्ट का टाइप ब्यौरा
इनपुट रिपोर्ट डिवाइस से ऐप्लिकेशन में भेजा गया डेटा (उदाहरण के लिए, एक बटन दबाया जाता है.)
आउटपुट रिपोर्ट ऐप्लिकेशन से डिवाइस को भेजा जाने वाला डेटा (उदाहरण के लिए, कीबोर्ड की बैकलाइट चालू करने का अनुरोध.)
सुविधा रिपोर्ट ऐसा डेटा जिसे किसी भी दिशा में भेजा जा सकता है. फ़ॉर्मैट, डिवाइस के हिसाब से अलग-अलग होता है.

रिपोर्ट डिस्क्रिप्टर, उन रिपोर्ट के बाइनरी फ़ॉर्मैट के बारे में बताता है जो डिवाइस पर काम करते हैं. इसकी स्ट्रक्चर हैरारकी है और यह टॉप लेवल कलेक्शन में रिपोर्ट को अलग-अलग कलेक्शन के तौर पर एक साथ ग्रुप कर सकती है. डिस्क्रिप्टर का फ़ॉर्मैट, एचआईडी की खास बातों से तय होता है.

एचआईडी का इस्तेमाल, अंकों वाली एक वैल्यू होती है. यह स्टैंडर्ड इनपुट या आउटपुट के बारे में बताती है. इस्तेमाल से जुड़ी वैल्यू की मदद से, डिवाइस को अपनी रिपोर्ट में डिवाइस को इस्तेमाल करने के मकसद और हर फ़ील्ड के मकसद के बारे में जानकारी मिलती है. जैसे, एक माउस के बाएं बटन के लिए तय किया गया है. इस्तेमाल को इस्तेमाल से जुड़े पेजों में भी व्यवस्थित किया गया है, जो डिवाइस या रिपोर्ट की हाई-लेवल कैटगरी का संकेत देते हैं.

WebHID एपीआई का इस्तेमाल करना

सुविधा की पहचान करने की सुविधा

यह देखने के लिए कि WebHID API काम करता है या नहीं, इसका इस्तेमाल करें:

if ("hid" in navigator) {
  // The WebHID API is supported.
}

एचआईडी कनेक्शन को चालू करना

WebHID API, डिज़ाइन के हिसाब से एसिंक्रोनस होता है. इससे, इनपुट का इंतज़ार करते समय वेबसाइट के यूज़र इंटरफ़ेस (यूआई) को ब्लॉक होने से रोका जा सकता है. यह ज़रूरी है, क्योंकि एचआईडी डेटा किसी भी समय मिल सकता है और इसे सुनने के लिए एक तरीके की ज़रूरत होती है.

एचआईडी कनेक्शन को खोलने के लिए, पहले HIDDevice ऑब्जेक्ट को ऐक्सेस करें. इसके लिए, उपयोगकर्ता को navigator.hid.requestDevice() पर कॉल करके डिवाइस चुनने का निर्देश दिया जा सकता है. इसके अलावा, navigator.hid.getDevices() में से डिवाइस चुनने का विकल्प भी दिया जा सकता है. इससे, वेबसाइट को उन डिवाइसों की सूची दिखेगी जिनका ऐक्सेस वेबसाइट को पहले दिया गया था.

navigator.hid.requestDevice() फ़ंक्शन में एक ज़रूरी ऑब्जेक्ट होता है, जो फ़िल्टर के बारे में बताता है. इनका इस्तेमाल यूएसबी वेंडर आइडेंटिफ़ायर (vendorId), यूएसबी प्रॉडक्ट आइडेंटिफ़ायर (productId), इस्तेमाल वाले पेज की वैल्यू (usagePage), और इस्तेमाल की वैल्यू (usage) से जुड़े किसी भी डिवाइस को मैच करने के लिए किया जाता है. इन्हें यूएसबी आईडी डेटा स्टोर करने की जगह और एचआईडी के इस्तेमाल की टेबल के दस्तावेज़ से लिया जा सकता है.

इस फ़ंक्शन से दिखाए गए एक से ज़्यादा HIDDevice ऑब्जेक्ट, एक ही डिवाइस पर कई एचआईडी इंटरफ़ेस दिखाते हैं.

// 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 जॉय-कॉन चुनने के लिए उपयोगकर्ता का अनुरोध.

ब्राउज़र पिकर से कुछ ऐसे डिवाइसों को बाहर रखने के लिए, 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 ऑब्जेक्ट में यूएसबी वेंडर और प्रॉडक्ट आइडेंटिफ़ायर होते हैं. इसके 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();

इनपुट रिपोर्ट पाएं

एचआईडी कनेक्शन बन जाने के बाद, डिवाइस के "inputreport" इवेंट सुनकर, आने वाली इनपुट रिपोर्ट को मैनेज किया जा सकता है. उन इवेंट में, एचआईडी डेटा को DataView ऑब्जेक्ट (data), उसका एचआईडी डिवाइस (device), और इनपुट रिपोर्ट (reportId) से जुड़ा 8-बिट रिपोर्ट आईडी होता है.

लाल और नीले रंग के निनटेंडो स्विच की फ़ोटो.
Nintendo Switch जॉय-कॉन डिवाइस.

पिछले उदाहरण की तरह, नीचे दिए गए कोड में आपको यह पता लगाने का तरीका बताया गया है कि उपयोगकर्ता ने जॉय-कॉन राइट डिवाइस पर कौनसा बटन दबाया है, ताकि उम्मीद है कि आप इसे घर पर आज़मा सकें.

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

आउटपुट रिपोर्ट भेजें

किसी एचआईडी डिवाइस को आउटपुट रिपोर्ट भेजने के लिए, आउटपुट रिपोर्ट (reportId) से जुड़े 8-बिट रिपोर्ट आईडी और बाइट को BufferSource (data) के तौर पर device.sendReport() के तौर पर पास करें. रिपोर्ट भेजे जाने के बाद, वापस किया गया प्रॉमिस रिज़ॉल्व हो जाता है. अगर एचआईडी डिवाइस, रिपोर्ट आईडी का इस्तेमाल नहीं करता है, तो reportId को 0 पर सेट करें.

नीचे दिया गया उदाहरण एक जॉय-कॉन डिवाइस पर लागू होता है और आपको दिखाता है कि इसे आउटपुट रिपोर्ट के साथ कैसे मिलाएं.

// 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));

सुविधा की रिपोर्ट भेजें और पाएं

सुविधा रिपोर्ट ही एक तरह की एचआईडी डेटा रिपोर्ट हैं, जो दोनों दिशाओं में जा सकती हैं. इनसे एचआईडी डिवाइसों और ऐप्लिकेशन को, स्टैंडर्ड एचआईडी डेटा का लेन-देन करने की सुविधा मिलती है. इनपुट और आउटपुट रिपोर्ट के उलट, सुविधा की रिपोर्ट ऐप्लिकेशन को नियमित रूप से नहीं मिलती या भेजी नहीं जाती.

काले और सिल्वर रंग वाले लैपटॉप कंप्यूटर की फ़ोटो.
लैपटॉप कीबोर्ड

किसी एचआईडी डिवाइस पर फ़ीचर रिपोर्ट भेजने के लिए, फ़ीचर रिपोर्ट (reportId) से जुड़े 8-बिट रिपोर्ट आईडी और बाइट को BufferSource (data) के तौर पर device.sendFeatureReport() के तौर पर पास करें. रिपोर्ट भेजे जाने के बाद, वापस किया गया प्रॉमिस रिज़ॉल्व हो जाता है. अगर एचआईडी डिवाइस, रिपोर्ट आईडी का इस्तेमाल नहीं करता है, तो 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);
}

किसी एचआईडी डिवाइस से सुविधा की रिपोर्ट पाने के लिए, सुविधा रिपोर्ट (reportId) से जुड़ा 8-बिट रिपोर्ट आईडी device.receiveFeatureReport() को पास करें. दिखाया गया प्रॉमिस DataView ऑब्जेक्ट के साथ रिज़ॉल्व हो जाता है, जिसमें सुविधा रिपोर्ट का कॉन्टेंट शामिल होता है. अगर एचआईडी डिवाइस, रिपोर्ट आईडी का इस्तेमाल नहीं करता है, तो reportId को 0 पर सेट करें.

// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);

// Read feature report contents with dataView.getInt8(), getUint8(), etc...

कनेक्शन और डिसकनेक्ट होने की आवाज़ सुनें

वेबसाइट को किसी एचआईडी डिवाइस को ऐक्सेस करने की अनुमति मिलने के बाद, वह "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.
});

किसी एचआईडी डिवाइस का ऐक्सेस रद्द करना

वेबसाइट, किसी ऐसे एचआईडी डिवाइस को ऐक्सेस करने के लिए अनुमतियां हटा सकती है जिसे अब बनाए रखने में उसकी दिलचस्पी नहीं है. इसके लिए, HIDDevice इंस्टेंस पर forget() को कॉल करें. जैसे, किसी शेयर किए गए कंप्यूटर पर कई डिवाइसों पर इस्तेमाल किए जाने वाले शिक्षा से जुड़े वेब ऐप्लिकेशन में, उपयोगकर्ता की जनरेट की गई बड़ी संख्या में अनुमतियों की वजह से, उपयोगकर्ता का अनुभव खराब होता है.

किसी एक HIDDevice इंस्टेंस पर forget() को कॉल करने से, एक ही डिवाइस पर मौजूद सभी एचआईडी इंटरफ़ेस का ऐक्सेस रद्द हो जाएगा.

// Voluntarily revoke access to this HID device.
await device.forget();

Chrome 100 या इसके बाद के वर्शन में forget() उपलब्ध है. इसलिए, देखें कि यह सुविधा इनके साथ काम करती है या नहीं:

if ("hid" in navigator && "forget" in HIDDevice.prototype) {
  // forget() is supported.
}

डेवलपर के लिए सलाह

Chrome में एचआईडी को डीबग करने के लिए, इंटरनल पेज about://device-log पर जाएं. यहां एक ही जगह पर एचआईडी और यूएसबी डिवाइस से जुड़े सभी इवेंट देखे जा सकते हैं.

एचआईडी को डीबग करने के लिए, अंदरूनी पेज का स्क्रीनशॉट.
एचआईडी को डीबग करने के लिए, Chrome में अंदरूनी पेज.

एचआईडी डिवाइस की जानकारी को ऐसे फ़ॉर्मैट में डालने के लिए एचआईडी एक्सप्लोरर देखें जिसे कोई भी व्यक्ति आसानी से पढ़ सके. यह हर एचआईडी इस्तेमाल के लिए, इस्तेमाल की वैल्यू से लेकर नाम तक मैप करता है.

ज़्यादातर Linux सिस्टम पर, एचआईडी डिवाइसों को डिफ़ॉल्ट रूप से रीड ओनली अनुमतियों के साथ मैप किया जाता है. Chrome को कोई एचआईडी डिवाइस खोलने की अनुमति देने के लिए, आपको एक नया udev नियम जोड़ना होगा. /etc/udev/rules.d/50-yourdevicename.rules पर, नीचे दिए गए कॉन्टेंट वाली फ़ाइल बनाएं:

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

ऊपर दी गई लाइन में, अगर आपका डिवाइस Nintendo Switch जॉय-कॉन है, तो [yourdevicevendor] 057e है. किसी खास नियम के लिए, ATTRS{idProduct} को भी जोड़ा जा सकता है. पक्का करें कि आपका user, plugdev ग्रुप का सदस्य है. इसके बाद, बस अपने डिवाइस को फिर से कनेक्ट करें.

ब्राउज़र समर्थन

WebHID API, Chrome 89 में सभी डेस्कटॉप प्लैटफ़ॉर्म (ChromeOS, Linux, macOS, और Windows) पर उपलब्ध है.

डेमो

कुछ WebHID डेमो, web.dev/hid-examples पर दिए गए हैं. एक नज़र डालें!

सुरक्षा और निजता

उपयोगकर्ताओं ने वेब प्लैटफ़ॉर्म की बेहतर सुविधाओं का ऐक्सेस कंट्रोल करना सेक्शन में बताए गए मुख्य सिद्धांतों के आधार पर WebHID API को डिज़ाइन और लागू किया है. इन सिद्धांतों में उपयोगकर्ता कंट्रोल, पारदर्शिता, और एल्गोरिदम की जानकारी शामिल है. इस एपीआई का इस्तेमाल करने की क्षमता मुख्य रूप से, अनुमति मॉडल से तय होती है. यह मॉडल एक बार में सिर्फ़ एक एचआईडी डिवाइस को ऐक्सेस देता है. उपयोगकर्ता के किसी सवाल के जवाब में, उपयोगकर्ता को किसी खास एचआईडी डिवाइस को चुनने के लिए, ऐक्टिव कार्रवाई करनी होगी.

सुरक्षा से जुड़े जोखिमों को समझने के लिए, WebHID की खास जानकारी का सुरक्षा और निजता से जुड़ी खास बातें सेक्शन देखें.

सबसे अच्छी बात यह है कि Chrome, हर टॉप-लेवल कलेक्शन के इस्तेमाल की जांच करता है. साथ ही, अगर किसी टॉप-लेवल कलेक्शन का इस्तेमाल सुरक्षित तरीके से होता है (जैसे, सामान्य कीबोर्ड, माउस), तो वेबसाइट उस कलेक्शन में तय की गई कोई भी रिपोर्ट न तो भेज पाएगी और न ही पा सकेगी. सुरक्षित इस्तेमाल की पूरी सूची सार्वजनिक रूप से उपलब्ध है.

ध्यान दें कि सुरक्षा के लिहाज़ से संवेदनशील एचआईडी डिवाइस (जैसे, बेहतर तरीके से पुष्टि करने के लिए इस्तेमाल किए जाने वाले FIDO एचआईडी डिवाइस) भी Chrome में ब्लॉक कर दिए जाते हैं. यूएसबी की ब्लॉकलिस्ट और एचआईडी की ब्लॉकलिस्ट फ़ाइलें देखें.

सुझाव/राय दें या शिकायत करें

Chrome टीम को WebHID API के बारे में आपके विचार और अनुभव जानने में खुशी होगी.

हमें इस एपीआई के डिज़ाइन के बारे में बताएं

क्या एपीआई में ऐसा कुछ है जो उम्मीद के मुताबिक काम नहीं करता? इसके अलावा, क्या अपना आइडिया लागू करने के लिए, तरीके या प्रॉपर्टी मौजूद नहीं हैं?

WebHID API GitHub के रेपो में किसी खास समस्या को दर्ज करें या किसी मौजूदा समस्या पर अपने विचार जोड़ें.

लागू करने से जुड़ी समस्या की शिकायत करें

क्या आपको Chrome को लागू करने में कोई गड़बड़ी मिली? या क्या इसे लागू करने का तरीका खास जानकारी से अलग है?

WebHID की गड़बड़ियों को फ़ाइल करने का तरीका जानें. पक्का करें कि ज़्यादा से ज़्यादा जानकारी दी गई हो, गड़बड़ी को ठीक करने के लिए आसान निर्देश दिए गए हों, और कॉम्पोनेंट को Blink>HID पर सेट किया गया हो. Glitch का इस्तेमाल करके, तुरंत और आसान तरीके शेयर करने में मदद मिलती है.

सहायता करें

क्या आपको WebHID एपीआई इस्तेमाल करना है? आपकी सार्वजनिक मदद से, Chrome की टीम को सुविधाओं को प्राथमिकता देने में मदद मिलती है. साथ ही, इससे दूसरे ब्राउज़र वेंडर को पता चलता है कि उनकी मदद करना कितना ज़रूरी है.

हैशटैग #WebHID का इस्तेमाल करके @ChromiumDev को एक ट्वीट भेजें और हमें बताएं कि आप उसका इस्तेमाल कहां और कैसे कर रहे हैं.

मददगार लिंक

स्वीकार हैं

इस लेख पर मिली समीक्षाओं के लिए, मैट रेनॉल्ड्स और जो मेडली का धन्यवाद. सारा कुरफ़ेश की लाल और नीले रंग की Nintendo Switch फ़ोटो और Unsplash पर अथुल साइरिएक अजय की ब्लैक ऐंड सिल्वर लैपटॉप कंप्यूटर फ़ोटो.