3 # © Copyright 2021-2022, Scott Gasch
5 """Utilities related to user input."""
10 from typing import List
12 import readchar # type: ignore
16 logger = logging.getLogger(__file__)
19 def single_keystroke_response(
20 valid_responses: List[str],
23 default_response: str = None,
24 timeout_seconds: int = None,
26 """Get a single keystroke response to a prompt."""
28 def _handle_timeout(signum, frame) -> None:
29 raise exceptions.TimeoutError()
31 def _single_keystroke_response_internal(
32 valid_responses: List[str], timeout_seconds=None
34 os_special_keystrokes = [3, 26] # ^C, ^Z
35 if timeout_seconds is not None:
36 signal.signal(signal.SIGALRM, _handle_timeout)
37 signal.alarm(timeout_seconds)
41 response = readchar.readchar()
42 logger.debug('Keystroke: 0x%x', ord(response))
43 if response in valid_responses:
45 if ord(response) in os_special_keystrokes:
49 if timeout_seconds is not None:
52 if prompt is not None:
56 response = _single_keystroke_response_internal(valid_responses, timeout_seconds)
57 if ord(response) == 3:
58 raise KeyboardInterrupt('User pressed ^C in input_utils.')
60 except exceptions.TimeoutError:
61 if default_response is not None:
62 response = default_response
63 if prompt is not None:
68 def yn_response(prompt: str = None, *, timeout_seconds=None) -> str:
69 """Get a Y/N response to a prompt."""
71 return single_keystroke_response(
72 ["y", "n", "Y", "N"], prompt=prompt, timeout_seconds=timeout_seconds
76 def keystroke_helper() -> None:
77 """Misc util to watch keystrokes and report what they were."""
79 print("Watching for keystrokes; ^C to quit.")
81 key = readchar.readkey()
83 print(f'That was "{key}" ({ord(key)}).')
87 print(f'That was sequence "{key}" (', end="")
89 print(f" {ord(_)} ", end="")