"""
Add helper code to the default IKeyboard class.
"""
import time
from virtualbox import library
# Define a scancode lookup dictionary
SCANCODES = {
'ESC': [[0x01], [0x81]],
'1': [[0x02], [0x82]], '!': [[0x2A, 0x02], [0x82, 0xAA]],
'2': [[0x03], [0x83]], '@': [[0x2A, 0x03], [0x83, 0xAA]],
'3': [[0x04], [0x84]], '#': [[0x2A, 0x04], [0x83, 0xAA]],
'4': [[0x05], [0x85]], '$': [[0x2A, 0x05], [0x85, 0xAA]],
'5': [[0x06], [0x86]], '%': [[0x2A, 0x06], [0x86, 0xAA]],
'6': [[0x07], [0x87]], '^': [[0x2A, 0x07], [0x87, 0xAA]],
'7': [[0x08], [0x88]], '&': [[0x2A, 0x07], [0x87, 0xAA]],
'8': [[0x09], [0x89]], '*': [[0x2A, 0x09], [0x89, 0xAA]],
'9': [[0x0A], [0x8A]], '(': [[0x2A, 0x0A], [0x8A, 0xAA]],
'0': [[0x0B], [0x8B]], ')': [[0x2A, 0x0B], [0x8B, 0xAA]],
'-': [[0x0C], [0x8C]], '_': [[0x2A, 0x0C], [0x8C, 0xAA]],
'=': [[0x0D], [0x8D]], '+': [[0x2A, 0x0D], [0x8D, 0xAA]],
'BKSP': [[0x0E], [0x8E]],
'\b': [[0x0E], [0x8E]],
'TAB': [[0x0F], [0x8F]],
'\t': [[0x0F], [0x8F]],
'q': [[0x10], [0x90]], 'Q': [[0x2A, 0x10], [0x90, 0xAA]],
'w': [[0x11], [0x91]], 'W': [[0x2A, 0x11], [0x91, 0xAA]],
'e': [[0x12], [0x92]], 'E': [[0x2A, 0x12], [0x92, 0xAA]],
'r': [[0x13], [0x93]], 'R': [[0x2A, 0x13], [0x93, 0xAA]],
't': [[0x14], [0x94]], 'T': [[0x2A, 0x14], [0x94, 0xAA]],
'y': [[0x15], [0x95]], 'Y': [[0x2A, 0x15], [0x95, 0xAA]],
'u': [[0x16], [0x96]], 'U': [[0x2A, 0x16], [0x96, 0xAA]],
'i': [[0x17], [0x97]], 'I': [[0x2A, 0x17], [0x97, 0xAA]],
'o': [[0x18], [0x98]], 'O': [[0x2A, 0x18], [0x98, 0xAA]],
'p': [[0x19], [0x99]], 'P': [[0x2A, 0x19], [0x99, 0xAA]],
'[': [[0x1A], [0x9A]], '}': [[0x2A, 0x1A], [0x9A, 0xAA]],
']': [[0x1B], [0x9B]], '{': [[0x2A, 0x1B], [0x9B, 0xAA]],
'ENTER': [[0x1C], [0x9C]],
'\r': [[0x1C], [0x9C]],
'\n': [[0x1C], [0x9C]],
'CTRL': [[0x1D], [0x9D]],
'a': [[0x1E], [0x9E]], 'A': [[0x2A, 0x1E], [0x9E, 0xAA]],
's': [[0x1F], [0x9F]], 'S': [[0x2A, 0x1F], [0x9F, 0xAA]],
'd': [[0x20], [0xA0]], 'D': [[0x2A, 0x20], [0xA0, 0xAA]],
'f': [[0x21], [0xA1]], 'F': [[0x2A, 0x21], [0xA1, 0xAA]],
'g': [[0x22], [0xA2]], 'G': [[0x2A, 0x22], [0xA2, 0xAA]],
'h': [[0x23], [0xA3]], 'H': [[0x2A, 0x23], [0xA3, 0xAA]],
'j': [[0x24], [0xA4]], 'J': [[0x2A, 0x24], [0xA4, 0xAA]],
'k': [[0x25], [0xA5]], 'K': [[0x2A, 0x25], [0xA5, 0xAA]],
'l': [[0x26], [0xA6]], 'L': [[0x2A, 0x26], [0xA6, 0xAA]],
';': [[0x27], [0xA7]], ':': [[0x2A, 0x27], [0xA7, 0xAA]],
'\'': [[0x28], [0xA8]], '\"': [[0x2A, 0x28], [0xA8, 0xAA]],
'`': [[0x29], [0xA9]], '~': [[0x2A, 0x29], [0xA9, 0xAA]],
'LSHIFT': [[0x2A], [0xAA]],
'\\': [[0x2B], [0xAB]], '|': [[0x2A, 0x2B], [0xAB, 0xAA]],
'z': [[0x2C], [0xAC]], 'Z': [[0x2A, 0x2C], [0xAC, 0xAA]],
'x': [[0x2D], [0xAD]], 'X': [[0x2A, 0x2D], [0xAD, 0xAA]],
'c': [[0x2E], [0xAE]], 'C': [[0x2A, 0x2E], [0xAE, 0xAA]],
'v': [[0x2F], [0xAF]], 'V': [[0x2A, 0x2F], [0xAF, 0xAA]],
'b': [[0x30], [0xB0]], 'B': [[0x2A, 0x30], [0xB0, 0xAA]],
'n': [[0x31], [0xB1]], 'N': [[0x2A, 0x31], [0xB1, 0xAA]],
'm': [[0x32], [0xB2]], 'M': [[0x2A, 0x32], [0xB2, 0xAA]],
',': [[0x33], [0xB3]], '<': [[0x2A, 0x33], [0xB3, 0xAA]],
'.': [[0x34], [0xB4]], '>': [[0x2A, 0x34], [0xB4, 0xAA]],
'/': [[0x35], [0xB5]], '?': [[0x2A, 0x35], [0xB5, 0xAA]],
'RSHIFT': [[0x36], [0xB6]],
'PRTSC': [[0x37], [0xB7]],
'ALT': [[0x38], [0xB8]],
'SPACE': [[0x39], [0xB9]],
' ': [[0x39], [0xB9]],
'CAPS': [[0x3A], [0xBA]],
'F1': [[0x3B], [0xBB]],
'F2': [[0x3C], [0xBC]],
'F3': [[0x3D], [0xBD]],
'F4': [[0x3E], [0xBE]],
'F5': [[0x3F], [0xBF]],
'F6': [[0x40], [0xC0]],
'F7': [[0x41], [0xC1]],
'F8': [[0x42], [0xC2]],
'F9': [[0x43], [0xC3]],
'F10': [[0x44], [0xC4]],
'F11': [[0x57], [0xD7]],
'F12': [[0x58], [0xD8]],
'NUM': [[0x45], [0xC5]],
'SCRL': [[0x46], [0xC6]],
'HOME': [[0x47], [0xC7]],
'UP': [[0x48], [0xC8]],
'PGUP': [[0x49], [0xC9]],
'MINUS': [[0x4A], [0xCA]],
'LEFT': [[0x4B], [0xCB]],
'CENTER': [[0x4C], [0xCC]],
'RIGHT': [[0x4D], [0xCD]],
'PLUS': [[0x4E], [0xCE]],
'END': [[0x4F], [0xCF]],
'DOWN': [[0x50], [0xD0]],
'PGDN': [[0x51], [0xD1]],
'INS': [[0x52], [0xD2]],
'DEL': [[0x53], [0xD3]],
'E_DIV': [[0xE0, 0x54], [0xE0, 0xD4]],
'E_ENTER': [[0xE0, 0x1C], [0xE0, 0x9C]],
'E_INS': [[0xE0, 0x52], [0xE0, 0xD2]],
'E_DEL': [[0xE0, 0x53], [0xE0, 0xD3]],
'E_HOME': [[0xE0, 0x47], [0xE0, 0xC7]],
'E_END': [[0xE0, 0x4F], [0xE0, 0xCF]],
'E_PGUP': [[0xE0, 0x49], [0xE0, 0xC9]],
'E_PGDN': [[0xE0, 0x51], [0xE0, 0xD1]],
'E_LEFT': [[0xE0, 0x4B], [0xE0, 0xCB]],
'E_RIGHT': [[0xE0, 0x4D], [0xE0, 0xCD]],
'E_UP': [[0xE0, 0x48], [0xE0, 0xC8]],
'E_DOWN': [[0xE0, 0x50], [0xE0, 0xD0]],
'RALT': [[0x0C, 0x38], [0xC0, 0xB8]],
'RCTRL': [[0x0C, 0x1D], [0xC0, 0x9D]],
'LWIN': [[0xE0, 0x5B], [0xE0, 0xDB]],
'RWIN': [[0xE0, 0x5C], [0xE0, 0xDC]],
# No scan code for pause key released
'PAUSE': [[0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5], []],
}
# Build the lookup tree.
LOOKUP = {}
OFF = 0
ON = 1
def _is_shift_key(c):
return len(c[0]) == 2 and c[0][0] == 0x2A
for key, codes in SCANCODES.items():
if _is_shift_key(codes):
continue
down, up = codes
node = LOOKUP
for code in down:
node = node.setdefault(code, {})
node['leaf'] = [ON, key]
node = LOOKUP
for code in up:
node = node.setdefault(code, {})
node['leaf'] = [OFF, key]
# Lock in duplicate key values to chosen types below:
LOOKUP[0x1C]['leaf'][1] = '\n'
LOOKUP[0x9C]['leaf'][1] = '\n'
LOOKUP[0x0E]['leaf'][1] = '\b'
LOOKUP[0x8E]['leaf'][1] = '\b'
LOOKUP[0x0F]['leaf'][1] = '\t'
LOOKUP[0x8F]['leaf'][1] = '\t'
LOOKUP[0x39]['leaf'][1] = ' '
LOOKUP[0xB9]['leaf'][1] = ' '
LOOKUP[0xB8]['leaf'][1] = 'ALT'
LOOKUP[0x9D]['leaf'][1] = 'CTRL'
# Build the shift state key lookup.
KEY_TO_CAPS = {}
for caps_key, codes in SCANCODES.items():
if not _is_shift_key(codes):
continue
key = LOOKUP[codes[0][1]]['leaf'][1]
KEY_TO_CAPS[key] = caps_key
class KeyCallbackDecorator(object):
"""This class decodes incoming scancodes and calls back to the
registered callback function with changes in keystate.
"""
LOOKUP = LOOKUP
KEY_TO_CAPS = KEY_TO_CAPS
def __init__(self, callback):
self._callback = callback
# Set decoder state.
self._search = self.LOOKUP
self._caps = OFF
self._caps_counter = 0
self._rshift = OFF
self._lshift = OFF
def __call__(self, event):
def yield_key(state, key):
# Convert key based on caps state.
if key == 'LSHIFT':
self._rshift = state
if key == 'RSHIFT':
self._lshift = state
if key == 'CAPS':
self._caps_counter += 1
if self._caps_counter % 4 == 0:
self._caps ^= 1
if (self._caps ^ (self._rshift | self._lshift)) == 1:
key = self.KEY_TO_CAPS.get(key, key)
self._callback(state, key)
for code in event.scancodes:
# Walk tree.
try:
if 'leaf' in self._search:
if code in self._search:
self._search = self._search[code]
continue
else:
self._search = self._search[code]
continue
except KeyError:
# This scancode wasn't found in the SCANCODES table.
raise Exception("Failed to decode %s" % event.scancodes)
yield_key(*self._search['leaf'])
self._search = self.LOOKUP[code]
if 'leaf' in self._search and len(self._search) == 1:
yield_key(*self._search['leaf'])
self._search = self.LOOKUP
[docs]class IKeyboard(library.IKeyboard):
__doc__ = library.IKeyboard.__doc__
SCANCODES = SCANCODES
[docs] def put_keys(self, press_keys=None, hold_keys=None, press_delay=50):
"""Put scancodes that represent keys defined in the sequences provided.
Arguments:
press_keys: Press a sequence of keys
hold_keys: While pressing the sequence of keys, hold down the keys
defined in hold_keys.
press_delay: Number of milliseconds to delay between each press
Note: Both press_keys and hold_keys are interable objects that yield
self.SCANCODE.keys() keys.
"""
if press_keys is None:
press_keys = []
if hold_keys is None:
hold_keys = []
release_codes = set()
put_codes = set()
try:
# hold the keys
for k in hold_keys:
put, release = self.SCANCODES[k]
# Avoid putting codes over and over
put = set(put) - put_codes
self.put_scancodes(list(put))
put_codes.update(put)
release_codes.update(release)
# press the keys
for k in press_keys:
put, release = self.SCANCODES[k]
# Avoid putting held codes
put = set(put) - put_codes
if not put:
continue
release = set(release) - release_codes
# Avoid releasing held codes
if not release:
continue
self.put_scancodes(list(put) + list(release))
time.sleep(press_delay / 1000.0)
finally:
# release the held keys
for code in release_codes:
self.put_scancode(code)
[docs] def register_on_guest_keyboard(self, callback):
"""Set the callback function to consume on guest keyboard events
Callback receives a IGuestKeyboardEvent object.
Example:
def callback(event):
print(event.scancodes)
"""
return self.event_source.register_callback(callback,
library.VBoxEventType.on_guest_keyboard)
[docs] def register_key_callback(self, callback):
"""Set a callback handler to consume decoded key events
Callback receives state and key where state is ON (1) or OFF (0) and
a string representation for that key.
Example:
def callback(state, key):
print("state = %s, key = %s" % (state, repr(key)))
"""
callback = KeyCallbackDecorator(callback)
return self.register_on_guest_keyboard(callback)