1、问题 
通过一个死循环将读取键盘对应的设备文件将触发键盘事件在屏幕上打印出来,按esc退出程序 
代码是在unbuntu10.04编译执行通过的 

2、input_event描述 
在Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作就是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为Input子系统已经完成了文件操作接口 
linux/input.h 这个文件定义了event事件的结构体,API和标准按键的编码等; 
struct input_event { 
    struct timeval time;  //按键时间 
    __u16 type; //事件类型 
    __u16 code; //要模拟成什么按键 
    __s32 value;//是按下还是释放 
}; 

type,指事件类型,常见的事件类型有: 
EV_KEY, 按键事件,如键盘的按键(按下哪个键),鼠标的左键右键(是非击下)等; 
EV_REL, 相对坐标,主要是指鼠标的移动事件(相对位移); 
EV_ABS, 绝对坐标,主要指触摸屏的移动事件 
code:事件的代码. 
如果事件的类型代码是EV_KEY,该代码code为设备键盘代码.0~127为键盘上的按键代码 
关于按键代码的定义,部分如下所示: 

#define KEY_RESERVED     0 
#define KEY_ESC  1 
#define KEY_1    2 
#define KEY_2    3 
#define KEY_3    4 
#define KEY_4    5 
#define KEY_5    6 
#define KEY_6    7 
#define KEY_7    8 
#define KEY_8    9 
#define KEY_9    10 
#define KEY_0    11 
#define KEY_MINUS    12 
#define KEY_EQUAL    13 
#define KEY_BACKSPACE    14 
#define KEY_TAB  15 
#define KEY_Q    16 
#define KEY_W    17 
#define KEY_E    18 
#define KEY_R    19 
#define KEY_T    20 
value:

事件的值.如果事件的类型代码是EV_KEY,当按键按下时值为1,松开时值为0;如果事件的类型代码是EV_REL,value的正数值和负数值分别代表两个不同方向的值. 


3、相关代码 
key_simulator.c 

Java代码  

1. #include  
2. #include  
3. #include  
4. #include  
5. #include  
6.   
7. int
8. {  
9. int
10. char ret[2];  
11.   struct input_event t;  
12.   
13. "/dev/input/event2", O_RDONLY);  
14. if (keys_fd <= 0)  
15.     {  
16. "open /dev/input/event2 device error!\n");  
17. return 0;  
18.     }  
19.   
20. while (1)  
21.     {  
22. if
23.         {  
24. if
25. if (t.value == 0 || t.value == 1)  
26.         {  
27. "key %d %s\n", t.code,  
28. "Pressed" : "Released");  
29. if(t.code==KEY_ESC)  
30. break;  
31.         }  
32.         }  
33.     }  
34.   close (keys_fd);  
35.   
36. return 0;  
37. }

4、注意问题 
1)不同的类型计算机,设备对应event信息会有所不同 
可以用cat获得的设备对应event信息: 
# cat /proc/bus/input/devices 
I: Bus=0019 Vendor=0000 Product=0001 Version=0000 
N: Name="Power Button" 
P: Phys=LNXPWRBN/button/input0 
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input0 
U: Uniq= 
H: Handlers=kbd event0 
B: EV=3 
B: KEY=100000 0 0 0 

I: Bus=0017 Vendor=0001 Product=0001 Version=0100 
N: Name="Macintosh mouse button emulation" 
P: Phys= 
S: Sysfs=/devices/virtual/input/input1 
U: Uniq= 
H: Handlers=mouse0 event1 
B: EV=7 
B: KEY=70000 0 0 0 0 0 0 0 0 
B: REL=3 

I: Bus=0011 Vendor=0001 Product=0001 Version=ab41 
N: Name="AT Translated Set 2 keyboard" 
P: Phys=isa0060/serio0/input0 
S: Sysfs=/devices/platform/i8042/serio0/input/input2 
U: Uniq= 
H: Handlers=kbd event2 
B: EV=120013 
B: KEY=4 2000000 3803078 f800d001 feffffdf ffefffff ffffffff fffffffe 
B: MSC=10 
B: LED=7 

I: Bus=0011 Vendor=0002 Product=0005 Version=0000 
N: Name="ImPS/2 Generic Wheel Mouse" 
P: Phys=isa0060/serio1/input0 
S: Sysfs=/devices/platform/i8042/serio1/input/input3 
U: Uniq= 
H: Handlers=mouse1 event3 
B: EV=7 
B: KEY=70000 0 0 0 0 0 0 0 0 
B: REL=103 


I line:这行包含身份信息,显示了 bus type是 3 (usb), vendor, product, version等信息。 
N line:这行包含了名字信息。 
P line:这行包含了物理设备信息。 
H line: 这行包含了与设备关联的 handler drivers。 
B line: 这些行包含了显示设备能力的一些位域 (bitfield)。 
我的键盘对应的事件类型是event2 

2)open /dev/input/event2 device error 
需要使用sudo或者改变设备的属性 
如下: 
sudo ./key_simulator 
或者 
chmod 777 /dev/input/event2 



本文欢迎转载,转载请注明作者与出处 
作者:流星 
出处:http://blog.sina.com.cn/staratsky 

 

认识linux input子系统 (一)

-0-.序

  本来只是想写个内核态的键盘记录的,但是发现现在的linux驱动模型已经和以前版本不同,增加了input层,几乎所有的底层驱动都把数据封装在event里上报给input子系统,这样一来,kernel看起来更加模块化,但是没有原来键盘驱动那种一站通的感觉了。

  于是研究起input层比起键盘记录更有意思了:)这里只是记录下自己学习后理清的思路,其实自己学习过程挺乱的,最近才有所感悟input层,毕竟硬件的底子我是没有的。

 

-1-.从用户层看input(event事件)

  经常捣鼓linux一定会对/dev,/sys,/proc这几个目录有所印象,这是从内核导出到用户层的接口(从这里几乎可以观览内核)。这下就方便了,kernel为我们导出了input在用户态的接口,就是/dev/input/下的接口,这里我们只关注此目录下的eventX字符设备。

  那么这些eventX是干什么用的?简单来说就是我们对计算机的输入(包括敲击键盘,移动鼠标等等操作)经过内核(底层驱动,input)处理最后就上报到这些eventX里面了。

  而这里event0,event1,..就是用来区分各个外设的,可以通过命令来查看外设具体和哪个event相关联:

  cat /proc/bus/input/devices 这里结果比较多,应为现在PC外设也蛮多的,我们可以看下键盘对应的条目,这里我截取2段:

I: Bus=0011 Vendor=0001 Product=0001 Version=ab54

N: Name="AT Translated Set 2 keyboard"

P: Phys=isa0060/serio0/input0

S: Sysfs=/devices/platform/i8042/serio0/input/input4

U: Uniq=

H: Handlers=kbd event4 

B: EV=120013

B: KEY=4 2000000 3803078 f800d001 feffffdf ffefffff ffffffff fffffffe

B: MSC=10

B: LED=7

 

I: Bus=0003 Vendor=046d Product=c52f Version=0111

N: Name="Logitech USB Receiver"

P: Phys=usb-0000:00:1d.0-1/input1

S: Sysfs=/devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.1/input/input8

U: Uniq=

H: Handlers=kbd event8 

B: EV=1f

B: KEY=837fff 2c3027 bf004444 0 0 1 f84 8a27c000 667bfa d9415fed 8e0000 0 0 0

B: REL=40

B: ABS=1 0

B: MSC=10

 

  从上面我们可以看到H:一行是 Handlers=kbd event4(或Handlers=kbd event8)

kbd(KEYBOARD)代表键盘,而后面eventX就是此键盘在/dev/input/下就对应的eventX字符设备,

我们可以看到,linux为我准备了2个驱动,分别是AT键盘和USB键盘,这里我的笔记本使用的是AT键盘(又叫PS/2键盘,一般笔记本自带的键盘都是AT兼容的)对应于/dev/input/event4,

bash> hexdump /dev/input/event4  [现在我们随意敲击键盘,会发现大量数据涌现]

  其实这些数据都是组织好的,在linux/input.h中有这些数据的结构:

struct input_event {
         struct timeval time;//事件发生的时间
         __u16 type;//事件类类型:按键和移动鼠标就是不同类型
         __u16 code;
         __s32 value;//事件值:按键a和按键b就对应不同值
};

这里事件指的是我们对外设的操作,比如按键一次a可能就产生数个input_event数据

有了这个结构就好办了,我们可以自己写个测试程序读取/dev/input/event4中的数据:

 

[c-sharp] view plaincopyprint?

 

    1. #include <stdio.h>  
    2. #include <sys/time.h>  
    3. #include <linux/input.h>  
    4. #include <stdlib.h>  
    5. void usage(char *str)  
    6. {  
    7. "<usage> %s /dev/input/eventX/n", str);  
    8.     exit(1);  
    9. }  
    10. int main(int argc,char **argv)  
    11. {  
    12.     FILE *fp;  
    13. struct input_event ie;  
    14. if (argc != 2)  
    15.         usage(argv[0]);  
    16. "r");  
    17. if (fp == NULL) {  
    18.         perror(argv[1]);  
    19.         exit(1);  
    20.     }  
    21. while (1) {  
    22. void *)&ie, sizeof(ie), 1, fp);  
    23. if (ferror(fp)) {  
    24. "fread");  
    25.             exit(1);  
    26.         }  
    27. "[timeval:sec:%d,usec:%d,type:%d,code:%d,value:%d]/n",  
    28.                     ie.time.tv_sec, ie.time.tv_usec,  
    29.                     ie.type, ie.code, ie.value);  
    30.     }  
    31. return 0;  
    32. }

    编译成测试test1

    bash> test1 /dev/input/event4  [按回车运行程序]
    [timeval:sec:1285305058,usec:857706,type:4,code:4,value:28]
    [timeval:sec:1285305058,usec:857718,type:1,code:28,value:0]
    [timeval:sec:1285305058,usec:857721,type:0,code:0,value:0]      [之后按键a]
    [timeval:sec:1285305059,usec:978376,type:4,code:4,value:30]      
    [timeval:sec:1285305059,usec:978401,type:1,code:30,value:1]
    [timeval:sec:1285305059,usec:978406,type:0,code:0,value:0]                   
    a[timeval:sec:1285305060,usec:34315,type:4,code:4,value:30]      [行首显示我在此终端按下的a]
    [timeval:sec:1285305060,usec:34327,type:1,code:30,value:0]
    [timeval:sec:1285305060,usec:34329,type:0,code:0,value:0]
    [timeval:sec:1285305061,usec:406962,type:4,code:4,value:29]

    运行程序后,我们首先看到3组input_event显示出来了,这三组数据其实是我们刚刚运行程序时按的回车键的UP码

    之后我们每按一次键都会有6组input_event显示出来,前3组是DOWN码,后3组是UP码(DOWN是按键被按下,UP是按键弹起,键盘有弹性:P)

    其实这个程序写完善,配合分析input_event输出便可以做个简单的用户态按键记录了。

    可以对照linux/input.h中分析input_event的结果,比如input_event.value值为30 对应于#define KEY_A 30 显然这是按键a被按下的值

     

    既然eventX可以读,那么eventX当然可以写,写对应的结果就是 模拟人敲击键盘,蛮有趣的,这有个完整的写eventX测试代码,

    由于我们可能对input_event不是很了解,所以先从eventX读input_event,稍加修改再回写进eventX.

     

    [c-sharp] view plaincopyprint?

    1. #include <stdio.h>  
    2. #include <sys/time.h>  
    3. #include <linux/input.h>  
    4. #include <stdlib.h>  
    5. void usage(char *str)  
    6. {  
    7. "<usage> %s /dev/input/eventX/n", str);  
    8.     exit(1);  
    9. }  
    10. int main(int argc,char **argv)  
    11. {  
    12.     FILE *fp;  
    13. struct input_event ie[6];  
    14. if (argc != 2)  
    15.         usage(argv[0]);  
    16. /* 先读取回车UP码对应的3个input_event 并将其丢弃 */  
    17. "r");  
    18. if (fp == NULL) {  
    19.         perror(argv[1]);  
    20.         exit(1);  
    21.     }  
    22. void *)ie, sizeof(struct input_event), 3, fp);  
    23. if (ferror(fp))  
    24. "fread");  
    25.     fclose(fp);  
    26. /* 循环读写eventX 每次读取完整按键码:6组input_event 
    27.      * 稍加修改并写回eventX
    28.      */  
    29. do {  
    30. "r");  
    31. if (fp == NULL) {  
    32.             perror(argv[1]);  
    33.             exit(1);  
    34.         }  
    35. void *)ie, sizeof(struct input_event), 6, fp);  
    36. if (ferror(fp))  
    37. "fread");  
    38.         fclose(fp);  
    39. "w");  
    40. if (fp == NULL) {  
    41.             perror(argv[1]);  
    42.             exit(1);  
    43.         }  
    44.         ie[1].code += 1;  
    45.         ie[4].code += 1;  
    46. void *)ie, sizeof(struct input_event), 6, fp);  
    47. if (ferror(fp)) {  
    48. "fwrite");  
    49.             exit(1);  
    50.         }  
    51.         fclose(fp);  
    52. //      printf("[timeval:sec:%d,usec:%d,type:%d,code:%d,value:%d]/n",  
    53. //              ie.time.tv_sec, ie.time.tv_usec,  
    54. //              ie.type, ie.code, ie.value);  
    55. while (1);  
    56. return 0;  
    57. }

    编译成test2

    bash> test2 /dev/input/event4           [按回车]

    asbncv                                                [连续敲击abc]

    这里KEY_A == 30 KEY_S == 31(即ie[1].code += 1;ie[4].code += 1;)

    以上测试代码有几个值得注意的地方:

    1.首先要清除我们运行测试程序时 回车的UP码

    2.eventX不能同时读写,先读出1次敲击键盘的完整6个input_event,再回写入eventX(若不这样做,测试不会成功)

    3.input_event.time不用修改,因为此项被内核忽视([?]从evdev_write()中可以看出内核并没有检测时间,而是直接响应事件)

    经过测试,更改第0,3项input_event.value并没有反映,更改第1,4项的code才有反映([?]这里更改此项只是欺骗Xwindows,Xwindows解析/dev/input/eventX向用户回显外设操作,至于详细input_event解析就要到Xwindows代码中去找了,我没看过不敢多说)

     

    这个测试程序只是简单模拟了按键事件

    -----------------------------------------------------------------------------

    发现语言不简洁,之后是从内核,从驱动看input:)