Commit Graph
92 Commits
Author SHA1 Message Date
fanlusky 43576b75ca fix(ui): make the icon font resolver recover from DirectWrite failures
ResolveIconGlyph() guarded its cache with a mutex, but the work under that
lock reached SharedFactory(), whose lazy DWriteCreateFactory() call was
unsynchronized -- and UiFontFamily()/ApplyUiFontFallback() call the same
helper without that mutex from render paths. Two window threads could both
see a null factory, both create one into the same ComPtr, leak one and race
on the raw pointer. Give SharedFactory() a lock of its own.

IconFonts() memoized its result in a function-local static initialized
exactly once. If SharedFactory() returned null or GetSystemFontCollection()
failed on that first call, the empty list was cached for the process
lifetime and every icon degraded to its text label permanently, with no
retry -- while SharedFactory() itself retried on each call, making the
failure mode asymmetric and hard to diagnose. Keep the list only once the
probe finds a font, and skip the ResolveIconGlyph cache while it is empty
so the retry is actually reachable.
2026-09-08 16:11:16 +08:00
fanlusky 43555460db fix(server): fall back to text when the icon font lacks the glyph
The floating toolbar and the emoji panel hardcoded "Segoe Fluent Icons",
which ships with Windows 11 only. DirectWrite substitutes a font silently
instead of failing, so on Windows 10 every private-use codepoint the toolbar
draws (U+E982, U+F110, U+F138, ...) rendered as a blank box: the buttons
still worked but no longer showed any state.

Resolve the font per codepoint rather than per family, because "Segoe MDL2
Assets" is a subset of Segoe Fluent Icons and swapping the family alone would
still draw tofu wherever a glyph is missing:
- add msimeui::ResolveIconGlyph(), which probes the installed icon fonts with
  IDWriteFont::HasCharacter() and returns the first family that really carries
  the glyph, or no family at all;
- give every toolbar and tab icon a text label to fall back on (中/英, 半/全,
  简/繁, ...) when neither font has the glyph;
- drop the unused ToolbarIconButton::SetGlyph/SetLabel, which would bypass the
  resolved family.

Add unit tests for the resolver, covering the missing-glyph path that cannot
be reached on a host that has Segoe Fluent Icons installed.
2026-09-08 12:41:01 +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
fanlusky a0c4daa9ad Allow Card window shadows to scale for compact surfaces
Add Card::SetShadowScale so the Win11 mist shadow can shrink with the
control. The floating toolbar can then keep the same shadow style without
clipping or a oversized host.
2026-09-02 19:12:01 +08:00
fanlusky 4e66d19614 Optimize the shadow style of the candidate window. 2026-09-02 17:23:03 +08:00
fanlusky 2ac86ea0e2 Stop mapping all Unicode through CJK fonts and cut D2D working-set growth
Custom font fallback no longer covers the whole BMP/SIP, so Segoe UI Emoji
can paint color glyphs again; CJK still prefers Noto Sans SC then YaHei.
Drop GaussianBlur compatible targets during BeginDraw (they retained GPU
memory as the candidate window grew), grow the composition swapchain in
64px steps without shrinking, and reuse Card chrome plus cached text
formats instead of reallocating them every layout.
2026-09-02 16:25:54 +08:00
fanlusky 5421c1dd1b Add SVG icon rendering, toggle switch, and font fallback for MenuFlyoutItem
MenuFlyoutItem gains leading SVG icons (ID2D1SvgDocument), an optional
trailing toggle switch, and content-based width measurement so tray-style
menu items match the HTML menu layout. Default UI font is now Noto Sans SC
with Microsoft YaHei fallback via IDWriteFontFallback; CreateTextFormat
gracefully falls back when the primary family is missing.
2026-09-02 16:03:57 +08:00
fanlusky d16f9f3fea Add DirectComposition hosting and candidate-list chrome for the IME window
Adopt an existing HWND, render through D3D11/DXGI/DirectComposition, and
extend CandidateList with horizontal layout, skin colors, click-to-commit,
and a D2D context menu. Preedit TextBlock draws a caret in a fixed insert
slot so neighboring glyphs reflow instead of using letter-spacing; the
default end caret sits slightly after the last character.
2026-09-02 15:29:14 +08:00
fanlusky 3d7b8a3c24 Add support for svg render. 2026-08-17 04:42:53 +08:00
fanlusky 64c08a7c02 Adjust some controls for emoji panel. 2026-08-15 02:32:00 +08:00
fanlusky f3c7a9557a Set the initial position setting method for the window. 2026-07-20 23:22:05 +08:00
fanlusky 548330b8cf Make a demo of an on-screen keyboard. 2026-07-15 18:22:50 +08:00
fanlusky 3a41372f24 Adjust font size and some layouts 2026-07-15 15:50:50 +08:00
fanlusky 59a73b8476 Optimize the emoji panel size. 2026-07-15 15:43:58 +08:00
fanlusky 05a4ada19d Optimize the emoji panel. 2026-07-15 15:34:53 +08:00
fanlusky 604cf987b1 emoji panel demo step01 2026-07-15 14:49:36 +08:00
fanlusky 4f62123699 Support image render 2026-07-15 14:39:55 +08:00
fanlusky a91baf56c1 Add dictionary-backed word deletion 2026-06-19 13:46:10 +08:00
fanlusky ab5516da96 Unclamp combo box popups 2026-06-19 13:03:32 +08:00
fanlusky e210c8eb4c Allow popups to scroll out of view 2026-06-19 13:00:35 +08:00
fanlusky 19257221cc Restore popup trigger press feedback 2026-06-19 12:54:41 +08:00
fanlusky 8a20c1461a Remove sticky button focus border 2026-06-19 12:50:00 +08:00
fanlusky d2ca118d89 Add candidate hover highlight 2026-06-19 11:52:43 +08:00
fanlusky 7361a13b19 Keep arrow cursor over candidate list 2026-06-19 11:49:15 +08:00
fanlusky 7075c06aee Emphasize candidate highlight bar 2026-06-19 11:46:45 +08:00
fanlusky 33350ab7c2 Trim candidate header text metrics 2026-06-19 11:34:30 +08:00
fanlusky fffcf648b2 Widen candidate highlight block 2026-06-19 11:24:32 +08:00
fanlusky abe110c6e1 Refine candidate preview weight 2026-06-19 11:14:22 +08:00
fanlusky 718d4f9559 Align candidate preview typography 2026-06-19 11:06:54 +08:00
fanlusky 9e7d1acdac Scale candidate preview back up 2026-06-19 10:59:29 +08:00
fanlusky eb6d49b7ea Compress candidate preview spacing 2026-06-19 08:12:20 +08:00
fanlusky 912cf30148 Align candidate bar to selection edge 2026-06-19 08:07:54 +08:00
fanlusky d35dd7b2de Tighten candidate preview toward reference 2026-06-19 08:00:25 +08:00
fanlusky 01612f3db3 Polish candidate preview shell 2026-06-19 07:52:08 +08:00
fanlusky a182e6a16f Tune candidate preview emphasis 2026-06-19 06:50:02 +08:00
fanlusky d102d0a72a Refine candidate selection geometry 2026-06-18 05:54:43 +08:00
fanlusky ddbfbabaec Match candidate preview to HTML styling 2026-06-17 20:36:17 +08:00
fanlusky 2953e2685a Fix compact candidate preview spacing 2026-06-17 20:31:59 +08:00
fanlusky 34ea7bf8cc Fix candidate preview text encoding 2026-06-17 20:23:29 +08:00
fanlusky 41f4571bd2 Add candidate popup keyboard navigation 2026-06-17 20:20:17 +08:00
fanlusky 10cfb70d66 Add candidate popup preview primitives 2026-06-17 19:08:27 +08:00
fanlusky e1156b32e2 Highlight scrollbar on hover 2026-06-17 16:44:51 +08:00
fanlusky cda3101b3c Remove multiline selection seam 2026-06-17 16:35:07 +08:00
fanlusky e70a8c5f11 Center demo window on startup 2026-06-17 16:17:18 +08:00
fanlusky 83d10fed4c Fix repeated Ctrl+Backspace deletion 2026-06-16 23:07:25 +08:00
fanlusky 8a95ecd9cd Smooth multiline selection and add Ctrl+Backspace 2026-06-16 22:56:23 +08:00
fanlusky 3666ff7e14 Add basic TextBox clipboard shortcuts 2026-06-16 22:46:03 +08:00
fanlusky 195ca6a47f Smooth TextBox selection highlight rendering 2026-06-16 22:39:51 +08:00