Skip to content

emrg: Windows TUI 适配 — Win32Console raw mode + stdin/resize 线程化 - #401

Merged
argszero merged 2 commits into
masterfrom
feature/win-tui
Aug 5, 2026
Merged

emrg: Windows TUI 适配 — Win32Console raw mode + stdin/resize 线程化#401
argszero merged 2 commits into
masterfrom
feature/win-tui

Conversation

@argszero

@argszero argszero commented Aug 5, 2026

Copy link
Copy Markdown
Owner

背景

rant 2026-08-05T15:54:28 — Windows 上运行 emrg 命令 TUI 崩溃:

File "...python_tui\terminal.py", line 469, in _enter_raw_mode
    self._original_termios = termios.tcgetattr(sys.stdin.fileno())
AttributeError: 'NoneType' object has no attribute 'tcgetattr'

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):恢复保存的原始模式
  • 每个 fd 独立保存/恢复;active 属性反映 raw 状态

2. terminal.py 平台分发(POSIX 路径零改动)

  • _enter_raw_mode/_exit_raw_modesys.platform == "win32" → Win32Console;否则 termios(现有逻辑)
  • 2 处 fcntl 调用(L119 stdout 强制 blocking、L347 BlockingIOError flush 恢复)加 fcntl is not None guard

3. app.py 平台分发

  • stdin reader:Windows → daemon 线程阻塞 os.read(stdin_fd, 4096)loop.call_soon_threadsafe(stdin_queue.put_nowait, data);POSIX 保持 add_reader+fcntl(rant #SIGWINCH-leak 结构不变)
  • resize:Windows → daemon 线程每 500ms 轮询 os.get_terminal_size(),变化时 call_soon_threadsafe(_resize_event.set);POSIX 保持 SIGWINCH handler;signal.SIGWINCHgetattr 保护
  • finally:Windows 停线程(daemon=True 兜底),POSIX remove_reader 加 try/except

验收对照(rant 清单)

  • Windows cmd/PowerShell/Windows Terminal 下 emrg TUI 无 traceback(待 v0.2.3 打包实测
  • 输入:字符/CJK/退格/方向键/Ctrl 编辑/ESC 中断/bracketed paste(字节流与 POSIX 一致 → InputParser 复用)
  • resize 实时生效(轮询线程)
  • 退出恢复终端模式(disable_raw_mode + CURSOR_SHOW)
  • macOS 全量回归 466 passed(POSIX 路径零改动)
  • 打包 v0.2.3 安装后 emrg 可用(待发布

验证

  • py_compile 3 文件 + 466 passed + import OK + emrg --help OK

注:rant 关联的 config.toml 解析错误优化(start_daemon 超时显示真实原因)为独立问题,另行 PR。

EMRG Evolution added 2 commits August 5, 2026 15:59
… 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 argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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/冒烟测试确认。设计合理,无阻塞问题。

@argszero
argszero merged commit eb9cd84 into master Aug 5, 2026
1 check passed
@argszero
argszero deleted the feature/win-tui branch August 5, 2026 08:37
argszero added a commit that referenced this pull request Aug 5, 2026
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>
argszero added a commit that referenced this pull request Aug 5, 2026
#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>
argszero added a commit that referenced this pull request Aug 5, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant