#!/usr/bin/env python3 """Type text into the VM console via QMP. type.py QMP_SOCKET TEXT (\\n = Enter) type.py QMP_SOCKET @FILE the file's first line, then Enter (secrets stay off the command line) type.py QMP_SOCKET --cad Ctrl+Alt+Del """ import json import os import socket import sys import time 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', } PLAIN = { ' ': 'spc', '\n': 'ret', '\t': 'tab', '-': 'minus', '=': 'equal', '[': 'bracket_left', ']': 'bracket_right', '\\': 'backslash', ';': 'semicolon', "'": 'apostrophe', ',': 'comma', '.': 'dot', '/': 'slash', '`': 'grave_accent', } def keys_for(ch): if ch.isascii() and ch.isalpha(): return (['shift'] if ch.isupper() else []) + [ch.lower()] if ch.isdigit(): return [ch] if ch in PLAIN: return [PLAIN[ch]] if ch in SHIFTED: return ['shift', SHIFTED[ch]] raise SystemExit(f'unsupported char {ch!r}') def main(): arg = sys.argv[2] if arg == '--cad': combos = [['ctrl', 'alt', 'delete']] else: if arg.startswith('@'): with open(arg[1:], encoding='utf-8') as fp: text = fp.readline().rstrip('\r\n') + '\n' else: text = arg.encode().decode('unicode_escape') combos = [keys_for(ch) for ch in text] s = socket.socket(socket.AF_UNIX) s.connect(sys.argv[1]) f = s.makefile('rw') f.readline() f.write(json.dumps({'execute': 'qmp_capabilities'}) + '\n') f.flush() f.readline() for combo in combos: keys = [{'type': 'qcode', 'data': k} for k in combo] f.write(json.dumps({'execute': 'send-key', 'arguments': {'keys': keys}}) + '\n') f.flush() f.readline() time.sleep(0.03) main()