* 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
Product contracts
These headers are independently consumable C++17 contracts. Including them does not link the input engine, start threads, access dictionaries, or require any third-party code. The TSF text service consumes only these headers; the server consumes the same source by linking the engine. Both live in this repository — windows/ and server/ — and so does the engine, so one commit covers all three and there is no pin to keep in step.
windows_ipc.h owns the wire layouts, pipe names and opcodes. voice_composition_pipe.h owns voice framing. uiless_page.h owns the candidate-page payload for UI-less (host-drawn) clients. Consumers may wrap these headers but must not duplicate their definitions.
punctuation/policy.json is the source of truth for the ASCII punctuation keys and their Chinese
translations. Its generated policy.h is consumed by the engine and included directly by the hosts
that route punctuation keys. Stateful quote alternation and nested book-title marks remain
part of the same contract, rather than being re-created in each host.
product_lock.py contains the shared product-lock primitives used by the three platform locks.
See product_lock.md for the vendoring and byte-equality rule.
Main handshake
Reverse endpoints retain their 16-byte hello and PipeReady acknowledgement. Once both endpoints are registered, the new client sends the existing 304-byte ClientHello with:
| Field | Meaning |
|---|---|
| keycode | MSIP magic |
| wch | Major wire protocol version |
| point[0] | Minor additive version |
| modifiers_down | Supported capability bits |
| point[1] | Required capability bits |
| request_id | Nonzero registration correlation ID |
The Server sends ProtocolReady or ProtocolMismatch on the registered reply endpoint before activating the main route. The reply echoes the correlation ID and contains major/minor, negotiated capabilities and magic in the first six UTF-16 units. These packets are consumed only during registration and are never text commits.
Existing DLLs with unversioned hello remain accepted by the new Server using the established v1 semantics; they receive no new opcode. New DLLs require an acknowledgement from the new Server within the existing bounded handshake budget. Against an old or incompatible Server they remain disconnected and use the existing raw-input fallback, rather than interpreting unnegotiated frames. A late acknowledgement from an old registration cannot authorize another one.
Append opcodes; never change released values or reuse them. An incompatible layout needs a new major protocol and an explicit migration, not another copy of a header. Minor additions must be optional capabilities.
CharacterSetShortcut is optional and is not included in the default capability set.
Windows implementations that support it pass Capabilities | CharacterSetShortcut
to Hello / Negotiate. Only after negotiating this bit may a Chinese-mode client
consume Ctrl+Shift+F (no Alt or Windows modifier) and send the existing KeyEvent
with keycode F and Shift/Control modifier bits. The server toggles the persisted
character set without clearing composition, and refreshes the current candidate
page and toolbar. It sends no key reply. Clients suppress auto-repeat and must not
replay an ambiguously delivered toggle; older peers simply omit the optional bit.
tests/windows_ipc_contract.cpp executes wire-layout/upgrade/framing cases on all Engine CI platforms and both Windows TSF architectures.
TrilingualCycle is optional. A supporting client advertises its capability and consumes
the existing configured language hotkeys only while the Server's TrilingualCycleChanged
setting ([keybindings] trilingual_cycle) is enabled. After matching a hotkey through the
existing shortcut handler, the client sends a normalized VK_SHIFT whose modifiers_down
carries FanyImeTrilingualInput::EncodeCycleRequest(ime_open, japanese): an explicit request
bit plus the client's own OPENCLOSE and native-language state. A plain VK_SHIFT is still the
binary toggle and is never treated as a cycle, and raw Shift key-down events never request one.
The Server computes the destination from the request's state with
FanyImeTrilingualInput::Destination; if its setting was turned off while the request was in
flight, it still answers, with the binary-toggle destination.
The Server retains any already selected word prefix and commits the remaining raw spelling,
matching the existing language toggle. It replies with TrilingualCycle: destination
(0 Chinese, 1 Japanese, 2 English),
a tab separator, and the exact text to commit. The client commits and ends composition before
applying the destination compartment in the same edit session. The Server must not also send
a worker candidate commit or compartment switch for that key, and leaves the requester out of
the InputModeChanged broadcast: the reply is that client's language update.
Clients that did not negotiate the capability retain their existing language toggle.
backend/protocol.json 定义 MSIME-Backend 的可选共通 HTTP 服务 API,详见后端协议与兼容性。
Temporary Japanese long vowels
TemporaryJapaneseLongVowel (bit 8) is optional. Only clients that negotiate it
receive Worker TemporaryJapanesePolicyChanged (opcode 36). Its payload is one
UTF-16 character 0 through 3: bit 0 allows Shift+R for the underlying Chinese
scheme (quanpin/shuangpin and utility.r_mode enabled); bit 1 indicates dedicated
English candidates. Full Japanese mode retains its existing behavior.
With bit 0 set and bit 1 clear, a composition beginning with uppercase R uses
the Japanese minus/equal key policy: ASCII - extends romaji with a long vowel;
_, = and + use the existing Japanese punctuation path. The visible R prefix
is excluded from engine input. Shared classification is in
temporary_japanese_input.h; both normal and deferred TSF classifiers use it.
Policy is sent on activation before FocusSessionReady, on Worker registration,
and when configuration or dedicated English mode changes. A TIP keeps policy
per bound service, clears it on disconnect, and ignores replaced-pipe frames.
Older clients and older Servers retain temporary R mode's previous key policy.