Python 非阻塞非干扰我的 TTY 的键盘按键检测
我有一个循环,它在不停地做一些工作,并且会打印很多信息到标准输出(就是屏幕上)。这个循环一直在重复。现在我想要实现的是,当用户按下某个键(可以是方向键、回车键或者字母键)时,能够检测到这个动作,并在发生时执行一些操作。
本来这应该是个很简单的小任务,但我花了四个小时尝试不同的方法,结果却没有什么进展。
这个功能只需要在Linux系统上运行。
我目前能做到的就是下面这个代码,但它只部分有效,只有在0.05
秒内才能捕捉到按键。
import sys,tty,termios
class _Getch:
def __call__(self, n=1):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(n)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def getch(timeout=0.2):
inkey = _Getch()
k = ''
start_sec = time()
while(time() - start_sec < timeout):
if k == '':
k = timeout_call(inkey, timeout_duration=timeout - (time() - start_sec))
if k == u'\x1b':
k += inkey(2)
if k == u'\x1b[A':
return "up"
if k == u'\x1b[B':
return "down"
if k == u'\x1b[C':
return "right"
if k == u'\x1b[D':
return "left"
elif k == "q":
return 'q'
elif k == "\n":
return 'enter'
else:
return None
while True:
do_some_work_that_lasts_about_0_2_seconds()
key = getch(0.05)
if key:
do_something_with_the(key)
2 个回答
1
这是我想到的一个解决方案。虽然不是完美的,因为它依赖于超时,有时候只能捕捉到一半的转义序列,如果按键在超时到期前的毫秒(微秒?纳秒?)内被按下的话。不过,这是我能想到的最不糟糕的解决办法。真让人失望……
def timeout_call(func, args=(), kwargs=None, timeout_duration=1.0, default=None):
if not kwargs:
kwargs = {}
import signal
class TimeoutError(Exception):
pass
def handler(signum, frame):
raise TimeoutError()
# set the timeout handler
signal.signal(signal.SIGALRM, handler)
signal.setitimer(signal.ITIMER_REAL, timeout_duration)
try:
result = func(*args, **kwargs)
except TimeoutError as exc:
result = default
finally:
signal.alarm(0)
return result
class NonBlockingConsole(object):
def __enter__(self):
self.old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
return self
def __exit__(self, type, value, traceback):
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)
def get_data(self):
k = ''
while True:
c = timeout_call(sys.stdin.read, args=[1], timeout_duration=0.05)
if c is None:
break
k += c
return k if k else False
使用方法:
with NonBlockingConsole() as nbc:
while True:
sleep(0.05) # or longer, but not shorter, for my setup anyways...
data = nbc.get_data()
if data:
print data.encode('string-escape')
4
这个问题之前有人问过。有人分享了一个简洁、经过改进的解决方案
在这里重新发布一下
import sys
import select
import tty
import termios
class NonBlockingConsole(object):
def __enter__(self):
self.old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
return self
def __exit__(self, type, value, traceback):
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)
def get_data(self):
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
return sys.stdin.read(1)
return False
if __name__ == '__main__':
# Use like this
with NonBlockingConsole() as nbc:
i = 0
while 1:
print i
i += 1
if nbc.get_data() == '\x1b': # x1b is ESC
break