因此,我一直在尝试在Linux中访问键盘输入。具体来说,我需要能够在 不 按下其他键的 情况下 访问修饰键。此外,我希望能够在不运行X系统的 情况下 执行此操作。
简而言之,我的要求是:
0 = not pressed
1 = currently pressed
我的普通Linux机器正在卡车上驶向我的新公寓。因此,我现在只能使用Macbook Air。因此,我正在VM中运行Linux进行测试。
VirtualBox中的虚拟机
下面的所有操作都是在此环境中完成的。我已经在运行X和其他tty之一中进行了尝试。
如果有人可以纠正我,我将更改此设置。
我已经读了很多书,以了解更高级别的库不提供这种功能。修饰键与其他键一起使用以提供备用键代码。通过Linux中的高级库本身访问修饰键并不容易。或者,相反,我还没有在Linux上找到用于此目的的高级API。
我以为libtermkey将是答案,但是它似乎不比普通的击键检索更好地支持Shift修饰键。我也不确定如果没有X,它是否可以工作。
在使用libtermkey时(在意识到Shift- Return之类的情况下没有发生移位之前),我正计划编写一个守护程序来运行以收集键盘事件。运行守护程序的副本将仅通过管道传送对键盘数据的请求并作为响应接收键盘数据。我可以使用此设置使某些内容始终在后台运行,以防万一我无法在特定时间检查键码状态(必须及时接收键码状态)。
下面是我两次尝试编写可以从Linux键盘设备读取的程序的尝试。我还附了一张小支票,以确保我拥有正确的设备。
我试图直接访问键盘设备,但是遇到问题。我在这里尝试了另一个堆栈溢出线程中的建议。这给了我分割错误。因此,我将其从fopen更改为open:
// ... 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); // ...
尽管没有分段错误,但没有任何按键指示(不仅仅是修饰键)。我使用以下方法进行了测试:
./foo && echo "TRUE" || echo "FALSE"
我已经用它来测试命令是否成功返回了很多代码。所以,我知道那很好。我还输出了密钥(始终为0)和掩码(0100)进行检查。它似乎什么也没检测到。
从这里开始,我想我会尝试一种稍微不同的方法。我想弄清楚我做错了什么。在此页面之后,提供了演示打印出关键代码的代码段,然后将其捆绑到程序中:
#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。我只知道它应该将位映射到特定键以指示按下或类似操作( 如果我输入错了,请纠正我! )。
我最初也没有循环,只是按住各种键以查看是否出现了键代码。我什么也没收到。因此,我认为循环可以使检查更容易进行检查,以防万一遗漏了某些东西。
我通过cat在输入设备上运行进行了测试。特别:
cat
$ sudo cat /dev/input/by-path/platform-i8042-serio-0-event-kbd
当我使用cat开始输出时,在按键和释放事件(从回车(回车)开始)时,垃圾ASCII被发送到我的终端。我也知道,这在运行Linux VM的Macbook上可以与诸如Shift,Control,Function甚至Apple的Command键之类的修饰键一起使用,似乎效果很好。按下键时出现输出,从按住该键发出的后续信号开始迅速出现,并在释放键时输出更多数据。
因此,尽管我的方法可能不正确(我愿意听到 其他 选择),但该设备似乎可以满足我的需求。
此外,我知道此设备只是从运行指向/ 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; }
如果发生任何错误,或者用户空间仅接收部分事件结构(不应发生,但可能在将来的/笨拙的内核中发生),则以上代码段将从循环中中断。您可能希望使用更强大的读取循环;我个人会被替换最后一个满足break用continue,所以,局部的事件结构被忽略。
break
continue
然后,您可以检查ev事件结构以查看发生了什么,并完成程序:
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.time
struct timeval
ev.type: EV_KEY
ev.type
EV_KEY
ev.code:KEY_*,密钥标识符;请参阅中的完整列表/usr/include/linux/input.h
ev.code
KEY_*
/usr/include/linux/input.h
ev.value:0如果释放按键1,2如果按键,如果自动重复按键
ev.value
0
1
2
有关更多详细信息,请参见Linux内核资源中的Documentation / input / input.txt。
in /usr/include/linux/input.h中的命名常量非常稳定,因为它是内核- 用户空间接口,内核开发人员非常努力地维护兼容性。(也就是说,您可以期望偶尔会有新的代码,但是现有代码很少更改。)