Ok, meu programa estava funcionando por uma noite e ainda funciona, então eu postei o código. É um pouco fugly mas trabalha. Eu também vou escrever sobre como eu fiz isso, porque será útil para pessoas que não têm exatamente o meu teclado. O programa precisa de uma libpcap e wireshark recentes. O debugfs precisa ser montado (montar -t debugfs none_debugs / sys / kernel / debug) e o módulo usbmon carregado (modprobe -v usbmon).
Este é o programa que é executado em segundo plano:
#!/usr/bin/python
# This program should be run as the logged in user. The user must have
# permissions to execute tshark as root.
from pexpect import spawn
from pexpect import TIMEOUT
from subprocess import PIPE
from subprocess import Popen
# Configuration variables
## Device ID from lsusb output
deviceID = "0458:0708"
## Output filter for tshark
filter = "usb.endpoint_number == 0x82 && usb.data != 00:00:00:00"
## Tshark command to execute
tsharkCmd = "/home/stribika/bin/tshark-wrapper"
## Keypress - command mapping
### Key: USB Application data in hex ":" between bytes "\r\n" at the end.
### Value: The command to execute. See subprocess.Popen.
commands = {
"00:00:20:00\r\n":[
"qdbus", "org.freedesktop.ScreenSaver", "/ScreenSaver",
"org.freedesktop.ScreenSaver.Lock"
],
"00:00:40:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Prev"
],
"00:00:10:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Next"
],
"02:00:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Pause"
],
"04:00:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Stop"
],
"00:04:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Mute"
],
"20:00:00:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "1"
],
"40:00:00:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "2"
],
"00:00:80:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "3"
],
"00:00:00:08\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "4"
],
"00:00:00:20\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "5"
],
"00:00:00:10\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "6"
],
}
# USB interface names change across reboots. This determines what is the correct
# interface called this week. If this turns out to be the case with endpoint
# numbers lsusb can tell that too.
lsusbCmd = [ "lsusb", "-d", deviceID ]
sedCmd = [
"sed", "-r",
"s/^Bus ([0-9]{3}) Device [0-9]{3}: ID " + deviceID + ".*$/\1/;s/^0+/usbmon/"
]
lsusb = Popen(lsusbCmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
sed = Popen(sedCmd, stdin = lsusb.stdout, stdout = PIPE, stderr = PIPE)
usbIface = sed.stdout.readline().rstrip()
# Arguments for Tshark
## -i is the interface (usbmon[0-9]+)
## -R is the output filter
tsharkArgs = [
"-T", "fields", "-e", "usb.data",
"-i", usbIface,
"-R", filter
]
# Start capturing
## pexpect is needed to disable buffering. (Nothing else actally disables it
## don't belive the lies about Popen's bufsize=0)
tshark = spawn(tsharkCmd, tsharkArgs, timeout = 3600)
line = "----"
# Read keypresses while tshark is running and execute the proper command.
while line != "":
try:
line = tshark.readline()
Popen(commands[line], stdin = PIPE, stdout = PIPE, stderr = PIPE)
# We do not care about timeout.
except TIMEOUT:
pass
Como você pode ver, há uma grande matriz de comandos indexada com os dados do aplicativo dos pacotes USB. Os valores são os comandos emitidos. Eu estou usando o DBus para fazer o que precisa ser feito, mas você pode usar o xvkbd para gerar eventos de pressionamento de teclas reais (eu achei o xvkbd muito lento em segundos para enviar uma simples combinação de teclas). O tshark-wrapper é um simples wrapper em volta do tshark, ele executa o tshark como root e desativa o stderr.
#!/bin/sh
sudo tshark "$@" 2> /dev/null
Existe o problema. O usuário precisa de permissão para executar tshark como root sem senha. Isso é realmente muito ruim. O risco poderia ser reduzido colocando mais argumentos no wrapper e menos no script python e permitindo que os usuários executem o wrapper como root.
Agora, sobre o processo de fazer isso com outros teclados. Eu não sei quase nada sobre USB e ainda não foi tão difícil. A maior parte do meu tempo foi gasto descobrindo como fazer a leitura sem buffer de um cano. Eu sabia da saída do lsusb que meu teclado está na segunda interface USB. Então comecei a capturar com wireshark no usbmon2. O mouse e outros hardwares geram muito ruído, então desconecte-os ou, pelo menos, não mova o mouse.
A primeira coisa que notei foi que as teclas extras têm ID de ponto de extremidade 0x82 e as chaves normais têm ID de ponto de extremidade 0x81. Havia alguns pacotes no começo com 0x80. Isso é bom, pode ser facilmente filtrado:
usb.endpoint_number == 0x82
Tecla normal:
Imprensaextra:
Foi fácil ver que uma tecla pressionada gera 4 pacotes USB: 2 para a impressora, 2 para a liberação. Em cada par o primeiro pacote foi enviado pelo teclado para o PC e o segundo foi ao contrário. Parecia ACK-s com TCP. O "ACK" era URB-SUBMIT e o pacote normal era do tipo URB-COMPLETE. Então eu decidi filtrar os "ACK" e mostrar apenas pacotes normais:
usb.urb_type == "C\x01\x82\x03\x02"
USB "ACK":
Agora,haviaapenasdoispacotesporpressionamentodetecla.Cadasegundotinhacampodevalordeaplicaçãozeroetodoorestotinhavaloresdiferentes.Então,eufiltravaoszeroseusavaosoutrosvaloresparaidentificarchaves.
usb.data!=00:00:00:00
Lançamentodechaveextra:
Meu teclado é um Slimstar 220 (espero que isso não se qualifique como spam. Se eu o fizer, removê-lo.) Se você tiver o mesmo tipo de chances, o programa não modificado funcionará. Caso contrário, acho que pelo menos o material do valor do aplicativo será diferente.
Se alguém quiser escrever um driver real com base nesses dados, entre em contato. Eu não gosto do meu hack feio.
Atualização: O código agora é à prova de reinicialização.