文章类型:技术科普 | 字数:约 4000 字 | 目标受众:Linux 用户、中文打字练习者、跟打器玩家
一个让我困惑了很久的问题
我是个中文打字练习爱好者,平时在 Linux 上用跟打器练五笔/拼音。跟打器圈子里大家最关心的指标是码长(打一个字平均按几下键)和击键(每秒按几下键)。
过去我在 Linux 的 X11 会话下使用网页版跟打器,码长和击键数据跟经验值基本吻合。但当我切到 Linux Wayland 会话后,问题出现了:码长数据明显偏低,击键也对不上。
起初我以为是输入法的锅,折腾了 fcitx5 的各种配置,换 Rime,甚至怀疑是不是浏览器的 bug。直到我搞清楚 Linux Wayland 下浏览器处理中文输入的事件模型,才意识到——这不是 bug,是架构层面的差异。
实测:两种典型的错误模式
在 Linux Wayland 下,不同浏览器使用网页版跟打器会出现两种不同的统计偏差:
模式 A:键数翻倍(约 ×2) 浏览器同时触发了 keydown 事件和 composition 事件,导致每个物理按键被重复计数一次。比如你实际按了 100 下,跟打器统计到约 200 下。码长被夸大,击键虚高。
模式 B:键数接近零(约 0.1×) 浏览器几乎不触发 keydown 事件,跟打器只在每次提交中文字符时收到一个事件。比如你打了 100 个字按了 300 下键,跟打器只统计到约 30 下(每个字只记了最终提交的一下)。码长被严重压低。
不同浏览器、不同配置下会落入不同的模式。我在测试中观察到:同一台机器,换一个浏览器就从模式 A 变成模式 B(或反过来)。无论哪种模式,码长和击键数据都与真实值不符。
根因:text-input-v3 协议
为什么会出现这两种截然不同的错误?关键在于 Linux Wayland 的 text-input-unstable-v3 协议(简称 text-input-v3)。
对比一下 X11 和 Wayland 的输入模型:
X11 模型: 物理按键 → X Server → key event(取决于 IME 框架,有时可见) → 输入法处理 → composition 事件 Wayland 模型: 物理按键 → 合成器 → text-input-v3 协议 → 输入法处理 → 仅提交最终字符给应用 ⚠️ composition 期间的按键对应用不可见
核心变化:text-input-v3 规定,当输入法处于 composition 状态时,合成器不再向应用转发按键事件。应用层只能收到预编辑文本和最终提交的字符——composition 流程对应用是黑盒。
这对隐私是好事(应用无法在 IME 组词期间侧录拼音按键),但对需要统计物理按键的跟打器来说是灾难:浏览器对这套协议的实现不统一,有的把 keydown 事件多发了一遍(模式 A),有的干脆不发(模式 B)。无论哪种,跟打器拿到的都不是真实按键数。
解决思路
搞清楚原理后,解决思路其实分两条路:
- 绕过合成器——直接从内核层读物理按键,不经过 text-input-v3 协议
- 绕回 X11——让应用走 XWayland 兼容层,回到传统的按键事件模型
两条路各有取舍,下面分别说。
方案一:绕过合成器,用 evdev 直接读内核
既然 Linux Wayland 合成器不给应用层看按键事件,那能不能绕过它?
答案是:可以。在 evdev 层面。
Linux 的输入子系统架构大致是这样的:
物理键盘 → 内核驱动 → /dev/input/event* (evdev) → Wayland 合成器读取 evdev → text-input-v3 → 应用
evdev 是 Linux 内核暴露的输入设备接口。Wayland 合成器本身也是从 evdev 读取按键数据的——如果你的应用也从 evdev 读,就绕过了合成器和 text-input-v3 协议,拿到的是统一的、不受浏览器实现差异影响的物理按键数据。
TypeType跟打器
TypeType 是一个基于 PySide6 + QML 的跟打器,走的就是这条路——从 evdev 层面直接读取物理按键事件,
evdev 是内核层的,不管你用什么浏览器、什么输入法框架,物理按键数据都是统一的。
实际使用时需要将用户加入 input 组(读取 /dev/input/event* 的权限):
sudo usermod -aG input $USER
方案二:绕回 X11,走 XWayland 兼容层
如果你不想碰 evdev 原生方案,还有一条路:让应用跑在 XWayland 模式下,回到传统的 X11 按键事件模型。
随心跟打器
随心跟打器 (follow-heart-typer) 就是这个思路。它基于 Electron.js,在 XWayland 模式下走 X11 协议通信,keydown/keyup 事件的行为与传统 X11 环境一致,实测码长和击键统计正确。
自己动手:浏览器回退 XWayland
许多 Chromium-based 浏览器也支持通过启动参数回退到 XWayland:
chromium --ozone-platform=x11
在这种模式下,keydown/keyup 事件的行为更接近传统 X11 环境。
但这个方案有局限:
- 依赖于 XWayland 的兼容性(部分发行版默认不装 XWayland)
- 本质上是用 X11 兼容层绕路,不是真正的 Linux Wayland 方案
限制与注意事项
两条方案各有局限,选择时需要权衡:
evdev 方案(方案一)的限制:
- 需要 root/input 组权限:读取
/dev/input/event* 需要适当权限 - 只能在本地使用
- 所有键盘都会被捕获
- 不区分应用:evdev 是全局的,会捕获所有按键——但对跟打器来说这正是需要的行为
XWayland 方案(方案二)的限制:
- 依赖于 XWayland 的兼容性(部分发行版默认不装)
- 本质上是用 X11 兼容层绕路,不是真正的 Linux Wayland 方案
总结
Linux Wayland 的 text-input-v3 协议将输入法的 composition 流程对应用层变成了黑盒,但不同浏览器对这套协议的实现不统一:有的导致按键被重复计数(键数翻倍),有的导致按键事件几乎全部丢失(键数接近零)。无论哪种情况,网页版跟打器在 Linux Wayland 中文输入场景下的码长和击键数据都不可靠。
解决思路分两条路:绕过合成器(evdev 直读内核)或绕回 X11(XWayland 兼容层)。目前社区有两个可用的跟打器:TypeType 走 evdev 路线,完全绕过显示协议限制;随心跟打器 走 XWayland 路线,实测统计正确。如果你暂时不想折腾,也可以试试让浏览器回退到 XWayland 模式。
如果你也在 Linux Wayland 下练中文打字,不妨留意一下你的跟打器数据是否与自己的输入习惯吻合。
附录:码长经验值参考
不同输入方案的码长差异很大,以下是打字圈的大致经验值(受词组使用率、简拼习惯等因素影响,仅供参考):
如果你使用全拼输入法,但跟打器显示码长明显低于 2.0(可能是模式 B 导致的键数丢失)或明显偏高(可能是模式 A 导致的键数翻倍),可以结合自己的输入习惯判断数据是否合理。