emrg: Windows TUI 适配 — Win32Console raw mode + stdin/resize 线程化 - #401
Conversation
… 2026-08-05T15:54:28) 根因:TUI 输入/信号层全部基于 POSIX(termios/fcntl/SIGWINCH/add_reader), Windows 上 termios=None 时 _enter_raw_mode 直接 AttributeError 崩溃。 修复(方案 A,宿主已确认): - 新增 emrg/client/python_tui/win32.py:Win32Console 类(ctypes 调 Win32 API, 无 pywin32 依赖)— SetConsoleMode 关 ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT| ENABLE_PROCESSED_INPUT、开 ENABLE_VIRTUAL_TERMINAL_PROCESSING;msvcrt setmode O_BINARY 关 CRLF 转换(方向键序列与 POSIX 字节一致) - terminal.py:_enter/_exit_raw_mode 平台分发(win32 → Win32Console; POSIX 保持 termios 零改动);fcntl 调用加 None guard(2 处) - app.py:stdin reader Windows 用 daemon 线程 os.read + call_soon_threadsafe; resize Windows 用 500ms 轮询线程(get_terminal_size 变化触发); SIGWINCH 用 getattr 保护;finally 清理平台分支 验证:py_compile 3 文件 + 466 passed + import OK + --help OK (POSIX 路径零改动;Windows 路径待 v0.2.3 打包实测) 注:config.toml 解析错误优化(start_daemon 超时显示真实原因)为 rant 关联 独立问题,另行处理。
…nter/exit Review found: _enter_raw_mode / _exit_raw_mode each created a fresh Win32Console() instance, but the saved original console modes live in the instance attribute _saved_modes → disable_raw_mode on a new instance could never restore the terminal (modes dict empty). Fix: module-level singleton `win32_console` shared by both paths. Verified: py_compile + 466 passed + import OK + --help OK.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle 20260805-1602. 第 1 个 LGTM(含 review 修复)。独立审查发现并修复 1 个关键 bug:_enter/_exit_raw_mode 各自新建 Win32Console() 实例 → 保存的原始 console 模式在实例属性 _saved_modes 中,退出时新实例模式字典为空 → 无法恢复终端。已改为模块级单例 win32_console(win32.py + terminal.py 2 处),push 156b72f。其余审查通过:① Win32Console ctypes 实现正确(SetConsoleMode raw + VT + msvcrt O_BINARY);② terminal.py fcntl 2 处 None guard;③ app.py stdin 线程化(call_soon_threadsafe)+ resize 轮询 + SIGWINCH getattr 保护 + finally 平台分支;④ POSIX 路径零改动(466 passed)。CI test pass 38s。
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle 20260805-1605. 第 2 个 LGTM。独立复核:① 单例修复已落实(win32_console = Win32Console() 模块级,terminal.py 2 处引用,saved modes 跨 enter/exit 存活);② Win32Console.enable_raw_mode 保存 in/out 模式 + disable_raw_mode 恢复(含 stdout VT 模式);③ msvcrt O_BINARY 保证字节流与 POSIX 一致(InputParser 零改动);④ app.py stdin 线程 + resize 轮询线程均 daemon=True 且 finally 显式 stop;⑤ POSIX 路径零改动(fcntl guard 2 处 + SIGWINCH getattr)。CI test pass 34s(修复后)。LGTM 2/3。
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle 20260805-1603. 独立审查:
① win32.py — ctypes 调 Win32 API(无 pywin32,runtime 保持精简),raw 模式正确(关 LINE_INPUT/ECHO/PROCESSED_INPUT 保留 WINDOW_INPUT 收 resize 事件),VT 输出模式开启,二进制模式禁 CRLF;模块级单例保存模式(修复版 156b72f 正确——fresh instance 会丢 _saved_modes)
② terminal.py — Windows 走 win32_console,POSIX termios 路径不变;fcntl None 保护(2 处)
③ app.py — Windows 线程阻塞 os.read + call_soon_threadsafe 替代 add_reader(ProactorEventLoop 不支持);500ms 轮询 resize 替代 SIGWINCH;finally 中线程 stop + remove_reader 保护(NotImplementedError/ValueError)
④ POSIX 路径零改动 → 466 pytest 通过 + import OK
Windows 专属路径无法在 macOS 实测(_overlapped/msvcrt 缺失),需 windows runner 验证——建议 merge 后跑 GUI/冒烟测试确认。设计合理,无阻塞问题。
v0.2.2 已发布(#399 卸载彻底 + #401 Windows TUI + #395 PATH 加固), README 中英双版补充: 1. Windows 安装行:PATH 自动注册 + 原生 TUI(cmd/PowerShell 直接 emrg) 2. 卸载说明:Windows 卸载彻底(kill GUI 防 daemon 复活 + 白名单全量清理 + [UninstallDelete] 兜底删 install/,~/.emrg 无残留) 验证:470 passed + import OK(仅文档改动)。 Co-authored-by: EMRG Evolution <emrg@argszero.dev>
#401 (R123) 后 Windows 已有原生 TUI(Win32Console raw mode + stdin/resize 线程化), terminal.py 的 termios/fcntl/tty import 有 None 保护,python_tui 包在 Windows 可安全 import。 历史遗留问题:6 个测试文件仍带 'TUI is POSIX-only (fcntl/termios)' 的过时 skipif, 导致 Windows 构建(build-release.yml regression gate 在 windows-2025 跑 pytest)跳过 65 个纯逻辑测试——buffer 写入/ANSI 生成/输入解析逻辑在 Windows 上完全无回归保障。 改动: 1. test_buffer/test_output/test_input_parser(65 测试):移除 skipif——纯逻辑、无平台调用、 import 链安全,Windows CI 将实际执行 2. test_app_widgets/test_daemon_manager/test_daemon_manager_e2e:保留 skip(交互/daemon 生命周期依赖 POSIX 终端/进程语义),但注释更新为准确原因(不再声称 POSIX-only) 验证:470 passed(macOS 全量回归)+ import OK + --help OK Co-authored-by: EMRG Evolution <emrg@argszero.dev>
* emrg: bump v0.2.3 — Windows 修复集打版(#399 卸载彻底 + #401 TUI + #403 真实报错) rant 2026-08-05T17:26:40:master 已合入 6 个 Windows 相关修复但未打 tag, Windows 用户仍在使用旧安装包。本次 bump 0.2.2 → 0.2.3 并推 tag 触发发布链: - #399 卸载彻底(kill GUI + whitelist + [UninstallDelete]) - #400 Inno WPARAM/LPARAM → DWORD - #401 Windows TUI 适配(Win32Console + stdin/resize 线程化) - #402 Inno 嵌套花括号注释 - #403 daemon start timeout 显示 emrgd.log 尾部真实报错 - #405 README 同步 版本文件 4 处同步:pyproject.toml / __init__.py / make-installer.sh / build-runtime.sh * emrg: sync uv.lock to 0.2.3 --------- Co-authored-by: EMRG Evolution <emrg@argszero.dev>
背景
rant 2026-08-05T15:54:28 — Windows 上运行
emrg命令 TUI 崩溃:TUI 输入/信号层全部基于 POSIX(termios/fcntl/SIGWINCH/add_reader),Windows 无降级直接抛 traceback。
修复(方案 A,宿主已确认;ctypes 调 Win32 API,不引入 pywin32,打包 runtime 保持精简)
1. 新增
emrg/client/python_tui/win32.py— Win32Console 类enable_raw_mode(fd):GetConsoleMode 保存原始模式 → SetConsoleMode 关闭ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT(保留 ENABLE_WINDOW_INPUT),打开ENABLE_VIRTUAL_TERMINAL_PROCESSING|ENABLE_PROCESSED_OUTPUT(cmd/conhost 需要,Windows Terminal 默认已有)确保 ANSI/VT 序列可用msvcrt.setmode(fd, os.O_BINARY)关闭 CRLF 转换 → 方向键ESC [ A序列与 POSIX 字节一致,InputParser 零改动disable_raw_mode(fd):恢复保存的原始模式active属性反映 raw 状态2.
terminal.py平台分发(POSIX 路径零改动)_enter_raw_mode/_exit_raw_mode:sys.platform == "win32"→ Win32Console;否则 termios(现有逻辑)fcntl is not Noneguard3.
app.py平台分发os.read(stdin_fd, 4096)→loop.call_soon_threadsafe(stdin_queue.put_nowait, data);POSIX 保持 add_reader+fcntl(rant #SIGWINCH-leak 结构不变)os.get_terminal_size(),变化时call_soon_threadsafe(_resize_event.set);POSIX 保持 SIGWINCH handler;signal.SIGWINCH用getattr保护验收对照(rant 清单)
验证
emrg --helpOK