Files
2026-09-15 15:24:03 -04:00

65 lines
2.5 KiB
Python

"""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