I have a loop that does some work and prints a lot of info to stdout. Over and over again (it's a loop...) What I'd like to do is to detect when / if user presses a key (it can be an arrow, enter, or a letter), and do some work when that happens.
This should have been a very simple subsubtask, but I've spent last four hours trying different approaches and getting pretty much nowhere.
This needs only work in Linux.
Best I could get is something like this below. But that works partially, catching the keys only if within the 0.05
sec.
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)
This has been asked before. Someone posted a nice, short, refactored solution
Reposted here
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
Here is a solution I came up with. Not perfect, because it relies on timeouts and sometimes can catch only half of escape sequences, if the key is pressed mili(micro? nano?)seconds before timeout expires. But it's the least bad solution I could come up with. Disappointing...
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
Usage:
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')