Português · English · Español · 中文 · 日本語 · 한국어
Controle virtual para o PC, no celular. O iPhone abre uma página (ou um app na Tela de Início), manda os toques pela rede local, e um servidor em Rust no PC cria um gamepad ou teclado virtual (uinput, Linux) que qualquer jogo ou programa enxerga como um dispositivo de verdade.
- Perfis: Xbox 360, DualShock 4, Switch Pro e teclado.
- Multi-toque, analógicos virtuais, gatilhos e direcional, em paisagem e tela cheia.
- Latência à vista: medidor com ping/pong e, se o Wi-Fi do celular "dormir", um modo de ping curto.
- Layout dos botões editável: mover, redimensionar, ocultar e soltar botões um a um.
- Seis idiomas: português, inglês, espanhol, chinês (simplificado), japonês e coreano, escolhidos pelo idioma do celular ou no painel.
- Seguro para a rede local: HTTPS com autoridade própria, pareamento por código e aprovação no PC.
Alvo atual: Linux (testado no Manjaro) e iPhone (Safari / app da Tela de Início). Windows não é suportado.
- Requisitos
- Instalação
- Primeiro uso
- Uso no dia a dia
- Opções de linha de comando
- Layout dos botões
- Modo teclado
- Segurança
- Arquivos de configuração
- Estrutura do projeto
- Desenvolvimento
- Problemas comuns
- Licença
No PC:
- Linux com o módulo
uinpute permissão de escrita em/dev/uinput. - Rust (edição 2024, ou seja, 1.85 ou mais novo) para compilar o servidor.
- Node.js e npm para compilar a página (Vite + Svelte).
No celular:
- iPhone com Safari. O PC e o celular precisam estar na mesma rede local.
Para liberar o uinput:
sudo modprobe uinput
# permissão para o seu usuário (exemplo com ACL):
sudo setfacl -m u:$USER:rw /dev/uinputPara o módulo carregar sozinho a cada boot, crie /etc/modules-load.d/uinput.conf com a linha
uinput. A permissão por ACL (ou uma regra do udev) também precisa persistir se você quiser
que sobreviva a reiniciar o PC.
Se você só quer usar, baixe o pacote da última versão (vpad-X.Y.Z-linux-x86_64.tar.gz, com o .sha256 ao lado), confira a soma e extraia. Não precisa de Rust nem de Node, mas o uinput dos requisitos continua necessário. Há pacotes para x86_64 (Intel/AMD) e aarch64 (ARM de 64 bits, como o Raspberry Pi 4/5 com sistema de 64 bits): no ARM, use o nome com aarch64 no lugar de x86_64. Depois siga o primeiro uso.
sha256sum -c vpad-X.Y.Z-linux-x86_64.tar.gz.sha256
tar xzf vpad-X.Y.Z-linux-x86_64.tar.gz
cd vpad-X.Y.Z-linux-x86_64
./vpadBaixe o projeto e entre na pasta:
git clone https://github.com/Wpaif/vpad.git
# ou, com SSH: git clone git@github.com:Wpaif/vpad.git
cd vpadA página fica embutida no binário (em compilação de release; em debug o servidor lê web/dist
do disco), então compile a página antes do servidor:
cd web && npm install && npm run build && cd ..
cargo build --releaseO binário sai em target/release/vpad. Se você mudar qualquer coisa em web/, rode
npm run build e cargo build --release de novo.
-
Inicie o servidor no PC:
./target/release/vpad
O terminal mostra um QR code, o endereço do controle (
https://IP-DO-PC:8443), a impressão digital SHA-256 da autoridade de certificação e os IPs cobertos pelo certificado. -
Instale o certificado no iPhone (uma vez só). Na primeira execução o servidor cria uma autoridade de certificação própria e abre por 5 minutos uma página de configuração em HTTP (
http://IP-DO-PC:8080). Para abri-la de novo mais tarde, digitecno terminal. No iPhone (a página abre no idioma do aparelho e tem links para trocar de idioma no topo; os nomes dos menus do iOS abaixo são os da versão em português):- Abra a página de configuração e toque em Baixar o perfil → Permitir.
- Em Ajustes → Perfil baixado, toque no certificado e em Mais detalhes, e confira se a impressão digital SHA-256 é igual à do terminal do PC. Só então toque em Instalar.
- Em Ajustes → Geral → Sobre → Ajustes de Confiança de Certificado, ative a confiança em vpad.
-
Pareie o aparelho. Por padrão o pareamento fica fechado. No terminal do PC, pressione Enter: ele abre o pareamento por 2 minutos e mostra um código de 6 dígitos que muda a cada 30 segundos. No iPhone, abra
https://IP-DO-PC:8443e digite o código. O PC pede a sua aprovação:saprova,taprova e libera também o modo teclado, qualquer outra tecla nega. -
Instale como app (opcional, recomendado): no Safari, Compartilhar → Adicionar à Tela de Início. O app abre em tela cheia e na horizontal.
O app da Tela de Início tem armazenamento próprio, separado do Safari: ele precisa ser pareado por conta própria. Se você reinstalar o app, é preciso parear de novo; o layout dos botões volta sozinho, porque fica guardado também no PC.
Com o servidor rodando, abra o app no iPhone. Ele reconecta sozinho se o Wi-Fi cair.
- Pílula de status (no topo): mostra a latência. Tocar nela abre o painel com a escolha do perfil, as estatísticas (último, média, mínimo, máximo, jitter, perda), o botão de ping curto e o acesso ao editor de layout.
- Olho (topo): oculta ou mostra os controles.
- Idioma: o painel tem um seletor com os seis idiomas. Na primeira abertura vale o idioma do celular (quem fala uma língua sem tradução recebe o inglês); trocar recarrega a página.
- Ping curto: se a latência sobe quando você fica sem tocar, é o Wi-Fi do iPhone entrando em economia de energia. Ligar o ping curto manda um aviso a cada 100 ms e mantém o rádio acordado (gasta mais bateria do celular).
Comandos do terminal do servidor (digite e tecle Enter):
| Comando | O que faz |
|---|---|
Enter |
abre o pareamento por 2 minutos e mostra o código |
f ou close |
fecha o pareamento agora |
c |
abre por 5 minutos a página que instala o certificado |
? |
ajuda |
s ou y / t ou k / outra |
responde a um pedido de aprovação: sim / sim e liberar o teclado / negar |
vpad [--perfil <xbox360|ds4|switch-pro|teclado>] [--porta <1-65535>]
[--porta-https <1-65535>] [--parear]
vpad --dispositivos
vpad --revogar <id|todos>
vpad --permitir-teclado <id> | --negar-teclado <id>
| Opção | Significado |
|---|---|
--perfil |
perfil inicial (o celular pode trocar depois). Padrão: xbox360 |
--porta |
porta HTTP da página de configuração do certificado. Padrão: 8080 |
--porta-https |
porta HTTPS do controle. Padrão: 8443 |
--parear |
abre o pareamento ao iniciar (útil quando o servidor roda sem terminal) |
--dispositivos |
lista os aparelhos pareados (id, nome, validade, último uso, teclado) |
--revogar |
revoga um aparelho pelo id, ou todos (todos ou all). Vale na hora, mesmo com o servidor rodando |
--permitir-teclado / --negar-teclado |
libera ou bloqueia o modo teclado de um aparelho |
Toque na pílula de status e em Editar layout…. O painel fecha, os grupos ganham contorno e aparece uma barra no centro da tela.
- Mover: arraste um grupo. Ele não sai da tela, respeita as áreas seguras do iPhone e gruda (com uma linha de guia) nas bordas e centros dos outros grupos.
- Redimensionar: arraste a alça redonda do canto de baixo à direita. A escala vai de 0,5× a 2×, o grupo não cresce além da borda e o menor botão nunca fica abaixo de 44 px.
- Ocultar: o botão Botões abre uma lista com um interruptor por controle. Os nomes seguem o perfil em uso (Cruz, Círculo… no DualShock; as teclas no modo teclado).
- Soltar um grupo: o cadeado do canto superior direito de cada grupo (com mais de um botão) o destrava para mover cada botão individualmente. Grupo travado se move em bloco.
- Opacidade: o controle deslizante da barra deixa todos os botões mais transparentes, de 100% até 20%. Um botão apertado aparece por inteiro, para você ver o toque mesmo com pouca opacidade.
- Restaurar / Cancelar / Salvar: só Salvar grava. Restaurar volta ao layout padrão (dentro do rascunho) e Cancelar descarta o que foi feito.
Durante a edição os toques não vão para o PC. O layout é guardado no aparelho e também no
PC (layout.json), compartilhado entre os aparelhos pareados: sobrevive a reinstalar o app e à
diferença entre Safari e app da Tela de Início. Sem conexão com o PC, o layout fica só no
aparelho e é enviado depois; o painel mostra em qual situação ele está.
No perfil Teclado os botões digitam no PC. Como isso é poderoso, tem duas travas:
- O aparelho precisa ter a permissão de teclado, dada na aprovação do pareamento (
t) ou depois comvpad --permitir-teclado <id>. - Cada vez que o modo é ativado, o PC pede confirmação no terminal.
O botão Editar teclas… do painel escolhe a tecla de cada botão; o mapa fica em
teclado.json.
O vpad é pensado para uma rede local doméstica, mas trata a rede como não confiável:
- HTTPS com autoridade própria. A autoridade de certificação (CA) é criada no PC e só vale para nomes e IPs de rede local (restrição de nomes). A chave dela nunca sai do PC. A impressão digital SHA-256 aparece no terminal para você conferir antes de instalar o perfil.
- Pareamento fechado por padrão. O código tem 6 dígitos e muda a cada 30 s; erros seguidos geram bloqueios crescentes (60 s, 300 s, 1800 s) e alertas com o IP no terminal. Mesmo com o código certo, quem está no PC precisa aprovar.
- Um token por aparelho. O servidor guarda só o hash SHA-256; o token vale 90 dias e cada aparelho pode ser revogado sem afetar os outros.
- Sem token na URL. O WebSocket recebe o token pelo subprotocolo, e a API pelo cabeçalho
Authorization. - Barreiras no servidor. Confere
HosteOrigin(contra DNS rebinding e páginas de fora), limita o tamanho das mensagens e dos corpos, e envia cabeçalhos de segurança (CSP restritiva,X-Frame-Options, HSTS e outros). Sem sinal do celular por 6 s (ele manda um ping por segundo), a conexão é encerrada e todos os botões são soltos. - Teclado com permissão e confirmação (veja acima).
Para dependências, make audit consulta o banco de vulnerabilidades do RustSec.
Tudo fica em $XDG_CONFIG_HOME/vpad/ (ou ~/.config/vpad/):
| Arquivo | Conteúdo |
|---|---|
dispositivos.json |
aparelhos pareados (id, nome, hash do token, validade, permissão de teclado) |
dispositivos.vistos.json |
último uso de cada aparelho |
teclado.json |
mapa de teclas do modo teclado |
layout.json |
layout dos botões, compartilhado pelos aparelhos pareados |
tls/ |
autoridade de certificação e certificado do servidor (ca-chave.pem é a chave da CA: não compartilhe) |
Para começar do zero, apague a pasta vpad (você precisará instalar o perfil e parear de novo).
src/ servidor (Rust, axum + rustls + evdev)
main.rs inicialização, console do terminal, CLI
lib.rs rotas, WebSocket, porteiro (Host/Origin), cabeçalhos de segurança
api.rs /api/parear, /api/teclado, /api/layout
servico.rs ponte entre os eventos do celular e o dispositivo virtual
dispositivo.rs gamepad e teclado virtuais (uinput)
perfil.rs perfis (Xbox 360, DualShock 4, Switch Pro, teclado) e opções
acesso.rs, pareamento.rs, dispositivos.rs, aprovacao.rs, alerta.rs, totp.rs
controle de acesso: código, aprovação, tokens, alertas
tls.rs, https.rs, configuracao.rs, mobileconfig.rs
CA própria, HTTPS e a página que instala o certificado no iPhone
layout.rs, armazem.rs, mapa.rs
armazenamento do layout e do mapa de teclas
tests/ testes de integração (API, HTTPS, uinput, página)
web/ página (Svelte 5 + TypeScript + Vite + Vitest)
src/Pad.svelte o controle
src/controller.ts toques → eventos; src/connection.ts: WebSocket e ping
src/layout*.ts modelo, editor, geometria e sincronização do layout
scripts/ auditar.sh (RustSec) e gerar-icones.sh (ícones a partir do SVG)
Protocolo binário, pelo WebSocket (detalhes em src/protocolo.rs):
| Mensagem | Sentido | Conteúdo |
|---|---|---|
[1, id, down] |
celular → PC | botão (0 a 9 são botões, 10 a 13 o direcional) |
[2, id, lo, hi] |
celular → PC | eixo, i16 little endian (analógicos e gatilhos) |
[3, …8 bytes] |
celular → PC | ping, devolvido igual ao celular |
[4, índice] |
nos dois sentidos | o celular pede a troca de perfil; o servidor informa o perfil ativo |
[5, 1] |
PC → celular | acesso recusado (token ausente, inválido ou revogado) |
[6, motivo] |
PC → celular | situação do pedido de ativar o modo teclado |
make test # cargo test + npm test
make audit # dependências Rust contra o RustSec (precisa de: cargo install cargo-audit --locked)
make check # test + audit- Página em modo de desenvolvimento (recarrega ao editar): com o servidor rodando,
cd web && npm run deve abrahttp://localhost:5173. O Vite repassa o WebSocket e a API ao HTTPS do servidor (porta 8443; para outra porta,VPAD_PORTA_HTTPS=18443 npm run dev) e a pareação funciona normalmente. É só para desenvolver: a página fica em HTTP (sem modo app/tela cheia do iPhone), o proxy reescreve aOriginpara a que o servidor aceita, e o Vite escuta na rede local. - Para o que vai de verdade ao celular, rode
npm run buildemweb/e reinicie o servidor (cargo runem debug lêweb/distdo disco). - Verificação de tipos:
cd web && npm run check. - Os testes que injetam entrada de verdade na sessão ficam marcados com
#[ignore]e usam só teclas modificadoras:cargo test -- --ignored. - Para testar sem mexer na sua configuração, use portas e pasta próprias:
XDG_CONFIG_HOME=/tmp/vpad-teste ./target/debug/vpad --porta 18080 --porta-https 18443. - Para regenerar os ícones depois de editar
web/public/icon.svg:./scripts/gerar-icones.sh(precisa dersvg-convertemagick).
Os textos da página ficam em web/src/locales/ (um arquivo por idioma). O português
(pt-BR.ts) define as chaves; os outros são tipados com as mesmas chaves, então esquecer uma
tradução é erro de compilação (npm run build) e de teste. Para mudar um texto, edite a chave nos
seis arquivos. Use t("chave") no código, sempre na hora do uso (nunca em constante de módulo,
porque o idioma é escolhido só depois do carregamento) e data-i18n no HTML. O teste
i18nTextos.test.ts falha se sobrar texto em português fixo no código.
A página de configuração do certificado (a que o iPhone abre em http://IP-DO-PC:8080) e o perfil
.mobileconfig também existem nos seis idiomas: o servidor escolhe pelo ?lang= da URL ou pelo
Accept-Language do navegador (sem tradução, inglês; sem informação, português). Os textos estão em
src/idiomas.rs, com testes que exigem todos os campos preenchidos nos seis idiomas.
O terminal do PC tem dois idiomas: português quando o sistema está em português e inglês nos
demais casos. O idioma vem de VPAD_LANG (por exemplo VPAD_LANG=pt ou VPAD_LANG=en, que vence
tudo), depois de LC_ALL, LC_MESSAGES e LANG. Os avisos da partida estão em src/avisos.rs e os
demais textos usam tr!("texto em português", "english text") (ou txt(...) para texto fixo) no lugar
em que aparecem, com o compilador conferindo os marcadores dos dois. As opções da linha de comando
(--permitir-teclado, --revogar…) são as mesmas nos dois idiomas, e as respostas do terminal
aceitam as duas famílias de letras (s/y, t/k).
Na API, toda resposta de erro traz um codigo estável (por exemplo
pareamento_fechado, mapa_invalido, gravacao_falhou) além da mensagem em português, e a página
traduz pelo código. Os rótulos de tecla (Espaço, Apagar) e os nomes dos grupos de teclas
também são traduzidos na página (web/src/keyNames.ts).
Os releases levam o binário para Linux x86_64 e aarch64 (vpad-X.Y.Z-linux-x86_64.tar.gz,
vpad-X.Y.Z-linux-aarch64.tar.gz e os .sha256): ao criar uma tag vX.Y.Z, o workflow
.github/workflows/release.yml decide a matriz (scripts/arquiteturas-do-release.sh) e, em cada runner
nativo (ubuntu-22.04 e ubuntu-22.04-arm, sem compilação cruzada), constrói a página, compila com
--locked, confere que o binário abre, empacota (scripts/empacotar-release.sh, que também recusa um
binário de outra arquitetura) e, no fim, publica, criando o release com as notas do changelog
(scripts/notas-do-release.sh). Para anexar pacotes a um release que já existe, use Actions → Release →
Run workflow, com tag (e, se a tag for anterior aos scripts, fonte com a branch ou o commit a
compilar; e arquiteturas para gerar só uma, sem refazer a outra). Sem tag, o workflow só gera os
pacotes como artefatos, para testar. O CI também roda os testes do Rust num runner ARM (rust-arm).
O histórico de versões fica em CHANGELOG.md (formato Keep a Changelog, versionamento
semântico). Ao preparar um release, a versão precisa ser a mesma no Cargo.toml, no Cargo.lock, no
web/package.json e no web/package-lock.json, e a seção dela no changelog precisa existir: o teste
web/release.test.ts confere tudo isso.
O README tem traduções para os outros cinco idiomas (README.en.md, README.es.md,
README.zh-CN.md, README.ja.md e README.ko.md), ligadas pela barra de idiomas do topo. Elas cobrem
tudo que o usuário lê (do começo até os problemas comuns e a licença) e trazem só um resumo da parte de
desenvolvimento, que fica neste arquivo. Ao mudar uma dessas seções aqui, atualize as traduções: o teste
web/readme.test.ts confere que todas têm as mesmas seções, os mesmos comandos, as mesmas tabelas e os
mesmos nomes de opções e arquivos, e que não sobrou texto em português nelas.
Fluxo de trabalho do projeto: uma branch por funcionalidade (feature/<nome>), testes escritos
antes da implementação, e merge na master só com os testes passando.
A master também é protegida no GitHub: só recebe mudanças por pull request (sem exigir
aprovação, porque o projeto tem um autor), não aceita force push nem exclusão, e a regra vale até
para administradores. O fluxo é git push -u origin feature/<nome>, gh pr create --fill e, com os
testes passando, gh pr merge --merge (merge commit, como nos merges locais).
O CI (GitHub Actions, .github/workflows/ci.yml) roda em todo pull request e em todo push na
master, com três jobs: web (checagem de tipos, build e testes da página), rust (cargo test --locked; no runner do GitHub não há /dev/uinput, então os testes do dispositivo virtual se pulam
sozinhos) e hooks (o teste do hook abaixo). A master só aceita o merge com os três verdes.
Localmente, o equivalente é make test.
O repositório bloqueia commit direto na master (hook .githooks/pre-commit): o trabalho entra
por uma branch e chega na master por merge, que o hook não atrapalha. Depois de clonar, instale-o
uma vez com make hooks (ele é copiado para .git/hooks; rode de novo se o arquivo mudar). Numa
emergência, de propósito:
VPAD_PERMITIR_COMMIT_NA_MASTER=1 git commit .... O teste do hook é ./scripts/testar-hooks.sh
(já incluído em make test).
| Sintoma | O que fazer |
|---|---|
não consegui criar o controle virtual |
carregue o módulo (sudo modprobe uinput) e dê permissão de escrita em /dev/uinput ao seu usuário |
| o Safari avisa que a conexão não é segura | o perfil do certificado não foi instalado, ou falta ativar a confiança em Ajustes de Confiança de Certificado |
| o certificado deixou de valer depois de o PC mudar de IP | o servidor renova o certificado sozinho ao iniciar; se a CA foi trocada, instale o perfil de novo |
| "o pareamento está fechado" | no terminal do PC, pressione Enter para abrir por 2 minutos |
| "muitas tentativas erradas" | espere o bloqueio acabar (o tempo aparece na mensagem) |
| a primeira tecla depois de uma pausa chega atrasada | ligue o ping curto no painel; é o Wi-Fi do celular dormindo |
| o app da Tela de Início pede o pareamento de novo | ele tem armazenamento separado do Safari; pareie uma vez dentro do app |
| o ícone antigo continua na Tela de Início | o iOS fixa o ícone ao adicionar: remova o app e adicione de novo |
MIT. Veja o arquivo LICENSE.