
> IBus 引擎崩溃 = 桌面崩溃。8 个坑差点让我放弃,到 cddcb40 怎么救场。
本篇是 《TUX IM 开发日志:从零到 0.1》 系列第 2/8 篇。
第 2 篇 · IBus 引擎的第一声惨叫——我是怎么把 daemon 写崩的
第 1 篇里我给自己立了个 flag:任何异常都不能逃出引擎进程。但说实话,写出那句话的时候,我已经崩了三次了。这篇是回顾——讲讲从 aa4b094 到 cddcb40 之间,我到底被哪些 bug 折磨过,以及 cddcb40 是怎么把这些洞一次性堵上的。
第一声惨叫:每个按键变两个字母
刚开始测试拼音输入时,我输入 n,期待 buffer 是 n,结果显示 nn。输入 ni 显示 nii。每个按键都被吃了两遍。
查了半小时才发现原因:IBus 给每个按键发两次事件——一次 press (state=0),一次 release (state=0x40000000 = IBus.ModifierType.RELEASE_MASK)。我的引擎只过滤了 release 事件走 shortcut 路径,但 _handle_key 还是会把它喂给 PinyinMode.feed_key,于是 release 事件再次被当成字母加进 buffer。
修复 commit 25b94ff Filter key-release events in do_process_key_event:把 release 过滤挪到 do_process_key_event 的最顶端,任何路径之前。
> 教训:IBus 状态字段不只是修饰位——RELEASE_MASK 是一个独立的事件类型,必须在引擎入口处就拦掉,不能依赖下游模块自己防御。
第二声惨叫:Ctrl+Shift+M 按了没反应
我在配置里写了 cycle_mode = "m",按下去——日志输出 "pressed but no handler"。再按——同样的日志。
两个 bug 叠在一起:
- shortcut parser 解析错误(
8c4b8f1):parse_shortcut("m")用replace('>', '><').strip('').split('')处理,结果得到['Ctrl', ' 这是被test_engine_flow.py抓到才修的——28 个端到端测试里有一个test_pinyin_with_tone_digits`,就是专门防这种 bug。如果没有测试套件,这个 bug 大概率会跟着版本发布出去。
踩坑时间线

> 从 aa4b094 到 cddcb40,跨度约 2 周,踩了 8 个坑,几乎每个坑都让 ibus-daemon 自杀过。
第八个坑:为什么 ibus-daemon 必自杀
我犯的很多 bug,其实不是单个 bug 多致命——而是任何 bug 都致命。因为 IBus 的进程模型:
- ibus-daemon 是常驻进程,挂了就要重启整个桌面输入法。
- 引擎进程异常退出 → ibus-daemon 默认自杀(避免脏状态)。
- 所以引擎里任何未捕获的
AttributeError、KeyError、IndexError,都会让整个桌面失声。
最搞笑的一次:我改了 tux_im/asr/overlay.py 里的 _PI_2 用法,结果输入 j(拼音 j)时,整个 daemon 死了——因为 wubi 启发式判断 _looks_like_wubi 太松,j 被识别成 wubi 编码(实际只有 1 个字符),触发了 overlay 绘制,overlay 里又调了未定义的 _PI_2,异常逃出去。整个上午的工作全没了。
cddcb40:把所有洞一次性堵上
2026 年 6 月 20 日,我做了两件事:
- 每个 IBus 生命周期 hook 加 try/except——
do_enable、do_disable、do_focus_in、do_focus_out、do_property_activate、do_process_key_event、do_reset,全部包了 try/except,并把实现逻辑抽到_do_*_impl()私有方法里。 - 写了 28 个端到端测试(
tests/test_engine_flow.py)——type → candidates → select → reset 全链路模拟,每个 input mode 至少一个击键序列测试。
从那以后,引擎再也没有把 daemon 拖死过。后来加了 emoji、Google Pinyin、ASR,每次新增功能都能从测试套件里看到旧的 input mode 没坏——这是 cddcb40 留下的最大遗产。
本篇小结
- IBus 引擎崩溃 = 桌面崩溃,这是不可违反的硬约束。
- 8 个坑:release 事件、shortcut 解析、Shift keyval、prop 前缀、tone digit trie key、preedit focus mode、append vs append_text、_PI_2 未定义。
- cddcb40 的两条腿:crash isolation(catch 一切)+ test suite(28 个端到端测试)。
- 经验:每加一个新功能,先看测试套件里旧的功能有没有坏;没坏才能 commit。
> 下一篇:6 个 bug 修复拼出的核心架构。从 4bee4ca 到 89f4984,讲讲 bug 修复如何反过来塑造项目的架构——不是设计出来的,是长出来的。
评论