* refactor(engine): 语音独立工具改用原生托盘菜单,移除 WebView2 托盘菜单改用 Win32 弹出菜单,设置项改为用关联编辑器打开 config.toml,关于页改为消息框。删除 window_webview2 与两份 HTML 资源,windows-app 特性不再依赖 webview2 端口。 * refactor(server): 移除 WebView2,候选窗、工具栏、托盘菜单与设置窗口只保留 Direct2D 删除 src/webview2 与 webview_utils,小窗口的置顶、皮肤重载和工具栏状态缓存移到 window/small_windows,presenter 成为唯一路径,D2D 下的置顶时序与 cloak 行为保持不变。设置进程去掉 WebView2 宿主、鼠标转发与启动图,只构建原生窗口;旧快捷方式传入的 --d2d / --webview2 被忽略。删除 appearance.ui_backend 与 appearance.settings_ui_backend(读取、写入、设置页下拉框与两份配置模板),升级时由模板合并丢弃旧值。设置窗口的图片资源移到 server/assets/settings,安装到数据目录的 assets\settings。不再链接 WebView2Loader,vcpkg 去掉 webview2 端口。 * build(installer): 打包与 CI 不再处理 WebView2 页面和运行时 Prepare-PackageFiles 去掉 -UiHtmlDirectory 与 html 暂存,改为暂存 server/assets/settings;安装器不再检查 WebView2 Runtime、不再创建 ProgramData 下的 WebView2 用户数据目录,升级和卸载时清理旧版留下的 webview2 / webview2-settings 与数据目录里的 html。CI 删除设置页构建、契约同步检查和候选窗 DOM 测试,release 的打包任务不再依赖网页产物;dependabot 去掉 npm。第三方声明去掉 WebView2Loader。 * refactor(ui-html): 删除 WebView2 页面与设置页前端工程 候选窗、工具栏、托盘菜单、设置页、主题和契约副本都已没有使用者;设置窗口用到的图片资源已移到 server/assets/settings。sync-contracts.py 与 pnpm/Vite/TypeScript 工程一并删除,契约仍以 engine/contracts/webview 为唯一来源。 * docs(server): 文档改为只描述 Direct2D 界面 仓库地图去掉 ui-html,组件边界、uiAccess 与置顶时序、设置窗口和配置读写的说明改写为 Direct2D 唯一界面;安装说明去掉 WebView2 Runtime 检查,打包命令去掉 -UiHtmlDirectory;engine/contracts/webview 的说明改为设置窗口是唯一使用者,名字是历史遗留。 * docs(skins): 皮肤说明不再区分 WebView2 与 D2D 渲染器 v2 字段去掉「仅 WebView2」,改为说明由 Direct2D 原生渲染、部分字段仍在补全;颜色必须带 # 改为清单格式约定。随包的 skins/niya-demo/skin.toml 不改,免得用户机器上的同名皮肤在升级时被当成已修改而改名备份。 * docs(ui): demo 说明文字不再提 WebView2
9.5 KiB
AGENTS.md — server
组织级约定见 组织 AGENTS.md,仓库地图和组件之间的边界见仓库根 AGENTS.md。本文件补充本组件的实现、数据和验证规则,路径都相对于 server/。
本组件是常驻后端进程:输入引擎与候选状态、配置、词典、Named Pipe 服务,以及候选窗、悬浮工具栏、托盘菜单(msimeui Direct2D presenter)。设置窗口(MetasequoiaImeSettings.exe)由本组件的 server/src/settings/ 单独构成一个进程,不在服务端进程内。
vcpkg_installed 的解析
CMakeLists.txt 引用 vcpkg_installed 的地方(头文件目录、sqlite3.dll 拷贝)全部走 ${CMAKE_BINARY_DIR}。
不要改回 ${CMAKE_SOURCE_DIR}/build/...。 那样 binary dir 就只能叫字面的 build,本仓自带的 vcpkg-release preset(binaryDir 是 build-release)和 scripts/lcompile-release.ps1 都会找不到 vcpkg 装好的头文件与运行时 DLL。
CI 刻意构建到 build-release 而不是 build,为的就是让这条不会悄悄退回去——两者在目录名恰好是 build 时解析结果相同,所以只有构建到别处才测得出来。
测试跑在真实词库上,本地要先备好数据
tests/CMakeLists.txt 构建 MetasequoiaImeServerTests,tests/src/ 下的测试源文件规模持续增长(引用具体数字前先数一遍)。本目录 CMakeLists.txt:25 的 include(CTest) 让 BUILD_TESTING 默认为 ON,:556 据此 add_subdirectory(tests),而 tests/CMakeLists.txt:169 用 add_test 注册同名用例。
CI 现在会真的跑它:根 .github/workflows/ci.yml 的 Server job 在 Build 之后执行 scripts/ci/test-server.ps1,那个脚本按 product-lock.json 取词库、备好数据根目录,再跑 ctest --test-dir server/build-release -C Release --timeout 120。断言失败会让 CI 变红,不再是编译通过就算数。
**关键在于这些测试依赖真实词库,不是 fixture。**引擎从数据根目录读数据,解析顺序是 METASEQUOIA_IME_DATA_DIR 环境变量 → 注册表 HKLM\Software\Metasequoia\MetasequoiaIME 的 DataDir(安装时用户选的位置)→ %LOCALAPPDATA%\metasequoiaime。装了正式版的机器上第二项会生效,所以本地跑测试必须用环境变量覆盖,否则会读到安装目录。另有 METASEQUOIA_IME_CONFIG_DIR 只改 config.toml 的位置、不搬词库,给那些既要真实词库又要隔离配置的测试用。scripts/ci/test-server.ps1 往那里放了四样东西,本地跑测试要凑齐同样的:
msime.db、others.db、english.db——从产品锁指定的dict-*release 下载,CI 逐个核对SHA256SUMS.txt。词库损坏要立刻失败,而不是拖到测试里表现成「候选为空」这种难查的样子helpcodes/——六套辅助码方案,本仓assets/tables只有其中一套,得从../engine/helpcode/取全assets/tables/*和assets/config/config.toml
数据不全时的典型症状是候选查询返回空集,断言信息看起来像逻辑错误,实际是缺数据。排查测试失败前先确认数据根目录是齐的。
IPC 线格式、opcode 和语音分帧的唯一实现位于 ../engine/contracts/——引擎已内嵌进本仓,和 Server 在同一个提交里;本仓的 src/ipc/ipc.h 只保留连接状态和 Server API。不要重新复制协议定义。共享头的 ABI 断言随主工程编译,主连接必须通过 NegotiateMainPipeClient 后才能激活;旧 DLL 的未版本化 hello 仍兼容,版本化客户端会收到关联 request_id 的协议确认。
改了协议或候选逻辑,正常构建并备好数据之后自己跑一次:
# scripts/lcompile-release.ps1 默认只构建 MetasequoiaImeServer 目标(打包用的那一套),
# 不含测试可执行文件。要跑 ctest 就得显式构建全部:
.\scripts\lcompile-release.ps1 -Target ALL_BUILD
ctest --test-dir build-release -C Release --output-on-failure
uiAccess 与 Topmost 时序
MetasequoiaImeServer.manifest 用 requestedExecutionLevel level="asInvoker" uiAccess="true",浮层才能盖在高完整性宿主之上。uiAccess 真正生效还依赖正确签名和安装在 Windows 认可的位置,清单、签名、安装路径是同一套发布约定。
进程在 src/main.cpp:90 设 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2。
三个小窗口的置顶由 src/window/small_windows.cpp 统一管理:建窗时不带 WS_EX_TOPMOST,以 DWM cloak 状态显示在真实显示器上;第一次真正显示时 EnsureSmallWindowsTopmost 打开置顶门,候选窗、悬浮工具栏、托盘菜单按固定间隔依次提升到 HWND_TOPMOST,托盘菜单最后(同一置顶带里后置顶的在上面)。这个分阶段顺序沿用自已移除的 WebView2 时期,D2D 窗口一直按它发布;要改成一次性置顶,先在签名并安装到受信位置的构建上验证 uiAccess 下的表现。不要在布局函数里随手传 HWND_TOPMOST 绕过这道门;托盘菜单需要回到最上面时用 RaiseTrayMenuAboveSmallWindows。
隐藏候选窗用 cloak 而不是 SW_HIDE,presenter 在下一次显示时解除 cloak;悬浮工具栏在 presenter 绑定失败时只 cloak 不 hide。
Boost
CMakeLists.txt:13 的 Boost_ROOT 是有守卫的:设了 BOOST_ROOT 环境变量就用环境变量,否则回退到作者本机路径。这是有意的,不要改成只认环境变量,那会破坏作者的本地环境。CI 就是靠设这个环境变量工作的。
Boost 是静态链接但没有写进 vcpkg.json,所以只能用 classic 模式装,且 triplet 必须是 x64-windows-static-md 而不是 x64-windows-static——Boost_USE_STATIC_LIBS ON 要静态 Boost 库,而项目其余部分用动态 CRT,纯 static triplet 会让 Boost 也切到静态 CRT,链接时报 LNK2038。
生成文件
scripts/prepare_env.py 只生成本目录的 .clangd(从 scripts/config_files/.clangd 模板展开)。测试使用 Server 主工程及同一套 preset,不再生成独立的 tests/CMakePresets.json。CMakePresets.json 是原地改写 VCPKG_ROOT 与 CMAKE_TOOLCHAIN_FILE,不再从模板覆盖。
CMakeLists.txt 和 tests/CMakeLists.txt 不再由脚本生成,直接改就行。这两个文件曾经也是从 scripts/config_files/ 覆盖过来的,但模板早已落后到 147 行对 400 行、以及一个无关的通用 WinCppTemplate,跑一次脚本就会毁掉构建,因此模板已删除。
.clangd 模板用 @NAME@ 占位符,不再按固定行号替换——旧的行号索引已经漂移过一次,把 Boost 路径写到了别的 include 上。新增 include 时在模板里加占位符、并在脚本的 clangd_substitutions 里补上对应项即可;有占位符没被替换脚本会直接报错。
提交
提交信息用 type(scope): 摘要。不要添加 Co-Authored-By、Generated with 或其他 AI 生成标记。
窗口与设置窗口的维护边界
- 界面只有 Direct2D 一套:候选窗、悬浮工具栏和托盘菜单的 HWND、尺寸、位置、显示、DPI、Z-order 在
src/window/(ime_windows*.cpp是宿主窗口过程,*_presenter.cpp用msimeui绘制)。WebView2 已整体移除,appearance.ui_backend/appearance.settings_ui_backend两个键也随之删除,不要恢复。 - 候选内容和输入状态的权威仍在 Server/引擎,presenter 只负责展示与发出用户动作;不要在界面侧复制 候选选择、翻页、输入模式或配置持久化的核心状态机。
- 设置窗口是独立进程
MetasequoiaImeSettings.exe(src/settings/):原生创建、激活、驻留和配置 读写映射在settings_app.cpp,界面在src/settings/native/,图片资源在assets/settings/(安装到 数据目录的assets\settings\)。界面与宿主之间走../engine/contracts/webview/的消息契约:宿主推送configSnapshot与各类响应,界面发configUpdate与请求,统一进HandleClientMessage校验和分发, 配置读写映射只在settings_app.cpp。每个native_page_*.cpp顶部注明了它当初移植自旧网页设置页的 哪个 partial / module,那些文件只在 git 历史里(ui-html/webview2/settings/ime-settings/src/)。 本地调试时可设METASEQUOIA_IME_SETTINGS_INSTANCE=<任意名>让开发版与已安装的设置窗口并存, 并用METASEQUOIA_IME_CONFIG_DIR隔离配置。旧快捷方式传来的--d2d/--webview2参数会被忽略。
尺寸、坐标与 DPI
- 所有涉及尺寸或坐标的代码都必须明确考虑 DPI。新增或修改窗口大小、位置、边距、间距、字体、 图标、命中区域、候选布局及屏幕边界计算时,不得默认固定像素在所有显示器上等价。
- 明确每个数值使用逻辑单位还是物理像素,并在正确的窗口/显示器 DPI 下统一换算;不要混用来自不同 DPI 空间的坐标和尺寸,也不要长期缓存可能在窗口跨屏或 DPI 变化后失效的换算结果。
- 原生窗口需处理运行时 DPI 变化(包括跨显示器移动),同步更新窗口、子窗口和相关布局;优先沿用项目已有的 DPI 获取与缩放辅助函数,避免各处自行实现不一致的换算规则。
- 涉及尺寸或布局的改动,至少检查 100%、125%、150%、200% 缩放,以及不同 DPI 显示器之间移动时 是否出现裁切、错位、模糊、点击区域偏移或窗口越出工作区。