3 # © Copyright 2021-2022, Scott Gasch
5 """Utilities related to user input."""
10 from typing import List, Optional
12 import readchar # type: ignore
16 logger = logging.getLogger(__file__)
19 def single_keystroke_response(
20 valid_responses: Optional[List[str]], # None = accept anything
23 default_response: str = None,
24 timeout_seconds: int = None,
25 ) -> Optional[str]: # None if timeout w/o keystroke
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: Optional[List[str]], timeout_seconds: int = 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 not valid_responses or response in valid_responses:
45 if ord(response) in os_special_keystrokes:
49 if timeout_seconds is not None:
53 if prompt is not None:
57 response = _single_keystroke_response_internal(valid_responses, timeout_seconds)
58 if ord(response) == 3:
59 raise KeyboardInterrupt('User pressed ^C in input_utils.')
61 except exceptions.TimeoutError:
62 if default_response is not None:
63 response = default_response
64 if prompt and response:
69 def yn_response(prompt: str = None, *, timeout_seconds=None) -> Optional[str]:
70 """Get a Y/N response to a prompt."""
72 yn = single_keystroke_response(
73 ["y", "n", "Y", "N"], prompt=prompt, timeout_seconds=timeout_seconds
81 prompt: str = "Press any key to continue...", *, timeout_seconds=None
83 """Press any key to continue..."""
85 return single_keystroke_response(None, prompt=prompt, timeout_seconds=timeout_seconds)
88 def up_down_enter() -> Optional[str]:
89 os_special_keystrokes = [3, 26] # ^C, ^Z
91 key = readchar.readkey()
93 if ord(key) in os_special_keystrokes:
98 if ord(key[0]) == 27 and ord(key[1]) == 91:
101 elif ord(key[2]) == 66:
105 def keystroke_helper() -> None:
106 """Misc util to watch keystrokes and report what they were."""
108 print("Watching for keystrokes; ^C to quit.")
110 key = readchar.readkey()
112 print(f'That was "{key}" ({ord(key)}).')
116 print(f'That was sequence "{key}" (', end="")
118 print(f" {ord(_)} ", end="")