* 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 同时清掉上次 的页记录,页文本相同也会重新载入。
Tests
Run the candidate DOM test from the repository root:
node --test server/tests/test-candidate-dom-render.cjs
The test runs the JavaScript renderer from windows_webview2_candidate.cpp.
It checks text, order, badges, translations, and fixed candidate color.
This project now includes a lightweight regression test target wired into the main CMake build.
What It Covers
Current tests focus on high-value IME behaviors that are easy to accidentally break during refactors:
shuangpinscheme request constructionquanpinscheme request construction- case-preserving shuangpin segmentation
- full-helpcode detection
engine + shuangpincontinuation behavior- cloud suggestion trigger timing for shuangpin
The goal is not to fully simulate the whole IME UI pipeline yet. The goal is to protect the core input rules and session behavior.
Build and run
Run these commands from the MSIME-Windows repository root after preparing
vcpkg and Boost with python server/scripts/prepare_env.py:
python scripts/product_lock.py fetch-dictionaries --staging-root .
if ($LASTEXITCODE -ne 0) { throw 'Dictionary verification failed' }
$env:LOCALAPPDATA = Join-Path $PWD 'build/test-data/user-local'
$env:METASEQUOIA_IME_DATA_DIR = Join-Path $env:LOCALAPPDATA 'metasequoiaime'
New-Item -ItemType Directory -Force $env:METASEQUOIA_IME_DATA_DIR | Out-Null
Copy-Item MetasequoiaImeDict/out/* $env:METASEQUOIA_IME_DATA_DIR -Force
Copy-Item engine/helpcode/helpcodes $env:METASEQUOIA_IME_DATA_DIR -Recurse -Force
Copy-Item server/assets/tables/* $env:METASEQUOIA_IME_DATA_DIR -Force
Copy-Item server/assets/config/config.toml $env:METASEQUOIA_IME_DATA_DIR -Force
.\server\tests\scripts\llaunch.ps1 -Configuration Release
Use a dedicated PowerShell terminal: these environment variables isolate both Windows helpers and the shared Engine from the installed user's data for the rest of that session. The scripts run CTest; they do not launch the interactive Server, install or register the input method. CI additionally runs the native Win32/x64 pipe probe after building the full Server.
Tests belong to the Server CMake project, which provides the Engine, GUI and
third-party targets. server/tests/CMakeLists.txt is an add_subdirectory input,
not an independent project. The obsolete test presets have been removed; an old
locally generated server/tests/CMakePresets.json is no longer used.
Once test data is prepared, the convenience scripts work from any directory:
.\server\tests\scripts\llaunch.ps1 -Configuration Release # build, then CTest
.\server\tests\scripts\lrun.ps1 -Configuration Release # CTest only
Omitting -Configuration selects Debug in server/build; Release uses
server/build-release. Both compile MetasequoiaImeServerTests and
test_webview_contract in the parent build tree. Configuration, compilation,
test failures and an empty CTest registry return failure rather than launching a
stale executable or reporting success.
The equivalent direct CTest command from the repository root is:
ctest --test-dir server/build-release -C Release --output-on-failure --no-tests=error
Test Layout
tests/src/main.cpp- tiny custom test runner
tests/includes/test_framework.h- lightweight assertion helpers
tests/src/test_shuangpin_scheme.cpp- shuangpin scheme unit tests
tests/src/test_quanpin_scheme.cpp- quanpin scheme unit tests
tests/src/test_shuangpin_query.cpp- query/helper function tests
tests/src/test_engine_shuangpin_session.cpp- session-level behavior tests
Model path benchmark
Build the Server test target in Release mode. Run the benchmark from a Windows terminal:
cmake --build server/build-release --config Release --target MetasequoiaImeServerTests
server/build-release/bin/Release/MetasequoiaImeServerTests.exe --benchmark-model-path 10000 > model-path.csv
The executable creates small model files in a temporary data directory.
It restores METASEQUOIA_IME_DATA_DIR when it exits.
It does not load model contents.
Each row reports P50, P95, P99, and worst latency in nanoseconds.
hot repeats one path.
rotating cycles through 128 paths.
The rotating set increases metadata lookup pressure.
It does not guarantee an operating system cache miss.
first-touch checks each missing path once without benchmark warmup.
The operating system can still cache shared parent directories.
contended adds a thread that creates and removes files in the model directory.
This tests directory activity on the same volume.
It does not simulate all disk workloads.
The benchmark calls the production path resolvers used by ApplyConfiguration().
It measures their cost per call.
It does not measure full handle_key() latency.
The benchmark sets METASEQUOIA_IME_DATA_DIR to an isolated temporary directory.
Each resolver call reads this override and builds a path.
Normal installations do not set this override. Their directory lookup takes a different path.
The disabled-empty-id row returns before data_directory() runs.
The data-directory-only row measures data_directory() with the temporary override.
Median differences give only a rough scale for the remaining resolver work.
Do not subtract percentiles from separate rows to assign costs to path joining or file checks.
Use the rows to inspect latency distributions and tail latency across path access patterns.
They do not establish the same cost or performance loss in production.
Run each case more than once on the target Windows machine.
Record the storage type and active disk workload with the CSV results.
Recommended Practice
When you change input behavior, add a regression test if the change affects:
- input segmentation
- space/enter/backspace behavior
- helpcode handling
- continuation after candidate selection
- create-word flow
- cloud suggestion timing
The best tests in this project are the ones that capture a concrete input example that previously broke.