[Android] 输入系统(三):加载按键映射
映射表基本概念
由于Android調用getEvents得到的key是linux發送過來的scan code,而Android處理的是類似于KEY_UP這種統一類型的key code,因此需要有映射表把scan code轉換成key code。映射表在板子上的位置是/system/usr/keylayout/xxx.kl,先看一下映射表是什么樣子的,下面截選了一段。
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 key 11 0 key 28 DPAD_CENTER key 102 HOME key 103 DPAD_UP WAKE_DROPPED key 105 DPAD_LEFT WAKE_DROPPED key 106 DPAD_RIGHT WAKE_DROPPED key 108 DPAD_DOWN WAKE_DROPPED key 111 DEL key 113 VOLUME_MUTE key 114 VOLUME_DOWN key 115 VOLUME_UP key 116 POWER可以看到每行都是一個映射項,映射項格式如下:
key? [scan code]? [key label] ?[flag label] ?[flag label] ?...
從3和4可以知道,還有一個key label到key code的過程,以及flag label到flag的過程
?
另外,映射表是設備相關的。由于不同設備發送到Android的scan code可能會不同,因此每個設備需要用自身對應的映射表才能正確解析出key code。
?
映射表加載過程
1. 獲取設備相關信息
在構造EventHub的時候,就決定了需要掃描輸入設備。然后會在第一次getEvents進行一次掃描。
掃描輸入設備主要有兩個目的:
?
掃描的目錄是/dev/input,linux中每加入一個輸入設備,都會在該目錄下創建設備文件。
status_t EventHub::scanDirLocked(const char *dirname) {char devname[PATH_MAX];char *filename;DIR *dir;struct dirent *de;dir = opendir(dirname);if(dir == NULL)return -1;strcpy(devname, dirname);filename = devname + strlen(devname);*filename++ = '/';while((de = readdir(dir))) {if(de->d_name[0] == '.' &&(de->d_name[1] == '\0' ||(de->d_name[1] == '.' && de->d_name[2] == '\0')))continue;strcpy(filename, de->d_name);openDeviceLocked(devname);}closedir(dir);return 0; }?
?
在openDeviceLocked中就能清晰分析出掃描設備的兩個目的
status_t EventHub::openDeviceLocked(const char *devicePath) {int fd = open(devicePath, O_RDWR | O_CLOEXEC); // Get device name.if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {//fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));} else {buffer[sizeof(buffer) - 1] = '\0';identifier.name.setTo(buffer);}// Get device driver version.int driverVersion;if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));close(fd);return -1;}struct input_id inputId;if(ioctl(fd, EVIOCGID, &inputId)) {ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));close(fd);return -1;}identifier.bus = inputId.bustype;identifier.product = inputId.product;identifier.vendor = inputId.vendor;identifier.version = inputId.version;...Device* device = new Device(fd, deviceId, String8(devicePath), identifier);// Figure out the kinds of events the device reports.ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);//mouse device?if (test_bit(BTN_MOUSE, device->keyBitmask)&& test_bit(REL_X, device->relBitmask)&& test_bit(REL_Y, device->relBitmask)) {device->classes |= INPUT_DEVICE_CLASS_CURSOR;}// See if this is a touch pad.// Is this a new modern multi-touch driver?if (test_bit(ABS_MT_POSITION_X, device->absBitmask)&& test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {// Some joysticks such as the PS3 controller report axes that conflict// with the ABS_MT range. Try to confirm that the device really is// a touch screen.if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;}// Is this an old style single-touch driver?} else if (test_bit(BTN_TOUCH, device->keyBitmask)&& test_bit(ABS_X, device->absBitmask)&& test_bit(ABS_Y, device->absBitmask)) {device->classes |= INPUT_DEVICE_CLASS_TOUCH;}// See if this device is a joystick.// Assumes that joysticks always have gamepad buttons in order to distinguish them// from other devices such as accelerometers that also have absolute axes.if (haveGamepadButtons) {uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK;for (int i = 0; i <= ABS_MAX; i++) {if (test_bit(i, device->absBitmask)&& (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) {device->classes = assumedClasses;break;}}}... }?
?
2. 加載映射表
通過設備信息與設備類型,我們就能去加載正確的映射表了
status_t EventHub::openDeviceLocked(const char *devicePath) {...if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {// Load the keymap for the device.keyMapStatus = loadKeyMapLocked(device);}... } status_t EventHub::loadKeyMapLocked(Device* device) {return device->keyMap.load(device->identifier, device->configuration); }?
加載配置文件分為下面幾個步驟
1. 通過設備的配置文件去加載配置文件內制定好的映射表
2. 如果1不成功則通過設備信息加載對應的映射表
3. 如果2不成功則加載通用映射表
4. 如果3不成功則加載虛擬映射表
?
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,const PropertyMap* deviceConfiguration) {// Use the configured key layout if available.if (deviceConfiguration) {String8 keyLayoutName;if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),keyLayoutName)) {status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);if (status == NAME_NOT_FOUND) {ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but ""it was not found.",deviceIdenfifier.name.string(), keyLayoutName.string());}}String8 keyCharacterMapName;if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),keyCharacterMapName)) {status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);if (status == NAME_NOT_FOUND) {ALOGE("Configuration for keyboard device '%s' requested keyboard character ""map '%s' but it was not found.",deviceIdenfifier.name.string(), keyLayoutName.string());}}if (isComplete()) {return OK;}}// Try searching by device identifier.if (probeKeyMap(deviceIdenfifier, String8::empty())) {return OK;}// Fall back on the Generic key map.// TODO Apply some additional heuristics here to figure out what kind of// generic key map to use (US English, etc.) for typical external keyboards.if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {return OK;}// Try the Virtual key map as a last resort.if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {return OK;}// Give up!ALOGE("Could not determine key map for device '%s' and no default key maps were found!",deviceIdenfifier.name.string());return NAME_NOT_FOUND; }?
一般的情況我們會走第2步,因此從probeKeyMap往下分析
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,const String8& keyMapName) {if (!haveKeyLayout()) {loadKeyLayout(deviceIdentifier, keyMapName);}if (!haveKeyCharacterMap()) {loadKeyCharacterMap(deviceIdentifier, keyMapName);}return isComplete(); }?
對于按鍵,有鍵盤按鍵與自定義按鍵兩種,兩者加載的文件后綴不同。鍵盤按鍵的映射表后綴是.kcm,而自定義按鍵映射表后綴是.kl。另外兩者映射表的格式也不同,我們這里以自定義按鍵映射表為例,其中有三個步驟:
?
1. 獲取映射表文件路徑
我們從加載映射表文件的步驟2進來,那傳入的name為空,則調用到getInputDeviceConfigurationFilePathByDeviceIdentifier,即通過設備標識來產生路徑
String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,const String8& name, InputDeviceConfigurationFileType type) {return name.isEmpty()? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type): getInputDeviceConfigurationFilePathByName(name, type); }?
如果設備標識中的vendor,product,version都不為0的話,表明可以通過這些信息來組合成一個字符串,這個字符串就是映射表文件的前綴,否則,會設備名稱deviceIdentifier.name就是映射表文件的前綴。后綴通過type指定。
String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(const InputDeviceIdentifier& deviceIdentifier,InputDeviceConfigurationFileType type) {if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {if (deviceIdentifier.version != 0) {// Try vendor product version.String8 versionPath(getInputDeviceConfigurationFilePathByName(String8::format("Vendor_%04x_Product_%04x_Version_%04x",deviceIdentifier.vendor, deviceIdentifier.product,deviceIdentifier.version),type));if (!versionPath.isEmpty()) {return versionPath;}}// Try vendor product.String8 productPath(getInputDeviceConfigurationFilePathByName(String8::format("Vendor_%04x_Product_%04x",deviceIdentifier.vendor, deviceIdentifier.product),type));if (!productPath.isEmpty()) {return productPath;}}// Try device name.return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type); }假設當前設備的設備名稱是input_ir,傳入的type是INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT,則設備的文件名為input_ir.kl
?
2.加載映射表文件
加載映射表文件最終目的是解析該文件得到映射表,其中也分為三個步驟:
- 打開映射表文件
- 創建映射表
- 解析映射表文件并把映射項加入映射表
?
我們直接看最重要的解析部分
parse函數是一個while循環,一行一行地解析映射表項
status_t KeyLayoutMap::Parser::parse() {while (!mTokenizer->isEof()) {mTokenizer->skipDelimiters(WHITESPACE);if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {String8 keywordToken = mTokenizer->nextToken(WHITESPACE);if (keywordToken == "key") {mTokenizer->skipDelimiters(WHITESPACE);status_t status = parseKey();if (status) return status;} else if (keywordToken == "axis") {mTokenizer->skipDelimiters(WHITESPACE);status_t status = parseAxis();if (status) return status;} else {ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),keywordToken.string());return BAD_VALUE;}mTokenizer->skipDelimiters(WHITESPACE);if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {ALOGE("%s: Expected end of line or trailing comment, got '%s'.",mTokenizer->getLocation().string(),mTokenizer->peekRemainderOfLine().string());return BAD_VALUE;}}mTokenizer->nextLine();}return NO_ERROR; }每一行的解析步驟如下:
?
?
下面以parseKey為例,分析它是怎么解析出scan code與key code的(由于我們沒用到usage code,所以忽略usage,直接分析scan code流程)
status_t KeyLayoutMap::Parser::parseKey() {String8 codeToken = mTokenizer->nextToken(WHITESPACE);//scan code從字符串轉換成數字int32_t code = int32_t(strtol(codeToken.string(), &end, 0));if (*end) {return BAD_VALUE;}//我們用的是scan codeKeyedVector<int32_t, Key>& map =mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;//如果有重復的scan code,會出錯返回if (map.indexOfKey(code) >= 0) {ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),mapUsage ? "usage" : "scan code", codeToken.string());return BAD_VALUE;}mTokenizer->skipDelimiters(WHITESPACE);String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);//通過label獲取key codeint32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());if (!keyCode) {ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),keyCodeToken.string());return BAD_VALUE;}//key label后可以接flag,flag從getKeyFlagByLabel解析uint32_t flags = 0;for (;;) {mTokenizer->skipDelimiters(WHITESPACE);if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;String8 flagToken = mTokenizer->nextToken(WHITESPACE);uint32_t flag = getKeyFlagByLabel(flagToken.string());if (!flag) {ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),flagToken.string());return BAD_VALUE;}if (flags & flag) {ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),flagToken.string());return BAD_VALUE;}flags |= flag;}Key key;key.keyCode = keyCode;key.flags = flags;map.add(code, key);return NO_ERROR; }?
我們在前面說過,還有個從key label到key code的流程,該流程就是在getKeyCodeByLabel中實現的
int32_t getKeyCodeByLabel(const char* label) {return int32_t(lookupValueByLabel(label, KEYCODES)); }最終從KEYCODES這個列表內,根據label查找key code
static const KeycodeLabel KEYCODES[] = {{ "SOFT_LEFT", 1 },{ "SOFT_RIGHT", 2 },{ "HOME", 3 },{ "BACK", 4 },{ "CALL", 5 },{ "ENDCALL", 6 },{ "0", 7 },{ "1", 8 },{ "2", 9 },{ "3", 10 },{ "4", 11 },{ "5", 12 },{ "6", 13 },{ "7", 14 },{ "8", 15 },{ "9", 16 },... }?
同理,在解析flag的時候也是從FLAGS這個列表內查找flag
uint32_t getKeyFlagByLabel(const char* label) {return uint32_t(lookupValueByLabel(label, FLAGS)); }// NOTE: If you edit these flags, also edit policy flags in Input.h. static const KeycodeLabel FLAGS[] = {{ "WAKE", 0x00000001 },{ "WAKE_DROPPED", 0x00000002 },{ "SHIFT", 0x00000004 },{ "CAPS_LOCK", 0x00000008 },{ "ALT", 0x00000010 },{ "ALT_GR", 0x00000020 },{ "MENU", 0x00000040 },{ "LAUNCHER", 0x00000080 },{ "VIRTUAL", 0x00000100 },{ "FUNCTION", 0x00000200 },{ NULL, 0 } };轉載于:https://www.cnblogs.com/TaigaCon/p/4763035.html
總結
以上是生活随笔為你收集整理的[Android] 输入系统(三):加载按键映射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 展示内容
- 下一篇: 【hdu 6444】Neko's loo