def x11_setup(): global _x11, xdisplay, modifier_keycodes, NET_ACTIVE_WINDOW, NET_WM_PID, WM_CLASS, xtest_available if _x11 is not None: return _x11 try: from Xlib.display import Display xdisplay = Display() modifier_keycodes = xdisplay.get_modifier_mapping( ) # there should be a way to do this in Gdk NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW') NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID') WM_CLASS = xdisplay.intern_atom('WM_CLASS') _x11 = True # X11 available if _log.isEnabledFor(_INFO): _log.info('X11 library loaded and display set up') except Exception: _log.warn('X11 not available - some rule capabilities inoperable: %s', exc_info=_sys.exc_info()) _x11 = False xtest_available = False return _x11
class PyKeyboardEvent(PyKeyboardEventMeta): """ The PyKeyboardEvent implementation for X11 systems (mostly linux). This allows one to listen for keyboard input. """ def __init__(self, capture=False, display=None): self.display = Display(display) self.display2 = Display(display) self.ctx = self.display2.record_create_context( 0, [record.AllClients ], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.lock_meaning = None #Get these dictionaries for converting keysyms and strings self.keysym_to_string, self.string_to_keysym = self.get_translation_dicts( ) #Identify and register special groups of keys self.modifier_keycodes = {} self.all_mod_keycodes = [] self.keypad_keycodes = [] #self.configure_keys() #Direct access to the display's keycode-to-keysym array logger.debug('Keycode to Keysym map') for i in range(len(self.display._keymap_codes)): logger.debug('{0}: {1}'.format(i, self.display._keymap_codes[i])) PyKeyboardEventMeta.__init__(self, capture) def run(self): """Begin listening for keyboard input events.""" self.state = True if self.capture: self.display2.screen().root.grab_keyboard( X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): """Stop listening for keyboard input events.""" self.state = False with display_manager(self.display) as d: d.record_disable_context(self.ctx) d.ungrab_keyboard(X.CurrentTime) with display_manager(self.display2): d.record_disable_context(self.ctx) d.ungrab_keyboard(X.CurrentTime) def handler(self, reply): """Upper level handler of keyboard events.""" data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.display.display, None, None) if self.escape(event): # Quit if this returns True self.stop() else: self._tap(event) def _tap(self, event): keycode = event.detail press_bool = (event.type == X.KeyPress) #Detect modifier states from event.state for mod, bit in self.modifier_bits.items(): self.modifiers[mod] = event.state & bit if keycode in self.all_mod_keycodes: keysym = self.display.keycode_to_keysym(keycode, 0) character = self.keysym_to_string[keysym] else: character = self.lookup_char_from_keycode(keycode) #All key events get passed to self.tap() self.tap(keycode, character, press=press_bool) def lookup_char_from_keycode(self, keycode): """ This will conduct a lookup of the character or string associated with a given keycode. """ #TODO: Logic should be strictly adapted from X11's src/KeyBind.c #Right now the logic is based off of #http://tronche.com/gui/x/xlib/input/keyboard-encoding.html #Which I suspect is not the whole story and may likely cause bugs keysym_index = 0 #TODO: Display's Keysyms per keycode count? Do I need this? #If the Num_Lock is on, and the keycode corresponds to the keypad if self.modifiers['Num_Lock'] and keycode in self.keypad_keycodes: if self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 0 else: keysym_index = 1 elif not self.modifiers['Shift'] and self.modifiers['Caps_Lock']: #Use the first keysym if uppercase or uncased #Use the uppercase keysym if the first is lowercase (second) keysym_index = 0 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr( keysym) in 'abcdefghijklmnopqrstuvwxyz': keysym_index = 1 elif self.modifiers['Shift'] and self.modifiers['Caps_Lock']: keysym_index = 1 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr( keysym) in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': keysym_index = 0 elif self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 1 if self.modifiers['Mode_switch']: keysym_index += 2 #Finally! Get the keysym keysym = self.display.keycode_to_keysym(keycode, keysym_index) #If the character is ascii printable, return that character if keysym & 0x7f == keysym and self.ascii_printable(keysym): return chr(keysym) #If the character was not printable, look for its name try: char = self.keysym_to_string[keysym] except KeyError: logger.info('Unable to determine character.') logger.info('Keycode: {0} KeySym {1}'.format(keycode, keysym)) return None else: return char def escape(self, event): if event.detail == self.lookup_character_keycode('Escape'): return True return False def configure_keys(self): """ This function locates the keycodes corresponding to special groups of keys and creates data structures of them for use by the PyKeyboardEvent instance; including the keypad keys and the modifiers. The keycodes pertaining to the keyboard modifiers are assigned by the modifier name in a dictionary. This dictionary can be accessed in the following manner: self.modifier_keycodes['Shift'] # All keycodes for Shift Masking It also assigns certain named modifiers (Alt, Num_Lock, Super), which may be dynamically assigned to Mod1 - Mod5 on different platforms. This should generally allow the user to do the following lookups on any system: self.modifier_keycodes['Alt'] # All keycodes for Alt Masking self.modifiers['Alt'] # State of Alt mask, non-zero if "ON" """ modifier_mapping = self.display.get_modifier_mapping() all_mod_keycodes = [] mod_keycodes = {} mod_index = [('Shift', X.ShiftMapIndex), ('Lock', X.LockMapIndex), ('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex), ('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex), ('Mod5', X.Mod5MapIndex)] #This gets the list of all keycodes per Modifier, assigns to name for name, index in mod_index: codes = [v for v in list(modifier_mapping[index]) if v] mod_keycodes[name] = codes all_mod_keycodes += codes def lookup_keycode(string): keysym = self.string_to_keysym[string] return self.display.keysym_to_keycode(keysym) #Dynamically assign Lock to Caps_Lock, Shift_Lock, Alt, Num_Lock, Super, #and mode switch. Set in both mod_keycodes and self.modifier_bits #Try to assign Lock to Caps_Lock or Shift_Lock shift_lock_keycode = lookup_keycode('Shift_Lock') caps_lock_keycode = lookup_keycode('Caps_Lock') if shift_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Shift_Lock'] = [shift_lock_keycode] self.modifier_bits['Shift_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Shift_Lock' elif caps_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Caps_Lock'] = [caps_lock_keycode] self.modifier_bits['Caps_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Caps_Lock' else: self.lock_meaning = None logger.debug('Lock is bound to {0}'.format(self.lock_meaning)) #Need to find out which Mod# to use for Alt, Num_Lock, Super, and #Mode_switch num_lock_keycodes = [lookup_keycode('Num_Lock')] alt_keycodes = [lookup_keycode(i) for i in ['Alt_L', 'Alt_R']] super_keycodes = [lookup_keycode(i) for i in ['Super_L', 'Super_R']] mode_switch_keycodes = [lookup_keycode('Mode_switch')] #Detect Mod number for Alt, Num_Lock, and Super for name, keycodes in list(mod_keycodes.items()): for alt_key in alt_keycodes: if alt_key in keycodes: mod_keycodes['Alt'] = keycodes self.modifier_bits['Alt'] = self.modifier_bits[name] for num_lock_key in num_lock_keycodes: if num_lock_key in keycodes: mod_keycodes['Num_Lock'] = keycodes self.modifier_bits['Num_Lock'] = self.modifier_bits[name] for super_key in super_keycodes: if super_key in keycodes: mod_keycodes['Super'] = keycodes self.modifier_bits['Super'] = self.modifier_bits[name] for mode_switch_key in mode_switch_keycodes: if mode_switch_key in keycodes: mod_keycodes['Mode_switch'] = keycodes self.modifier_bits['Mode_switch'] = self.modifier_bits[ name] #Assign the mod_keycodes to a local variable for access self.modifier_keycodes = mod_keycodes self.all_mod_keycodes = all_mod_keycodes #TODO: Determine if this might fail, perhaps iterate through the mapping #and identify all keycodes with registered keypad keysyms? #Acquire the full list of keypad keycodes self.keypad_keycodes = [] keypad = [ 'Space', 'Tab', 'Enter', 'F1', 'F2', 'F3', 'F4', 'Home', 'Left', 'Up', 'Right', 'Down', 'Prior', 'Page_Up', 'Next', 'Page_Down', 'End', 'Begin', 'Insert', 'Delete', 'Equal', 'Multiply', 'Add', 'Separator', 'Subtract', 'Decimal', 'Divide', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ] for keyname in keypad: keypad_keycode = self.lookup_character_keycode('KP_' + keyname) self.keypad_keycodes.append(keypad_keycode) def lookup_character_keycode(self, character): """ Looks up the keysym for the character then returns the keycode mapping for that keysym. """ keysym = self.string_to_keysym.get(character, 0) if keysym == 0: keysym = self.string_to_keysym.get(KEYSYMS[character], 0) return self.display.keysym_to_keycode(keysym) def get_translation_dicts(self): """ Returns dictionaries for the translation of keysyms to strings and from strings to keysyms. """ keysym_to_string_dict = {} string_to_keysym_dict = {} #XK loads latin1 and miscellany on its own; load latin2-4 and greek Xlib.XK.load_keysym_group('latin2') Xlib.XK.load_keysym_group('latin3') Xlib.XK.load_keysym_group('latin4') Xlib.XK.load_keysym_group('greek') #Make a standard dict and the inverted dict for string, keysym in Xlib.XK.__dict__.items(): if string.startswith('XK_'): string_to_keysym_dict[string[3:]] = keysym keysym_to_string_dict[keysym] = string[3:] return keysym_to_string_dict, string_to_keysym_dict def ascii_printable(self, keysym): """ If the keysym corresponds to a non-printable ascii character this will return False. If it is printable, then True will be returned. ascii 11 (vertical tab) and ascii 12 are printable, chr(11) and chr(12) will return '\x0b' and '\x0c' respectively. """ if 0 <= keysym < 9: return False elif 13 < keysym < 32: return False elif keysym > 126: return False else: return True
if x11: display = Display() context = display.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) modifier_keycodes = display.get_modifier_mapping() current_key_modifiers = 0 def modifier_code(keycode): if keycode == 0: return None for m in range(0, len(modifier_keycodes)): if keycode in modifier_keycodes[m]: return m def key_press_handler(reply): global current_key_modifiers data = reply.data while len(data):
class KeyboardListener: def __init__(self, callback=None, on_error=None): self.keys = [] self.grabbed: List = [] self.temporary_grab: List = [] self.on_error = on_error self.callback = callback # XLib errors are received asynchronously, thus the need for a running state flag self.stopped = False self.well_thread = threading.Thread(target=self.x_client_loop, daemon=True, name='hotkey well thread') self.well_connection = Display() self.well_connection.set_error_handler(self._local_display_error_handler) self.mod_keys_set = set() for mods in self.well_connection.get_modifier_mapping(): for mod in mods: self.mod_keys_set.add(mod) self.root: Xlib.display.Window = self.well_connection.screen().root self.root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask) self.accelerators_root = {'level': 0, 'children': []} self.contextual_accelerators = self.accelerators_root # # API # def add(self, key): self.keys.append(key) def start(self): for key in self.keys: self._bind_to_root(key) self.well_thread.start() def _bind_to_root(self, key): self._bind(key, self.accelerators_root) def _bind(self, key: Key, node): gdk_key_val, code, mask = parse_accelerator(key.accelerator) node['has_children'] = True if (code, mask) in node: raise Exception('key ({}) already mapped'.format(', '.join(key.accelerator))) we = {'code': code, 'mask': mask, 'has_children': False, 'children': [], 'level': node['level'] + 1, 'key': key} node[(code, mask)] = we node['children'].append(we) if we['level'] == 1: self._grab_keys(code, mask) self.well_connection.sync() if self.stopped: raise Exception('Unable to bind: {}'.format(', '.join(key.accelerator))) for combination in key.combinations: self._bind(combination, we) def stop(self): self.stopped = True self.well_connection.close() # # xlib plugs # def _local_display_error_handler(self, exception, *args): print('Error at local display: {}'.format(exception), file=sys.stderr) if not self.stopped: self.stopped = True self.on_error() # # Internal API # def _grab_keys(self, code, mask): self.root.grab_key(code, mask, True, X.GrabModeAsync, X.GrabModeAsync) self.root.grab_key(code, mask | X.Mod2Mask, True, X.GrabModeAsync, X.GrabModeAsync) self.root.grab_key(code, mask | X.LockMask, True, X.GrabModeAsync, X.GrabModeAsync) self.root.grab_key(code, mask | X.Mod2Mask | X.LockMask, True, X.GrabModeAsync, X.GrabModeAsync) self.grabbed.append((code, mask)) def _ungrab_keys(self, code, mask): self.root.ungrab_key(code, mask) self.root.ungrab_key(code, mask | X.Mod2Mask) self.root.ungrab_key(code, mask | X.LockMask) self.root.ungrab_key(code, mask | X.Mod2Mask | X.LockMask) self.grabbed.remove((code, mask)) # # Event handling # def x_client_loop(self): while not self.stopped: event = self.well_connection.next_event() if event.type == X.KeyPress and event.detail not in self.mod_keys_set: self.handle_keypress(event) # http://python-xlib.sourceforge.net/doc/html/python-xlib_13.html def handle_keypress(self, event: Xlib.protocol.event.KeyPress): _wasmapped, keyval, egroup, level, consumed = Gdk.Keymap.get_default().translate_keyboard_state( event.detail, Gdk.ModifierType(event.state), 0) code = event.detail mask = normalize_state(event.state) event.keyval = keyval event.keymod = Gdk.ModifierType(mask) # TODO: explain key_name = Gdk.keyval_name(event.keyval) # print('key: {} wid: {} root_x: {} event_x: {}'.format(key_name, event.window.id, event.root_x, event.event_x)) if (code, mask) not in self.contextual_accelerators: self.reset_key_streak(event.time) if (code, mask) in self.contextual_accelerators: self.callback(self.contextual_accelerators[(code, mask)]['key'], event) if self.contextual_accelerators[(code, mask)]['has_children']: self.contextual_accelerators = self.contextual_accelerators[(code, mask)] self.root.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, event.time) self.temporary_grab = True else: self.reset_key_streak(event.time) def reset_key_streak(self, time): self.contextual_accelerators = self.accelerators_root if self.temporary_grab: self.well_connection.ungrab_keyboard(time) self.temporary_grab = False
class PyKeyboardEvent(PyKeyboardEventMeta): """ The PyKeyboardEvent implementation for X11 systems (mostly linux). This allows one to listen for keyboard input. """ def __init__(self, display=None): self.display = Display(display) self.display2 = Display(display) self.ctx = self.display2.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.lock_meaning = None #Get these dictionaries for converting keysyms and strings self.keysym_to_string, self.string_to_keysym = self.get_translation_dicts() #Identify and register special groups of keys self.modifier_keycodes = {} self.all_mod_keycodes = [] self.keypad_keycodes = [] #self.configure_keys() #Direct access to the display's keycode-to-keysym array #print('Keycode to Keysym map') #for i in range(len(self.display._keymap_codes)): # print('{0}: {1}'.format(i, self.display._keymap_codes[i])) PyKeyboardEventMeta.__init__(self) def run(self): """Begin listening for keyboard input events.""" self.state = True if self.capture: self.display2.screen().root.grab_keyboard(True, X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): """Stop listening for keyboard input events.""" self.state = False self.display.record_disable_context(self.ctx) self.display.ungrab_keyboard(X.CurrentTime) self.display.flush() self.display2.record_disable_context(self.ctx) self.display2.ungrab_keyboard(X.CurrentTime) self.display2.flush() def handler(self, reply): """Upper level handler of keyboard events.""" data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self.display.display, None, None) if self.escape(event): # Quit if this returns True self.stop() else: self._tap(event) def _tap(self, event): keycode = event.detail press_bool = (event.type == X.KeyPress) #Detect modifier states from event.state for mod, bit in self.modifier_bits.items(): self.modifiers[mod] = event.state & bit if keycode in self.all_mod_keycodes: keysym = self.display.keycode_to_keysym(keycode, 0) character = self.keysym_to_string[keysym] else: character = self.lookup_char_from_keycode(keycode) #All key events get passed to self.tap() self.tap(keycode, character, press=press_bool) def lookup_char_from_keycode(self, keycode): """ This will conduct a lookup of the character or string associated with a given keycode. """ #TODO: Logic should be strictly adapted from X11's src/KeyBind.c #Right now the logic is based off of #http://tronche.com/gui/x/xlib/input/keyboard-encoding.html #Which I suspect is not the whole story and may likely cause bugs keysym_index = 0 #TODO: Display's Keysyms per keycode count? Do I need this? #If the Num_Lock is on, and the keycode corresponds to the keypad if self.modifiers['Num_Lock'] and keycode in self.keypad_keycodes: if self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 0 else: keysym_index = 1 elif not self.modifiers['Shift'] and self.modifiers['Caps_Lock']: #Use the first keysym if uppercase or uncased #Use the uppercase keysym if the first is lowercase (second) keysym_index = 0 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr(keysym) in 'abcdefghijklmnopqrstuvwxyz': keysym_index = 1 elif self.modifiers['Shift'] and self.modifiers['Caps_Lock']: keysym_index = 1 keysym = self.display.keycode_to_keysym(keycode, keysym_index) #TODO: Support Unicode, Greek, and special latin characters if keysym & 0x7f == keysym and chr(keysym) in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': keysym_index = 0 elif self.modifiers['Shift'] or self.modifiers['Shift_Lock']: keysym_index = 1 if self.modifiers['Mode_switch']: keysym_index += 2 #Finally! Get the keysym keysym = self.display.keycode_to_keysym(keycode, keysym_index) #If the character is ascii printable, return that character if keysym & 0x7f == keysym and self.ascii_printable(keysym): return chr(keysym) #If the character was not printable, look for its name try: char = self.keysym_to_string[keysym] except KeyError: print('Unable to determine character.') print('Keycode: {0} KeySym {1}'.format(keycode, keysym)) return None else: return char def escape(self, event): if event.detail == self.lookup_character_keycode('Escape'): return True return False def configure_keys(self): """ This function locates the keycodes corresponding to special groups of keys and creates data structures of them for use by the PyKeyboardEvent instance; including the keypad keys and the modifiers. The keycodes pertaining to the keyboard modifiers are assigned by the modifier name in a dictionary. This dictionary can be accessed in the following manner: self.modifier_keycodes['Shift'] # All keycodes for Shift Masking It also assigns certain named modifiers (Alt, Num_Lock, Super), which may be dynamically assigned to Mod1 - Mod5 on different platforms. This should generally allow the user to do the following lookups on any system: self.modifier_keycodes['Alt'] # All keycodes for Alt Masking self.modifiers['Alt'] # State of Alt mask, non-zero if "ON" """ modifier_mapping = self.display.get_modifier_mapping() all_mod_keycodes = [] mod_keycodes = {} mod_index = [('Shift', X.ShiftMapIndex), ('Lock', X.LockMapIndex), ('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex), ('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex), ('Mod5', X.Mod5MapIndex)] #This gets the list of all keycodes per Modifier, assigns to name for name, index in mod_index: codes = [v for v in list(modifier_mapping[index]) if v] mod_keycodes[name] = codes all_mod_keycodes += codes def lookup_keycode(string): keysym = self.string_to_keysym[string] return self.display.keysym_to_keycode(keysym) #Dynamically assign Lock to Caps_Lock, Shift_Lock, Alt, Num_Lock, Super, #and mode switch. Set in both mod_keycodes and self.modifier_bits #Try to assign Lock to Caps_Lock or Shift_Lock shift_lock_keycode = lookup_keycode('Shift_Lock') caps_lock_keycode = lookup_keycode('Caps_Lock') if shift_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Shift_Lock'] = [shift_lock_keycode] self.modifier_bits['Shift_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Shift_Lock' elif caps_lock_keycode in mod_keycodes['Lock']: mod_keycodes['Caps_Lock'] = [caps_lock_keycode] self.modifier_bits['Caps_Lock'] = self.modifier_bits['Lock'] self.lock_meaning = 'Caps_Lock' else: self.lock_meaning = None #print('Lock is bound to {0}'.format(self.lock_meaning)) #Need to find out which Mod# to use for Alt, Num_Lock, Super, and #Mode_switch num_lock_keycodes = [lookup_keycode('Num_Lock')] alt_keycodes = [lookup_keycode(i) for i in ['Alt_L', 'Alt_R']] super_keycodes = [lookup_keycode(i) for i in ['Super_L', 'Super_R']] mode_switch_keycodes = [lookup_keycode('Mode_switch')] #Detect Mod number for Alt, Num_Lock, and Super for name, keycodes in list(mod_keycodes.items()): for alt_key in alt_keycodes: if alt_key in keycodes: mod_keycodes['Alt'] = keycodes self.modifier_bits['Alt'] = self.modifier_bits[name] for num_lock_key in num_lock_keycodes: if num_lock_key in keycodes: mod_keycodes['Num_Lock'] = keycodes self.modifier_bits['Num_Lock'] = self.modifier_bits[name] for super_key in super_keycodes: if super_key in keycodes: mod_keycodes['Super'] = keycodes self.modifier_bits['Super'] = self.modifier_bits[name] for mode_switch_key in mode_switch_keycodes: if mode_switch_key in keycodes: mod_keycodes['Mode_switch'] = keycodes self.modifier_bits['Mode_switch'] = self.modifier_bits[name] #Assign the mod_keycodes to a local variable for access self.modifier_keycodes = mod_keycodes self.all_mod_keycodes = all_mod_keycodes #TODO: Determine if this might fail, perhaps iterate through the mapping #and identify all keycodes with registered keypad keysyms? #Acquire the full list of keypad keycodes self.keypad_keycodes = [] keypad = ['Space', 'Tab', 'Enter', 'F1', 'F2', 'F3', 'F4', 'Home', 'Left', 'Up', 'Right', 'Down', 'Prior', 'Page_Up', 'Next', 'Page_Down', 'End', 'Begin', 'Insert', 'Delete', 'Equal', 'Multiply', 'Add', 'Separator', 'Subtract', 'Decimal', 'Divide', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] for keyname in keypad: keypad_keycode = self.lookup_character_keycode('KP_' + keyname) self.keypad_keycodes.append(keypad_keycode) def lookup_character_keycode(self, character): """ Looks up the keysym for the character then returns the keycode mapping for that keysym. """ keysym = self.string_to_keysym.get(character, 0) if keysym == 0: keysym = self.string_to_keysym.get(special_X_keysyms[character], 0) return self.display.keysym_to_keycode(keysym) def get_translation_dicts(self): """ Returns dictionaries for the translation of keysyms to strings and from strings to keysyms. """ keysym_to_string_dict = {} string_to_keysym_dict = {} #XK loads latin1 and miscellany on its own; load latin2-4 and greek Xlib.XK.load_keysym_group('latin2') Xlib.XK.load_keysym_group('latin3') Xlib.XK.load_keysym_group('latin4') Xlib.XK.load_keysym_group('greek') #Make a standard dict and the inverted dict for string, keysym in Xlib.XK.__dict__.items(): if string.startswith('XK_'): string_to_keysym_dict[string[3:]] = keysym keysym_to_string_dict[keysym] = string[3:] return keysym_to_string_dict, string_to_keysym_dict def ascii_printable(self, keysym): """ If the keysym corresponds to a non-printable ascii character this will return False. If it is printable, then True will be returned. ascii 11 (vertical tab) and ascii 12 are printable, chr(11) and chr(12) will return '\x0b' and '\x0c' respectively. """ if 0 <= keysym < 9: return False elif 13 < keysym < 32: return False elif keysym > 126: return False else: return True
class PyKeyboardEvent(PyKeyboardEventMeta): """ The PyKeyboardEvent implementation for X11 systems (mostly linux). This allows one to listen for keyboard input. """ def __init__(self, display=None): PyKeyboardEventMeta.__init__(self) self.display = Display(display) self.display2 = Display(display) self.ctx = self.display2.record_create_context( 0, [record.AllClients], [{ 'core_requests': (0, 0), 'core_replies': (0, 0), 'ext_requests': (0, 0, 0, 0), 'ext_replies': (0, 0, 0, 0), 'delivered_events': (0, 0), 'device_events': (X.KeyPress, X.KeyRelease), 'errors': (0, 0), 'client_started': False, 'client_died': False, }]) self.shift_state = 0 # 0 is off, 1 is on self.alt_state = 0 # 0 is off, 2 is on self.mod_keycodes = self.get_mod_keycodes() def run(self): """Begin listening for keyboard input events.""" self.state = True if self.capture: self.display2.screen().root.grab_keyboard(True, X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime) self.display2.record_enable_context(self.ctx, self.handler) self.display2.record_free_context(self.ctx) def stop(self): """Stop listening for keyboard input events.""" self.state = False self.display.record_disable_context(self.ctx) self.display.ungrab_keyboard(X.CurrentTime) self.display.flush() self.display2.record_disable_context(self.ctx) self.display2.ungrab_keyboard(X.CurrentTime) self.display2.flush() def handler(self, reply): """Upper level handler of keyboard events.""" data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value(data, self.display.display, None, None) if event.type == X.KeyPress: if self.escape_code(event): # Quit if this returns True self.stop() else: self._key_press(event.detail) elif event.type == X.KeyRelease: self._key_release(event.detail) else: print('WTF: {0}'.format(event.type)) def _key_press(self, keycode): """A key has been pressed, do stuff.""" #Alter modification states if keycode in self.mod_keycodes['Shift'] or keycode in self.mod_keycodes['Lock']: self.toggle_shift_state() elif keycode in self.mod_keycodes['Alt']: self.toggle_alt_state() else: self.key_press(keycode) def _key_release(self, keycode): """A key has been released, do stuff.""" #Alter modification states if keycode in self.mod_keycodes['Shift']: self.toggle_shift_state() elif keycode in self.mod_keycodes['Alt']: self.toggle_alt_state() else: self.key_release(keycode) def escape_code(self, event): if event.detail == self.lookup_character_value('Escape'): return True return False def lookup_char_from_keycode(self, keycode): keysym =self.display.keycode_to_keysym(keycode, self.shift_state + self.alt_state) if keysym: char = self.display.lookup_string(keysym) return char else: return None def get_mod_keycodes(self): """ Detects keycodes for modifiers and parses them into a dictionary for easy access. """ modifier_mapping = self.display.get_modifier_mapping() modifier_dict = {} nti = [('Shift', X.ShiftMapIndex), ('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex), ('Alt', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex), ('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex), ('Mod5', X.Mod5MapIndex), ('Lock', X.LockMapIndex)] for n, i in nti: modifier_dict[n] = list(modifier_mapping[i]) return modifier_dict def lookup_character_value(self, character): """ Looks up the keysym for the character then returns the keycode mapping for that keysym. """ ch_keysym = string_to_keysym(character) if ch_keysym == 0: ch_keysym = string_to_keysym(special_X_keysyms[character]) return self.display.keysym_to_keycode(ch_keysym) def toggle_shift_state(self): '''Does toggling for the shift state.''' if self.shift_state == 0: self.shift_state = 1 elif self.shift_state == 1: self.shift_state = 0 else: return False return True def toggle_alt_state(self): '''Does toggling for the alt state.''' if self.alt_state == 0: self.alt_state = 2 elif self.alt_state == 2: self.alt_state = 0 else: return False return True
class KeyboardListener: def __init__(self, callback=None, on_error=None): self.on_error = on_error self.callback = callback # XLib errors are received asynchronously, thus the need for a running state flag self.stopped = False self.record_thread = threading.Thread( target=self._record, name='x keyboard listener thread') self.well_thread = threading.Thread(target=self._drop_key, daemon=True, name='hotkey well thread') self.recording_connection = Display() self.well_connection = Display() self.recording_connection.set_error_handler( self._record_display_error_handler) self.well_connection.set_error_handler( self._local_display_error_handler) if not self.recording_connection.has_extension("RECORD"): raise Exception("RECORD extension not found") r = self.recording_connection.record_get_version(0, 0) print("RECORD extension version %d.%d" % (r.major_version, r.minor_version)) self.context = self.recording_connection.record_create_context( 0, [record.AllClients], CONTEXT_FILTER) self.mod_keys_set = set() for mods in self.well_connection.get_modifier_mapping(): for mod in mods: self.mod_keys_set.add(mod) self.root = self.well_connection.screen().root self.root.change_attributes(event_mask=X.KeyPressMask | X.KeyReleaseMask) self.modifiers_count = self.modified_count = 0 self.code_map = {} self.composed_code_map = {} self.composed_mapping_first_code = None self.multiplier = '' # # API # def bind(self, key): if self.stopped: return if len(key.accelerators) == 1: self._bind_single_accelerator(key) elif len(key.accelerators) == 2: self._bind_composed_accelerator(key) self.well_connection.sync() if self.stopped: print('Unable to bind: {}'.format(', '.join(key.accelerators)), file=sys.stderr) def start(self): self.well_connection.sync() self.recording_connection.sync() if self.stopped: return self.well_thread.start() self.record_thread.start() def stop(self): self.stopped = True if self.record_thread.is_alive(): self.well_connection.record_disable_context(self.context) self.well_connection.close() print('display stopped recording') self.record_thread.join() print('recording thread ended') # # Thread targets # def _drop_key(self): while not self.stopped: self.well_connection.next_event() def _record(self): self.recording_connection.record_enable_context( self.context, self.handler) self.recording_connection.record_free_context(self.context) self.recording_connection.close() def _record_display_error_handler(self, exception, *args): print('Error at record display: {}'.format(exception), file=sys.stderr) if not self.stopped: self.stopped = True self.on_error() def _local_display_error_handler(self, exception, *args): print('Error at local display: {}'.format(exception), file=sys.stderr) if not self.stopped: self.stopped = True self.on_error() # # Internal API # def _grab_keys(self, code, mask): self.root.grab_key(code, mask, True, X.GrabModeAsync, X.GrabModeAsync) self.root.grab_key(code, mask | X.Mod2Mask, True, X.GrabModeAsync, X.GrabModeAsync) def _bind_single_accelerator(self, key): gdk_keyval, code, mask = parse_accelerator(key.accelerators[0]) self._grab_keys(code, mask) if code not in self.code_map: self.code_map[code] = {} self.code_map[code][mask] = key def _bind_composed_accelerator(self, key): gdk_keyval, code, mask = parse_accelerator(key.accelerators[0]) second_gdk_keyval, second_code, second_mask = parse_accelerator( key.accelerators[1]) self._grab_keys(code, mask) if code not in self.composed_code_map: self.composed_code_map[code] = {} if mask not in self.composed_code_map[code]: self.composed_code_map[code][mask] = {} if second_code not in self.composed_code_map[code][mask]: self.composed_code_map[code][mask][second_code] = {} if second_mask in self.composed_code_map[code][mask][second_code]: raise Exception('key ({}) already mapped'.format(', '.join( key.accelerators))) self.composed_code_map[code][mask][second_code][second_mask] = key # # Event handling # def handler(self, reply): data = reply.data while len(data): event, data = rq.EventField(None).parse_binary_value( data, self.recording_connection.display, None, None) if event.detail in self.mod_keys_set: self.modifiers_count += 1 if event.type == X.KeyPress else -1 self.modified_count = 0 continue if self.modifiers_count: self.modified_count += 1 if event.type == X.KeyPress else -1 if event.type == X.KeyPress: self.handle_keypress(event) def handle_keypress(self, event): _wasmapped, keyval, egroup, level, consumed = Gdk.Keymap.get_default( ).translate_keyboard_state(event.detail, Gdk.ModifierType(event.state), 0) code = event.detail event.keyval = keyval # TODO: explain mask = normalize_state(event.state) if self.composed_mapping_first_code and self.composed_mapping_first_code != ( code, mask): key_name = Gdk.keyval_name(event.keyval) if not mask and key_name and key_name.isdigit(): self.multiplier = self.multiplier + key_name return second_code_map = self.composed_code_map[ self.composed_mapping_first_code[0]][ self.composed_mapping_first_code[1]] if code in second_code_map and mask in second_code_map[code]: multiplier_int = int(self.multiplier) if self.multiplier else 1 self.callback(second_code_map[code][mask], event, multiplier=multiplier_int) self.composed_mapping_first_code = None elif self.modified_count == 1: if code in self.code_map and mask in self.code_map[code]: self.callback(self.code_map[code][mask], event) if code in self.composed_code_map and mask in self.composed_code_map[ code]: self.composed_mapping_first_code = (code, mask) self.multiplier = ''
# KeyPress, MouseScroll, and MouseClick actions use XTest (under X11) or uinput. # For uinput to work the user must have write access for /dev/uinput. # To get this access run sudo setfacl -m u:${user}:rw /dev/uinput # # Rule GUI keyname determination uses a local file generated # from http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h # and http://cgit.freedesktop.org/xorg/proto/x11proto/plain/XF86keysym.h # because there does not seem to be a non-X11 file for this set of key names XK_KEYS = _keysymdef.keysymdef try: import Xlib from Xlib.display import Display xdisplay = Display() modifier_keycodes = xdisplay.get_modifier_mapping( ) # there should be a way to do this in Gdk x11 = True NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW') NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID') WM_CLASS = xdisplay.intern_atom('WM_CLASS') # set up to get keyboard state using ctypes interface to libx11 import ctypes class XkbDisplay(ctypes.Structure): """ opaque struct """ class XkbStateRec(ctypes.Structure): _fields_ = [ ('group', ctypes.c_ubyte), ('locked_group', ctypes.c_ubyte),
class KeyTools: KEY_PRESS = X.KeyPress KEY_RELEASE = X.KeyRelease def __init__(self): self._xdisplay = Display() self._xroot = self._xdisplay.screen().root self._clipboard = gtk.clipboard_get() self._clipPrimay = gtk.clipboard_get("PRIMARY") self._entryForPaste = 118, X.ShiftMask self._group = 0 self.loadModifiers() self._keymap = gdk.keymap_get_default() # @UndefinedVariable def loadModifiers(self): self._modifiers = [] self._modifierList = [] for key in self._xdisplay.get_modifier_mapping(): li = [k for k in key if k] #for altgr key if 92 in li: li.append(108) self._modifierList += li self._modifiers.append(li) def filterGroup(self, entries): if entries: return [e for e in entries if e[-2] == self._group] return [] def remapKey(self, keycode, keysyms): allKeysyms = list(self._xdisplay.get_keyboard_mapping(keycode, 1)[0]) keysyms = keysyms + [0]*(4 - len(keysyms)) allKeysyms[:2] = keysyms[:2] allKeysyms[4:6] = keysyms[2:] self._xdisplay.change_keyboard_mapping(keycode, [allKeysyms]) self._xdisplay.sync() def resetMapping(self): try: process = Popen('setxkbmap -print -verbose 7'.split(), stdout=PIPE, stderr=PIPE) except OSError: print 'install setxkbmap' for line in process.stderr: print 'setxkbmap error: {}'.format(line) layout = variant = '' for line in process.stdout: line = line.rstrip() if line == '': break if line.startswith('layout:'): layout = line.split()[1] elif line.startswith('variant:'): variant = line.split()[1] break command = ['setxkbmap'] if layout: command += ['-layout', layout] if variant: command += ['-variant', variant] if layout or command: try: process = Popen(command, stdout=PIPE, stderr=PIPE) except OSError: print 'install setxkbmap' for line in process.stderr: print 'setxkbmap error: {}'.format(line) def isModifier(self, keycode): return keycode in self._modifierList def getModMask(self, keycode): for i, mods in enumerate(self._modifiers): if keycode in mods: return 2**i return 0 def modifiersKeycodeList(self): return self._modifierList def numMask(self): return X.Mod2Mask def keycode2char(self, keycode, mods, group=0): char = '' name = '' info = self._keymap.translate_keyboard_state(keycode, mods, group) if info: keysym = info[0] char = gdk.keyval_to_unicode(keysym) # @UndefinedVariable if char: char = unichr(char) name = gdk.keyval_name(keysym) # @UndefinedVariable return char or '', name or '' def removeNumLockMask(self, keycode, mod): if not self.isKeypadKey(keycode) and mod & X.Mod2Mask: return mod ^ X.Mod2Mask return mod def entry2keysym(self, keycode, modMask): info = self._keymap.translate_keyboard_state(keycode, modMask, self._group) if info: return info[0] return None def entry2name(self, keycode, modMask): keysym = self.entry2keysym(keycode, modMask) if keysym is not None: return gdk.keyval_name(keysym) # @UndefinedVariable return None def keycode2entries(self, keycode): return self.filterGroup(self._keymap.get_entries_for_keycode(keycode)) def keysym2entry(self, keysym): if not keysym: return None infos = self._keymap.get_entries_for_keyval(keysym) # @UndefinedVariable if infos: for info in infos: keycode, group, level = info if group == self._group: if level < len(LEVEL_MOD): mod = LEVEL_MOD[level] return keycode, mod return None def keysym2deadEntries(self, keysym): resp = () entry = self.keysym2entry(keysym) if entry: keycode, mod = entry resp = ((keycode, mod), ) if not resp: deadKeys = self.findWithDeadKey(keysym) if deadKeys: keyKeysym, deadKeysym = deadKeys keyKeycodes = self.keysym2entry(keyKeysym) deadKeycodes = self.keysym2entry(deadKeysym) if keyKeycodes and deadKeycodes: keyKeycode, keyMod = keyKeycodes deadKeycode, deadMod = deadKeycodes resp = ((deadKeycode, deadMod), (keyKeycode, keyMod)) return resp def keycode2charsAndNames(self, keycode): entries = self.keycode2entries(keycode) chars = [] names = [] for entry in entries: chars.append(keysym2char(entry[0])) names.append(keysym2name(entry[0])) if len(chars) >= 4: break while not names[-1]: chars.pop(-1) names.pop(-1) return chars, names def keycode2keysyms(self, keycode): entries = self.keycode2entries(keycode) return [e[0] for e in entries][:4] def char2entries(self, char): keysym = gdk.unicode_to_keyval(ord(char)) # @UndefinedVariable if keysym: return self.keysym2deadEntries(keysym) return () def findWithDeadKey(self, keysym): name = gdk.keyval_name(keysym) # @UndefinedVariable for deadName in DEAD_KEYS: if name.endswith(deadName): keyName = name[:-len(deadName)] deadName = {'ring': 'abovering', 'schwa': 'small_schwa', 'SCHWA': 'capital_schwa'}.get(deadName, deadName) deadName = 'dead_' + deadName keyKeysym = gdk.keyval_from_name(keyName) # @UndefinedVariable deadSym = gdk.keyval_from_name(deadName) # @UndefinedVariable return keyKeysym, deadSym return None def isKeypadKey(self, keycode): entry = self._keymap.get_entries_for_keycode(keycode) if entry: for info in entry: if info[2] == self._group: name = gdk.keyval_name(info[0]) # @UndefinedVariable if name and name.startswith('KP_'): return True return False def grabKey(self, keycode, modMask): self._xroot.grab_key(keycode, modMask, 0, X.GrabModeAsync, X.GrabModeAsync) if not self.isKeypadKey(keycode) and not modMask & X.Mod2Mask: self._xroot.grab_key(keycode, modMask | X.Mod2Mask, 0, X.GrabModeAsync, X.GrabModeAsync) def ungrabKey(self, keycode, modMask): self._xroot.ungrab_key(keycode, modMask) if not self.isKeypadKey(keycode) and not modMask & X.Mod2Mask: self._xroot.ungrab_key(keycode, modMask | X.Mod2Mask) def nextKeyEvent(self, typ=KEY_PRESS): if isinstance(typ, int): typ = (typ,) num = self._xdisplay.pending_events() if num: for _ in range(num): event = self._xdisplay.next_event() if event.type in typ: return keyEvent(event.type, event.detail, event.state) self._xdisplay.allow_events(X.AsyncKeyboard, X.CurrentTime) return None def slotClipboard(self, clipboard, text, backup): self.sendEntry(*self._entryForPaste) t = Timer(0.1, self.restoreClipboard, (backup,)) t.start() def restoreClipboard(self, backup): self._clipboard.request_text(lambda a, b, c: None) if backup: self._clipboard.set_text(backup or '') self._clipPrimay.clear() self._clipboard.store() def sendText(self, text): backup = self._clipboard.wait_for_text() self._clipboard.set_text(text) self._clipPrimay.set_text(text) self._clipboard.request_text(self.slotClipboard, backup) self._clipboard.store() self._clipPrimay.store() def sendKeysym(self, keysym): entries = self.keysym2deadEntries(keysym) for entry in entries: self.sendEntry(*entry) def sendEntry(self, keycode, mod): self.pressKey(keycode, mod) self.releaseKey(keycode, mod) def pressKey(self, keycode, modMask): window = self._xdisplay.get_input_focus()._data["focus"] evt = Xlib.protocol.event.KeyPress( # @UndefinedVariable time = X.CurrentTime, root = self._xroot, window = window, same_screen = 0, child = Xlib.X.NONE, root_x = 0, root_y = 0, event_x = 0, event_y = 0, state = modMask, detail = keycode ) window.send_event(evt, propagate = True) def releaseKey(self, keycode, modMask): window = self._xdisplay.get_input_focus()._data["focus"] evt = Xlib.protocol.event.KeyRelease( # @UndefinedVariable time = X.CurrentTime, root = self._xroot, window = window, same_screen = 0, child = Xlib.X.NONE, root_x = 0, root_y = 0, event_x = 0, event_y = 0, state = modMask, detail = keycode ) window.send_event(evt, propagate = True)
class Keyboard(KeyboardMeta): """ The PyKeyboard implementation for X11 systems (mostly linux). This allows one to simulate keyboard input. """ def __init__(self, display=None): PyKeyboardMeta.__init__(self) self.display = Display(display) self.root = self.display.screen().root XK.load_keysym_group('xkb') altList = self.display.keysym_to_keycodes(XK.XK_ISO_Level3_Shift) self.__usable_modifiers = (0, 1) for code, offset in altList: if code == 108 and offset == 0: self.__usable_modifiers += (4, 5) break mapping = self.display.get_modifier_mapping() self.modmasks = {} for keyname in MODIFIERS: keysym = XK.string_to_keysym(keyname) keycodes = self.display.keysym_to_keycodes(keysym) found = False for keycode, lvl in keycodes: for index, mask in MASK_INDEXES: if keycode in mapping[index]: self.modmasks[keycode] = mask found = True if found: break self.flags = { 'Shift': X.ShiftMask, 'Lock': X.LockMask, 'Ctrl': X.ControlMask, 'Alt': 0, 'AltGr': self.modmasks[altList[0][0]], 'Hankaku': 0} self.special_key_assignment() def __findUsableKeycode(self, keycodes): for code, mask in keycodes: if mask in self.__usable_modifiers: return code, mask return None, None def press_key(self, character='', modifier=0): """ Press a given character key. Also works with character keycodes as integers, but not keysyms. """ window = self.display.get_input_focus().focus char_val, char_mask = self.lookup_character_value(character) if char_val == None or char_mask == None: return False char_mask ^= modifier print character, char_mask, modifier event = protocol.event.KeyPress( detail = char_val, time = X.CurrentTime, root = self.root, window = window, child = X.NONE, root_x = 0, root_y = 0, event_x = 0, event_y = 0, state = char_mask, same_screen = 0) window.send_event(event) self.display.sync() def release_key(self, character='', modifier=0): """ Release a given character key. Also works with character keycodes as integers, but not keysyms. """ window = self.display.get_input_focus().focus char_val, char_mask = self.lookup_character_value(character) if char_val == None or char_mask == None: return False char_mask ^= modifier event = protocol.event.KeyRelease( detail = char_val, time = X.CurrentTime, root = self.root, window = window, child = X.NONE, root_x = 0, root_y = 0, event_x = 0, event_y = 0, state = char_mask, same_screen = 0) window.send_event(event) self.display.sync() def special_key_assignment(self): """ Determines the keycodes for common special keys on the keyboard. These are integer values and can be passed to the other key methods. Generally speaking, these are non-printable codes. """ #This set of keys compiled using the X11 keysymdef.h file as reference #They comprise a relatively universal set of keys, though there may be #exceptions which may come up for other OSes and vendors. Countless #special cases exist which are not handled here, but may be extended. #TTY Function Keys self.backspace_key = self.lookup_character_value('BackSpace')[0] self.tab_key = self.lookup_character_value('Tab')[0] self.linefeed_key = self.lookup_character_value('Linefeed')[0] self.clear_key = self.lookup_character_value('Clear')[0] self.return_key = self.lookup_character_value('Return')[0] self.enter_key = self.return_key # Because many keyboards call it "Enter" self.pause_key = self.lookup_character_value('Pause')[0] self.scroll_lock_key = self.lookup_character_value('Scroll_Lock')[0] self.sys_req_key = self.lookup_character_value('Sys_Req')[0] self.escape_key = self.lookup_character_value('Escape')[0] self.delete_key = self.lookup_character_value('Delete')[0] #Modifier Keys self.shift_l_key = self.lookup_character_value('Shift_L')[0] self.shift_r_key = self.lookup_character_value('Shift_R')[0] self.shift_key = self.shift_l_key # Default Shift is left Shift self.alt_l_key = self.lookup_character_value('Alt_L')[0] self.alt_r_key = self.lookup_character_value('Alt_R')[0] self.alt_key = self.alt_l_key # Default Alt is left Alt self.alt_gr_key = self.lookup_character_value('ISO_Level3_Shift')[0] self.control_l_key = self.lookup_character_value('Control_L')[0] self.control_r_key = self.lookup_character_value('Control_R')[0] self.control_key = self.control_l_key # Default Ctrl is left Ctrl self.caps_lock_key = self.lookup_character_value('Caps_Lock')[0] self.capital_key = self.caps_lock_key # Some may know it as Capital self.shift_lock_key = self.lookup_character_value('Shift_Lock')[0] self.meta_l_key = self.lookup_character_value('Meta_L')[0] self.meta_r_key = self.lookup_character_value('Meta_R')[0] self.super_l_key = self.lookup_character_value('Super_L')[0] self.windows_l_key = self.super_l_key # Cross-support; also it's printed there self.super_r_key = self.lookup_character_value('Super_R')[0] self.windows_r_key = self.super_r_key # Cross-support; also it's printed there self.hyper_l_key = self.lookup_character_value('Hyper_L')[0] self.hyper_r_key = self.lookup_character_value('Hyper_R')[0] #Cursor Control and Motion self.home_key = self.lookup_character_value('Home')[0] self.up_key = self.lookup_character_value('Up')[0] self.down_key = self.lookup_character_value('Down')[0] self.left_key = self.lookup_character_value('Left')[0] self.right_key = self.lookup_character_value('Right')[0] self.end_key = self.lookup_character_value('End')[0] self.begin_key = self.lookup_character_value('Begin')[0] self.page_up_key = self.lookup_character_value('Page_Up')[0] self.page_down_key = self.lookup_character_value('Page_Down')[0] self.prior_key = self.lookup_character_value('Prior')[0] self.next_key = self.lookup_character_value('Next')[0] #Misc Functions self.select_key = self.lookup_character_value('Select')[0] self.print_key = self.lookup_character_value('Print')[0] self.print_screen_key = self.print_key # Seems to be the same thing self.snapshot_key = self.print_key # Another name for printscreen self.execute_key = self.lookup_character_value('Execute')[0] self.insert_key = self.lookup_character_value('Insert')[0] self.undo_key = self.lookup_character_value('Undo')[0] self.redo_key = self.lookup_character_value('Redo')[0] self.menu_key = self.lookup_character_value('Menu')[0] self.apps_key = self.menu_key # Windows... self.find_key = self.lookup_character_value('Find')[0] self.cancel_key = self.lookup_character_value('Cancel')[0] self.help_key = self.lookup_character_value('Help')[0] self.break_key = self.lookup_character_value('Break')[0] self.mode_switch_key = self.lookup_character_value('Mode_switch')[0] self.script_switch_key = self.lookup_character_value('script_switch')[0] self.num_lock_key = self.lookup_character_value('Num_Lock')[0] #Keypad Keys: Dictionary structure keypad = ['Space', 'Tab', 'Enter', 'F1', 'F2', 'F3', 'F4', 'Home', 'Left', 'Up', 'Right', 'Down', 'Prior', 'Page_Up', 'Next', 'Page_Down', 'End', 'Begin', 'Insert', 'Delete', 'Equal', 'Multiply', 'Add', 'Separator', 'Subtract', 'Decimal', 'Divide', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] self.keypad_keys = {k: self.lookup_character_value('KP_'+str(k)[0]) for k in keypad} self.numpad_keys = self.keypad_keys #Function Keys/ Auxilliary Keys #FKeys self.function_keys = [None] + [self.lookup_character_value('F'+str(i)[0]) for i in xrange(1,36)] #LKeys self.l_keys = [None] + [self.lookup_character_value('L'+str(i)[0]) for i in xrange(1,11)] #RKeys self.r_keys = [None] + [self.lookup_character_value('R'+str(i)[0]) for i in xrange(1,16)] #Unsupported keys from windows self.kana_key = None self.hangeul_key = None # old name - should be here for compatibility self.hangul_key = None self.junjua_key = None self.final_key = None self.hanja_key = None self.kanji_key = None self.convert_key = None self.nonconvert_key = None self.accept_key = None self.modechange_key = None self.sleep_key = None def lookup_character_value(self, character): """ Looks up the keysym for the character then returns the keycode mapping and modifier for that keysym. """ ch_keysym = XK.string_to_keysym(character) ch_mask = 0 if ch_keysym == 0: if character in SPECIAL_X_KEYSYMS: ch_keysym = XK.string_to_keysym(SPECIAL_X_KEYSYMS[character]) elif len(character) == 1: ch_keysym = ord(character) ch_keycodes = self.display.keysym_to_keycodes(ch_keysym) if len(ch_keycodes) == 0 and len(character) == 1: ch_keycodes = self.display.keysym_to_keycodes(ord(character.lower())) ch_mask ^= X.LockMask if len(ch_keycodes) > 0: ch_keycode, mask = self.__findUsableKeycode(ch_keycodes) if ch_keycode == None or mask == None: return None, None else: ch_mask ^= mask else: return None, None if ch_mask ^ 4 < 4: ch_mask ^= 4 ch_mask ^= self.modmasks[self.alt_gr_key] return ch_keycode, ch_mask