65 lines
2.5 KiB
Python
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
|