Lu Fan(SonnyCalcr)
|
36bde9bbdb
|
Merge pull request #497 from fanlusky/perf/tsf-focus-wakeup
perf(windows): 焦点激活与 worker 管道发布改为事件唤醒
|
2026-09-27 18:16:12 +08:00 |
|
Lu Fan(SonnyCalcr)
|
447d3a5654
|
Merge branch 'develop' into perf/tsf-focus-wakeup
|
2026-09-27 18:13:01 +08:00 |
|
Lu Fan(SonnyCalcr)
|
077345e3ca
|
Merge pull request #496 from fanlusky/refactor/split-tsf-files
refactor(windows): 按职责拆分 KeyEventSink / MetasequoiaIME / CompositionProcessorEngine
|
2026-09-27 18:06:21 +08:00 |
|
Lu Fan(SonnyCalcr)
|
bb8c5e9be8
|
Merge pull request #495 from fanlusky/refactor/split-config-and-controls
refactor(server,ui): 按职责拆分 ime_config.cpp 与 Controls.cpp
|
2026-09-27 18:06:05 +08:00 |
|
Lu Fan(SonnyCalcr)
|
bec6aa036d
|
Merge pull request #494 from fanlusky/perf/engine-benchmark
test(engine): 新增逐键耗时基准 bench_input_session
|
2026-09-27 18:05:50 +08:00 |
|
Lu Fan(SonnyCalcr)
|
66dd0f2b76
|
Merge pull request #493 from fanlusky/ci/engine-tests
ci: 单独构建并运行引擎自己的测试
|
2026-09-27 18:05:35 +08:00 |
|
fanlusky
|
8b65243b11
|
perf(windows): 焦点激活与 worker 管道发布改为事件唤醒
切换焦点后,EnsureNamedpipeFocusSessionActivated 等 worker 管道回显 focus
token 时用 Sleep(1) 轮询,最长 150 ms。Sleep(1) 按系统时钟粒度向上取整(默认约
15.6 ms),而回显通常不到 1 ms 就到了,所以每次切换焦点都白等一个时钟周期。
冷激活、Server 重启或重连之后,IPC worker 线程没有管道时每 50 ms 才检查一次
新发布的句柄,回显又在管道里多躺最多 50 ms。两处都在宿主 UI 线程的首键路径上。
- 新增两个自动重置事件:worker 记下确认后 SetEvent(_workerAckEvent);
PublishWorkerPipeHandleIfChanged 发布非空句柄后 SetEvent(_workerPipePublishedEvent)。
- 等确认的循环改为 WaitForSingleObject(ack, 剩余时间)。检查逻辑、令牌比对和
150 ms 上限不变;每次唤醒都先重新检查,worker 在 release 写入之后才发信号,
不会丢唤醒,残留信号只多一轮检查。与 Sleep 一样不泵消息,不会重入 TSF 回调。
- worker 无管道时改为 WaitForMultipleObjects({stop, published}, 50),stop 在
下标 0 优先;50 ms 超时保留为兜底。
- 事件经新增的 BindNamedpipeWakeEvents 绑定到线程本地,UnbindNamedpipeFocusState
一并清除。在 IPC 线程启动前创建、join 之后关闭;Deactivate 关闭前先解绑,
其后的 _EndComposition 若再进等待循环会退回轮询,不会等已关闭的句柄。创建
失败或等待失败时退回原来的 Sleep(1)。
- 不动 PeekNamedPipe 存活检查,也不用 timeBeginPeriod(会改宿主进程的全局状态)。
|
2026-09-27 17:43:43 +08:00 |
|
fanlusky
|
ab58b10cdc
|
refactor(windows): 按职责拆分 CompositionProcessorEngine.cpp
CompositionProcessorEngine.cpp 有 3000 行,预编辑缓冲、按键表与
preserved key、输入模式与标点、虚拟键判定全挤在一个文件里。按职责拆成
以下几个翻译单元:
- CompositionProcessorEngine.cpp:_AddTextProcessorEngine、构造/析构、
SetupLanguageProfile、预编辑缓冲与光标(AddVirtualKey 到
GetCandidateList)、SetupConfiguration、语言栏按钮、
CMetasequoiaIME::CreateInstance/ComLessCreateInstance/GetComModuleName、
候选编号范围与默认字体
- CompositionProcessorEngine_Modes.cpp:中英文、标点、全半角 compartment
的读写与回调、SendCaretStateSwitchEvent、外部关闭键盘时的提交,以及
标点表、成对/嵌套标点和智能标点映射
- CompositionProcessorEngine_PreservedKeys.cpp:按键表、preserved key 的
设置/注册/资格/动作、OnPreservedKey、XPreservedKey 和 InitKeyStrokeTable
- CompositionProcessorEngine_KeyClassify.cpp:IsVirtualKeyNeed、
IsVirtualKeyNeedForFreshComposition 及其用到的按键表、候选键和数字范围
判定
纯搬移,不改行为:函数体、注释和格式都没动。匿名命名空间里的辅助函数、
CCaretStateSwitchEditSession 和智能标点映射表各自只被一个新文件用到,原样
留在该文件的匿名命名空间,不需要内部头文件,也没有新增全局构造器或函数内
静态变量。
用脚本比对了原文件与拆分后全部文件的非空、非 #include 行多重集:没有缺
失的行,差异只有新文件的文件头注释。可用 git diff --color-moved 复核。
|
2026-09-27 17:39:28 +08:00 |
|
fanlusky
|
a3739fc528
|
refactor(windows): 按职责拆分 MetasequoiaIME.cpp
MetasequoiaIME.cpp 有 3000 多行,激活/停用生命周期、按键路径的 edit
session 请求、Worker Pipe 读线程和消息窗口处理全挤在一个文件里。按职责
拆成以下几个翻译单元:
- MetasequoiaIME.cpp:Server 拉起辅助、CreateInstance、构造/析构、焦点
会话与 composition epoch 校验、本地会话重置、候选 presenter 延迟清理、
QueryInterface/AddRef/Release、ActivateEx/Deactivate、
_WakeServerIfNeeded/_NoteKeyEventIpcFailure 和 ITfFunctionProvider 系列
- MetasequoiaIME_AsyncRequests.cpp:标点、智能标点、延迟应用文本几个
edit session 及其 _Request*EditSession,待取的 Server 候选,异步按键
请求表(_PostAsyncKeyRequest/_TakeAsyncKeyRequest/_ClearAsyncKeyRequests)
和 _ClearPendingIpcRequests
- MetasequoiaIME_IpcWorker.cpp:IpcWorkerThread,以及把它收到的上屏、
语音快照和 compartment 切换交给 TSF 线程的令牌表
- MetasequoiaIME_WindowProc.cpp:CMetasequoiaIME_WindowProc 及其用到的
状态快照发送、CommitCandidateAndContinue edit session,还有主题注册表
监听与语言栏图标刷新
- MetasequoiaIMEInternal.h:上述文件之间共享的声明,只给
MetasequoiaIME*.cpp 包含
纯搬移,不改行为:函数体、注释和格式都没动。原来在匿名命名空间里、现在
要跨文件用的 NextWindowMessageToken 仍定义在 MetasequoiaIME.cpp,改放进
metasequoia_ime_detail 命名空间并在内部头文件里声明;
CONNECT_NAMEDPIPE_RETRY_INTERVAL_MS 常量挪进内部头文件的同一命名空间。
只被一个文件用到的仍留在该文件的匿名命名空间。_bareShiftHookOwner 这个
thread_local 和 #pragma comment 仍只在 MetasequoiaIME.cpp。没有新增全局
构造器或函数内静态变量。
用脚本比对了原文件与拆分后全部文件的非空、非 #include 行多重集:没有缺
失的行,差异只有新增的声明、命名空间包装、using 指令、#pragma once 和文
件头注释。可用 git diff --color-moved 复核。
|
2026-09-27 17:39:11 +08:00 |
|
fanlusky
|
9c1c4b82b2
|
refactor(windows): 按职责拆分 KeyEventSink.cpp
KeyEventSink.cpp 有 3100 多行,同步按键判定、延迟按键队列与回放、输入模式
热键全挤在一个文件里。按职责拆成以下几个翻译单元:
- KeyEventSink.cpp:_IsKeyEaten、ConvertVKey、_IsKeyboardDisabled、退格
长按与 CapsLock 处理、直通字符统计,以及 ITfKeyEventSink 各回调
(OnSetFocus、OnTestKeyDown、OnKeyDown/_DispatchKeyDown、OnTestKeyUp、
OnKeyUp、OnPreservedKey/_DispatchPreservedKey)和 sink 的注册/注销
- KeyEventSink_DeferredKeys.cpp:延迟按键屏障与容量、预测状态投影、恢复
前缀、_ClassifyDeferredKeyDown,以及入队、清空、完成、重试、丢弃歧义键、
排空调度和 Server 不可用回退
- KeyEventSink_Hotkeys.cpp:mintty 的裸 Shift 键盘钩子、裸修饰键武装、
组合键与修饰键松开热键匹配、_QueueInputHotkey
- KeyEventSinkInternal.h:上述文件之间共享的声明,只给
KeyEventSink*.cpp 包含
纯搬移,不改行为:函数体、注释和格式都没动。原来在匿名命名空间里、现在
要跨文件用的函数(SegmentEditFunction、CaptureIpcModifiers、
IsEnglishInputModeToggle、IsTranslationCommitShortcut、
IsCharacterSetInputModeToggle、PostOwnerMessageWithSyncFallback、
IsShiftVk)仍定义在 KeyEventSink.cpp,改放进 key_event_sink_detail 命名
空间并在内部头文件里声明;只被一个文件用到的仍留在该文件的匿名命名空间。
THIRDPARTY_NEXTPAGE/THIRDPARTY_PREVPAGE 宏和本来就是 inline 的
VKeyFromVKPacketAndWchar 原样挪进内部头文件。唯一的非搬移改动是
PostOwnerMessageWithSyncFallback 的默认实参从定义挪到了头文件里的声明上。
没有新增全局构造器或函数内静态变量。
用脚本比对了原文件与拆分后全部文件的非空、非 #include 行多重集:除上述
函数签名外,差异只有新增的声明、命名空间包装、using 指令、#pragma once
和文件头注释。可用 git diff --color-moved 复核。
|
2026-09-27 17:38:47 +08:00 |
|
fanlusky
|
c3bfa69a29
|
refactor(ui): 按控件拆分 Controls.cpp
Controls.cpp 有 4200 行,Image、Button、CheckBox、ProgressBar、Slider、Separator、
Popup 及其宿主、菜单行、ComboBox、ListView、CandidateList、TreeView、TabControl、
Accordion 的实现,连同它们共用的文字测量与绘制工具全挤在一个文件里。沿用
TextBox.cpp 的做法,按控件拆成以控件命名的翻译单元:
- Image.cpp:Image(含只它用到的 WIC 读图尺寸)
- Button.cpp:Button 与 CheckBox(含只 Button 用到的 DrawTextBlock)
- RangeControls.cpp:ProgressBar 与 Slider
- Separator.cpp:Separator
- Popup.cpp:Popup(含 LayoutOverlay)、PopupHost、ContextMenuHost,以及只有
Popup 用到的 DeflateSizeLocal / DeflateRectLocal 和 DrawPopupShadow
- Menu.cpp:MenuFlyoutItem 与 MenuSeparator(含托盘菜单字号常量和
EstimateTrayLabelWidth)
- ComboBox.cpp:ComboBox 与下拉列表 ComboBoxPopupContent
- ListView.cpp:ListView
- CandidateList.cpp:CandidateList 及其文字度量缓存(含 FillRoundedRectCorners
和彩色字体裁剪选项)
- TreeView.cpp、TabControl.cpp、Accordion.cpp:各自的控件
- ControlsInternal.h / ControlsInternal.cpp:多个控件共用的 GetSharedDWriteFactory、
PointInRect、PointInRoundedRect、MeasureText、CreateCachedTextLayout、
FillRoundedRect、DrawLabel 和两个共用圆角常量,只给 src/ 下的实现文件包含
纯搬移,不改行为:函数体、注释和格式都没动。多个文件共用的工具放进
msimeui::controls_detail 命名空间,声明在 ControlsInternal.h、定义只在
ControlsInternal.cpp 里有一份——CreateCachedTextLayout 的函数内静态
formatCache 和 GetSharedDWriteFactory 的静态工厂因此仍是全库唯一的一份,
不会按翻译单元各复制一份。只被一个控件用到的工具和常量仍留在该文件的匿名
命名空间。原来没人用的 MakeInsetRect 和 kCandidateItemGap 原样保留,分别放在
ControlsInternal.cpp 和 CandidateList.cpp 的匿名命名空间里。
using Microsoft::WRL::ComPtr 挪进内部头文件。Controls.cpp 已经没有剩余内容,
删除;ui/CMakeLists.txt 的源文件列表相应更新,Server 通过 add_subdirectory
引入 msimeui,不需要改。
非搬移改动只有两处:CreateCachedTextLayout 和 DrawLabel 的默认实参从定义挪到了
头文件里的声明上,DrawLabel 定义的形参因此由 clang-format 重新折行。
ui/scripts/check-boundary.py 仍然通过,没有新增对产品的依赖。用脚本比对了原文件
与拆分后全部文件的非空、非 #include 行多重集:除上述两个函数签名外,差异只有
新增的声明、命名空间包装、using 指令、#pragma once 和文件头注释。可用
git diff --color-moved 复核。
|
2026-09-27 17:28:16 +08:00 |
|
fanlusky
|
b5def6a2e3
|
refactor(server): 按职责拆分 ime_config.cpp
ime_config.cpp 有 4000 行,配置状态与加载、TOML 文本编辑、升级时的模板合并、
写回与通知 Server,以及上百对 GetConfigured* / SetConfigured* 全挤在一个文件里。
按职责拆成以下几个翻译单元:
- ime_config.cpp:全部配置全局变量的定义(顺序与初始值不变)、语音配置的读写锁
快照与发布、各项校验白名单、LoadImeConfig(读取与缺省值)、旧版语音配置迁移、
InitImeConfig / ReloadImeConfigIfChanged / InvalidateImeConfigWriteTime
- ime_config_toml.cpp:保留格式的键值替换与插入、跨行值定位、赋值遍历与解析、
读文件、原子写文件与 TOML 解析校验
- ime_config_template.cpp:凭证兜底重放、MergeTomlIntoTemplate /
MergeConfigIntoTemplate、损坏配置的留证与抢救、SyncConfigWithInstalledTemplate、
旧 ACP 乱码目录的配置找回
- ime_config_write.cpp:WriteConfiguredValues / WriteConfiguredValue 与
NotifyImeServer*(Aux 管道与窗口消息两条通知路径)
- ime_config_appearance.cpp:每页候选数、字体与字号、文字颜色、候选窗布局与跟随
光标、小窗 UI 后端、皮肤、预编辑样式、固定徽标、设置窗口保留时长
- ime_config_fonts.cpp:ResolveSystemFontFamilyForCss 与 GetSystemFontFamilies
- ime_config_theme.cpp:主题模式、各界面主题覆盖与 ResolveConfiguredTheme
- ime_config_input.cpp:输入方案与中日文模式、简繁、中英切换键、双拼与五笔方案、
上屏样式、辅助码、全拼纠错、模糊音、调频
- ime_config_keys.cpp:翻页键、方向键选词、以词定字、智能标点、配对标点、标点锁定
及发给 TSF 的载荷
- ime_config_toolbar.cpp:悬浮工具栏与光标状态角标
- ime_config_general.cpp:诊断日志、输入统计、中英与表情混输、候选翻译、云候选、
整句联想、工具模式与剪贴板历史
- ime_config_services.cpp:语音输入、AI 助手、腾讯 TMT、自定义翻译与小牛翻译
- ime_config_internal.h:上述文件之间共享的声明,只给 ime_config*.cpp 包含
纯搬移,不改行为:函数体、注释和格式都没动。原来在匿名命名空间里、现在
要跨文件用的符号放进 ime_config_detail 命名空间并在内部头文件里声明,
避免与 Server 其他翻译单元里的同名内部符号冲突;只被一个文件用到的仍留在
该文件的匿名命名空间,包括 g_session_backend、两个语音 token 槽位补写标记、
g_config_last_write_time、PublishVoiceInput、AiAssistantProviders、LoadImeConfig
和各文件自己的小工具。全局变量的定义都还在 ime_config.cpp,按原顺序排列,
初始化顺序不变;函数内的 static(字体缓存、DirectWrite 工厂、凭证键表、剪贴板
首次加载标记等)仍各自只有一份定义。取值范围常量、kFuzzyPinyinRuleKeys、
ConfigFileLock、ParseScheme 前置声明、模板文件名、TomlAssignmentVisitor 和
ConfigValueUpdate 原样挪进内部头文件。MetasequoiaImeServer、
MetasequoiaImeSettings 和测试工程都编译 ime_config.cpp,三处源文件列表一并
补上新文件。
非搬移改动只有两处:NotifyImeServer 的默认实参从定义挪到了头文件里的声明上;
测试里 kFuzzyPinyinRuleKeys 副本的注释改指 ime_config_internal.h。
用脚本比对了原文件与拆分后全部文件的非空、非 #include 行多重集:除上述
函数签名外,差异只有新增的声明、extern、命名空间包装、using 指令、
#pragma once 和文件头注释。可用 git diff --color-moved 复核。
|
2026-09-27 17:22:17 +08:00 |
|
fanlusky
|
69ee4fb49b
|
test(engine): 新增逐键耗时基准 bench_input_session
在真实词库上逐字母输入几串典型拼音(单词、短语、整句),每串前清空候选缓存,
按整句联想的几档设置统计每个键在引擎里的耗时(中位数 / p90 / 最大)。只构建、
不注册 ctest,也不选词、不写用户日志;用 METASEQUOIA_IME_DATA_DIR 指向数据目录
的副本运行。
本机一次结果(ms/键,冷缓存,5 轮):
设置 中位数 p90 最大
全关 0.10 0.19 1.04
词格 0.39 2.12 3.97
词格+Google 0.34 1.93 4.43
全开(含两档神经) 0.44 2.72 5.61
引擎查询本身不是打字卡顿的来源,性能工作应放在绘制、IPC 与 UI 线程等待上。
|
2026-09-27 17:02:04 +08:00 |
|
fanlusky
|
4b30659fca
|
ci: 单独构建并运行引擎自己的测试
Server 任务以 BUILD_TESTING=OFF 把引擎当库引入(避免 EXCLUDE_FROM_ALL 下的测试
找不到可执行文件),于是引擎的 23 组测试从来没在 CI 里跑过,只在开发机上跑。
server/tests 覆盖不到的行为——比如用户日志的连接管理、关闭它时要放开数据目录里
所有缓存句柄——只有这些测试能发现;#491 的一个回归就是这样在本地才测出来的。
新增 Engine 任务:按 engine/vcpkg.json 单独配置引擎、构建、再用
scripts/ci/test-engine.ps1 在与 Server 相同的锁定词库上跑 ctest。数据目录与
LOCALAPPDATA 都隔离到临时目录,布局与 test-server.ps1 一致。同步更新
server/CMakeLists.txt 里「引擎测试经 server/tests 覆盖」的旧说法。
本地按同样命令(不设 BOOST_ROOT,只靠 vcpkg 清单)配置、构建并运行:23/23 通过。
|
2026-09-27 16:54:20 +08:00 |
|
Lu Fan(SonnyCalcr)
|
a220b59a0e
|
Merge pull request #491 from fanlusky/perf/dictionary-io
perf: 选词学习写库挪出按键线程,本地模式复用词库连接
|
2026-09-27 16:41:18 +08:00 |
|
Lu Fan(SonnyCalcr)
|
6892d6cc26
|
Merge pull request #490 from fanlusky/refactor/split-large-server-files
refactor(server): 按职责拆分 event_listener / windows_webview2 / ime_windows
|
2026-09-27 16:41:00 +08:00 |
|
fanlusky
|
2c3546f823
|
perf(engine): 简拼、快捷短语、表情、颜文字查询复用词库连接
这四种模式每个键都新开一次 SQLite 连接,第一次 prepare 还要解析整个 schema
——msime.db 有两百多张分表。新增 local_modes/local_database,四个查询统一经它
打开只读连接:路径正是当前数据目录下的 msime.db 或 others.db 时共享一条连接,
其余路径(测试、工具)照旧每次独立打开、用完即关。
只共享这两个随安装包发布的库:msime.db 本来就被引擎整个会话持有,others.db 只在
Server 停止时由安装程序替换;保持其它文件常开会让删除它们的工具或测试在 Windows
上失败。close_default_user_database() 同时释放这些连接,调用方原本就用它在删除
或替换数据目录前放开文件。
引擎测试(本地单独构建,CI 不跑)23/23 通过;不加释放钩子时 input_session、
mixed_expressive_input_session、jianpin_input_session 会因目录删不掉而失败。
|
2026-09-27 16:10:01 +08:00 |
|
fanlusky
|
8f2c81dd0f
|
perf(server,engine): 选词学习的写库挪出按键线程
权重调整(AdjustCandidateRanking)和输入过的英文词(LearnEnteredEnglishWord)
原本和按键排在同一个 worker 队列里:每次选非首位候选都要在下一个键之前付一次
用户日志的提交和 fsync,触发阈值时还有 msime.db 的一次提交。磁盘忙或杀软扫描
时,这就是肉眼可见的卡顿。
- server/src/utils/serial_task_runner:单线程、按提交顺序执行、低优先级,Stop()
先执行完队列再退出,选词不会在退出时丢失。附单元测试。
- 两类写入改为投递给这个执行器。它们只调用 user_dictionary,不碰输入会话;唯一
的会话副作用——权重变化后清引擎候选缓存——经 EnqueueResetInputSessionCacheTask
回到 worker。删除对应的 TaskType 和 Task 字段。main 在 worker join 之后调用
ShutdownDictionaryWriter()。
- user_dictionary:默认日志连接改为每线程一条。FULLMUTEX 只保证单次调用安全,
保证不了事务:连接只有一个事务状态,写线程的 BEGIN IMMEDIATE..COMMIT 会把
worker 的读卷进来,worker 侧 UserDatabase 析构时还会把它回滚。
close_default_user_database() 递增代号,各线程下次访问时关闭旧连接。
- WorkerThread 取任务改为 move,不再每次复制整个 Task。
行为变化:选词后如果写入还没完成就立刻再打同一串,会短暂看到旧顺序;写完后
worker 清缓存,下一次查询即是新顺序。此前的“下一个键之前一定写完”换成了不阻塞
按键。
|
2026-09-27 16:09:51 +08:00 |
|
fanlusky
|
1e372c9c48
|
fix(engine): 读词库的连接等写锁而不是直接失败
msime.db 的三条读连接(全拼词典、双拼词典、quanpin_query 的 SqliteDb)都没设
busy timeout。设置页或学习写入提交的那几毫秒里,落在这段时间的查询直接拿到
SQLITE_BUSY,这一页候选就是空的。统一设 250 ms(与 english.db 读连接一致):
写锁只占一次提交,这点等待足够;大批量导入时最多卡一下,而不是出空页。
这也是把学习写入挪出按键线程的前提:之后读写会真正并发。
|
2026-09-27 16:09:28 +08:00 |
|
fanlusky
|
a8b5246852
|
refactor(server): 按职责拆分 ime_windows.cpp
ime_windows.cpp 有 4600 行,窗口创建与总窗口过程、候选窗定位与裁剪、WebView2
候选窗显示流程、候选窗消息处理、悬浮工具栏、托盘菜单、设置窗口和光标状态角标
全挤在一个文件里。按职责拆成以下几个翻译单元:
- ime_windows.cpp:共享状态与全局变量、系统明暗切换判定、宿主 cloak 与状态
描述(DescribeCandidateHostState、DescribeTrayMenuHostState 等)、
SyncHostWebViewBounds、ApplyConfiguredInputScheme / ApplyConfiguredShuangpinSchema、
窗口类与消息注册、CreateCandidateWindow、WndProc 分发,以及光标状态角标的
WndProcCaretStateWindow
- ime_windows_candidate_layout.cpp:光标锚点、四分之一屏宿主尺寸、装饰偏移、
翻转与裁剪记忆、ReanchorCandidateHostToPaintedCard、
ClipCandidateWindowToContent / SetWindowRgn、KeepCandidateCardInsideHostAndMonitor
- ime_windows_candidate_show.cpp:RefreshCandidateClipAfterPaint、
PlaceCandidateHostNearCaret、槽位测量扩展裁剪、FinishCandidateShowAfterSlots
和两遍测量的 FineTuneWindow
- ime_windows_candidate.cpp:WndProcCandWindow(显示 / 隐藏 / 移动消息、去重与
隐藏宽限、DPI 变化、状态切换转发、配置同步定时器、候选鼠标动作)
- ime_windows_toolbar.cpp:悬浮工具栏的配置尺寸、拖动后收回可见屏幕、
LayoutFloatingToolbar、显隐决策与绘制宽限、ApplyConfiguredFloatingToolbarSize、
WndProcFtbWindow
- ime_windows_menu.cpp:托盘菜单的 DIP 到物理像素换算与 WndProcMenuWindow
- ime_windows_settings.cpp:设置窗口的前台激活与重试、非客户区与最大化按钮、
鼠标输入转发、WndProcSettingsWindow
- ime_windows_internal.h:上述文件之间共享的声明,只给 ime_windows*.cpp 包含
纯搬移,不改行为:函数体、注释和格式都没动。原来在匿名命名空间里、现在
要跨文件用的符号放进 ime_windows_detail 命名空间并在内部头文件里声明,
避免与 Server 其他翻译单元里的同名内部符号冲突;只被一个文件用到的仍留在
该文件的匿名命名空间,包括只有候选窗消息处理用到的强制重排、隐藏宽限与
去重签名状态,以及只有设置窗口用到的激活重试计数。其余共享全局变量的定义
仍在 ime_windows.cpp。WEBVIEW_DIAG_LOGF 宏、定时器 ID 与时长常量、
FineTuneWindow 等前置声明和 SuppressCandidateDpiChange 原样挪进内部头文件。
非搬移改动只有两处:LogCandidatePositionAudit 和 PlaceCandidateHostNearCaret
的默认实参从定义挪到了头文件里的声明上;DescribeTrayMenuHostState 上方注释
里的文件名改成 windows_webview2_menu.cpp,菜单的生命周期事件在上一次拆分后
已经搬到那里。
用脚本比对了原文件与拆分后全部文件的非空、非 #include 行多重集:除上述
两个函数签名和那一行注释外,差异只有新增的声明、命名空间包装、using 指令、
#pragma once 和文件头注释。可用 git diff --color-moved 复核。
|
2026-09-27 15:49:35 +08:00 |
|
fanlusky
|
f4aba3bcfc
|
refactor(server): 按职责拆分 windows_webview2.cpp
windows_webview2.cpp 有 5500 行,小窗 WebView2 环境与 controller 的排队创建、
Topmost 分阶段提升、皮肤注入、候选窗脚本、托盘菜单、设置窗口和悬浮工具栏全挤
在一个文件里。按职责拆成以下几个翻译单元:
- windows_webview2.cpp:共享状态与全局变量、小窗 WebView2 environment 创建与
controller 排队重试、导航就绪门控、lazy-topmost 分阶段提升、内容截断回退、
DPI / RasterizationScale 辅助、可见性与状态查询,以及 GetAppdataPath、
InitSmallWindowWebviews、PrepareTrayMenuWebviewForShow、ShutdownWebviews
- windows_webview2_skin.cpp:读取页面 HTML、外部候选皮肤 CSS 注入与资源内嵌、
PrepareHtmlForWnds,以及候选窗、悬浮工具栏的布局 / 主题 / 外观应用
- windows_webview2_candidate.cpp:候选内容与测量区脚本、悬停锁与粘性卡片脚本、
候选槽位增量更新、测量 bounds、OnControllerCreatedCandWnd 与页面消息处理
- windows_webview2_menu.cpp:托盘菜单 controller 创建与页面消息处理、
SyncMenuFloatingToolbarToggle
- windows_webview2_settings.cpp:设置窗口的 DirectComposition 视觉树、
composition controller、设置页消息与 configUpdate 分发、
PostSettingsWindowState、PostSettingsConfig、InitWebviewSettingsWnd
- windows_webview2_toolbar.cpp:悬浮工具栏状态缓存与渲染脚本、
OnControllerCreatedFtbWnd 与页面消息处理、各个 UpdateFtb*
- windows_webview2_internal.h:上述文件之间共享的声明,只给
windows_webview2*.cpp 包含
纯搬移,不改行为:函数体、注释和格式都没动。原来在匿名命名空间里、现在
要跨文件用的符号(导航就绪标志、工具栏导航重试与绘制宽限标志、内容截断冷却
时间戳、JsonNumberAsDouble、HandleContentTruncatedMessage、
OnSmallWindowControllerSettled、NotifySmallWindowNavigationReady)放进
windows_webview2_detail 命名空间并在内部头文件里声明,避免与 Server 其他
翻译单元里的同名内部符号冲突;只被一个文件用到的仍留在该文件的匿名命名空间。
共享全局变量的定义仍在 windows_webview2.cpp。诊断日志的屏蔽宏、json 别名、
前置声明和 kTruncationSizeFactor 原样挪进内部头文件,每个拆出的文件都先包含
它,日志宏在各文件里仍是同样的空展开。
用脚本比对了原文件与拆分后全部文件的非空、非 #include 行多重集:原文件的
每一行都恰好出现一次,差异只有新增的声明、命名空间包装、using 指令、
#pragma once 和文件头注释。可用 git diff --color-moved 复核。
|
2026-09-27 15:38:01 +08:00 |
|
fanlusky
|
b6ca8bd8ba
|
refactor(server): 按职责拆分 event_listener.cpp
event_listener.cpp 有 5700 行,按键、候选组页、异步候选合并、选词、任务
队列和命名管道服务端全挤在一个文件里。按职责拆成以下几个翻译单元:
- event_listener.cpp:共享状态(特殊模式开关、UILess、译文缓存、异步请求
来源、状态快照、候选 UI 归属)、模式判定与候选窗显隐辅助,以及
CancelCloudCandidateRequest、RegisterStatusSnapshotWindow、
SendCurrentDataToClient、SendUiLessCompositionToClient、ClearState
- event_listener_worker.cpp:Task 定义、任务队列、顶字 Hide 抑制记账、
WorkerThread 分发循环、ReplayDeferredClientActivation 和各个 Enqueue*
- event_listener_pipes.cpp:管道握手与日志辅助、各管道监听线程、主管道
客户端线程、RegisteredPipeMonitorThread、关停时唤醒监听
- event_listener_candidates.cpp:候选组页与发布(BuildCurrentCandidatePage、
RefreshCandidatePageUi 等)、调频排序键、翻页扩展、PrepareCandidateList
- event_listener_async_candidates.cpp:云联想、AI、神经重排、英文、译文、
emoji、颜文字的 Apply* 合并
- event_listener_keys.cpp:按键分类、ApplyCompositionEditKey、译文副候选页、
五笔顶字负载、PublishRestoredCompositionCandidates、HandleImeKey
- event_listener_selection.cpp:ResolveCandidateItem、
WaitForCandidateRenderSync、ProcessSelectionKey
- event_listener_internal.h:上述文件之间共享的声明,只给
event_listener*.cpp 包含
纯搬移,不改行为:函数体、注释和格式都没动。原来在匿名命名空间里、现在
要跨文件用的符号放进 event_listener_detail 命名空间并在内部头文件里声明,
避免与 Server 其他翻译单元里的同名内部符号冲突;只被一个文件用到的仍留在
该文件的匿名命名空间。共享全局变量的定义仍在 event_listener.cpp。唯一的
非搬移改动是 UpdateCloudInput/UpdateEnglishInput/UpdateEmojiInput/
UpdateKaomojiInput/UpdateAiInput 的默认实参从定义挪到了头文件里的声明上。
用脚本比对了原文件与拆分后全部文件的非空、非 #include 行多重集:除上述
五个函数签名外,差异只有新增的声明、命名空间包装、using 指令、
#pragma once 和文件头注释。可用 git diff --color-moved 复核。
|
2026-09-27 15:26:19 +08:00 |
|
Lu Fan(SonnyCalcr)
|
6cfff373bb
|
Merge pull request #489 from fanlusky/perf/typing-latency
perf: 减少每次按键的重复工作(配置读取、D2D 绘制、译文重绘、引擎缓存)
|
2026-09-27 15:21:28 +08:00 |
|
fanlusky
|
fc6f6469f7
|
perf(engine): 上屏不再清空候选缓存,按键不再重复重建混合候选
- set_rescoring_context:每次选词都会换上文并清空 cache_、series_cache_ 和
segmentation_cache_,下一个词的前几键因此都是冷查询。上文只经神经重排
进入候选,没有生效的神经模型时与缓存无关,此时不清;之后打开神经重排
会经 set_sentence_association 清缓存。全拼、双拼同改。
- set_quanpin_autocorrect_types:Server 每键重新应用配置,值没变也会调用
update_mixed_candidates 复制整份候选列表。值不变时直接返回;构造时已把
初值同步给引擎。
|
2026-09-27 15:03:17 +08:00 |
|
fanlusky
|
356884bf0a
|
perf(server): 译文没有变化时不重绘候选页
ApplyCandidateTranslations 每次收到译文结果都 RefreshCandidatePageUi(true),
即使译文与已有的完全相同。候选页构建时本就从 g_candidate_translation_glosses
读译文,新按键的候选大多已在上一页查过,于是每键多出一次整页发布和绘制,
还会让随后的空格/数字选词多等一次渲染同步。
只有译文表确实新增、改写或删除条目时才刷新。
|
2026-09-27 15:03:17 +08:00 |
|
fanlusky
|
dff42f92df
|
perf(server): D2D 候选窗每次显示只绘制一遍
PlaceAndShow 同步 Present 之后,前面各个 setter 的 InvalidateRect 留下的
WM_PAINT 又把整张卡片(含阴影模糊)画一遍;宿主改尺寸时 WM_SIZE 也会在
显式 Present 之前同步画一遍。同步 Present 后 ValidateRect 丢弃过时的
WM_PAINT;PlaceAndShow 自己触发的 WM_SIZE 只调整交换链尺寸,由随后的
Present 出帧。
|
2026-09-27 15:03:17 +08:00 |
|
fanlusky
|
7bbb9042fc
|
perf(ui): 缓存卡片阴影、TextBlock 字体格式和候选列表文字度量
- Card:每帧为每道阴影新建离屏位图、画刷和高斯模糊特效。改为把全部阴影
合成到一张位图,按尺寸、圆角、DPI、阴影参数和 render target 复用;
尺寸不变的重绘(选中项移动、译文与云候选补齐)直接贴图。合成失败时仍
走原来的逐帧绘制。
- TextBlock:每次测量都新建 IDWriteTextFormat,自定义回退字体还要把系统
回退表整份复制进新 builder。格式与文字、光标无关,改为只在字体、字号、
对齐或回退字体变化时重建。
- CandidateList:一帧里会测量多次(自然尺寸、保留尺寸、按最终宽度排版),
每次都为每段文字新建 DirectWrite layout,一页九项每键约一两百个。文字
宽高只取决于文字、字号、宽度和字体,改为记忆化,字体或外观变化时清空,
超过上限整体重来。空的辅助码和译文不再创建 layout。
|
2026-09-27 15:03:16 +08:00 |
|
fanlusky
|
f87809cf59
|
perf(windows): 换语言热键不再每次按键重读 config.toml
_MatchChordInputHotkey 和 _MatchModifierReleaseHotkey 在 OnTestKeyDown、
OnKeyDown、OnTestKeyUp、OnKeyUp 里都要调 ReadConfiguredSwitchLanguageHotkeys,
每次都查注册表 DataDir、再逐行解析整个 config.toml,一次按键最多四遍,
全在宿主 UI 线程上,不在组合里的按键也照付。
配置路径每个进程只解析一次(环境变量固定,DataDir 只在重装时变);解析
结果按文件修改时间和大小缓存,最多每秒检查一次时间戳,设置页改了热键一秒
内生效。缓存全部常量初始化,路径对象按需分配且不释放,不引入全局构造器。
|
2026-09-27 15:03:16 +08:00 |
|
Lu Fan(SonnyCalcr)
|
e3b4f432fe
|
Merge pull request #488 from fanlusky/fix/candidate-sticky-per-input
fix(server): 候选卡片只在同一输入内保留尺寸,换键或翻页后不再留空白
|
2026-09-27 14:40:10 +08:00 |
|
fanlusky
|
e829a1b1ab
|
fix(server): 候选卡片只在同一输入内保留尺寸,换键或翻页后不再留空白
3a7beb18 让卡片在整次组合内只增不减,自然尺寸低于保留值 70% 才收回。
这本是为了吸收同一输入下陆续到达的译文、云/AI 候选和混输英文,但换键
后候选本就换了一页,也沿用了旧尺寸:gw(4 行)→ gw'r(3 行)时自然高度
约为 80%,卡片保持旧高度,底部留出一行空白;翻到末页同理。
保留尺寸改为按输入计算,键为 preedit(含光标标记)+ 页码。键不变时仍只
增不减;换键、移动光标或翻页后从自然尺寸开始。WebView2 由 C++ 把键传给
MsimeStickyCandidateCard,D2D 用 stickyCardKey_,两端逻辑一致。
|
2026-09-27 14:36:48 +08:00 |
|
Lu Fan(SonnyCalcr)
|
3e7a8cc19d
|
Merge pull request #487 from fanlusky/fix/y-mode-english-flicker
fix(server): Y 模式临时英文查询返回前不再绘制占位页
|
2026-09-27 12:48:51 +08:00 |
|
fanlusky
|
661c622c49
|
fix(server): Y 模式临时英文查询返回前不再绘制占位页
中文模式下用 Y 触发临时英文时,PrepareCandidateList 会先发布一个只有原样输入
这一行的占位页并请求显示,英文词典的结果回来后再换成完整一页。每次按键卡片都
先缩成一行再撑开,高度明显闪动;缩小幅度远超 30%,会话内只增不减的规则也拦不
住。英文模式此前只处理了「页面为空」这一种占位形态。
改成由 PrepareCandidateList 在每次发出专用英文查询(英文模式、Y 模式)时标记
「回应未到」,ApplyEnglishCandidates 应用回应时清除,期间的显示请求交给回应去
触发。英文 worker 对非空输入一定会回应。造词和译文页状态下回应会被拒绝,不做
推迟;ClearState 也会清除标记。
|
2026-09-27 12:25:55 +08:00 |
|
Lu Fan(SonnyCalcr)
|
cdd47db07e
|
Merge pull request #486 from fanlusky/fix/tray-menu-corner-radius
fix: 托盘菜单项圆角与外框同心对齐,放宽 D2D 菜单间距
|
2026-09-27 11:40:11 +08:00 |
|
fanlusky
|
1348ca8eb7
|
fix(server): D2D 托盘菜单项圆角与外框同心对齐,放宽行高与内边距
|
2026-09-27 11:19:07 +08:00 |
|
fanlusky
|
4ae5b842b7
|
fix(ui-html): WebView2 托盘菜单项圆角与外框同心对齐
|
2026-09-27 11:19:06 +08:00 |
|
Lu Fan(SonnyCalcr)
|
b322b32321
|
Merge pull request #485 from fanlusky/fix/webview2-commit-render-sync
fix(server): WebView2 选词不再白等渲染同步超时
|
2026-09-27 10:32:22 +08:00 |
|
fanlusky
|
42cff6de4a
|
fix(server): WebView2 选词不再白等渲染同步超时
WebView2 下几乎每次空格/数字选词都等满渲染同步上限(日志 waited_ms 82-94,
rendered 恒比 current 小 1),体感为上屏顿一下。
每个按键 worker 会以相同内容发布两次页面:第一次走 content-only,回显在
异步 DOM 回调里;第二次命中 dedup,立即回显新代数。随后迟到的 DOM 回调把
回显改回旧代数,选词就一直等到超时。D2D 同步回显,不受影响。
- 渲染回显代数只进不退(AdvanceRenderedCandidateGeneration),迟到的旧回显
不再覆盖新回显。
- folded-inflight 分支补上回显:进行中的 FineTune 只回显它捕获的那一页,
之后折叠进来的页面此前永远没有回显。
|
2026-09-27 10:24:50 +08:00 |
|
Lu Fan(SonnyCalcr)
|
4192ce8a05
|
Merge pull request #484 from fanlusky/fix/caret-state-below-candidate
fix(server): 状态提示窗层级置于候选框之下,避免打字时重叠闪烁
|
2026-09-27 09:56:19 +08:00 |
|
fanlusky
|
7e4ffc2dcf
|
fix(server): 状态提示窗层级置于候选框之下,避免打字时重叠闪烁
状态提示窗每次显示和跟随光标移动都用 HWND_TOPMOST 把自己提到置顶带最上层,打字时光标持续移动,会反复盖过候选框,两窗重叠处出现闪烁。改为插在候选框宿主窗口正下方;仅在两者处于同一 z-band 时这样做,否则保持原来的 HWND_TOPMOST / HWND_TOP。
|
2026-09-27 09:52:30 +08:00 |
|
Lu Fan(SonnyCalcr)
|
49748ea9bc
|
Merge pull request #483 from fanlusky/fix/candidate-flicker
fix(server): 减少候选框闪烁与右缘越界
|
2026-09-27 09:34:03 +08:00 |
|
fanlusky
|
6a172d448d
|
fix(server): D2D 候选宿主窗口不再越过当前显示器工作区
卡片本身已经被夹在工作区内,但宿主窗口还带着阴影内边距和 64px 取整余量,贴近
右缘或下缘时会伸到相邻显示器上。DWM 分别应用窗口移动和交换链呈现,而且先后不
固定,某一帧可能把新卡片配上旧的宿主原点(或反过来),落在卡片外侧的内容就闪
到了旁边的屏幕上。诊断日志显示每次放置时卡片都在屏幕内(右缘 3837 < 3840),
但宿主一直延伸到 3927 左右。
宿主的宽高现在截到工作区的右缘和下缘,不管 DWM 怎么配对,都不会在光标所在显示器
之外画出任何东西。卡片完整保留,只裁掉伸出边缘的那部分阴影。移窗与呈现的先后
排序保留,它只决定哪种中间帧更常见。
|
2026-09-27 09:13:15 +08:00 |
|
fanlusky
|
dcf1b6e2d3
|
fix(server): D2D 候选窗隐藏时清空交换链,避免重新显示时闪出上一张卡片
Hide() 只把宿主窗口 cloak 起来,交换链里还留着最后一帧。下次显示时先呈现、再
取消 cloak,但 DWM 可能在拿到新帧之前就先合成了已取消 cloak 的窗口,于是有一帧
在新位置画出了上一张卡片。上一张卡片较宽时,新的窄卡片贴屏幕右缘,位置更靠右,
旧画面就会越过边缘落到右侧扩展屏上。删光输入后重新打字、或上屏后开始下一次输入
时最容易出现。
隐藏前先呈现一帧透明画面,交换链里不再留下旧卡片。同时在 candidate-d2d place
诊断日志里补上最终宿主位置、先移动到的位置、卡片像素矩形和显示器工作区。
|
2026-09-27 09:06:49 +08:00 |
|
fanlusky
|
3a7beb18ab
|
fix(server): 同一次输入内候选卡片只增不减
译文、云/AI 候选、混输英文等内容是陆续到达的,每一批都让卡片在两帧之间改变
宽高。每一帧本身画得完整,但尺寸来回变化看起来就是闪烁。
同一次组合内卡片只增大;自然尺寸低于保留尺寸的 70% 时(页面确实变小了)才跟着
收回,候选窗隐藏后重新开始。D2D 给 card 设置 MinWidth/MinHeight 后重新排版;
WebView2 在更新内容的同一个 JS 任务里给 #realContainer 和 #measureContainer
设置最小宽高,绘制前就是最终尺寸,原生区域测量的也是同一个盒子。两端的收回比例
常量互相注明了对应关系。
|
2026-09-27 09:00:47 +08:00 |
|
fanlusky
|
444b015a02
|
fix(server): 候选译文改为跨页缓存,换页时不再先丢后补
译文原先只是当前页的查询结果:每次按键换页、页面为空(英文模式两次按键之间)
或一次输入结束都会清空。新页第一帧因此不带任何译文,行宽先缩回去,查询返回后
再撑开,候选框在两帧之间来回改变大小。
现在 g_candidate_translation_glosses 按 TranslationIdentity 缓存,跨页、跨组合
保留,新页第一帧直接带上已知译文。上限 2048 条;关闭译文或翻译服务商开关变化
时清空;权威查询确认没有译文的词会从缓存中剔除。
|
2026-09-27 09:00:47 +08:00 |
|
fanlusky
|
5fc9485abf
|
fix(server): D2D 候选窗按移动方向决定移窗与呈现的先后
DWM 分别合成窗口移动和交换链呈现,窗口可见时总有一帧只完成了其中一步。原先
总是先移窗后呈现:卡片在屏幕右缘变窄时要向右挪,上一帧更宽的画面会先出现在新
位置,越过屏幕边缘落到右侧扩展屏上,下一帧再弹回来。
现在按轴分别处理:向左或向上移动的轴先移窗再呈现,向右或向下移动的轴先呈现再
移窗,这样中间帧不会超出新旧两张卡片覆盖的范围。DPI override 提前到调整窗口
尺寸之前,因为 WM_SIZE 里已经会呈现一次。
|
2026-09-27 09:00:35 +08:00 |
|
fanlusky
|
7802777d06
|
fix(server): 英文模式查询返回前不再绘制空候选页
专用英文模式在词典查询返回前会发布一个空页。原先照常请求显示,卡片先缩成只剩
预编辑行,查询结果回来后再撑开,每次按键都会闪一下,D2D 与 WebView2 都受影响。
英文 worker 对非空输入一定会回应(无匹配时回退为原样输入),回应本身会请求显示,
因此查询进行中、页面为空时跳过这次显示请求。选词仍按已绘制的页面代次等待,
不会上屏未显示的内容。
|
2026-09-27 09:00:19 +08:00 |
|
Lu Fan(SonnyCalcr)
|
c894eb1062
|
Merge pull request #482 from fanlusky/fix/sentence-badge-source
fix(engine,server): 整句来源标签只挂在整句联想候选上
|
2026-09-27 08:28:20 +08:00 |
|
fanlusky
|
57dd0fc2f3
|
fix(engine,server): 整句来源标签只挂在整句联想候选上
候选窗按 CandidateSource 挂〔Trigram〕/〔Unigram〕,但 Generated/Fallback 同时被
原样上屏兜底、英文模式原文、日期、Unicode、假名、译文义项等合成候选借用,导致
输入 E 等特殊模式前缀时原样候选被误标为〔Unigram〕。
WordItem 新增 sentence_association 标记,只由 Google 解码器整句(全拼、双拼)与
kenlm 词格整句置位;Server 挂标签时同时要求来源与标记。source 本身不变,落库与
学习策略不受影响。
|
2026-09-27 08:25:51 +08:00 |
|
Lu Fan(SonnyCalcr)
|
390c941b32
|
Merge pull request #481 from fanlusky/fix/selection-timeout-replay
fix(windows,server): 选词回复超时不再上屏未显示的候选
|
2026-09-27 00:51:52 +08:00 |
|
fanlusky
|
d852a05687
|
fix(windows,server): 选词回复超时不再上屏未显示的候选
电池模式下数字选词常上屏与所见不同的候选。链路是:TSF 只等 50ms
回复;Server 在回复前同步写调频,渲染同步 30ms 到点后按未绘制的页面
选词;超时后 TSF 断管重放按键,Server 又调一次频。
- server:选词调频(拼音/五笔/英文)改为 worker 队列任务,在回复之后、
下一键之前执行,不再占用回复时间。
- server:渲染同步改为条件变量,绘制回显即唤醒;上限 30ms 提到 80ms。
- windows:上屏类回复等待 300ms;请求已送达仍超时时视为投递不确定,
只重建前缀并丢弃上屏键,不再无条件重放。
- server:同一选择 3s 内在新连接上再次到达时跳过调频,避免重放重复计数。
|
2026-09-27 00:47:36 +08:00 |
|