Files
Jean-Pierre MartineauandClaude Opus 5.5 3e251a9707 Docstrings and comments of meshterm/emulator are in ASD-STE100
The same rewrite as for the core package. The physical screen of a
handheld is now the display, and the computer is the handheld. The
comment on FONT_ENV now says that it is the environment variable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 03:51:55 -04:00

85 lines
3.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPDX-License-Identifier: Apache-2.0
"""The bytes that a terminal sends for each keyboard key: what the emulator types into MeshTerm.
Both front ends use this module. The handheld reads key codes from the event device file
of the keyboard, and the window reads them from the desktop. Each front end changes its
own events into a :class:`Key`, and :func:`encode` spells it as xterm does. The spelling
of xterm is the contract, because prompt_toolkit parses it. A desktop terminal that runs
``--platform cardputer-zero`` sends it too. Thus the TUI cannot tell the three apart:
* Shift with F4–F8 is xterm's ``CSI 1;2 S`` / ``CSI 15;2 ~`` …, which prompt_toolkit
reads as F16–F20. These are the Shift bank of the Cardputer deck
(:data:`~meshterm.ui.tui.fkeys.CARDPUTER_ZERO_DECK`).
* A modified arrow, Home, End, Page key, or F-key has the modifier parameter of xterm
(``1 + Shift + 2·Alt + 4·Ctrl``). Thus Ctrl+PgUp is ``CSI 5;5 ~``, the section jump.
* Ctrl with a letter is the C0 control of that letter. Alt with any keyboard key is ESC,
then that keyboard key.
"""
from __future__ import annotations
from dataclasses import dataclass
#: The keyboard keys that are not text, by name. Arrows, Home, and End take the modified
#: form ``CSI 1;m X`` of xterm. The others take ``CSI n;m ~``.
_CURSOR_FINAL = {"up": "A", "down": "B", "right": "C", "left": "D", "home": "H", "end": "F"}
_TILDE = {
"insert": 2,
"delete": 3,
"pageup": 5,
"pagedown": 6,
"f5": 15,
"f6": 17,
"f7": 18,
"f8": 19,
"f9": 20,
"f10": 21,
"f11": 23,
"f12": 24,
}
_SS3 = {"f1": "P", "f2": "Q", "f3": "R", "f4": "S"}
_PLAIN = {"escape": "\x1b", "enter": "\r", "tab": "\t", "backspace": "\x7f"}
@dataclass(frozen=True, slots=True)
class Key:
"""One key press: a named keyboard key (``"up"``, ``"f4"``, ``"enter"``) or its text."""
name: str = ""
text: str = ""
shift: bool = False
ctrl: bool = False
alt: bool = False
def encode(key: Key) -> str:
"""The bytes that a terminal sends for ``key``, or ``""`` if no spelling exists for it."""
modifier = 1 + key.shift + 2 * key.alt + 4 * key.ctrl
name = key.name
if name in _CURSOR_FINAL:
final = _CURSOR_FINAL[name]
return f"\x1b[{final}" if modifier == 1 else f"\x1b[1;{modifier}{final}"
if name in _SS3:
final = _SS3[name]
return f"\x1bO{final}" if modifier == 1 else f"\x1b[1;{modifier}{final}"
if name in _TILDE:
number = _TILDE[name]
return f"\x1b[{number}~" if modifier == 1 else f"\x1b[{number};{modifier}~"
if name == "tab" and key.shift:
return "\x1b[Z"
if name in _PLAIN:
sequence = _PLAIN[name]
return "\x1b" + sequence if key.alt and name != "escape" else sequence
text = key.text
if not text:
return ""
if key.ctrl and len(text) == 1:
lower = text.lower()
if "a" <= lower <= "z":
text = chr(ord(lower) - 96)
elif text in "@[\\]^_":
text = chr(ord(text) & 0x1F)
elif text == " ":
text = "\x00"
return "\x1b" + text if key.alt else text