Прошитый контроллер Stadia действует как стандартный геймпад, а это значит, что не все его кнопки доступны с помощью Gamepad API. С помощью WebHID теперь вы можете получить доступ к недостающим кнопкам.
После закрытия Stadia многие опасались, что контроллер окажется бесполезным оборудованием на свалке. К счастью, команда Stadia решила вместо этого открыть контроллер Stadia, предоставив специальную прошивку, которую вы можете прошить на свой контроллер, перейдя на страницу режима Bluetooth Stadia . Благодаря этому ваш контроллер Stadia будет выглядеть как стандартный геймпад, к которому можно подключиться через USB-кабель или по беспроводной сети через Bluetooth. Страница Stadia Bluetooth, с гордостью представленная на Project Fugu API Showcase , сама использует WebHID и WebUSB , но это не тема данной статьи. В этом посте я хочу объяснить, как можно общаться с контроллером Stadia через WebHID.
Контроллер Stadia как стандартный геймпад
После перепрошивки контроллер выглядит как стандартный геймпад операционной системы. На следующем снимке экрана показано общее расположение кнопок и осей стандартного геймпада. Как определено в спецификации Gamepad API , стандартные геймпады имеют кнопки от 0 до 16, то есть всего 17 (крестовина считается за четыре кнопки). Если вы попробуете контроллер Stadia в демо-версии тестера геймпада , вы заметите, что он работает просто великолепно.
Однако если посчитать кнопки на контроллере Stadia, их 19. Если вы будете систематически пробовать их одну за другой в тестере геймпада, то поймете, что кнопки «Ассистент» и «Захват» не работают. Даже если атрибут buttons
геймпада, определенный в спецификации геймпада, является открытым, поскольку контроллер Stadia отображается как стандартный геймпад, сопоставляются только кнопки 0–16. Вы по-прежнему можете использовать другие кнопки, но большинство игр не ожидают их существования.
WebHID спешит на помощь
Благодаря WebHID API можно поговорить с недостающими кнопками 17 и 18. А если очень захотеть, то можно даже получить данные обо всех остальных кнопках и осях, которые уже доступны через Gamepad API. Первый шаг — выяснить, как контроллер Stadia сообщает о себе операционной системе. Один из способов сделать это — открыть консоль Chrome DevTools на любой случайной странице и запросить нефильтрованный список устройств у WebHID API. Затем вы вручную выбираете контроллер Stadia для дальнейшей проверки. Получите нефильтрованный список устройств, просто передав пустой массив параметров filters
.
const [device] = await navigator.hid.requestDevice({filters: []});
В пикере предпоследняя запись выглядит как контроллер Stadia.
После выбора устройства «Stadia Controller rev. A» зарегистрируйте полученный объект HIDDevice
в консоли. Это покажет productId
контроллера Stadia ( 37888
, шестнадцатеричный код 0x9400
) и vendorId
( 6353
, шестнадцатеричный код 0x18d1
). Если вы посмотрите vendorID
в официальной таблице идентификаторов поставщиков USB , вы обнаружите, что 6353
соответствует тому, что вы ожидаете: Google Inc.
Альтернативой описанному выше процессу является переход к chrome://device-log/
в строке URL-адреса, нажатие кнопки «Очистить» , подключение контроллера Stadia, а затем нажатие «Обновить» . Это предоставляет вам ту же информацию.
Еще одна альтернатива — использование инструмента HID Explorer , который позволяет вам еще больше изучить HID-устройства, подключенные к вашему компьютеру.
Используйте эти два идентификатора, vendorId
и productId
, чтобы уточнить то, что отображается в средстве выбора, и теперь правильно фильтровать нужное устройство WebHID.
const [stadiaController] = await navigator.hid.requestDevice({filters: [{
vendorId: 6353,
productId: 37888,
}]});
Теперь шум от всех несвязанных устройств исчез, и отображается только контроллер Stadia.
Далее откройте HIDDevice
, вызвав метод open()
.
await stadiaController.open();
Снова зарегистрируйте HIDDevice
, и флаг opened
будет установлен в true
.
Открыв устройство, прослушивайте входящие события inputreport
, подключив прослушиватель событий.
stadiaController.addEventListener('inputreport', (e) => {
console.log(e);
});
Когда вы нажимаете и отпускаете кнопку «Ассистент» на контроллере, на консоли регистрируются два события. Вы можете думать о них как о событиях «Нажатие кнопки Ассистента » и «Нажатие кнопки Ассистента ». Если не считать timeStamp
, эти два события на первый взгляд кажутся неотличимыми.
Свойство reportId
интерфейса HIDInputReportEvent
возвращает однобайтовый префикс идентификации для этого отчета или 0
, если интерфейс HID не использует идентификаторы отчета. В данном случае это 3
. Секрет кроется в свойстве data
, которое представлено в виде DataView
размером 10. DataView
предоставляет низкоуровневый интерфейс для чтения и записи нескольких типов чисел в двоичном ArrayBuffer
. Чтобы получить что-то более удобоваримое из этого представления, можно создать Uint8Array
из ArrayBuffer
, чтобы вы могли видеть отдельные 8-битные целые числа без знака.
const data = new Uint8Array(event.data.buffer);
Когда вы затем снова регистрируете входные данные о событиях отчета, все становится более понятным, и события «Нажатие кнопки помощника » и «Нажатие кнопки помощника » начинают становиться расшифровываемыми. Первое целое число ( 8
в обоих событиях), похоже, связано с нажатиями кнопок, а второе целое число ( 2
и 0
), похоже, связано с тем, нажата ли кнопка Assistant или нет.
Нажмите кнопку «Захват» вместо кнопки «Ассистент» , и вы увидите, что второе целое число переключается с 1
при нажатии кнопки на 0
при ее отпускании. Это позволяет вам написать очень простой «драйвер», позволяющий использовать недостающие две кнопки.
stadia.addEventListener('inputreport', (event) => {
if (!e.reportId === 3) {
return;
}
const data = new Uint8Array(event.data.buffer);
if (data[0] === 8) {
if (data[1] === 1) {
hidButtons[1].classList.add('highlight');
} else if (data[1] === 2) {
hidButtons[0].classList.add('highlight');
} else if (data[1] === 3) {
hidButtons[0].classList.add('highlight');
hidButtons[1].classList.add('highlight');
} else {
hidButtons[0].classList.remove('highlight');
hidButtons[1].classList.remove('highlight');
}
}
});
Используя такой подход обратного проектирования, вы можете, кнопка за кнопкой и ось за осью, выяснить, как взаимодействовать с контроллером Stadia с помощью WebHID. Как только вы освоите это, все остальное станет почти механической работой по отображению целых чисел.
Единственное, чего сейчас не хватает, — это плавного подключения, которое дает вам Gamepad API. Хотя из соображений безопасности вам всегда необходимо один раз пройти начальный этап выбора, чтобы работать с устройством WebHID, таким как контроллер Stadia, для будущих подключений вы можете повторно подключиться к известным устройствам. Сделайте это, вызвав метод getDevices()
.
let stadiaController;
const [device] = await navigator.hid.getDevices();
if (device && device.vendorId === 6353 && device.productId === 37888) {
stadiaController = device;
}
Демо
Вы можете увидеть контроллер Stadia, управляемый совместно API геймпада и API WebHID, в созданной мной демонстрации . Обязательно ознакомьтесь с исходным кодом , основанным на фрагментах из этой статьи. Для простоты я показываю только кнопки A , B , X и Y (управляемые API геймпада), а также кнопки «Ассистент» и « Захват» (управляемые API WebHID). Под изображением контроллера вы можете увидеть необработанные данные WebHID, чтобы вы могли почувствовать все кнопки и оси контроллера.
Выводы
Благодаря новой прошивке контроллер Stadia теперь можно использовать как стандартный геймпад с 17 кнопками, чего в большинстве случаев более чем достаточно для управления обычными веб-играми. Если по какой-либо причине вам нужны данные со всех 19 кнопок контроллера, WebHID позволяет вам получить доступ к низкоуровневым отчетам о вводе, которые вы можете расшифровать, реконструируя их одну за другой. Если после прочтения этой статьи вам случится написать полноценный драйвер WebHID, обязательно свяжитесь со мной, и я с радостью размещу здесь ссылку на ваш проект. Удачного WebHIDing!
Благодарности
Эту статью рецензировал Франсуа Бофор .