* fix(installer): TSF DLL 改装到系统目录,CS2 等 Trusted Mode 游戏里可以切出输入法 CS2 的 Trusted Mode 只放行位于系统目录且有代码签名的第三方 in-proc DLL, 装在 Program Files 下的 DLL 会被拒绝加载,游戏里切不出输入法。 x64 DLL 改装到 System32\IME\MetasequoiaIME\msime_v*,x86 装到 SysWOW64 下同名目录, 语言配置图标随之放到两处根目录;升级时清掉旧版留在 Program Files 的版本目录。 * fix(windows): CS2、Dota 2 不理会 UI-less 声明,照常显示候选窗 起源 2 引擎的 Valve 游戏以 UI-less 激活 TSF,但只替白名单里的输入法绘制候选。 我们据声明收起了自己的候选窗,游戏又不画,结果只剩左上角的系统默认 IME 小框,只能盲打。 对 cs2.exe、dota2.exe 忽略 UI-less,照常由 Server 绘制候选窗。 * fix(server): 前台为 D3D 独占全屏时不弹候选窗与光标状态提示 独占全屏下别的进程的窗口画不上去,弹出去反而可能把游戏踢出独占模式(闪黑、切分辨率、卡死)。 在 UI 线程真正显示前用 SHQueryUserNotificationState 判一次(300ms 缓存),独占全屏就收起候选窗, 并清掉已渲染可见标记,数字键与空格选词不再等待不会到来的绘制。 * fix(engine,server,windows): 游戏自绘候选时后台改动的候选列表实时同步给宿主 英雄联盟等 UI-less 宿主自己画候选。云候选、AI 候选、模型重排、英文混排在按键回复之后才改 候选列表,却从不通知 DLL,游戏还画着上一键读到的旧列表,数字键和空格却按新列表上屏, 于是「选的和上屏的不一样」。 - 契约新增 Worker opcode 35 UiLessCandidatesChanged,载荷与 UiLessComposition 同形, 由 uiless_page.h 统一格式化与解析,并附候选页代号;旧 DLL 读选中位时在第三个制表符处停下,兼容。 - Server 在上述后台改动之后把新页推给候选归属的客户端。 - DLL 收到后回到 TSF 线程核对焦点与组合纪元,更新候选 UI 元素并通知宿主重读。推送与按键回复 走不同管道,按代号只接受不旧于当前的页,避免旧页盖掉新页。 - 从共享内存重读到不同的页时高亮回到第一项,与 Server 每次重建都选中首项一致, 不再保留旧高亮造成「高亮这个、空格上屏那个」。 * feat(windows,server,ui-html,installer): 游戏兼容开关,CS2 / Dota 2 独占全屏打字不再黑屏 CS2、Dota 2 只替内置白名单(按输入法登记名称全等比对)里的输入法处理输入法消息。不在名单里时 组合落到 Windows 默认组合窗 CiceroUIWndFrame,左上角弹出编码小框;独占全屏下这个窗口一出现 游戏就被踢出全屏,表现为一打字就黑屏。清风输入法未开兼容时同样如此。 - 设置页帮助页新增「游戏兼容(CS2 / Dota 2)」:开启后把水杉在系统里登记的名称换成白名单里的 名字(默认「拼音输入法」),游戏会自己画候选。需要管理员权限:设置进程以 --apply-game-compat-name 提权重启自身,写 HKLM 记录值并改语言配置的 Description。 - TSF DLL 注册时以记录值为准,安装/升级重新注册不再把用户选的名称冲掉;卸载时删除记录值。 - 开启时不再对 cs2.exe / dota2.exe 忽略「宿主自己画候选」的声明,避免两个候选框。 - Server 的独占全屏判断覆盖进出独占模式时的 QUNS_BUSY 间隙,不在切换途中弹候选窗。 - TSF 诊断日志在组合开始/结束与 BeginUIElement 时记录宿主窗口状态(只有窗口与标志,没有文本)。 * fix(windows): 后台刷新回调持有加载器引用,宿主卸载 TSF DLL 后不再崩溃 当前输入法是水杉时,CS2 启动途中会加载再卸载 TSF DLL。诊断日志的线程池回调 FlushTsfDiagnosticLogs 正在 Sleep(250),只靠 DllAddRef 撑着:那只回答 DllCanUnloadNow, 挡不住宿主直接卸载。DLL 卸载后 SDL3 映射进同一段地址,回调醒来跑进 SDL3 的代码, 游戏崩溃(0xc0000005,崩溃转储里卸载模块的地址范围与崩溃地址吻合)。 - 诊断日志与统计上报的线程池回调改为用 GetModuleHandleEx 取加载器引用, 回调里 FreeLibraryWhenCallbackReturns,函数完全返回后才放手。 - 析构函数把 DllRelease 挪到最后,不在等待 IPC 线程退出之前就让 DLL 可卸载。 * fix(windows): 游戏自绘候选时不再把 Server 的 HTML 候选页当成候选交给游戏 CS2 开了游戏兼容后打一个码,游戏里的候选只有一项且是乱码。宿主要等 BeginUIElement 才说 自己画候选,而那时组合首键已经不带 UI-less 标记发给了 Server;Server 往共享内存写的是给 自己候选窗用的 HTML 页(辅助码、译文 span),DLL 的候选元素从共享内存回读后整串交给了游戏。 - 开了游戏兼容名称的 cs2.exe / dota2.exe 在 ActivateEx 就认定由游戏画候选,每个按键都带标记。 - 候选会话里收到过 Server 的 UI-less 页之后才从共享内存回读,之前一律不读。 * fix(windows): 游戏自绘候选时翻页能刷新,部分上屏后不再出现乱码候选 三角洲行动(虚幻引擎,以 TF_TMF_UIELEMENTENABLEDONLY 激活)里按 = / - / PageUp / PageDown 翻页,Server 已翻到第 N 页,游戏却还画着第一页;接着按空格上屏的是第 N 页的首项(多为单字), 剩下的拼音继续组合时候选框里出现乱码。 - 翻页、移高亮等键走异步的 WM_AsyncServerCandidateKey,以前把回复当作「只是确认」丢掉; UI-less 下这份回复就是新的候选页,现在应用并通知宿主重读。 - UI-less 的 _SetText 拿不到 Server 的候选页时不再退回本地引擎的占位列表——那不是候选, 宿主会画成一条乱码。部分上屏后从共享内存回读 Server 写好的新页;_ClearList 同时清掉上次 的页记录,页文本相同也会重新载入。
Input engine (engine/)
The C++ conversion engine behind Metasequoia IME for Windows: input sessions, candidate lookup, helpcodes, the shared contracts and the voice module.
This directory used to be a submodule of metasequoiaime/MSIME-Engine. It is now a component of this repository, a sibling of server/, ui/, windows/ and installer/. Its upstream commit, and exactly what the Windows specialization removed, are recorded in UPSTREAM.md. Treat it as first-party code: reviewed, formatted and tested like anything else here, no longer merged from upstream.
| Directory | Purpose |
|---|---|
core/, quanpin/, shuangpin/, english/, japanese/ |
Input sessions and candidate lookup |
contracts/ |
The authoritative IPC wire format, dictionary format, punctuation policy and WebView message schema |
helpcode/ |
Helpcode (形码) tables and generators |
local_modes/, providers/, user_dictionary/ |
Local modes, online candidate sources, the user dictionary |
voice/ |
Recording, WAV encoding, recognition and text cleanup; builds independently |
googlepinyinime-rev/, ngram/kenlm/, utfcpp/, voice/third_party/ |
Vendored third-party code, kept in its upstream formatting and licence |
Integration is through <metasequoia/session.h>. See runtime architecture.
Build
The product build does not build this directory on its own: server/CMakeLists.txt pulls it in with add_subdirectory, so CI compiles and exercises the engine through the server and the TSF frontend. After changing something here, run the server's build and tests.
Building it standalone still works and is much faster while working on the engine. Dependencies are CMake 3.25+, a C++17 compiler, Boost, fmt, spdlog and SQLite3.
vcpkg install --triplet x64-windows-static
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build build --config Release --parallel
ctest --test-dir build -C Release --output-on-failure --timeout 20
ctest runs every engine target registered in the root CMakeLists.txt: the segmentation, IPC and punctuation contracts, the platform API, data paths, runtime isolation, dictionary state and the per-mode session tests. tests/ additionally holds a separate Windows-only project that is not part of that build — add new tests to the registered targets so they actually run.
Formatting is driven from the repository root by scripts/format.sh, using the .clang-format in this directory (voice/ has its own with a wider column limit). The vendored third-party trees and the generated headers are excluded.
Contract rules
The IPC wire format, opcodes, voice framing and WebView message definitions live in contracts/ and are the single source of truth. Consumers include the same headers; do not restate them on either side. Dictionary naming is defined by contracts/dictionary/format.json, shared with the repository root's scripts/dictionary_product.py.
Data and licensing
The dictionaries are neither stored nor built here. They arrive as a release pinned by tag and digest in product-lock.json and are downloaded at build time; the pipeline that produced them left with the specialization. Per-source licensing is tracked in NOTICE.md — read it before redistributing the built databases.
Recognition goes through a cloud provider; local Whisper inference was removed. The only committed model is voice/assets/models/silero_vad.onnx, loaded solely by the standalone Windows host that is off by default.
Licence
GPL-3.0. Vendored googlepinyinime-rev, ngram/kenlm (LGPL-2.1-or-later), utfcpp and voice/third_party/miniaudio keep their upstream licences.