# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries # # SPDX-License-Identifier: MIT import select import sys # pylint: disable=no-self-use try: import termios _orig_attr = None # pylint: disable=invalid-name def _nonblocking(): global _orig_attr # pylint: disable=global-statement _orig_attr = termios.tcgetattr(sys.stdin) attr = termios.tcgetattr(sys.stdin) attr[3] &= ~(termios.ECHO | termios.ICANON) attr[6][termios.VMIN] = 1 attr[6][termios.VTIME] = 0 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, attr) def _blocking(): if _orig_attr is not None: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, _orig_attr) except ImportError: def _nonblocking(): pass def _blocking(): pass # LINES = 24 # COLS = 80 LINES = 33 COLS = 120 special_keys = { "\x1b": ..., # all prefixes of special keys must be entered as Ellipsis "\x1b[": ..., "\x1b[3": ..., "\x1b[5": ..., "\x1b[6": ..., "\x1b[A": "KEY_UP", "\x1b[B": "KEY_DOWN", "\x1b[C": "KEY_RIGHT", "\x1b[D": "KEY_LEFT", "\x1b[H": "KEY_HOME", "\x1b[F": "KEY_END", "\x1b[5~": "KEY_PGUP", "\x1b[6~": "KEY_PGDN", "\x1b[3~": "KEY_DELETE", } class Screen: def __init__(self, terminal=None): self._poll = select.poll() self._poll.register(sys.stdin, select.POLLIN) self._pending = "" self._terminal = terminal def _sys_stdin_readable(self): return hasattr(sys.stdin, "readable") and sys.stdin.readable() def _sys_stdout_flush(self): if hasattr(sys.stdout, "flush"): sys.stdout.flush() def _terminal_read_blocking(self): return sys.stdin.read(1) def _terminal_read_timeout(self, timeout): if self._sys_stdin_readable() or self._poll.poll(timeout): r = sys.stdin.read(1) return r return None def move(self, y, x): if self._terminal is not None: self._terminal.write(f"\033[{y+1};{x+1}H") else: print(end=f"\033[{y+1};{x+1}H") def erase(self): if self._terminal is not None: self._terminal.write("\033H\033[2J") else: print(end="\033H\033[2J") def addstr(self, y, x, text): self.move(y, x) if self._terminal is not None: self._terminal.write(text) else: print(end=text) def getkey(self): self._sys_stdout_flush() pending = self._pending if pending and (code := special_keys.get(pending)) is None: self._pending = pending[1:] return pending[0] while True: if pending: c = self._terminal_read_timeout(50) if c is None: self._pending = pending[1:] return pending[0] else: c = self._terminal_read_timeout(50) if c is None: return None c = pending + c code = special_keys.get(c) if code is None: self._pending = c[1:] return c[0] if code is not Ellipsis: return code pending = c def wrapper(func, *args, **kwds): stdscr = Screen() try: _nonblocking() return func(stdscr, *args, **kwds) finally: _blocking() stdscr.move(LINES - 1, 0) print("\n") def custom_terminal_wrapper(terminal, func, *args, **kwds): stdscr = Screen(terminal) try: _nonblocking() return func(stdscr, *args, **kwds) finally: _blocking() stdscr.move(LINES - 1, 0) print("\n")