Commit Graph
16 Commits
Author SHA1 Message Date
fanlusky a7779d2b05 refactor(engine): 将 engine 从 submodule 内嵌为仓内一等组件
`vendor/MetasequoiaImeEngine` submodule 改为 `engine/` 目录,导入的是
product-lock.json 原本钉住的提交 c810d201,逐文件核对过 blob,因此这是一次
搬迁而不是引擎升级。引擎自己的嵌套 submodule(googlepinyinime-rev、utfcpp、
miniaudio)按各自 gitlink 一并展开,来源与提交见 engine/UPSTREAM.md。

Windows 专门化裁掉了不属于本产品的部分:whisper.cpp 与本地推理 provider、
macOS 示例宿主、整个 dictionary/ 建库流水线、上游 CI 与 release-please 配置。
词库仍按 product-lock.json 在构建时取发布产物。

引擎不再进 product-lock.json:没有 gitlink 就没有可比对的东西,锁里写一个上游
commit 等于宣称这棵树仍等于上游,而第一个 Windows 专门化改动落地时这句话就假了。
verify-contracts / verify-checkout 随之移除,verify-published 保留。

CI 与工具链:引擎源码并入根 scripts/format.sh(删掉引擎自己的那份,避免两套排除
列表分叉);引擎 voice.yml 里的 silero_vad.onnx 摘要校验移到本仓 lock job;
engine-bump-triage 工作流与其测试删除;gitsubmodule 的 dependabot 频率由 daily
改为 monthly。

README、README.en、AGENTS.md、CONTRIBUTING、docs/product-release.md 及各子目录
文档同步说明新的位置与边界。
2026-09-12 03:40:48 +08:00
Jia Sui 753312f32c fix(server): RDP 会话中候选窗缩放跟随宿主应用实际渲染比例 2026-09-11 22:21:57 +08:00
Jia Sui bd7cf1114d fix(server): D2D 内置皮肤补齐 light 配色,对齐 WebView2 CSS
Signed-off-by: Jia Sui <jsfaint@gmail.com>
2026-09-11 22:19:24 +08:00
Jia Sui 1ae63ad13f fix(server): D2D 候选窗明暗主题跟随设定与系统切换实时刷新
ApplySkin 的指纹缓存只含 theme_cand 原始值,follow 之下改 theme_mode
或系统明暗切换时指纹不变,候选窗停留在首次渲染的暗色 token。
把 ResolveConfiguredTheme 的解析结果并入指纹,让明暗变化穿透缓存。

同时补上系统明暗切换的实时刷新路径:WM_SETTINGCHANGE(ImmersiveColorSet)
时候选窗可见则重绘、常驻的悬浮工具栏即时 ApplyTheme;托盘菜单每次
打开已先 ApplyTheme,无需处理。
2026-09-11 22:19:24 +08:00
fanlusky 093b485124 Merge branch 'develop' into fix/issue-242-settings-cannot-save
冲突解决:

- windows/src/Utils/FanyUtils.h:develop 通过新增的 .gitattributes 把整个文件
  从 CRLF 规范化成 LF,与本分支删除 GetIMEDataDirPath() 声明的改动整文件冲突。
  取 develop 的 LF 版本,再删掉该声明。
- server/tests/src/test_config_non_ascii_path.cpp:仅 include 块冲突,取两侧并集
  (algorithm/filesystem/fstream/iterator/string/system_error)。另修正一处错误的
  自动合并——develop 新增的候选回退字体断言本属于
  config_round_trips_under_non_ascii_profile_path,git 却按上下文并进了本分支新增的
  config_migrates_legacy_acp_mangled_path,已移回原测试。
2026-09-10 19:33:43 +08:00
Neptrue_Lin ff7650bba2 Merge pull request #293 from Aegon8719/feat/issue-55-fallback-fonts
issue-55 候选窗支持可配置的有序回落字体
2026-09-10 17:14:29 +08:00
Aegon8719 dea9292814 issue-55 候选窗支持可配置的有序回落字体 2026-09-09 18:03:56 +08:00
Aegon8719 848cf6a288 issue-288 修复候选窗在光标坐标无效时提前显示的问题 2026-09-09 11:40:26 +08:00
linym 075fbb2a84 fix(server): persist settings after leftover non-ASCII config paths
Elevated setup left high-IL config.toml that medium-IL Settings never
rewrote on machines hit by the ANSI path bug. Make the data directory
writable, migrate ACP-mangled leftovers, rebuild unparseable files, and
keep remaining asset paths on wchar_t.

Fixes #242
2026-09-08 22:42:26 +08:00
Ouchihao 3fc7ba295f feat(server): show translations in horizontal candidate windows 2026-09-06 18:09:01 +08:00
Evan 42238a2af6 style: 用 .clang-format 重排 server / windows / ui / log
一次性重排,无任何行为改动。四个目录各自的 .clang-format 放在仓库里已久,但全组织只有
MSIME-Linux 在 CI 里执行 clang-format 检查,这边 149 个文件里有 149 个不符合自己声明的风格。

已逐文件核对语义等价:去掉空白、注释与相邻字符串字面量拆分之后,全部 149 个文件与重排前逐字节
相同。差异只有三类——缩进与换行、clang-format 补的 } // namespace 结尾注释、以及长字符串被拆成
相邻字面量(C++ 保证自动拼接)。

这个提交会写进 .git-blame-ignore-revs。
2026-09-06 12:13:47 +09:00
Evan 2562026a2a fix: 修复审计发现的候选页竞态、管道权限、TSF 空指针与皮肤 CSS 逃逸等八项缺陷 (#169)
* fix(server): publish candidate pages as immutable snapshots

Global::candidate_ui is a plain global with no lock. The IPC worker rebuilds and destroys its vectors while the UI thread paints from them, and the handoff is an asynchronous PostMessage, so the worker returns to its loop and pops the next task without waiting. A second keystroke, or an async english.db candidate arrival, could free the page_views buffer and its strings while FillItemsFromUi held a reference into it. ShowItemContextMenu reads the same vectors from a mouse callback with no message handoff at all.

The worker now copies each finished page into an immutable snapshot and publishes it at the single point where the rebuild completes; every UI-thread reader loads that snapshot once and reads only its own copy. PostMessage is deliberately left alone: the worker also serves UI-originated tasks, so a synchronous handoff would invite a cross-thread deadlock.

* fix(server): bound the aux and diagnostic pipe reads

Both listeners serve a single pipe instance inline and polled a NOWAIT handle for as long as the process ran, breaking only on a read that succeeded or failed with something other than ERROR_NO_DATA. A client that connected and never wrote held the only instance forever, so the loop never returned to ConnectNamedPipe and no later client was accepted, permanently losing the langbar right-click channel and the terminal-deactivation teardown fallback.

Give both loops the same deadline the hello reads already use and go back to accepting when it expires.

* fix(server): snapshot the voice config and replace config.toml atomically

GetConfiguredVoiceInput handed out a reference to a global that LoadImeConfig rewrites from the IPC worker thread, clearing and refilling two std::map members and around twenty strings. The voice control thread and the low-level keyboard hook, which copies the struct on every keystroke, walked those nodes as they were freed. LoadImeConfig now parses into a local and publishes it in one step under a process-local shared_mutex, and the getter returns a snapshot by value. The file lock is deliberately not reused for readers: it is a named cross-process mutex with a five second wait, and taking it in a keyboard hook would stall typing system-wide.

WriteConfiguredValue also reopened the live config.toml with ios::trunc, so a crash mid-write destroyed the user settings and could leave the file unparseable for good. It now goes through WriteFileTextAtomically, which this file already uses elsewhere. That makes the directory entry swap atomic; it does not flush, so it is not a durability guarantee.

* fix(ipc): restrict the server pipes to the owning account

The pipe DACL granted Everyone FILE_ALL_ACCESS. That bit set includes FILE_CREATE_PIPE_INSTANCE, so any local account could add an instance to a name this server already owned and answer part of the connections in its place. The names are machine-global with no per-user component, and the TSF client opened them with no SQOS, so a squatter could also impersonate the connecting thread.

Build the descriptor from the process owner SID and admit only LocalSystem, that account, and app containers with connect-only rights so UWP text input still works; fail pipe creation outright when the SID cannot be resolved rather than falling back to something permissive. Pass FILE_FLAG_FIRST_PIPE_INSTANCE on the first instance of each name so a squatted name fails loudly, and request SECURITY_IDENTIFICATION on the client side.

Authenticating the peer process after connect is a larger change to the handshake and is not attempted here.

* fix(tsf): guard the null focus paths and unadvise the compartment sink

_CandidateChangeNotification treated ITfThreadMgr::GetFocus and ITfDocumentMgr::GetTop as if they could only fail through a FAILED HRESULT. Both return S_OK with a null out-pointer when nothing has focus or the document stack is empty, and the results were dereferenced unconditionally, so an external UILess host calling Finalize while focus moved away crashed the host application. The sibling _RequestCancelComposition in the same class already checks both.

CLangBarItemButton::CleanUp only unregistered the compartment inside the SUCCEEDED branch of a CoCreateInstance, then deleted the sink regardless. This TIP registers COM-less activation support, where that call does fail, and the sink was freed while TSF still held it advised. Unadvising never needed the thread manager, so it now runs unconditionally before the deletes.

* fix(ui): drop the cached visuals when the scene is replaced

Window caches focused, hovered and captured Visual pointers into the scene tree, and SetScene destroyed that tree without clearing them. Toggling the input mode rebuilds the floating toolbar scene, so the next mouse move dispatched a virtual call through a freed object.

Clear all three before the assignment, and release the capture only when this window actually holds it: an adopted HWND may never have called SetCapture, and capturedVisual_ is set even when it did not.

* fix(settings): scope external skin CSS through the CSS parser

injectScopedSkinCss concatenated a skin toolbar.css into an @scope block as text. Block nesting is textual, so a leading close brace in the skin ended the block early and the rest styled the whole settings window. The host validates only the skin file name, never the body, and candidatePreviewCss interpolated unvalidated colour strings into rule bodies the same way.

Create the scope rule empty and re-insert every rule parsed out of the skin as a child of it, so nothing lands outside however the braces are balanced, and accept a colour only in the plain notations we actually render.

* fix(release): gate the dictionary source commit on its default branch

verify_published iterated only data["repositories"], but the dictionary block pins a source_commit exactly like they do. The manifest check merely proves the release agrees with the lock about that commit, which a branch commit satisfies just as happily, so a dictionary built from an unmerged branch shipped with the provenance the gate exists to refuse.

Run the dictionary through the same loop under its own name, resolving its repository from the lock. A retired dictionary repository that no longer answers is reported rather than failing the release: its published artifacts are immutable history, and being unable to re-check them is not the same as finding them unmerged.
2026-09-06 10:31:48 +09:00
Evan Hu 6783ff7175 refactor(session): use shared composition and local queries with one candidate view model 2026-09-06 00:26:38 +09:00
fanlusky 895abeee08 Optimize the width expansion logic of the vertical window so that it does not leave large white spaces. 2026-09-02 19:31:44 +08:00
fanlusky 9f4c8efad7 Reuse the candidate D2D scene and keep the host size stable while typing
ApplySkin was rebuilding the card/scene on every keystroke and
SetWindowPos resized the HWND by a few pixels each time, which forced
DXGI ResizeBuffers and looked like a leak. Skip skin work when the
fingerprint is unchanged, update the existing Card in place, and only
grow the host in 64px buckets. Point msimeui at the emoji-fallback and
swapchain fixes.
2026-09-02 16:26:14 +08:00
fanlusky 5c02503139 Render the candidate window with D2D instead of WebView2
Drive the existing candidate HWND through CandidatePresenter and msimeui,
keep WebView2 for the menu, floating toolbar, and settings, and load
external skins from skin.toml color tokens. Restore cloud/AI badges on
D2D items, and keep click, pin, fix-position, and delete on the same
window messages as before.
2026-09-02 15:30:03 +08:00