112 Commits
Author SHA1 Message Date
Lu Fan(SonnyCalcr) c0a9d371f0 feat(server,windows,engine): 横排候选时左右方向键移动高亮 (#1080)
- 快捷键页「翻页方式」新增「左 / 右(横排时移动候选项)」,与「上 / 下」各自独立,默认关闭,
  只在候选窗为横排时生效。配置项 general.candidate_horizontal_arrow_navigation 已写入出厂模板。
- 光标在拼音串尾时右键移动高亮(到页末翻页),左键往回移高亮,高亮回到第一个候选后再按才移动光标;
  光标在串中间时右键先把光标移回串尾,再按才移动高亮。Ctrl+左/右与 UILess 宿主不变。
- 新增 worker 消息 HorizontalArrowSelectionChanged(39)和按键标记 FanyImePipeFlags::HorizontalArrowSelection:
  开关生效时 TSF 把候选态下的左右键交给 Server 决定,移光标时 Server 用 CompositionRestored 回光标,
  移高亮时回 MoveSelection*,TSF 不再自己预测光标。旧 DLL 忽略新消息,保持原行为。

Closes #779
Closes #895
2026-10-19 02:11:25 +08:00
Lu Fan(SonnyCalcr) 75d8c710a9 feat(server,windows,engine): 新增顿号自定义,翻页方式移到快捷键页 (#1071)
- 设置「输入」页在智能标点上方新增「顿号自定义」:\ 和 / 各一个勾,至少保留一个,
  未勾选的键按原样输出。新装默认两个都开;升级用户的配置里没有 / 这一项,
  模板合并时补成关,保持原来只有 \ 出顿号。
- 新增 worker 消息 EnumerationCommaKeysChanged(38),载荷见
  engine/contracts/enumeration_comma.h;旧 DLL 忽略它,保持只用 \。
- 选候选时按 / 改为按配置上屏「候选 + 、」;未作顿号的 \ 或 / 跟在候选后原样上屏。
  小键盘 / 与小键盘 . 一样始终输出原字符。
- 「翻页方式」从「输入」页移到「快捷键」页。
- AtLeastOneChecks 从辅助码页移到公共绑定,两页共用。
2026-10-18 23:49:41 +08:00
Lu Fan(SonnyCalcr) 5de73de7a6 feat(engine,server,windows): 双拼末尾辅助码支持间接触发,句中辅助码单独成区 (#1068)
双拼辅助码新增「直接辅助码」开关(默认开,行为不变)。关掉后末尾辅助码改由引导键触发:
反引号、分号、大写字母可多选,触发键后接一到两个辅码(如 ulpb;xy、ulpbXy),一码重排、
两码筛选,只在输入末尾生效。触发键在 TSF 缓冲与 Server raw 里统一记成 |,与句中辅助码的
反引号段区分。

两组触发键互斥:一边勾上某个键会取消另一边的同一个键;手改配置两边都勾时归句中辅助码。
新增 Worker 消息 EndHelpcodeTriggersChanged(37),旧 DLL 忽略。

设置页把句中辅助码单独成区(仅双拼),其中间接(引导键)与直接(万象式)两种方式互斥。
新配置项同时写进出厂模板与开发配置。
2026-10-18 22:54:17 +08:00
Neptrue_Lin ce70cbdcf3 Merge pull request #1036 from Ten-Shine/feat/ctrl-num-translation
feat(server,windows): 译文快捷键增加 Ctrl+数字
2026-10-18 19:50:09 +08:00
Ten-Shine bf33d44751 refactor(server,windows): 译文快捷键只保留 Ctrl+Enter 与 Ctrl+数字 2026-10-17 18:43:40 +08:00
Ten-Shine f46646234c feat(server,windows): 译文快捷键增加 Ctrl/Shift+数字 2026-10-17 17:35:07 +08:00
Jia Suiandfanlusky 3b1206ea82 fix(windows,engine): 提交键回复等待不再因超时重置会话或丢字 (#1019)
* fix(windows,engine): 提交键回复等待不再因超时重置会话或丢字

服务端按键处理偶发数百毫秒尖峰时,提交键(空格顶首选 / 确定选词 / 标点顶高亮候选)
的回复等待会走到会话内那条 300ms 同步读;一超时就被判成 TransportUnavailable
→ DeliveryAmbiguous → Transport 完整重置(丢键 + 吞队列 + 清组合 + 轮换 epoch),
用户看到的是整段已输入内容消失。

- 删除 FANY_IME_COMMIT_REPLY_SESSION_WAIT_MS 那条破坏性兜底读。会话侧改为非阻塞探测,
  拿不到回复返回「未就绪」,由 session 自身在 DoEditSession 内交回,沿用同一个
  FANY_IME_COMMIT_REPLY_TIMEOUT_MS 分片轮询窗口,不重开窗口、不新造等待原语。
- 交回必须发生在 session 内部:RequestEditSession 用 TF_ES_ASYNCDONTCARE,TSF 异步授权
  时调用方读不到 session 的 HRESULT;若靠调用方读返回值,该键会既不丢也不提交,
  deferred 闸门再不打开(后续按键全部只入队)。也不能改用 TF_ES_SYNC(WinWord 上不成立)。
- 真失败(写失败 / 读失败 / 坏帧 / 字节数不符 / 无 request id / 无管道)仍判 Transport 重置。
- 预取覆盖面扩到 candidate 类 finalize(空格顶首选等),堵住原先只能靠那条 300ms
  兜底读的路径。
- 新增按键级延迟诊断 bench_key_latency_diag(build 里有同名 exe 但源码已不在树),
  以及两份对照测试:commit_reply_not_ready(回复三态与交回状态机)、
  candidate_consistency(同配置下候选集合/顺序/分页逐字一致,引擎侧护栏)。

实测(bench_key_latency_diag):稳态逐键 median 0.6~0.8ms / max 12.5ms,尖峰指向内存
压力下的页回收而非稳态算力。在「结果完全不变」的约束下,预热 / 缓存 / 投机预解码均无效
或反向,故本次不改引擎打分路径。

* fix(windows): 直接标点提交会话补齐未就绪交回,并写准窗口沿用边界

审查 `7aca5e5d` 发现的问题:

- `CPunctuationCommitEditSession` 在 `FANY_S_COMMIT_REPLY_NOT_READY` 时只抑制了 token
  结算(`applied` / `_FailDeferredKey` 都不走),**没有**像 `CKeyHandlerEditSession` 那样把
  请求交回 `_PollCommitReply`。一旦走到这条分支——`_HandleCompositionPunctuation` 的高亮候选
  分支要求 `_candidateMode != CANDIDATE_NONE`,而派发该会话的条件是
  `_candidateMode == CANDIDATE_NONE`,两者在 session 执行时才求值——deferred token 会永久留在
  飞行中,后续所有按键只入队不处理,正是本 PR 在别处修掉的失效模式。现在两处都交回,
  并把原因写进注释,避免下一个人再照半个写法抄一遍。
- `_HandOffUnreadyCommitReply` 的两处注释与实现不符或不够具体:交回沿用第一片已经开始的窗口;
  只有从未开窗的路径(标点处理函数在派发时高亮候选条件为假、session 执行时条件成立)才在此
  开窗,那种情况键一点没等过、`waitedMs` 为 0。另一支(无法保活 → `_FailUnansweredCommitKey`)
  只在请求不属于协作轮询时到达,实务上即 `commitReplyMessage == 0`:派发没能用消息窗,
  没有可交回的对象。

签名相应扩展:`_RequestDirectPunctuationEditSession` 新增 `commitReplyMessage` /
`commitReplyDeadlineTick`,`CPunctuationCommitEditSession` 携带它们。没有行为回退,两个新参数
默认 0 时与改动前一致(而那正是「无消息窗」的既有降级路径)。

验证:windows x64 Release 构建通过,`ctest` 17/17 通过,`clang-format 18.1.8 --dry-run
--Werror --style=file` 对本次涉及文件全过。

* fix(windows): 把 _HandOffUnreadyCommitReply 移到公共区,修 CI 编译失败

CI 的 Windows x64 与 Windows Win32 两条腿都挂在同一处编译错误:

  MetasequoiaIME_AsyncRequests.cpp: error C2248:
  “CMetasequoiaIME::_HandOffUnreadyCommitReply”: 无法访问 private 成员

上一提交给 CPunctuationCommitEditSession 补「未就绪交回」时调用了这个成员,但它在 private
区。CKeyHandlerEditSession 能调是因为它在 MetasequoiaIME.h 里被显式声明为 friend;而
CPunctuationCommitEditSession 定义在 MetasequoiaIME_AsyncRequests.cpp 的匿名命名空间里
(该文件 31-254 行),头文件里的 friend 声明指不到它,加 friend 也无效。

处置:把该声明移到公共区,与同样由 session 调用的 _FailDeferredKey、_CompleteDeferredKeyReplay、
_HandleCompositionPunctuation 并列,并在注释里写明「它必须公共」的原因,免得下次有人顺手挪回私区。
仅可见性与注释,行为零变化。

验证(triplet 与 CI 一致,且这次看退出码、不截断构建输出):
- x64-windows-static:cmake --build build --config Release --parallel 退出码 0;
  ctest --no-tests=error 17/17。
- x86-windows-static:cmake --build build32 --config Release --parallel 退出码 0;
  ctest --no-tests=error 17/17。
- clang-format 18.1.8 --dry-run --Werror --style=file 对本次改动文件通过。

另:上一轮「构建通过」是我误报——当时用 Select-String 过滤构建输出又加了 -Last 8,错误行被截掉,
且没有检查 $LASTEXITCODE;ctest 过的是测试目标,与 DLL 编译失败并不冲突。

* fix(windows): 提交回复交回后轮询不再停摆,同步兜底保留有界等待

- 轮询定时器按请求 id 判断是否仍在等待:session 交回时换了新 token,
  按旧 token 查会误判为已结算并 KillTimer,交回的请求从此无人再分发。
- 没有轮询可交回的 session(同步 _InvokeKeyHandler 路径)恢复
  FANY_IME_COMMIT_REPLY_SESSION_WAIT_MS 的单次有界等待,不再一读不到就丢键重置。
- 确定并输入在提交回复为传输失败时不再继续键入字符并返回 S_OK。
- session 交回占用新槽位时同样受 kMaxPendingAsyncKeys 约束。
- 修正注释:未就绪由 session 自己的完成钩子交回,不是消息处理函数。
- 测试:覆盖轮询分发、等待上限与满表交回;SAL 剥离按最长前缀匹配;
  引擎一致性用例③不再清缓存,否则测不到跨输入的缓存泄漏。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-17 16:19:26 +08:00
Lu Fan(SonnyCalcr) 1b875267db refactor: 移除 WebView2,界面只保留 Direct2D (#1013)
* refactor(engine): 语音独立工具改用原生托盘菜单,移除 WebView2

托盘菜单改用 Win32 弹出菜单,设置项改为用关联编辑器打开 config.toml,关于页改为消息框。删除 window_webview2 与两份 HTML 资源,windows-app 特性不再依赖 webview2 端口。

* refactor(server): 移除 WebView2,候选窗、工具栏、托盘菜单与设置窗口只保留 Direct2D

删除 src/webview2 与 webview_utils,小窗口的置顶、皮肤重载和工具栏状态缓存移到 window/small_windows,presenter 成为唯一路径,D2D 下的置顶时序与 cloak 行为保持不变。设置进程去掉 WebView2 宿主、鼠标转发与启动图,只构建原生窗口;旧快捷方式传入的 --d2d / --webview2 被忽略。删除 appearance.ui_backend 与 appearance.settings_ui_backend(读取、写入、设置页下拉框与两份配置模板),升级时由模板合并丢弃旧值。设置窗口的图片资源移到 server/assets/settings,安装到数据目录的 assets\settings。不再链接 WebView2Loader,vcpkg 去掉 webview2 端口。

* build(installer): 打包与 CI 不再处理 WebView2 页面和运行时

Prepare-PackageFiles 去掉 -UiHtmlDirectory 与 html 暂存,改为暂存 server/assets/settings;安装器不再检查 WebView2 Runtime、不再创建 ProgramData 下的 WebView2 用户数据目录,升级和卸载时清理旧版留下的 webview2 / webview2-settings 与数据目录里的 html。CI 删除设置页构建、契约同步检查和候选窗 DOM 测试,release 的打包任务不再依赖网页产物;dependabot 去掉 npm。第三方声明去掉 WebView2Loader。

* refactor(ui-html): 删除 WebView2 页面与设置页前端工程

候选窗、工具栏、托盘菜单、设置页、主题和契约副本都已没有使用者;设置窗口用到的图片资源已移到 server/assets/settings。sync-contracts.py 与 pnpm/Vite/TypeScript 工程一并删除,契约仍以 engine/contracts/webview 为唯一来源。

* docs(server): 文档改为只描述 Direct2D 界面

仓库地图去掉 ui-html,组件边界、uiAccess 与置顶时序、设置窗口和配置读写的说明改写为 Direct2D 唯一界面;安装说明去掉 WebView2 Runtime 检查,打包命令去掉 -UiHtmlDirectory;engine/contracts/webview 的说明改为设置窗口是唯一使用者,名字是历史遗留。

* docs(skins): 皮肤说明不再区分 WebView2 与 D2D 渲染器

v2 字段去掉「仅 WebView2」,改为说明由 Direct2D 原生渲染、部分字段仍在补全;颜色必须带 # 改为清单格式约定。随包的 skins/niya-demo/skin.toml 不改,免得用户机器上的同名皮肤在升级时被当成已修改而改名备份。

* docs(ui): demo 说明文字不再提 WebView2
2026-10-17 02:48:02 +08:00
6072ae7784 fix(windows): 修复临时日语 R 模式无法输入长音符 (#1000)
* fix(windows): 修复临时日语 R 模式无法输入长音符

* fix(server,windows): 临时日语策略每键只算一次,并去掉多余的帧

- Server 在 HandleImeKey 开头按本次按键所属客户端算一次策略,不再每个谓词都查活动客户端、加锁、构造 payload 再解析回来。
- 普通激活只补发 TemporaryJapanesePolicyChanged;整套输入模式状态(含语言栏刷新)仍只在 FocusRestored 时发送。
- 配置重载时输入方案变了由 ApplyConfiguredInputScheme 统一广播,不再重复广播策略。
- TSF 的 WaitForProtocolReady 不再清零策略:worker 管道先于 Main 注册,可能已收到本次连接的策略;断开时已有清零路径。

---------

Co-authored-by: ranly <ran618ly210805@qq.com>
Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 22:30:28 +08:00
chr331andfanlusky 1c3bd7cb83 feat(server,ui-html,windows): 支持自定义“输入状态切换”中的快捷键 (#985)
* fix(windows): 支持标点切换快捷键改绑与取消绑定

* fix(shortcut): 对齐虚拟键并处理语音与窗口快捷键冲突

* fix(ui-html): 绑定标点快捷键前提示应用快捷键占用

* fix(ui-html): 仅对已知快捷键冲突提示确认

* feat(shortcuts): 支持自定义输入状态切换快捷键

* fix: keep shortcut editor buttons on one row

* fix(shortcuts): 限定输入状态快捷键为 Ctrl 组合

* fix: clarify shortcut override and punctuation lock behavior

* perf(server,windows): 提前筛选输入状态快捷键

* fix(windows,server,ui-html): 修正输入状态快捷键的兼容与保存问题

- windows: 未协商 InputStateShortcuts 时回退到旧的 Ctrl+Shift+E KeyEvent,
  新 DLL 配旧 Server 时英文候选快捷键不再失效
- windows: 全角快捷键与标点快捷键一样可在待处理按键后排队,不再漏给应用
- windows: Server 不可用时排队的英文候选切换按 FUNCTION_CANCEL 离线回放,
  不再丢弃其后的按键
- server: 非字符串配置值不再以 "!invalid" 作为 binding 发给设置页
- server: 仅三个输入状态快捷键路径强制回传配置快照;去掉 worker 中与
  reader 重复的协商检查;抽出 InputStateShortcutViews 去除重复转换
- ui-html: 保存后 5 秒未收到快照则恢复编辑器并提示重试

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 03:31:19 +08:00
Jia Suiandfanlusky 9beab473f4 fix(windows,server): 提交键慢响应改为分片让出等待,不再丢字或冻结宿主 (#982)
* fix(windows,server): 提交键慢响应改为分片让出等待,不再丢字或冻结宿主

* fix(windows): 提交回复轮询不阻塞宿主,补齐标点顶候选路径并保持结算顺序

- 标点顶高亮候选(WM_AsyncPunctuationCommit 无预取文本)也在处理函数里分片取回复,
  edit session 不再在宿主 UI 线程上阻塞 300 ms
- 轮询改为不阻塞:先查缓存、PeekNamedPipe,有帧才以零超时读,不再每片阻塞 16 ms
- 定时器只重派已开始等待的请求,按请求 id 升序结算,较新的提交排在仍在等的旧提交之后
- 处理函数取到 TransportUnavailable 时直接按歧义丢键并重置,不再交给 edit session
- 已在等回复的提交键在同一焦点下被组合纪元取代时按 DeliveryAmbiguous 处理,避免两端分叉
- 回复缓存淘汰不再淘汰刚写入的条目;CachePendingReply 与提交回复缓存共用一份逻辑
- 停用时显式 KillTimer(TIMER_COMMIT_REPLY_POLL);windows/AGENTS.md 同步新的提交预算

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 02:48:58 +08:00
HowGeGeandfanlusky d98c420822 fix(windows): pass CapsLock Shift letters through to applications (#976)
* fix(tsf): pass CapsLock Shift letters through to applications

* fix(windows): 延迟路径按与同步路径一致的输入状态判断 CapsLock 直通

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 01:16:48 +08:00
Jia Suiandfanlusky d414268b5f [Feat] U 模式重构为部件拆字反查 (#973)
* feat(engine,server,windows,ui-html,installer): U 模式重构为部件拆字反查 (#971)

把 U 模式从 Unicode 十六进制码位输入改成部件拆字反查:Shift+U 后输入各
组成部分的拼音连写(晶 = ririri)即可出字,候选上反显注音。

- 引擎:新增 local_modes/radical_query(前缀匹配、字频降序),数据表
  engine/radical/decomposition.txt 与生成脚本;LocalInputMode::Unicode
  改为 Radical;删除 unicode_query。
- Server:g_radical_mode_triggered + IsRadicalCompositionActive + 候选分发,
  用 cpp-pinyin 反显注音;删除 Unicode 码位路径。
- 前端 TSF:删除 U 模式的十六进制/数字特例与影子预判,按普通组合处理。
- 配置:utility.unicode_mode 改为 utility.radical_mode。
- 部署:assets 登记 radical_decomposition,安装器与 CI 拷表。

数据来源 mirtlecn/rime-radical-pinyin (GPL-3.0),授权见 engine/radical/NOTICE.md。

* feat(server): U 模式反显拼音改为带调风格(jīng)

* fix(server,installer): 修复 U 模式 CI(拆字表打包夹具与 clang-format)

* fix(engine,server): 修正 U 模式按键错位、重复候选与配置迁移

- Server 在 U 模式不再吞掉撇号、分号、斜杠、大写字母等键:TSF 已把它们记进按键缓冲,
  吞掉又不重同步会让两侧串和光标错位。照常插入,含非 a-z 的码查不出候选
- 拆字查询全码命中排在仅前缀命中之前,同一个字的几种拆法只出一次
- 升级合并把旧键 utility.unicode_mode 的值带到 utility.radical_mode,读取时也兜底旧键
- 生成脚本只收汉字,重建拆字表去掉 abc 码下的 51 行数字、字母与符号
- 开着 U 模式时在后台预热拆字表与 cpp-pinyin 词典,避开首个按键的冷加载
- Server 按会话的 RuntimePaths 查拆字表,删掉走全局数据目录的便捷重载
- cpp-pinyin 初始化合并到 utils/hanzi_pinyin,设置页导入注音共用;U 模式与快捷短语的收键分支合并

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 01:01:18 +08:00
Jia Suiandfanlusky 456b23ed94 feat(engine,server): 五笔支持 T 模式与 V 模式 (#942)
* feat(engine,server): 五笔支持 T 模式与 V 模式

五笔方案此前被排除在 Shift+T 日期时间模式与 V 模式之外。根因是方案层就把
raw 归一成小写 a–y、丢弃数字与运算符,Server 的形状判定读不到大写 T/V 前缀。

- 引擎 WubiScheme 新增 raw_input_with_cases_:保留大小写与 T/V 模式的非字母键
  (数字、运算符、: /),但按码字域丢掉越域字母(默认配置下 z)。查询码仍由
  normalize_wubi_code 派生,普通码下两者长度相等——Server 用原样串长度数四码。
- Server:T 触发新增独立谓词 date_time_scheme(chinese_scheme || Wubi),不并入
  chinese_scheme 本身;FormatVModeWorkerPayload 对五笔返回 UppercaseOnly。
- 自动上屏 / 顶字两个谓词新增 special_mode_active 前置:T 模式关键词归一后可能与
  合法四码重合(Tdate → tdat),不挡会在第四个字母上结束模式。
- 引擎与 Server 测试、README 与两份配置模板文案同步。

* fix(engine,server,ui-html): 五笔 T/V 模式由宿主标注字面输入

原实现让五笔原样串不再裁到码长,普通码下原样串可以比码长(混输关、z 通配时 wqazb 对 wqaz),
Server 读回后光标、退格和上屏都会错位;守卫也只认 T 模式的全小写主体,TdatE 仍会顶出 tdat。

- 引擎:InputSession::set_wubi_literal_input 随 pending 序列一起下发,只作用于同一次设置。
  普通码的原样串就是带大小写的码,与查询码逐位对应、一起截断;字面输入原样保留,
  不查码表与拼音,也不算完整四码。退格从原样串重新派生码,字母键两份视图取同一个字母。
- Server:ApplyCompositionEditKey 按 IsWubiLiteralComposition(T 模式触发标记或 V 模式形状)
  标注字面输入;两个五笔上屏策略恢复原签名,不再需要 special_mode_active。
- 设置页 T/V 模式说明补上五笔。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-15 02:08:36 +08:00
Ten-Shine 850ad772a1 feat(server,ui-html,windows): 添加任务栏语言栏按钮显隐配置
任务栏语言栏(输入法指示器旁)此前固定注册中/英模式、全/半角、中文标点三个按钮,
用户无法隐藏。新增 [language_bar] 配置节:enabled 是总开关,关闭时不再向
ITfLangBarItemMgr 注册任何按钮;show_ime_mode / show_double_single_byte /
show_punctuation 分别控制单个按钮。键缺失或共享配置不存在时默认全部显示,
旧配置与全新安装的行为不变。

- 出厂模板 config.default.toml 与工作配置 config.toml 新增 [language_bar] 节,
  四个键默认 true;升级时 MergeConfigIntoTemplate 以出厂模板为骨架重建用户配置
- Server:LoadImeConfig 读取四个开关,ime_config_general 提供成对读写入口;
  设置进程 BuildConfigMessage(configSnapshot)与 ApplyConfigUpdate(path 分发)
- 设置页「工具」分区新增语言栏卡片:总开关 + 三个子开关。总开关关闭时子开关
  置灰禁用但保留勾选展示,交互与候选混输一致;快照回填与点击上报均有测试覆盖
- TSF DLL:SetupLanguageBar 在注册时机读取共享配置,不注册的按钮不会出现在
  语言栏。按钮对象仍创建以保持析构对称;模式与标点切换走 compartment,
  隐藏按钮不影响输入
- 改动需重启生效(按钮在 TSF 激活时注册),设置页已注明
2026-10-14 13:54:11 +08:00
Lu Fan(SonnyCalcr) 526bf85cc5 fix(windows): read the Caps Lock edge after the key is handled (#933)
The key sink inverted the toggle it read in OnTestKeyDown, assuming the
test callback always runs before Windows applies the press. IMM hosts
such as Minecraft Java (GLFW) and Bedrock call it afterwards and never
call OnKeyDown for the uneaten key, so the tip reported the opposite
state and overwrote the Server's copy: the floating toolbar kept showing
Caps Lock after it was turned off. Both callbacks now post the edge to
the tip's message window and read the toggle there, once the key message
has been handled.
2026-10-14 01:27:33 +08:00
Lu Fan(SonnyCalcr) a281dbb6e8 fix(windows): pin the candidate window to the top-left in Minecraft Bedrock (#932)
Minecraft.Windows.exe never reports its chat box position. Before a
composition starts GetTextExt returns the work area's bottom-right pixel,
and after it starts the position of the system default composition window
at the top-left, so every composition flashed the candidate window in the
bottom-right corner before it jumped to the top-left. In these hosts the
anchor is now the work-area top-left of the game's monitor.
2026-10-14 00:51:37 +08:00
Lu Fan(SonnyCalcr) e46294308d fix: CS2 / Dota 2 / 三角洲行动等游戏里的输入法兼容(加载、候选、翻页、独占全屏、启动崩溃) (#925)
* fix(installer): TSF DLL 改装到系统目录,CS2 等 Trusted Mode 游戏里可以切出输入法

CS2 的 Trusted Mode 只放行位于系统目录且有代码签名的第三方 in-proc DLL,
装在 Program Files 下的 DLL 会被拒绝加载,游戏里切不出输入法。
x64 DLL 改装到 System32\IME\MetasequoiaIME\msime_v*,x86 装到 SysWOW64 下同名目录,
语言配置图标随之放到两处根目录;升级时清掉旧版留在 Program Files 的版本目录。

* fix(windows): CS2、Dota 2 不理会 UI-less 声明,照常显示候选窗

起源 2 引擎的 Valve 游戏以 UI-less 激活 TSF,但只替白名单里的输入法绘制候选。
我们据声明收起了自己的候选窗,游戏又不画,结果只剩左上角的系统默认 IME 小框,只能盲打。
对 cs2.exe、dota2.exe 忽略 UI-less,照常由 Server 绘制候选窗。

* fix(server): 前台为 D3D 独占全屏时不弹候选窗与光标状态提示

独占全屏下别的进程的窗口画不上去,弹出去反而可能把游戏踢出独占模式(闪黑、切分辨率、卡死)。
在 UI 线程真正显示前用 SHQueryUserNotificationState 判一次(300ms 缓存),独占全屏就收起候选窗,
并清掉已渲染可见标记,数字键与空格选词不再等待不会到来的绘制。

* fix(engine,server,windows): 游戏自绘候选时后台改动的候选列表实时同步给宿主

英雄联盟等 UI-less 宿主自己画候选。云候选、AI 候选、模型重排、英文混排在按键回复之后才改
候选列表,却从不通知 DLL,游戏还画着上一键读到的旧列表,数字键和空格却按新列表上屏,
于是「选的和上屏的不一样」。

- 契约新增 Worker opcode 35 UiLessCandidatesChanged,载荷与 UiLessComposition 同形,
  由 uiless_page.h 统一格式化与解析,并附候选页代号;旧 DLL 读选中位时在第三个制表符处停下,兼容。
- Server 在上述后台改动之后把新页推给候选归属的客户端。
- DLL 收到后回到 TSF 线程核对焦点与组合纪元,更新候选 UI 元素并通知宿主重读。推送与按键回复
  走不同管道,按代号只接受不旧于当前的页,避免旧页盖掉新页。
- 从共享内存重读到不同的页时高亮回到第一项,与 Server 每次重建都选中首项一致,
  不再保留旧高亮造成「高亮这个、空格上屏那个」。

* feat(windows,server,ui-html,installer): 游戏兼容开关,CS2 / Dota 2 独占全屏打字不再黑屏

CS2、Dota 2 只替内置白名单(按输入法登记名称全等比对)里的输入法处理输入法消息。不在名单里时
组合落到 Windows 默认组合窗 CiceroUIWndFrame,左上角弹出编码小框;独占全屏下这个窗口一出现
游戏就被踢出全屏,表现为一打字就黑屏。清风输入法未开兼容时同样如此。

- 设置页帮助页新增「游戏兼容(CS2 / Dota 2)」:开启后把水杉在系统里登记的名称换成白名单里的
  名字(默认「拼音输入法」),游戏会自己画候选。需要管理员权限:设置进程以
  --apply-game-compat-name 提权重启自身,写 HKLM 记录值并改语言配置的 Description。
- TSF DLL 注册时以记录值为准,安装/升级重新注册不再把用户选的名称冲掉;卸载时删除记录值。
- 开启时不再对 cs2.exe / dota2.exe 忽略「宿主自己画候选」的声明,避免两个候选框。
- Server 的独占全屏判断覆盖进出独占模式时的 QUNS_BUSY 间隙,不在切换途中弹候选窗。
- TSF 诊断日志在组合开始/结束与 BeginUIElement 时记录宿主窗口状态(只有窗口与标志,没有文本)。

* fix(windows): 后台刷新回调持有加载器引用,宿主卸载 TSF DLL 后不再崩溃

当前输入法是水杉时,CS2 启动途中会加载再卸载 TSF DLL。诊断日志的线程池回调
FlushTsfDiagnosticLogs 正在 Sleep(250),只靠 DllAddRef 撑着:那只回答 DllCanUnloadNow,
挡不住宿主直接卸载。DLL 卸载后 SDL3 映射进同一段地址,回调醒来跑进 SDL3 的代码,
游戏崩溃(0xc0000005,崩溃转储里卸载模块的地址范围与崩溃地址吻合)。

- 诊断日志与统计上报的线程池回调改为用 GetModuleHandleEx 取加载器引用,
  回调里 FreeLibraryWhenCallbackReturns,函数完全返回后才放手。
- 析构函数把 DllRelease 挪到最后,不在等待 IPC 线程退出之前就让 DLL 可卸载。

* fix(windows): 游戏自绘候选时不再把 Server 的 HTML 候选页当成候选交给游戏

CS2 开了游戏兼容后打一个码,游戏里的候选只有一项且是乱码。宿主要等 BeginUIElement 才说
自己画候选,而那时组合首键已经不带 UI-less 标记发给了 Server;Server 往共享内存写的是给
自己候选窗用的 HTML 页(辅助码、译文 span),DLL 的候选元素从共享内存回读后整串交给了游戏。

- 开了游戏兼容名称的 cs2.exe / dota2.exe 在 ActivateEx 就认定由游戏画候选,每个按键都带标记。
- 候选会话里收到过 Server 的 UI-less 页之后才从共享内存回读,之前一律不读。

* fix(windows): 游戏自绘候选时翻页能刷新,部分上屏后不再出现乱码候选

三角洲行动(虚幻引擎,以 TF_TMF_UIELEMENTENABLEDONLY 激活)里按 = / - / PageUp / PageDown
翻页,Server 已翻到第 N 页,游戏却还画着第一页;接着按空格上屏的是第 N 页的首项(多为单字),
剩下的拼音继续组合时候选框里出现乱码。

- 翻页、移高亮等键走异步的 WM_AsyncServerCandidateKey,以前把回复当作「只是确认」丢掉;
  UI-less 下这份回复就是新的候选页,现在应用并通知宿主重读。
- UI-less 的 _SetText 拿不到 Server 的候选页时不再退回本地引擎的占位列表——那不是候选,
  宿主会画成一条乱码。部分上屏后从共享内存回读 Server 写好的新页;_ClearList 同时清掉上次
  的页记录,页文本相同也会重新载入。
2026-10-13 21:58:15 +08:00
Ray Wong 998f661099 fix(windows): restore server input after delayed reconnect (#912)
Run the guarded raw-fallback exit after the reconnect timer acknowledges the focus session, before publishing status and draining keys. Match the existing immediate reconnect path; keep offline compositions and stale acknowledgements fenced.
2026-10-13 21:49:55 +08:00
fanlusky 371d651a40 fix(windows,installer): 输入法语言配置图标改用固定路径,升级后任务栏不再显示「简体」
TSF DLL 每次升级都装进新的 msime_v* 版本目录,语言配置注册的图标指向 DLL 自身,
注册表里的图标路径随之每次变化。任务栏恰好在换目录时取不到图标,就改显示「简体」文字,
并一直沿用到资源管理器重启。

- 安装器把 windows/image/msime.ico 装成 Program Files 与 Program Files (x86) 下
  metasequoiaime\MetasequoiaImeTip.ico,排在 TSF DLL 的 regserver 条目之前
- RegisterProfiles 发现 DLL 上一级目录有这个文件就注册它(索引 0),否则退回 DLL
  内嵌的 IDIS_METASEQUOIAIME,开发时从 build 目录注册不受影响
- 打包脚本收集并断言该图标;打包测试检查两份都装、且先于 DLL 安装
2026-10-12 04:20:17 +08:00
Lu Fan(SonnyCalcr) 715028e1b9 feat(server): AI 联想上下文跳过密码框、换应用清空、过滤类凭证串 (#845)
AI 联想会把最近的上屏文本作为上下文发到云端,以前不区分输入框,也跨应用累积。

- 密码 / PIN 输入框:TSF 在一段输入的首键上读 InputScope(IS_PASSWORD、IS_NUMERIC_PIN 等),
  按键包带上 FanyImePipeFlags::SensitiveField;Server 收到后不发 AI 请求,上屏也不进上下文
- 只在 AI 联想开启时才检测:Server 通过新的 worker opcode AiContextUploadChanged(34)
  告诉 TSF 开关状态,关着时按键路径只多一次 atomic load
- 上下文按焦点会话归属:client_id 或 activation_epoch 变了就清空,不再把 A 应用里打的字带进
  B 应用的请求
- 发送前去掉像凭证的 ASCII 串:不含空白、长度 >= 8 且混了字母 / 数字 / 符号中两类以上
2026-10-11 21:58:34 +08:00
fanlusky 37550e6cc0 fix(windows): Caps Lock 开启时切换为英文标点模式
#771 在 Caps Lock 下把空闲时的标点键直接交给宿主,但不改标点状态,
悬浮工具栏和语言栏仍显示中文标点;它还绕过了「始终中文」标点锁定,
用户也无法在 Caps Lock 下手动改回中文标点。

改为让 Caps Lock 参与「标点跟随中英文模式」的规则:开启时标点切到英文,
关闭时按当前中英文模式重算。标点锁定仍然优先,手动切换不经过这条规则,
因此 Caps Lock 下仍可用 Ctrl+. 或工具栏改回中文标点。按键路径和 Server
的 CapsLockChanged 广播都会触发同步,只在 Caps Lock 状态真正变化时重算,
重复上报不会冲掉手动切换。延迟按键队列的影子标点状态同步更新。
2026-10-11 00:46:52 +08:00
Neptrue_Lin 9ccb2da07c Merge pull request #771 from chr331/fix/caps-lock-punctuation
fix(windows): Caps Lock 开启时使用英文标点
2026-10-10 18:17:39 +08:00
chr331 72da84bbc5 test(windows): cover Caps Lock punctuation policy combinations 2026-10-10 15:12:23 +08:00
imluyg 23f943a044 fix(windows): 标点提交带上造词已选前缀
部分选词后,已选汉字同时存在于 composition 范围和 word_for_creating_word。
标点提交只组装「候选 + 标点」并整段替换该范围,已选汉字就随范围一起消失了。
Enter 和中英文切换都已经带上前缀,标点是同一族里唯一漏掉的一处。

前缀只加在写进 composition 的文本上:成对标点、智能标点指纹和
_NoteCommittedChinesePunctuation 仍读未加前缀的串,标点自身的选取不受影响。

#747
2026-10-10 11:49:14 +08:00
chr331 2b856c41b3 fix(windows): use English punctuation while Caps Lock is enabled 2026-10-10 04:34:14 +08:00
xiaozhu1337andfanlusky 9c0f6da9be feat(windows): add opt-in per-app Vim Escape mode (#737)
* feat(windows): add opt-in per-app Vim Escape mode

* fix(windows): 改用 config.toml 的 vim_mode_apps 并移除 yaml-cpp

TSF DLL 会注入所有宿主进程,约定保持 /Zc:threadSafeInit- 且不加全局构造器;
yaml-cpp 带命名空间级 std::string 数组和线程安全局部静态,违背这一约定。
配置改为 [input] vim_mode_apps = [...],由 DLL 内的小型解析器读取,
沿用 config.toml 的时间戳缓存;键写入出厂模板与工作配置,升级合并保留用户值。

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-09 16:59:05 +08:00
AkaneIwasaandfanlusky 568d07f513 feat(windows): 添加中日英三语轮换 (#717)
* feat(windows): 添加中日英三语轮换

* fix(windows): 三语轮换改为快捷键开关并修复两端状态不同步

- 开关移到「快捷键 → 中英文切换」下的「允许中日英循环切换」,配置键改为
  [keybindings] trilingual_cycle;输入模式恢复为中文/日文,二者互不影响
- 循环请求带显式标记位和请求方自身的中英/日文状态,Server 据此计算目标语言;
  普通 VK_SHIFT 仍是中英切换,不再被误当成循环
- Server 开关在请求途中被关闭时仍回复,按中英切换处理,TSF 只丢弃后续排队按键,
  不再超时重连
- 已发出的循环回复未被应用(含 Superseded)时走完整重连,避免日文状态两端不一致
- 循环切换补上光标处状态提示和标点同步
- 切换语言时复用暂存的另一语言会话,不在按键路径上重建引擎和打开 msime.db
- 配置读取的语言与开关改为原子量;TrilingualCycleChanged 的能力过滤移到发送方
- 中日切换不再重发 TrilingualCycleChanged;结束组合被拒绝时不计入统计

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-09 15:44:41 +08:00
Ehtasham-Yasin 0fe601e259 feat(server): submit converted shuangpin with Shift+Enter 2026-10-06 15:09:29 +02:00
Lu Fan(SonnyCalcr) 53ee180ec3 feat(engine): 直接辅助码句中四码可用第二位辅码大写结束 (#630)
直接辅助码的四码在句中原来只能补 / 结束(uiab/),现在也可以把第二位辅码敲成大写(uiaB)。
设置页「直接辅助码句中四码标记」两个勾选框可多选、至少留一个,默认只勾 /。

- 引擎:拼写图新增 Aux2Upper(非缩写)。开着大写标记时大写字母只能当第二位辅码,
  走不通再退回不分大小写的旧规则,第一位辅码敲大写(uiAuiQ)照常可用。/ 的收键受勾选控制。
- 协议:DirectHelpcodeChanged 载荷改为 "0"/"1"/"2"(2 = 开着但 / 不是编码键),
  TSF 据此决定吃不吃 /。
- 配置:新增 helpcode.shuangpin_direct_helpcode_slash / _uppercase,同步写入出厂模板、
  工作配置和两个设置宿主的读写映射。
2026-10-05 20:43:02 +08:00
Lu Fan(SonnyCalcr) 3c79876205 Merge pull request #629 from fanlusky/feat/v-mode
feat: 新增 V 模式,数字转中文与算式计算
2026-10-05 17:51:21 +08:00
Lu Fan(SonnyCalcr) b391fd616b feat: T 模式支持输入指定的日期和时间 (#626)
Shift+T 后面除了 rq / sj / xq 唤醒词,还可以直接写数字:

- YYYYMMDD、YYYY/M/D、M/D(年份取今年)出那一天的日期格式,星期按那一天算
- YYYY/M 出年月格式(新增 date:ym_* 五种)
- h:mm、h:mm:ss 出时间格式,写了秒才给带秒的格式
- YYYYMMDDhh[:mm[:ss]] 出日期加时间的格式

指定的日期时间沿用唤醒词的格式 ID,学到的顺序、置顶、固定位置跟着走。

输入形状放在 engine/contracts/date_time_input.h,TSF、Server、引擎共用:
数字、/ 和 : 只在还能接成某种形状时才进输入串,接不上的数字仍是选词键,
/ 和 : 仍是标点,Shift+数字始终选词。小键盘数字同样可以输入。

Refs #531
2026-10-05 03:42:12 +08:00
Lu Fan(SonnyCalcr) 5208753ce8 feat: 句中辅助码新增大写字母触发方式 (#620)
* feat(engine): add uppercase trigger for shuangpin mid-sentence helpcode

With the uppercase trigger on, an uppercase letter right after a complete
two-key syllable opens a mid-sentence helpcode block by itself, as if the
backtick had been typed before it: ulpbXihfa is ulpb`xihfa, and a following
uppercase letter is still the second code (ulpbXYih is ulpb`xYih). The raw
input keeps the typed letters; the block rules live in
contracts/mid_sentence_helpcode.h so TSF, Server and engine parse alike,
including the semicolon final, which no longer counts uppercase blocks.

The scheme now hands the parsed layout (source index and decorations) to the
request, so selection advance and preedit no longer re-parse the raw input.
Adds the MidSentenceHelpcodeUppercaseChanged worker opcode (31).

* feat(windows): count uppercase helpcode blocks before backtick and semicolon

With MidSentenceHelpcodeUppercaseChanged on, TSF uses the shared block rules
when deciding whether '`' opens a block and whether ';' is the ing final, in
both the synchronous and the deferred key classification.

* feat(server): wire the mid-sentence helpcode uppercase trigger

New helpcode.shuangpin_mid_sentence_helpcode_uppercase (default off) in both
config templates, both settings hosts and the settings page as a third
trigger checkbox. It takes effect only with mid-sentence helpcode on and
direct helpcode off; the Server applies it to the semicolon final rule and
broadcasts MidSentenceHelpcodeUppercaseChanged to TSF.
2026-10-04 21:00:41 +08:00
Lu Fan(SonnyCalcr) 22fb1e2569 Merge pull request #619 from fanlusky/feat/shuangpin-direct-helpcode
feat: 双拼直接辅助码(万象式,无需引导键)
2026-10-04 19:32:10 +08:00
Lu Fan(SonnyCalcr) 58c1070d3a Merge pull request #586 from ouchihao/codex/fix-509-mode-switch-prefix
fix(windows): preserve selected words when switching input mode
2026-10-03 17:36:28 +08:00
fanlusky d9b5b20004 fix(windows): clear the selected word when the host ends the composition
OnCompositionTerminated only purged the keystroke buffer, so the selected
word and the toggle fallback spelling survived a host-forced termination.
The next Shift toggle then committed them at the new caret.
2026-10-03 17:26:03 +08:00
Jia Sui 2557ee4a63 docs: 修正 AGENTS.md 与注释中与代码不符的五处描述
spec 校对时逐条核对「文件:行号」锚点发现,五处文档描述与代码现状不符:

- windows/AGENTS.md:格式化遵循的是本目录 .clang-format,仓库没有根目录 .clang-format
- 根 AGENTS.md:engine/... 前缀 include 只对 server/ 成立,windows/ 无仓库根,走相对路径
- scripts/format.sh:注释声称 CI 会跑 generate.py --check,实际没有任何 CI 作业调用
- server/AGENTS.md:测试源文件数与三处行号锚点过期
- server/CMakeLists.txt:注释里的 "eleven add_test()" 实际是 24 个,删去具体数字

纯文档与注释改动,无行为变化。
2026-10-03 08:29:50 +08:00
fanlusky 233ac9ef33 feat(engine,server,windows,ui-html,installer): 句中辅助码触发键可多选,新增分号
设置页「句中辅助码触发键」可勾选反引号和分号,至少保留一个。

- 分号只在 AcceptsMarkerAt 成立(这一节是偶数键)时才当触发键;微软/搜狗/紫光双拼的
  ing 韵母只跟在奇数键之后,两者不抢同一个位置,判断时分号先让给 ing 韵母
- 无论按哪个键,TSF 按键缓冲、Server raw 与引擎输入串里都记成反引号,引擎解析不变
- 新配置项 helpcode.shuangpin_mid_sentence_helpcode_backtick / _semicolon 同步写入出厂
  模板、工作配置与两个设置宿主
- opcode 28 保持「反引号是否触发」、载荷单个 "0"/"1"(TSF 会丢弃更长的帧);分号走新增
  的 opcode 29,旧 DLL 忽略它
2026-10-02 23:55:46 +08:00
Ouchihao 6bcf582aff fix(windows): preserve selected words when switching input mode 2026-10-02 22:34:08 +08:00
fanlusky 45c473158a feat(engine,server,windows,ui-html): 双拼预编辑可显示转换后的全拼
外观里行内预编辑与候选窗预编辑各加一个「双拼显示转换后的全拼」开关,默认关闭。
开启后在双拼方案下:行内原始按键显示不带分词符号的全拼,拼音分词显示带分词符号的
全拼;候选窗显示带分词符号的全拼。

- 引擎新增 get_shuangpin_preedit_forms,同时给出原串切分与全拼切分,不受
  shuangpin_preedit_mode 影响。
- 原始按键样式下 TSF 不等 Server 回包,开关开启且为双拼时 Server 下发的实际样式
  改为 pinyin,回包里再按用户样式去掉分词符号;换方案时重新下发。
- 转换后字母数与按键不再对应,Server 在预编辑末尾附光标映射表
  (engine/contracts/preedit_caret_map.h),DLL 用它定位光标和处理方向键,
  无表或长度不符时回落到原有字母映射。
2026-10-02 16:14:50 +08:00
fanlusky 31241d22ed fix(engine,server,windows): 句中辅助码支持光标在句中插入,上屏按不带约束的排序调频
光标用箭头或 Ctrl+箭头移回句中后敲反引号,TSF、Server、引擎三处都要求光标在串尾,
反引号被当成标点,光标前的预编辑连同「·」一起上屏。改为只看光标前的部分:光标前是
完整的两键音节就收,落在音节中间或已有段的前面不收。形状规则新增
FanyImeMidSentenceHelpcode::AcceptsMarkerAt,三处共用;引擎的光标插入路径同时接受
句中的第一码和大写第二码。

句中辅助码筛过的列表里,要的词往往已经排第一,选第一个不调频,调了也是在筛过的列表
里排位,下次不敲辅助码时排序不变。现在带句中辅助码上屏词库候选时,改用去掉约束后的
候选作排位参照,选的是第一个也照样调;空格、数字键、鼠标点选都走 ProcessSelectionKey,
引擎自己的上屏路径同样处理。
2026-10-02 12:43:16 +08:00
fanlusky fae80660c1 feat(engine,server,windows,ui-html): 双拼句中辅助码
在完整音节后敲反引号,接着的字母筛这个音节上的字:第一码大小写均可,
紧跟的大写字母是第二码,之后可以接着打,一个输入里可以有多段。开关
helpcode.shuangpin_mid_sentence_helpcode 默认关闭。

约束在整句解码时生效,而不是解完再筛:
- Google 解码器在 add_char_qwerty 里送 extend_mtrx_nd 之前按约束过滤词条,
  锁住的字带着上下文重新整句解码;约束变化时整段重解,不污染增量搜索。
- kenlm 词格建图时按约束筛边,受约束的跨度不截断地查词库;神经重排只在
  词格 n-best 里挑,自动满足约束。
- 普通词条、云和 AI 候选按同一规则收尾校验;满足不了就不出整句。

反引号段在请求里换成手动分隔符,原串留给宿主回读与预编辑显示;选词推进
时剩余音节上的约束随原串保留。输入形状规则放在 contracts/mid_sentence_helpcode.h,
TSF、Server、引擎共用,新增 Worker opcode MidSentenceHelpcodeChanged = 28。
googlepinyinime-rev 已与上游解绑,AGENTS 与 UPSTREAM.md 同步说明。
2026-10-02 02:20:37 +08:00
fanlusky 9f5572c525 fix(build): 所有 MSVC 目标显式按 UTF-8 编译源文件
#548 修了 ngram,同类问题还有几处:源文件或头文件是无 BOM 的 UTF-8,
目标却没有 /utf-8,在代码页 936 的系统上注释会吞掉换行。

- ui:Controls.h 让 msimeui 与各 demo 单独构建失败,/utf-8 设为 PUBLIC
- server/tests/portable:input_key_policy.h 让 MSVC 构建失败
- engine test_windows_ipc_contract、tests/pipe-probe、windows/tests:C4819
- windows DLL:此前只靠 fmt 的 INTERFACE 选项带来 /utf-8,改为显式设置,
  否则 L"“" 等宽字符标点会在去掉 fmt 后静默变成乱码
2026-09-30 15:37:13 +08:00
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 482e43c38e docs(windows): 按新的失败模型改写 AGENTS.md
去掉对按键回放、恢复前缀和「DeliveryAmbiguous 不能无条件本地重放」的描述,
新增「按键失败:不回放」一节:被吃掉的键要么处理、要么有意吞掉,绝不交还宿主、
绝不处理两次;失败时清组合、吞队列,从空开始。列出 Stale / Resync / Transport /
Offline 四类失败的来源与动作,说明新增失败点应走 _FailDeferredKey /
_ResetSessionAfterFailure 而不是单独调用 MarkNamedpipeSessionDirty*,以及翻页和
高亮移动键回复缺失是软错过。并注明派发 token 在代码里仍叫 deferredReplayToken。
2026-09-27 19:06:40 +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