Using the Input Subsystem
part 2
Linux input子系统一个很重要的特性是它提供了 event interface。它通过字符设备节点对用户空间导出了原生 event,允许用户程序操作任何 event,不会遗失任何信息。
查找 event interface版本
使用 EVIOCGVERSION ioctl function。参数是 32位 int类型,代表 major version (two high bytes), minor version (third byte), patch level (low byte)。 Listing 1显示了使用 EVIOCGVERSION的例子:第 1个参数是 event device node的打开文件描述符。你需要传递一个指向 int数据的一个指针作为第 3个参数。
查找设备身份信息
event interface支持获取设备的身份信息,使用 EVIOCGID ioctl function。参数是指向 input_id数据结构的指针。 input_id数据结构定义如 listing 2所示。_u16数据类型是 16为无符号整型。
Figure 2. input_id Structure Definitionsstruct input_id { __u16 bustype; __u16 vendor; __u16 product; __u16 version; }; |
bustype 域包含了你需要的准确的数据信息。你应把它当作是一个非透明的枚举类型,需要与通过 <linux/input.h>里定义的 BUG_x 类型比较获知。vendor/product/version是与 bus type相关的表示设备身份信息的域。现代设备(如 USB, PCI)都使用了这些域,但是传统的设备( serial mice, ps/2 keyboard, ISA sound card)没有这些域。 EVIOCGID的使用如 listing 3所示。这个例子调用了 ioctl并打印了结果。
除了 bus type/vendor/product/version等设备信息,某些设备还会提供一个有意义的名字字符串,使用 EVIOCGNAME获取。如果名字串太长,则在返回的参数里被截取。 Listing 4显示了它的使用的例子。
下面是上述例子运行的结果样例:
The device on /dev/input/event0 says its name is Logitech USB-PS/2 Optical Mouse
尽管设备身份信息和名字信息通常很有用,但是它也许并没有提供足够的信息告诉你当前在使用哪个设备。例如,你当前有两个完全相同的遥控杆,你需要确定每个使用哪个端口。这通常属于拓扑信息( topology information),可以使用 EVIOCGPHYS ioctl获取。它返回一串字符串(或者一个负值错误码)。 Listing 5显示了它的使用的例子。
Listing 5. Using EVIOCGPHYS for Topology Informationif(ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys) < 0) { perror("event ioctl"); } printf("The device on %s says its path is %s/n", argv[1], phys); |
它的运行结果样例如下所示:
The device on /dev/input/event0 says its path is usb-00:01.2-2.1/input0
为了了解输出的含义,你需要将其分成几部分。 Usb部分意味着这使用 usb系统的一个物理拓扑。 00:01.2是 usb host controller所在的 pci bus information (bus 0, slot 1, function 2)。 2.1表示了从 root hub到 device的路径,这里表示上行 hub接在 root hub的第 2个端口上,设备接在上行 hub的第1个端口上。 Input0表示这是设备的第 1个 event device 节点。大部分设备只有一个 event device节点,但是有些设备例外。比如多媒体键盘,它有一个event device节点用于 normal keyboard,另一个 event device节点用于 multimedia keyboard。拓扑示例如下图表示。
如果你在同一根连接线上将一个设备还成另外一个同样的设备,你无法区分设备更换,除非每一个设备有一个唯一号,比如 serial number。使用EVIOCGUNIQ可以获取。 Listing 6是其示例。绝大多数设备没有这样的唯一号,所以你使用该 ioctl将返回一个空字符串。
Listing 6. Finding a Unique Identifierif(ioctl(fd, EVIOCGUNIQ(sizeof(uniq)), uniq) < 0) { perror("event ioctl"); } printf("The device on %s says its identity is %s/n", argv[1], uniq); |
确定设备能力和特性
对于一些设备,也许知道设备的身份信息就足够了,因为它允许你根据设备的使用情况处理设备的任何 case。但是这总做法的尺度不好。比如,你有一个设备仅有一个滑轮,你想使能处理滑轮的 handler,但是你并不想在 code里列出每个带有滑轮的鼠标的 vendor/product信息。为此, event interface允许你对于某个设备确定有哪些功能和特性。 Event interface支持的 feature types有:
-
EV_KEY: absolute binary results, such as keys and buttons.
-
EV_REL: relative results, such as the axes on a mouse.
-
EV_ABS: absolute integer results, such as the axes on a joystick or for a tablet.
-
EV_MSC: miscellaneous uses that didn't fit anywhere else.
-
EV_LED: LEDs and similar indications.
-
EV_SND: sound output, such as buzzers.
-
EV_REP: enables autorepeat of keys in the input core.
-
EV_FF: sends force-feedback effects to a device.
-
EV_FF_STATUS: device reporting of force-feedback effects back to the host.
-
EV_PWR: power management events
这些仅仅是 type features。每个 type feature包含了很大范围的不同的个体 feature。例如, EV_REL type区别了 x轴, y轴, z轴,横轮,竖轮。同样,EV_KEY type包含了成千上百个 keys和 buttons。
使用 EVIOCGBIT ioctl可以获取设备的能力和特性。它告知你设备是否有 key或者 button。
EVIOCGBIT ioctl处理 4个参数 ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是个特殊 case,表示返回设备支持的所有的 type features)。 max_bytes表示返回的最大字节数。 bitfield域是指向保存结果的内存指针。 return value表示保存结果的实际字节数,如果调用失败,则返回负值。 Listing 7展现了其使用示例,测试了 /dev/input/event0设备节点支持哪些 type feature。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <linux/input.h>
#define BITS_PER_LONG 32 #define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
static int test_bit(int nr, const volatile unsigned long *addr) { return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))); }
int main(int argc, char ** argv) {
int fd; unsigned long *evtype_b = malloc(sizeof(int)); int yalv;
if ((fd = open(argv[1], O_RDONLY)) < 0) { perror("evdev open"); exit(1); }
memset(evtype_b, 0, sizeof(evtype_b)); if (ioctl(fd, EVIOCGBIT(0, EV_MAX), evtype_b) < 0) { perror("evdev ioctl"); }
printf("Supported event types:/n");
for (yalv = 0; yalv < EV_MAX; yalv++) { if (test_bit(yalv, evtype_b)) { /* the bit is set in the event types list */ printf(" Event type 0x%02x ", yalv); switch ( yalv) { case EV_SYN : printf(" (Synch Events)/n"); break; case EV_KEY : printf(" (Keys or Buttons)/n"); break; case EV_REL : printf(" (Relative Axes)/n"); break; case EV_ABS : printf(" (Absolute Axes)/n"); break; case EV_MSC : printf(" (Miscellaneous)/n"); break; case EV_LED : printf(" (LEDs)/n"); break; case EV_SND : printf(" (Sounds)/n"); break; case EV_REP : printf(" (Repeat)/n"); break; case EV_FF : case EV_FF_STATUS: printf(" (Force Feedback)/n"); break; case EV_PWR: printf(" (Power Management)/n"); break; default: printf(" (Unknown: 0x%04hx)/n", yalv); } } }
close(fd);
} |
这个例子使用了 evtype_bit掩码 EV_MAX(在 <linux/input.h>里定义),觉得 bit array需要多少内存。当 ioctl提交后,由 event layer填充 bit array。我们测试 bit array里的每一个 bit,来确定设备支持哪些 feature type。所有的设备都支持 EV_SYNC,该 bit由 input core来设置。
下面是运行结果的样例:
如果是 keyboard,则
Supported event types: Event type 0x00 (Synchronization Events) Event type 0x01 (Keys or Buttons) Event type 0x11 (LEDs) Event type 0x14 (Repeat |
如果是 mouse,则
Supported event types: Event type 0x00 (Synchronization Events) Event type 0x01 (Keys or Buttons) Event type 0x02 (Relative Axes) |
获取设备 (from or to)的 input
当确定了设备的能力和特性后,你就知道了设备的事件类型。
获取设备的 event通过 char设备的 read function。当你从 event device (例如 /dev/input/event0)里读取 event时,你将获得一系列 events,每个 event由 input_event结构表示。
Listing 8示例展示了读取一个打开文件描述符 fd的事件。它过滤了不属于 key的事件,并打印 input_event结构里的每个域。
下面是运行的结果样例:
Event: time 1033621164.003838, type 1, code 37, value 1 Event: time 1033621164.027829, type 1, code 38, value 0 Event: time 1033621164.139813, type 1, code 38, value 1 Event: time 1033621164.147807, type 1, code 37, value 0 Event: time 1033621164.259790, type 1, code 38, value 0 Event: time 1033621164.283772, type 1, code 36, value 1 Event: time 1033621164.419761, type 1, code 36, value 0 Event: time 1033621164.691710, type 1, code 14, value 1 Event: time 1033621164.795691, type 1, code 14, value 0 |
对于每个 key,你会获得一个 key press和一个 key depress的 event。
这是 char设备的标准 read接口,所以你不需要在程序里 busy loop。此外,如果你想同时获得许多设备的 input事件时,使用 poll/select函数。
给设备发送信息,使用 char设备的标准 write函数,发送的数据必须是 input_event数据结构。
Listing 9展示了简单的给设备写数据的示例。这个例子先让 Caps Lock LED打开,等 200毫秒,然后让 Caps Lock LED关闭;然后让 Num Lock LED打开,等 200毫秒,然后让 Num Lock LED关闭。不断循环这个过程,你会看到键盘上的指示灯交替闪烁。
现在我们清楚的知道如何接收或者发送一个事件 ---key按下 /抬起,鼠标移动等等。对于一些程序,可能还需要知道设备的一些全局状态。比如,一个管理keyboard的程序需要知道当前的哪些指示灯在亮,哪些键被释放。
EVIOCGKEY ioctl用于确定一个设备的全局 key/button状态,它在 bit array里设置了每个 key/button是否被释放。 Listing 10展示了该示例。
EVIOCGLED和 EVIOCGSND ioctl与 EVIOCGKEY类似,分别表示当前 LED亮灯和声音通道打开。 Listing 11展示了 EVIOCGLED的使用。
使用 EVIOCGREP ioctl来获取键盘的重复速率。 Listing 12展示了该示例。(重复速率指你按下键后,输出的事件的次数。例如,按下 1键且不释放,console里会输出多个 1的速率)
Listing 12. Checking the Repeat Rate Settingsint rep[2]; if(ioctl(fd, EVIOCGREP, rep)) { perror("evdev ioctl"); } printf("[0]= %d, [1] = %d/n", rep[0], rep[1]); |
其中, rep[0]表示在按键重复出现之前 delay的时间; rep[1]表示按键重复出现的时间间隔。
使用 EVIOCSREP ioctl来设置键盘的重复速率。 Listing 13展示了该示例。
Listing 13. Setting the Repeat Ratesint rep[2]; rep[0] = 2500; rep[1] = 1000; if(ioctl(fd, EVIOCSREP, rep)) { perror("evdev ioctl"); } |
rep[0]/rep[1]的含义同 Listing 12。
有些 input driver支持 key mapping。使用 EVIOCGKEYCODE ioctl获取一个 key对应的 scancode。 Listing 14/Listing 15展示了 key mapping的示例。注意有些键盘驱动并不支持这个特性(比如 USB键盘)。如果你想修改 key mapping,使用 EVIOCSKEYCODE ioctl即可。
Listing 15. Mapping Keysint codes[2]; codes[0] = 58; /* M keycap */ codes[1] = 49; /* assign to N */ if(ioctl(fd, EVIOCSKEYCODE, codes)) { perror("evdev ioctl"); } |
使用 EVIOCGABS ioctl提供绝对坐标轴设备的状态信息。它为每一个绝对坐标轴提供了一个 input_absinfo数据结构(参考 Listing 16)。如果你想查看设备的全局状态,对每一个坐标轴调用 EVIOCGABS ioctl函数。 Listing 17展示了该示例。
Force Feedback (力回馈)
有三个 ioctl用于控制 force-feedback设备: EVIOCSFF, EVIOCRMFF, EVIOCGEFFECT。这三个 ioctl分别表示发送一个 force-feedback effect,删除一个 force-feedback effect,获取当前多少个 effect在同时使用。