Skip to content

input

File providing the getch() function to easily read character inputs.

Credits: - Original getch implementation: Danny Yoo (https://code.activestate.com/recipes/134892) - Modern additions & idea: kcsaff (https://github.com/kcsaff/getkey)

Keys

Class for easy access to key-codes.

The keys for CTRL_{ascii_letter}-s can be generated with the following code:

for i, letter in enumerate(ascii_lowercase):
    key = f"CTRL_{letter.upper()}"
    code = chr(i+1).encode('unicode_escape').decode('utf-8')

    print(key, code)
Source code in pytermgui/input.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
class Keys:
    """Class for easy access to key-codes.

    The keys for CTRL_{ascii_letter}-s can be generated with
    the following code:

    ```python3
    for i, letter in enumerate(ascii_lowercase):
        key = f"CTRL_{letter.upper()}"
        code = chr(i+1).encode('unicode_escape').decode('utf-8')

        print(key, code)
    ```
    """

    def __init__(self, platform_keys: dict[str, str], platform: str) -> None:
        """Initialize Keys object.

        Args:
            platform_keys: A dictionary of platform-specific keys.
            platform: The platform the program is running on.
        """

        self._keys = {
            "SPACE": " ",
            "ESC": "\x1b",
            # The ALT character in key combinations is the same as ESC
            "ALT": "\x1b",
            "TAB": "\t",
            "ENTER": "\n",
            "RETURN": "\n",
            "CTRL_SPACE": "\x00",
            "CTRL_A": "\x01",
            "CTRL_B": "\x02",
            "CTRL_C": "\x03",
            "CTRL_D": "\x04",
            "CTRL_E": "\x05",
            "CTRL_F": "\x06",
            "CTRL_G": "\x07",
            "CTRL_H": "\x08",
            "CTRL_I": "\t",
            "CTRL_J": "\n",
            "CTRL_K": "\x0b",
            "CTRL_L": "\x0c",
            "CTRL_M": "\r",
            "CTRL_N": "\x0e",
            "CTRL_O": "\x0f",
            "CTRL_P": "\x10",
            "CTRL_Q": "\x11",
            "CTRL_R": "\x12",
            "CTRL_S": "\x13",
            "CTRL_T": "\x14",
            "CTRL_U": "\x15",
            "CTRL_V": "\x16",
            "CTRL_W": "\x17",
            "CTRL_X": "\x18",
            "CTRL_Y": "\x19",
            "CTRL_Z": "\x1a",
        }

        self.platform = platform

        if platform_keys is not None:
            for key, code in platform_keys.items():
                if key == "name":
                    self.name = code
                    continue

                self._keys[key] = code

    def __getattr__(self, attr: str) -> str:
        """Gets attr from self._keys."""

        if attr == "ANY_KEY":
            return attr

        return self._keys.get(attr, "")

    def get_name(self, key: str, default: Optional[str] = None) -> Optional[str]:
        """Gets canonical name of a key code.

        Args:
            key: The key to get the name of.
            default: The return value to substitute if no canonical name could be
                found. Defaults to None.

        Returns:
            The canonical name if one can be found, default otherwise.
        """

        for name, value in self._keys.items():
            if key == value:
                return name

        return default

    def values(self) -> ValuesView[str]:
        """Returns values() of self._keys."""

        return self._keys.values()

    def keys(self) -> KeysView[str]:
        """Returns keys() of self._keys."""

        return self._keys.keys()

    def items(self) -> ItemsView[str, str]:
        """Returns items() of self._keys."""

        return self._keys.items()

__getattr__(attr)

Gets attr from self._keys.

Source code in pytermgui/input.py
263
264
265
266
267
268
269
def __getattr__(self, attr: str) -> str:
    """Gets attr from self._keys."""

    if attr == "ANY_KEY":
        return attr

    return self._keys.get(attr, "")

__init__(platform_keys, platform)

Initialize Keys object.

Parameters:

Name Type Description Default
platform_keys dict[str, str]

A dictionary of platform-specific keys.

required
platform str

The platform the program is running on.

required
Source code in pytermgui/input.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
def __init__(self, platform_keys: dict[str, str], platform: str) -> None:
    """Initialize Keys object.

    Args:
        platform_keys: A dictionary of platform-specific keys.
        platform: The platform the program is running on.
    """

    self._keys = {
        "SPACE": " ",
        "ESC": "\x1b",
        # The ALT character in key combinations is the same as ESC
        "ALT": "\x1b",
        "TAB": "\t",
        "ENTER": "\n",
        "RETURN": "\n",
        "CTRL_SPACE": "\x00",
        "CTRL_A": "\x01",
        "CTRL_B": "\x02",
        "CTRL_C": "\x03",
        "CTRL_D": "\x04",
        "CTRL_E": "\x05",
        "CTRL_F": "\x06",
        "CTRL_G": "\x07",
        "CTRL_H": "\x08",
        "CTRL_I": "\t",
        "CTRL_J": "\n",
        "CTRL_K": "\x0b",
        "CTRL_L": "\x0c",
        "CTRL_M": "\r",
        "CTRL_N": "\x0e",
        "CTRL_O": "\x0f",
        "CTRL_P": "\x10",
        "CTRL_Q": "\x11",
        "CTRL_R": "\x12",
        "CTRL_S": "\x13",
        "CTRL_T": "\x14",
        "CTRL_U": "\x15",
        "CTRL_V": "\x16",
        "CTRL_W": "\x17",
        "CTRL_X": "\x18",
        "CTRL_Y": "\x19",
        "CTRL_Z": "\x1a",
    }

    self.platform = platform

    if platform_keys is not None:
        for key, code in platform_keys.items():
            if key == "name":
                self.name = code
                continue

            self._keys[key] = code

get_name(key, default=None)

Gets canonical name of a key code.

Parameters:

Name Type Description Default
key str

The key to get the name of.

required
default Optional[str]

The return value to substitute if no canonical name could be found. Defaults to None.

None

Returns:

Type Description
Optional[str]

The canonical name if one can be found, default otherwise.

Source code in pytermgui/input.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
def get_name(self, key: str, default: Optional[str] = None) -> Optional[str]:
    """Gets canonical name of a key code.

    Args:
        key: The key to get the name of.
        default: The return value to substitute if no canonical name could be
            found. Defaults to None.

    Returns:
        The canonical name if one can be found, default otherwise.
    """

    for name, value in self._keys.items():
        if key == value:
            return name

    return default

items()

Returns items() of self._keys.

Source code in pytermgui/input.py
299
300
301
302
def items(self) -> ItemsView[str, str]:
    """Returns items() of self._keys."""

    return self._keys.items()

keys()

Returns keys() of self._keys.

Source code in pytermgui/input.py
294
295
296
297
def keys(self) -> KeysView[str]:
    """Returns keys() of self._keys."""

    return self._keys.keys()

values()

Returns values() of self._keys.

Source code in pytermgui/input.py
289
290
291
292
def values(self) -> ValuesView[str]:
    """Returns values() of self._keys."""

    return self._keys.values()

getch(printable=False, interrupts=True)

Wrapper to call the platform-appropriate character getter.

Parameters:

Name Type Description Default
printable bool

When set, printable versions of the input are returned.

False
interrupts bool

If not set, KeyboardInterrupt is silenced and chr(3) (CTRL_C) is returned.

True
Source code in pytermgui/input.py
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
def getch(printable: bool = False, interrupts: bool = True) -> Any:
    """Wrapper to call the platform-appropriate character getter.

    Args:
        printable: When set, printable versions of the input are returned.
        interrupts: If not set, `KeyboardInterrupt` is silenced and `chr(3)`
            (`CTRL_C`) is returned.
    """

    try:
        key = _getch()

        # msvcrt.getch returns CTRL_C as a character, unlike UNIX systems
        # where an interrupt is raised. Thus, we need to manually raise
        # the interrupt.
        if key == chr(3):
            raise KeyboardInterrupt

    except KeyboardInterrupt as error:
        if interrupts:
            raise KeyboardInterrupt("Unhandled interrupt") from error

        key = chr(3)

    if printable:
        key = key.encode("unicode_escape").decode("utf-8")

    return key

getch_timeout(duration, default='', printable=False, interrupts=True)

Calls getch, returns default if timeout passes before getting input.

No timeout is applied on Windows systems, as there is no support for SIGALRM.

Parameters:

Name Type Description Default
duration float

How long the call should wait for input.

required
default str

The value to return if timeout occured.

''
Source code in pytermgui/input.py
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
def getch_timeout(
    duration: float, default: str = "", printable: bool = False, interrupts: bool = True
) -> Any:
    """Calls `getch`, returns `default` if timeout passes before getting input.

    No timeout is applied on Windows systems, as there is no support for `SIGALRM`.

    Args:
        duration: How long the call should wait for input.
        default: The value to return if timeout occured.
    """

    if isinstance(_getch, _GetchWindows):
        return getch()

    with timeout(duration):
        return getch(printable=printable, interrupts=interrupts)

    return default

timeout(duration)

Allows context to run for a certain amount of time, quits it once it's up.

Note that this should never be run on Windows, as the required signals are not present. Whenever this function is run, there should be a preliminary OS check, to avoid running into issues on unsupported machines.

Source code in pytermgui/input.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@contextmanager
def timeout(duration: float) -> Generator[None, None, None]:
    """Allows context to run for a certain amount of time, quits it once it's up.

    Note that this should never be run on Windows, as the required signals are not
    present. Whenever this function is run, there should be a preliminary OS check,
    to avoid running into issues on unsupported machines.
    """

    def _raise_timeout(*_, **__):
        raise TimeoutException("The action has timed out.")

    try:
        # set the timeout handler
        signal.signal(signal.SIGALRM, _raise_timeout)
        signal.setitimer(signal.ITIMER_REAL, duration)
        yield

    except TimeoutException:
        pass

    finally:
        signal.alarm(0)