Como se conectar a dispositivos HID incomuns

A API WebHID permite que os sites acessem teclados auxiliares alternativos e gamepads exóticos.

François Beaufort
François Beaufort

Há uma cauda longa de dispositivos de interface humana (HIDs), como dispositivos teclados ou gamepads exóticos muito novos, muito antigos ou muito incomuns para serem acessíveis pelos sistemas drivers de dispositivos. A API WebHID resolve isso fornecendo uma de implementar a lógica específica do dispositivo no JavaScript.

Casos de uso sugeridos

Um dispositivo HID recebe entradas de humanos ou fornece saídas para eles. Exemplos de dispositivos incluem teclados, dispositivos apontadores (mouse, tela touch etc.) e gamepads. Com o protocolo HID é possível acessar esses dispositivos no computador computadores que usam drivers de sistema operacional. A plataforma da Web oferece suporte a dispositivos HID por esses drivers.

A incapacidade de acessar dispositivos HID incomuns é particularmente difícil quando chega a teclados auxiliares alternativos (por exemplo, Elgato Stream Deck, Jabra fones de ouvido, X-keys) e suporte a gamepad exótico. Gamepads criados para computadores geralmente usam o HID para entradas (botões, joysticks, gatilhos) e saídas de gamepad (LEDs, barulho). Infelizmente, as entradas e saídas do gamepad não funcionam bem padronizados e navegadores da Web muitas vezes exigem lógica personalizada para dispositivos específicos. Isso é insustentável e resulta em um suporte insuficiente para a cauda longa de tecnologias e dispositivos incomuns. Isso também faz com que o navegador dependa de peculiaridades no comportamento de dispositivos específicos.

Terminologia

O HID consiste em dois conceitos fundamentais: relatórios e descritores de relatórios. Relatórios são os dados trocados entre um dispositivo e um cliente de software. O descritor do relatório descreve o formato e o significado dos dados que o dispositivo suporta.

Um dispositivo de interface humana (HID, na sigla em inglês) é um tipo de dispositivo que recebe entradas gera saídas para humanos. Também se refere ao protocolo HID, um padrão para comunicação bidirecional entre um host e um dispositivo que é projetado para simplificar o procedimento de instalação. O protocolo HID foi desenvolvido para dispositivos USB, mas desde então foi implementado em muitos outros protocolos, incluindo Bluetooth.

Aplicativos e dispositivos HID trocam dados binários por meio de três tipos de relatório:

Tipo de relatório Descrição
Relatório de entrada Dados enviados do dispositivo para o aplicativo (por exemplo, um botão é pressionado).
Relatório de saída Dados enviados do aplicativo para o dispositivo (por exemplo, uma solicitação para ativar a luz de fundo do teclado).
Relatório de recursos Dados que podem ser enviados em qualquer direção. O formato é específico do dispositivo.

Um descritor de relatório descreve o formato binário de relatórios suportados pelo dispositivo. Sua estrutura é hierárquica e pode agrupar relatórios como diferentes dentro da coleção de nível superior. O formato do descritor é: definido pela especificação HID.

O uso de HID é um valor numérico que se refere a uma entrada ou saída padronizada. Os valores de uso permitem que um dispositivo descreva o uso pretendido e as a finalidade de cada campo em seus relatórios. Por exemplo, um bloco é definido para a esquerda botão do mouse. Os usos também são organizados em páginas de uso, que fornecem uma indicação da categoria de alto nível do dispositivo ou relatório.

Como usar a API WebHID

Detecção de recursos

Para verificar se a API WebHID é compatível, use:

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

Abrir uma conexão HID

A API WebHID é assíncrona por padrão para evitar que a interface do site seja o bloqueio de usuários ao aguardar entradas. Isso é importante porque os dados HID podem ser recebidos a qualquer momento, exigindo uma maneira de ouvi-la.

Para abrir uma conexão HID, primeiro acesse um objeto HIDDevice. Para isso, é possível solicite que o usuário selecione um dispositivo chamando navigator.hid.requestDevice() ou escolha uma das navigator.hid.getDevices() que retorna uma lista dos dispositivos aos quais o site recebeu acesso antes.

A função navigator.hid.requestDevice() usa um objeto obrigatório que define filtros. Eles são usados para corresponder a qualquer dispositivo conectado a um fornecedor USB. identificador (vendorId), identificador de produto USB (productId), página de uso (usagePage) e um valor de uso (usage). Você pode obtê-los no Repositório de IDs de USB e o documento de tabelas de uso de HID.

Os vários objetos HIDDevice retornados por essa função representam vários Interfaces HID no mesmo dispositivo físico.

// 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();
Captura de tela de um prompt de dispositivo HID em um site.
Prompt do usuário para selecionar um Nintendo Switch Joy-Con.

Também é possível usar a chave exclusionFilters opcional em navigator.hid.requestDevice() para excluir alguns dispositivos do seletor de navegador que estejam com defeito, por exemplo.

// 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 }],
});

Um objeto HIDDevice contém identificadores de produto e fornecedor USB para o dispositivo. identificação pessoal. O atributo collections é inicializado com uma hierarquia descrição dos formatos de relatório do dispositivo.

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
}

Por padrão, os dispositivos HIDDevice são retornados em um formato "fechado" e precisam ser sejam abertos chamando open() antes do envio ou recebimento de dados.

// Wait for the HID connection to open before sending/receiving data.
await device.open();

Receber relatórios de entrada

Depois que a conexão HID for estabelecida, você poderá lidar com as entradas ouvindo os eventos "inputreport" do dispositivo. Esses eventos contêm os dados HID como um objeto DataView (data), o dispositivo HID ao qual ele pertence. para (device) e o ID do relatório de 8 bits associado ao relatório de entrada (reportId).

Foto do Nintendo Switch vermelha e azul.
Dispositivos Nintendo Switch Joy-Con

Continuando com o exemplo anterior, o código abaixo mostra como detectar qual botão o usuário pressionou em um dispositivo Joy-Con direito para que você possa tente fazer isso em casa.

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

Enviar relatórios de saída

Para enviar um relatório de saída a um dispositivo HID, transmita o ID do relatório de 8 bits associado com o relatório de saída (reportId) e bytes como um BufferSource (data) para device.sendReport(). A promessa retornada é resolvida assim que o relatório é enviada. Se o dispositivo HID não usar IDs de relatórios, defina reportId como 0.

O exemplo abaixo se aplica a um dispositivo Joy-Con e mostra como criá-lo com relatórios de saída.

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

Enviar e receber relatórios de recursos

Os relatórios de recursos são o único tipo de relatório de dados HID que podem ser enviados em ambos direções Eles permitem que dispositivos e aplicativos HID troquem dados não padronizados dados HID. Ao contrário dos relatórios de entrada e saída, os relatórios de recursos não são recebidos ou enviados pelo aplicativo regularmente.

Foto de laptop em preto e prata.
Teclado de laptop

Para enviar um relatório de recursos a um dispositivo HID, transmita o ID do relatório de 8 bits associado com o relatório de recursos (reportId) e bytes como um BufferSource (data) para device.sendFeatureReport(). A promessa retornada é resolvida quando o relatório foi enviado. Se o dispositivo HID não usar IDs de relatórios, defina reportId como 0.

O exemplo abaixo ilustra o uso de relatórios de recursos mostrando como solicitar um dispositivo com luz de fundo do teclado da Apple, abri-lo e fazê-lo piscar.

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

Para receber um relatório de recursos de um dispositivo HID, transmita o ID do relatório de 8 bits associadas ao relatório do recurso (reportId) para device.receiveFeatureReport(). A promessa retornada é resolvida com uma Objeto DataView que tem o conteúdo do relatório do recurso. Se o HID dispositivo não usa IDs de relatórios, defina reportId como 0.

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

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

Ouvir conexão e desconexão

Quando o site recebe permissão para acessar um dispositivo HID, ele pode receber ativamente eventos de conexão e desconexão ouvindo "connect"; e "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.
});

Revogar acesso a um dispositivo HID

O site pode limpar as permissões para acessar um dispositivo HID que ele não é mais interessados em manter chamando forget() na instância do HIDDevice. Para exemplo, para um aplicativo da Web educacional usado em um computador compartilhado com muitos dispositivos, um grande número de permissões geradas pelo usuário gera uma baixa experiência do usuário.

Chamar forget() em uma única instância do HIDDevice revogará o acesso a todos as interfaces HID no mesmo dispositivo físico.

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

Como o forget() está disponível no Chrome 100 ou versões mais recentes, verifique se esse recurso está compatível com:

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

Dicas para desenvolvedores

Depurar o HID no Chrome é fácil com a página interna about://device-log onde você pode ver todos os eventos relacionados a dispositivos HID e USB em um só lugar.

Captura de tela da página interna para depurar o HID.
Página interna no Chrome para depurar HID.

Confira o HID Explorer para despejar dispositivos HID as informações em um formato legível. Ele mapeia de valores de uso a nomes para cada Uso de HID.

Na maioria dos sistemas Linux, os dispositivos HID são mapeados com permissões somente leitura padrão. Para permitir que o Chrome abra um dispositivo HID, você precisará adicionar um novo udev específica. Crie um arquivo em /etc/udev/rules.d/50-yourdevicename.rules com o seguinte conteúdo:

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

Na linha acima, [yourdevicevendor] será 057e se o dispositivo for um Nintendo Switch da Joy-Con, por exemplo. ATTRS{idProduct} também podem ser adicionados para um resultado regra de firewall. Verifique se o user é um membro do grupo plugdev. Depois, reconecte seu dispositivo.

Suporte ao navegador

A API WebHID está disponível para todas as plataformas de computador (ChromeOS, Linux, macOS, e Windows) no Chrome 89.

Demonstrações

Algumas demonstrações do WebHID estão listadas em web.dev/hid-examples. Dê uma olhada!

Segurança e privacidade

Os autores das especificações projetaram e implementaram a API WebHID usando o núcleo definidos em Como controlar o acesso a recursos avançados da Web Platform, incluindo controle do usuário, transparência e ergonomia. A possibilidade de usar A API é controlada principalmente por um modelo de permissão que concede acesso a apenas um um dispositivo HID por vez. Em resposta a um prompt do usuário, o usuário precisa ativar para selecionar um dispositivo HID específico.

Para entender os prós e contras da segurança, confira o módulo Segurança e privacidade Considerações da especificação do WebHID.

Além disso, o Chrome inspeciona o uso de cada coleção de nível superior e se uma coleção de nível superior tiver um uso protegido (por exemplo, teclado genérico, mouse), então um site não poderá enviar nem receber relatórios definidos no coleção. A lista completa de usos protegidos está disponível publicamente.

Dispositivos HID sensíveis à segurança (como os dispositivos HID FIDO usados para autenticação mais forte) também são bloqueados no Chrome. Consulte a lista de bloqueio de USB e Lista de bloqueio de HID.

Feedback

A equipe do Chrome adoraria saber o que você acha e tem experiência com o a API WebHID.

Fale sobre o design da API

Alguma coisa na API não funciona como esperado? Ou há métodos ou propriedades ausentes de que precisa para implementar sua ideia?

Registre um problema de especificação no repositório da API WebHID no GitHub (link em inglês) ou deixe sua opinião a um problema existente.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação diferente das especificações?

Confira Como registrar bugs do WebHID. Não deixe de incluir todos os detalhes possíveis, fornecer instruções simples para reproduzir o bug e Componentes definido como Blink>HID. O Glitch funciona muito bem para o compartilhamento de reproduções rápidas e fáceis.

Mostrar apoio

Você planeja usar a API WebHID? Seu apoio público ajuda o Chrome de segurança da nuvem a priorizar recursos e mostrar a outros fornecedores de navegadores apoiá-los.

Envie um tweet para @ChromiumDev usando a hashtag #WebHID e avise nossa equipe onde e como estão sendo usados.

Links úteis

Agradecimentos

Agradecemos a Matt Reynolds e Joe Medley pelas avaliações deste artigo. Foto do Nintendo Switch vermelha e azul de Sara Kurfeß (em inglês), com laptop em preto e prateado foto de computador de Athul Cyriac Ajay no Unsplash.