一. 概述
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
insmod installd
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 1 ESCAPE
key 2 1
key 3 2
key 4 3
key 5 4
key 6 5
key 7 6
key 8 7
key 9 8
key 10 9
....
看到這就明白了爲什麼我們前面輸入的鍵值2,最後在瀏覽器上看到了1; 我們修改下這個文件:
key 1 ESCAPE
key 2 3
...
卸載驅動, 再重新加載驅動後,再依次輸入如下指令:
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文件;