"""ASCII text to QEMU key events. QMP can press keys but has no notion of "type this string", so a literal string has to become a sequence of QKeyCode presses with shift where the US layout needs it. This is that table. It is deliberately ASCII-only and deliberately refuses what it cannot type. The alternative - dropping unmappable characters silently - means the guest receives text that is subtly not what the agent asked for, and the agent then debugs the wrong thing. An explicit refusal is one confusing error; a silent substitution is an afternoon. """ from __future__ import annotations # Unshifted keys whose QKeyCode is not simply the character itself. _PUNCT = { " ": "spc", "-": "minus", "=": "equal", "[": "bracket_left", "]": "bracket_right", "\\": "backslash", ";": "semicolon", "'": "apostrophe", ",": "comma", ".": "dot", "/": "slash", "`": "grave_accent", "\n": "ret", "\t": "tab", } # US layout: what shift turns each key into. _SHIFTED = { "!": "1", "@": "2", "#": "3", "$": "4", "%": "5", "^": "6", "&": "7", "*": "8", "(": "9", ")": "0", "_": "minus", "+": "equal", "{": "bracket_left", "}": "bracket_right", "|": "backslash", ":": "semicolon", '"': "apostrophe", "<": "comma", ">": "dot", "?": "slash", "~": "grave_accent", } class KeymapError(ValueError): pass def to_chords(text: str) -> list[list[str]]: """Turn a string into a list of key chords, in order. Raises rather than guessing on anything outside ASCII - accented characters, emoji and smart quotes all need a compose sequence or an input method, neither of which a bare keycode stream can express. """ chords: list[list[str]] = [] for index, char in enumerate(text): if char in _PUNCT: chords.append([_PUNCT[char]]) elif char in _SHIFTED: chords.append(["shift", _SHIFTED[char]]) elif "a" <= char <= "z" or "0" <= char <= "9": chords.append([char]) elif "A" <= char <= "Z": chords.append(["shift", char.lower()]) elif char == "\r": continue # CRLF arrives as one return else: raise KeymapError( f"cannot type {char!r} (at position {index}): only ASCII " "letters, digits, punctuation, space, tab and newline can be " "sent as key presses. For anything else, write the text to a " "file with write_file and open it in the guest." ) return chords