2014-07-05 2 views
3

일부 작업을 수행하고 stdout에 많은 정보를 인쇄하는 루프가 있습니다. 반복해서 (루프입니다 ...) 내가하고 싶은 것은 사용자가 키를 누를 때 (화살표, 입력 또는 문자가 될 수 있음)를 감지하고 그 일이 발생하면 작업을 수행하는 것입니다 .python non-blocking non-messing-my-tty 키 누름 감지

이것은 매우 간단한 서브 서브 태스크 였어 야했지만 지난 4 시간 동안 다른 접근 방식을 시도하고 거의 아무 것도 얻지 못했습니다.

이것은 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) 

답변

4

이것은 이전에 질문되었습니다. 누군가가 짧은, 멋진 게시 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 
+0

하나의 문제 : 어떻게이 방법 화살표 키와 일반 키를 읽을 수 있습니까? 화살표 키는 세 개의 문자를 사용하는 것처럼 보이지만,'return sys.stdin.read (3)'을 실행하면 콘솔은 더 이상 "비 차단"이 아닙니다. – frnhr

+0

나는 그것이 올바른 방향으로 나를 인도했기 때문에 대답을 받아 들일 것입니다. 감사. 하지만 이스케이프 시퀀스를 지원하지 않기 때문에 제 케이스의 해결책은 아닙니다. select.select는 그 점에 부적합한 것처럼 보입니다. – frnhr

1

재 게시 내가 생각 해낸 해결책이 리팩토링. 완벽하지는 않습니다. 타임 아웃에 의존하기 때문에 시간 제한이 만료되기 전에 키가 밀리 초 (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')