【问题标题】:python non-blocking non-messing-my-tty key press detectionpython non-blocking non-messing-my-tty 按键检测
【发布时间】:2014-07-05 02:40:43
【问题描述】:

我有一个循环可以完成一些工作并将大量信息打印到标准输出。一遍又一遍(这是一个循环......)我想做的是检测用户何时/如果用户按下一个键(它可以是箭头,输入或字母),并在发生这种情况时做一些工作.

这应该是一个非常简单的子任务,但我在过去的四个小时里尝试了不同的方法,但几乎一无所获。

这只需要在 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)

【问题讨论】:

    标签: python stdin


    【解决方案1】:

    以前有人问过这个问题。有人发布了一个漂亮、简短、重构的solution

    在这里转发

    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
    

    【讨论】:

    • 一个问题:如何用这种方法读取方向键和常规键?箭头键似乎使用三个字符,但如果我使用return sys.stdin.read(3),那么控制台就不再是“非阻塞”了。
    • 我会接受你的回答,因为它引导我朝着正确的方向前进。谢谢。但这并不是我的情况的真正解决方案,因为它不支持转义序列。 select.select 似乎不足以解决这个问题。
    【解决方案2】:

    这是我想出的解决方案。不完美,因为它依赖于超时并且有时只能捕获一半的转义序列,如果在超时到期前按下键 mili(micro?nano?) 秒。但这是我能想出的最不坏的解决方案。令人失望...

    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')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-14
      • 2016-06-04
      • 2014-12-03
      • 2011-07-28
      • 1970-01-01
      • 2017-11-10
      相关资源
      最近更新 更多