tcsetattr(TCSAFLUSH)未按预期生效,退出cbreak模式后终端出现鼠标上报乱码如何解决
问题根因
你遇到的问题本质上是两类异步残留数据导致的:
- 终端收到鼠标禁用转义码到真正停止上报鼠标事件存在微小的时间差,这个窗口内产生的鼠标事件仍会发送给程序
- 内核终端输入队列中已经存在还未被读取的鼠标事件字节,恢复带回显的终端模式后,这些未读字节会被自动回显到标准输出,也就是你看到的乱码。
你之前尝试的termios.tcdrain只能保证你写入的转义码已经被发送到终端设备,既不能等待终端完成转义码的处理逻辑,也无法清空已经到达输入队列的残留事件,因此无法解决问题。sleep方案是靠等待足够长的时间让所有残留事件完成上报、队列状态稳定,属于临时规避方案而非可靠方案,在系统负载高、终端响应慢的场景下仍可能失效。
解决方案
在写入鼠标禁用转义码之后、恢复终端模式之前,按顺序执行以下两步即可可靠解决问题:
- 调用
termios.tcdrain(term_out)确保禁用转义码已经完整发送到终端 - 清空标准输入队列里的所有残留数据,丢弃所有未读取的鼠标事件字节
修改后的关键代码段
# First disable mouse disable_mouse = b"\x1b[?1003;1006;1005;1004;1000l" os.write(term_out, disable_mouse) # 新增代码开始 # 确保禁用转义码已经发送到终端 termios.tcdrain(term_out) # 清空输入队列中所有残留的鼠标事件 old_stdin_flags = fcntl.fcntl(term_in, fcntl.F_GETFL) fcntl.fcntl(term_in, fcntl.F_SETFL, old_stdin_flags | os.O_NONBLOCK) try: while True: # 持续读取直到没有可用数据 os.read(term_in, 1024) except BlockingIOError: # 输入队列已清空 pass # 恢复stdin原有的阻塞属性 fcntl.fcntl(term_in, fcntl.F_SETFL, old_stdin_flags) # 新增代码结束 # 再恢复终端模式 termios.tcsetattr(term_in, termios.TCSAFLUSH, term_mode)
原理解释
先调用tcdrain保证禁用指令已经送达终端,此时终端后续最多只会发送极少量还在传输链路中的鼠标事件,这些事件都会被后续的非阻塞读操作全部捕获并丢弃。等到输入队列完全清空后再恢复带回显的终端模式,自然就没有残留数据会被回显成乱码,方案可靠性远高于固定时长的sleep。
内容的提问来源于stack exchange,提问作者Giovanni Funchal
相关产品推荐
相关产品推荐

