Skip to content

Releases: 173787247/pingzhu

v0.10.6 — 七种键盘布局,六个前端都接得上

Choose a tag to compare

@173787247 173787247 released this 28 Oct 03:55

v0.10.6 — 七种键盘布局,六个前端都接得上

一句话

引擎从两种键盘布局扩到七种(新增大千式以外的许氏、倚天 26 键、IBM、精业、神通),
并且每一个前端都能选:Windows 与 Linux 读设定档、macOS 用菜单、Android 用设置页、
HarmonyOS 长按「繁/简」、网页试打版用选单。

一、七种布局,表不是手抄的

路线图 M8 的第 4 项原本写着「许氏、倚天 26 键未支援,这两种排列需要有状态的编辑器」——
这个判断是错的。多成分键(许氏的 l 同时是 ㄌ/ㄥ/ㄦ)不需要状态机:分词搜索本来就会
把每个键展开成所有成分组合,再交给库存与语法检查。

表格来自参考实作(McBopomofo,MIT),用生成器读出来,而不是凭记忆敲:

node research/tools/import-mcbopomofo-layouts.mjs --ts     # 吐 TypeScript
node research/tools/import-mcbopomofo-layouts.mjs --rust   # 吐 Rust

手抄一张 25 键的布局只会在一个格子上错,而且不会出声。生成器的输出再由
core-rs/tests/fixture-layouts.tsv(7 布局 56 条)把 TS 与 Rust 逐句对照。

文书里原来举的例子也不成立:它说许氏把 ㄑ 实作为 v(ㄔ) + ㄧ/ㄩ 的自动转换 ——
其实 ㄑ 就在 v 上,vuj 直接得到 ㄑㄩˋ → 去。

二、每个前端怎么选

前端 怎么选 改了何时生效
Windows(可携 + 文字服务) pingzhu.ini 的 layout = hsu 重新执行/切换输入法
Linux(fcitx5) ~/.local/share/fcitx5/pingzhu/layout(一个名字) 重新启动 fcitx5
macOS 输入法菜单的「键盘布局」子菜单,七项打勾 立刻(重建引擎)
Android 设置页「键盘布局」下拉 重新启用输入法
HarmonyOS 长按「繁/简」键 立刻(重建引擎)
网页试打版 键盘上方的选单 立刻

布局在建立引擎时定下(解码器围绕它构建),所以没有「切换」这回事,只有「用哪个名字建」。
Windows 会校验名字:清单外的值被忽略并留在 standard,而不是传给引擎让它静默退回 ——
否则拼错一个字母的结果只是「布局没生效」,没有任何线索。

三、顺带修掉的:键面是「另一份」布局

Android 与 HarmonyOS 画键面的地方各自写死了布局(JNI 里 Layout::Standard、ArkTS 里
keyboardRows('standard'))。于是选了许氏之后,引擎按许氏解码、画出来的还是大千 ——
每一个键显示的都不是它实际的意思。

这是同一个形状的错误第三次出现(第一次是网页试打版那张手写的标准表)。现在布局名从设定
一路传到画键面的地方,键面与解码器用同一份表。

四、其他

  • HarmonyOS 打不出任何拉丁字母 —— 网址、邮箱、密码在真机上无处可打。现在功能列上的
    「英/中」切到 QWERTY 键面,英文直接送进编辑器
  • HarmonyOS 组字时显示整句,引擎没把握时变淡、倾斜(bestSentence() 从第一天就在
    napi 表里,而 ArkTS 里没有一行调用它)
  • Linux 的繁简热键 Ctrl+Alt+S,与 Windows 一致
  • 网页试打版记得住教过的字(存在这个浏览器里),并说出「这句是猜的」
  • 两个 Android 诊断修复:一行一直印「0 keys」的报告(它读的字段在视窗建立前必为空),
    以及它数的到底是格子还是画出来的键

五、验证到哪

平台 验证
Windows 本地构建 + 四项测试(含新的 12 项配置检查)
Linux 构建 + engine_smoke/userdict/script 三项
Android 模拟器实测:设置页选许氏 → 键面变成许氏 → 按 nefhwf 得到「你好」
网页试打版 headless 浏览器实测:选单切到许氏 → nefhwf → 你好
macOS 只到 CI 编译 —— 本机没有 Mac,也没有 swiftc
HarmonyOS 只到 CI 编译 —— 本机没有 SDK,也没有真机

最后两行是诚实的边界:它们能编译、类型正确,但没有在真机上打字验证过。

六、下载

  • Windows:pingzhu-0.10.6-setup.exe 或 pingzhu-0.10.6-win-x64.zip
  • Android:pingzhu-0.10.6-android.apk(versionCode 8,可直接覆盖安装 0.10.5)
  • Linux:fcitx5-pingzhu_0.10.6_amd64.deb 或 fcitx5-pingzhu-0.10.6-1.x86_64.rpm
  • HarmonyOS:pingzhu-0.10.6-harmonyos-project.zip(在 DevEco 打开、签名、Run)
  • 引擎:pingzhu-engine-0.10.6.tgz

macOS 这一版仍然没有产物:输入法要向系统登记,签章必须是 Apple 签发的 Developer ID,
而入会仍在等。

Windows 升级注意:这一版 TSF DLL 换了新档名(pingzhu-tsf-0.10.6.dll)。Windows 会
锁住已载入的 DLL 整个行程生命周期,换档名等于换一个新档案 —— 这是升级能成功覆盖的原因。

v0.10.5 — 选的字要能上屏,教过的字要记得住

Choose a tag to compare

@173787247 173787247 released this 19 Oct 17:34

v0.10.5 — 选的字要能上屏,教过的字要记得住

一句话

两个修复,都是「功能在,但结果是空的」:Android 与 macOS 上选字之后一个字也没送进应用,
Linux 与 HarmonyOS 上使用者教过的字重启就忘。两个都没有报错,也没有任何画面提示。

一、选字之后什么都没上屏

engine.select(n) 选字并在同一次调用里提交,返回的就是选中的文字。而三个地方
又去调了一次 commit() —— 那一次拿到的已经是空字串:

if (engine.select(oneBased).isNotEmpty()) commit()   // ← commit() 里 engine.commit() 已空

于是选中的字没有被送进应用,组字却被 reset() 清掉了。用户按了候选,屏幕上什么都没有,
只能以为自己没点准。

平台 选字路径 修前
Windows(可携版与文字服务) 读 select 的返回值 ✓ 一直是对的
Linux 读返回值 ✓
HarmonyOS 读返回值 ✓
网页试打版 读返回值 ✓
Android(触控选字、数字键选字) 又调 commit() ✗ 本版修复
macOS(数字键选字) 又调 commit() ✗ 本版修复

二、教过的字重启就没了

引擎从使用者选的字里学习,那是这个输入法唯一的训练信号。Windows、macOS、Android 都在
启动时载入、学习后保存;Linux 的 fcitx5 附加组件与 HarmonyOS 键盘一处都没有 ——
学到的词只活在内存里,重启即失。

本版让它们和另外三个外壳一样:

  • Linux:~/.local/share/fcitx5/pingzhu/userdict.tsv(尊重 $XDG_DATA_HOME),
    建好引擎后载入,选字与送出之后写回;写不进去会记一条 FCITX_WARN,不再安静地丢掉
  • HarmonyOS:${filesDir}/userdict.tsv(放在 model 目录外——那个目录每次启动都会被
    重写),启动时载入,选字与送出之后保存

验证

部分 怎么验的
Android 选字(触控与数字键) 模拟器上装 release APK 实测:选中的字进应用、候选列收起
Linux 词典往返 新增 tests/userdict.c:教一次 → 存 → 建第二个引擎 → 载入 → 那个词还在 ✓
Linux 引擎与路由 engine_smoke ✓、共享向量 27 条 0 失败 ✓
HarmonyOS 词典 只到 CI 编译验证 ✗(没有装置)
macOS 选字 只到 CI 编译验证 ✗(没有 Mac)

给下一个版本留的基线

文字服务的 DLL 档名本版不动(仍是 pingzhu-tsf-0.10.4.dll):档名的规则是
「windows/ 自上一个 tag 起有改动才改」,而 v0.10.4..v0.10.5 之间 windows/ 零改动 ——
这条规则第一次在发版里实际被用上,写起来只是 git log <tag>..HEAD -- windows/ 一行。

v0.10.4 — 每个外壳都修好一个「够不到」的功能

Choose a tag to compare

@173787247 173787247 released this 16 Oct 16:28

v0.10.4 — 每个外壳都修好一个「够不到」的功能

一句话

五个平台里有四个,各有一个功能存在但到不了:Android 的候选列从没显示过、
HarmonyOS 的 ⌫ 会把整个组字丢掉、Linux 翻不到第二页候选、Windows 的候选视窗
切掉了最下面一行。这一版把它们都修好,并给 windows/ 与 android/ 补上了 CI ——
在那之前,这两个外壳一次都没有在 CI 里建过。

四个「够不到」

Android:候选列从来没有显示过,候选单也打不开

候选列(CandidateBar)建好了、点选回调也接了,但全仓没有一处调用
setCandidatesViewShown(true)
,而 Android 的 candidates view 预设是隐藏的;就算叫了
也没用 —— 视窗被钉成整屏,框架那块容器拿不到位置(实测 isShown=false、
vis=VISIBLE、1080x551:量得好好的,就是不上屏)。

修法是让候选列成为输入视图自己的一部分(候选列在上、键盘在下、整体贴底),
不经过框架那块容器。同时 hasCandidates 原本算成 windowOpen && …,而路由开单的规则
正是 !windowOpen && hasCandidates —— 那一格永远走不到,空白每次都把句子送出。

底排另加了 ▼ 浏览键:手机键盘没有 ↓,所以翻页本来无处可去。

HarmonyOS:⌫ 丢掉整个组字,又删掉编辑区一个字

一次按键做两件错事:pingzhu.reset()(丢掉整份组字)+ Session.deleteBackward()
(删掉已经送进编辑器的一个字)。根因是 napi 从来没导出 backspace() ——
当初只能拿 reset 顶上。

Linux:翻不到第二页,而且高亮是装饰性的

空白键先开单再看状态,于是「已经开着」和「刚开好」长得一模一样,第二次按只刷新、
既不送出也不翻页;Engine::nextPage() 没有任何呼叫点;↑↓ 移动的那个高亮
没有任何键会采用 —— 把第三个候选照亮,按空白送出的还是第一个。

按键规则搬进了 src/router.cpp(不含任何 fcitx5 标头),从此和 Windows/macOS/Android
一起被 tools/routing-vectors.tsv 钉住。候选列每页也从 9 改成 10 —— 原本每页第十个
候选被挤到 fcitx5 自己开的第二页上,而选它的数字键(0)不在画面上。

Windows:候选视窗矮一行

视窗画四行(组字、候选、→ 预览、页码),高度却只算到第三行 —— 多页时候选单最下面
那条状态列被切掉下半截。行距现在只有一份(candidate_window.h 的 layout::),
高度由「最后一行画在哪」推出来。

也给这个仓库自己补了两处

  • windows/ 与 android/ 的 CI:四个旧 workflow 都按目录过滤,这两个外壳的改动
    因此不触发任何东西 —— 它们的 bug 只在开发者自己的机器上被发现。现在 Windows 会建
    外壳与文字服务并跑四组测试,Android 会交叉编译两个 ABI、跑 JVM 测试并产出 APK。
  • 发版检查单进仓库(docs/RELEASING.md):流程原本只写在本机的交接档里,
    于是「TSF DLL 档名什么时候该改」这条规则漏过一次。

升级注意

  • Windows 的文字服务 DLL 换了档名:pingzhu-tsf-0.10.2.dll → pingzhu-tsf-0.10.4.dll。
    这是故意的:Windows 会锁住已载入的 DLL,换名才升得动。安装包会自动登记新的那个。
  • Linux addon 的版本号:fcitx5-diagnose 以前一直显示 0.9.0 —— linux/fcitx5/pingzhu.conf
    的 Version= 从 0.9.0 起就没动过(改 CMakeLists.txt 的 project(... VERSION) 没有用,
    那是死值)。现在跟着发版号走。

验证

平台 怎么验的
Windows 候选视窗几何由 pingzhu-candidate-test.exe 真开窗、读回矩形核对;路由对共享向量 27 条
Android 模拟器实测:候选列显示 1 你 2 妳 3 擬…、空白开单、2 选字送出、▼ 翻页;JVM 18 项
HarmonyOS 引擎语义由 TS 参考实现生成的 225 条场景钉住;外壳只到编译验证(本机没有装置)
Linux ctest 两项:引擎冒烟与路由对共享向量;编译零警告
macOS 未变;CI 仍在真的 macOS runner 上建置与安装验证

没验的:Android 的实机行为(只在模拟器上)、HarmonyOS 的真机按键行为 ——
这两项在各自的 README 里都写着。

v0.10.3 — Linux 有安装包了:deb 与 RPM

Choose a tag to compare

@173787247 173787247 released this 07 Oct 17:10

v0.10.3 — Linux 有安装包了:deb 与 RPM

一句话

Linux 的 fcitx5 附加组件以前只能自己编译;现在仓库里就带着打包定义,
dpkg-buildpackage 与 rpmbuild 各产出一种包,可以直接装。

问题

引擎和五个外壳一直在,Linux 的 addon 也一直能构建 —— 但没有任何打包定义:

cmake -DCMAKE_INSTALL_PREFIX=/usr   → 装得到这台机器上   ✓
把 addon 交给别人的机器             → 没有东西可以给     ✗

UOS、Kylin、deepin、openEuler 的使用者拿到仓库也只能自己编译,而这条路在国产
发行版上并不总是通的:Rust 版本、fcitx5 开发包、cmake 宏,各家都不一样。

三种装法

方式 给谁用 怎么做
.deb 统信 UOS / 麒麟 Kylin / deepin / Ubuntu sudo dpkg -i fcitx5-pingzhu_0.10.3_amd64.deb
.rpm openEuler / 麒麟部分版本 / Fedora sudo rpm -ivh fcitx5-pingzhu-0.10.3-1.x86_64.rpm
从原始码 想自己控制前缀的人 cmake -S linux/fcitx5 -B build -DCMAKE_INSTALL_PREFIX=/usr(原有方式,未变)

加了什么

档案 指令 产出
debian/(仓库根目录) dpkg-buildpackage -b -us -uc fcitx5-pingzhu_0.10.3_amd64.deb
fcitx5-pingzhu.spec rpmbuild -bb fcitx5-pingzhu.spec fcitx5-pingzhu-0.10.3-1.x86_64.rpm

两种包装的是同一组东西:

/usr/lib/*/fcitx5/libpingzhu.so             RPM 侧是 /usr/lib64/fcitx5/
/usr/share/fcitx5/addon/pingzhu.conf        附加组件定义
/usr/share/fcitx5/inputmethod/pingzhu.conf  输入法定义
/usr/share/pingzhu/*.tsv                    引擎资料

打包时暴露出来的三件事

① 宣告的 Rust 下限根本达不到。 core-rs/Cargo.toml 写着
rust-version = "1.75",但 crate 里用了 C 字串字面值(c"..."),那需要 1.77。
用 1.75 构建会停在 E0658 —— 也就是说宣告的下限是一个构建不起来的版本。
两个包的依赖栏位也跟着写成 1.77。

② RPM 的并行参数展开成 -j-j8。 %{_smp_mflags} 自带 -j,再补一个就成了
make -j-j8,make 印出用法就退出。改用 --parallel。

③ 安装路径原本让 CMake 自己挑。 在 Debian 上它挑 lib/x86_64-linux-gnu,
而 RPM 的档案清单要的是 %{_libdir}(openEuler 上是 /usr/lib64)。现在两边都由
各自的宏决定:-DCMAKE_INSTALL_LIBDIR=%{_libdir} 与
-DCMAKE_INSTALL_DATADIR=%{_datadir}。档案清单原先还指着
%{_datadir}/fcitx5/pingzhu,而资料实际装在 %{_datadir}/pingzhu。

安装说明也补上了

linux/README.md 多了一节「安装」:两种包的命令、从原始码构建、以及装完怎么确认
东西真的在 fcitx5 会去找的位置(libpingzhu.so 在不在、ldd 有没有 not found)。

验证

.deb     实际构建出来,解开确认含 addon、输入法定义与引擎资料
.rpm     实际构建完成并确认内容:/usr/lib64/fcitx5/libpingzhu.so、两个 .conf、
         引擎资料与授权档案
TypeScript  68 项 · typecheck
Rust        29 项(26 单测 + 差异 1529 例 + 交互 207 例 + 文件测试)

engine_smoke 那三个词,与其他四个平台一致(下面是它实际印出来的样子):

ok    su3cl3: 你好
ok    ji394su3: 我愛你
ok    w96j0: 台灣

仍有的缺口

RPM 的 BuildRequires 还没在 openEuler 上解析过一次。 本机是 Ubuntu(rpm 4.18),
rpmbuild 不认 apt 的套件名,所以是 --nodeps 加现有的 cmake 宏建起来的。
规格档已经刻意写成直接调用 cmake(不依赖 Fedora 那套 cmake 宏),但
在 openEuler 上跑一次 dnf builddep fcitx5-pingzhu.spec 才算真的验过。

CI 不建这两种包。 .github/workflows/linux.yml 仍然是从原始码构建并安装 addon;
两种包是本地实测出来的,不是每次推送都重新验一遍。


升级方式同前:Windows 用 pingzhu-0.10.3-setup.exe、Android 装
pingzhu-0.10.3-android.apk、HarmonyOS 用对应的 project zip、引擎用 .tgz;
Linux 这次多了 .deb 与 .rpm 两种包。
macOS 仍无产物(签章待办,见 v0.10.0 的说明)。

v0.10.2 — 修掉退格键与下箭头在没打字时被吃掉

Choose a tag to compare

@173787247 173787247 released this 03 Oct 13:46

v0.10.2 — 修掉退格键与下箭头在没打字时被吃掉

一句话

没在打字的时候,退格键现在会正常删字;下箭头也不会被无谓地吃掉了。

问题

macOS 与 Android 这两个外壳在任何时候都接管退格键与下箭头。于是:

没在打字,按退格    → 输入法吃掉了,应用程式收不到,字删不掉
没在打字,按下箭头  → 同上

同类的输入法不会这样:没在组字的时候,这两个键属于使用者正在编辑的东西。

Windows 与 Linux(fcitx5) 本来就是对的,所以这是两个平台上的既有缺陷。

怎么发现的

不是有人报 bug,而是把 Windows 与 Android 接上共享的路由测试向量之后
自己红的。

tools/routing-vectors.tsv 里写着一条规则:

\b  composing=0  →  Pass    (没在组字,退格交给应用程式)
\v  composing=0  →  Pass    (没有东西可以浏览)

这两条从写下来就没被执行过 —— 因为只有 macOS 的测试真的读那份向量。
把另外两边接上去的第一个动作,就把它们变成了红的。

(向量本身也有两条写错了 —— 把「没组字」的退格写成 Backspace、把「组字中
但没有候选」的下箭头写成 OpenCandidates。一并更正。)

修了什么

之前 现在
退格 一律接管 组字中才接管,否则交给应用程式
下箭头 一律接管 有候选可看才接管,否则交给应用程式

四个外壳现在行为一致。

测试保护也补上了

原本「改一条规则,四个外壳一起红」这句话只有 macOS 兑现。现在三边都读
同一份向量:

macos/Tests/Router/main.swift       (本来就读)
windows/tests/test_router.cpp       (本版起)
android/.../RouterTest.kt           (本版起)

验证

Windows   24 条共享向量 · 0 失败 + 原有断言全过
Android   16 项(含新增的闸门断言)
macOS     由 CI 在 macOS runner 上编译
TypeScript 68 项 · Rust 29 项 · 连结检查 657 个 URL 0 损坏

仍有的缺口

仓库没有 Windows CI,所以那一份向量只在本机 windows/build.sh --run
时执行;Android 的 workflow 只建 APK、不跑单元测试。也就是说这两个平台的
向量保护目前靠人记得跑 —— 保护是有的,自动化还没有。


升级方式同前。macOS 仍无产物(签章待办,见 v0.10.0 的说明)。

v0.10.1 — 空白改为先开候选单再接受

Choose a tag to compare

@173787247 173787247 released this 03 Oct 13:34

v0.10.1 — 空白改为「先开候选单、再接受」

一句话

打完字按空白,现在会先列出候选;再按一次空白才送出。想选第二个的人这就能选到了。

为什么改

5j4u4(ㄓㄨˋ-ㄧˋ)有两种正确的读法:

ㄓㄨˋ-ㄧˋ  → 注意   ← 原本按空白直接送出的
ㄓㄨ-ㄧˋ    → 助益   ← 在同一张候选单上排第 2

两个都对,只是切分不同。但原本按空白只会得到「注意」 —— 想选「助益」的人
必须事先知道要按 ↓ 才看得到那张单子。而这正是空白要解决的问题:它是大家
拿来「接受」的键,所以它必须能以接受收尾。

翻页因此移到 ↓(它本来就是「开单/翻页」一键)。原本「开着就翻页」的规则有
两个毛病:第二个候选到不了,以及最常按的键去卷一张没人要求卷的清单。

空白的三态

状态 按空白
组字中、没有候选 送出(打了一半,没有东西可挑)
组字中、有候选、候单未开 开单,不送出
候单开着 送出目前那一个
没在组字 一般空白

操作对照

5j4u4        只打字            首选 注意
5j4u4 ␣      开单              注意 助益 註譯 …
5j4u4 ␣ 2    选第 2 个          助益
5j4u4 ␣ 1    选第 1 个          注意
5j4u4 ␣ ␣    送出首选           注意
5j4u4 ␣ ↓ …  翻页(↓ 可重复按)
su3cl3 ␣ ␣   你好
2l3 ␣ ␣      導

★ 升级后你会立刻感觉到的一件事

最高频的动作——打完就送出——从一次空白变成两次。

这是刻意的取舍,不是疏漏:要让「第二个候选到得了」与「空白以接受收尾」同时
成立,第一次按下就不能送出。如果你更想要「一次空白直接送出」,那第二个候选
就必须继续靠 ↓,也就是回到 v0.10.0 的行为。

另外修掉一个既有缺陷

Windows 的 ↓ 从不翻页 —— 候选单开着时它仍回传「开单」,而 macOS、Android
与文件都写着「↓ 一键开单与翻页」。现在四个外壳一致。

四个外壳都改了

Windows  windows/src/router.cpp          Android  .../ime/Router.kt
macOS    macos/Sources/Router.swift      Linux    linux/fcitx5/...engine.cpp

引擎侧(Rust 核心与 TypeScript 规格)同步更新,交互测试重录为 207 行,
新增 ↓、↓ ↓、↓ 选第 1 个、空白 ↓ 覆盖翻页。

验证

TypeScript   68 项 + typecheck + 打包
Rust         29 项(26 单测 + 差异 1529 例 + 交互 207 例 + 冒烟)
Windows      路由测试全过
Android      14 项
macOS        由 CI 在 macOS runner 上编译

已知的覆盖缺口(记录在案)

tools/routing-vectors.tsv 的用意是「改这里,四边一起红」,但目前只有 macOS
的测试真的读它
。Windows 与 Android 是各自写死的断言,所以这次改规则时它们
没有自己红,两边的断言是手动跟着改的。修法是让那两个测试也读同一份向量 ——
尚未做。


升级方式同前:Windows 用 pingzhu-0.10.1-setup.exe、Android 装
pingzhu-0.10.1-android.apk、HarmonyOS 用对应 project zip、引擎用 .tgz。
macOS 仍无产物(签章待办,见 v0.10.0 的说明)。

v0.10.0 — 尚未成音节的按键显示为注音

Choose a tag to compare

@173787247 173787247 released this 02 Oct 04:58

v0.10.0 — 尚未成音节的按键显示为注音

一句话

打 gj2 时组字行从 ㄕㄨ [2] 变成 ㄕㄨ [ㄉ] —— 括号里显示注音,而不是你按下的那个键。

为什么改

括号里原本原样回显按键。于是 gj2 显示 ㄕㄨ [2]:一个使用者按下的数字
坐在音节旁边,对没读过解码器的人没有任何意义。su3c 更糟,泄漏的是内部
诊断符号 [c]。

现在渲染注音:ㄕㄨ [ㄉ]、ㄋㄧˇ [ㄏ]。数字键 4 显示为 ˋ(大千式上 4 就是
四声)。该布局上没有注音的按键仍然显示自己,所以不会有东西从画面上消失。

这不只是美观问题:ㄕㄨ [ㄉ] 让「数字键到底在选字还是在组字」变得可见,
使用者不必猜。

受影响的地方

平台 是否受影响
Windows(可携版/TSF) ✅ 经由共用 Rust 核心
Android ✅ 同上
HarmonyOS ✅ 同上
引擎包(npm/.tgz) ✅
macOS ⚠️ 本版未附产物,见下

验证

TypeScript   68 项全过(新增 2 项钉住基准工具的抽样性质)
Rust         29 项全过(26 单测 + 差异测试 1529 例 + 交互测试 171 例 + 冒烟)

差异测试钉住「Rust 核心」与「TypeScript 规格」行为一致 —— 本次两边同时
改,1529 例逐例比对;交互测试新增一个形状:词由第二个音节造出。

ru04      → ㄐㄧㄢˋ        首选「建」(未选字)
ru04gk4   → ㄐㄧㄢˋ ㄕㄜˋ   首选「建設」
空白键     → 送出「建設」

它防的不是选错字,而是过早定案:若第一个音节完成时就被固定,第二个音节
再也无法回头改它,那么不论候选表多好,建設 都打不出来。

★ 关于 macOS

本版不附 macOS 产物。 原因不是这次没改 macOS,而是 macOS 那条线卡在一个
外部依赖上:输入法要出现在系统清单里,签章必须是 Apple 签发的 Developer ID,
而申请该凭证需要付费入会 + 扫脸验证。这一点已由六个变量逐个实测确认:

组合 结果
ad-hoc,不呼叫注册 API ✗ 不出现
自签 + 系统信任,不呼叫 API ✗ 不出现
自签,呼叫 API ✗ noErr 但看不到
ad-hoc,呼叫 API(从 app 内部) ✗ noErr,系统列出 0 个
Apple 签发 + 公证 + 呼叫 API ← 待办,需先取得凭证

macOS 使用者请继续用 v0.9.1,或在入会完成后自行从原始码构建 + 签章。
相关步骤见 macos/README.md。

其他修正

  • macOS install.sh 在 spctl 那一步就退出 —— 它从来没真正装过东西。
    同一个 bug 让每次执行都下载 0.9.1、印出 REJECTED 然后结束,把 0.9.0
    留在原地。已修,并把结论指向 notarize.sh。
  • macOS 安装文档修正:原本教人走「免费 Apple ID 就能做」那条路,
    而该路径已被实测推翻(免费帐号在 portal 看不到 Certificates;Xcode Personal
    Team 发的 Apple Development 凭证 spctl -t exec 也不接受)。三份文件同步更正。
  • 补齐许可全文:LICENSES/Apache-2.0.txt(data/ts-conversion.tsv 衍生自
    OpenCC,Apache-2.0 第 4(a) 条要求散布时附上副本,原先只给了连结)、
    LICENSES/MIT.txt。并更正 McBopomofo 的版权行为其 LICENSE.txt 原文。
  • 授权记载修正:libchewing-data 是逐目录多授权(dict/chewing/ 为
    LGPL-2.1-or-later、dict/moe/ 为 CC BY-ND 3.0 TW、只有 dict/chewing_v4/
    是 CC BY 4.0);g0v/moedict-data 是分层授权而非「无授权」。

升级方式

平台 方式
Windows 下载 pingzhu-0.10.0-setup.exe,或解压缩 pingzhu-0.10.0-win-x64.zip
Android 安装 pingzhu-0.10.0-android.apk
HarmonyOS 下载 pingzhu-0.10.0-harmonyos-project.zip,在 DevEco 打开、签名、Run
引擎 pingzhu-engine-0.10.0.tgz

已知限制:macOS 产物待签章(见上);符号表、联想词、快捷输入尚未实作;
许氏/倚天 26 键盘尚未实作(许氏需要新增有状态的按键层,架构上是一次改动
而非加一张表)。

v0.9.1 — macOS 加上 TIS 注册

Choose a tag to compare

@173787247 173787247 released this 28 Sep 11:55

macOS 端加上了一直缺的那一半:app 启动时呼叫 TISRegisterInputSource
和 TISEnableInputSource。

查 vChewing 的原始码查出来的——注册不是自动的,每个能用的输入法
都自己呼叫它(或靠 installer 呼叫)。

这个版本还没有 Developer ID 签章。测试目的是看「呼叫注册 API」
能不能让系统列出它,也就是把签章这个变数和注册这个变数分开。

v0.8.0 — Android 版

Choose a tag to compare

@173787247 173787247 released this 27 Sep 04:35

下载

平台 档案
Windows 10/11 pingzhu-0.7.6-setup.exe · 可携版 ZIP
Android 8+ pingzhu-0.8.0-android.apk
macOS 12+ ⚠️ pingzhu-0.9.0-macos-UNVERIFIED.zip

(Windows 那两个是 0.7.6 的建置,这次没有改动——版本号没有跟著专案走,
因为那是同一份已经验证过的档案,重新编号只会让人以为它变了。)

Android:真的能用了

su3cl3  →  ㄋㄧˇ ㄏㄠˇ  →  你好  →  空白送出

零权限——没有网路、没有储存空间、没有通讯录。语言模型在 APK 里,
使用者词库在应用自己的目录里。

键 行为
空白 有选字单 → 下一页;组字中 → 送出整句;否则 → 空白
数字 选字单开著 → 选字;否则 → 照打(1 是 ㄅ、2 是 ㄉ)
繁/简 切换输出字形(显示的是当前字形)

键盘是自绘的,键面来自引擎——由解码用的同一张表推导。手写的键盘图是会一个键
一个键走样的东西,而且只有「已经把大千式背下来的人」才会发现,而那正是不需要
看图的人
。两个 Rust 测试把它钉住。

按键路由是可测的纯逻辑:12 项测试在 JVM 上跑,不需要模拟器、APK 或装置。

这一版背后:一个追了很久的 bug

键盘建置、安装、可选取;引擎载入;JNI 回传正确的键面;View 量测到 1080x514;
系统说视窗 isVisible=true mHasSurface=true mDrawState=HAS_DRAWN。

但画面上什么都没有,而且 onDraw 从来没被呼叫过。

一路排除掉的错误方向:

  • 自绘 View 有问题 → 换成普通 TextView 加绿色背景 → 仍然 0 个绿色像素
  • 宿主是自己的 Activity → 换成系统设定 → 一样
  • 模拟器不合成输入法视窗 → Gboard 在同一个模拟器上正常显示

真正的原因在把两个视窗倾印并排看之后才出现,差别只有一行:

ours:   mAttrs={(0,0)(fillxwrap)     ← 包住內容
Gboard: mAttrs={(0,0)(fillxfill)     ← 填滿

API 34 的输入法视窗是填满萤幕、键盘画在底部的。我们的视窗「包住内容」,
而内容在引擎还没产生任何按键之前就被量测,于是包成 0——
之后每一次量测的上限都是 AT_MOST 0,稍后到达的键盘就再也没有地方可以画。

那个限制正是正在被计算的东西,所以它不能同时是答案。

两处修正:给输入法一个 windowIsFloating=false 的主题(在程式码里呼叫
setLayout 会被框架覆盖,主题才是对的地方),以及让根容器在遇到 0 上限时
退回萤幕高度。

顺带修掉三个「安静地失败」

  1. worker.execute { loadEngine() } 里的例外会被 Executor 吞掉——加上 try/catch 与记录
  2. Rust 的 guarded() 把 panic 吞成空字串,和合法的空结果无法区分——现在回传 PANIC IN NATIVE CODE: ...
  3. --indicator 模式的托盘气泡在讲一颗不存在的热键

同一个形状:失败是安静的。 这是这个专案一路在修的东西。

也发布了 npm 套件

pingzhu-engine 现在可以 npm install:零执行期依赖,浏览器安全的入口
(pingzhu-engine)与 Node 入口(pingzhu-engine/node)分开,语言模型随套件附上。


macOS:先放上来,但名字里就写了未验证

pingzhu-0.9.0-macos-UNVERIFIED.zip
                        ^^^^^^^^^^

为什么叫 UNVERIFIED:它在真的 macOS 上编译、打包、签名,
引擎也在 CI 上真的解码过(su3cl3 → 你好 ✓),但是——

系统从来没有接受过它。 在 CI runner 上试了四件事都失败:

  1. 等 20 秒 ✗
  2. lsregister 向 LaunchServices 注册 ✗(安装程式就是这样做的)
  3. 重启输入源代理 ✗
  4. open 启动它一次 ✗

而原因不明 ✗。我先前在文件里写「macOS 只在登入时扫描那个目录」——
那是我把猜测写成了原因,而探测结果否定了它(runner 报告的
launchctl managername 是 Aqua,本来就是已登入的 GUI 工作阶段)。

所以诚实的说法是观察到的事实:同一个 session 里新装的输入法
不会变成可见的输入来源。

要做的事(十分钟,需要一台真的 Mac):

cp -R PingZhu.app ~/Library/Input\ Methods/
# 登出,再登入
# 系統設定 → 鍵盤 → 輸入方式 → 加入「平注」
# 打 su3cl3 按空白

清单见 macos/TESTING.md。

通过了我就把它改名去掉 UNVERIFIED;没通过就照日志修。

v0.7.6 — 记录档写出 DLL 版本

Choose a tag to compare

@173787247 173787247 released this 26 Sep 16:22

下载

档案
pingzhu-0.7.6-setup.exe
pingzhu-0.7.6-win-x64.zip

记录档现在会写出 DLL 档名

--- activated in chrome.exe [pingzhu-tsf-0.7.6.dll] ---

这是唯一能可靠判断「那个行程跑的是哪一版」的方法。

Windows 会把已载入的 DLL 留到行程结束,COM 又不会重读注册表——一个开了很久的程式
跑的是它启动时注册的那一版,而画面上没有任何东西说明这件事。

这个缺口今天骗了两个人

使用者回报「点了按钮但输出没变」。查记录档:主机名、资料目录、引擎就绪全都正常,
只是没有「settings file changed」那一行。

真相是那个行程载入的是 pingzhu-tsf-0.5.1.dll——两小时前的版本,
根本没有即时同步的程式码。

而之所以会测到那么旧的版本:Windows 11 的记事本是单一行程加分页,
所以「开启记事本」只是在那个旧行程里开一个新分页,它从来没有重新载入过。

记录档从头到尾都在说 activated in Notepad.exe——
说了主机,没说版本,而版本才是当时唯一重要的资讯。

这属于整个专案一直在遇到的那一类问题

先前的六次失败,形状都是「使用者说不能用,而程式码看起来完全正确」。
这次多了一个变体:程式码是对的,跑的东西是旧的,而没有任何东西指出这件事。

升级机制(DLL 档名带版本)解决了「新版本能不能载入」,
但没解决「怎么知道现在跑的是哪一版」。这是那个机制的缺口。

使用建议

要拿到新版本,必须让那个程式重新启动。 具体来说:

程式 要怎么做
Chrome、Edge、VS Code、Cursor 完全关闭后重开
Windows 11 记事本 关掉所有记事本视窗(它是单一行程,开新分页不算)
档案总管 工作管理员 → 重新启动 explorer.exe

或者登出再登入,一次解决全部。