【发布时间】:2023-12-01 22:38:01
【问题描述】:
为长代码帖子道歉,但我相信这是有用的上下文。
我正在尝试在原始 Python 中解析特殊键(没有诅咒),但似乎 select 进行非阻塞输入的技巧在这种情况下不起作用。特别是,在读取输入的第一个字符后,select 似乎返回 stdin 不可读,尽管还有更多的输入字符要读取。
重现问题的步骤:
- 运行下面的代码。
- 按左箭头键(或任何其他命名的特殊键)。
- 观察输出是
ESC,后跟单独行中的转义序列的其余部分。预期行为:输出ARROW_LEFT。
是否可以正确读取特殊键的完整转义序列,同时仍能正确读取 ESC 本身?
#!/usr/bin/env python3
import sys
from enum import Enum
import tty
import termios
import select
import signal
# Takes a given single-character string and returns the string control version
# of it. For example, it takes 'c' and returns the string representation of
# Control-C. This can be used to check for control-x keys in the output of
# readKey.
def controlKey(c):
return chr(ord(c) & 0x1f)
def nonblock_read(stream, limit=1):
if select.select([stream,],[],[],0.1)[0]:
return stream.read(limit)
return None
# Read a key of input as a string. For special keys, it returns a
# representative string. For control keys, it returns the raw string.
# This function assumes that the caller has already put the terminal in raw mode.
def readKey():
c = nonblock_read(sys.stdin, 1)
if not c: return None
# Handle special keys represented by escape sequences
if c == "\x1b":
seq = [None] * 3
seq[0] = nonblock_read(sys.stdin, 1)
if not seq[0]: return "ESC"
seq[1] = nonblock_read(sys.stdin, 1)
if not seq[1]: return "ESC"
if seq[0] == '[':
if seq[1] >= '0' and seq[1] <= '9':
seq[2] = nonblock_read(sys.stdin, 1)
if not seq[2]: return "ESC"
if seq[2] == '~':
if seq[1] == '1': return "HOME_KEY"
if seq[1] == '3': return "DEL_KEY"
if seq[1] == '4': return "END_KEY"
if seq[1] == '5': return "PAGE_UP"
if seq[1] == '6': return "PAGE_DOWN"
if seq[1] == '7': return "HOME_KEY"
if seq[1] == '8': return "END_KEY"
else:
if seq[1] == 'A': return "ARROW_UP"
if seq[1] == 'B': return "ARROW_DOWN"
if seq[1] == 'C': return "ARROW_RIGHT"
if seq[1] == 'D': return "ARROW_LEFT"
if seq[1] == 'H': return "HOME_KEY"
if seq[1] == 'F': return "END_KEY"
elif seq[0] == 'O':
if seq[1] == 'H': return "HOME_KEY"
if seq[1] == 'F': return "END_KEY"
return 'ESC'
return c
def main():
# Save terminal settings
fd = sys.stdin.fileno()
old_tty_settings = termios.tcgetattr(fd)
# Enter raw mode
tty.setraw(sys.stdin)
################################################################################
interrupt = controlKey("c")
while True:
s = readKey()
if s:
print(f"{s}", end="\r\n")
if s == interrupt:
break
################################################################################
# Exit raw mode
fd = sys.stdin.fileno()
termios.tcsetattr(fd, termios.TCSADRAIN, old_tty_settings)
if __name__ == "__main__":
main()
【问题讨论】:
标签: python python-3.x linux select stdin