keyboard_handler 源码详细分析
工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros-tooling/keyboard_handler
版本:0.0.5,许可证 Apache 2.0。
keyboard_handler 是 跨平台终端键盘输入库:通过回调订阅按键组合(含 Ctrl/Alt/Shift 修饰键),在后台线程轮询 stdin,供 CLI 工具在运行时响应快捷键。主要下游是 rosbag2 的 play / record 交互控制;本身 不依赖 rclcpp,是纯 C++ 库 + ament 打包。
1. 仓库结构
1 | keyboard_handler/ |
构建产物: 单一共享库 libkeyboard_handler.so(Windows 为 DLL)。
2. 架构总览
| 层次 | 职责 |
|---|---|
| KeyboardHandlerBase | 回调注册/删除、KeyCode/KeyModifiers 枚举 |
| Unix/Windows Impl | 终端模式切换、后台读键、字节→枚举解析 |
| Key Map | 平台原始码 → 统一 KeyCode |
| keyboard_handler.hpp | #ifdef _WIN32 选择实现 |
3. 公共 API
3.1 平台入口
1 | #ifdef _WIN32 |
用户只需 #include "keyboard_handler/keyboard_handler.hpp"。
3.2 核心方法
| 方法 | 说明 |
|---|---|
add_key_press_callback(callback, key_code, key_modifiers) |
注册按键回调,返回 handle |
delete_key_press_callback(handle) |
按 handle 移除 |
invalid_handle (0) |
注册失败时返回 |
1 | callback_handle_t add_key_press_callback(...) |
同一按键可注册多个回调(unordered_multimap + equal_range 派发)。
3.3 KeyCode 与 KeyModifiers
- KeyCode:100+ 枚举值(字母、数字、F1–F12、方向键、Home/End 等)
- KeyModifiers:位掩码
SHIFT | ALT | CTRL - 自定义运算符:
operator|组合修饰键,operator&&检测位
工具函数:
enum_key_code_to_str()/enum_str_to_key_code()enum_key_modifiers_to_str()
4. Unix 实现(POSIX)
4.1 初始化流程
1 | if (!isatty_fn(stdin_fd_)) { |
要点:
- 必须真实 TTY:stdin 重定向到文件/管道时禁用键盘(不抛异常,便于 gtest)
- 构造时切非规范模式,析构时恢复 canonical
- Unix 可选
KeyboardHandler(false)不安装 SIGINT 处理器(rosbag2 使用此模式,避免与进程信号冲突)
4.2 读键线程
1 | do { |
4.3 parse_input 解析逻辑
| 输入特征 | 解析 |
|---|---|
2 字节且首字节 0x1B (ESC) |
ALT + 第二字节 |
单字节 'A'..'Z' |
转小写 + SHIFT |
| 单字节 0–26 | CTRL + 对应字母(+96) |
| 其余 | 查 key_codes_map_(xterm 序列) |
映射表在 default_unix_key_map.cpp,基于 xterm 控制序列(如 \x1b[A = 上箭头)。注释说明不同终端模拟器序列可能不同。
4.4 SIGINT (Ctrl+C) 处理
1 | void on_signal(int signal_number) { |
Ctrl+C 不会通过 callback 传给客户端(设计限制);仅保证终端模式恢复。
5. Windows 实现
5.1 读键线程
1 | while (!exit_) { |
- 使用
_kbhit+_getch(DOS 传统 API) - 功能键需 两次 getch(第一次 0 或 0xE0)
- ALT 通过
GetAsyncKeyState(VK_MENU)检测
5.2 win_key_code_to_enums
类似 Unix:处理 CTRL+F1..F12、SHIFT+F1..F12、大写字母→SHIFT、0–26→CTRL 等,再查 default_windows_key_map.cpp 中的 {first, second} 对。
Windows 无 SIGINT/termios 处理;构造函数也无 install_signal_handler 参数。
6. 键位映射表
Unix(xterm 序列示例)
1 | static constexpr char CURSOR_UP[] = {27, 91, 65, '\0'}; // ESC [ A |
SHIFT+F1..F12 序列在注释中列出但未启用(修饰键检测局限)。
Windows(_getch 码示例)
1 | {KeyCode::CURSOR_UP, {0xE0, 72}}, |
7. 回调生命周期管理
设计文档给出两种模式:
- 显式删除:析构时
delete_key_press_callback(handle)(rosbag2 Recorder 采用) - weak_ptr lambda:客户端先于 handler 销毁时避免悬空指针
测试中的 FakePlayer / FakeRecorder 演示 enable_shared_from_this + weak_ptr 模式。
8. 下游集成:rosbag2
8.1 Player 默认快捷键
| 键 | 默认 KeyCode | 功能 |
|---|---|---|
| 空格 | SPACE |
暂停/继续 |
| → | CURSOR_RIGHT |
播放下一条 |
| ↑ | CURSOR_UP |
提高播放速率 |
| ↓ | CURSOR_DOWN |
降低播放速率 |
8.2 Recorder
- 空格:
toggle_paused() - 析构时删除 callback handle
8.3 Unix 特殊构造
1 | #ifndef _WIN32 |
测试注入 MockKeyboardHandler 模拟按键,无需真实终端。
9. 依赖关系
1 | keyboard_handler |
纯工具库,ROS 集成体现在 rosbag2 等消费者侧。
10. 测试
| 测试 | 平台 | 方式 |
|---|---|---|
keyboard_handler_unix_tests.cpp |
Unix | 注入 mock read/isatty/tcgetattr/tcsetattr |
keyboard_handler_windows_tests.cpp |
Windows | 注入 mock _kbhit/_getch/_isatty |
| gmock + fake_player/recorder | 两者 | 生命周期、多回调、修饰键组合 |
测试覆盖:按键解析、callback 注册/删除、对象先于 handler 销毁、stdin 非 TTY 安全模式等。
11. 已知局限(设计文档 + 头文件注释)
| 问题 | Unix | Windows |
|---|---|---|
| CTRL + 0..9 | ❌ | ❌ |
| CTRL/ALT/SHIFT + F1..F12 | ❌ | 部分 |
| CTRL + SHIFT + key → 仅 CTRL + key | ✅ | ✅ |
| CTRL + ALT + key | — | ❌ |
| ALT + F1..F12 | — | ❌ |
| 多修饰键同时按下 | 可能误判 | 可能误判 |
| Ctrl+C 作 callback | ❌(SIGINT 专用) | — |
| SIGINT 与外部 handler 冲突 | 可能 | — |
| stdin 重定向 | 禁用(不死锁) | 禁用 |
| 终端类型差异 | xterm 序列可能不匹配 | — |
12. 使用示例
1 |
|
13. 设计特点
| 特点 | 说明 |
|---|---|
| 跨平台统一枚举 | 平台差异隐藏在 Impl + KeyMap |
| 回调多订阅 | multimap 支持同一键多个 listener |
| 可测试性 | 系统调用可注入(DI 构造函数) |
| gtest 友好 | 非 TTY 时不抛异常,返回 invalid_handle |
| 线程模型 | 专用读键线程 + mutex 保护 callback 表 |
| 终端恢复 | 析构/SIGINT 路径恢复 canonical 模式 |
14. 推荐阅读顺序
docs/design/README.md— 设计目标、局限、生命周期模式keyboard_handler_base.hpp— KeyCode/KeyModifiers APIkeyboard_handler_unix_impl.cpp— termios + parse_input + 线程keyboard_handler_windows_impl.cpp— kbhit/getch 路径default_*_key_map.cpp— 平台码表- rosbag2
player.cpp/recorder.cpp— 真实集成 keyboard_handler_unix_tests.cpp— mock 测试模式
15. 小结
keyboard_handler 是 ros-tooling 提供的 轻量跨平台终端键盘库:基类管理回调表,Unix 用 termios 非规范 read,Windows 用 _kbhit/_getch 轮询,通过静态映射表统一为 KeyCode + KeyModifiers。它不绑定 ROS 中间件,但被 rosbag2 play/record 用作运行时交互控制的核心依赖。使用时需注意 修饰键检测局限、Ctrl+C 不进入 callback、以及 stdin 必须连接真实终端 等约束。
如需,我可以把本文写入 ros2doc/ros-tooling/keyboard_handler源码详细分析.md,或继续分析 rosbag2 Player 如何绑定全部快捷键。
正在加载留言…