Files
StellarX-kaifa/开发记录/模块/Module-20261009-0007-阶段3键盘定向路由与Button接入实现.md

191 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 新增功能模块 / 模块重构
> 本记录描述通用输入系统阶段 3 的实际实现、生产换轨边界与验证证据。阶段 3 完成 KeyEvent 路由和 Button 接入,但不代表 TextBox 文本编辑、真实 Dialog FocusScope 或复合控件高级键盘行为已经完成。
## 基本信息
- 模块 ID:Module-20261009-0007
- 模块名称:阶段 3 键盘定向路由、Button 接入与 EX_KEY 生产换轨
- 状态:已完成;实现、自动回归、微软拼音/搜狗及 KEY1/KEY5 人工门槛全部关闭
- 类型:新增模块 / 架构演进 / 兼容性迁移
- 所属系统 / 子系统:StellarX Core / Window / Native Input / Focus / Keyboard Routing / Button
- 版本 / 分支:`master`,实现提交 `29df994`
- 环境:Windows + Visual Studio 2022 + MSVC v143 + EasyX + MBCS 工程配置
- 负责人:Codex 协作实现
## 背景与目标
- 阶段 1 已建立 WndProc 采集、规范化和跨线程 NativeInputQueue;阶段 2 已建立稳定身份焦点树和 FocusScope 机制。
- 阶段 3 要把 `KeyEvent` 从当前逻辑焦点叶子沿逻辑父链定向冒泡,并接入 Tab、Button Enter/Space、窗口激活与 IME 所有权门控。
- 最终切片必须一次性停止旧 `EX_KEY -> Control::handleEvent()` 生产交付,同时继续排空 EasyX 队列,避免新旧业务源并存或内部队列累积。
## 实现边界
### 已实现
- 公共 `RoutedKeyEvent`、`KeyRouteResult` 和 `Control::handleKeyEvent()` 默认空实现。
- 内部 `KeyboardRouter`:固定容量原始身份路径、逐节点重新解析、叶子到祖先冒泡、独立的停止冒泡与阻止框架默认动作结果位。
- 单事件 mutation transaction 与可嵌套 `managedDispatchActive` 边界;用户回调后不保留裸 `Control*`。
- 采集侧原子 `{ keyboardFocusActive, generation }` 快照;失活、跨激活代次、IME 所有和 Alt/System 输入在业务路由前被抑制。
- 失活和模态入口沿当前焦点路径取消 keyboard armed 等瞬时状态,但保留逻辑焦点。
- Tab / Shift+Tab 冒泡后默认动作:每次按键重新采集活动 FocusScope 的当前焦点树,支持循环、无焦点首/末项与 `preventFrameworkDefault`。
- Button 独立 `keyboardArmed`:Space 首次 KeyDown 按下、KeyUp 确认;Enter 首次 KeyDown 确认;自动重复不重复激活。
- Escape、焦点丢失、模式变化、隐藏、窗口失活和模态隔离取消 armed;按压色与阶段 2 焦点框叠加。
- 生产默认改为 Routed;Window 主循环和 Dialog 模态循环同时继续排空 `EX_KEY`,但不再交给 `handleEvent()`。
- `easyXKeyboardMessagesDrained` 与 `legacyEasyXKeyboardBusinessDeliveries` 诊断计数,用于证明排空持续且旧业务为零。
- `WM_GETDLGCODE` 窄同步分支:先取 EasyX 旧窗口过程结果,只对明确普通 Tab 查询合并 `DLGC_WANTTAB`。
- UI 运行器可指定 fixture、结果根目录和 Legacy/Routed 预期,并输出每场景结构化耗时 `run-summary.json`。
- 生产端到端 `keyboard-routing` 场景覆盖 Space 激活首按钮、Tab 换焦和 Enter 激活第二按钮。
- Window 背景与非模态 Canvas 空白左键清焦点并消费,避免焦点框残留和重叠兄弟穿透。
- Table 内部分页 Button 焦点变化提升为 Table 整体重绘;同父 Canvas 的下层持久 coverage 命中上层时,上层完整恢复优先于局部脏子树提交。
### 明确未实现
- `TextInputEvent` / `CompositionEvent` 向 TextBox 的正式业务交付;当前仍只排空、观察和计数。
- TextBox UTF-16 编辑内核、自绘、caret、选区、剪贴板和 IME anchor。
- Table 方向键/翻页命令、TabControl roving-tab-stop 和方向键切页。
- 真实 Dialog FocusScope、模态 Dialog 子树键盘路由、默认按钮和 Escape 关闭;当前模态循环抑制底层 Window 业务路由。
- `DLGC_WANTALLKEYS`、`DLGC_WANTCHARS`、`DLGC_WANTARROWS`;没有相应功能前不提前声明。
- WndProc 生产消费白名单;阶段 0B 没有给出跨 IME 安全白名单,因此原生按键仍按默认策略采集并转发。
## 关键契约
1. NativeInputQueue 是生产键盘业务源;EasyX `EX_KEY` 仅排空和观察。
2. Routed 与 LegacyCompatibility 互斥。Legacy 仅供测试在事件循环锁定前选择,不能运行中切换,也不是公开兼容开关。
3. 每次只从队列弹出一条事件,释放队列锁后才进入焦点、控件和用户回调;不得预取整批事件。
4. 路由开始时构造原始身份路径,之后逐节点重新解析。早期回调删除的后续节点跳过;回调改变焦点只影响下一事件。
5. `stopBubble` 与 `preventFrameworkDefault` 独立;二者都不能回溯改变已经完成的 WndProc 返回值。
6. 原始 `VK_PROCESSKEY` 是单键 IME 所有权威;`imeComposingAtCapture` 是独立生命周期轴。两者任一命中时,阶段 3 不执行控件、Tab 或默认动作。
7. 激活门控同时比较事件采集快照和当前原子 generation;窗口重新激活不能让旧周期积压键重新生效。
8. Button Space 在 KeyUp 确认,Enter 在首次 KeyDown 确认;命令阻止框架默认动作但继续允许祖先观察。
9. WndProc `WM_GETDLGCODE` 只在 Routed 普通 Tab 查询时增加 `DLGC_WANTTAB`;System/Alt+Tab 不认领。
10. `EX_MOUSE` 永久保留在 `Control::handleEvent()`;本次迁移只切断 `EX_KEY` 业务交付。
## 六个实现切片
### 切片 1:公共契约与互斥门控
- 新增 routed key 公共类型、Control 默认入口和内部 Legacy/Routed 模式。
- Window 与 Dialog 共用旧业务交付判定,测试 Routed 模式从第一天起就先关闭自己的旧 EX_KEY 业务路径,避免双投递。
### 切片 2:身份路由与 mutation
- 固定容量 256 的 `FocusTargetId` 路径;循环、超深或构造期失效时在调用控件前 fail closed。
- Control、Canvas、Table、TabControl 提供递归身份分发;借用指针不逃逸当前调用。
### 切片 3:激活、IME 与模态门控
- NativeInputEvent 携带采集时激活快照。
- `VK_PROCESSKEY`、标准组合生命周期内按键、Alt/System、失活、跨代和模态期底层输入不进入业务回调。
- 失活与模态入口调用焦点路径暂停协议,保留逻辑焦点。
### 切片 4:Tab 默认动作
- 无 Ctrl 的 Tab KeyDown 在冒泡后执行;Shift 反向、边界循环、无焦点进入首/末项。
- 每键重新获取焦点树;Table 分页 Button 在首次 draw 前的空结果不缓存。
### 切片 5:Button 键盘状态机
- `keyboardArmed` 与鼠标/Toggle 的 `click` 分离,只在绘制层合并按压视觉。
- Space/Enter、自动重复、Escape 和全部取消边界均由单测覆盖。
### 切片 6:生产原子换轨
- `Window::routedKeyboardPipeline` 生产默认改为 true;主循环和模态循环同时切断旧 EX_KEY 业务交付。
- 新增窄 `WM_GETDLGCODE` 分支、排空/旧业务计数、生产端到端 UI 场景、迁移说明和换轨前后 A/B。
- `TextInputEvent` 与 `CompositionEvent` 不提前交给控件,避免阶段 3 越界实现 TextBox。
## 实现文件
- `include/StellarX/KeyboardRouting.h`
- `include/StellarX/InputEvents.h`
- `include/StellarX/Control.h` / `src/StellarX/Control.cpp`
- `include/StellarX/FocusManager.h` / `src/StellarX/FocusManager.cpp`
- `include/StellarX/Button.h` / `src/StellarX/Button.cpp`
- `include/StellarX/Window.h` / `src/StellarX/Window.cpp`
- `src/StellarX/KeyboardRouter.cpp`
- `src/StellarX/Dialog.cpp`
- `src/StellarX/SxNativeInput.cpp`
- `src/StellarX/internal/SxKeyboardRouterInternal.h`
- `src/StellarX/internal/SxEventLoopInternal.h`
- `src/StellarX/internal/SxNativeInputInternal.h`
- `Canvas`、`Table`、`TabControl` 对应递归分发实现
- `tests/StellarXTests.cpp`
- `tests/UiFixture.cpp`
- `scripts/run-ui-tests.ps1`
## 自动验证
### 逻辑与构建矩阵
- Debug x64:67/67。
- x64 ASan:67/67,无 sanitizer 报错。
- Release x64:67/67。
- Debug Win32:67/67。
- KEY1–KEY6:全部编译/链接成功。
- 覆盖路由顺序、结果位独立性、删除/改焦点重入、激活代次、IME/Alt/System/模态抑制、Tab 当前树、Button 状态机、Routed 默认/Legacy 锁定和窄 `DLGC_WANTTAB`。
### 换轨前后 UI A/B
- 换轨前 fixture:`TestResults/Stage3CutoverAB/baseline/StellarX.UiFixture.exe`。
- 基线 SHA-256:`3F6267B6D5EE85A7A97244C3DAE21DB44CEFBD185F56A38516E5E2F6F6234974`。
- 共同八场景执行 5 对 5:Legacy 40/40、Routed 40/40。
- 结构化证据:
- `TestResults/Stage3CutoverAB/legacy/20261009-152906-951-18728`
- `TestResults/Stage3CutoverAB/legacy-expanded/20261009-153941-779-34800`
- `TestResults/Stage3CutoverAB/routed-formal/20261009-153745-045-33364`
- `TestResults/Stage3CutoverAB/routed-expanded/20261009-154044-849-12588`
- Routed 首轮 ownership 为 `1330 ms`,其余四次 `531–601 ms`,与 Legacy `517–709 ms` 重叠;其它场景平均差为 `-19 ms` 至 `+43 ms`。未观察到稳定耗时劣化,但仍承认交互桌面存在残余偶发波动。
### Routed 正式 UI
- 连续两轮九个业务场景共 18/18;普通/模态原生关闭检查每轮通过。
- `keyboard-routing` 2/2:首 Button Space 激活一次,Tab 聚焦第二 Button,Enter 激活一次。
- `input-shadow` 两轮一致:
- Routed 4000/4000,Down/Up 各 2000;
- EasyX `EX_KEY` 排空 4000;
- 旧 EX_KEY 业务交付 0;
- pushed/popped 4000/4000、depth 0、maximumDepth 40;
- sequence 严格、零 fault、零规范化异常、内存增长 0。
- 最初受限桌面运行因 `MoveMouseToScreen` 返回 Access denied,未进入产品断言;授权交互桌面后全部重跑,失败样本不计作产品回归。
- PowerShell 7 宿主同时注入 `PATH` / `Path` 时,旧 `test.ps1` 会让 MSBuild 在创建 `CL.exe` 前以 `MSB6001` 失败。运行器已改为只在检测到重复键时通过 `cmd` 子进程删除大写 `PATH`、保留同值 `Path`;不修改调用者环境。修复后 PowerShell 7 与 Windows PowerShell 5.1 的 Debug x64 均为 66/66。
### 人工反馈修复自动复验
- 新增 `CanvasBackgroundPointerDownClearsFocusAndConsumes` 后,四配置单测均为 67/67,KEY1–KEY6 全部编译/链接成功。
- 扩展 `focus` UI 连续 3/3 通过,证据位于 `TestResults/ui/20261009-220625-941-15096`;覆盖 Table 分页焦点框即时出现/消失、Window/Canvas 空白清焦点和 KEY5 同批上下层重绘。
- 同一 fixture 的完整 UI 1/1、九个业务场景 9/9 通过,证据位于 `TestResults/ui/20261009-220940-941-31488`;Tooltip、Dialog、input-shadow 与 keyboard-routing 均通过。
- 移除 UiFixture 中用于定位问题的临时 Button JSON 诊断字段后,用户从项目终端重新执行最终源码完整 UI。证据位于 `TestResults/ui/20261009-223506-755-18932`:fixture SHA-256 为 `F439C0636CD6B979A47C4594BC5AB3728F6342F49602FE43078435011A877793`,与 `run-summary.json` 一致;九个业务场景 9/9 通过,普通/模态原生关闭均通过。
- 自动化过程中曾分别出现一次沙箱 `SendInput Access denied`、一次 Button 点击未命中和一次 ownership 等待超时;失败证据保留。随后定向 3/3 与完整 UI 通过只说明未形成稳定产品回归,不把这些桌面输入波动写成从未发生。
## 兼容性与迁移
- `Control::handleEvent()` 仍保留,但生产循环不再向其交付 `EX_KEY`;第三方源码控件必须把键盘逻辑迁到 `handleKeyEvent()`。
- `EX_MOUSE` 和既有鼠标命中/分发保持不变。
- 新增虚接口再次改变 `Control` vtable;已编译的第三方派生控件必须使用当前头文件和 Core 库重新编译。
- 默认 `handleKeyEvent()` 为空,因此未迁移的第三方控件不会崩溃,但其旧键盘能力不会继续生效。
- 详细迁移示例见 [`../MIGRATION.md`](../MIGRATION.md)。
## 人工门槛与后续
- 微软拼音与搜狗已分别完成真实候选输入复验:候选出现/取消和 Space 交互正常,未报告卡顿、重复提交或输入法按键误触发 Button。
- Button Space/Enter 行为已由人工确认符合锁定语义:Space 按下显示按压态、松开执行;Enter 首次按下立即执行且不显示持续按压态。
- KEY1/KEY5 人工反馈暴露三项问题:Table 分页焦点框延迟、Window/Canvas 空白点击不清焦点、同父 Canvas 下层重绘覆盖 D-2 上层区域;对应实现和自动回归已补齐。
- 模态 Dialog Tab 不可用和非模态作用域顺序属于阶段 4 真实 Dialog FocusScope 范围,本轮不做临时补丁。
- 用户已定向复测 KEY1 与 KEY5,暂未发现问题:Table 分页焦点框即时出现/消失,Window/Canvas 空白点击清焦点,KEY5 D-1 → D-2 未再发生覆盖。
- 阶段 3 验收至此关闭;后续可单独讨论阶段 4 的 Table、TabControl 和真实 Dialog FocusScope 接入,但本记录不提前宣称这些能力已经实现。
## 落地信息
- 关联功能变更 ID:无
- 关联 BUG / Fix:`BUG-20261009-0001` / `Fix-BUG-20261009-0001`;`BUG-20261009-0002` / `Fix-BUG-20261009-0002`
- Commit:`29df994`(阶段 3 键盘路由、生产换轨与焦点重绘收口)
- PR:无
- 发布版本:未发布
- 相关文档:
- [`Module-20261001-0001-通用输入焦点与键盘路由系统设计.md`](./Module-20261001-0001-通用输入焦点与键盘路由系统设计.md)
- [`Module-20261007-0005-阶段1输入采集与规范化实现.md`](./Module-20261007-0005-阶段1输入采集与规范化实现.md)
- [`Module-20261008-0006-阶段2通用焦点管理器实现.md`](./Module-20261008-0006-阶段2通用焦点管理器实现.md)
- [`../MIGRATION.md`](../MIGRATION.md)
- [`../TESTING.md`](../TESTING.md)