AndroidR Input子系统(5)解析“.kl“文件

上一篇文章看了Input系统首次启动时会扫描/dev/input目录,并且会通过函数loadKeyLayoutloadKeyCharacterMap加载和解析".kl",".kcm"文件,这两个文件的解析规则对应熟悉Input系统还是比较重要的,上一篇文章限于篇幅只看了加载过程,本篇我们就来分析一下其解析过程,还是先将loadKeyLayout函数贴出来看看:

KeyMap::loadKeyLayout

status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
        const std::string& name) {
    std::string path(getPath(deviceIdentifier, name,
            INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
    if (path.empty()) {
        return NAME_NOT_FOUND;
    }

    status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
    if (status) {
        return status;
    }

    keyLayoutFile = path;
    return OK;
}

getPath会得到".kl"的路径,KeyLayoutMap::load函数拿到路径之后进行解析,并且会为每一个解析的".kl"文件构造一个KeyLayoutMap(描述scancdoe到Android keycode的映射):

KeyLayoutMap::load

status_t KeyLayoutMap::load(const std::string& filename, sp<KeyLayoutMap>* outMap) {
    outMap->clear();

    Tokenizer* tokenizer;
    //打开".kl"文件
    status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
    if (status) {
        ALOGE("Error %d opening key layout map file %s.", status, filename.c_str());
    } else {
        sp<KeyLayoutMap> map = new KeyLayoutMap();
        if (!map.get()) {
            ALOGE("Error allocating key layout map.");
            status = NO_MEMORY;
        } else {
            //解析".kl"
            Parser parser(map.get(), tokenizer);
            status = parser.parse();
          
            if (!status) {
                *outMap = map;
            }
        }
        delete tokenizer;
    }
    return status;
}

此函数中首先通过Tokenizer打开".kl"文件,得到一个Tokenizer,这个类主要作用就是逐行解析文本文件,接着如果成功打开了".kl"文件会创建一个KeyLayoutMap对象,接着使用KeyLayoutMapTokenizer又构造了一个Parser,一看名字就知道这个对象就是解析".kl"文件的核心,其构造函数如下:

// --- KeyLayoutMap::Parser ---

KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) :
        mMap(map), mTokenizer(tokenizer) {
}

接下来我们重点进入Parserparse()函数,在看parse()函数之前我们这里先来看看parse()函数用到的Tokenizer的几个处理文本文件的函数作用:
isEof()函数:

 /**
     * 返回当前读取到的字符是否是文本结尾
     */
    inline bool isEof() const { return mCurrent == getEnd(); }

skipDelimiters(const char* delimiters))函数:

/**
     * 跳过该行中指定的字符和空格.
     */
    void skipDelimiters(const char* delimiters);

isEol()函数:


    /**
     * 返回当前读取到的字符是否是文本结尾或者是否是行尾换行
     */
    inline bool isEol() const { return isEof() || *mCurrent == '\n'; }

peekChar()函数:

 /**
     *获取当前位置的字符,文末返回空
     */
    inline char peekChar() const { return isEof() ? '\0' : *mCurrent; }

nextToken()函数:

/**
     * 在该行中查找指定字符的位置,并返回其位置之前的所有字符
     */
    String8 nextToken(const char* delimiters);

了解了这几个函数的作用之后再来看Parser::parse就很简单了:

Parser::parse

status_t KeyLayoutMap::Parser::parse() {
   //如果没有解析到".kl"文件末尾,则while循环不结束
    while (!mTokenizer->isEof()) {
        //跳过指定字符WHITESPACE,WHITESPACE = " \t\r";
        //即跳过空格,tab键和回车,
        mTokenizer->skipDelimiters(WHITESPACE);
        //isEol和isEof的区别是,换行isEol也会返回true,peekChar作用是
        //如果没到文本结尾,则返回当前读取到的字符,这里即跳过"#"开头的行(注释)
        if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
            //找到该行中,空格,tab键或者回车之前的所有字符,从".kl"文件的格式来看,
            //这里的作用就是查找该行行首的字符,有三种,"key","axis","led"
            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 if (keywordToken == "led") {
                mTokenizer->skipDelimiters(WHITESPACE);
                status_t status = parseLed();
                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;
}

此函数在一个while循环中不断的解析".kl"文件,并且是一行一行的解析,一行为一个循环,直到解析到末尾,在解析每一行时首先会跳过空格,tab键和回车,然后判断当前读取到的字符不是文本末尾或者该行的末尾,并且不是注释"#"开头,则此行是可以有效解析的行,接着通过函数nextToken获取这一行中第一个空格,tab键或者回车之前的所有字符,用个例子来说明:

Generic.kl文件中随便找一行,key之后有一个空格,所以调用nextToken函数获取到的字符串就是"key"

key 116   POWER

上面函数中就有三种解析情况:
keyword是"key",调用parseKey解析,如果解析出错则返回错误。
keyword是"axis",调用parseAxis解析,如果解析出错则返回错误。
keyword是"led",调用parseLed解析,如果解析出错则返回错误。
其他的就是不按规则的格式则返回BAD_VALUE。

先来看parseKey()函数:

Parser::parseKey

status_t KeyLayoutMap::Parser::parseKey() {
    //获取该行中第二个空格,tab键或者回车和第一个空格,tab键或者回车之间的所有字符,
    //例:key 116   POWER中,实际作用就是获取scan code为116
    String8 codeToken = mTokenizer->nextToken(WHITESPACE);
    bool mapUsage = false;
    //scan code为usage的情况
    if (codeToken == "usage") {
        mapUsage = true;
        mTokenizer->skipDelimiters(WHITESPACE);
        codeToken = mTokenizer->nextToken(WHITESPACE);
    }

    char* end;
    //使用函数strtol来进行进制转换,返回值code为转换后的结果strtol最后一个参数为0,
    //意思是:
    //采用10进制转换,但如果遇到 '0x'/'0X' 开头则会使用16进制转换,遇到'0'
    //开头则会使用8进制转换,通常情况按键的scan code都是非0开头。
    int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
    if (*end) {
        ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
                mapUsage ? "usage" : "scan code", codeToken.string());
        return BAD_VALUE;
    }
    KeyedVector<int32_t, Key>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
    
    if (map.indexOfKey(code) >= 0) {
        //添加重复值,不合法
        return BAD_VALUE;
    }
    //继续跳过空格,tab键或者回车
    mTokenizer->skipDelimiters(WHITESPACE);
    //继续使用nextToken,实际作用就是获取key 116   POWER中的POWER
    String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
    //scancdoe转keycode
    int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
    if (!keyCode) {
        ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
                keyCodeToken.string());
        return BAD_VALUE;
    }

    uint32_t flags = 0;
    for (;;) {
        mTokenizer->skipDelimiters(WHITESPACE);
        if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;

        String8 flagToken = mTokenizer->nextToken(WHITESPACE);
        //对于特殊功能按键,获取flag
        uint32_t flag = getKeyFlagByLabel(flagToken.string());
        if (!flag) {
            ...
            return BAD_VALUE;
        }
        if (flags & flag) {
            ...
            return BAD_VALUE;
        }
        flags |= flag;
    }
    //构造Key
    Key key;
    key.keyCode = keyCode;
    key.flags = flags;
    //map
    map.add(code, key);
    return NO_ERROR;
}

要理解parseKey很重要的是先理解其中用到的C/C++库函数strtol的作用,简单来说这个函数作用就是进制转换,函数原型如下:

strtol简介

long int strtol(const char *str, char **endptr, int base)

strtol会将str指向的字符串,根据参数base,按进制转化为long int, 然后返回转换后的值,而endptr是用来保存str指向的字符串中根据base规则不合法的字符串,base的取值是2~36,和特殊值0,因为上面函数用到的base为0,所以我们不去看其他值,来讲讲0的作用,base 为0是表示会自动根据字符串的格式来判断进制,非0开头的会按str为10进制转换为10进制,0x/0X开头的会按str为16进制转换为10进制,0开头的会按str为8进制转换为10进制。

举个例子:
如下str为117,则会按str10进制转换为10进制,ret就等于117,并且117中不包含10进制格式不合法的字符,所以ptr为null。*
在这里插入图片描述
如下修改str为117a,依然会按str10进制转换为10进制,ret就等于117,但是117a中包含了10进制格式不合法的字符a,所以ptr为a。*

在这里插入图片描述
如下修改str为0x117a,则会按str16进制转换为10进制,ret就等于4474,并且0x117a中不包含16进制格式不合法的字符,所以ptr为null。*
在这里插入图片描述
如下修改str为0117a,则会按str8进制转换为10进制,ret就等于79,并且0117a中包含了8进制格式不合法的字符,所以ptr为a。*
在这里插入图片描述
看了上面的例子应该对strtol函数base为0的情况有了一定理解了,这下再来看parseKey函数就会比较轻松,继续parseKey函数函数:

parseKey函数首先会继续通过nextToken函数获取按键scan code,对于scan code为"usage"的情况修改mapUsage 为true,跳过空格,tab键或者回车,拿到第三个空格,tab键或者回车与第二个空格,tab键或者回车之间的字符串,如下例子,codeToken就等于0x0c006F

key usage 0x0c006F BRIGHTNESS_UP
key usage 0x0c0070 BRIGHTNESS_DOWN

接着对于非"usage"的情况,就会直接拿到scan code通过strtol函数进行转换,从Generic.kl表来看,对于按键类型"key"调用strtol函数转换时只有两种情况,一种非0开头,一种0x开头,所以这句函数的作用就是对于非0开头的scan code就返回自身,对于0x开头的就按16进制转换为10进制。

int32_t code = int32_t(strtol(codeToken.string(), &end, 0));

继续根据mapUsage选择KeyedVector<int32_t, Key>为mKeysByScanCode还是mKeysByUsageCode,这是一个初始化的作用,接着再调用skipDelimitersnextToken函数,我们到现在已经发现了很多次这两个函数成对使用,第一次获取"key",第二次获取"116",这里第三次就是获取"POWER"了。

key 116   POWER

继续重点函数getKeyCodeByLabel,返回值为keyCode,可以想象,这个函数结束后我们按键的扫描码就成功映射到了Android键盘码。

//keyCodeToken就是"key"的按键名称,例如POWER
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());

getKeyCodeByLabel

//InputEventLabels.h
static inline int32_t getKeyCodeByLabel(const char* label) {
    return int32_t(lookupValueByLabel(label, KEYCODES));
}

继续调用lookupValueByLabel函数,传了一个重要参数KEYCODESKEYCODES是一个InputEventLabel结构体数组,

struct InputEventLabel {
    const char *literal;  //按键名称
    int value;            //按键的值
};

static const InputEventLabel KEYCODES[] = {
    // NOTE: If you add a new keycode here you must also add it to several other files.
    //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
    DEFINE_KEYCODE(UNKNOWN),
    ...
    DEFINE_KEYCODE(HOME),
    DEFINE_KEYCODE(BACK),
    DEFINE_KEYCODE(CALL),
    ...
    DEFINE_KEYCODE(STAR),
    DEFINE_KEYCODE(POUND),
    DEFINE_KEYCODE(DPAD_UP),
    DEFINE_KEYCODE(DPAD_DOWN),
    DEFINE_KEYCODE(DPAD_LEFT),
    DEFINE_KEYCODE(DPAD_RIGHT),
    DEFINE_KEYCODE(DPAD_CENTER),
    DEFINE_KEYCODE(VOLUME_UP),
    DEFINE_KEYCODE(VOLUME_DOWN),
    DEFINE_KEYCODE(POWER),
    DEFINE_KEYCODE(CAMERA),
    DEFINE_KEYCODE(CLEAR),
    ....
    ... 
   }

再把宏DEFINE_KEYCODE展开看看:

#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }

这个宏里面构造了一个InputEventLabel结构体,还是拿POWER来举例,通过宏DEFINE_KEYCODE就可以得到{“POWER”,AKEYCODE_POWER}的对应关系,"POWER"时按键名称,其按键值为AKEYCODE_POWER

继续函数lookupValueByLabel:

lookupValueByLabel

//InputEventLabels.h
static int lookupValueByLabel(const char* literal, const InputEventLabel *list) {
    while (list->literal) {
        if (strcmp(literal, list->literal) == 0) {
            return list->value;
        }
        list++;
    }
    return list->value;
}

lookupValueByLabel函数就会遍历KEYCODES数组,通过C库函数strcmp查找literal是否包含在KEYCODES数组,返回值是InputEventLabel结构体的value,即根据"POWER"则返回AKEYCODE_POWERAKEYCODE_POWER的具体数值定义在哪里呢?
来看keycodes.h:

/**
 * Key codes.
 */
enum {
    /** Unknown key code. */
    AKEYCODE_UNKNOWN         = 0,
    /** Soft Left key.
     * Usually situated below the display on phones and used as a multi-function
     * feature key for selecting a software defined function shown on the bottom left
     * of the display. */
    AKEYCODE_SOFT_LEFT       = 1,
    /** Soft Right key.
     * Usually situated below the display on phones and used as a multi-function
     * feature key for selecting a software defined function shown on the bottom right
     * of the display. */
    AKEYCODE_SOFT_RIGHT      = 2,
    /** Home key.
     * This key is handled by the framework and is never delivered to applications. */
    AKEYCODE_HOME            = 3,
    /** Back key. */
    AKEYCODE_BACK            = 4,
    /** Call key. */
    AKEYCODE_CALL            = 5,
   ...
    AKEYCODE_VOLUME_UP       = 24,
    /** Volume Down key.
     * Adjusts the speaker volume down. */
    AKEYCODE_VOLUME_DOWN     = 25,
    /** Power key. */
    AKEYCODE_POWER           = 26,
    ...
  }

我们看到这里面定义了很多AKEYCODE_XX的枚举值,AKEYCODE_POWER对应的数值就是26。

最终getKeyCodeByLabel函数就会根据按键名称,返回对应的数值,这即是scan code和keycode的一一映射关系,如果自己需要添加物理按键,就需要按照此流程进行定义。

接着再回到parseKey()函数:

status_t KeyLayoutMap::Parser::parseKey() {
 	...
 	...
	int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
    if (!keyCode) {
        
        return BAD_VALUE;
    }

    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;
}

获取到合法keycode之后来到一个死循环,又见skipDelimiters函数,继续该行跳过空格,tab键或者回车,如果时常规定义的按键此时就到行尾了,就会直接break跳出循环,最后构造一个结构体Key,将keyCodeflags保存进去,再将这个结构体保存在mKeysByScanCode(非Usage)这个map中,以scan code为键,keyCode为值。

struct Key {
        int32_t keyCode;
        uint32_t flags;
    };

另外还有一些按键是这种格式:

key 476   F11               FUNCTION
key 477   F12               FUNCTION
key 478   1                 FUNCTION

就会继续通过nextToken获取flagToken等于FUNCTION,再调用getKeyFlagByLabel函数获取flag,这个函数和前面getKeyCodeByLabel一样的作用,它会从FLAGS数组中寻找:

static const InputEventLabel FLAGS[] = {DEFINE_FLAG(VIRTUAL),
                                        DEFINE_FLAG(FUNCTION),
                                        DEFINE_FLAG(GESTURE),
                                        DEFINE_FLAG(WAKE),

                                        {nullptr, 0}};
#define DEFINE_FLAG(flag) { #flag, POLICY_FLAG_##flag }

拿到FUNCTION展开宏DEFINE_FLAG得到POLICY_FLAG_FUNCTIONPOLICY_FLAG_FUNCTION的值定义在Input.h,指示这是一个特殊功能的按键。

enum {
    ...
    // Indicates that the key is the special function modifier.
    POLICY_FLAG_FUNCTION = 0x00000004,
    ...
    }

到此Parser::parseKey()这个函数我们就已经完全分析清楚了,这个函数最大的作用就是通过InputEventLabel.h的getKeyCodeByLabel函数完成scan codekeycode的映射关系。

另外两个解析函数parseAxisparseLed的规则和parseKey类似,仅仅是其函数内部调用InputEventLabel.h的函数不一样,如parseAxis内部调用getAxisByLabelparseLed内部调用getLedByLabel,这两个函数和getKeyCodeByLabel实现也基本一致,也只有用到的宏不同,我们也没必要再浪费时间去看了。

#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
#define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }
#define DEFINE_LED(led) { #led, ALED_##led }
#define DEFINE_FLAG(flag) { #flag, POLICY_FLAG_##flag }

到此解析“.kl“文件就已经分析完了,个人认为还是比较清晰的,其实解析“.kl“文件就是简单的解析文本,一行一行的循环解析,从文本中拿到什么字符串,作对应的操作就行了,解析过程中通过三个函数parseKeyparseAxisparseLed对三种类型的的按键分别处理,对于"key",最终会构造Key结构体,存入mKeysByScanCode或者mKeysByUsageCode中,对于"axis",最终会构造AxisInfo结构体,存入mAxes中,对于Led结构体,存入mLedsByUsageCode或者mLedsByScanCode中。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值