返回

Linux输入子系统音量键双击实现方案详解

Linux

Linux 输入子系统:如何处理音量键双击事件?

最近折腾一个单按钮设备,想通过修改配置文件来实现翻页功能。用nextPage EV_KEY KEY_VOLUMEUP 1 实现了按音量键翻到下一页。 现在想加个双击音量键返回上一页的功能, 遇到点麻烦。

配置文件尝试了这样改:

nextPage EV_KEY KEY_VOLUMEUP 1
prevPage EV_KEY KEY_VOLUMEUP_DOUBLETAP 1

没成功。试过很多其他写法, 都有问题。 看起来我不太懂 Linux 事件代码,求助!

由于我是在 Windows 上操作的, 没法直接用 evtest 这类工具来查看输入事件。我研究过 Linux 事件代码,也查阅过 input-event-codes.h 文件,还是没弄明白。

问题分析:为什么双击事件不好使?

KEY_VOLUMEUP_DOUBLETAP 这种写法在 Linux 输入子系统中并不存在。 Linux 输入子系统处理的是底层硬件事件,它只知道按键按下和释放(EV_KEY事件的value值为 1 和 0),至于“单击”、“双击”这些高级事件,需要在用户空间程序中自行判断和处理。 简单修改配置文件达不到区分双击的效果。

解决方案:如何实现音量键双击?

要实现双击识别,核心思路是在程序中记录按键事件的时间戳,通过判断两次按下事件的时间间隔来区分单击和双击。以下是几种可行的方案:

方案一:修改程序逻辑 (C/C++)

如果可以直接修改 kobo-btpt 的源代码,这是最直接有效的方案。

  1. 原理:

    在程序中维护一个变量,记录上次音量键按下的时间。 每次捕获到音量键按下事件(EV_KEYKEY_VOLUMEUPvalue 为 1)时,都和上次记录的时间比较。 如果时间间隔小于一个阈值(比如 500 毫秒),则认为是双击;否则认为是单击。

  2. 代码示例 (C/C++):

    这里提供一段修改后的代码示例 (仅为演示, 基于原有项目结构假设):

    #include <stdio.h>
    #include <stdlib.h>
    #include <linux/input.h>
    #include <sys/time.h> // for gettimeofday()
    
    // ... 其他代码 ...
    // 假设这是处理输入事件的函数
    void process_input_event(struct input_event ev) {
      static struct timeval last_volumeup_press = {0, 0}; // 上次音量键按下时间
      long doubleClick_threshold_ms = 500;
      if (ev.type == EV_KEY && ev.code == KEY_VOLUMEUP) {
          if (ev.value == 1) { // 按键按下
              struct timeval current_time;
              gettimeofday(&current_time, NULL);
    
              long time_diff_ms = (current_time.tv_sec - last_volumeup_press.tv_sec) * 1000 +
                                 (current_time.tv_usec - last_volumeup_press.tv_usec) / 1000;
    
              if (time_diff_ms < doubleClick_threshold_ms) {
                   // 处理双击事件 (上一页)
                  printf("Double Click Detected! (Previous Page)\n");
              } else
              {
                //   处理单击
                  printf("Single Click Detected! (Next Page)\n");
              }
               last_volumeup_press = current_time; // 更新上次按下时间
    
        } else if (ev.value == 0)
        {
          //按键释放
        }
    }
     //... 其他代码
    }
    

    这段代码加入了 gettimeofday获取时间。通过 last_volumeup_press来存储上一次点击的时间,并且在每次点击的时候与这次时间进行对比,如果在设置的时间阈值之内,则认为是双击。

  3. 进阶技巧:

    • 防抖动: 在处理按键按下事件时,可以加入一个短暂的延时(例如 20 毫秒),忽略这段时间内的所有事件,以防止按键抖动造成的误判。
    • 可配置的双击间隔: 可以将双击的时间阈值(doubleClick_threshold_ms)做成可配置的,方便用户根据自己的习惯调整。

方案二:使用触发器和脚本

如果无法直接修改源代码,可以利用现有的事件触发机制和脚本来实现。

  1. 原理:

    监听原始的按键事件(EV_KEY KEY_VOLUMEUP)。利用脚本记录每次按键事件的时间戳。通过比较时间戳判断是否为双击,然后执行对应的翻页命令。

  2. 示例(Bash 脚本结合 evemu-recordevemu-play):

    此方法稍微复杂。首先需要安装 evemu-tools(如果你的系统上没有的话)。

    • 安装:

      sudo apt-get update  # (Debian/Ubuntu)
      sudo apt-get install evemu-tools
      

      或者

      sudo yum install evemu  # (Fedora/CentOS/RHEL)
      
    • 查找输入设备: 使用evemu-record 不带参数运行可以显示设备列表,找到你的音量按钮的设备路径(/dev/input/eventX).

    • 创建脚本 (double_click_handler.sh):

      #!/bin/bash
      
      device="$1" # 第一个参数是输入设备路径
      threshold=500 # 双击阈值,单位毫秒
      last_press=0
      
      evemu-record "$device" | while read line; do
         if [[ "$line" =~ "EV_KEY.*KEY_VOLUMEUP.*value 1" ]]; then # 检测按键按下
            current_time=$(date +%s%3N) # 获取当前时间 (秒+毫秒)
      
            if [ "$last_press" != "0" ]; then
              time_diff=$((current_time - last_press))
      
               if (( time_diff < threshold )); then
                 # 双击事件, 执行上一页操作
                echo "Double Click!  Previous Page"
                # 在这里替换为你的设备上一页的命令 (使用 evemu-play 模拟按键)
               else
                  # 单击,可以执行其他操作(例如暂停)
                  echo "Single Click."
                  #执行暂停的指令.
              fi
             fi
      
             last_press=$current_time #记录这次的时间.
      
         fi
      
         if [[ "$line" =~ "EV_KEY.*KEY_VOLUMEUP.*value 0" ]]; then
            #   按键抬起, 什么都不做
             :
        fi
      
      done
      
    • 添加单机操作到配置文件: 你的kobo-btpt配置文件添加如下命令(用于实现单机下一页操作):

       nextPage EV_KEY KEY_VOLUMEUP 1
      
    • 运行脚本:

      ./double_click_handler.sh /dev/input/eventX # 替换为你的设备路径
      
  3. 原理补充:

    • evemu-record 持续输出设备事件。
    • while read line; do ... done 循环处理每一行输出。
    • date +%s%3N 获取当前时间戳(秒和毫秒)。
    • 脚本中处理value 1 (按下), 判断是否是双击. value 0直接忽略(释放)。
  4. 安全性建议:

    • 确保脚本只有执行权限,避免被恶意修改。
    • 尽量使用绝对路径引用命令,避免 PATH 环境变量被篡改。
  5. 进阶使用技巧:

    • 可以使用更精确的时间戳(如纳秒级别)来提高双击识别的准确性。
    • 如果设备支持,可以同时监听多个按键的组合操作。
    • 将双击时间可配置到文件中,读取使用。

方案三: 外部程序监控(Python)

用 Python 编写一个独立的监控程序,实现双击逻辑,再通过模拟按键输入来实现翻页控制。

  1. 原理:

利用 Python 的 evdev 库 (如果没有安装,可以使用pip install evdev)可以直接访问和读取 Linux 输入设备。程序通过监控输入事件流,识别双击并触发翻页事件.

  1. 代码示例 (Python):

    from evdev import InputDevice, categorize, ecodes, KeyEvent
    import time
    
    def handle_double_click(device_path, double_click_threshold_ms=500):
       try:
         device = InputDevice(device_path)
       except:
           print(f"Can't open device on path {device_path}")
           return
       print(f"Listening for double clicks on {device.name} ({device_path})")
       last_press_time = 0
    
       for event in device.read_loop():
          if event.type == ecodes.EV_KEY:
            if event.code == ecodes.KEY_VOLUMEUP: #按键判断
              if event.value == 1 :  #1表示按下
                 current_time = int(round(time.time() * 1000)) # 毫秒级时间戳
                 if last_press_time != 0:
                   time_diff = current_time - last_press_time
    
                   if time_diff < double_click_threshold_ms: #小于设定时间差
                        # 在这里模拟一个上一页的按键输入 (根据实际情况修改)
                       print("Double click, sending KEY_LEFT")
    
                       # 假设设备可以通过模拟 KEY_LEFT 来翻到上一页,模拟一次按下和抬起:
                       device.write(ecodes.EV_KEY, ecodes.KEY_LEFT, 1)  # 按下
                       device.write(ecodes.EV_KEY, ecodes.KEY_LEFT, 0)  # 抬起
                       device.write(ecodes.EV_SYN, ecodes.SYN_REPORT, 0) # 同步事件
    
                   else:
                      print("Single Click , do something for single click") #执行单机对应的操作
    
                 last_press_time = current_time # 单机双击都需要记录这次时间
    
              elif event.value == 0:
                 pass
    # 使用示例:
    if __name__ == "__main__":
        device_path = "/dev/input/eventX"  #  改为实际设备路径!!!
        handle_double_click(device_path)
    

    这段Python程序可以直接监控输入的input。获取其中音量增加按钮的时间,设置一个阈值,并在按下后和上一次按下的时间进行比对,来判断是单机还是双击. 通过device.write()来模拟按钮输入(这里用的是KEY_LEFT,需要按需修改).

  2. 使用 : 通过python you_file_name.py 执行, 注意device_path 要改成你设备的事件路径。

  3. 进阶技巧:

    • 可以把双击的判定阈值和模拟按键的code都改成参数,提升灵活性.
    • 和方案二结合,Python 脚本可以更方便地和其他工具、系统服务集成。