从Linuxinput设备访问密钥

我正在尝试做什么

所以,我一直在尝试访问Linux中的键盘input。 具体来说,我需要能够访问修改键的按下, 而不要按其他键。 此外,我希望能够在没有 X系统运行的情况下做到这一点。

所以,总之,我的要求是这样的:

  • 在Linux上运行
  • 不需要X11
  • 无需按任何其他键即可检索修改器按键
    • 这包括以下键:
      • 转移
      • 控制
      • Alt键
    • 我所需要的只是一个简单的0 = not pressed1 = currently pressed让我知道键盘被选中时是否按住

我的电脑设置

我的普通Linux机器正在一辆卡车上,驶向我的新公寓。 所以,我现在只有一台Macbook Air。 因此,我在一台虚拟机上运行Linux来testing这个。

VirtualBox中的虚拟机

  • 操作系统:Linux Mint 16
  • 桌面环境:XFCE

以下的一切都是在这个环境下完成的 我已经尝试了X运行和其他ttys之一。

我的想法

如果有人能纠正我,我会改变这一点。

我已经做了相当一部分的阅读,意识到更高级别的库不提供这种function。 修改键与其他键一起使用以提供备用键码。 在Linux中通过高级库访问修饰键本身并不容易。 或者,我还没有在Linux上find这个高级的API。

我以为libtermkey会是答案,但它似乎并没有比普通的按键检索更好地支持Shift修饰键。 我也不确定它是否工作没有X.

在使用libtermkey的时候(之前我意识到在Shift-Return这种情况下没有变化),我打算编写一个守护进程来收集键盘事件。 运行守护进程程序的副本只需pipe理键盘数据的请求并接收键盘数据作为响应。 我可以使用这个设置在后台运行一些东西,以防在特定时间检查关键代码状态(必须在接收到关键代码时)。

下面是我写的一个可以从Linux键盘设备读取的程序的两次尝试。 我还包括我的小检查,以确保我有正确的设备。

尝试#1

我试图直接访问键盘设备,但遇到问题。 我已经在这里尝试了另一个堆栈溢出线程的build议。 它给了我一个分段错误; 所以,我把它从fopen改为打开:

 // ... int fd; fd = open("/dev/input/by-path/platform-i8042-serio-0-event-kbd", O_RDONLY); char key_map[KEY_MAX/8 + 1]; memset(key_map, 0, sizeof(key_map)); ioctl(fd, EVIOCGKEY(sizeof key_map), key_map); // ... 

虽然没有分段错误,但没有任何按键的指示(不仅仅是修饰键)。 我testing了这个使用:

 ./foo && echo "TRUE" || echo "FALSE" 

我用它来testing命令的成功返回代码, 所以,我知道这很好。 我也输出了密钥(总是0)和掩码(0100)来检查。 它似乎没有检测到任何东西。

尝试#2

从这里,我想我会尝试一个稍微不同的方法。 我想弄清楚我做错了什么。 在这个页面提供了一个演示打印键码的片段,我把它捆绑到一个程序中:

 #include <stdio.h> #include <stdint.h> #include <string.h> #include <fcntl.h> #include <linux/input.h> int main(int argc, char** argv) { uint8_t keys[128]; int fd; fd = open("/dev/input/by-path/platform-i8042-serio-event-kbd", O_RDONLY); for (;;) { memset(keys, 0, 128); ioctl (fd, EVIOCGKEY(sizeof keys), keys); int i, j; for (i = 0; i < sizeof keys; i++) for (j = 0; j < 8; j++) if (keys[i] & (1 << j)) printf ("key code %d\n", (i*8) + j); } return 0; } 

以前,我的大小是16字节而不是128字节。 我应该诚实地花更多时间了解ioctl和EVIOCGKEY。 我只是知道,它应该将位映射到特定的键来指示按下,或类似的东西(请纠正我,如果我错了,请! )。

我也没有最初的循环,只是按住不同的键,看看是否出现一个关键代码。 我什么也没收到 所以,我认为一个循环可能会使检查更容易,以防万一错过了一些东西。

我怎么知道input设备是正确的

我通过在input设备上运行cat来testing它。 特别:

 $ sudo cat /dev/input/by-path/platform-i8042-serio-0-event-kbd 

当我开始使用cat输出时,Garbage ASCII被发送到我的terminal上,并按下键并释放以return(enter)键开始的事件。 我也知道,这似乎在运行Linux VM的Macbook上使用修改键(如shift,control,function,甚至是Apple的命令键)都可以正常工作。 当按键被按下时,输出出现,随后按下按键发出的信号开始快速出现,并且当按键被释放时输出更多的数据。

所以,虽然我的方法可能不是正确的(我愿意听到任何select),该设备似乎提供了我所需要的。

而且,我知道这个设备只是一个指向/ dev / input / event2的链接,

 $ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd 

我用/ dev / input / event2尝试了上面的两个程序,并没有收到任何数据。 在/ dev / input / event2上运行cat提供了与链接相同的输出。

打开输入设备,

 #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <linux/input.h> #include <string.h> #include <stdio.h> static const char *const evval[3] = { "RELEASED", "PRESSED ", "REPEATED" }; int main(void) { const char *dev = "/dev/input/by-path/platform-i8042-serio-0-event-kbd"; struct input_event ev; ssize_t n; int fd; fd = open(dev, O_RDONLY); if (fd == -1) { fprintf(stderr, "Cannot open %s: %s.\n", dev, strerror(errno)); return EXIT_FAILURE; } 

然后从设备中读取键盘事件:

  while (1) { n = read(fd, &ev, sizeof ev); if (n == (ssize_t)-1) { if (errno == EINTR) continue; else break; } else if (n != sizeof ev) { errno = EIO; break; } 

如果发生任何错误,或者如果用户空间只接收到部分事件结构(不应该发生,但可能在未来的某些内核中),则上面的代码片断将从循环中跳出。 您可能希望使用更强大的读取循环; 我个人会满意的,用continue替换最后一个break ,所以部分事件结构被忽略。

然后,您可以检查ev事件结构以查看发生了什么,并完成该程序:

  if (ev.type == EV_KEY && ev.value >= 0 && ev.value <= 2) printf("%s 0x%04x (%d)\n", evval[ev.value], (int)ev.code, (int)ev.code); } fflush(stdout); fprintf(stderr, "%s.\n", strerror(errno)); return EXIT_FAILURE; } 

对于按键,

  • ev.time :事件的时间( struct timeval类型)

  • ev.typeEV_KEY

  • ev.codeKEY_* ,密钥标识符; 请参阅/usr/include/linux/input.h完整列表

  • ev.value :如果按键释放, ev.value 0 ;如果按键按下,则为1如果是自动重复按键,则为2

有关更多详细信息,请参阅Linux内核源文件中的Documentation / input / input.txt 。

/usr/include/linux/input.h中的命名常量是相当稳定的,因为它是一个内核用户空间接口,并且内核开发者非常努力地维护兼容性。 (也就是说,你可以期待有新的代码,但现有的代码很少改变。)