Initial Commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user