Skip to content

[R2][输入验收] Qt IME 候选窗与固定拼音交叉验证 #155

Open

Description

@JavaZeroo

实现已合入,剩余为验收(2026-11-01)

Qt IME组合/提交/取消由#205(dea6065)和#230(a44bd2c)进入main;旧PR165已关闭,不再列为“待合入实现”。固定微软拼音已有preedit→cancel→commit“你好”精确6B的限定证据。父项#30。

最新原版对照

N11报告已由#302合入。每侧3次,Qt为历史冻结5fab30b,官方WindTerm2.7.0:输出后即时/延后点都保持候选面板旧位;resize后延后点都更新。不能继续将输出后不动列为TermForge独有故障。

字体阶段原版合成Ctrl+=未触发可见放大,无法比较;密集亮度带只支持候选面板几何观察,没有证明窗口身份/归属。原版运行收尾失败保留,不称整次native通过,presented_done仍未知。

剩余验收

  • 明确候选窗身份/关联及覆盖边界;几何识别不能替代归属证明。
  • 先确认原版字体放大的有效操作,再做组合期字体变化对照;不能把未触发算不跟随。
  • 区分组合期Ctrl-C实际交付、provider消费和child字节,未知继续保留。
  • 在当前正式源/包完成组合与输出、resize、字体、焦点/切换、隐藏、关闭/错误交叉,核对preedit不写入、commit不重复、cancel零字节及目标隔离。
  • 与[R2-07][文字] 验收复杂 Unicode、IME 与键盘/选择语义 #30/#35连接单屏96/144及实际glyph/交互验收,保留构建身份与所有失败;旧5fab结果不直接验收当前main。

Qt入口 · 固定拼音 · N11原记录。

本项保持开放。N1–N10、构建监督残留及原版关闭失败均保留各自证据;台账刷新不改变它们的结果。

Activity

  1. JavaZeroo commented on Oct 28, 2026

    @JavaZeroo
    OwnerAuthor

    已执行一次冻结 N1 原生观察,source 194101a9bc2bf1fe22cba2479677b7f3bcf30a49,runtime ZIP SHA 5caf0fc14b18069280dd2a43971577f5cc24ffa9de427ddfebd46df372426899,root plan SHA 80ede34f0318a5910378857ce4b3c0aef61990f321f914f871fb10a772c09006。结果为 执行前置失败,SendInput 批次0,不是IME功能通过或失败:两个接收端已ready,但 controller 的一次 SetForegroundWindow 返回false,故在真实输入前停止,无自动重试。

    目标 Qt GUI 线程在本次保留采样中 GetActiveProfile=S_OK、固定微软拼音/provider/profile、语言2052、active flags3、HKL8040804;Qt has_focus/ImEnabled为true,IMM open=false、conversion1。这些各自只是观察值,线程焦点不代替全局前台,IMM open也不代替TSF转换是否可用的结论。未取得提交/取消/Ctrl-C/候选关联的功能证据。

    controller 对精确自有 Job 发起一次终止后,root已reap,Job0/5/0、cleanup verified;接收端没有自然完成/complete.json。外层70s监督未超时、未强杀,自然CLI1、Job0/11/0。7207源/依赖、158旧原件、89 lifecycle原件前后零变;旧0db包、r1/r2草稿及本次失败均保留。

    之后独立只读环境探针看到 Session1 的 WinSta0/Default 与输入desktop Default(Job0/4/0),不支持该时刻已切Winlogon的假设;它不能倒填N1瞬间的拒绝原因。接续仅调查前台授权/实际FG和TSF只读状态,不重跑原批次。最终full尚未执行,#155/#30/#31保持开放,native acceptance与实际呈现仍null。

  2. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    N2已按新source 0deb19bf4da73a1cc0e82f398925c540da7edb55 唯一执行并停止,仍为 0个输入批次 / 0次Shift / 0截图,不判IME功能。与N1不同,实际全局foreground已是exact Qt root,故正确跳过SetFG/鼠标;随后首次读取仍由receiver写入的native-a.bin时,采集器File.ReadAllBytes触发Windows共享冲突。接收端允许RW/Delete共享,但默认reader的FileShare.Read不允许现有writer;这是采集工具前置缺陷。

    精确root Job按失败路径终止一次后0/5/0、rootreaped/cleanupverified;outer自然CLI1、Job0/13/0、无timeout或外层终止。7229源码/依赖及641旧原件前后0变。原完整ZIP已本地留存,SHA 9db061b42e108e623b11e57195d866d7a64dda6a33f6c52ce9b38196d3e5c656。原N2不重跑。

    后续仅修共享live-reader,以合成普通文件持写句柄形成确定性RED→GREEN;postexit最终raw/receiver判据不改。新的N3必须另冻结同0deb程序、新采集器与单次计划,不通过重复旧批次取绿。最终full及真实拼音提交/取消/候选关联仍未验收。

  3. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    原生观测 N3/N4 已分别执行各一次,均保留失败,产品输入批次仍为 0;没有形成 native IME 功能结论。固定软件源为 0deb19bf4da73a1cc0e82f398925c540da7edb55,打包 EXE SHA256 fb3df045ddd8654af4cfbf70d6aa4820553385aa4b742075282e89346d7cd596。

    N3 在首张全屏 PNG 超过原采集上限时停止;N4 仅修正采集 ROI 为自有窗口与 monitor 交集,实际 976×679、662704 像素,PNG 编码完成为 2123283B,仍超过工程采集上限 2097152B,因此首张未保存、输入/Shift/候选结论均为0/0/unknown。实际 Qt GUI 线程处于固定微软拼音 profile、IME open=true/native conversion1025,借用输入上下文已释放;这只是运行环境观测。

    N4 保留失败 metadata,exact owned root Job 请求一次终止后 root124、reaped、Job0/5/0,outer CLI1自然退出、Job0/13/0,无 outer timeout/强制清理。源/Qt/ConPTY/CRT共7229项及先前641文件后验零变化;N2/N3/N4原ZIP已逐文件解压校验,N4 ZIP12518B/SHA256 9c7c23ab04b9cc00e86c15791d498d545f16c510509e3e12d6ad061bd52fec6c,均仅私有保留。不会重复旧case。

    同HEAD hosted Qt PR检查6/6、14.90s通过(run40795030570/job123189306275,offscreen);默认PR检查通过。但push默认Windows job123188586384为60/61,shell selection场景内部deadline,原因未明,不能由PR绿覆盖。下一步只修采集预算并复审,输入序列、receiver原字节oracle与原时间预算保持。#155保持开放,PR165仍draft,main尚无此实现。

  4. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    N5 一次原生执行已停止,固定source/EXE仍为0deb19b及既有身份。6批OS虚拟键N/I/H/A/O/Space,各SendInput2/2、焦点guard通过;5张实际屏幕图共2393505B。Qt收到5次非空preedit,无commit事件,两个最终私有raw均0B。原2秒阶段在 exact nihao commit 未满足,未继续B/取消/Ctrl-C/自然finish,不称native通过。

    root查看原PNG,候选第一项“你好”,同时存在微软拼音“启用来自必应的建议”首次提示。该提示与Space未提交同时存在,因果未建立;先独立处理VM首次环境,不加长期限或盲重跑产品。

    失败清理:exact root Job一次Terminate后root124/reaped、Job0/5/0;outer自然CLI1、Job0/13/0,无outer超时/强制终止。7229源/依赖+641原证据后验0变。40个原成员及ZIP逐SHA校验,ZIP2435732B/SHA256 6c16723b609773b4c2aaf3a043e70085fae08a4491382bb580a81b17b29a3515,私有保留。

    另确认PS5.1 JSON小数类型Decimal未被原Numeric-Vector接受,导致4张cursor mapping unknown;原件不改。N6草稿只补Decimal,离线执行实际Snapshot纯mapping片段,对5个冻结JSON为旧1/5、新5/5匹配字面矩形,6无效向量拒绝。这是离线分析修正,不是candidate/native验收。N6执行0、配置false,待复审及VM环境准备。

  5. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    旧正文状态留证:N1–N8 与既有 source/package 身份;由 N9 当前摘要接续,原失败不追认为成功。

    最新原生进展(仍未合入 main)

    PR165当前源5bbf86dc已整合main05f04d0及专用CI补线;app相关源/核心头/根CMake相对冻结C6无变化。C6实际包347136B EXE/SHA 07c19d17795da0250d26b615e7bb7ca651a67b9766074bc204d0aabf14d8a787,41文件ZIP/SHA 0624f492f582d2c11abea527b5f3e19ed2c67eee3e7d5ff59384343c846375c1。独立构建与4CTest(原IME5组、console8例、normal/cancel)通过、自然Job0/176/0及0/39/0。C6首次Hosted Qt是6/6,未选择新console;新的CI已补构建和regex,结果另核,不追记成7/7。

    N7仅一次、10个输入批次:实际A commit事件一次UTF16 604f7d59,child准确6B e4bda0e5a5bd;B出现preedit后Escape空取消,child0B。后续回切A时private reader的FileStream构造遇共享异常,root被唯一强收尾124/Job0/5,outer自然1/0/13;无complete,未完成Ctrl-C/正常退出,整次失败。56原件逐文件验证,ZIP2549618B/SHA fcab13bf07ccfa3af0f2117f2793d907312313c5afd5179eda00aa9019f5cc66。

    N8仅一次、1个N键批次:新reader仅对确切HResult32做原期限内有界open重试(30项纯managed检查已过,不是原生计时证明);本次无该冲突记录,C6 observer先因native-state.json json-commit失败退出,expected/actual均4240B、QtFileError10、错误为拒绝访问。root自然exit1/Job0/5,outer自然1/0/13,无强收尾或timeout;A/B raw均0B。21原件ZIP2153188B/SHA 8b146918b6392621ce503173a7c46541c4aff87ca5819ad36d9cdf11fb063f7f。两次源7230+旧3973后验零变,另N7全部98本地文件在N8前后零变。

    下一步是无GUI/无IME输入的QSaveFile无reader/ReadWrite/ReadWriteDelete三模式覆盖对照。普通File.Move成功不等于QSaveFile覆盖成功;目前不得将N6唯一根因、N7/N8同根因或产品IME故障写成已证。N7照片可见字体变化后候选位置与新query不一致,异步/组合起点/provider策略及WindTerm对应行为尚未知;不先改产品策略。原生提交/取消已有上述限定事实,整项和完整candidate/焦点/终态回收验收仍开放。

    当前状态:实现候选已验证,真实原生验收未通过,未合入 main

    main 当前为 3aa78f068154387851ce6776a35077ec02da029e;PR165 的已发布冻结候选为 0deb19bf4da73a1cc0e82f398925c540da7edb55。正式 C++/Qt/固定 ConPTY 路线不变。preedit/commit/cancel/query、目标状态和原始字节功能测试已实现,公开事件与 offscreen 生命周期验证各自绑定候选;PR Qt 首次 CI 为 6/6。push 默认 Windows 曾在 ui-copy-system 第二轮内部 30s 截止失败,已留证在 #73;另一个工作流成功不覆盖该失败。

    隔离 Win11 固定拼音:N1–N4 各一次前置/采集失败,产品输入为0;N5 实际 N/I/H/A/O/Space 六批均返回2/2,有5次真实 preedit,但没有 commit,raw A/B 各0字节。截图中存在必应首次启用提示,提示与 Space 未提交的因果关系未知。

    N6 是独立一次观察,同一0deb包/41 runtime文件;实际 N/I/H/A/O 五批均2/2,尚未 Space、环境 click 或 continuation GO。约3.4s observer 因 native observer evidence gap 主动关闭:源码与最终事件可将首发原因限定到粘滞 write_failed_(JSON留证异常或PNG保存失败),原代码未保留具体操作/错误码。随后才发生 FocusOut/preedit清除和被拒收的 ASCII nihao,不能记为产品 IME 自发失败。该次截图没有必应面板,不能由此推断环境设置完成或根因消失。A/B raw仍各0字节,候选关联/实际呈现/提交与取消均未获原生验收。

    N6 子树自然exit1/Job0/5/0,外层自然exit1/Job0/13/0,无超时或Terminate;30份原件逐文件验证,ZIP 2247320B/SHA256 0cd20325817c795b985b44189a005a8608c2393326fbca63b0da10466708307c。7229源/依赖及641旧原件后验零变化。原始OS截图和本地标识只留私有。当前继续为观察器补首错操作与Qt错误上下文,保留原failstop;共享读句柄与QSaveFile原子替换冲突仅是假设,尚未确证。真实原生条件、最终完整矩阵和main集成仍未闭环。

  6. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    N10 独立几何观察已完成,保留为未验收结果,不重跑 N9:同一冻结 5fab30b/41-file 包,只有 N/I/H/A/O/Escape 六批,返回12/12 INPUT,A/B最终均0B、commit0。四阶段各即时与延后采集,实际延后255/254/262/264ms(从即时采集返回起算,250ms仅采集方法,不是通过门槛),共10张图。

    输出后的即时/延后图、字体变化后的即时/延后图中,白色候选面板均仍在旧位置;resize后可见位置更新。这里只证明这些固定采集点的现象,原因、更长时段和与WindTerm的差异尚未确定,不能写成单即时帧问题已经消失。

    57个immutable packet、final57/complete精确绑定,26接受记录/20事件副本/10PNG逐项匹配;双receiver和UI自然exit0/Job0,无timeout或Terminate,外层自然exit1明确pending comparison。124-file归档1732132B/SHA cd77ffe31ee7c30d7a07e0f23d6a5ee553655c177cbcf0b1018af31dd418a57c;独立只读机械审0 finding。7253源/依赖+5314旧保护+2858 C6 tracked后验零变,N9全部180本地文件也逐个零变。

    原版pure 2.7.0参考入口也已单次bootstrap尝试,但在ResumeGui之前的desktop/station查询出现空句柄,GUI/导航/IME输入均0;旧错误未区分station/current/target,确切失败调用未知。唯一仍挂起的自有root强收尾124/Job0,外层自然1/Job0(total9),无外层timeout或Terminate。它没有产生原版IME/候选对照结果。后续只补具体调用错误上下文和输入前严格desktop验证,不改产品行为。

    本项/PR165继续开放和Draft,native/candidate acceptance及presented_done仍null;原始OS截图/本地身份不公开。

  7. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    原版 bootstrap 的“GUI 之前就停下”已定位到私有采集前置的确定性缺陷,并已在 VM 上验证修复前半段。

    本地因果证明(合成探针):创建 suspended 的 cmd.exe 后查询 GetThreadDesktop(TID) 返回 NULL(last_error=0);ResumeThread + 800ms 后返回 Default。也就是说,线程在从未运行过时不持有 desktop 句柄。

    这正好命中原失败:私有 bootstrap-native.cs/旧的 bootstrap.ps1 在 ResumeGui() 之前调用 Desktop(RootTid),其中 Name(GetThreadDesktop(targetTid)) 必然拿到 NULL,于是抛出笼统的 Missing desktop/station handle——上一批记录的“GUI/输入之前停止、确切失败调用未知”由此解释。

    修复(仅私有 harness):

    1. 前置探测改为非门控诊断 DesktopDiagnostics(),并让 Name() 指明是哪个句柄、附 last_error;
    2. WinSta0/Default(controller/target/input 一致)的硬前置移到 ResumeGui() + 等到 owned root HWND 之后再执行;
    3. 其余身份、Job、一次性 lease、清理合同不变。

    VM 实测(reference-2,一次性):bundle zip 26,141,357B / SHA-256 cf34290294f418ca857b17f27f0a7c7f904f062da87a3b03f8aaaa361c357425,4057 文件,stage exit 0。

    • desktop_before_resume:station=WinSta0、controller_desktop=Default、target_error=Missing target desktop handle (last_error=0) —— 直接留证旧前置确实无效;
    • desktop(resume 之后):station WinSta0 / controller Default / target Default / input Default,前置通过;
    • 原版 GUI 实际起来了:native_roots = 1 个 owned 可见顶层窗口,class=Qt5152QWindowIcon、bounds=[94,71,929,686]、pid=3336/tid=8680、owner=0;
    • 清理:Passed=true / CleanupVerified=true / RootExitCode=0 / JobAfterActive=0,无强收尾。
    • 新停止点:Original root is not actual foreground; no activation guess(gui_runs=1,ready_event=false,未发送任何 IME 键、无 UIA/几何结论)。

    下一步(仍为私有 harness 的有界项):

    1. 记录实际 foreground 的 HWND/PID/class(谁抢了前台:控制台宿主、PowerShell 窗口或桌面本身),再决定策略——明确一次受记录的 SetForegroundWindow 激活 vs 在无竞争窗口的会话里运行;二者都要先取证,不能用“重试到前台”取绿。
    2. 前台通过后再继续原 bootstrap 的 UIA/合成会话/ready 事件路径;之后才能做候选窗几何对照([R2][输入验收] Qt IME 候选窗与固定拼音交叉验证 #155)与 1049 样式/颜色观察([R2][调查] 补齐原版 1049 样式与活动缓冲区观察 #123/[R2][调查] 用受控 Qt 栅格核对原版 1049 当前颜色 #126)。

    本条不改变 #155/#165 的产品实现,也不关闭任何 issue;原 bootstrrap 失败记录与一次尝试的约定保持不变。

  8. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    前台阻塞已定位并解决,bootstrap 现在真正走到 UIA 阶段(reference-3,一次性)。

    前台事实(探针 + 实际运行):VM 交互会话空闲时 GetForegroundWindow() 是 Progman / "Program Manager"(pid 4884),没有任何应用窗口持有前台;owned WindTerm 窗口出现后不会自动获得前台(启动方是非前台进程)。

    采用的处置:一次显式、受记录的 SetForegroundWindow(不循环、不重试;失败即停止),并保留前后对照。reference-3 实测:

    before = hwnd 65800 pid 4884 class Progman visible=true
    called = true, last_error = 203, attempts = 1
    after  = hwnd 1310780 pid 9816 class Qt5152QWindowIcon visible=true
    foreground = true
    

    即:一次调用成功把 owned 原版窗口带到前台;last_error=203 只作观察记录,不作为判定。

    本轮在 reference-3 上实际达到的阶段:

    • gui_runs=1、root 窗口 1 个(Qt5152QWindowIcon、bounds [94,71,929,686])、前台检查通过;
    • UIA 可用:一次同步扫描得到 125 个元素(MainWindow / TerminalWindow / QMenuBar / StatusBar / QSplitter / QAction 等),说明 Qt 的 UIA/MSAA 桥在本 VM 是通的;
    • 清理 Passed / CleanupVerified / RootExitCode=0 / JobAfterActive=0;
    • 新停止点:UIA synthetic leaf absent; no verified expand route——UIA 树里没有出现合成 profile 的 TermForge Lab 组与 TF_UI_CMD 叶。

    这说明:之前从未验证过的“合成 profile 是否被原版 2.7.0 真正加载”现在是唯一的卡点;桌面/前台/窗口/清理链路已经闭环。

    下一步(有界):核对 2.7.0 的 profile 发现路径与格式(profiles.config 指向 TOOL\profile、session 写在 .wind/profiles/default.v10/terminal/user.sessions),用 UIA 125 行树与磁盘/日志证据确认它是未加载、加载到别处,还是 UIA 未暴露;确认后再决定是否需要单独一修。原一次尝试、不重试、不改产品行为的约定保持。

    本条不改变 #155/#165 的产品实现状态;bundle(26,141,762B / SHA a786f54903efa29a0836afb4c5afb109a01fb83683d074382371fb69ea5b86ce,4057 文件,stage exit 0)与 result 已按私有身份保存。

  9. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    合成 profile 的加载已定位:WindTerm 确实读取了我们的 profile,组节点已出现,缺的是会话叶。

    reference-4 实测(一次性,bundle 26,141,840B / SHA ef573f38f1dc5bc8b72135ac440a624e80599140e2fef67dda2f45eaecaf0e89,4057 文件,stage exit 0):

    • uia_attempts=1、uia_wait_ms=2:树在窗口出现时已就绪,不是加载竞态;
    • UIA 共 125 行,其中 synthetic_tag=synthetic-group 存在(ControlType.TreeItem,bounds [790,190,131,24])→ 说明 user.sessions 被读到了,session.group="TermForge Lab" 生效;
    • 但 synthetic-session(TF_UI_CMD)叶不存在,且该组节点没有 ExpandCollapse 可展开路由,所以走到 UIA synthetic leaf absent; no verified expand route;
    • 受控端 qt_ime_input_test.exe 在 guest 存在(358,912B,与固定包身份一致),ready_event=false 属预期(会话要等 End+Return 才启动);
    • profile 目录被原版实际写入(user.config、global/reopen.history、terminal/reopen.history),而我们的 terminal/user.sessions 未被改写——文件格式被接受,会话条目没有形成可见叶;
    • 清理 Passed / CleanupVerified / RootExitCode=0 / JobAfterActive=0。

    旁证:树里 ExplorerList 的 rect 是 {∞,∞,-∞,-∞}(无效),而组节点落在 [790,190](右侧),与已验的 research/ui/launch.py / benchmarks/tools/windterm_c0_settings.py 使用的同一 schema 相比,差异只在 process.arguments(我们的是带引号的受控 exe 命令行)与会话树所在面板/布局。

    下一步(有界):

    1. 用已验的 C0/launch 合成会话(/d /q + cmd.exe)做一次同环境对照,确认“同 schema 下叶一定出现”,从而把变量收敛到 process.arguments 或面板/布局;
    2. 若确认是面板/布局(reopen.history 记忆的停靠状态导致会话树未实例化),改为在合成 profile 中固定初始布局或直接走 launch 的已验证入口;
    3. 只有叶可见后才继续 End/Return、ready 事件与候选窗几何。

    本条不改产品代码、不关闭 #155/#165;bundle/result 按私有身份保存。

  10. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    UIA 叶节点缺失已做四组对照,结论是"UIA 不是本 profile 的可用入口",需要一次 harness 路径决策。

    已排除的变量

    对照 结果
    原始会话参数(受控 exe 命令行) 窗口正常,130 个 UIA 元素,只有 TermForge Lab 组
    改成已验证的 /d /q(无 BOM 写回) 完全相同:130 元素、同样只有组 → 参数无关
    清空 user.config/reopen.history(只剩 user.sessions) 仍只有组(124 元素)→ 累积状态无关
    组节点详情 raw_children=0、无 ExpandCollapse、无 SelectionItem;右侧 TreeView [770,190,151,106] 只有这 1 个 child;另一个 TreeView 的 rect 是 {∞,∞,-∞,-∞}(未布局)
    Right-arrow 展开(SetFocus + 前台键) 组仍然无子项
    CLI 会话入口探测(-s/--session/-n/--new-session/-h) 5 种参数下 WindTerm 都只是普通启动,没有拉起受控子进程 → 没有可用的命令行开会话入口

    已知的有效路径:已验收的 research/ui/launch.py 用的是 Frida + Qt 探针(qt_*.js)直接读 Qt widget 树,而不是 UIA。也就是说,本 bootstrap"依赖 UIA 暴露合成会话"的假设从未被验证,现在被证伪。

    建议(需要一次明确取舍,不是产品行为变更)

    1. 改走已验收的 Frida/Qt 探针:与 R1/UI 实验同一机制,可绕过 UIA 暴露问题;代价是 harness 会从"零注入"变成部署 Frida,必须在 lease/plan 里如实记录 frida_deploys>0,并保留一次性的注入身份与回收证据。
    2. 或放弃 UIA 叶节点作为前置:只在 UIA 中确认组节点存在,改用键盘/快捷键路径启动那一个合成会话(需要先证明快捷键能选中该条目)。
    3. 或把 bootstrap 的目标降级:不再要求驱动会话,只验证原版 GUI/组节点出现;这样 [R2][输入验收] Qt IME 候选窗与固定拼音交叉验证 #155 的候选窗几何对照仍无法进行。

    在拿到取舍前,我不改 harness 的注入策略;本条不关闭 #155/#165。

  11. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    UIA 路线追加两组实测:菜单栏是可用的激活入口,但"新建会话"是模态对话框。

    观察 1:菜单栏可通过 UIA 展开与调用

    • 窗口的 MenuBar 暴露 10 个 QAction:会话 / 编辑 / 搜索 / 选择 / 转到 / 查看 / 模式 / 工具 / 窗口 / 帮助(界面语言为 zh-CN)。
    • 对"会话"执行 ExpandCollapsePattern.Expand() 后,子项全部出现:新建会话(N) Alt+N、新建窗口(W)、断开会话(D)、重新连接会话(R)、打开会话...(O) Alt+O、最近打开(N)、保存会话(S)、日志(L)、关闭视图(C)、退出(X) 等。
    • 也就是说:UIA 不是完全不可用——菜单驱动是可用的,缺的只是"会话树节点"。

    观察 2:新建会话 触发模态对话框

    • 对 新建会话(N) 调用 InvokePattern 后,脚本执行 35 秒超时(RootExitCode=124、JobBefore/After 3→0、清理正常);说明弹出的对话框让后续 UIA 遍历或脚本收尾阻塞。本次没有对对话框做任何输入,也没保存任何东西。

    可行性判断

    • 若要继续走 UIA:下一步应是"新建会话对话框的字段填充"或"打开会话... + 预制会话文件",两者都能绕开"会话树不暴露叶节点"的问题,且不需要 Frida 注入。
    • 若对话框字段可编程(Name/Group/Protocol/Target/Arguments),则可以把受控 exe 作为 Target 填入并保存,再由 WindTerm 自己写出规范 user.sessions——同时解决"合成 schema 是否被接受"的疑问。

    本条不改变 harness 策略;#155/#165 保持开放。若你倾向 Frida 路线,我仍可在拿到确认后切换。

  12. 20 remaining items

  13. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    候选窗枚举结果(同一 VM/同一固定拼音)

    在组合态(ni'hao 已输入、未选候选)用 UIA 枚举所有顶层窗口,结果只有 4 个:

    {"rect":"1024,566,819,48","pid":4884,"name":"","class":"Shell_SecondaryTrayWnd"}
    {"rect":"0,720,1024,48","pid":4884,"name":"","class":"Shell_TrayWnd"}
    {"rect":"141,58,742,604","pid":3464,"name":"TermForge — local shells","class":"QWidget"}
    {"rect":"0,0,2048,768","pid":4884,"name":"","class":"Progman"}

    即:组合期间没有枚举到独立的候选窗窗口(任务栏/桌面除外,剩下只有我们自己的 Qt 窗口)。

    说明与边界

    • 组合事件确实发生(上一轮 preedit 序列到 ni'hao,并成功提交 你好),所以输入法在本应用里是工作的;
    • 该 VM 里的候选 UI 可能由 UWP TextInputHost 在另一会话/桌面上绘制,或当前输入法配置不显示候选列表,因此 UIA 顶层枚举看不到;
    • 因此不能据此判断"候选窗位置是否正确",本项剩余验收保持开放;
    • 下一步可改用 WM_IME_*/UI Automation 的文本模式,或直接在屏幕上检测候选窗矩形(本 VM 的截图通道已可用)。
  14. added a commit that references this issue on Oct 29, 2026
  15. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    候选窗观测结论:本 VM 配置下没有候选窗 UI

    在组合态(ni'hao 已输入、未选候选)用两种 UIA 范围枚举:

    范围 结果
    顶层窗口(Children) 4 个:任务栏、桌面、以及我们的应用窗口
    全部后代(Descendants) 61 个元素,全部属于 explorer(任务栏/小组件/搜索)与我们的应用,没有任何输入法候选窗元素

    同时:

    • 事件序列显示组合确实发生并成功提交(preedit 到 ni'hao → commit e4bda0e5a5bd);
    • 组合期间屏幕变化集中在应用窗口内光标格附近(全局 (295,157) 与变化框 (295,163) 对齐)。

    判读

    该 VM 里的固定微软拼音处于行内组合、无候选列表 UI 的状态(或被配置为不显示候选窗),因此:

    1. 我们的应用侧行为(组合预览锚定光标格、提交字节正确)是可验证的;
    2. 候选窗身份/位置在本环境不可观测,不能据此判定"候选窗位置是否正确";
    3. 该项需要换环境(真实交互桌面会话或启用候选窗 UI 的配置)才能继续。

    建议

    • 本项的"候选窗位置/身份"子项按"本环境不可观测"记录;若你希望继续,需要允许在该 VM 上调整输入法设置(启用候选窗),或在你的机器上做一次对照;
    • 其余子项(Ctrl-C 与 IME 消费关系、native acceptance/presented_done)仍开放。
  16. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    对照实验:记事本同样没有候选窗

    为区分"我们的应用没有候选窗"与"该 VM 的输入法本身不显示候选窗",在同一 VM 用 Windows 记事本做对照:

    • 启动记事本,聚焦后发送 n i h a o;
    • 1.8 s 后枚举所有后代 UI 元素(33 个,排除 explorer)。

    结果:

    {"step":"typed","window":"*ni'hao - Notepad","count":33}
    • 记事本标题变成 *ni'hao - Notepad,文档里显示的就是组合串 ni'hao(说明 IME 在记事本里同样处于行内组合);
    • 33 个元素里没有任何候选窗(只有 Notepad/NotepadTextBox/RichEditD2DPT/TabView 等)。

    结论

    该 VM 的固定微软拼音处于行内组合、不显示候选窗的状态,与我们的应用无关;因此:

    1. 我们的应用侧行为(组合预览锚定光标格、提交 你好)可验证;
    2. 候选窗身份/位置在本环境不可观测属于环境限制,不是应用缺陷;
    3. 若要继续该项,需要在该 VM 上启用候选窗 UI(输入法设置/兼容模式),或在具备标准候选窗的机器上对照。
  17. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    Ctrl-C 在组合态下的观测(我们自己的应用 + 固定拼音)

    新增可选观测 TERMFORGE_QT_INPUT_LOG(记录成功发送到会话的字节),与已有 IME 事件观测一起使用,在隔离 VM 上执行:

    1. 组合输入 ni'hao(IME 日志 5 条 preedit);
    2. 组合态下按 Ctrl-C;
    3. 结果:
    观测 结果
    input.log 未生成 —— 没有任何字节发到会话(无 03)
    IME 日志 ni'hao 之后没有 cancel/commit 事件 —— 组合未被取消

    即:组合态下的 Ctrl-C 既没有到达 keyPressEvent(没有 0x03),也没有触发 IME 取消,被输入法/系统层吞掉。这与本 issue 早前 N9 的观察("没有 C KeyPress、没有 03")一致。

    意义

    • 说明"组合期间的 Ctrl-C"不是应用能直接观测的路径;若产品需要该行为(例如组合时 Ctrl-C 取消组合并中断),需要在 UI 层按按键状态自行判定,而不能依赖 IME 转发;
    • 原版 WindTerm 在同样情形下的行为仍需对照(属本项剩余验收)。

    边界

    • 单次运行、单一输入法配置;
    • 未覆盖真实候选窗(该 VM 不显示候选窗,已另行记录)。

    观测代码:PR #209(默认关闭)。

  18. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    对照组:不在组合态时 Ctrl-C 确实发送 03

    为验证上一轮"组合态下没有 03"不是观测方法本身失效,做了同样流程但不输入拼音、直接按 Ctrl-C:

    {"bytes":1,"hex":"03"}

    即 TERMFORGE_QT_INPUT_LOG 能正常捕获 0x03;组合态那次 input.log 不生成,是真的没有字节发出。

    两条结果合并

    情形 到达会话的字节 IME 事件
    非组合态 Ctrl-C 03 无(正常)
    组合态 Ctrl-C 无 无 cancel/commit

    结论更明确:组合期间 Ctrl-C 被输入法层吞掉,既不到按键路径也不到 IME 事件路径。

    (观测代码:PR #209,默认关闭。)

  19. added a commit that references this issue on Oct 29, 2026
  20. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    原版对照:组合态 Ctrl-C 同样不打断会话

    方法:原版 WindTerm 2.7.0(固定 EXE/文件清单),用同一 harness 展开会话组并打开 TF_UI_CMD,会话命令为 ping -n 300 127.0.0.1(持续输出)。

    步骤:

    1. 等待 ping 输出稳定;
    2. 键入 n i h a o(触发组合);
    3. 按下 Ctrl-C;
    4. 在 +2 s 与 +4 s 各抓一张全屏图。

    结果:

    • orig-a1.png(548,408 B)与 orig-a2.png(548,559 B)大小不同;
    • 采样逐点比较 diff = 319(1024×768,每 2 px 采一点),说明两张图内容仍在变化。

    即:组合态 Ctrl-C 之后,原版终端画面 仍在滚动,ping 没有被中断 —— 与我们的应用观测一致(组合态 Ctrl-C 不产生 0x03)。

    边界

    • 画面变化也可能包含光标闪烁,单凭像素差不能 100% 排除;
    • 该结论只说明"原版在这次固定配置下同样没有把 Ctrl-C 送到会话",不解释其内部原因;
    • 需与我们的应用并列看待:两者行为相同,因此不构成"重要行为差异"。

    对 #155 的意义

    "Ctrl-C 与 IME 的消费关系"这一项现在有了双方一致的观测;若要判定"应用是否应主动处理组合态 Ctrl-C",那属于产品决策,需要你确认,而不是原版差异。

    原版对照图片与脚本保存在本地私有目录,未上传仓库。

  21. added a commit that references this issue on Oct 29, 2026
  22. JavaZeroo commented on Oct 30, 2026

    @JavaZeroo
    OwnerAuthor

    #155 剩余验收项对照:代码/测试 vs 固定拼音 VM 证据 vs 未观察

    只读核对:未改产品源码、未合入、未派发 CI。

    身份与入口

    项 观察
    合并 PR #205 f84dd01(2026-10-29T20:45Z)、#206 def8a37(21:00Z)、#207 c380d7a(21:33Z),均 MERGED
    代码 src/qt/terminal_widget.hpp:143-152(inputMethodQuery)、:172-188(inputMethodEvent)、:218-228(preedit 预览)、:250-253(WA_InputMethodEnabled);origin/main 7ff5cde 与当前 worktree HEAD 的 src/qt 无差异
    CI 路径 .github/workflows/qt-cpp.yml:44-45 构建、:68-69 在 owned Job 内跑 `^(qt_cpp_(shell
    VM 证据 research/ui/results/2026-10-30-qt-ime-fixed-pinyin/README.md:14-22(7 条事件)、:32-34(锚点)、:36-40(未覆盖)、validation.json:74-80

    逐项对照

    判定口径:已覆盖=代码与测试都指到;仅 VM=只有实测、无代码级守卫;未观察=两者都没有。

    # 验收条目(正文原文) 代码/测试 VM 证据 判定
    1 先在公开事件→独立子进程原始接收边界保留红例,再实现 无红例原件;src/qt/unicode_input_test.cpp:270 仍写 {"ime": false},shell_smoke.cpp:363-386 直接是绿断言 无 未观察(次序性缺口)
    2 组合/提交/取消接收;preedit 不上屏到 child;commit 整包一次、字面 UTF-8 terminal_widget.hpp:172-188:model_.send(commit.toUtf8()) 整包一次、无键盘 Backspace→DEL 改写;合成断言 shell_smoke.cpp:363-386(preedit 不入会话、commit 入会话、空 preedit 取消不写) commit e4bda0e5a5bd 6B(README:21) 已覆盖(事件级)
    2b 不截断/部分发送/静默丢文本 单包 >1024B 被 InputQueue 拒绝(experiments/shell/queues.hpp:134,容量 16KiB:69);terminal_widget.hpp:183 未看 send() 返回值,只留通用错误(terminal_session.hpp:246)后仍清 preedit 无 未观察(无测试)
    3 复用 ImeContext 边界(256 UTF-16 单元、非法 UTF-16 明确失败;surrounding/selection 明确处理) ImeContext 仅被 experiments/shell/* 引用(ime.hpp:15-42),src/qt 未用;Qt 侧只显式返回空 ImSurroundingText/ImCurrentSelection 与 ImCursorPosition=0(143-152),无 256 上限;QString::toUtf8 对孤立代理项替换 U+FFFD 而非失败 无 未观察(未移植,也无策略记录)
    4 preedit 文字/属性可观察;锚点跟随光标/输出/resize/字体 预览画在光标格(218-228),未读 event->attributes();query 每次现算光标矩形(143-147),无缓存 局部 (152,74)+原点 (143,83)=全局 (295,157),同轮变化框起点 (295,163) 部分:文字+锚点有证;属性未观察
    5 组合更新/空取消/提交、失焦/隐藏/切换/关闭/自然退出/失败目标/两标签交叉/两次相同 commit 各发一次 只有单窗口合成序列;失焦/隐藏仅 keyboard_.reset()(73-75),preedit_ 不清;标签用例不含 IME(84-151) 无 未观察(矩阵大部分缺)
    6 固定拼音实输入→Qt 事件→child 字节 + 候选窗/焦点/Job 回收留证 — 事件与锚点已记;焦点/Job 回收未记;候选窗 UIA 顶层 4 窗、后代 61 元素中 0 个候选窗元素(本 issue 2026-10-29T21:25/21:45Z 评论) 部分
    7 复验键盘/resize/cmd、两轴审查、文档同步后合入 shell_smoke.cpp 保留 750/exit 37/标签;docs/FEATURES.md:26、docs/EVIDENCE.md:11 已记录 — 记录在案,但关闭条件未满足

    代码级缺口(读代码得到,无测试覆盖)

    观察 位置 后果
    预览按 preedit_.size()(UTF-16 单元)算格宽 terminal_widget.hpp:222 CJK/代理项 preedit 的预览宽度不等于实际列数
    preedit 属性未读 terminal_widget.hpp:172-188 下划线/目标子串等属性一律被折叠为单一下划线
    失焦/隐藏不清 preedit terminal_widget.hpp:73-75 若 provider 不发空 preedit,预览会在失焦后残留(未证)
    commit 发送失败被吞 terminal_widget.hpp:183 + queues.hpp:134 超限整包提交丢失,仅落一条通用错误文本

    三项未观察:各自最便宜的一次观察

    未观察项 最便宜的单次观察 成本 能否分离因果
    候选窗身份/关联与位置跟随 改用 Win32 枚举(EnumWindows+EnumChildWindows,按本进程 PID 过滤,记 class/rect/IsWindowVisible/owner)代替 UIA:UIA 只暴露 provider 实现的元素,枚举含不可见窗口。同一探针在组合态对 输出写入/字体变更/resize 各读一次 rect ~50 行只读探针 + 1 次运行,不改产品 能:inputMethodQuery 每次都现算(143-147),rect 不变即可判为 provider 未重查,而非我们的锚点陈旧
    组合态 Ctrl-C 在既有 env 守卫通道里加一条 KeyPress 记录(TERMFORGE_QT_IME_LOG 已有同型先例 156-171;TERMFORGE_QT_INPUT_LOG 132-141 已把是否发出字节分开),组合态与非组合态各一次 1 次运行 能:只差 widget 是否收到 Ctrl-C 一位;合成 QKeyEvent 不能替代(不经过 Windows IME provider)
    native acceptance / presented_done 0 次新运行:用该轮已有全屏捕获对(基线帧 vs 组合帧)补一条期望内容首次可见帧时间戳,字段定义见 benchmarks/PROTOCOL.md:52 离线重算 能补 presented_done;native acceptance 另需前两项之后再记整场景

    边界

  23. added a commit that references this issue on Oct 30, 2026
  24. JavaZeroo commented on Oct 31, 2026

    @JavaZeroo
    OwnerAuthor

    现状核对:实现已在 main,正文的状态行已过期

    本项正文开头写「PR165 未合入 main」,这条已不成立:

    提交 来源 PR 合入日期 标题
    dea6065 #205 2026-10-29 feat(qt): Qt 终端接入 IME 组合/提交/取消 (#155)
    a44bd2c #230 2026-10-30 fix(qt): IME 预览测量/失焦清理/失败提交 (#155)

    两者均已核对为 origin/main 的祖先。

    • docs/EVIDENCE.md 也已有对应行:
    | Qt 终端固定拼音 IME 组合/提交/取消 | research/ui/results/2026-10-30-qt-ime-fixed-pinyin/{README.md,validation.json}
      隔离 VM、固定微软拼音,preedit ni'hao → cancel → commit e4bda0e5a5bd(你好);
      光标全局矩形与同轮屏幕变化框起点互证。未覆盖候选窗身份/矩形、其他 DPI、完整字形与呈现 |
    

    因此本项性质已变

    正文里「原生机械链路完成,候选框对照仍开放」这句仍然准确,但"未合入"的前提消失后,本项剩余的是纯验收,不含实现:

    1. 候选框身份与位置:N9 即时照片里既有锚点尚未更新的帧,也有后续帧位置已更新,需要固定时间点逐帧对照
    2. 输出 / resize / 字体变化后的候选框位置与覆盖
    3. WindTerm 2.7.0 对照
    4. Ctrl-C delivery / consumption(正文记为未知)
    5. 其他 DPI、完整字形与呈现(EVIDENCE 行已声明未覆盖)

    建议

    把本项改写为验收项(例如标题或正文首段明确「实现已合入 main,剩余为候选框与对照验收」),并按上列 1–5 拆分对应的证据要求。这样它不会继续与"实现是否合入"混在一起,也便于与 #30 的固定 TUI 验收衔接。

    本条只核对状态与证据位置,不关闭本项,也不改验收标准。

  25. JavaZeroo commented on Nov 1, 2026

    @JavaZeroo
    OwnerAuthor

    N11 候选框几何对照完成:输出后不更新与官方 WindTerm 2.7.0 一致;字体阶段原版放大未触发,保持未知

    在隔离 Windows 11 固定微软拼音下,把 TermForge 冻结源(Qt,5fab30b)与官方纯 WindTerm 2.7.0 做同输入轨迹、同固定时间点对照,各 3 次独占运行。

    协议:N I H A O 组合 → 受控输出 6 行纯文本 → 窗口 resize +96/+48 → 一次 Ctrl+= 字体放大 → Esc 取消;四阶段各采集「即时」与「延后 ≥250 ms(实测 252–269 ms)」两张全屏图,候选框几何用同一采集器 n11-capture.cs 的密集亮度带检测。候选面板在组合期表现为窗口标题带下方新增的 2 条亮带。

    候选面板矩形(物理像素,主屏 1024×768/96 DPI):

    阶段 Qt(3 次一致) WindTerm 2.7.0(3 次)
    组合期 [73,108,711,142] [373,232,1011,266]
    输出后(即时/延后) 不变 不变
    resize 后 即时 → 延后 [181,196,819,230](3/3 即时更新) run0 即时更新;run1,2 旧位置→[373,340,1011,374](2/3 延后更新)
    字体后(即时/延后) 不变(且 Ctrl+= 产生可见变化) 不变(但前后 PNG 逐字节相同,未产生可见变化)

    结论(区分实测/推断/未验证):

    1. 输出后候选框仍在旧位置 = 原版一致(实测)。 即时与延后两点,Qt 与 WindTerm 2.7.0 都把候选框保持在组合期位置。N10 观察到的「输出后固定延后点仍在旧位置」在官方 WindTerm 2.7.0 上同样成立,不是 TermForge 特有缺陷,无需据此改产品行为。
    2. resize 后两产品都会更新(实测),仅时序差异。 Qt 3/3 即时更新;WindTerm 1/3 即时、2/3 延后更新——官方 WindTerm 自身逐次不一致,属帧/调度时序,非产品行为差异。两者都在延后点前到达新位置,不存在「持续不更新」。
    3. 字体放大阶段不可直接比对,保持未知(未验证)。 Qt 侧 Ctrl+= 有可见变化但候选框未动;WindTerm 侧同样的合成 Ctrl+= 未产生任何可见变化(前后 PNG 逐字节相同),即官方 WindTerm 2.7.0 在该合成输入下未触发字体放大。因此「字体变化后候选框不更新」只在 Qt 侧实测,原版侧因放大未触发而无结论。

    归档(原始截图只留私有,仅哈希公开;6 个 zip 已逐文件解压并与各自 manifest 逐项核对 SHA-256,0 不匹配;文件数 = zip 内条目数):

    qt-ime-n11-qt-0.zip       2025455 B  112 files  0c6829cfa93ea4083933c8c122f292da028e89c6a19b4823f8085aa41b3add4c
    qt-ime-n11-qt-1.zip       2322587 B  117 files  da78f6f8e9ddfa252fd20979b720aa3a9abc42970a3cf5f80b2a3fcbdd24b72a
    qt-ime-n11-qt-2.zip       2332066 B  114 files  a8a03aaf9cf0cf46af010288fa66891372dc2f68e114044820b7250e5224f5f7
    windterm-n11-orig-0.zip   5230029 B   34 files  342101c30fa19a53893fab14c2601848dc9b62c6707e56ab835e4fc847636610
    windterm-n11-orig-1.zip   6523389 B   40 files  27aa5c9e73e3d14fb4f1b2b4911c845eda15f46afd4ed0f0dcd58c93c4100f7f
    windterm-n11-orig-2.zip   6510251 B   40 files  a959baf89ed0fb5f2b60b46f54b712681930da344dfbb2f798ddadf0f685e6e8
    

    样本数:Qt 3 次共 38 采集(12/13/13),WindTerm 3 次共 45 采集(13/16/16);每侧 4×3=12 个几何配对。

    边界与仍开放项:

    • WindTerm 3 次的 controller-record 状态为 failed,但失败点都在收尾关闭阶段(自动化 Ctrl+W / WM_CLOSE / 对话框确认 / SC_CLOSE 后官方 WindTerm 2.7.0 未在预算内退出);四个几何配对在失败前均已完整采集,不影响候选框几何结论。这本身是官方 WindTerm 的自动化关闭行为,不是候选框差异。
    • 候选框「身份/关联」仍非本结论:本页只用密集亮度带作几何位置,不含窗口句柄归属证明(正文第 4 条「候选框身份与位置」只覆盖了位置,未覆盖身份)。
    • 字体阶段需换触发方式或先确认官方 WindTerm 的 Text.ZoomIn 快捷键映射,再复验候选框是否跟随。
    • native acceptance / presented_done 仍 null,本评论不改变其他 DPI / 完整字形 / [R2-07][文字] 验收复杂 Unicode、IME 与键盘/选择语义 #30 / [R2-08][显示] 明确字体网格并验收真实 DPI 与跨显示器切换 #31 的验收口径。
  26. changed the title [-][R2][输入] 为正式Qt终端接入组合、提交、取消及固定拼音验证[/-] [+][R2][输入验收] Qt IME 候选窗与固定拼音交叉验证[/+] on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions