* 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
93 lines
9.5 KiB
Markdown
93 lines
9.5 KiB
Markdown
# AGENTS.md — server
|
||
|
||
组织级约定见 [组织 AGENTS.md](https://github.com/metasequoiaime/.github/blob/main/AGENTS.md),仓库地图和组件之间的边界见[仓库根 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 的协议确认。
|
||
|
||
改了协议或候选逻辑,正常构建并备好数据之后自己跑一次:
|
||
|
||
```powershell
|
||
# 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 显示器之间移动时
|
||
是否出现裁切、错位、模糊、点击区域偏移或窗口越出工作区。
|