Input系統之鍵值對映
一. 概述
android系統的輸入事件來源在linux核心提供的 /dev/input
的裝置節點下, 當該裝置下及誒點有資料刻度時,將資料獨處並進行一系列的翻譯和加工,然後在所有的視窗中尋找合適的接受者,並派發給它;
輸入系統總體流程如下(引之深入理解android卷3 ):
1.1 開發環境
- 系統: ubuntu 16.04
- 執行環境: firefly-rk3288
- android版本: arm-5.1.0
二. 準備工作
2.1 模擬輸入事件
為了接下來講解原理方便, 在這裡模擬一個輸入裝置, 方法-寫一個驅動;
- 驅動原始碼
/* 參考drivers\input\keyboard\gpio_keys.c */ #include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/input.h> static struct input_dev *input_emulator_dev; static int input_emulator_init(void) { int i; int ret; /* 1. 分配一個input_dev結構體 */ input_emulator_dev = input_allocate_device(); /* 2. 設定 */ /* 2.1 能產生哪類事件 */ set_bit(EV_KEY, input_emulator_dev->evbit); set_bit(EV_REP, input_emulator_dev->evbit); /* 2.2 能產生所有的按鍵 */ for (i = 0; i < BITS_TO_LONGS(KEY_CNT); i++) input_emulator_dev->keybit[i] = ~0UL; /* 2.3 為android構造一些裝置資訊 */ input_emulator_dev->name = "smart remote"; input_emulator_dev->id.bustype = 1; input_emulator_dev->id.vendor= 0x0001; input_emulator_dev->id.product = 0x0010; input_emulator_dev->id.version = 1; /* 3. 註冊 */ ret = input_register_device(input_emulator_dev); if (ret < 0) { return -1; } return 0; } static void input_emulator_exit(void) { input_unregister_device(input_emulator_dev); input_free_device(input_emulator_dev); } module_init(input_emulator_init); module_exit(input_emulator_exit); MODULE_LICENSE("GPL");
- makefile
KERN_DIR = /media/sourcelink/Backups/5.CompileCode/firefly3288/kernel all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m+= smart_remote.o
編譯完後,放到板子上載入,使用命令 cat /proc/bus/input/devices
檢視裝置載入情況如下:
root@firefly:/data/nfs # ins insmodinstalld root@firefly:/data/nfs # insmod smart_remote.ko root@firefly:/data/nfs # cat /proc/bus/input/devices .... I: Bus=0001 Vendor=0001 Product=0010 Version=0001 N: Name="smart remote" P: Phys= S: Sysfs=/devices/virtual/input/input3 U: Uniq= H: Handlers=sysrq event3 ddr_freq keychord B: PROP=0 B: EV=100003 B: KEY=ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe
如上是我板子載入完驅動的情況,原預設的資訊我已經刪除, 也可以看下在<font color=red>/dev/input</font>, 節點下多了個<font color=red>event3</font>;
root@firefly:/data/nfs # ls /dev/input/ event0 event1 event2 event3
這樣就完成一個輸入裝置的模擬,接下來介紹兩個工具模擬事件的產生;
2.2 工具使用
android系統提供了<font color=red>getevent</font> 與<font color=red>sendevent</font>兩個工具供開發從裝置節點中讀取和寫入事件;
- getevent
語法:
getevent [-opera] [節點路徑]
可以使用 getevent -help
檢視一些具體的operation;
如果不帶指定裝置節點, 這樣會監控所有的裝置節點:
root@firefly:/data/nfs # getevent add device 1: /dev/input/event3 name:"smart remote" add device 2: /dev/input/event2 name:"RK_ES8323 Headphone Jack" add device 3: /dev/input/event1 name:"rk29-keypad" add device 4: /dev/input/event0 name:"ff680000.pwm"
現在我將自己的鍵盤插到開發板上了,使用 getevent
命令來監控事件;
當我按下並鬆開鍵盤上的數字 1
鍵獲取到的資料如下:
root@firefly:/data/nfs # getevent /dev/input/event4 0004 0004 0007001e 0001 0002 00000001 0000 0000 00000000 0004 0004 0007001e 0001 0002 00000000 0000 0000 00000000
資料意義依次是: 事件型別, 事件程式碼, 事件值
事件值的1表示按下, 0表示抬起, 觀察資料可以發現每次按下或抬起都會獲取到 0000 0000 00000000
的資料, 表示同步事件,通知此次事件已經結束可以進行處理了;
這裡的事件程式碼是linux端發來的原始資料, 在android端還會進行一次鍵值佈局,下面筆者會講解這個對映關係;
- setevent
語法:
setevent [節點路徑] [事件型別] [事件程式碼] [事件值]
現在操作下往裝置幾點寫入一個事件, 開啟我開發板的瀏覽器:
依次輸入如下指令:
sendevent /dev/input/event3 1 2 1 sendevent /dev/input/event3 1 2 0 sendevent /dev/input/event3 0 0 0
效果如下:
螢幕上出現了一個數字1, 最後傳送的 0 0 0
表示同步事件,通知此次事件已經結束可以進行處理了.
三. 按鍵佈局和鍵值對映
3.1 Key Layout
按鍵事件來源於linux核心, 但是在android端對按鍵的值有重新做一個佈局,即將linux key code轉換為android key code, 這個佈局依賴於個 .kl
檔案, 全稱: Key Layout Files;
該檔案搜尋路徑如下:
/odm/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/vendor/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/odm/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/vendor/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl
/odm/usr/keylayout/DEVICE_NAME.kl
/vendor/usr/keylayout/DEVICE_NAME.kl
/system/usr/keylayout/DEVICE_NAME.kl
/data/system/devices/keylayout/DEVICE_NAME.kl
/odm/usr/keylayout/Generic.kl
/vendor/usr/keylayout/Generic.kl
/system/usr/keylayout/Generic.kl
/data/system/devices/keylayout/Generic.kl
從上面路徑資訊可以看出Key Layout Files檔案的命名和廠家資訊有關, 具體為供應商id, 產品id和裝置名有關;
比如我們的模擬輸入裝置的需要的 .kl
檔案可以命名為 Vendor_0001_Product_0010.kl
或 smart remote.kl
;
輸入系統在檢測到有新裝置接入時, 在上述路徑查詢對應符合規則的 .kl
檔案並載入它,如果沒有找到則載入 Generic.kl
檔案;
- 修改測試
拷貝一個 Generic.kl
和我們模擬按鍵裝置名字一樣, 並加上許可權,如果你的板子上沒有該目錄的話則建立它;
cp /system/usr/keylayout/Generic.kl /data/system/devices/keylayout/smart_remote.kl chmod 777 /data/system/devices/keylayout/smart_remote.kl
我們開啟這個檔案看下里面的內容:
key 1ESCAPE key 21 key 32 key 43 key 54 key 65 key 76 key 87 key 98 key 109 ....
看到這就明白了為什麼我們前面輸入的鍵值2,最後在瀏覽器上看到了1; 我們修改下這個檔案:
key 1ESCAPE key 23 ...
解除安裝驅動, 再重新載入驅動後,再依次輸入如下指令:
sendevent /dev/input/event3 1 2 1 sendevent /dev/input/event3 1 2 0 sendevent /dev/input/event3 0 0 0
效果如下:
這樣就達到了輸入同樣按鍵卻得到不同之的效果了;
3.2 Key Character Map
負責將android key code與修飾符的組合對映到Unicode字元。
/odm/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/vendor/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/system/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/odm/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/vendor/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/system/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX.kcm
/odm/usr/keychars/DEVICE_NAME.kcm
/vendor/usr/keychars/DEVICE_NAME.kcm
/system/usr/keychars/DEVICE_NAME.kcm
/data/system/devices/keychars/DEVICE_NAME.kcm
/odm/usr/keychars/Generic.kcm
/vendor/usr/keychars/Generic.kcm
/system/usr/keychars/Generic.kcm
/data/system/devices/keychars/Generic.kcm
/odm/usr/keychars/Virtual.kcm
/vendor/usr/keychars/Virtual.kcm
/system/usr/keychars/Virtual.kcm
/data/system/devices/keychars/Virtual.kcm
輸入系統在建立裝置時會在這些路徑下查詢對應的 .kl
和 .kcm
檔案進行載入, 如果沒有找到對應的檔案將會載入 Generic.kl
和 Generic.kcm
檔案
- 修改測試
我們現在根據我們的模擬裝置來更改下kcm檔案, 操作如下:
mkdir /data/system/devices/keychars cp /system/usr/keychars/Generic.kcm /data/system/devices/keychars/smart_remote.kcm
開啟該檔案檢視下里面內容:
### Basic QWERTY keys ### key A { label:'A' base:'a' shift, capslock:'A' } key B { label:'B' base:'b' shift, capslock:'B' } key C { label:'C' base:'c' shift, capslock:'C' alt:'\u00e7' shift+alt:'\u00c7' } ....
以按鍵<font color=red>A</font>為例, 檢視 .kl
檔案當輸入事件程式碼為 30
時, 對應到android的 key A
,
根據 .kcm
檔案知道此時會對映成字元 a
, 當按下 shift
鍵時再按下 A
會顯示字元 A
;
輸入如下指令看下效果:
sendevent /dev/input/event3 1 30 1 sendevent /dev/input/event3 1 30 0 sendevent /dev/input/event3 0 0 0
效果如下:
為了方便大家檢視都使用了檔案前面的內容進行修改並演示;
如果想當按下a鍵時顯示字元b, 按下shift+a時顯示字元2修改下kcm檔案,如下:
key A { label:'A' base:'b' shift, capslock:'2' } ...
重新解除安裝驅動並載入驅動, 再次執行:
sendevent /dev/input/event3 1 30 1 sendevent /dev/input/event3 1 30 0 sendevent /dev/input/event3 0 0 0
效果如下:
現在試下同時按下shift鍵的效果, 依次輸入如下:
sendevent /dev/input/event3 1 42 1 sendevent /dev/input/event3 1 30 1 sendevent /dev/input/event3 1 30 0 sendevent /dev/input/event3 0 0 0
效果如下:
果然和我們修改的kcm檔案對映的字元保持了一致;
3.3 總結
按鍵事件的轉化流程大致如下圖:
如果想個性化定製輸入的按鍵的鍵值和字元顯示只需要修改kl和kcm檔案;