连接到Linux键盘事件处理

20
我想要钩取Linux桌面键盘事件处理。按下CapsLock应该进入某种命令行。我想实现以下一些命令:
  • d/x: 从当前光标位置删除到字符 x。(受vi启发)
  • a: 转到行开头,就像pos1。(受emacs启发)
  • k: 删除到行末。(受emacs启发)
  • ...
这些命令应该在任何文本字段中都可用:浏览器、邮件客户端、gnome终端...据我所知,低级别的xmodmap对我没用。这种做法是否可行?我需要在哪里放置钩子?目标平台是Ubuntu >= 14.04。背景:我希望保持我的指示手指在F和J上,并且可以在不看键盘的情况下使用计算机。多年来A-Z键都可以使用,但例如Pos1/End等键不易访问。

更新

此问题仅涉及如何挂钩键事件处理。其他内容(命令行)属于不同的话题。如何捕获例如CapsLock x

更新2

我发现没有简单直接的解决方案。如果您没有答案,但知道我可以在哪里找到更多帮助(例如在邮件列表FOO上询问),请告诉我。

更新3

由于一些人不理解我的意思,我尝试解释一下:如果我使用emacs或bash,我感觉像是控制着计算机:这就像飞行,只需非常少的动作,我就可以告诉计算机我想要什么。在网页浏览器文本区域、LibreOffice中编辑文本或使用Thunderbird会使这种感觉消失。光标移动很麻烦,感觉不像飞行。我想控制桌面,而不仅仅是单个应用程序,并保持指示手指放在F和J键上。

更新4:解决方案:input-remapper

我找到了一个很好的解决方案:input-remapper。请看我的小文章Ten Flying Fingers(更舒适的触摸打字)


6
亲爱的投票者们:请告诉我这个问题有什么问题。我该如何改进它?因为“范围过大”,有两个人对它进行了投票。我不明白,这个问题究竟哪里太广泛了? - guettli
这在没有 X 屏幕处于活动状态的情况下能够工作吗? - Basilevs
@Basilevs 不,对我来说只要X桌面正在运行就足够了。如果X登录屏幕要求用户/密码或文本控制台(Ctrl-Alt-F1),它不需要可用。 - guettli
4个回答

12

更新

不要告诉X服务器忽略该设备,您可以使用下面程序中我添加的EVIOCGRAB ioctl。

您需要执行以下步骤:

1.确保您已编译并加载了CONFIG_UINPUT模块,我相信Ubuntu已经有了。如果您没有看到/dev/uinput设备,请尝试运行modprobe -v uinput加载模块。

2.以root身份运行以下程序,并给出键盘设备的路径,例如:

./process /dev/input/by-id/usb-Microsoft_Wired_Keyboard_600-event-kbd

以下程序创建一个名为uinput-sample的虚拟输入设备,并将给定输入设备的所有事件转发到它。 我从http://thiemonge.org/getting-started-with-uinput的示例中进行了调整。

您可以修改它以执行您想要执行的操作。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
#include <linux/uinput.h>

#define die(str, args...) do { \
        perror(str); \
        exit(EXIT_FAILURE); \
    } while(0)

int
main(int argc, char* argv[])
{
    int                    fdo, fdi;
    struct uinput_user_dev uidev;
    struct input_event     ev;
    int                    i;

    if(argc != 2) die("error: specify input device");

    fdo = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
    if(fdo < 0) die("error: open");

    fdi = open(argv[1], O_RDONLY);
    if(fdi < 0) die("error: open");

    if(ioctl(fdi, EVIOCGRAB, 1) < 0) die("error: ioctl");

    if(ioctl(fdo, UI_SET_EVBIT, EV_SYN) < 0) die("error: ioctl");
    if(ioctl(fdo, UI_SET_EVBIT, EV_KEY) < 0) die("error: ioctl");
    if(ioctl(fdo, UI_SET_EVBIT, EV_MSC) < 0) die("error: ioctl");

    for(i = 0; i < KEY_MAX; ++i)
        if(ioctl(fdo, UI_SET_KEYBIT, i) < 0) die("error: ioctl");

    memset(&uidev, 0, sizeof(uidev));
    snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "uinput-sample");
    uidev.id.bustype = BUS_USB;
    uidev.id.vendor  = 0x1;
    uidev.id.product = 0x1;
    uidev.id.version = 1;

    if(write(fdo, &uidev, sizeof(uidev)) < 0) die("error: write");
    if(ioctl(fdo, UI_DEV_CREATE) < 0) die("error: ioctl");

    while(1)
    {
        if(read(fdi, &ev, sizeof(struct input_event)) < 0)
            die("error: read");

        ev.time.tv_sec = 0;
        ev.time.tv_usec = 0;

        if(write(fdo, &ev, sizeof(struct input_event)) < 0)
            die("error: write");
    }

    if(ioctl(fdo, UI_DEV_DESTROY) < 0) die("error: ioctl");

    close(fdi);
    close(fdo);

    return 0;
}

谢谢你的回答。我会写C,但我更喜欢Python。我想在接下来的几天将你的解决方案移植到http://tjjr.fi/sw/python-uinput/。 - guettli
@guettli,请查看我为Python解决方案提供的其他答案。 - Super-intelligent Shade
Python-uinput 可用于创建设备和发出事件,但不能用于抓取、克隆和监视现有设备。 - tuomasjjrasanen
这对我不起作用 - 它似乎只是吞噬事件。它正在接收它们 - 我已经添加了printf来显示它们。例如,如果我按下并释放F键,我会看到:https://gist.github.com/matvore/6d8e468e77921d3433328fd666fe78a6 - matvore
1
将<KEY_MAX更改为<=KEY_MICMUTE(在阅读linux/input-event-codes.h时似乎是一个合理的截止点)对我有用。 - matvore

5
粗暴的方法是修改/重建xserver-xorg-input-evdev软件包并替换/usr/lib/xorg/modules/input/evdev_drv.so. 我会尝试修改xf86-input-evdev-2.9.0/src/evdev.c中的EvdevQueueKbdEvent()函数。这看起来不是很优雅的解决方案,但我认为您将获得修改键盘事件队列的灵活性。
使用XGRabKey()(有关详细信息在此处)和/或XGrabKeyboard()可能可以实现较少侵入性的解决方案。
一些信息可能会有所帮助在此处(关于XTest扩展)。

1
非常感谢。这是我收到的第一个积极反馈。修改evdev.c看起来不太灵活。我的扩展需要为每个xserver编译。也许可以在运行时替换该方法... - guettli
@guettli,是的,这有点像“可行性分析”。实际上,如果找不到更好的解决方案,我认为您可以构建和分发修改/重命名的evdev驱动程序,但在原型制作时可能需要更多的工作。而且我不确定这是您短期内遇到的最大问题。 - kestasx

3

从另一个角度看,您想要一些专业的窗口管理器。详细了解EWMH规范。在此之前,请先了解X11的概述。

或者考虑使用现有的X窗口管理器。有很多选择。我猜ratpoisonxmonad(或者sawfish等)可以被配置以满足您的需求。(但我不太了解这些窗口管理器)。

在从头开始实现您的窗口管理器之前三思。这可能意味着数年的工作!据我所知,窗口管理器可以重定向、过滤、抓取或合成键盘或鼠标事件。

当然,使用Wayland将会有所不同。

是的,实现一个窗口管理器需要很多工作。我不想重复造轮子。我的解决方案应该适用于每个窗口管理器。我只想在从手指按下到X应用程序的键盘事件传递过程中捕获和修改它们。 - guettli
根据定义,窗口管理器可能(甚至必须)捕获此类键盘事件,并且可以抓取它们。您需要一些窗口管理器。选择一个(或适应一个)以满足您的需求。请按照我给您提供的链接,它们是相关的! - Basile Starynkevitch
我知道我需要一个窗口管理器。但是强制用户使用特定的窗口管理器的解决方案并不用户友好。我认为可以通过任何窗口管理器来解决这个问题。 - guettli
你想要的并不是每个用户都友好的(对我来说肯定不友好,因为它会影响我的emacs使用)。它只是符合你对用户需求的看法而已。所以选择并自定义一个窗口管理器以适应你特定的需求。 - Basile Starynkevitch
很有趣,你提到了 emacs。我从emacs转换到了 pyCharm。我错过了非常方便的快捷键,例如ctrl-a ctrl-e。然后我问自己:为什么要修改pyCharm?我想在任何地方都进行基本文本编辑(如thunderbird,Webbrowser等)。DEL、Backspace、Pos1、End这些键是存在的。但是像emacs中的ctrl-a和ctrl-e一样对于触摸打字者来说更容易。尽管如此,我想我会尝试在按键进入窗口管理器之前进入事件键。 - guettli

3

非常感谢。看起来正是我想要的。 - guettli
@guettli,太棒了。随意接受答案...因为有人得到了悬赏 :) - Super-intelligent Shade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接