Input系統之鍵值映射

一. 概述

android系統的輸入事件來源在linux內核提供的/dev/input的設備節點下, 當該設備下及誒點有數據刻度時,將數據獨處並進行一系列的翻譯和加工,然後在所有的窗口中尋找合適的接受者,並派發給它;

輸入系統總體流程如下(引之深入理解android卷3 ):

clipboard.png

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 [節點路徑] [事件類型] [事件代碼] [事件值]

現在操作下往設備幾點寫入一個事件, 打開我開發板的瀏覽器:

clipboard.png

依次輸入如下指令:

sendevent /dev/input/event3 1 2 1
sendevent /dev/input/event3 1 2 0
sendevent /dev/input/event3 0 0 0

效果如下:

clipboard.png

屏幕上出現了一個數字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.klsmart 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

效果如下:

clipboard.png

這樣就達到了輸入同樣按鍵卻得到不同之的效果了;

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.klGeneric.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

效果如下:

clipboard.png

爲了方便大家查看都使用了文件前面的內容進行修改並演示;

如果想當按下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

效果如下:

clipboard.png

現在試下同時按下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

效果如下:

clipboard.png

果然和我們修改的kcm文件映射的字符保持了一致;

3.3 總結

按鍵事件的轉化流程大致如下圖:

clipboard.png

如果想個性化定製輸入的按鍵的鍵值和字符顯示只需要修改kl和kcm文件;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章