把「特定输入设备」的按键绑定到自定义 KOReader 功能——参考 kindle-hid-passthrough 的 「按下按钮 → 记录键码 → 绑定到 KOReader 动作」思路。 目前主要用于 Kindle(PW3)上的 Skycolor 翻页笔等外接 USB HID 设备(USB-A 接收器 + Micro-USB OTG 转接)。
一个带设置面板的键位绑定插件,参考 kindle-hid-passthrough 的交互,不改 KOReader 源码:
- 选设备:从
/proc/bus/input/devices列出的输入设备里挑一个。 - Map a button…:点一下后保持菜单打开,按一次设备按钮即可自动捕获键码;若该键已有映射,会直接覆盖原映射。
- Choose a key…:从全量键名列表里直接挑一个键(常用键
UP/DOWN/PAGEUP/PAGEDOWN/...排在最前,其余所有键盘/媒体/按键也都列出),不用手动按。 - 动作选择器:只用 KOReader Dispatcher 动作(翻页用
page_jmp±1 等,Paging / Display排最前),后面是 KOReader 的全部无参 Dispatcher 动作,按General / Reader / Screen & lights / Device / File browser分组。不再使用直接 event 绑定。 - 改动作 / 删除:对已存在的映射项,点进去可
Change action…或Delete mapping。 - 持久化:保存在 KOReader 设置里(
settings.reader.lua的custom_key_mapper),重启/更新不丢。 - 吞掉重复/释放,只在下按时触发一次,避免与
externalkeyboard双触发。
不再需要手改
keymap.lua。它只在首次运行作为初始默认(103→上一页、108→下一页、15→全屏刷新)种到设置里。
customkeymapper.koplugin/
├── main.lua # 插件入口:菜单、状态、按键回调
├── _meta.lua # 插件元信息(KOReader 插件列表用)
├── keymap.lua # 首次运行的默认键位(之后存进 KOReader 设置)
├── keynames.lua # 键名表
├── koreader_actions.lua # 由 KOReader dispatcher 生成的动作列表
├── ckm.map # .so 导出符号白名单(只导出 ckm_*)
├── lib/
│ ├── ckm_input.so # 已提交的原生产物(加载内核模块 / 扫设备)
│ ├── ckm_h.lua # 与 .so 对应的 FFI 声明
│ ├── ckm_hotplug.lua # 热插拔与设备打开
│ ├── ckm_ui.lua # 设置面板
│ ├── ckm_helpers.lua # 不依赖 KOReader 的小工具
│ ├── customkeymapper_i18n.lua
│ └── modules/ # 已提交的 hid.ko / usbhid.ko / mousedev.ko(+来源说明)
├── src/ckm_input.c # lib/ckm_input.so 的源码
├── scripts/ # build-ckm.sh / check-artifacts.sh
├── locale/ # en.po / zh_CN.po
└── librime_koreader/ # 构建期 submodule(交叉编译工具链,装机不用带)
Kindle 内核不内置 USB HID 输入支持:KOReader 启动时扫一次输入设备,而 /dev/input/eventN
只有在 hid/usbhid 模块加载后才会出现。本插件把这两件事都接管了,不再依赖 KindleLazy
在开机时加载模块:
- 自加载内核模块:插件
init时,lib/ckm_input.so依次insmod插件自带的lib/modules/hid.ko → usbhid.ko → mousedev.ko(幂等,已加载则跳过)。 - 补开机漏检:加载模块后重新扫描输入设备,并额外打开
/proc/bus/input/devices里 列出的按键类 USB 设备(如翻页笔),以及设置里选中的device。 - 热插拔:监听 KOReader 的
EvdevInputInsert/Remove事件,拔/插后自动开/关对应 event fd;同时带一个轻量的/proc轮询兜底(每 2 秒),即使内核不掉 USB HID 的 uevent 也能正常识别拔插。 - 仅
Device:isKindle()下启用,不影响其它平台;与externalkeyboard并存时靠Input.opened_devices去重,不改其行为。
交叉编译工具链(Zig 包装器 + 每个 target 的 ABI)来自随仓库固定的 librime_koreader
submodule(pin 见 .gitmodules),所以本插件与 rime 插件用同一套 ABI:
kindlepw2、softfp、cortex_a9、glibc 2.12、-Wl,-z,lazy。
submodule 不会随普通 git clone 自动下载,先初始化:
git clone --recurse-submodules <repo> # 新 clone
git submodule update --init --recursive # 已经 clone 过(或忘了加参数)(librime_koreader/ 里只有 .git 占位、没有 targets/ 就是没初始化;构建脚本会直接
把这行命令提示出来。)
然后构建:
./scripts/build-ckm.sh # 产物 lib/ckm_input.so只支持 PW3(kindlepw2) 一种目标:插件自带的内核模块是 3.0.35-lab126 的,换机型
.ko 根本加载不了,所以不提供 kindlehf 之类的构建——传别的 target 会被直接拒绝。
需要 zig(实测 0.16.0)与 llvm-readelf/llvm-strip(brew install zig llvm;脚本也会
自己找 /opt/homebrew/opt/llvm/bin)。若你有别处的 checkout,用
RIME_PROJ=/path/to/librime_koreader 覆盖;默认查找顺序是 RIME_PROJ →
./librime_koreader → ../../rime/librime_koreader。
产物 lib/ckm_input.so 只依赖 libc,只导出 ckm_* 符号(见 ckm.map),并由
librime_koreader/scripts/verify-elf.sh 校验 ABI 与 GLIBC 上限。插件自带的
lib/ckm_h.lua 是 FFI 声明,必须与 C 保持一致。
lib/ckm_input.so 和 lib/modules/*.ko 都在仓库里,clone 下来直接可用:不用先编译,
也不用再去别的项目捞内核模块。
./scripts/check-artifacts.sh # 校验 .ko 哈希 / .so ABI / FFI 与 C 是否同步
./scripts/check-artifacts.sh --rebuild # 再就地重编一次,逐字节比对(需要 zig)改了 src/ckm_input.c(或 lib/ckm_h.lua 的 FFI 声明)之后,必须重跑
./scripts/build-ckm.sh kindlepw2 并把新的 lib/ckm_input.so 一起提交——否则仓库里的
产物就是过期的。
.ko 的来源、vermagic 与替换步骤见 lib/modules/README.md:它们匹配 PW3 内核
3.0.35-lab126,换机型/换内核必须换成该内核的模块,否则 KOReader 只会记录
insmod ... failed,设备不会出现。
cp -r customkeymapper.koplugin /mnt/us/koreader/plugins/librime_koreader/ 只是构建期工具链,装机时不必带上
(rsync -a --exclude librime_koreader customkeymapper.koplugin /mnt/us/koreader/plugins/)。
重启 KOReader 后,打开 ⚙ 设置 → Custom key mapper,即可用面板:选设备、Map a button… 按键捕获、选动作、改/删映射。
./scripts/package-release.sh # PW3/kindlepw2;版本号取自 _meta.lua产物在 dist/release/:customkeymapper-<target>-v<version>.tar.gz 与同名 .sha256。
包内顶层就是 customkeymapper.koplugin/,直接解压进 /mnt/us/koreader/plugins/ 即可。
- 打包前会跑
scripts/check-artifacts.sh(.ko哈希、.soABI、导出符号与 FFI 是否一致), 产物漂移就直接失败,不会出包; - 包内含
MANIFEST.sha256,在插件目录里shasum -a 256 -c MANIFEST.sha256可整体校验; librime_koreader/、.git/.gitmodules、dist/都不会进包;CKM_LIBRARY=/path/to/xxx.so可指定别的构建产物(仍须是 kindlepw2 ABI),包内一律落在lib/ckm_input.so(插件固定按这个路径加载);CKM_VERSION可覆盖版本号。
lib/customkeymapper_i18n.lua:解析插件自带locale/<lang>.po,检测语言(用settings.reader.lua的language)。msgid 用group.key分组; 释义顺序 = 当前语言.po→en.po(英文回退)→ KOReadergettext(核心动作标题)。 不改 KOReader 源码、不动全局 gettext。locale/en.po:英文源串(回退语言);locale/zh_CN.po:中文翻译。 新增语言:复制locale/en.po→locale/<lang>.po,填msgstr即可,无需改代码。- 核心动作标题(
Full screen refresh、Toggle night mode…)来自 KOReader 自身翻译,自动跟随界面语言。
- 越狱的 PW3 + KUAL + KOReader 2026.07+(PW3 上要装
koreader-kindlepw2版,不要装kindlehf版)。本插件只支持 PW3/kindlepw2,其它机型不适用。 lib/ckm_input.so与lib/modules/的三个.ko都已随仓库提供,匹配 PW3 内核3.0.35-lab126(来源与追踪方法见lib/modules/README.md)。机型内核不同时插件会退化: 仍需外部先加载模块,且只依赖/proc轮询识别已有设备。- 音效/灯光等仅需 KOReader 自带动作,无需额外依赖。
若你之前手动改过
externalkeyboard.koplugin/event_map_keyboard.lua和readerui.lua,可以把这些改动(和各自的.bak)删掉,按键绑定统一由本插件管理。
- 禁用 KindleLazy 开机加载;把激光笔插好再启动 KOReader → 能翻页(补开机漏检)。
- 启动 KOReader → 拔出笔 → 插回 → 立即能翻页(热插拔回归点)。
- 连续多次拔插、sleep 唤醒后再拔插 → 均正常。
- 拔下后按键无动作、插回恢复,不误触发旧 fd。
- 检查
crash.log:出现[CKM]的模块加载 / 设备打开日志,以及(若内核掉 uevent)[FBInk]hotplug 日志。
真机实测发现:这支「TianCP LaserPen」在重新热插拔后,设备的 USB 可能枚举不回来, 导致 KOReader 收不到任何按键。这是硬件/主控兼容问题,不是插件 bug:
- 现象:
dmesg出现input irq status -75 received(EOVERFLOW,鼠标中断道上报溢出), 随后USB disconnect,再往后probe ... failed with error -110 / -71、can't set config #1, error -71。此时/dev/input/event2不出现,插件无从打开。 - 插件行为:软件层面已修好——拔插乱序导致的旧 fd /
Input.opened_devices残留会被 强制清理,只要设备重新枚举出来,插件就会自动重新打开并与按键绑定恢复。
软件上的 usbhid rebind、authorized 切 0/1、整体 unbind/bind 都无法救回,
因为 -71 是设备不给控制端点握手响应。只有物理断电能复位:
- 把翻页笔整个从 OTG 口拔下来,等约 10 秒再插回去(给笔自己的 USB 芯片掉电);
- 仍不行就长按电源彻底关机再开机(复位 eUSB/ehci 主控);
- 恢复后重新进 KUAL → 打开 KOReader,即可再次翻页。
建议:不要反复快速热插拔。一旦把它弄到“枚举不回来”,只能断电恢复。
- 硬件路线(最稳):换带供电的 USB OTG HUB、更稳的转接线,或换一个对 PW3 ehci 主控更友好的 HID 翻页器。
- 软件实验:
-75(EOVERFLOW)几乎可以肯定是鼠标接口持续上报把中断道挤爆, 进而引发整颗 USB 复位。可让插件在识别到笔时解绑 HID 鼠标接口(1-1:1.1), 只保留稳定的键盘接口(1-1:1.0),从源头去掉-75。目前尚未上线,待真机验证。
main.lua 只保留插件类、共享状态、配置持久化与输入映射钩子;功能拆到 lib/ 下:
main.lua— 插件类、shared状态、loadCfg/saveCfg、trigger/register_hook、init、 组件装配(ckm_ui.attach/ckm_hotplug.attach)。lib/ckm_ui.lua— 设置界面(addToMainMenu/ 各gen*/ 按键捕获 / 动作选择器)。lib/ckm_hotplug.lua— Kindle USB HID 自加载 + 热插拔(模块加载、设备扫描/接管、轮询、EvdevInputInsert/Remove、setupUsbHidSupport)。lib/ckm_helpers.lua— 纯工具(list_input_devices、is_button_device)。lib/ckm_h.lua— 原生lib/ckm_input.so的 FFI 声明(保持不变)。
各模块通过 attach(plugin_class, shared, deps) 注入共享状态并把自己的方法挂到插件类上,
因此 reader/filemanager 两个实例仍共享同一份 shared 状态。