中文组句中小键盘数字以前只能选词,打编号、型号时得切英文;加 [general] numpad_digit,缺省 select 照旧选词,direct 让数字进组句一起上屏。 Closes #217
青简 Windows 输入法
Windows 端是一个产品、两个产物,各自一个 package,同放本目录:
| 目录 | package | 产物 | 职责 |
|---|---|---|---|
server/ |
qingjian-windows-server |
qingjian-server.exe |
持有唯一的输入内核 qingjian-core::Engine,跑在所有应用进程之外 |
tsf/ |
qingjian-windows-tsf |
qingjian_tsf.dll |
TSF 文本服务,被加载进每个应用进程,只做按键转发与文档写入(候选窗口由 Server 自绘) |
应用进程 A ── qingjian_tsf.dll ─┐
应用进程 B ── qingjian_tsf.dll ─┼─ 命名管道 \\.\pipe\qingjian ─▶ qingjian-server(唯一的 Engine)
应用进程 C ── qingjian_tsf.dll ─┘
为什么核心逻辑要在进程外
Windows 的文本服务(TSF,Text Services Framework)是一个 COM DLL(ITfTextInputProcessor),
系统会把它加载进每一个接受文本输入的应用进程。因此输入内核不能待在 DLL 里(会被复制进几十个进程、
状态无法共享、崩溃会连累宿主应用)。青简照 Weasel(WeaselServer + WeaselTSF)的做法:
Engine 只此一份,跑在独立的 Server 进程;每个应用进程里的 TSF DLL 只做两件事——把系统按键翻成协议消息发来、
把 Server 回的候选画到候选窗口。
为什么是两个 package 而不是一个
两个产物的依赖集合刻意不同:DLL 只依赖 qingjian-core、qingjian-platform 与官方 windows crate(COM
implement 宏),Server 才依赖词库 / 学习 / 翻译 / 云联想(含 tokio)整棵树。合成一个 package 后,DLL 的
编译单元会拉进 Server 的依赖;用 feature 区分也不行,workspace 一起构建时 feature 会统一。crate 边界就是
「DLL 不含 Engine」这条约束的强制手段。判断标准与 macOS 的 IMK 壳一致:换掉平台适配层,不应该需要改
Core 的任何一行。
三个部分
- Server 进程(
server/):装配并持有 Engine(词库 / 语言模型 / 翻译 / 学习),按SessionId为每个 应用会话维护各自的组句状态,处理按键、产出候选与上屏文本,把云联想 / 本地整句模型的异步结果主动推给 对应会话。 - IPC 协议(
qingjian-platform::protocol,两端共用):ClientMessage(DLL → Server:开 / 关会话(带宿主 exe 名,[apps]按应用设置据此查)、按键、上屏、回上下文)与ServerMessage(Server → DLL:按键结果、上屏结果、异步重绘、请求上下文),一次要绘制的状态是Frame(preedit 分段 + 候选页)。长度前缀 JSON 帧的编解码与缺省管道名也在这里,DLL 不必依赖整个 Server 库。 - TSF DLL(
tsf/):分「引擎层」client(平台无关的管道客户端EngineClient,泛型在任意Read + Write上,本机就能接真 Server 端到端测)与「COM 层」com(cfg(windows):DllGetClassObject→IClassFactory→#[implement(ITfTextInputProcessor, ITfKeyEventSink)],编辑会话上屏,把组句位置报给 Server 摆候选窗口,DllRegisterServer注册文本服务)。
设计细节见 docs/design/architecture.md「Windows:TSF」。
构建
本机(macOS / Linux)只做交叉 check,产出不了可用二进制,但协议层的端到端测试能跑:
cargo check --target x86_64-pc-windows-gnu -p qingjian-windows-server -p qingjian-windows-tsf
cargo test -p qingjian-windows-server -p qingjian-windows-tsf
真正编译与试用都在 Windows 机器上(MSVC 工具链)。
本地联调:scripts/test-local.ps1
改了 Server 或 DLL 之后要试手,用这个脚本,别手敲:
pwsh -File apps\windows\scripts\test-local.ps1 # 编译 + 换 Server + 起,DLL 那步要管理员
pwsh -File apps\windows\scripts\test-local.ps1 -SkipBuild -SkipRegister
它做四件事:编 Server 与 TSF DLL(带 QINGJIAN_UIACCESS=0,没签名的 exe 带 uiAccess 起不来)、
停掉在跑的 Server、把已安装目录整份拷到 ~\qingjian-devtest 再换上新的 Server 与随包数据、
起 Server 并打印剩下要手动做的事(注册 DLL、设方案、敲哪几组键)。
脚本存成带 BOM 的 UTF-8(
sign-local.ps1也是)。Windows PowerShell 5.1 对没有 BOM 的.ps1按系统 ANSI 码页解析,中文会变成乱码、引号配对跟着崩,报出来的却是「字符串缺少终止符」这种语法错。 编辑时别把 BOM 去掉。PS7 两种都读得对,所以只用pwsh验会漏掉这个问题。
为什么不在仓库里直接跑:bundled_root() 按 exe 位置找数据,target\debug\ 下会落到仓库根,
而 data\generated\ 是 gitignore 的、本机多半没有,于是退回 assets\sample\ 样例——形码候选照样出得来
(码表独立),但译文几乎全空,会让人误以为释义那条设计没生效。
为什么两边要一起换:qingjian_core::Candidate 是线上格式的一部分(见 protocol/mod.rs)。
Core 加一个 CandidateKind 变体,老 DLL 就解不出整条帧、把按键原样放行——表现是「输入法突然只出英文」,
日志里只有一句 unknown variant。所以 Server 与 DLL 必须同一份源码编出来的;协议版本号对不上时
Server 会记警告,但它只警告、不拒绝,别指望它兜住。
手工步骤(脚本里也在做,供对照)
:: 1) 编译出 DLL 与 Server
cargo build -p qingjian-windows-tsf -p qingjian-windows-server
:: 2) 注册文本服务(改 HKEY_CLASSES_ROOT,图标写到 %ProgramData%\Qingjian\qingjian.ico,要管理员)
regsvr32 target\debug\qingjian_tsf.dll
:: 3) 起 Server(引擎在这里;没起时 DLL 吃掉字母键但没有候选,起来后下一键 / 下次聚焦自动重连)
cargo run -p qingjian-windows-server
:: 4) 在系统「语言 / 输入法」里应能看到「青简」,切到它,在任意输入框敲字
:: 日志都在 %LOCALAPPDATA%\Qingjian\logs\:server.<日期>.log / tsf.<日期>.log / settings.<日期>.log(按天,留 7 天)
:: 反注册
regsvr32 /u target\debug\qingjian_tsf.dll
旧版设置程序与 Windows App Runtime
设置程序是三平台共用的 apps/settings(qingjian-settings.exe),状态条的齿轮打开它。
settings/(qingjian-settings-legacy.exe,开始菜单「青简旧版设置」)是旧版,留一个版本作回退;它用 Windows Reactor(WinUI 3)画界面,是唯一依赖 Windows App Runtime 的产物。
它的部署方式是自包含:build.rs 让 windows-reactor-setup 把 Microsoft.WindowsAppSDK.Runtime 的 MSIX 解到
target\release\ 并按自包含标记嵌清单,安装包把这些文件装到 exe 同级——不依赖机器上装没装框架包。
Windows 10 上框架依赖的引导走不通(它要先调 Windows 11 才有的 TryCreatePackageDependency),
同一个 build.rs 还把这两个 API 改成延迟加载:否则它们会进 exe 的导入表,Windows 10 在加载期就起不来。
定位过程、上游 issue / PR 与取舍见 docs/notes/windows-win10.md;打包侧见 apps/windows/installer/README.md。
版本与发布
各平台壳版本号独立(见 docs/notes/release.md):两个 package 的 version 各自写在自己的 Cargo.toml,
不跟 workspace 走;发布标签用 windows-v<版本>。