• Android .kl按键布局文件


    1.介绍

    一个硬件按键的处理流程大致为:当用户按下或释放一个键时,键盘硬件会生成一个扫描码scan code,然后操作系统读取这个scan code,并将scan code扫描码映射到虚拟键码key code,最后操作系统根据映射的keycode生成输入事件,并将这些事件传递给应用程序或系统服务,进而执行相应的操作。

    scan code和key code通过Key layout file 映射,Key layout file一般用于定义物理键盘上各个键的功能和映射关系。文件通常以 .kl 为后缀,例如我们熟知的 Generic.kl文件。

    2.scan code和key code

    先来介绍下scan code和key code:

    scan code:扫描码,用于表示物理键盘或其他输入设备上按键的唯一标识符。scan code是硬件级别的编码,用于告诉操作系统哪个键被按下。

    key code:键码,是操作系统层面上用于识别和处理输入事件的编码,可以将硬件层面的按键映射到更高层次的抽象,使得应用程序能够处理输入事件而不必直接关心底层硬件的细节。key code定义在KeyEvent.java中,一些常见的按键:

    1. public static final int KEYCODE_DPAD_UP = 19;//方向上键的keycode为19
    2. public static final int KEYCODE_DPAD_DOWN = 20;//方向下键
    3. public static final int KEYCODE_DPAD_LEFT = 21;//方向左键
    4. public static final int KEYCODE_DPAD_RIGHT = 22;//方向右键
    5. public static final int KEYCODE_DPAD_CENTER = 23;//中间键

    3.按键声明

    按键映射关系声明在kl文件内,下面以一些常见的按键声明来说明它的语法规则:

    1. frameworks/base/data/keyboards/Generic.kl
    2. key 103 DPAD_UP
    3. key 104 PAGE_UP
    4. key 105 DPAD_LEFT
    5. key 106 DPAD_RIGHT
    6. key 465 ESCAPE FUNCTION

    按键声明包含关键字 key(后跟一个 Linux 按键代码编号和 Android 按键代码名称),任何一项声明都可以后跟一组由空格分隔的可选 flag。

    常见的 flag:

    FUNCTION:按键应解读为如同也按下了 FUNCTION 键。
    GESTURE:按键由用户手势(例如手掌摸触摸屏)生成。
    VIRTUAL:按键是与主触摸屏相邻的虚拟软键(电容式按钮)。这会导致启用特殊的去抖动逻辑

    上述例子表明:scan code为103映射KeyEvent上的KEYCODE_DPAD_UP(后面以此类推)。

    4.位置

    按键布局文件由 USB vendor、product(可能还包括version)ID 或输入设备名称来确定位置。系统会按顺序查阅以下路径:

    1. /odm/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
    2. /vendor/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
    3. /system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
    4. /data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
    5. /odm/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
    6. /vendor/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
    7. /system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
    8. /data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl
    9. /odm/usr/keylayout/DEVICE_NAME.kl
    10. /vendor/usr/keylayout/DEVICE_NAME.kl
    11. /system/usr/keylayout/DEVICE_NAME.kl
    12. /data/system/devices/keylayout/DEVICE_NAME.kl
    13. /odm/usr/keylayout/Generic.kl
    14. /vendor/usr/keylayout/Generic.kl
    15. /system/usr/keylayout/Generic.kl
    16. /data/system/devices/keylayout/Generic.kl

    5.Generic.kl

    系统提供了一个常规按键布局文件,名为 Generic.kl。此按键布局旨在支持各种标准外部键盘和操纵杆,一般情况下不要轻易修改这个文件。

    6.自定义kl文件

    Android支持为硬件设备自定义kl文件,kl文件的定制需要遵循一定的规则,以帮助系统和开发者区分不同的硬件设备或输入设备。

    命名规则:

    命名规则通常包括硬件的制造商(Vendor)、设备型号(Product)以及布局类型等信息,格式通常为:

    Vendor_XXX_Product_XXX.kl
    

    Vendor:代表设备的制造商或供应商。通常是一个厂商的标识符或名称的缩写。
    Product:代表具体的硬件产品型号。这个部分通常与设备的具体型号相关联,帮助区分不同的硬件设备。例如:

    某个厂商制造了一个键盘设备,厂商ID为 abcd,产品ID为 efgh,则键盘布局文件的命名可能是:
    Vendor_abcd_Product_efgh.kl

    除了基本的 Vendor、Product 形式外,一些设备可能会使用其他的命名约定来包含更多的信息,例如:

    Vendor_1234_Product_5678_Layout1.kl:可能表示设备 5678 的布局1。
    Vendor_1234_Product_5678_Pro.kl:可能表示设备 5678 的专业版布局。

    目录和位置:
    这些 .kl 文件通常存放在Android设备的系统目录中,例如 /system/usr/keylayout/。系统会根据这些文件的定义来正确处理与物理键盘相关的输入事件。

    文件内容:
    .kl 文件的内容包括键位定义和映射,和Generic.kl语法一致

    如果没有可用的设备专属kl文件,则系统将使用默认Generic.kl文件。

    7.加载流程

    kl文件的加载流程主要是在EventHub.cpp里的openDeviceLocked()方法里进行的

    1. frameworks/native/services/inputflinger/reader/EventHub.cpp
    2. void EventHub::openDeviceLocked(const std::string& devicePath) {
    3. ......
    4. // Load the key map.
    5. // We need to do this for joysticks too because the key layout may specify axes, and for
    6. // sensor as well because the key layout may specify the axes to sensor data mapping.
    7. status_t keyMapStatus = NAME_NOT_FOUND;
    8. if (device->classes.any(InputDeviceClass::KEYBOARD | InputDeviceClass::JOYSTICK |
    9. InputDeviceClass::SENSOR)) {
    10. // Load the keymap for the device.
    11. keyMapStatus = device->loadKeyMapLocked();
    12. }
    13. ......
    14. }

    调用loadKeyMapLocked()方法继续加载

    1. frameworks/native/services/inputflinger/reader/EventHub.cpp
    2. status_t EventHub::Device::loadKeyMapLocked() {
    3. return keyMap.load(identifier, configuration.get());
    4. }

     调用frameworks/native/libs/input/Keyboard.cpp里的load方法,用来加载和配置键盘映射

    1. frameworks/native/libs/input/Keyboard.cpp
    2. status_t KeyMap::load(const InputDeviceIdentifier& deviceIdentifier,
    3. const PropertyMap* deviceConfiguration) {
    4. // Use the configured key layout if available.
    5. if (deviceConfiguration) {//如果.idc文件里有指定使用的kl文件,则首先加载指定的文件
    6. std::optional keyLayoutName =
    7. deviceConfiguration->getString("keyboard.layout");//根据keyboard.layout字段来获取指定的.kl文件
    8. if (keyLayoutName.has_value()) {//如果存在指定的kl文件
    9. status_t status = loadKeyLayout(deviceIdentifier, *keyLayoutName);//加载键盘布局
    10. if (status == NAME_NOT_FOUND) {//未找到对应名称的kl文件
    11. ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
    12. "it was not found.",
    13. deviceIdentifier.name.c_str(), keyLayoutName->c_str());
    14. }
    15. }
    16. std::optional keyCharacterMapName =
    17. deviceConfiguration->getString("keyboard.characterMap");//根据keyboard.characterMap字段来获取字符映射表的名称,一般是以.kcm结尾的文件
    18. if (keyCharacterMapName.has_value()) {
    19. status_t status = loadKeyCharacterMap(deviceIdentifier, *keyCharacterMapName);//加载kcm文件。
    20. if (status == NAME_NOT_FOUND) {//未找到对应名称字符映射表
    21. ALOGE("Configuration for keyboard device '%s' requested keyboard character "
    22. "map '%s' but it was not found.",
    23. deviceIdentifier.name.c_str(), keyCharacterMapName->c_str());
    24. }
    25. }
    26. if (isComplete()) {//加载完成之后返回
    27. return OK;
    28. }
    29. }
    30. // Try searching by device identifier.
    31. if (probeKeyMap(deviceIdentifier, "")) {//通过设备标识符查找键盘映射,即查找对应目录下是否有自定义的kl文件
    32. return OK;
    33. }
    34. // Fall back on the Generic key map.
    35. // TODO Apply some additional heuristics here to figure out what kind of
    36. // generic key map to use (US English, etc.) for typical external keyboards.
    37. if (probeKeyMap(deviceIdentifier, "Generic")) {//如果没有找到合适的kl文件,返回Generic.kl文件
    38. return OK;
    39. }
    40. // Try the Virtual key map as a last resort.
    41. if (probeKeyMap(deviceIdentifier, "Virtual")) {//查找虚拟键盘
    42. return OK;
    43. }
    44. // Give up!
    45. ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
    46. deviceIdentifier.name.c_str());
    47. return NAME_NOT_FOUND;//以上方式都找不到kl文件,查找失败
    48. }

    可以看出kl文件的加载遵循以下顺序:

    (1)如果.idc文件里keyboard.layout何keyboard.characterMap有指定使用的kl、kcm文件,则首先加载指定的kl、kcm文件

    (2)如果.idc文件里没有配置指定的kl文件,那么通过设备信息去查找kl文件,一般为Vendor_XXX_Product_XXX(_Version_XXX).kl文件

    (3)如果上述两种情况都没有找到kl文件,那么加载默认的Generic.kl文件

    (4)最后尝试加载虚拟键盘映射表

  • 相关阅读:
    Hadoop生态选择(一)
    揭秘亚马逊,eBay,沃尔玛的运营秘籍:如何实现爆款产品并获得更高流量?
    QT之UDP通信
    .net core-利用BsonDocumentProjectionDefinition和Lookup进行 join 关联查询(MongoDB)
    【Web 实战】记一次攻防实战
    css总结
    佐助题库1228答案
    【数据集转换】VOC数据集转COCO数据集·代码实现+操作步骤
    月涨粉超150W,B站知识UP主是如何强势崛起?
    spring bean 生命周期
  • 原文地址:https://blog.csdn.net/li_5033/article/details/141053813