代碼是基於android4.1的。
1recovery輸入事件及處理分析
1.1時序圖
1.2代碼分析
1.2.1 輸入事件初始化
Recovery的入口是recovery.cpp中的main函數,當然會根據參數的不同,進入recovery的模式也就不一樣,這裏我們就不一一介紹了,我們這裏主要看圖形界面模式,即有個人機交互的見面,用戶可以通過按鍵選擇不同的執行操作。
根據上面的時序圖中,我們可以看到,在main函數中,需要做一些界面顯示、輸入事件的初始化工作。而在這裏,我們就主要先關注輸入事件的初始化工作,即在main函數中調用了ui.cpp的Init()方法,下面看看其代碼:
void RecoveryUI::Init(){ ev_init(input_callback, NULL); //輸入事件初始化,並註冊回調函數 pthread_create(&input_t, NULL, input_thread, NULL);//創建新的線程讀取輸入事件 } |
在該方法中,主要完成了兩個動作,第一就是初始化話輸入事件,註冊了回調函數,當有輸入事件的時候回調,第二就是創建了一個新的線程,用於讀取輸入事件的數據。
我們先看看events.c中的ev_init()方法,輸入事件初始化,代碼如下:
int ev_init(ev_callbackinput_cb, void *data) { ...... dir =opendir("/dev/input");//打開文件 if(dir != 0) { while((de = readdir(dir))) { unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; if(strncmp(de->d_name,"event",5))continue; fd = openat(dirfd(dir),de->d_name, O_RDONLY); //打開設備節點 if(fd < 0) continue;
if (ioctl(fd, EVIOCGBIT(0,sizeof(ev_bits)), ev_bits) < 0){//獲取節點特性 close(fd); continue; } //判斷是否是按鍵設備 if (!test_bit(EV_KEY, ev_bits)&& !test_bit(EV_REL,ev_bits) ) { close(fd); continue; } //保存設備的信息 ev_fds[ev_count].fd = fd; ev_fds[ev_count].events = POLLIN; ev_fdinfo[ev_count].cb = input_cb; ev_fdinfo[ev_count].data = data; ev_count++; ev_dev_count++; if(ev_dev_count == MAX_DEVICES) break; } } return 0; } #define test_bit(bit, array) \ ((array)[(bit)/BITS_PER_LONG] & (1<< ((bit) %BITS_PER_LONG))) |
代碼看起來還是非常簡單的,輸入設備的設備節點都在/dev/input/目錄下,所以需要掃面下面所有的設備節點。
調用openat()打開設備節點,這是linux的系統調用,這裏就不說了。
調用ioctl的,並用宏EVIOCGBIT產生參數,獲取一個設備的特性,這裏特性會說明該設備是按鍵設備還是觸摸設備等,並將特性存在中數組ev_bits中。
定義了test_bit的宏,用於判斷ev_bits中的特性是否是我們想要的,在linux input系統中,EV_KEY指的是按鍵設備,EV_REL值相對座標,如光標移動。看到這裏了,如果我們想要接受觸摸消息,那麼我將在這裏添加EV_ABS的支持。後續會說明添加具體方法。
當打開的設備是我們想要監聽的設備的時候,我們將設備節點的文件描述符等信息添加到ev_fds結構體數組中,還有將註冊的回調函數input_cb添加到結構體數據ev_fdinfo中。
到此就完成了輸入事件的初始化,接下來就看在創建的新線程中讀取輸入事件。
1.2.2 創建線程讀取輸入事件
在前面的RecoveryUI::Init()方法中,我們看到了這麼一句:
pthread_create(&input_t, NULL,input_thread, NULL); //創建新的線程讀取輸入事件 |
調用了pthread_create()方法創建新的線程,該方法的原型如下:
int pthread_create(pthread_t*restricttidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
若成功則返回0,否則返回出錯編號
返回成功時,由tidp指向的內存單元被設置爲新創建線程的線程ID。attr參數用於制定各種不同的線程屬性。新創建的線程從start_rtn函數的地址開始運行,該函數只有一個萬能指針參數arg,如果需要向start_rtn函數傳遞的參數不止一個,那麼需要把這些參數放到一個結構中,然後把這個結構的地址作爲arg的參數傳入。
linux下用C開發多線程程序,Linux系統下的多線程遵循POSIX線程接口,稱爲pthread。 由restrict修飾的指針是最初唯一對指針所指向的對象進行存取的方法,僅當第二個指針基於第一個時,才能對對象進行存取。對對象的存取都限定於基於由restrict修飾的指針表達式中。由restrict修飾的指針主要用於函數形參,或指向由malloc()分配的內存空間。restrict數據類型不改變程序的語義。編譯器能通過作出restrict修飾的指針是存取對象的唯一方法的假設,更好地優化某些類型的例程。下面看四個參數:
第二個參數用來設置線程屬性。
第三個參數是線程運行函數的起始地址。
第四個參數是運行函數的參數。
另外,在編譯時注意加上-lpthread參數,以調用鏈接庫。因爲pthread並非Linux系統的默認庫。
在我們這裏用到的代碼中,第一個參數的定義爲:pthread_t input_t;爲指向線程標識符的指針。接下來我們主要看新線程的入口函數input_thread()方法,代碼如下:
void*RecoveryUI::input_thread(void *cookie) { for(;;) { if(!ev_wait(-1)) //查看是否有輸入事件 ev_dispatch();//有輸入事件,那麼將派發輸入事件 } return NULL; } |
在一個循環裏面不斷的查詢是否有輸入事件,那麼,我們看看events.c中的ev_wait()方法,代碼如下:
int ev_wait(int timeout) { intr; r =poll(ev_fds, ev_count,timeout); if(r <= 0) return -1; return 0; } |
還記得在輸入事件初始化的時候,將按鍵等我們想監聽的設備點信息添加到了ev_fds結構體中,在這裏我們將用到了。
系統調用poll()方法,如果ev_fds包含的設備節點中有消息,那麼返回的值r將大於0,所以ev_wait()方法的返回值爲0。再回頭看看input_thread()方法,當ev_wait()返回值等於零的時候,將調用ev_dispatch()派發輸入消息。那麼我們看看events.c中的該方法代碼:
void ev_dispatch(void) { unsigned n; intret; for(n = 0; n < ev_count; n++) { ev_callback cb = ev_fdinfo[n].cb; if (cb && (ev_fds[n].revents& ev_fds[n].events)) cb(ev_fds[n].fd, ev_fds[n].revents,ev_fdinfo[n].data);//回調 } } |
找到ev_fdinfo結構體數組中之前註冊的回調函數,實際是調用了ui.cpp中的RecoveryUI::input_callback()方法,下面看看其代碼:
intRecoveryUI::input_callback(int fd, short revents, void*data) { struct input_event ev; intret; ret = ev_get_input(fd, revents,&ev);//讀取輸入消息 if(ret) return -1; if(ev.type == EV_SYN) { return 0; } else if (ev.type == EV_REL) { ...... } if(ev.type == EV_KEY&& ev.code <=KEY_MAX)//按鍵消息 self->process_key(ev.code,ev.value);//處理按鍵消息
return 0; } |
在之前的調用用,只是通知有輸入消息,那麼在回調函數中,我們就需要去讀取輸入消息了,定義了結構體input_event的變量ev,調用了events.c的ev_get_input()方法,看下其代碼:
int ev_get_input(int fd,short revents, struct input_event *ev) { intr; if(revents & POLLIN) { r = read(fd, ev,sizeof(*ev));//讀取input消息,數據存在ev變量中 if (r == sizeof(*ev)) return 0; } return -1; }
|
在該方法中,非常簡單,系統調用read()方法去讀fd指定的設備節點的數據,並保存在ev變量中。
我們回到input_callback()方法中看,調用ev_get_input()後,讀取到的input數據存儲在ev結構體中,我們看看其結構體的定義:
struct input_event{ struct timeval time;//時間 __u16 type; //事件的類型,可以確定出是按鍵消息還是觸摸消息,或者其他. __u16 code; //數據的類型,假如是觸摸消息,那麼code可以確定消息是x座標還y座標或者其他 __s32 value; //數據的值 }; |
在input_callback()方法中,我們看到了調用ui.cpp的RecoveryUI::process_key()方法處理按鍵消息。
1.2.3 處理按鍵消息
在上一小節中,講到了在ui.cpp的RecoveryUI::process_key()方法中處理按鍵消息,下面我們看看其代碼:
voidRecoveryUI::process_key(int key_code, int updown) { boolregister_key = false; if(updown) { } else { register_key =true;//表示是按鍵的up消息 } if(register_key) { switch (CheckKey(key_code)) { case RecoveryUI::IGNORE: break; case RecoveryUI::TOGGLE: ShowText(!IsTextVisible()); break;
case RecoveryUI::REBOOT: android_reboot(ANDROID_RB_RESTART, 0, 0); break; case RecoveryUI::ENQUEUE: pthread_mutex_lock(&key_queue_mutex);//互斥鎖 const int queue_max = sizeof(key_queue) /sizeof(key_queue[0]); if (key_queue_len < queue_max) { key_queue[key_queue_len++] = key_code; //將按鍵號記錄下來 pthread_cond_signal(&key_queue_cond);//喚醒阻塞線程開始讀數據 } pthread_mutex_unlock(&key_queue_mutex); break; } } } |
在代碼中,我們可以看到,這裏指處理按鍵的up消息,所以我們只需要關注switch語句中的RecoveryUI::ENQUEUE:處理。
其實傳進來的參數 key_code就是按鍵號,在這裏的處理,就是把它存入key_queue[]數組中。但是我們知道,process_key()方法是在新開的線程中被調用的,當然還需要將消息傳送到主線程中去,所以,這裏就需要用到了互斥鎖和線程阻塞和喚醒。
在主線程初始化的時候,就初始化了互斥鎖和線程阻塞喚醒的條件變量,代碼如下:
pthread_mutex_tkey_queue_mutex; pthread_cond_t key_queue_cond; pthread_mutex_init(&key_queue_mutex,NULL);//初始化互斥鎖 pthread_cond_init(&key_queue_cond,NULL);//初始化條件變量 |
我對互斥鎖的理解是被鎖的代碼保證當前只有一個調用着,這樣可以完成代碼的同步操作。
我們在設計的時候,當沒有輸入消息的時候,主線程會進入阻塞狀態,當有輸入消息的時候,將會喚醒主線程。
所以,在上面代碼中,將對數組key_queue[]的賦值放到互斥鎖裏面,然後調用pthread_cond_signal(&key_queue_cond)喚醒主線程開始讀取按鍵消息。
1.2.4 主線程讀取輸入消息
在這節介紹之前,先來學習兩個知識點吧,互斥鎖和線程阻塞喚醒。
爲了跟上一節的輸入消息處理銜接上,我們這裏先看ui.ccp的RecoveryUI::WaitKey()方法,因爲該方法直接讀取到了key_queue[]數組中的數據,其代碼如下:
intRecoveryUI::WaitKey() { pthread_mutex_lock(&key_queue_mutex);//互斥鎖 do{ struct timeval now; struct timespec timeout; gettimeofday(&now, NULL); timeout.tv_sec = now.tv_sec; timeout.tv_nsec = now.tv_usec * 1000; timeout.tv_sec +=UI_WAIT_KEY_TIMEOUT_SEC; int rc = 0; while (key_queue_len == 0 && rc !=ETIMEDOUT) { rc =pthread_cond_timedwait(&key_queue_cond,&key_queue_mutex, &timeout);//主線程阻塞了 } }while (usb_connected() &&key_queue_len == 0);
intkey = -1; if(key_queue_len > 0) { key =key_queue[0];//獲取數組裏面第一個值 memcpy(&key_queue[0],&key_queue[1], sizeof(int) *--key_queue_len);//數據移動 } pthread_mutex_unlock(&key_queue_mutex); return key; } |
這裏的整個函數代碼都放在互斥鎖裏面,是在主線程中被調用的。我們知道當沒有輸入消息的時候,key_queue_len=0,所以將在中pthread_cond_timedwait()阻塞。對於這個阻塞,解除阻塞有兩種方法,第一種當然是在有輸入事件的時候,調用pthread_cond_signal(...)解除阻塞,第二種的timeout時間到了,會解除阻塞喚醒主線程。
所以,當有輸入事件的時候,主線程被喚醒,然後將key_queue[]數組中的第一個元素賦值給key變量,然後返回。這裏還有一個對key_queue[]的操作,當取出數組第一個元素的時候,將後面的元素通過指針的方式,移動到前面一位。
好了,這時候應該知道了主線程如何獲取到案件消息了。下面我們將從recovery.cpp的main函數開始,講解主線程得得到按鍵消息是怎麼處理的。在main方法中,進入人機交互界面調用的方法是prompt_and_wait(),其代碼如下:
static void prompt_and_wait(Device*device) { const char* const* headers =prepend_title(device->GetMenuHeaders()); for(;;) { finish_recovery(NULL); //等待用戶線程菜單 int chosen_item =get_menu_selection(headers,device->GetMenuItems(), 0, 0, device); chosen_item =device->InvokeMenuItem(chosen_item); int status; int wipe_cache; switch (chosen_item) { case Device::REBOOT://重啓 return; case Device::WIPE_DATA://格式化data分區 ...... break; case Device::WIPE_CACHE://格式化cache分區 ...... break; case Device::APPLY_EXT://從SD卡中選擇升級包升級 ....... break; case Device::APPLY_CACHE://從cache分區中選擇升級包升級 ...... break;
case Device::APPLY_ADB_SIDELOAD://通過adb進行升級 ...... break; } } } |
在該方法中,有一個for的無線循環,在該循環中,調用了get_menu_selection()方法,這個方法是核心,它會經過一步步調用,最終調用到RecoveryUI::WaitKey()方法獲取到按鍵事件,然後做出相應的處理,並返回體現用戶選擇的chosen_item變量,再經過InvokeMenuItem()處理,得到用戶真正的操作,下面的switch方法中,就是對用戶選擇做出相應的動作。
那麼,在這裏,我們就主要關注get_menu_selection()方法,裏面有我們想要的東西,其代碼如下:
static int get_menu_selection(constchar* const * headers, const char* const * items, int menu_only, int initial_selection, Device* device) { ui->StartMenu(headers,items, initial_selection);//顯示在屏幕上的菜單 intselected = initial_selection; intchosen_item = -1; while (chosen_item < 0) { int key =ui->WaitKey();//看到了這個苦等的方法,獲取按鍵號 int visible = ui->IsTextVisible(); int action = device->HandleMenuKey(key,visible);//處理按鍵消息 if (action < 0) { switch (action) { case Device::kHighlightUp:;//光標上移 --selected; selected = ui->SelectMenu(selected) break; case Device::kHighlightDown://光標下移 ++selected; selected = ui->SelectMenu(selected); break; case Device::kInvokeItem://選擇了當前的菜單 chosen_item = selected; break; case Device::kNoAction: break; } } else if (!menu_only) { chosen_item = action; } } ui->EndMenu(); return chosen_item; } |
這裏首先需要調用 ui->StartMenu(),在屏幕上顯示菜單對話框,接着在一個while()循環中等待用戶按鍵消息。這裏我們看到了調用ui->WaitKey()方法,其實就是我們前面介紹的,讀取按鍵消息,返回的是按鍵號,然後根據按鍵號,調用HandleMenuKey()方法處理,得到用戶的意圖,有上下移動光標,選擇當前菜單進入。
好了,有了上面的介紹,那麼現在我們開始加入觸摸的支持了。
2 添加touch支持
有了上面的介紹,加入觸摸的支持應該就不是什麼難事了的。說起來應該有三個步驟:
第一,應該添加觸摸消息的輸入。
第二,處理觸摸消息,將觸摸消息處理成上、下移動、點擊三種事件。
第三,添加主線程讀取觸摸消息及處理。
當然,還必須保證在recovery模式中,觸摸驅動的加載。按照上面定的三個步驟,我們一步步來實現。
2.1添加觸摸消息的輸入
這個非常簡單,只需要在events.c中的ev_init()方法中添加EV_ABS的支持即可,代碼如下:
int ev_init(ev_callbackinput_cb, void *data) { ......
if(!test_bit(EV_KEY, ev_bits) &&!test_bit(EV_REL, ev_bits)&&!test_bit(EV_ABS,ev_bits)) { close(fd); continue; } ...... } |
添加了這麼一句,就可以將觸摸事件的設備節點添加到了ev_fds[]結構體的數組中了。
2.2 處理觸摸消息
在處理觸摸消息之前,我給大家一個打印信息,是對一次觸摸消息的打印:
printf,type=3,code=57,value=0 //觸摸屏幕的手指id號,0表示第一個手指 printf,type=3,code=48,value=200 //表示觸摸的工具的接觸面爲200 printf,type=3,code=53,value=549 //x座標 printf,type=3,code=54,value=596 //y座標 printf,type=3,code=50,value=1 //可以不關注
printf,type=0,code=2,value=0 //上報了一次觸摸消息完畢 printf,type=0,code=0,value=0
printf,type=3,code=57,value=0 printf,type=3,code=48,value=200 printf,type=3,code=53,value=549 printf,type=3,code=54,value=596 printf,type=3,code=50,value=1
printf,type=0,code=2,value=0 printf,type=0,code=0,value=0
printf,type=3,code=48,value=0 //手指離開了觸摸面 printf,type=0,code=0,value=0 |
我觸摸了屏幕立即抽開,就有了上面的打印。在上面我們可以看到,一條打印代表一條輸入消息,需要5條消息才能描述一個觸摸面,上面描述了兩個觸摸面,兩次的座標是一樣的。input_event,type=3,說明是觸摸消息。下面看看input_event.code代表的意思:
#define ABS_MT_POSITION_X 0x35 //表示x座標 #define ABS_MT_POSITION_Y 0x36 //表示y座標 #define ABS_MT_TOUCH_MAJOR 0x30 //接觸面的長軸。 #define ABS_MT_WIDTH_MAJOR 0x32 //接觸工具的長軸 #define ABS_MT_TRACKING_ID 0x39 //表示當前多觸摸手指分配的ID號 |
好了,有了上面的一點點介紹,就可以對其處理了。
需要在回到函數中添加處理觸摸事件的方法,代碼如下:
intRecoveryUI::input_callback(int fd, short revents, void*data) { struct input_event ev; intret; ret= ev_get_input(fd, revents, &ev); ...... if(self->touch_handle_input(ev))//處理觸摸消息 return 0; ...... if(ev.type == EV_KEY && ev.code<= KEY_MAX) self->process_key(ev.code, ev.value);
return 0; } |
在介紹touch_handle_input()方法方法之前,先看看我定義的幾個變量及方法,在ui.h中添加如下:
structTouchEvent{ int x; int y; }mTouchEvent[5],lastEvent,firstEvent; int touch_id,move_pile; |
定義了結構體TouchEvent用來存儲觸摸事件的x、y軸座標,因爲觸摸屏支持5個手指觸摸,所以mTouchEvent[5]數組分別存儲5個手指的座標,lastEvent表示最新的一個觸摸座標,firstEvent表示觸摸按下的第一個座標。
我自己定義了touch_handle_input()方法,處理觸摸消息,其定義在ui.cpp中,代碼如下:
intRecoveryUI::touch_handle_input(input_event ev){ if(ev.type==EV_ABS){ int touch_code = 0; switch(ev.code){ case ABS_MT_TRACKING_ID: touch_id = ev.value; break; caseABS_MT_TOUCH_MAJOR: if(ev.value==0){//所有手指離開觸摸面 if((firstEvent.y==lastEvent.y)){ int*sreenPara=self->GetScreenPara();//獲取當前屏幕顯示數據 int select =mTouchEvent[0].y/sreenPara[2]-sreenPara[0]; if(select>=0&&select<sreenPara[1]){ menu_select = select;//記錄點擊的菜單列編號 touch_code =Device::kInvokeItem;//點擊動作 } } for(int i=0;i<5;i++){ mTouchEvent[i].x = 0; mTouchEvent[i].y = 0; } lastEvent.x=lastEvent.y=0; firstEvent.x=firstEvent.y=0;
} break; caseABS_MT_WIDTH_MAJOR: break; caseABS_MT_POSITION_X: //記錄x座標 lastEvent.x =ev.value; if(mTouchEvent[touch_id].x == 0){ mTouchEvent[touch_id].x = ev.value; } if(firstEvent.x==0&&touch_id==0){ firstEvent.x = ev.value; }
break; caseABS_MT_POSITION_Y: //記錄y座標 if((ev.value-lastEvent.y)*move_pile<0){ move_pile = 0; key_queue_len = 0; }else if(lastEvent.y!=0){ move_pile += ev.value-lastEvent.y; } lastEvent.y =ev.value; if(firstEvent.y==0&&touch_id==0){ firstEvent.y = ev.value; } if(mTouchEvent[touch_id].y ==0){ mTouchEvent[touch_id].y = ev.value; }elseif((ev.value-mTouchEvent[touch_id].y)>20){ touch_code =Device::kHighlightDown;//向下移動 mTouchEvent[touch_id].y =ev.value; mTouchEvent[touch_id].x =lastEvent.x; }elseif((ev.value-mTouchEvent[touch_id].y)<-20){ touch_code =Device::kHighlightUp;//向上移動 mTouchEvent[touch_id].y =ev.value; mTouchEvent[touch_id].x =lastEvent.x; } break; default : break; } pthread_mutex_lock(&key_queue_mutex); const int queue_max = sizeof(key_queue) /sizeof(key_queue[0]); if (key_queue_len <queue_max&&touch_code!=0){ key_queue[key_queue_len++] =touch_code;//往數組中添加數據 pthread_cond_signal(&key_queue_cond); //喚醒主線程豬肚數據 } pthread_mutex_unlock(&key_queue_mutex); return1; }else if(ev.type == EV_SYN){ touch_id = -1; return 0; } return 0; } |
其實代碼量也沒多少的。觸摸消息是一條條傳過來的,而且需要5條消息才能描述一個觸摸面,所以需要定義一些變量來存儲消息。
對於滑動消息,我是根據一次滑動的距離爲20個座標點的時候,認爲是一次up、或down動作,而根據第一次觸摸的座標和離開的時候的座標相等的時候,認爲is一次點擊動作。
我們看下手指離開觸摸屏的處理,當手指離開的時候,如果firstEvent.y==lastEvent.y,那麼然爲是一個點擊的動作,則需要作出響應。GetScreenPara()方法是在screen_ui.cpp中定義的,代碼如下:
int*ScreenRecoveryUI::GetScreenPara() { int*ScreenPara = new int[3]; ScreenPara[0] = menu_top; //菜單的頭信息的列數 ScreenPara[1] = menu_items; //用戶可以選擇的菜單列數 ScreenPara[2] = CHAR_HEIGHT; //每列菜單佔的高度 returnScreenPara; } |
其實這個方法就是獲取了當前顯示菜單的佈局數據,通過這三個數據,我就可以計算出每列菜單所在的座標範圍,舉個列子,用戶可以選擇的第2個菜單的座標範圍應該是從
(menu_top+1)*CHAR_HEIGHT到(menu_top+2)*CHAR_HEIGHT之間。有了這些數據,我就可以根據點擊的y座標,計算出用戶點擊的是哪一列菜單了。
好了,觸摸處理的三個動作,我存儲在一個變量touch_code中,觸摸消息與按鍵消息公用一個數據數組key_queue[],所以爲了避免與按鍵消息的衝突,我將觸摸消息處理後定義了負數,存在touch_code變量中。描述三個動作的三個變量,是在Device中原來已經定義好了的:
Device::kInvokeItem = -4 //選中
Device::kHighlightDown = -3//向下移動
Device::kHighlightUp = -2//向上移動
與按鍵消息類似,將消息存在key_queue[]數組中,主線程還是需要通過WaitKey()獲取。下面看主線程的處理。
2.3 主線程處理觸摸消息
主線程觸摸消息的處理,在get_menu_selection()方法中,該後的代碼如下:
static int get_menu_selection(constchar* const * headers, const char* const * items, int menu_only, int initial_selection, Device* device) { ui->StartMenu(headers, items,initial_selection); intselected = initial_selection; intchosen_item = -1; while (chosen_item < 0) { int key =ui->WaitKey();//讀取輸入消息 int visible = ui->IsTextVisible(); int action ; if (key == -1) { //ui_wait_key() timed out if (ui->WasTextEverVisible()) { continue; } else { LOGI("timed out waiting for key input; rebooting.\n"); ui->EndMenu(); return 0; // XXX fixme } }elseif(key<=Device::kHighlightUp&&key>=Device::kInvokeItem){//觸摸消息 action = key; }else { action =device->HandleMenuKey(key,visible);//按鍵消息 } if (action < 0) { switch (action) { case Device::kHighlightUp: --selected; selected= ui->SelectMenu(selected); break; case Device::kHighlightDown: ++selected; selected = ui->SelectMenu(selected); break; case Device::kInvokeItem: if(ui->menu_select!=-1){//觸摸消息 chosen_item = ui->menu_select; ui->menu_select = -1; }else{ chosen_item = selected; } break; case Device::kNoAction: break; } } else if (!menu_only) { chosen_item = action; } } ui->EndMenu(); return chosen_item; } |
ui->WaitKey()讀取的輸入消息存在key變量中,key=-1,說明是主線程阻塞超時,不需要任何操作。當key是爲-2、-3、-4的任意一個值時,說明是觸摸事件。那麼就可以做處理了。
參考:
使用EVIOCGBIT ioctl可以獲取設備的能力和特性:
http://hi.baidu.com/fountainblog/item/6b30290781a06a13cd34ea31
互斥鎖:http://baike.baidu.com/view/4518300.htm