- 整句候选翻译译的是猜出来的候选,猜错就白发;改成用户在面板的框里自己打一句、停键后再译,整句只发确认过的内容 - 翻译组排在最前,每种目标语言算一个分类([ ] 换),打拼音时按 ↑ 直接落在这组;没在打字时 [shortcut] translate_panel(Ctrl+Alt+Y / ⌃⌥Y)打开 - Core panel/translate:原文带光标,停键 800 ms、两个字以上才发,内容没变不重发,没翻出来 2 秒后自动重试一次、再失败等回车;译文按原文 + 语言缓存在内存(换提供方清空),不落盘 - 译文放进应用的 preedit:Windows 译文靠轮询送到,DLL 记着写进文档的组句文字,变了就补一次编辑会话;行内不显示拼音的设置下也照样放 - 框的排版抽成 bamboo-slip 的 TextFlow,与设置程序的 TextField 共用;面板不是 Ui 搭的,布局后按各字位置给出 PanelHit::Caret - [predict] sentence_translation 不再认(旧配置里写着不报错),两个设置程序的开关与文档一并去掉;生词本整句翻译保留 - Windows dispatch/composed/mod.rs 超了 800 行,按联想、导航、出帧拆开 - 初音主题当前分组胶囊改纯色
青简 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<版本>。