遇到的问题是,需要取消Linux的Input驱动对面板按键长按事件的拦截。
主体过程:
一.在Input驱动中加了log,观察event的传递情况,找出input驱动是在哪里进行长按事件拦截的。
二.分析得知在Input.c的input_get_disposition函数中有对长按事件的拦截。
三.处理:在Input驱动中屏蔽拦截代码
四.更好的处理方式:设备驱动注册事件到input_event的时候改变用于表示按键状态的参数value:1改成2。
详细过程:
1.事件注入到input_event中,有三个值type,code,value
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
printk("duanliang,function=%s,type=%d,code=%d,value=%d;\n",__FUNCTION__,type,code,value);
if (is_event_supported(type, dev->evbit, EV_MAX)) {
printk("duanliang,function=%s,is_event_supported==true\n",__FUNCTION__);
spin_lock_irqsave(&dev->event_lock, flags);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
EXPORT_SYMBOL(input_event);
2.如果按键事件类型支持,则交给input_handle_event处理
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition;
disposition = input_get_disposition(dev, type, code, value);
printk("duanliang,function=%s,type=%d,code=%d,value=%d,disposition=%d;\n",__FUNCTION__,type,code,value,disposition);
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
{
printk("duanliang,function=%s,dev->event\n",__FUNCTION__);
dev->event(dev, type, code, value);
}
if (!dev->vals)
{
printk("duanliang,function=%s,!dev->value\n",__FUNCTION__);
return;
}
if (disposition & INPUT_PASS_TO_HANDLERS) {
printk("duanliang,function=%s,disposition & INPUT_PASS_TO_HANDLERS,dev->num_vals=%d\n",__FUNCTION__,dev->num_vals);
struct input_value *v;
if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
}
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
if (disposition & INPUT_FLUSH) {
printk("===============================\nduanliang,function=%s,disposition & INPUT_FLUSH==true,dev->num_vals=%d\n",__FUNCTION__,dev->num_vals);
if (dev->num_vals >= 2)
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
printk("duanliang,function=%s,before input_pass_values\n",__FUNCTION__);
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
}
}
3.其中会通过input_get_disposition处理按键的状态,决定是否拦截,这里只看type==EV_KEY的情况,这里就是对长按事件是否进行拦截的代码:
static int input_get_disposition(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
printk("duanliang,function=%s,dev->event\n",__FUNCTION__);
disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
break;
case SYN_MT_REPORT:
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break;
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX)) {
printk("duanliang,function=%s,EV_KEY,dev->key = %0xd\n",__FUNCTION__,dev->key);
/* auto-repeat bypasses state updates */
if (value == 2) {
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
if (!!test_bit(code, dev->key) != !!value) {
printk("*************************************\nduanliang,function=%s,!!test_bit(code, dev->key) != !!value\n",__FUNCTION__);
__change_bit(code, dev->key);
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
}
return disposition;
}
4.log分析:
如果是面板按键的长按,设备驱动会不停向input_event注册DOWN事件:
DOWN(value=1)
DOWN(value=1)
DOWN(value=1)
DOWN(value=1)
DOWN(value=1)
UP(value=0)
log如下:
[ 440.804905] duanliang,function=input_event,type=1,code=106,value=1;
[ 440.804909] duanliang,function=input_event,is_event_supported==true
[ 440.804911] duanliang,function=input_get_disposition,EV_KEY
[ 440.804915] duanliang,function=input_handle_event,type=1,code=106,value=1,disposition=0;
[ 440.804919] duanliang,function=input_event,type=0,code=0,value=0;
[ 440.804921] duanliang,function=input_event,is_event_supported==true
[ 440.804923] duanliang,function=input_get_disposition,dev->event
[ 440.804927] duanliang,function=input_handle_event,type=0,code=0,value=0,disposition=9;
[ 440.804930] duanliang,function=input_handle_event,disposition & INPUT_PASS_TO_HANDLERS
[ 440.804933] ===============================
[ 440.804933] duanliang,function=input_handle_event,disposition & INPUT_FLUSH==true,dev->num_vals=1
如果是红外遥控器的长按,由于红外遥控器用连续的短按来模拟长按,所以这里红外遥控器发送的是连续的一个DOWN,一个UP的发送:
DOWN(value=1)
UP(value=0)
DOWN(value=1)
UP(value=0)
DOWN(value=1)
UP(value=0)
DOWN(value=1)
UP(value=0)
DOWN(value=1)
UP(value=0)
log如下:
/ # [ 1456.495682] duanliang,function=input_event,type=1,code=106,value=1;
[ 1456.501934] duanliang,function=input_event,is_event_supported==true
[ 1456.508173] duanliang,function=input_get_disposition,EV_KEY
[ 1456.513720] *************************************
[ 1456.513720] duanliang,function=input_get_disposition,!!test_bit(code, dev->key) != !!value
[ 1456.526620] duanliang,function=input_handle_event,type=1,code=106,value=1,disposition=1;
[ 1456.534671] duanliang,function=input_handle_event,disposition & INPUT_PASS_TO_HANDLERS
[ 1456.542551] duanliang,function=input_event,type=0,code=0,value=0;
[ 1456.548614] duanliang,function=input_event,is_event_supported==true
[ 1456.554852] duanliang,function=input_get_disposition,dev->event
[ 1456.560747] duanliang,function=input_handle_event,type=0,code=0,value=0,disposition=9;
[ 1456.568625] duanliang,function=input_handle_event,disposition & INPUT_PASS_TO_HANDLERS
[ 1456.576505] ===============================
[ 1456.576505] duanliang,function=input_handle_event,disposition & INPUT_FLUSH==true,dev->num_vals=2
[ 1456.589485] duanliang,function=input_pass_values,dev=-608648192,vals=-608975360,count=2;
[ 1456.597535] duanliang,function=input_to_handler,input_to_handler begin
[ 1456.604032] duanliang,function=input_to_handler,input_to_handler begin
[ 1456.610560] duanliang,function=input_to_handler,input_to_handler begin
[ 1456.617061] duanliang,evdev_events begin
[ 1456.621042] duanliang,evdev_read begin
[ 1456.621120] duanliang,evdev_read begin
[ 1456.621123] duanliang,evdev_read,in while
[ 1456.621130] duanliang,evdev_read,in while
[ 1456.638306] duanliang,function=input_event,type=1,code=106,value=0;
[ 1456.639698] duanliang,evdev_read,in while
[ 1456.640480] duanliang,evdev_read begin
[ 1456.640484] duanliang,evdev_read,in while
[ 1456.656696] duanliang,function=input_event,is_event_supported==true
[ 1456.663158] duanliang,function=input_get_disposition,EV_KEY
[ 1456.668724] *************************************
[ 1456.668724] duanliang,function=input_get_disposition,!!test_bit(code, dev->key) != !!value
[ 1456.681622] duanliang,function=input_handle_event,type=1,code=106,value=0,disposition=1;
[ 1456.689672] duanliang,function=input_handle_event,disposition & INPUT_PASS_TO_HANDLERS
[ 1456.697733] duanliang,function=input_event,type=0,code=0,value=0;
[ 1456.703930] duanliang,function=input_event,is_event_supported==true
[ 1456.710300] duanliang,function=input_get_disposition,dev->event
[ 1456.716210] duanliang,function=input_handle_event,type=0,code=0,value=0,disposition=9;
[ 1456.724094] duanliang,function=input_handle_event,disposition & INPUT_PASS_TO_HANDLERS
[ 1456.731976] ===============================
[ 1456.731976] duanliang,function=input_handle_event,disposition & INPUT_FLUSH==true,dev->num_vals=2
[ 1456.744963] duanliang,function=input_pass_values,dev=-608648192,vals=-608975360,count=2;
[ 1456.753021] duanliang,function=input_to_handler,input_to_handler begin
[ 1456.759518] duanliang,function=input_to_handler,input_to_handler begin
[ 1456.766044] duanliang,function=input_to_handler,input_to_handler begin
[ 1456.772547] duanliang,evdev_events begin
[ 1456.776546] duanliang,evdev_read begin
[ 1456.776562] duanliang,evdev_read begin
[ 1456.776565] duanliang,evdev_read,in while
[ 1456.776578] duanliang,evdev_read begin
[ 1456.776580] duanliang,evdev_read,in while
[ 1456.796522] duanliang,evdev_read,in while
[ 1456.800611] duanliang,evdev_read,in while
拦截代码中有两个关键代码:
A:(!!test_bit(code, dev->key) != !!value)用于检测上次按键的状态是否为value,状态就存储在dev->key这个bitmap中
B:__change_bit(code, dev->key);用于反转dev->key这个bitmap中用于表示按键状态的标志位
先分析log可以知道同样的type,code,value,面板按键的第一个DOWN可以通过(!!test_bit(code, dev->key) != !!value),后续的DOWN都不可以,最后的UP可以:
面板按键:
1.第一个DOWN来的时候,value是1,此时标示为0,A成立,用B将标示改为1,
2.后续所有DOWN来的时候,A均不成立,不做处理,直到UP的到来
3.UP到来的时候,value为0,此时A成立,用B又将标示改为0,完成一次按键。
这样子,中间所有的DOWN都过滤掉了。
而红外按键,DOWN和UP都可以通过A的条件判断。
导致最后:
面板按键:
DOWN(value=1)--不拦截
DOWN(value=1)--拦截
DOWN(value=1)--拦截
DOWN(value=1)--拦截
DOWN(value=1)--拦截
UP(value=0)--不拦截
红外按键:
DOWN(value=1))--不拦截
DOWN(value=1))--不拦截
UP(value=0))--不拦截
DOWN(value=1))--不拦截
UP(value=0))--不拦截
DOWN(value=1))--不拦截
UP(value=0))--不拦截
DOWN(value=1))--不拦截
UP(value=0))--不拦截
DOWN(value=1))--不拦截
UP(value=0))--不拦截
所以要想面板按键达到和红外按键一样的效果,可以取消拦截代码,使得面板按键的拦截状况变成
DOWN(value=1)--不拦截
DOWN(value=1)--不拦截
DOWN(value=1)--不拦截
DOWN(value=1)--不拦截
DOWN(value=1)--不拦截
UP(value=0)--不拦截
这样子,上层Android系统就可以在一次面板长按过程中收到多个DOWN事件,而不是只有一个DOWN事件了。
tip:不过本身对于按键长按就有不同的处理方式:
1.长按时设备驱动只在按下和抬起的两个时间点向input_event注册一个DOWN,一个UP,上层Android系统根据DOWN,UP之间的时间间隔来判断是否长按,即利用Android自带的长按补发机制来实现长按效果。
2长按时设备驱动只要处于按下状态就向input_event不停注册DOWN,在抬起时注册一个UP,上层Android系统看来这一个长按虽然是连续的短按,但也可以达到长按效果。
我这里面板按键用的就是方式1,而红外遥控器用的是方式2,取消拦截,面板中间一系列的DOWN就不被拦截了。
而四提到的更好的方式,其实是将value改为2 来表示这个按键不需要拦截,来代替屏蔽代码以取消拦截,这样input驱动就不需要做修改,所以更为合理。
(这个从拦截代码中也能看出来,为2的话,后面的A,B两段代码就不起作用了)