fanlusky
5658067329
fix(windows): 跳过光标未变的候选窗移动,避免微信下 Main 管道写满
...
微信每按一个键会触发十余次 layout change,大多光标不变,每次都发一帧
MoveCandidate。Main 管道以 PIPE_NOWAIT 写入,入站配额被占满时 WriteFile
返回成功但写入 0 字节,被当作传输失败而重连并取消组合,表现为按键被吞、
输入 2-3 个字母后输入框被清空。
- 记录最近一次成功送达 Server 的光标,未变时不再发送 Move
- Show/Move 发送如实返回结果,失败的帧不会抑制下一次发送
- Main 管道写失败时记录诊断日志
2026-09-29 15:47:49 +08:00
fanlusky
995174af47
feat(server): 造词时 Esc 可只清除未选拼音、保留已选词
...
新增 input.escape_keeps_selected_word(默认 false)。开启后,造词中按 Esc
只清掉还没选的拼音,保留已选的词,候选窗只显示该词作为 preedit;
再按一次 Esc 整体取消(小狼毫风格)。R 模式下仍整体取消。
- server:HandleCreatingWordEscape 以 CompositionRestored 帧回复
- windows:_HandleEscapeCancel 读取回复并应用,超时或不支持时回退到原取消
- ui-html:设置页「输入」增加开关
- 配置模板、两个设置宿主同步新增该键
2026-09-29 01:58:38 +08:00
fanlusky
1c1798c26b
fix(windows): 系统 Ctrl+Space 切换中英文时显示光标状态提示
...
Ctrl+Space 是系统的中文输入法开关,不经过本 DLL 的 preserved key,系统直接改写
OPENCLOSE compartment,因此从未发出光标状态提示事件。在 OPENCLOSE 回调中,对非本
DLL 抑制写入的状态翻转,且此刻仍按着 Ctrl+Space(未按 Alt)时,按用户切换发送提示;
Server、语言栏和宿主写入不受影响。
2026-09-28 01:29:20 +08:00
fanlusky
44caaefcec
perf(windows): 健康路径去掉多余的排空投递,删除死掉的 50 ms 硬读取
...
健康路径上 OnKeyDown 把键入队后立即同步排空(Excel 首键修复,保留),但入队时
仍先投递一条 WM_DrainDeferredKeyDown。同步排空已经把这颗键取出:它要么还在
飞行中、要么已完成,那条消息运行时只会看到「有键在飞」或「队列为空」而直接
返回;后面排队的键由这颗键完成时自己安排排空。所以现在入队时不再投递,同步
排空之后只在键仍留在队列里(焦点会话未连上或刚开了重置)时才补一次投递,
这两种情况与原先的行为一致。每个健康按键省掉一次 PostMessage 和一次空转的
窗口消息。PeekNamedPipe 存活探测不动。
同时删除已确认没有调用者的硬读取路径:ReadDataFromServerViaNamedPipe、
单参数的 TryReadDataFromServerPipeWithTimeout(50 ms 超时即拆管道),以及
ClearNamedpipeDataIfExists——它唯一存活的调用不带 force,函数体什么也不做。
2026-09-27 19:05:59 +08:00
fanlusky
f7064eb7c7
refactor(windows): 翻页与高亮移动的回复缺失改为软错过
...
WM_AsyncServerCandidateKey 对所有候选键都用 300 ms 提交预算,回复缺失即按
「可能已上屏」处理:丢键并完整重置传输。可对 PageUp/PageDown/Up/Down/Tab(含
Shift+Tab)来说,Server 只翻页或移动高亮,回复本来就只是确认(处理函数原先的
注释也这么写),永远不会上屏。把一次慢确认升级成拆管道、清组合,代价完全不对等。
现在这几颗键走普通的 50 ms 软读取:超时只是软错过,不拆管道、不清队列,迟到
的回复按 request id 缓存;只有真正的读失败/坏帧(TransportUnavailable)才按传输
失败处理。回复内容一律不应用,因为 Server 对这些键从不提交文本。
逗号、句号、括号、减号/等号(可配置翻页,也可能以词定字或随标点上屏)、
Ctrl+Enter 译文上屏和选词键仍保留 300 ms 提交预算与「歧义即丢键 + 传输重置」。
判定放在 IsCandidateNavigationOnlyKey 里并补了单元测试。
2026-09-27 19:02:17 +08:00
fanlusky
5f05c15a52
refactor(windows): 去掉按键回放,失败一律丢键并按策略重置
...
回放随 4f2fe01d 引入,当时唯一点名的动机是 Win+. 往返后候选窗拉不起来;
之后 #259、#404、#427 都是回放本身造成的(闪烁、重复上屏、重试循环),
PR #275 的讨论也已经指出整缓冲区回放在架构上就是错的。维护者的决定是:
会话在组合中途断掉时清空组合、从空开始,失败那一刻已被吃掉并排队的键
一律吞掉,既不交还宿主也不重发。偶尔需要重打一段拼音,换来的是不再闪烁、
不会双上屏、没有重试循环。
删除的机制:恢复前缀 _deferredAppliedPrefix、_RefreshDeferredRecoveryPrefix、
_ArmDeferredRecoveryForTransport、完成时追加/改写前缀的逻辑、重试计数与
kMaxDeferredKeyReplayAttempts,以及只为二次提交存在的 preservedApplied 分支。
_RetryDeferredKeyReplay 与 _DropAmbiguousDeferredKey 合并为 _FailDeferredKey:
永不重新分发这颗键,由 DeferredKeyFailurePolicy 决定代价——
- Stale(焦点 token、组合纪元、重置 token 已被取代):静默丢键,不再发起重置;
- Resync(宿主拒绝 edit session、RequestEditSession 失败、异步投递失败):
丢键、吞队列、本地取消组合并在当前 token 上让 Server 清组合;
- Transport(写失败、回复读失败、TransportUnavailable、DefinitelyNotSent、
DeliveryAmbiguous、提交回复 300 ms 超时、worker 断开):丢键、吞队列、完整重置;
- Offline(离线原始通道里的宿主失败):丢键、吞队列,保留本地组合。
DeliveryAmbiguous 绝不重发;DefinitelyNotSent 也不再重排队,因为已经没有回放。
保留:FIFO、屏障与投影、所有 token/纪元/请求号校验、先重置再排空、离线原始
通道、300 ms 提交预算、Excel 首键同步排空、失焦 300 ms 延迟、连续 6 次失败
拉起 Server(计数只来自排空路径的 Ensure 失败与离线通道,回放从未单独喂它)。
2026-09-27 18:58:48 +08:00
fanlusky
18cf3efe0a
refactor(windows): 引入按键失败策略与本地重同步
...
新增 DeferredKeyFailurePolicy.h:把「按键为什么没能应用」映射成四种代价,
Stale(只丢这颗键)、Offline(离线原始通道,丢键并吞掉队列,保留本地组合)、
Resync(管道健康,但本地组合与 Server 可能不一致)、Transport(传输坏了或
状态未知,走完整重置)。纯函数,不依赖 TSF/COM,配套 tests/deferred_key_failure_policy.cpp。
新增 _RequestLocalResync:复用本地重置闸门取消组合,保持「先重置再排空」的
顺序,但不轮换 focus token、不关任何管道;闸门重新打开时在同一个 token 上补发
HideCandidateWnd,让 Server 清掉自己的组合(闸门关着时取消路径发出的 Hide 会被
挡下,所以必须补发)。重同步期间不清 _workerCommitReady,否则 WM_IpcReconnect
会把一次重同步等成 150 ms 超时并升级成完整重置。
_ResetSessionAfterFailure 统一处理失败后的会话丢弃:清空延迟队列(队列里的键
是按即将丢弃的组合分类的,一律吞掉,既不交还宿主也不重发),保留退格长按
守卫(#347),再按类别触发重置;重置期间把投影设为空组合,重置结束且队列为空
时释放投影,健康路径(包括 Excel 首键的同步排空)不会被残留的屏障挡住。
非按键路径里原本走完整传输重置、但管道其实没坏的几处改走重同步:OnEndEdit 的
组合属性检查与 EndComposition 失败、OnSetFocus 文档不匹配、WM_CheckGlobalCompartment
切换提交失败、_PostServerTextDelivery 投递失败。
2026-09-27 18:50:07 +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
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
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
fanlusky
b3b3b3303c
feat(windows): 焦点切换到另一个输入框时提示当前中英文状态
...
新增开关 general.caret_state_indicator_on_focus(默认关闭,需同时开启光标
状态提示)。
- DLL 把以下情况视为“换了输入框”:焦点窗口变化(Win32 编辑框、对话框、
其他应用各有自己的窗口);焦点经过一段没有可编辑文档的间隙后落到可
编辑文档上(Chromium、WPF 等单窗口宿主在字段之间就是先失焦再聚焦);
焦点会话真正结束后重新开始。Chromium 打字时在两个可编辑文档之间直接
替换文档管理器,不会触发。从 Win+. 面板返回不计入;线程焦点在真正的
应用切换后返回计入。一次切换的多次回调由定时器合并为一次提示;组字
过程中、以及没有光标的控件都保持静默。每次判断在 TSF 诊断日志里记录
判定标志,不含任何文本
- 协议复用 IMESwitch:wch 改为 FanyImeCaretStateTrigger(UserToggle /
CapsLockEdge / FocusEntered),CapsLockEdge 保持首发时的 VK_CAPITAL
取值,Server 对未知值按 UserToggle 处理
- Server 在开关打开时显示当前的有效模式(Caps Lock 开启时为“英”);
配置项同步写入两份模板和两个设置窗口宿主的读写映射
- 设置页在光标状态提示卡片中增加“切换输入框时提示”开关
2026-09-26 17:09:34 +08:00
fanlusky
597d087580
fix(server): 光标状态提示仅由用户快捷键触发并收紧协议边界
...
- DLL 只在 Shift/Ctrl 切换、Shift+Space、Ctrl+.、Caps Lock 与
Ctrl+Shift+F 处显式请求提示;Server 下发的模式同步、宿主转换模式
同步和激活初始化不再弹出提示,移除 compartment 回调里的推断逻辑
- 恢复文档切换时候选窗的 OnKillThreadFocus,改用 COM 身份比较
- 能力位改名为 CaretStateIndicator,DLL 仅在 Server 确认后发送切换
事件;Server 端这些事件只驱动提示,不再改写工具栏或状态快照
- 标点提示的中英模式由 DLL 随 PuncSwitch 携带
- UILess 宿主不显示提示,会话转为 UILess 时隐藏已显示的提示
- Ctrl+Shift+F 的锚点就地读取,不再覆盖 Global::Point,且只采信
协商了该能力的客户端
- 撤回对所有包默认 point 的修改;提示窗口创建失败不再阻断 Server
启动;提示内容改为结构化 CaretStateBadge
- Caps Lock 在同一按键重复进入 TestKeyDown 时只处理一次
2026-09-26 13:58:22 +08:00
fanlusky
dc7cfa0354
Merge branch 'develop' into feat/issue-229-ime-status-indicator
2026-09-26 13:58:03 +08:00
Jia Sui
c06dc17ae1
feat(server,windows): 造词中的退格优先撤销最近一次选词
...
把"退格撤销上次选词"从旧的两条窄触发(剩余输入只剩一个字符、或光标
正好停在这个词后面)放宽为造词态下的默认行为,对齐 Rime/微信:只要
造词还活着且有可选回的快照,首个退格先撤销最近一次选词,剩余输入
长度与光标位置不再参与判定。
- 选后编辑锁(selected_before_editing):选词后又输入了新字符,退格
继续删除这些新输入,而不是把选词从脚下抽走;只标记最新快照,与
Rime 一致。空输入例外——没有字符可删时退格只能是撤销,否则会退回
#35 的整句丢弃。
- 恢复改为前插:被选词消耗的拼音插回未删除的后缀之前,光标随恢复
长度平移("输入变长、光标不动"),不再整串覆盖 raw 而丢掉后缀。
- 撤销后重新高亮:选词快照记下所选候选的绝对位置,撤销重建候选页
后把高亮放回同一项(一次性消费),下次按键可直接改选,不用再找。
- 客户端契约拆成两层:DLL 只凭造词镜像(word_for_creating_word)
挂起等待回复,看不到 Server 侧的快照与编辑锁,因此新增
HasRetreatBackspaceShape 描述客户端可见形态,Server 对形态内每个
退格都必须答帧(撤销、锁定删除或空历史空操作),否则挂起白烧满
50ms 超时;ShouldRetreatCreatingWordSelection 只在真能改写状态时
为真。
- 撤销改为纯状态操作,引擎序列、候选重建与光标前缀由尾部统一执行
一次,替换原先每次撤销都额外跑一遍完整候选查询的 helper。
- DLL 侧:已消耗请求槽位的回复(或光标 0 处的 Normal 应答)之后
丢弃 request id,让 worker 回退本地读取,而不是为已消失的槽位
再烧一个 50ms 超时。
Signed-off-by: Jia Sui <jsfaint@gmail.com >
2026-09-24 20:52:12 +08:00
AaGMix
a855a9c7f4
feat(windows): 发送光标状态切换事件
2026-09-22 21:20:07 +08:00
Jia Sui
536d0a95ef
feat(windows): 五笔四码后继续输入不丢字(顶字)
...
四码(不论是否唯一)之后继续敲字母时,先上屏该码的首选候选,再把这个字母
保留为下一次组合的开头。根因是两侧对「组合里有几个字母」认知不一致:TSF 的
本地按键缓冲跟着敲键涨(raw 预编辑样式下 preedit 就是它),服务端的五笔 raw
被引擎裁在四码;空格提交时组合文本被整段替换成候选并清掉本地缓冲,多敲的
字母随之丢失。
- 新增 worker 消息 CommitCandidateAndContinue(27),payload "<consumed>\t<text>":
TSF 按服务端给出的消费数裁**自己**的组合缓冲,而不是套用服务端按旧视图算好
的剩余串——快打时用户已多敲的字母因此不会被覆盖
- TSF 处理序列全部复用既有原语:_InsertTextToComposition + _HandleCompleteCommitFirst
真提交,_StartComposition + AddVirtualKey + _HandleCompositionInputWorker(NO_REQUEST_ID)
重建余下字母的组合;无组合时退化为直写
- 服务端顶字不看自动上屏开关:开关只决定唯一码要不要多敲一键才上屏,
不决定丢不丢输入;自动上屏那条路也改用同一消息,顺带收掉快打竞态
- 引擎新增 wubi_four_code_is_complete(完整四码、码表作答、不要求唯一;
空码不算——没有候选可提交时不触发顶字)
已知限制:光标在组合中间时的改码行为维持现状;空码继续敲字母仍会被忽略。
2026-09-22 09:23:25 +08:00
fanlusky
94df9a7917
fix(stats): 修复统计功能审查发现的 8 个问题
...
- Server 退出时唤醒统计管道监听线程,避免从未有客户端连接时 join 永久阻塞
- DLL 发送批次时对 ERROR_FILE_NOT_FOUND 重试,覆盖服务端重建管道的窗口
- 新增 StatisticsEnabledChanged(26) 推送开关,DLL 侧在采集入口直接短路:
关闭时不读 range、不分类、不开管道
- stats_meta 的 schema_version 写入正确的键名(此前键与值都是 "1")
- dropped_count 仅在实际发送的批次中消费,空批次不再吞掉丢弃计数
- _CaptureCompositionStats 改为遍历 composition range 的克隆,避免移动宿主的活动区间
- FanyImeStatsEvent 显式声明 reserved 尾部,不再把未初始化的填充字节写入管道
- 透传统计去重标记在抑制一次后即消费,避免同一 tick 内的连击被漏计
2026-09-20 20:34:50 +08:00
Jia Sui
ffcd1aee28
feat: 输入统计(DLL 采集、Server 聚合存储与设置页展示)
...
- windows: 三上屏出口 + OnTestKeyDown 直通字符采集,修复半角数字恒零
- engine: 统计 Web 消息契约(statsRequest/statsResponse)
- server: 统计管道接收、聚合、SQLite 存储、保留策略与每日自动清理
- ui-html: 设置页「统计」模块(卡片、热图、明细、开关、清理)
- docs: PRIVACY / README 改为内置可选统计
2026-09-20 19:08:16 +08:00
fanlusky
1640cb353f
fix(punctuation): 终端里智能标点改写恢复 SendInput 兜底
...
重构智能标点(#413)之后,「英文/数字后面连按两次同一个英文标点变回中文
标点」在 Windows Terminal 里失效:输入 `1.` 再按一次 `.`,屏幕上变成
`1.。`。
#413 把原来的 SendInput(合成 VK_BACK + KEYEVENTF_UNICODE)改写换成了编
辑会话里的 ITfRange::SetText 原地改写。原地改写要求宿主背后有一份真正的
文档文本存储;终端类宿主的 TSF 上下文只是一层代理,不保存已上屏的文字,
于是 ShiftStart(-1) 拿不到前一个字符,代码退回 fallback
insertChinesePunctuation(),把中文标点追加在 ASCII 标点后面而不是替换掉
它。SendInput 走的是系统输入队列,与宿主实现无关,这也是老方案在哪儿都
能用的原因。
两条路并存:先试原地改写,失败才排队 SendInput 改写,两条都不成立时才是
原来的 fallback。区分宿主不靠进程名,而靠写入前把待改写字符读回来校验——
空壳存储会照单全收 ShiftStart 和 SetText 并报告成功,只有把字符交还回来
的存储才真的能被原地改写。
SendInput 改写从编辑会话里 PostMessage 出去,执行前校验焦点会话 token、
前台窗口和 500 ms 期限,任一不符就放弃,而不是对着已经挪走的光标盲目退
格;每个清除 action 的地方一并取消排队中的改写。合成事件带自生成标记,本
输入法自己的按键钩子据此放行。
顺带补上一个洞:FUNCTION_SMART_PUNCTUATION_CONVERT/REVERT 在 replay 与
prevalidated 路径上没有处理者,按键会被吃掉后静默丢弃,现在走同一个编辑
会话。
两条改写路径各加一行诊断日志,走已有的 tsf_diagnostic_log 开关,默认关
闭:宿主用的是哪种存储从外面看不出来,出问题时不用靠猜。
2026-09-19 17:21:30 +08:00
Lu Fan(SonnyCalcr)
4dea1cd12f
Merge pull request #420 from metasequoiaime/feat/ctrl-arrows-segment-caret
...
feat(input): 组词内按分词的光标与删除(#35 / #187 / #347)
2026-09-18 12:35:52 +08:00
Jia Sui
2576986881
feat(windows): Ctrl+左右键按分词移动光标
...
组词中 Ctrl+Left/Right 按一个字的拼音(音节段)移动光标,
与 Ctrl+Backspace 的删除粒度共用同一套引擎分段边界。
- 闸门归纳为 SegmentEditFunction(code, modifiers):Ctrl-only + 组词存在,
VK_BACK/LEFT/RIGHT 分别映射到 segment 函数;三处判据与延迟投影共用
- 新增 FUNCTION_MOVE_LEFT_SEGMENT / FUNCTION_MOVE_RIGHT_SEGMENT,
编辑会话只按 CompositionRestored 的 caret 移动本地光标,不重建 buffer
- Server 用 segment_raw_boundaries() 取前/后边界,能力位与非 UiLess 时回包;
非拼音/本地模式/UiLess/旧端仍按单字符 ±1 回退
Refs #187
2026-09-17 23:36:34 +08:00
Jia Sui
b1ec3ab1e0
feat(server): Ctrl+Backspace 按分词删除前置拼音(#187)
...
组词中 Ctrl+Backspace 删除光标前的一个分词单元:剩余拼音非空时删
最后一个字的拼音(音节段),剩余拼音为空时删除最后一段已选分词。
engine:
- InputSession::segment_raw_boundaries() 作为按段删除/移动的唯一分段来源:
全拼把纠错显示串的分隔符映回 raw 下标,双拼与 pinyin_segmentation 共用贪心谓词,
2 字辅助码自成一段;非拼音方案与本地模式返回空表示无单位模型
server:
- IsSegmentBackspaceKey(Ctrl-only)、整段丢弃 / 边界截断 / 单字符回退三分支,
截断点残留分隔符清理
- 复用 CompositionRestored 回包(含 caret),仅协商能力位且非 UiLess 时发送;
新出现「只剩已选汉字、无拼音」的中间态
windows:
- 两处 Ctrl 闸门对组词中的 Ctrl+VK_BACK 放行,新增 FUNCTION_BACKSPACE_SEGMENT
与 _HandleCompositionBackspaceSegment(读回包重建,失败回退单字符)
- 空 raw 状态渲染、终态清理 word_for_creating_word,延迟投影失效处理
Closes #187
2026-09-17 23:01:31 +08:00
fanlusky
bc37ac9a9e
feat(installer): 安装时可自定义数据目录位置
...
词库、用户配置和皮肤此前固定在 %LOCALAPPDATA%\metasequoiaime,整包几百 MB
全压在 C 盘。安装向导新增一页,允许把这部分数据放到任意盘。
程序本体仍固定装在 Program Files:MetasequoiaImeServer.exe 带 uiAccess=true,
只有在受信任目录里这个标志才生效。
运行期的权威来源是 HKLM\Software\Metasequoia\MetasequoiaIME 的 DataDir。
Server、TSF DLL 和引擎三个组件各自按同一顺序解析:METASEQUOIA_IME_DATA_DIR
环境变量 -> 该注册表值 -> %LOCALAPPDATA%\metasequoiaime。三处各持一份实现,
因为组件边界规则禁止 windows/ 与 server/ 互相 include。32 位 TSF DLL 用
KEY_WOW64_64KEY 读,避开 WOW64 重定向。
安全相关的两个约束:
- 安装器在数据目录里写 .metasequoiaime-data 标记。覆盖安装的清理和卸载的整目录
删除只在看到这个标记(或目录就是历史默认位置)时才执行——用户可能把数据目录
指到一个本来就有自己文件的文件夹。
- 选择页与 /DATADIR= 走同一套校验:拒绝相对路径、盘符根目录、不存在的盘、程序
目录内部,以及 Windows/Program Files/各 AppData/用户主目录本身。
升级时改了位置,PrepareToInstall 在停掉输入法进程后用 robocopy /MOVE 搬运用户
拥有的数据(msime_user.db 及其 WAL/SHM/journal、config.toml、config.base.toml、
skins\),词库和前端资源由本次安装重新写入。跨卷移动目录 MoveFile 会失败,所以用
robocopy。用户词库搬运失败会中止安装:否则新版本对着空的 msime_user.db 启动,
ssPostInstall 的回放只记一句 skipped,用户的自造词就静悄悄没了。
测试侧新增 METASEQUOIA_IME_CONFIG_DIR,只改 config.toml 的位置而不搬词库。原先
靠重定向 LOCALAPPDATA 做隔离的办法在装了正式版的机器上已不再成立——注册表值
的优先级高于它。
2026-09-17 22:49:19 +08:00
Jia Sui
62c9f5cf6b
fix(windows): Backspace 清空预编辑后长按不外溢(#347)
...
从组词里起手的那一次 Backspace 按住,一旦 composition 被删空,
同一次按住的后续 auto-repeat 一并吞掉;松开后重新按下才恢复退格。
- 新增 KeyRepeatGuard 纯谓词(repeat 位判定,不依赖 keyup、不用计时器)+ x64/x86 单测
- OnTestKeyDown / _DispatchKeyDown / _ClassifyDeferredKeyDown 三处同判据:
吞键不发 IPC、不请求编辑会话,命中的排队键完成 replay token
- 延迟投影把 inputLength 打到 0 时置位,慢 Server 下重放不会漏到宿主
- 焦点/上下文清理路径复位(OnSetFocus、_ClearDeferredKeyDowns、
ThreadFocusSink::OnKillThreadFocus),不在 Chromium 文档切换路径误复位
Refs #347
2026-09-17 21:02:07 +08:00
fanlusky
a898c3b11c
feat(candidate): Ctrl+Enter 上屏候选右侧的译文
...
候选框开着且高亮候选带译文时,Ctrl+Enter 上屏那条译文而不是候选本身。
译文只有一条就直接上屏;有多条时把候选框整个换成这几条译义,空格/数字键
照常选一条上屏。
TSF 端原先在分类器最前面把所有 Ctrl 组合都还给应用,Ctrl+Enter 到不了
Server。现在在那条拦截之前先判一次:候选框开着才认,否则组合仍归应用。
认下来的键走既有的 FUNCTION_SERVER_CANDIDATE_KEY 通道,由 Server 决定
是上屏(CommitExactText)还是只开一个新候选页(NavigationIgnored)。
译文页复用同一份 candidate_ui.items,因此云/AI/英文/emoji/颜文字这些异步
回调和候选扩展都要在这个子模式下让路,否则会把译文冲掉。输入串全程没动,
退出时把存下来的 items 和页码原样放回去即可。译文页上的空格/数字键只回
Normal / OutofRange——SELECT_BY_NUMBER 的 TSF 路径只认这两种。
2026-09-17 20:21:56 +08:00
Jia Sui
e64195731e
feat(server): Backspace 删空剩余拼音后退选已选分词(#35)
...
已选分词是造词中间态:删空剩余拼音时不再整条 composition 丢弃,
而是退选最后一段并恢复其原始拼音与候选。
engine:
- SelectionTransition 暴露本次选择消费掉的原始输入(保留大小写与双拼按键)
engine/contracts:
- 新增回复类型 CompositionRestored=14 与能力位 CompositionRestore;
MaxKnown 同步抬高,Server 按协商能力位门控发送,旧 DLL 不会收到未知帧
server:
- 造词中间态压 (consumed_raw, previous_creating_word) 快照,边界 Backspace 弹栈退选
- 任何清空 raw 的路径同时清 creating_word 与快照历史,避免跨 composition 泄漏
windows:
- _HandleCompositionBackspace 提示退选时读回包重建 keystroke buffer / word / 预编辑 / 光标
- NeedToCreateWord 与 CompositionRestored 共用载荷解析;旧 Server 与 UiLess 回退旧行为
Closes #35
2026-09-17 18:45:01 +08:00
Jia Sui
d4a0796400
feat(windows): 重构智能标点
...
原「智能标点」是小狼毫 digit_separators 的语义:字母或数字后的 , . : 直出英文,
2 秒内再按同一标点转回中文——后半段靠 SendInput 往应用里补一个 Backspace 再打
字符,落点全靠光标当时在哪。这次换掉主路径:输入中文标点后按空格,用 TSF 编辑
会话就地把它改写成对应英文(。,!?;:、 → . , ! ? ; : /),2 秒内再按同一标点
键撤回中文;原来的直出降为子开关,整个家族默认全关、改为显式开启。
- 按键与会话:转换/撤回是本地文档改写,不进 IPC、不进延迟队列;判据落在
_IsKeyEaten,OnTestKeyDown 的预判与 _DispatchKeyDown 的第二遍预判共用同一份,
声称吃掉的键不交还宿主。改写走编辑会话,请求不上时另起一个最小会话把键本身
的字符写回去,避免已吃掉的键静默丢失。
- 状态:原来散落的九个成员收成一个 SmartPunctuationAction(None / ChineseCommitted
/ AsciiConverted),带焦点令牌、前台窗口和 beforeChar 指纹;清理以分类结果为准,
丢焦点与上下文栈变化一并清掉。
- 转换范围:。,!?;:、 与单独出现的 “”‘’【】《》() 都是映射表里的单字符
条目,路径完全一致;成对补全产生的一对(autoClosedPair)不 arm,按空格只落空格,
不会留下 <》 这类孤儿。
- 开关:总开关 + 中文标点后按空格转换 + 同键转回中文 + 数字后直出 + 字母后直出,
五键全部默认关;直出拆成数字/字母两个独立子开关(IPC 24 收窄为数字、25 追加为
字母),TSF 按前字符类型分别查键。
- 命名:子开关「独立符号」改名「中文标点后按空格转换」,配置键
smart_punctuation_standalone → smart_punctuation_space_convert,IPC 22 只改常量名
(SmartPunctuationSpaceConvertChanged),编号与语义不变、新旧测试包互通;设置页
文案同步重写,五处配置链路与死宿主一并跟改。
- 兼容:旧键未出厂、不迁移(升级合并按模板缺键丢弃、取默认 false);编号 23 随
「按空格整体转换一对」的设计退役并留空不复用。
- 验证:TSF x86/x64 与 Server Release 编译、Server 335 单测、windows_ipc_contract
x64/x86、设置页 build + 27 测试、clang-format 18.1.8 全绿。真机未跑。
Signed-off-by: Jia Sui <jsfaint@gmail.com >
2026-09-17 09:29:57 +08:00
fanlusky
5a5dea6b7e
feat(japanese): 日语模式禁用 -/= 翻页并用 - 打长音符
...
日语模式下把 - 让给长音符(ー)输入,同时禁掉 -/= 的候选翻页:
- 罗马字方案接受 VK_OEM_MINUS,set_raw_input 不再把 - 过滤掉;
罗马字表里本来就有 - 到 ー 的条目,只缺按键准入。
- n 后面接 - 时按辅音处理,让前面的 n 收成 ん,避免 n- 卡在 pending。
- 空编码时单独按 - 也起头组合,候选框第一项是 ー、第二项是普通 -。
- 组词流程同样适用:判据只看日语模式加组合状态,不再走翻页和词转字。
- -/= 在日语模式下不翻页,_ = + 这些没有假名写法的字符退回标点上屏,
并把 = _ 补进服务端的标点集合,和 TSF 侧保持一致,否则会等不到回包。
TSF 侧的影子分类、IsVirtualKeyNeed(ForFreshComposition) 和服务端按键策略
三处同时改,统一以配置项 input_mode == japanese 为判据(R 模式不在范围内,
TSF 侧看不到临时会话,用会话类型判断会让两侧分类打架)。
2026-09-15 01:57:50 +08:00
fanlusky
795bffcf96
chore(windows): set release version to 0.7.1
...
Release-As: 0.7.1
2026-09-14 13:51:39 +08:00
fanlusky
e990ca2796
fix(windows): expose correct TSF version metadata
2026-09-14 13:44:22 +08:00
fanlusky
8e4bb44c91
fix(punctuation): Excel 中禁用成对标点自动补全
...
Excel 单元格编辑不接受补全后的光标左移,右半边上屏后回到两半之间的动作
会让焦点单元格往左偏一格。与微信输入法一致,把 Excel 排除在该功能之外,
标点回落到原有的左右轮换行为。
2026-09-14 01:04:16 +08:00
fanlusky
96de13315b
fix(punctuation): 成对标点下书名号退化为〈〉的问题
...
开启成对标点(自动补全右括号)时,输入 `<` 会让引擎返回 `《` 并把嵌套
深度 `_nestCount` 加一,随后 Windows 侧自动补上 `》`;而按 `>` 走的是
“步过”路径,两者都不经过 GetPunctuation,深度永远不会减回。于是每输入
一对 `《》` 深度净增一,第二次输入就退化成内层的 `〈〉`。
在自动补右半括号时调用 BalanceNestPairAfterAutoClose 抵消开括号的自增,
使一对完整书名号对嵌套深度净影响为零,保证每次都稳定输出 `《》`。
2026-09-14 00:42:57 +08:00
fanlusky
c9a5d0fd2b
feat(tsf): 成对标点补全加修饰键闸门与配对栈
...
问题:成对标点上屏后靠 PostMessage + SendInput(VK_LEFT) 把光标送回中
间。(〈《""' 全是 Shift 组合键,而 PostMessage 是异步的,合成的
VK_LEFT 常常在物理 Shift 仍按住时抵达宿主,于是变成 Shift+Left——右
半边标点被选中。机器差异只是竞态窗口大小的差异,【】这类不需要 Shift
的键从不复现,可作对照。另外按右半边标点会再插一个,得到(内容));
而引号方向由左右交替维护,右引号根本打不出来。
改动:
- 修饰键闸门:送键前用 GetAsyncKeyState 查 Shift/Ctrl/Alt/Win 是否物
理按下,按住就挂 15ms 重试定时器推迟,而不是合成一次 Shift 抬起
(WindInput 的源码注释记录了那条路走不通)。Win 键同样要拦,Win+Left
是贴靠窗口的快捷键,箭头落进按住的 Win 组合会重排桌面。带 2s 超时
和焦点会话校验,过期即放弃并清栈。
- 自生成按键标记:合成的 INPUT 带 dwExtraInfo =
PAIRED_PUNCTUATION_SENDINPUT_EXTRA_INFO,四个按键 sink 入口和
_BareShiftKeyboardHookProc 在开头识别后放行,IME 不会把自己发出去
的键再处理一遍,跳过配对时也就不会清掉正在走的那个栈。
- 配对栈:参照 WindInput 的 pair_tracker,记录 {左半, 右半, 焦点令牌},
深度上限 16。按右半边标点时先读文档确认光标右边确实是那个字符,确
认后跳过而不是再插一个;焦点切换、中英与标点模式切换、任何结构性
按键(方向键、Backspace、回车等)都清栈。
- 引号在成对模式下每次按都开一对新的,不再走 GetPunctuation 的左右交
替,右引号因此可达。
- 消息窗口销毁时走 _CancelPairedPunctuationCaretMove 而不是裸
KillTimer:重试定时器额外挂着 _pairedCaretRetryTimerActive,只杀定
时器会把标志停在 true,消息窗口重建后就再也装不上定时器,等于延迟
送键这条路被静默废掉。
已知限制:连续按住 Shift 跨两个成对标点键(如 " 接 ()会得到 ""()
而不是 "()"。这是延迟送键设计的固有取舍,WindInput 同样如此;要消
掉需走 TSF SetSelection 兜底,或合成一次 Shift 抬起。
Backspace 整对删除未实现:判断左右两半必须读文档,而读文档需要编辑
cookie,不吃键的 Backspace 路径没有。不验证就合成 VK_DELETE,在栈因
鼠标点击而失效时会删掉无关字符。
验证:Debug 与 Release 的 x64、x86 四种组合均编译通过;clang-format
对改动文件无差异;本机实测成对标点上屏后光标落在成对标点中间。
2026-09-13 17:48:41 +08:00
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
fanlusky
0febd575ac
refactor(windows): clear the TSF /W4 warnings
...
The bulk of them were left behind when the keystroke latency logging was
removed: PerfTimer objects and *ElapsedMs doubles that nothing reads any more
(C4189). Dropping them also takes the QueryPerformanceCounter calls back out of
the keystroke path. The timers that still feed DebugTsfKeyLatency (Ipc.cpp,
KeyEventSink.cpp, KeyHandlerEditSession.cpp) are untouched.
The rest, all behaviour-preserving:
- C4463: the four CompositionProcessorEngine configuration flags were one-bit
BOOL fields, so every `= TRUE` stored -1. They are plain BOOLs now.
- C4456: renamed the shadowing inner locals (pLI -> pItem, focusToken ->
expectedFocusToken).
- C4100: commented out the parameter names that are part of a TSF interface but
unused in the implementation.
- C4702: dropped the unreachable `return 0;` after FreeLibraryAndExitThread,
which is __declspec(noreturn).
- C4102: removed the two vestigial NoPresenter labels and the empty branches
that used to jump to them.
- C4267/C4244: made the narrowing conversions in the pipe write and the offline
key handler explicit.
- C4996: SharedConfigPath now uses GetEnvironmentVariableW instead of the
deprecated _wgetenv.
x64 and x86 Release both build clean; ctest passes in both.
2026-09-12 00:55:56 +08:00
Ouchihao
1d2431add6
fix(windows): commit highlighted candidates with numpad punctuation
2026-09-11 19:32:17 +08:00
fanlusky
580ac98d0a
fix(windows): match bare-Shift release on the arming latch, not GetKeyState
...
Word does deliver the bare-Shift release to the key-event sink; what it
does not deliver is a fresh GetKeyState() snapshot alongside it. Hosts
that call into ITfKeystrokeMgr outside the message dispatch see the
modifier state as of the last message the thread pulled, so
Global::PureShiftKeyUp/IsShiftKeyDownOnly -- both derived from
GetKeyState() -- can still describe the previous keystroke. The release
match then silently fails even though OnTestKeyUp and OnKeyUp both ran.
Match on the released virtual key plus _shiftHotkeyArmed instead.
The latch already means "this Shift went down alone and nothing else has
been pressed since" (_TrackModifierHotkeyArming disarms on any other
key-down) and it self-clears, so TestKeyUp/KeyUp cannot double-toggle.
Arming reads GetAsyncKeyState(), which reports the real keyboard rather
than the host thread's queue. Same treatment for bare Ctrl.
This is how Weasel and WindInput handle it -- neither installs a
keyboard hook for the toggle -- so revert the hook back to mintty-only.
mintty is a genuinely different failure: it offers no bare modifier
key-up to the sink at all, so there is no event to match against.
2026-09-11 03:07:14 +08:00
fanlusky
0838681465
fix(windows): restore bare-Shift CN/EN toggle in Word
...
70930df stopped eating the bare-Shift release so JetBrains double-Shift
keeps working, and moved the toggle into OnTestKeyUp because TSF does not
call OnKeyUp after a FALSE test result. Word never completes a bare-Shift
release through ITfKeyEventSink at all, so neither callback runs and the
CN/EN toggle was lost there.
The mintty workaround already solves exactly this shape: a thread-local
WH_KEYBOARD hook watches the real bare-Shift press/release and feeds it
back into the deferred hotkey path, with _MarkBareShiftHandled() latching
presses the key-event sink already toggled so hosts that do deliver the
release never toggle twice. Install that hook for every host instead of
only mintty.exe, and rename its members off the mintty-specific naming.
Also log the three bare-Shift release paths under [issue47] -- key-up was
previously uninstrumented, which is why the Word diagnostic log could not
distinguish "release never arrived" from "toggle queued but dropped".
2026-09-11 01:34:00 +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
Evan
997fdfd9cb
chore: settle line endings in .gitattributes ( #310 )
...
* chore: settle line endings in .gitattributes
Nothing declared line endings, so each file kept whatever the contributor's core.autocrlf produced when it was added: 382 text files ended up LF and 50 CRLF. The CRLF ones are the hazard. Editing one on macOS or Linux writes LF lines into it, and clang-format rejects a file that mixes both -- which is how `quality / Workflow validation` went red on KeyStateCategory.h, where three lines out of a CRLF file had arrived as LF. #309 converted those three lines back and left the other 49 files loaded.
The repository now declares LF and the working trees are renormalized to match, so the failure mode is gone rather than deferred to whichever of those files is edited next.
The four binary types are declared rather than left to git's heuristic, which decides from the first 8000 bytes alone. user_dict.dat is caught by that sample today only because the 40-byte seed is almost entirely NUL.
Verified: the 56 renormalized files are byte-identical to their old contents once CR is stripped, no text file in the index still carries CRLF, and scripts/format.sh --check passes.
* chore: skip the line-ending renormalization in blame
The commit before this one rewrote every line of 56 files without changing a character of them, which is what this file exists to hide.
2026-09-10 03:04:23 +00:00
Evan
0d72ecfc64
fix(windows): normalize key header line endings for CI ( #309 )
2026-09-10 10:32:21 +09:00
Evan
edb2074d7c
test(ipc): make fallback policy independently executable ( #307 )
2026-09-10 08:04:47 +09:00
Evan
9f9cc0e4c7
fix(ipc): distinguish definite local fallback ( #305 )
2026-09-10 07:58:08 +09:00
Evan
e2a4f3dcfb
fix(tsf): remove unused client router shim ( #303 )
2026-09-10 07:49:31 +09:00
Evan
db166187d9
feat(tsf): convert edit session keys for client router ( #301 )
...
* feat(tsf): add client key router boundary
* feat(tsf): convert edit session keys for client router
2026-09-10 07:41:53 +09:00
Evan
e45b9fb7b9
feat(tsf): add client key router boundary ( #300 )
2026-09-10 07:40:28 +09:00
fanlusky
58fa715921
fix(tsf): stop the candidate window flickering when the host lags
...
Two host-timing paths could hide or displace the candidate window between
ordinary keystrokes. Both get much more likely while the machine is busy,
which is when the flicker is reported.
TS_E_NOLAYOUT from ITfContextView::GetTextExt means "not laid out yet, ask
again", but _GetTextExt reported it as a valid measurement at INVALID_Y.
Server then parks the host off-screen for that frame and the next successful
measurement pulls it back. Chromium-based hosts answer TS_E_NOLAYOUT
whenever their renderer is behind, so under load this repeats per keystroke.
Keep the anchor the session already has instead, once it has one; composition
start still falls back to the sentinel rather than reusing a stale anchor
from another document.
TF_LC_DESTROY was treated as candidate-session teardown for every process
except a hardcoded Telegram.exe. Hosts that transiently destroy and recreate
their context view mid-composition therefore got a HideCandidate between
keystrokes, which clears the live composition on the Server side: the window
blinks and the pending candidate can no longer be committed. Gate on a live
composition instead of a process name. Commit, cancel, focus loss, and
presenter cleanup all end the composition first, so they still tear the
session down through their own paths.
2026-09-08 22:53:59 +08:00