Files
msime-windows/windows
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
..

windows —— TSF 文本服务

水杉输入法在 Windows 上的前端:一个纯 TSF 方案的文本服务 DLL,注入宿主进程,负责按键预判、焦点与线程归属、composition 的 edit session 写入,以及与 Server 之间的命名管道协议。候选窗、工具栏、设置页和实际的候选计算都在 ../server/,本目录只做宿主适配。

产品介绍、安装和功能说明见仓库根目录的 README;本目录的代码地图、TSF/COM 硬约定和改动要求见 AGENTS.md。

构建

从仓库根目录配置,源码目录是本目录:

cmake -S windows -B windows/build -A x64 `
  -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" `
  -DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build windows/build --config Release --parallel
ctest --test-dir windows/build -C Release --output-on-failure

Win32 用 -A Win32 和 x86-windows-static;安装包两种架构都要。scripts/ 下是本机开发用的生成、构建、注册和签名脚本,从本目录运行。

引擎契约来自 ../engine/contracts/,是本仓的目录,普通检出就有。TSF 只引用这些头文件,不链接引擎运行库。