Last active
February 16, 2022 22:39
-
-
Save therealchjones/148ec3965bc989fe84431341c8f30b97 to your computer and use it in GitHub Desktop.
Keycodes for Parallels Desktop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Parallels Virtualization SDK 17 for Mac: | |
# https://www.parallels.com/download/pvsdk17/ | |
import prlsdkapi | |
import string | |
# Using Parallels SDK to send keycodes/keystrokes to Parallels Desktop virtual | |
# machine example ("Creating a Simple OS Installation Program"): | |
# https://download.parallels.com/desktop/v17/docs/en_US/Parallels%20Virtualization%20SDK%20Programmer's%20Guide/27991.htm | |
# Tables & dictionaries below are generated from a Parallels Desktop 17 virtual | |
# machine with a clean installation of macOS 12.2 Monterey and the default | |
# (U.S.) keyboard layout. Different operating systems, versions, and keyboard | |
# layouts may have different results. | |
# Tab-delimited: | |
# Keycode name Keycode output LEFT_SHIFT+Keycode output | |
# A a A | |
# B b B | |
# C c C | |
# D d D | |
# E e E | |
# F f F | |
# G g G | |
# H h H | |
# I i I | |
# J j J | |
# K k K | |
# L l L | |
# M m M | |
# N n N | |
# O o O | |
# P p P | |
# Q q Q | |
# R r R | |
# S s S | |
# T t T | |
# U u U | |
# V v V | |
# W w W | |
# X x X | |
# Y y Y | |
# Z z Z | |
# 0 0 ) | |
# 1 1 ! | |
# 2 2 @ | |
# 3 3 # | |
# 4 4 $ | |
# 5 5 % | |
# 6 6 ^ | |
# 7 7 & | |
# 8 8 * | |
# 9 9 ( | |
# BACKSLASH \ | | |
# CBRACE_LEFT [ { | |
# CBRACE_RIGHT ] } | |
# COLON ; : | |
# GREATER . > | |
# LESS , < | |
# LESS_GREATER § ± | |
# MINUS - _ | |
# PAD_EQUAL = = | |
# PAD_PLUS + + | |
# PAD_MINUS - - | |
# PAD_SLASH / / | |
# PAD_STAR * * | |
# PLUS = + | |
# QUOTE ' " | |
# SLASH / ? | |
# TILDA ` ~ | |
VM_NAME = "macOS" | |
def sendKey(vm, io, key): | |
if io is None: | |
raise RuntimeError("IO not connected to virtual machine") | |
keycodes = prlsdkapi.prlsdk.consts.ScanCodesList | |
chars = { | |
'a': 'A', | |
'b': 'B', | |
'c': 'C', | |
'd': 'D', | |
'e': 'E', | |
'f': 'F', | |
'g': 'G', | |
'h': 'H', | |
'i': 'I', | |
'j': 'J', | |
'k': 'K', | |
'l': 'L', | |
'm': 'M', | |
'n': 'N', | |
'o': 'O', | |
'p': 'P', | |
'q': 'Q', | |
'r': 'R', | |
's': 'S', | |
't': 'T', | |
'u': 'U', | |
'v': 'V', | |
'w': 'W', | |
'x': 'X', | |
'y': 'Y', | |
'z': 'Z', | |
'0': '0', | |
'1': '1', | |
'2': '2', | |
'3': '3', | |
'4': '4', | |
'5': '5', | |
'6': '6', | |
'7': '7', | |
'8': '8', | |
'9': '9', | |
'\\': 'BACKSLASH', | |
'[': 'CBRACE_LEFT', | |
']': 'CBRACE_RIGHT', | |
';': 'COLON', | |
'.': 'GREATER', | |
',': 'LESS', | |
'§': 'LESS_GREATER', | |
'-': 'MINUS', | |
'=': 'PAD_EQUAL', | |
'+': 'PAD_PLUS', | |
'/': 'PAD_SLASH', | |
'*': 'PAD_STAR', | |
"'": 'QUOTE', | |
'`': 'TILDA', | |
' ': 'SPACE', | |
' ': 'TAB' | |
} | |
shiftChars = { | |
'A': 'A', | |
'B': 'B', | |
'C': 'C', | |
'D': 'D', | |
'E': 'E', | |
'F': 'F', | |
'G': 'G', | |
'H': 'H', | |
'I': 'I', | |
'J': 'J', | |
'K': 'K', | |
'L': 'L', | |
'M': 'M', | |
'N': 'N', | |
'O': 'O', | |
'P': 'P', | |
'Q': 'Q', | |
'R': 'R', | |
'S': 'S', | |
'T': 'T', | |
'U': 'U', | |
'V': 'V', | |
'W': 'W', | |
'X': 'X', | |
'Y': 'Y', | |
'Z': 'Z', | |
')': '0', | |
'!': '1', | |
'@': '2', | |
'#': '3', | |
'$': '4', | |
'%': '5', | |
'^': '6', | |
'&': '7', | |
'*': '8', | |
'(': '9', | |
'|': 'BACKSLASH', | |
'{': 'CBRACE_LEFT', | |
'}': 'CBRACE_RIGHT', | |
':': 'COLON', | |
'>': 'GREATER', | |
'<': 'LESS', | |
'±': 'LESS_GREATER', | |
'_': 'MINUS', | |
'"': 'QUOTE', | |
'?': 'SLASH', | |
'~': 'TILDA' | |
} | |
if key in chars: | |
io.send_key_event(vm, keycodes[chars[key]], prlsdkapi.consts.PKE_CLICK) | |
elif key in shiftChars: | |
io.send_key_event( | |
vm, keycodes['SHIFT_LEFT'], prlsdkapi.consts.PKE_PRESS | |
) | |
io.send_key_event( | |
vm, keycodes[shiftChars[key]], prlsdkapi.consts.PKE_CLICK | |
) | |
io.send_key_event( | |
vm, keycodes['SHIFT_LEFT'], prlsdkapi.consts.PKE_RELEASE | |
) | |
elif key in keycodes: | |
io.send_key_event( | |
vm, keycodes[key], prlsdkapi.consts.PKE_CLICK | |
) | |
elif key.upper() in keycodes: | |
io.send_key_event( | |
vm, keycodes[key.upper()], prlsdkapi.consts.PKE_CLICK | |
) | |
else: | |
raise RuntimeError("Unable to find keycode for '{}'".format(key)) | |
def sendEnter(vm, io): | |
sendKey(vm, io, 'enter') | |
def makeKeycodeTable(vm, io): | |
lookup = prlsdkapi.consts.ScanCodesList | |
characters = list(string.ascii_uppercase + string.digits) | |
fkeys = [] | |
for i in range(1, 24): | |
fkeys.append('F'+str(i)) | |
mediakeys = [ | |
'APP_CALCULATOR', 'APP_MAIL', 'APP_MYCOMPUTER', 'EJECT', | |
'MEDIA_NEXT_TRACK', 'MEDIA_PLAY_PAUSE', 'MEDIA_PREV_TRACK', | |
'MEDIA_PLAY', 'MEDIA_STOP', 'MEDIA_SELECT', 'MENU', 'MUTE', | |
'SYSTEM_POWER', 'SYSTEM_SLEEP', 'SYSTEM_WAKE', 'VOLUME_DOWN', | |
'VOLUME_UP', 'WWW_BACK', 'WWW_FAVORITES', 'WWW_FORWARD', 'WWW_HOME', | |
'WWW_REFRESH', 'WWW_SEARCH', 'WWW_STOP' | |
] | |
modifierkeys = [ | |
'ALT_LEFT', 'ALT_RIGHT', 'CMD_LEFT', 'CMD_RIGHT', 'CTRL_LEFT', | |
'CTRL_RIGHT', 'SHIFT_LEFT', 'SHIFT_RIGHT' | |
] | |
symbols = [ | |
'BACKSLASH', 'CBRACE_LEFT', 'CBRACE_RIGHT', 'COLON', 'DOLLAR', | |
'GREATER', 'LESS', 'LESS_GREATER', 'MINUS', 'PAD_EQUAL', | |
'PAD_PLUS', 'PAD_MINUS', 'PAD_SLASH', 'PAD_STAR', 'PLUS', 'QUOTE', | |
'SLASH', 'SPACE', 'TAB', 'TILDA' | |
] | |
actions = [ | |
'BACKSPACE', 'BREAK', 'CAPS_LOCK', 'DELETE', 'DOWN', 'END', 'ENTER', | |
'ESC', 'HOME', 'INSERT', 'LEFT', 'NUM_LOCK', 'PAD_DEL', 'PAD_ENTER', | |
'PAGE_DOWN', 'PAGE_UP', 'PAUSE_BREAK', 'PRINT_SCREEN', 'PRINT_SCREEN2', | |
'RIGHT', 'SCROLL_LOCK', 'SYSRQ', 'UP' | |
] | |
others = [ | |
'BRAZILIAN_KEYPAD', 'EURO', 'EUROPE_1', 'HANGUEL', 'HANJA', 'HENKAN', | |
'HIRAGANA', 'HIRAGANA_KATAKANA', 'KATAKANA', 'MUHENKAN', 'PAD_0', | |
'PAD_1', 'PAD_2', 'PAD_3', 'PAD_4', 'PAD_5', 'PAD_6', 'PAD_7', 'PAD_8', | |
'PAD_9', 'PC9800_KEYPAD', 'RO', 'YEN', 'ZENKAKU_HANKAKU' | |
] | |
basics = characters | |
basics.extend(symbols) | |
for key in basics: | |
print(key + ',') | |
io.send_key_event( | |
vm, lookup[key], prlsdkapi.consts.PKE_CLICK) | |
io.send_key_event( | |
vm, lookup['SHIFT_LEFT'], prlsdkapi.consts.PKE_PRESS) | |
io.send_key_event( | |
vm, lookup[key], prlsdkapi.consts.PKE_CLICK | |
) | |
io.send_key_event( | |
vm, lookup['SHIFT_LEFT'], prlsdkapi.consts.PKE_RELEASE | |
) | |
sendEnter(vm, io) | |
def sendCommand(text, vm, io): | |
for i in range(len(text)): | |
sendKey(vm, io, text[i]) | |
sendEnter(vm, io) | |
prlsdkapi.init_desktop_sdk() | |
service = prlsdkapi.Server() | |
service.login_local().wait() | |
vmList = service.get_vm_list().wait() | |
vm = None | |
vmName = VM_NAME | |
for i in range(vmList.get_params_count()): | |
if vmList.get_param_by_index(i).get_name() == vmName: | |
vm = vmList.get_param_by_index(i) | |
break | |
if vm is None: | |
raise RuntimeError( | |
"Unable to access virtual machine '{}'.".format(vmName)) | |
io = prlsdkapi.VmIO() | |
runstate = vm.get_state().wait().get_param().get_state() | |
if runstate != prlsdkapi.consts.VMS_RUNNING: | |
raise RuntimeError( | |
"Virtual machine '{}' is not running".format(vm.get_name())) | |
io.connect_to_vm(vm).wait() | |
makeKeycodeTable(vm, io) | |
sendCommand('echo "The quick brown fox jumps over the lazy dog."', vm, io) | |
io.disconnect_from_vm(vm) | |
prlsdkapi.deinit_sdk() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment