我最近买了一个 USB 键盘。它有 12 个额外的按钮,但只有 5 个可以工作。日志中没有“未知扫描代码”消息。Evtest 无法检测到它们,甚至无法检测到工作的 5 个(只有常规键)。Xev 检测到工作的 5 个,但检测不到其他的。“cat /dev/input/by-path/pci-0000:00:02.0-usb-0:4:1.0-event-kbd”与 evtest 相同,但输出更丑陋。事实上,我能够检测到其他 7 个键的唯一方法是使用 wireshark 进行 USB 嗅探。所以我的键盘没有缺陷。
我使用的是 Gentoo Linux,其内核为 gentoo-sources-2.6.30-r4,xorg-server-1.6.2-r1,xf86-input-evdev 驱动程序版本为 1.6.2-r1。以下是相关的 xorg.conf 部分:
Section "InputDevice"
Identifier "Keyboard0"
Driver "evdev"
Option "Device" "/dev/input/by-path/pci-0000:00:02.0-usb-0:4:1.0-event-kbd"
Option "XkbLayout" "hu"
EndSection
我尝试查找有关 XkbModel 等选项的更多信息,但手册页没有太大帮助。我搜索了这里的每个 [键盘] 问题,但只在 Windows 上找到了类似的内容。
我该怎么做才能让密钥正常工作?如果这是一个错误,我应该在哪里报告?
更新:以下是 showkeys -s 的输出。执行此操作时,X 服务器尚未运行。
kb mode was UNICODE
[ if you are trying this under X, it might not work
since the X server is also reading /dev/console ]
press any key (program terminates 10s after last keypress)...
0xe0 0x22
0xe0 0xa2
0xe0 0x24
0xe0 0xa4
0xe0 0x20
0xe0 0xa0
0xe0 0x32
0xe0 0xb2
0xe0 0x6c
0xe0 0xec
我从左到右按下了额外的键。每个键都有 2 行(我猜是按下和释放),并且只检测到了有效的 5 行。
更新:我想到了一个非常糟糕的方法。我可以在后台运行 tshark(wireshark 的命令行界面),解析输出并在正确的 USB 数据包上执行任意程序。这存在一个严重的安全问题:任何被允许使用额外密钥的用户都能够看到任何 USB 和网络流量。这种方法的唯一优点是它有效。经过一些清理后,我将发布完整的程序。
答案1
好吧,我的程序运行了一晚,它仍然有效,所以我发布了代码。它有点丑陋,但可以工作。我还会写下我是如何做到的,因为这对那些键盘和我不一样的人很有用。该程序需要足够新的 libpcap 和 wireshark。需要安装 debugfs(mount -t debugfs none_debugs /sys/kernel/debug)并加载 usbmon 模块(modprobe -v usbmon)。
这是在后台运行的程序:
#!/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
如您所见,有一个很大的命令数组,索引来自 USB 数据包的应用程序数据。这些值是发出的命令。我正在使用 DBus 来执行需要执行的操作,但您可以使用 xvkbd 来生成真正的按键事件(我发现 xvkbd 非常慢,发送一个简单的组合键需要几秒钟)。tshark-wrapper 是 tshark 的一个简单包装器,它以 root 身份执行 tshark 并禁用 stderr。
#!/bin/sh
sudo tshark "$@" 2> /dev/null
问题就在这里。用户需要权限才能以 root 身份执行 tshark,无需密码。这真的真的坏事。可以通过在包装器中放入更多参数、在 Python 脚本中放入更少参数并允许用户以 root 身份执行包装器来降低风险。
现在介绍使用其他键盘执行此操作的过程。我对 USB 几乎一无所知,但这并不难。我大部分时间都花在弄清楚如何从管道进行无缓冲读取上。我从 lsusb 输出中知道我的键盘位于第二个 USB 接口上。所以我开始在 usbmon2 上使用 wireshark 进行捕获。鼠标和其他硬件会产生大量噪音,因此请拔掉它们,或者至少不要移动鼠标。
我注意到的第一件事是,额外密钥的端点 ID 为 0x82,普通密钥的端点 ID 为 0x81。开头有一些数据包的 ID 为 0x80。这很好,可以轻松过滤:
usb.endpoint_number == 0x82
正常按键:
额外按键:
很容易看出,按下一个键会产生 4 个 USB 数据包:按下 2 个,松开 2 个。在每一对中,第一个数据包由键盘发送到 PC,第二个数据包则由 PC 发送到键盘。这看起来像是 TCP 的 ACK。“ACK”是 URB-SUBMIT,正常数据包是 URB-COMPLETE 类型。因此,我决定过滤“ACK”,只显示正常数据包:
usb.urb_type == "C\x01\x82\x03\x02"
USB“确认”:
现在每次按键只有 2 个数据包。每秒的应用程序值字段都为零,其他所有字段的值都不同。因此我过滤了零,并使用其他值来识别按键。
usb.data != 00:00:00:00
额外按键释放:
我的键盘是 Slimstar 220(我希望这不算垃圾邮件,如果算,我会删除它。)如果你有相同类型的键盘,未修改的程序很可能会工作。否则,我认为至少应用程序值的东西会有所不同。
如果有人想根据这些数据编写真正的驱动程序,请告诉我。我不喜欢我的丑陋的黑客。
更新:现在希望代码可以防重启。
答案2
通常情况下,如果您没有收到来自内核或 X 的任何消息,则意味着内核正在丢弃密钥。除了内核中的一些 printk 调试(这可能有点过头)之外,我不知道我还能推荐什么。
答案3
我使用 lineakd 映射 中生成键码的任何键xev (1)
。在当前键盘上,大约有一半的按钮会生成键码,其他的则不会。我还没有找到 X 无法识别为生成键码的键的解决方案。
KeyPress event, serial 28, synthetic NO, window 0x4400001,
root 0xfd, subw 0x0, time 9475187, (382,534), root:(417,620),
state 0x0, **keycode 69** (keysym 0xffc0, F3), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False
我之前写过这篇文章,但尚未更新帖子中使用 xev 补充所提供的密钥代码的详细信息。