霓虹语标题我都想好了。evdevの力を貸して、Linuxでホットキーの魔改造

Linux用户就像Minecraft玩家,虽然大家玩的都是Minecraft,但是,卧槽,我们一定是在玩不同的游戏(见到建筑师的MC作品时来自小白的惊叹)。要让自己的Linux别人不会用,别人的Linux自己不会用,最重要的当然是要把快捷键改得惊天地泣鬼神。

作为一个Vim瘾君子,我的需求就是手尽量不要离开主键盘区。对,方向键我都不想按。于是我想通过一些组合键去实现上下左右。有人认为CapsLock按起来方便,我自己比较喜欢按Alt,因为就在空格键旁边,触手可及。总的来说,我希望Alt+[H|J|K|L]分别变成左下上右,Alt+0变成Home, Alt+4变成End。

我自己试用过很多修改键位或者添加热键的工具,包括著名的AutoKey。很可惜,它们大都不好用。比如说,我采取这样的操作序列Alt Down, K Down, K Up, Alt Up,这些软件大多会采取在检测到K Down的时候,同时发出Alt Up, ↑ Down,以便撤销掉先前一个Alt Down的作用,然后发出键按下的事件。这里有一个问题,就是很多GUI在Alt Down, Alt Up之后,会唤出菜单,从而失焦于输入框。

另外一方面就是,真的不是哪里都能用。至少,你不可能拿它去玩赛车游戏。你也不能在TTY中继续使用这些热键。此外还有许许多多的地方不能使用。热键是一种会上瘾的东西,在它失效的时候,你就会有戒断综合征。想摔键盘。

evdev和uinput

我试过从GNOME和X11入手,貌似没有什么好用的方案。XGrabKeyboard一定程度上可以做到接管的效果,但是要指定窗口,似乎还会让窗口失焦。总而言之稍微有点太绕。不过Linux最好的一点,就是它很裸露。如果从高于驱动低于X11的层入手,兴许会有比较好的效果。

evdev内核中通用的输入设备驱动,它为设备提供了/dev/input下字符设备接口。它非常底层,内核在进行中断处理后,第一时间就将输入数据交由它处理。但是它一点都不反直觉,甚至还提供了很好用的工具libevdev,可以直接用Python处理消息。不少人改游戏手柄都是通过evdev进行的。

uinput是一个特殊的虚拟设备,它允许你直接在用户态向内核插入输入事件 —— 一般而言就是直接向/dev/uinput写数据,不过当然,要服从libevdev提供的接口/数据结构。这些事件之后会在另一个evdev字符设备里,假装是物理设备的输入,被X的libinput取出来或者由TTY转为stdin。

Quick Start

这种实验性的东西就不用C写了。evdev提供了十分好用的Python Bindings,我们可以直接在Python里写我们的快捷键配置。我们先用pip install evdev安装它,然后在用它提供的示例脚本来测试一下evdev输入的究竟是什么东西:sudo python -m evdev.evtest /dev/input/by-path/platform-i8042-serio-0-event-kbd(这里我键盘的路径是i8042键盘控制器上的,你需要根据你电脑上的配置来调整)

time 1504189579.19    type 4 (EV_MSC), code 4    (MSC_SCAN), value 21
time 1504189579.19    type 1 (EV_KEY), code 21   (KEY_Y), value 1
time 1504189579.19    --------- SYN_REPORT --------
time 1504189579.28    type 4 (EV_MSC), code 4    (MSC_SCAN), value 21
time 1504189579.28    type 1 (EV_KEY), code 21   (KEY_Y), value 0
time 1504189579.28    --------- SYN_REPORT --------
time 1504189579.29    type 4 (EV_MSC), code 4    (MSC_SCAN), value 18
time 1504189579.29    type 1 (EV_KEY), code 18   (KEY_E), value 1
time 1504189579.29    --------- SYN_REPORT --------
time 1504189579.4     type 4 (EV_MSC), code 4    (MSC_SCAN), value 18
time 1504189579.4     type 1 (EV_KEY), code 18   (KEY_E), value 0
time 1504189579.4     --------- SYN_REPORT --------
time 1504189579.48    type 4 (EV_MSC), code 4    (MSC_SCAN), value 31
time 1504189579.48    type 1 (EV_KEY), code 31   (KEY_S), value 1
time 1504189579.48    --------- SYN_REPORT --------
time 1504189579.64    type 4 (EV_MSC), code 4    (MSC_SCAN), value 31
time 1504189579.64    type 1 (EV_KEY), code 31   (KEY_S), value 0
time 1504189579.64    --------- SYN_REPORT --------

这里我按了yes三个键,可以看到,每一个动作(按下或释放,表现在EV_KEY的value的1或0上),都会产生三个事件,分别是EV_MSCEV_KEYEV_SYN。根据 https://lp007819.wordpress.com/2013/02/12/再谈linux-input子系统/的说法,事实上是有四个消息的发出,但是第一个通常不被支持(隐身了),第二个MSC_SCAN通常会被应用程序忽略,第三个EV_KEY才是真正会被接收的,第四个是同步,可以看到就是用来产生萌萌的分界线的(笑)

有了这一层认识我们就知道,三个事件合起来,才是一次真正的输入。

开始动工

就我的需求而言,我脑子里第一个浮现的抽象机制就是状态机,不知为何。一个比较合适的设计是两层的自动机,第一层用来为这三个一组的事件分组,分好组之后成为第二层状态机的输入,一个二元组(key-scan-code, up/down/hold)。第二层状态机我花了好些时间去构思,结果大概是这样的:

State Input Pattern Transition Action
Normal (Left Alt, Down) Alt -
Normal ELSE Normal inject
Alt (J/K/H/L/0/4, *) Mapped mapped
Alt (Left Alt, Up) Normal inject_alt_down, inject_alt_up
Alt ELSE Inject inject_alt_down, inject
Inject (J/K/H/L/0/4, *) Mapped inject_alt_up, mapped
Inject (Left Alt, Up) Normal inject_alt_up
Inject ELSE Inject inject
Mapped (J/K/H/L/0/4, *) Mapped mapped
Mapped (Left Alt, Up) Normal -
Mapped ELSE Inject inject_alt_down, inject

是不是头都晕了。把它画出来或许会比较清楚,不过我也没这个闲心拿绘图软件再画一遍了。有四个状态,分别是:

  • 正常状态 (Normal),除了Alt以外所有键都直接发射到uinput里

  • 刚按了下Alt键 (Alt),现在还不能确定Alt键会否形成组合键

  • 插入状态 (Inject),不是我们想要的热键,连刚才的Alt一起发射到uinput里

  • 映射状态 (Mapped),是热键,把映射过之后的键发射出去,比如按下了K就发射Up

Inject和Mapped状态之间转换时,还要把一些Alt键的动作补充一下,以防误导其它应用程序。

关于evdev本身的使用上,evdev的文档已经说得非常详尽。在这个脚本里,仅仅用到了少量的功能,比如从/dev/input里读事件,我是block read,但是你也可以用select或者epoll去异步完成这些操作。:

# 留意!需要Root权限!
dev = evdev.InputDevice('/dev/input/by-path/platform-i8042-serio-0-event-kbd')for event in dev.read_loop():kev = evdev.categorize(event)ks.input(kev) # 第一层状态机process(ks) # 第二层状态机,以第一层状态的结果为输入

Inject这样的操作就是往uinput里面一次过写三个事件(含同步):

ui = evdev.UInput()def __inject(keycode, keystate):global uiui.write(ecodes.EV_MSC, ecodes.MSC_SCAN, keycode)ui.write(ecodes.EV_KEY, keycode, keystate)ui.syn()

当然,最重要的一点是怎样做到独占读,也就是托管整个设备的事件处理不让它侧漏呢?我们为它设置GRAB。Grab了之后,整个系统只有当前进程读得到键盘的输入。如果这个设备已经被别人Grab了,这个操作就会失败。

dev.grab()
dev.ungrab()

整份代码已经上传到 https://github.com/Shihira/la...,欢迎斧正。

参考资料

  • https://lp007819.wordpress.com/2013/02/12/再谈linux-input子系统/ ,可能需要梯子

  • https://python-evdev.readthed...

借evdev之力 Linux全局热键魔改造相关推荐

  1. linux下qt响应全局热键,Qt全局热键(windows篇)(使用RegisterHotKey和句柄进行注册)...

    Qt对于系统底层,一直没有很好的支持,例如串口并口通信,还有我们经常都会用到的全局热键,等等.既然Qt可能出于某种原因,不对这些进行支持,我们就只能自己写代码,调用系统相关的API了. 注意,这个是W ...

  2. 程序全局热键_如何在Linux下将全局热键绑定到WINE程序

    程序全局热键 Have you ever installed a Windows program in Linux under WINE, only to discover that it doesn ...

  3. java 设置全局热键_Java设置全局热键——第三方包jintellitype实现

    Java原生API并不支持为应用程序设置全局热键.要实现全局热键,需要用JNI方式实现,这就涉及到编写C/C++代码,这对于大多数不熟悉C/C++的javaer来说,有点困难.不过幸好,国外有人已经实 ...

  4. java 设置热键_Java设置全局热键——第三方包jintellitype实现

    Java原生API并不支持为应用程序设置全局热键.要实现全局热键,需要用JNI方式实现,这就涉及到编写C/C++代码,这对于大多数不熟悉C/C++的javaer来说,有点困难.不过幸好,国外有人已经实 ...

  5. 拍牌神器是怎样炼成的(三)---注册全局热键

    要想在上海拍牌的超低中标率中把握机会.占得先机,您不仅需要事先准备好最优的竞拍策略,还要制定若干套应急预案,应对不时之需.既定策略交给计算机自动执行,没有问题.可是谁来召唤应急预案呢?使用全局热键应该 ...

  6. 热键枚举Winform程序全局热键与局部热键(键盘快捷键的捕获)

    最近研究热键枚举,稍微总结一下,以后继续补充: Winform程序全局热键 Winform程序全局热键一般采取的是,调用windows api的情势,主要是通过面下的四个函数 /// <summ ...

  7. Qt全局热键(windows篇)

    Qt对于系统底层,一直没有很好的支持,例如串口并口通信,还有我们经常都会用到的全局热键,等等.既然Qt可能出于某种原因,不对这些进行支持,我们就只能自己写代码,调用系统相关的API了. 注意,这个是W ...

  8. Delphi全局热键的注册

    1.在窗启动时创建ATOM;(aatom:ATOM;定义在private中) 1 if FindAtom('ZWXhotKey')=0 then 2 begin 3 aatom:=GlobalAddA ...

  9. linux双网卡端口聚合,Linux双网卡聚合改造

    Linux双网卡聚合改造 一.环境和需求 Linux主机只有一块网卡接到交换机上,为了消除交换机的单点,新增一台交换机,Linux主机端新接一块网卡到新交换机上,对这两块网卡做聚合达到目的. 二.物理 ...

最新文章

  1. Microsoft Platform SDK Febrary 2003更新vc6的SDK网址
  2. 计算机视觉未来走向:视频理解等5大趋势详解
  3. python绘制柱形图-Python openpyxl Excel绘制柱形图
  4. Python 学习记录1
  5. HDU 5486 Difference of Clustering 图论
  6. java解析表字段_从SQL / HQL Java解析表和列名
  7. 谁动了我的选择器?深入理解CSS选择器优先级
  8. 使用WildFly 9和Jolokia监视DevOps样式
  9. LeetCode 1521. 找到最接近目标值的函数值(位运算)
  10. es6学习 http://es6.ruanyifeng.com/
  11. matlab padarray
  12. Python静态方法 类方法
  13. 拒绝LOW ---青鸟影院购票系统
  14. 2018年的好书基本都在这了,你一共读过几本?
  15. 28.【分解质因数】
  16. 在线绘制流程图网站、思维导图网站总结
  17. Word中用Endnote插入的引用如何快速定位到参考文献
  18. linux的7za无法使用,提示命令找不到:-bash 7za command not found的解决方法.doc
  19. 焦虑的80后新中产,崩溃得不动声色,到底是新中产还是“心中惨”?
  20. HTML中表单的练习代码。

热门文章

  1. VC\JS Base64转码
  2. 工作中常用的linux命令,工作中常用的Linux命令
  3. 随机数排列JAVA_随机数生成器,按排序顺序
  4. 定时任务--mysql数据库备份
  5. 洛谷P4136 谁能赢呢?——博弈
  6. C#流对象使用完后不立即释放的问题
  7. Java中的Serialization
  8. Windows 8 系列 --Block Game --随笔
  9. 原创译文 | 通过设计让学习变轻松
  10. linux下文件无法删除不能编辑