O controle Stadia piscando funciona como um gamepad padrão, o que significa que nem todos os botões são acessíveis usando a API Gamepad. Com o WebHID, agora é possível acessar os botões ausentes.
Desde que o Stadia foi desativado, muitos temiam que o controle se tornasse um hardware inútil no aterro sanitário. Felizmente, a equipe do Stadia decidiu abrir o Controle Stadia, fornecendo um firmware personalizado que pode ser instalado no controle. Acesse a página Modo Bluetooth do Stadia para fazer isso. Isso faz com que o Controle Stadia apareça como um gamepad padrão que pode ser conectado por cabo USB ou sem fio por Bluetooth. A página do Bluetooth do Stadia, apresentada no Showcase de APIs do Project Fugu, usa WebHID e WebUSB, mas esse não é o tema deste artigo. Neste post, vou explicar como você pode se comunicar com o controle do Stadia usando o WebHID.
O Controle Stadia como um gamepad padrão
Após a atualização, o controle aparece como um gamepad padrão para o sistema operacional. Confira a captura de tela a seguir para ver uma disposição comum de botões e 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, ou seja, 17 no total (o direcional conta como quatro botões). Se você testar o controle do Stadia na demonstração do Gamepad Tester, vai perceber que ele funciona muito bem.
No entanto, se você contar os botões no Controle Stadia, são 19. Se você tentar sistematicamente um por um no testador de gamepad, vai perceber que os botões Assistente e Captura 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 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, é possível interagir com os botões 17 e 18 ausentes. E se você quiser, é possível até mesmo receber dados sobre todos os outros botões e eixos que já estão disponíveis na API Gamepad. A primeira etapa é descobrir como o controle do Stadia se informa 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, você escolhe manualmente o controle Stadia para uma inspeção mais detalhada. Para receber uma lista não filtrada de dispositivos, basta transmitir uma matriz de opções filters
vazia.
const [device] = await navigator.hid.requestDevice({filters: []});
No seletor, a penúltima entrada se parece com o controle do 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ê procurar o vendorID
na tabela oficial de IDs de fornecedores USB, vai descobrir que o 6353
é mapeado 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 vai fornecer 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, vendorId
e 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();
Registre o HIDDevice
novamente, e a flag opened
será definida como true
.
Com o dispositivo aberto, detecte eventos inputreport
de entrada anexando um listener de eventos.
stadiaController.addEventListener('inputreport', (e) => {
console.log(e);
});
Quando você pressiona e solta o botão Assistente no controle, dois eventos são registrados no console. Eles podem ser considerados eventos de "botão Google Assistente pressionado" e "botão Google Assistente levantado". 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. Nesse 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 ler e gravar vários tipos de números em um ArrayBuffer
binário. Para tornar essa representação mais fácil de digerir, crie um Uint8Array
do ArrayBuffer
para ver os números inteiros não assinados de 8 bits.
const data = new Uint8Array(event.data.buffer);
Quando você registra os dados de eventos do relatório de entrada novamente, as coisas começam a fazer mais sentido e os eventos "Botão do Google Assistente pressionado" e "Botão do Google Assistente pressionado" começam a ser decifrados. O primeiro número inteiro (8
nos dois eventos) parece estar relacionado a pressionamentos de botão, e o segundo número inteiro (2
e 0
) parece estar relacionado ao botão Google Assistente pressionado ou não.
Pressione o botão Capture em vez do botão Assistente. O segundo número inteiro muda de 1
quando o botão é pressionado para 0
quando ele é liberado. Isso permite que você escreva um "driver" muito simples que permite usar os 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 descobrir como se comunicar com o controle do Stadia com o WebHID, botão por botão e eixo por eixo. Depois de entender o conceito, o resto é um trabalho de mapeamento de inteiros quase mecânico.
O que falta agora é a experiência de conexão tranquila que a API Gamepad oferece. Por motivos de segurança, você precisa passar pela experiência inicial do seletor 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 criei. Confira o código-fonte, que é baseado nos snippets deste artigo. Para simplificar, mostro apenas os botões A, B, X e Y (controlados pela API Gamepad) e os botões Assistente e Captura (controlados pela API WebHID). Abaixo da imagem do controle, você pode conferir 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 da Web comuns. Se, por algum motivo, você precisar de dados de todos os 19 botões do controlador, o WebHID permite acessar 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 e vou vincular seu projeto aqui. Boas-vindas ao WebHID!
Agradecimentos
Este artigo foi revisado por François Beaufort.