/dev/input/eventX
实在是太多了,十几二十个。
而且每个人的电脑上鼠标、键盘对应的文件都不一样,可能插拔过后就变了。
给我们写程序带来很大不便,下面来解决它。
一、过滤出需要的键盘 / 鼠标文件
注意到还有/dev/input/by-path/
,以及/proc/bus/input/devices
,同时还有一些工具,比如evtest
。
这些都或多或少提供了一些信息,只是不完整
1. shell过滤
首先想到的思路肯定是shell,grep、awk什么的搞起来。
比如筛选键盘设备:
$ ls /dev/input/by-path | grep -- -kbd$
pci-0000:00:14.0-usb-0:5:1.0-event-kbd
platform-i8042-serio-0-event-kbd
然后,你可能直接就想:
#include <stdlib.h>
int main(void)
{
system("ls /dev/input/by-path | grep -- -kbd$");
}
system()
会fork()
一个子进程,这个子进程调用shell解释器来执行命令,但是它执行完就G了,最多留个返回值给我们(好吧,其实你可以把输出写到文件里,再读出来)。
这样可不行,就无法获取结果了,全输出到屏幕了。
正确做法是popen()
系统调用:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
FILE *pipe_out =
popen("ls /dev/input/by-path | grep -- -kbd$", "r");
char buf[4096]
fread(buf, 1, sizeof(buf), pipe_out);
pclose(pipe_out);
}
它会开一个进程(管道),并且把进程的输出端重定向,通过返回值获取,成功完成父子进程通信。
下来按行解析即可。
2. glob()
从来没见到过这个函数,看stackoverflow上例子学到的。
这个函数真是用到地方了,也是首推的方式。
#include <glob.h>
#include <stddef.h>
#include <stdio.h>
int main (void)
{
glob_t keyboard_dev;
glob ("/dev/input/by-path/*-kbd", 0, NULL, &keyboard_dev);
for (int i = 0; i < keyboard_dev.gl_pathc ; ++i) {
puts (keyboard_dev.gl_pathv[i]);
}
globfree (&keyboard_dev);
}
大概懂了吧?详细内容请看手册。
二、I/O复用
虽然我只有一个键盘,但是里面还是有两个键盘设备。
不知道你们用来做什么,我是用来进行按键检测。如果一个键盘按键会被检测,另一个键盘不被检测——比如笔记本外接机械键盘的,
这样就体验不好。
所有肯定要处理多键盘的情况。
然而,这里的键盘检测,其实是依赖于read()
,这样一个键盘读不出来东西就会卡在那里,可能后面的键盘数据已经到了,但是它执行不了。
你大概也不想每个键盘都开一个线程吧?我能预见,这样设计会很烂,它让问题过度复杂化了。
为什么不用多线程?理论上,你可能有100个键盘,这样你要再开99个线程。
实际上,你也要面临竞争的风险——不同键盘相同的按键(这极有可能,只要在一个结束之前按下另一个)会触发相同的处理程序,它们可能同时设置一份资源。
下面来解决它。
1. select
#include <glob.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <linux/input.h>
ssize_t readn (int fd, void *buf, size_t size)
{
size_t nleft = size;
while (nleft > 0) {
int n = read (fd, & ( (char *) buf) [size - nleft], nleft);
if (n > 0)
nleft -= n;
else if (n == -1) {
if (errno == EINTR)
continue;
else
return -1;
} else if (n == 0)
break;
}
return size - nleft;
}
static void solve_keyboard (int fd)
{
static struct input_event event;
int n = readn (fd, &event, sizeof (event));
if (n != sizeof (event))
perror ("read()"), exit (EXIT_FAILURE);
if (event.type != EV_KEY)
return;
switch (event.value) {
case 0:
puts ("key released");
break;
case 1:
printf ("key pressed, code:%d\n", event.code);
case 2:
puts ("repeat automatically");
}
}
static int find_max_int (const int *arr, size_t len)
{
int max = arr[0];
for (size_t i = 1; i < len ; ++i) {
if (arr[i] > max)
max = arr[i];
}
return max;
}
void detect_readonly_devices (const int *dev, size_t count, void (*solver) (int))
{
fd_set readfds;
while (1) {
FD_ZERO (&readfds);
for (int i = 0; i < count ; ++i)
FD_SET (dev[i], &readfds);
int n_readable = select (find_max_int (dev, count) + 1, &readfds, NULL, NULL, NULL);
if (n_readable == -1) {
if (errno == EINTR)
continue;
else
perror ("select()"), exit (EXIT_FAILURE);
} else if (n_readable > 0) {
for (int i = 0; i < count ; ++i) {
if (FD_ISSET (dev[i], &readfds))
solver (dev[i]);
}
}
}
}
int main (void)
{
glob_t keyboard_devs;
glob ("/dev/input/by-path/*-kbd", 0, NULL, &keyboard_devs);
if (keyboard_devs.gl_pathc <= 0)
return 0;
const int fd_count = keyboard_devs.gl_pathc;
int fds[fd_count];
for (int i = 0 ; i < fd_count ; ++i) {
int fd = open (keyboard_devs.gl_pathv[i], O_RDONLY);
if (fd == -1)
perror ("open()"), exit (EXIT_FAILURE);
else
fds[i] = fd;
}
globfree (&keyboard_devs);
detect_readonly_devices (fds, fd_count, solve_keyboard);
}
2. poll
#include <glob.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <poll.h>
#include <errno.h>
#include <linux/input.h>
ssize_t readn (int fd, void *buf, size_t size)
{
size_t nleft = size;
while (nleft > 0) {
int n = read (fd, & ( (char *) buf) [size - nleft], nleft);
if (n > 0)
nleft -= n;
else if (n == -1) {
if (errno == EINTR)
continue;
else
return -1;
} else if (n == 0)
break;
}
return size - nleft;
}
static void check_keyboard (int fd)
{
static struct input_event event;
int n = readn (fd, &event, sizeof (event));
if (n != sizeof (event))
perror ("read()"), exit (EXIT_FAILURE);
if (event.type != EV_KEY)
return;
switch (event.value) {
case 0:
puts ("key released");
break;
case 1:
printf ("key pressed, code:%d\n", event.code);
break;
case 2:
puts ("repeat automatically");
break;
}
}
int main (void)
{
int *fds;
int fds_count;
{
glob_t kbd_dev;
if (glob ("/dev/input/by-path/*-kbd", 0, NULL, &kbd_dev) != 0)
perror ("glob()"), exit (EXIT_FAILURE);
if (kbd_dev.gl_pathc < 1)
exit (EXIT_SUCCESS);
fds = malloc (sizeof (int) * kbd_dev.gl_pathc);
fds_count = kbd_dev.gl_pathc;
for (int i = 0; i < kbd_dev.gl_pathc; ++i) {
int fd = open (kbd_dev.gl_pathv[i], O_RDONLY);
if (fd == -1)
perror ("open()"), exit (EXIT_FAILURE);
else
fds[i] = fd;
}
globfree (&kbd_dev);
}
struct pollfd poll_fds[fds_count];
{
for (int i = 0; i < fds_count ; ++i) {
poll_fds[i].fd = fds[i];
poll_fds->events = POLLIN;
}
free (fds);
}
while (1) {
int n = poll (poll_fds, fds_count, -1);
if (n == -1) {
if (errno == EINTR)
continue;
else
perror ("poll()"), exit (EXIT_FAILURE);
} else if (n == 0) {
printf ("warning: poll() timeout\n");
continue;
} else if (poll < 0) {
perror ("poll()"), exit (EXIT_FAILURE);
} else if (poll > 0) {
for (int i = 0; i < fds_count ; ++i) {
if (poll_fds[i].revents & POLLIN)
check_keyboard (poll_fds[i].fd);
}
}
}
}