Skip to content

input

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

Credits:

Note that the original link seems to no longer be active, but an archive can be found on GitHub.

keys: Keys = Keys(_platform_keys, 'nt') module-attribute

Instance storing platform specific key codes.

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
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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
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
280
281
282
283
284
285
286
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
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
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
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
316
317
318
319
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
311
312
313
314
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
306
307
308
309
def values(self) -> ValuesView[str]:
    """Returns values() of self._keys."""

    return self._keys.values()

feed(text)

Manually feeds some text to be read by getch.

This can be used to emulate input, as well as to "interrupt" a blocking getch call (though getch_timeout works better for that scenario).

Source code in pytermgui/input.py
85
86
87
88
89
90
91
92
93
def feed(text: str) -> None:
    """Manually feeds some text to be read by `getch`.

    This can be used to emulate input, as well as to "interrupt" a blocking `getch`
    call (though `getch_timeout` works better for that scenario).
    """

    feeder_stream.write(text)
    feeder_stream.seek(0)

getch(printable=False, interrupts=True, windows_raise_timeout=False)

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
windows_raise_timeout bool

If set, TimeoutException (raised by Windows' getch when no input is available) isn't silenced.

False
Source code in pytermgui/input.py
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
def getch(
    printable: bool = False,
    interrupts: bool = True,
    windows_raise_timeout: bool = False,
) -> str:
    """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.
        windows_raise_timeout: If set, `TimeoutException` (raised by Windows' getch when
            no input is available) isn't silenced.
    """

    fed_text = feeder_stream.getvalue()

    if fed_text != "":
        feeder_stream.seek(0)
        feeder_stream.truncate(0)
        return fed_text

    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)

    except TimeoutException:
        if windows_raise_timeout:
            raise

        key = ""

    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. Instead, it will return immediately if no input is provided, since the Windows APIs expose a way to detect that case.

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
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
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`. Instead, it will return immediately if no input is provided, since the
    Windows APIs expose a way to detect that case.

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

    if isinstance(_getch, _GetchWindows):
        try:
            return getch(windows_raise_timeout=True)

        except TimeoutException:
            return default

    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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@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)