Контроллер Stadia с подсветкой работает как стандартный геймпад, поэтому не все его кнопки доступны через API Gamepad. С помощью WebHID вы можете получить доступ к отсутствующим кнопкам.
С момента закрытия Stadia многие опасались, что контроллер окажется бесполезным железом на свалке. К счастью, команда Stadia решила вместо этого открыть контроллер Stadia, предоставив специальную прошивку, которую вы можете прошить на своем контроллере, перейдя на страницу режима Bluetooth Stadia . Это сделает ваш контроллер Stadia стандартным геймпадом, к которому можно подключиться по USB-кабелю или по беспроводной связи через Bluetooth. Сама страница Stadia Bluetooth, с гордостью представленная на Project Fugu API Showcase , использует WebHID и WebUSB , но это не тема этой статьи. В этой публикации я хочу объяснить, как можно общаться с контроллером Stadia через WebHID.
Контроллер Stadia как стандартный геймпад
После прошивки контроллер отображается в операционной системе как стандартный геймпад . На следующем снимке экрана показано расположение кнопок и осей на стандартном геймпаде. Согласно спецификации API геймпада , стандартные геймпады имеют кнопки с номерами от 0 до 16, то есть всего 17 (крестовина считается четырьмя кнопками). Если вы попробуете контроллер Stadia в демоверсии тестера геймпадов , вы заметите, что он работает просто идеально.
Однако, если посчитать кнопки на контроллере Stadia, их 19. Если вы будете систематически нажимать их одну за другой в тестере геймпадов, то обнаружите, что кнопки Assistant и Capture не работают. Даже если атрибут buttons
геймпада, определённый в спецификации геймпада, не имеет ограничений, поскольку контроллер Stadia отображается как стандартный геймпад, назначены только кнопки с 0 по 16. Вы по-прежнему можете использовать остальные кнопки, но большинство игр не ожидают их наличия.
WebHID спешит на помощь
Благодаря API WebHID вы можете получить доступ к отсутствующим кнопкам 17 и 18. А при желании можно даже получить данные обо всех остальных кнопках и осях, которые уже доступны через API Gamepad. Первый шаг — выяснить, как контроллер Stadia сообщает о себе операционной системе. Один из способов сделать это — открыть консоль Chrome DevTools на любой случайной странице и запросить неотфильтрованный список устройств из API WebHID. Затем вы вручную выбираете контроллер 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/
в адресной строке, нажатие кнопки «Очистить» , подключение контроллера 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
), по-видимому, связано с тем, нажата ли кнопка Ассистента .
Нажмите кнопку «Захват» вместо кнопки «Помощник» , и вы увидите, что второе целое число переключается с 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. Как только вы освоитесь, всё остальное будет практически механической работой по целочисленному сопоставлению.
Единственное, чего сейчас не хватает, — это плавного подключения, которое обеспечивает API Gamepad. Хотя из соображений безопасности вам всегда необходимо пройти первоначальный этап выбора для работы с устройством WebHID, таким как контроллер Stadia, для последующих подключений вы можете повторно подключаться к известным устройствам. Для этого вызовите метод getDevices()
.
let stadiaController;
const [device] = await navigator.hid.getDevices();
if (device && device.vendorId === 6353 && device.productId === 37888) {
stadiaController = device;
}
Демо
В созданной мной демонстрации вы можете увидеть контроллер Stadia, управляемый совместно API Gamepad и API WebHID. Обязательно ознакомьтесь с исходным кодом , который основан на фрагментах из этой статьи. Для простоты я показываю только кнопки A , B , X и Y (управляемые API Gamepad), а также кнопки Assistant и Capture (управляемые API WebHID). Под изображением контроллера вы видите необработанные данные WebHID, чтобы вы могли понять назначение всех кнопок и осей на контроллере.
Выводы
Благодаря новой прошивке контроллер Stadia теперь можно использовать как стандартный геймпад с 17 кнопками, чего в большинстве случаев более чем достаточно для управления обычными веб-играми. Если по какой-либо причине вам нужны данные со всех 19 кнопок контроллера, WebHID позволяет получить доступ к низкоуровневым отчётам о вводе, которые можно расшифровать, проведя обратную разработку каждого из них по отдельности. Если после прочтения этой статьи вам удастся написать полноценный драйвер WebHID, обязательно свяжитесь со мной, и я с радостью размещу здесь ссылку на ваш проект. Удачного WebHID-а!
Благодарности
Рецензент этой статьи — Франсуа Бофор .