The Server job's Build step dominates the pipeline (~6m37s of a ~9.5m job;
the dictionary/integration test is only ~43s). sccache caches individual
MSVC compiles across runs so an incremental change rebuilds only what it
touched instead of the whole server + engine tree.
sccache only caches an MSVC compile whose debug info is embedded in the
object, so switch the Server build's Release debug info from /Zi to /Z7.
The linker's /DEBUG still emits the beside-the-executable PDB the release
job validates, so the symbol layout is unchanged.
Its compiler-launcher hook is honoured only by the Ninja and Makefile
generators, so configure the Server build with Ninja Multi-Config (keeping
the --config Release / bin/Release layout the later steps depend on) and
add an MSVC-environment step, since Ninja does not locate cl.exe on its own.
Conflict resolutions:
* windows/src/Utils/FanyUtils.h -- develop deleted GetIMEDataDirPath too
(via #273), so both sides had identical content and the whole-file
conflict was only develop's .gitattributes LF normalisation. Took
develop's version; the symbol is now gone repo-wide.
* server/src/skin/candidate_skin_catalog.cpp -- #273 landed the same
wide-path manifest read on develop. Took develop's code verbatim so
this hunk is a no-op against develop, keeping only the comment that
explains why toml::parse_file(manifest.string()) is unusable here.
* server/tests/src/test_quanpin_scheme.cpp (x2) -- union: this branch's
test::Utf8(db_path) plus develop's `both` autocorrect-flag local.
Verified: server Release build clean, 282 tests pass.
The portable test target builds on a bare Linux runner with no vcpkg tree, so
the UTF-8 path helper cannot live in test_framework.h -- including the engine's
data_path.h there broke the sanitizer build. Move it to test_utf8_path.h, which
only the Windows tests that need it include.
Narrow strings in this repo are UTF-8, but std::filesystem converts narrow
strings through the system ANSI code page. The two agree only on ASCII, so any
path crossing that boundary breaks for users whose profile directory is not
ASCII -- and on a code page that cannot represent the characters at all,
path::string() throws rather than mangling.
The user dictionary replay tool hit exactly that. It handed the journal API
paths converted with path::string(); on a Chinese user name the conversion
threw, the exception escaped main(), and MSVC fastfailed the process with
0xC0000409. The installer treats any non-zero exit from replay as fatal, by
design, so those users could not upgrade at all. Build the paths from the wide
form via path_from_utf8/path_to_utf8 instead, and wrap main() so an unexpected
failure reports a diagnosable message and exit 1 rather than a fastfail code.
The skin catalog had the same defect: toml::parse_file(manifest.string())
routed the manifest path through the ANSI code page, and skins live under the
user profile. The throw escaped past the toml handlers around it. Read the file
through the wide path and parse the text instead.
Also drop FanyUtils::GetIMEDataDirPath, which built a path from the ANSI
environment block and dereferenced getenv without a null check. It had no
callers.
Tests cover the three layers this spans: the conversion contract itself, the
journal API in-process, the replay tool as the installer actually launches it,
and the skin catalog. Each non-ASCII case is paired with an ASCII control so a
failure says whether the path encoding or the data caused it. The replay and
skin tests were both confirmed to fail with their fix reverted.
Test fixtures pass paths through the new test::Utf8 helper rather than
path::string(), since the temp directory sits inside the user profile too.
Two independent failures kept the v0.6.5 installer from being built after
the pipe probe was fixed.
The package job died in "Verify the release commit is on main" with git
exiting 128 on dubious ownership: runner instance 2 leaves _work owned by
SYSTEM while the job runs as `runner`. actions/checkout is unaffected, so
the first plain git call is what fails, and only jobs scheduled onto that
instance see it. Mark the workspace as safe in the three self-hosted jobs.
Neither vcpkg manifest carried a builtin-baseline, so vcpkg resolved ports
against the registry at HEAD and every build depended on a live fetch of
microsoft/vcpkg. When that fetch degraded, the ports simply vanished:
Fetching registry information from https://github.com/microsoft/vcpkg (HEAD)...
fmt does not exist
utfcpp does not exist
Build TSF x64 failed this way twice and passed twice on the same runner,
which is what an unpinned registry looks like. Pin both manifests to
04a9d8e5, the commit vcpkg-lock.json already records as resolved.
PrepareCandidateList runs on the shared task thread, so a stall inside it
delays the hide/show messages queued behind it and reaches the screen as
candidate-window flicker. The diagnostic log showed an identical workload
take 8 ms once and 688 ms moments later, but not which segment paid for it.
Split the function into measured segments and emit one "candidate build"
line per keystroke, separating the engine candidate lookup from the
user-dictionary fixed-position pass -- the latter opens a SQLite
connection and runs its schema DDL on every call.
Measurement only; no behaviour change.
The unconditional 50ms hide grace cost typing its snap: every commit left the
candidate window up for an extra frame or three, and when the next word started
inside the window the pending hide was cancelled outright, so the box stopped
disappearing between words and just morphed instead.
Only a hide that waited behind a stalled task thread can describe state the user
has already typed past, and only that one has a show queued behind it worth
waiting for. Measure the queue wait where the task is dispatched and mark the
posted message; normal typing never reaches the threshold, so it hides
immediately exactly as before.
The wait is now logged per hide task, so the threshold can be checked against a
real capture rather than assumed.
Diagnostic logs from a busy machine show the candidate host repainting far
more often than the input changes. Three separate causes, all in the UI
thread's handling of the worker's posted messages:
- A HideCandidate for one keystroke of a burst (backspacing the composition
empty, then typing again) cloaks a visible window and un-cloaks it ~30ms
later. Defer the hide on a short grace timer and drop it when a show
arrives first; a real commit or focus loss still hides, 50ms later.
- Each keystroke posts two WM_SHOW_MAIN_WINDOW messages, since the candidate
rebuild and the English/cloud merge both request a show. Skip a repaint
whose preedit, page, selection and caret match the frame already on screen
within a short window.
- When the worker stalls it drains several keystrokes at once and posts a
show for each. The handler always renders the newest published state, so an
older queued show can only repaint what the newer one is about to; drop it
when a newer show is already queued.
The stall itself (PrepareCandidateList blocking the shared task thread, worst
case 688ms against a median of 11ms) is untouched here.
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
The sub-page nav titles were English ("Sticker", "GIF", "Kaomoji",
"Symbols") while the Clipboard page was already Chinese, and the text
labels the tabs fall back to when no icon font carries the glyph are all
Chinese. On a Windows 10 machine that actually hits that fallback, the user
taps a tab labelled 贴纸 / 颜文字 / 符号 and lands on a page headed
"Sticker" / "Kaomoji" / "Symbols".
Translate the titles to match the tab labels and drop the per-page font and
size branch, which only existed because Clipboard was the one Chinese title.
Rename kClipboardTitleFontSize to kNavTitleFontSize now that every page uses
it, and move it next to the other nav metrics. "GIF" stays as is: it is an
acronym and the tab label already reads GIF.
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.
std::filesystem::path(std::string) and path::string() convert through the
system ANSI code page, so a non-ASCII (e.g. Chinese) user profile path on a
non-UTF-8 (GBK 936) system was corrupted and every config read/write failed
with "设置保存失败"; on a code page that cannot represent the characters
path::string() threw, crashing the Server at startup in a watchdog restart loop.
Never route config paths through the ANSI narrow encoding:
- build g_config_path and the legacy voice path from the wide accessors;
- read config by opening the wide path + toml::parse(text) instead of
toml::parse_file(path.string()) (LoadImeConfig, MigrateLegacyVoiceInputConfig);
- build and open the TSF shared config path as a wide std::filesystem::path.
Add a regression test that round-trips config read/write under a Chinese
profile path.