Coverage for view / keyboard.py: 100.00%
41 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-07 00:07 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-07 00:07 +0000
1import sys
2import tty
3import termios
4from enum import Enum, auto
5from dataclasses import dataclass
6from typing import Optional
8class KeyboardKey(Enum):
9 UP = auto()
10 DOWN = auto()
11 ENTER = auto()
12 SPACE = auto()
13 BACKSPACE = auto()
14 CHAR = auto()
15 UNKNOWN = auto()
18@dataclass(frozen=True)
19class KeyboardToken:
20 key: KeyboardKey
21 char: Optional[str] = None
25def read_char():
26 """Reads a single character from stdin without waiting for Enter."""
27 fd = sys.stdin.fileno()
28 attr = termios.tcgetattr(fd)
29 try:
30 tty.setraw(fd)
31 ch = sys.stdin.read(1)
32 if ch == "\x1b":
33 next1 = sys.stdin.read(1)
34 next2 = sys.stdin.read(1)
35 if next1 == "[":
36 if next2 == "A":
37 return KeyboardToken(KeyboardKey.UP)
38 if next2 == "B":
39 return KeyboardToken(KeyboardKey.DOWN)
40 return KeyboardToken(KeyboardKey.UNKNOWN)
42 if ch in ("\r", "\n"):
43 return KeyboardToken(KeyboardKey.ENTER)
44 if ch == " ":
45 return KeyboardToken(KeyboardKey.SPACE)
46 if ch in ("\x7f", "\b"):
47 return KeyboardToken(KeyboardKey.BACKSPACE)
48 return KeyboardToken(KeyboardKey.CHAR, ch.lower())
50 finally:
51 termios.tcsetattr(fd, termios.TCSANOW, attr)