O controle Stadia com flash funciona como um gamepad padrão, o que significa que nem todos os botões podem ser acessados usando a API Gamepad. Com a WebHID, agora é possível acessar os botões que não estavam aparecendo.
Desde que o Stadia foi desativado, muitos temiam que o controle acabasse como um hardware inútil em um aterro sanitário. Felizmente, a equipe do Stadia decidiu abrir o Controle Stadia, fornecendo um firmware personalizado que pode ser instalado no controle acessando a página do modo Bluetooth do Stadia. Assim, o Controle Stadia aparece como um gamepad padrão que pode ser conectado por cabo USB ou sem fio por Bluetooth. Destaque na vitrine de APIs do Projeto Fugu, a página do Bluetooth do Stadia usa WebHID e WebUSB, mas esse não é o assunto deste artigo. Nesta postagem, quero explicar como você pode conversar com o Controle Stadia usando a WebHID.
O Controle Stadia como um gamepad padrão
Depois da atualização, o controlador aparece como um gamepad padrão para o sistema operacional. Confira a captura de tela a seguir para ver um botão comum e um arranjo de eixos em um gamepad padrão. Conforme definido na especificação da API Gamepad, os gamepads padrão têm botões de 0 a 16, totalizando 17 (o d-pad conta como quatro botões). Se você testar o controle do Stadia na demonstração do testador de gamepad, vai perceber que ele funciona perfeitamente.
No entanto, se você contar os botões do Controle Stadia, vai encontrar 19. Se você testar um por um no testador de gamepad, vai perceber que os botões Assistente e Capturar não funcionam. Mesmo que o atributo buttons
do gamepad, conforme definido na especificação do Gamepad, seja aberto, como o controle do Stadia aparece como um gamepad padrão, apenas os botões de 0 a 16 são mapeados. Você ainda pode usar os outros botões, mas a maioria dos jogos não espera que eles existam.
WebHID ao resgate
Graças à API WebHID, você pode conversar com os botões 17 e 18 ausentes. E, se quiser, você pode até receber dados sobre todos os outros botões e eixos já disponíveis na API Gamepad. A primeira etapa é descobrir como o Controle Stadia se reporta ao sistema operacional. Uma maneira de fazer isso é abrir o console do Chrome DevTools em qualquer página aleatória e solicitar uma lista não filtrada de dispositivos da API WebHID. Em seguida, escolha manualmente o Controle Stadia para mais inspeções. Para receber uma lista sem filtros, basta transmitir uma matriz de opções filters
vazia.
const [device] = await navigator.hid.requestDevice({filters: []});
No seletor, a penúltima entrada parece o Controle Stadia.
Depois de selecionar o dispositivo "Stadia Controller rev. A", registre o objeto HIDDevice
resultante no console. Isso revela o productId
(37888
, que é 0x9400
em hexadecimal) e o vendorId
(6353
, que é 0x18d1
em hexadecimal) do Controle Stadia. Se você pesquisar o vendorID
na tabela oficial de IDs de fornecedor USB, vai descobrir que 6353
mapeia para o que você esperaria: Google Inc.
.
Uma alternativa ao fluxo descrito acima é navegar até chrome://device-log/
na barra de URL, pressionar o botão Limpar, conectar o Controle Stadia e pressionar Atualizar. Isso fornece as mesmas informações.
Outra alternativa é usar a ferramenta HID Explorer, que permite conferir ainda mais detalhes dos dispositivos HID conectados ao computador.
Use esses dois IDs, o vendorId
e o productId
, para refinar o que é mostrado no seletor, filtrando corretamente o dispositivo WebHID certo.
const [stadiaController] = await navigator.hid.requestDevice({filters: [{
vendorId: 6353,
productId: 37888,
}]});
Agora o ruído de todos os dispositivos não relacionados desapareceu, e apenas o Controle Stadia aparece.
Em seguida, abra o HIDDevice
chamando o método open()
.
await stadiaController.open();
Faça login novamente, e a flag opened
será definida como true
.HIDDevice
Com o dispositivo aberto, ouça os eventos inputreport
recebidos anexando um listener de eventos.
stadiaController.addEventListener('inputreport', (e) => {
console.log(e);
});
Quando você pressiona e solta o botão Assistente no controlador, dois eventos são registrados no console. Pense neles como eventos "botão do Assistente pressionado" e "botão do Assistente solto". Além do timeStamp
, os dois eventos parecem indistinguíveis à primeira vista.
A propriedade reportId
da interface HIDInputReportEvent
retorna o prefixo de identificação de um byte para esse relatório ou 0
se a interface HID não usar IDs de relatório. Neste caso, é 3
. O segredo está na propriedade data
, que é representada como um DataView
de tamanho 10. Um DataView
fornece uma interface de baixo nível para leitura e gravação de vários tipos de números em um ArrayBuffer
binário. Para ter algo mais fácil de entender dessa representação, crie um Uint8Array
com base no ArrayBuffer
para ver os números inteiros não assinados de 8 bits individuais.
const data = new Uint8Array(event.data.buffer);
Quando você registra os dados do evento de relatório de entrada novamente, as coisas começam a fazer mais sentido, e os eventos "Botão Assistente pressionado" e "Botão Assistente solto" começam a ficar decifráveis. O primeiro número inteiro (8
nos dois eventos) parece estar relacionado a toques de botão, e o segundo (2
e 0
) parece estar relacionado ao fato de o botão do Assistente ter sido pressionado ou não.
Pressione o botão Capturar em vez do botão Assistente. Você vai notar que o segundo número inteiro alterna de 1
quando o botão é pressionado para 0
quando ele é solto. Isso permite que você escreva um "driver" muito simples que possibilita o uso dos dois botões ausentes.
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');
}
}
});
Usando uma abordagem de engenharia reversa como essa, você pode, botão por botão e eixo por eixo, descobrir como se comunicar com o Controle Stadia usando o WebHID. Depois de pegar o jeito, o restante é quase um trabalho mecânico de mapeamento de números inteiros.
O que está faltando agora é a experiência de conexão tranquila que a API Gamepad oferece. Por motivos de segurança, você sempre precisa passar pela experiência inicial do seletor uma vez para trabalhar com um dispositivo WebHID, como o Controle Stadia. No entanto, para conexões futuras, é possível se reconectar a dispositivos conhecidos. Para fazer isso, chame o método getDevices()
.
let stadiaController;
const [device] = await navigator.hid.getDevices();
if (device && device.vendorId === 6353 && device.productId === 37888) {
stadiaController = device;
}
Demonstração
Você pode conferir o controle do Stadia controlado em conjunto pela API Gamepad e pela API WebHID em uma demonstração que eu criei. Confira o código-fonte, que se baseia nos snippets deste artigo. Para simplificar, só mostro os botões A, B, X e Y (controlados pela API Gamepad) e os botões Assistente e Capturar (controlados pela API WebHID). Abaixo da imagem do controle, você pode ver os dados brutos do WebHID para ter uma ideia de todos os botões e eixos do controle.
Conclusões
Graças ao novo firmware, o Controle Stadia agora pode ser usado como um gamepad padrão com 17 botões, o que, na maioria dos casos, é mais do que suficiente para controlar jogos comuns da Web. Se, por qualquer motivo, você precisar de dados de todos os 19 botões do controlador, a WebHID permitirá o acesso a relatórios de entrada de baixo nível que podem ser decifrados por engenharia reversa, um por um. Se você escrever um driver WebHID completo depois de ler este artigo, entre em contato comigo. Vou adorar vincular seu projeto aqui. Aproveite o WebHID!
Agradecimentos
Este artigo foi revisado por François Beaufort.