如何让 USB 键盘的附加按钮正常工作?

如何让 USB 键盘的附加按钮正常工作?

我最近买了一个 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 补充所提供的密钥代码的详细信息。

相关内容