class Keystroke_Watcher: def __init__(self, master, sticky=False): self.logger = logging.getLogger(master.logger.name + '.Keystroke_Watcher') self.hm = HookManager() self.hm.KeyDown = self.on_key_down self.hm.KeyUp = self.on_key_up self.function_map = {} self.keys_held = set() self.sticky = sticky self.hm.HookKeyboard() def get_key_combo_code(self): return '+'.join( [HookConstants.IDToName(key) for key in self.keys_held]) def register_function(self, key_combo, function): self.function_map[key_combo.lower()] = function self.logger.info("Registered function <{}> to keycombo <{}>.".format( function.__name__, key_combo.lower())) def unregister_function(self, key_combo): self.logger.info("Unregistered function <{}> at keycombo <{}>".format( self.function_map[key_combo.lower()].__name__, key_combo.lower())) del self.function_map[key_combo.lower()] def on_key_down(self, event): try: self.keys_held.add(event.KeyID) finally: return True def on_key_up(self, event): keycombo = self.get_key_combo_code().lower() try: if keycombo in self.function_map.keys(): self.logger.info( "Shortcut <{}> pressed. Calling function <{}>.".format( keycombo, self.function_map[keycombo].__name__)) self.function_map[keycombo]() finally: if not self.sticky: self.keys_held.remove(event.KeyID) return True def shutdown(self): self.hm.UnhookKeyboard() def restart(self): self.keys_held = set() self.hm.HookKeyboard()
class KeybindManager: """ Interface with Mouse/Keyboard and register functions to keyboard shortcuts. """ def __init__(self, master, sticky=False): self.logger = logging.getLogger(master.logger.name + '.Keystroke_Watcher') self.hook_manager = HookManager() self.hook_manager.KeyDown = self._on_key_down self.hook_manager.KeyUp = self._on_key_up self.function_map = {} self.keys_held = set() self.sticky = sticky self.hook_manager.HookKeyboard() def get_key_combo_code(self): """ Converts the keys currently being held into a string representing the combination """ return '+'.join([HookConstants.IDToName(key) for key in self.keys_held]) def register_function(self, key_combo, function): """ Register function callback to key_combo """ self.function_map[key_combo.lower()] = function self.logger.info( "Registered function <%s> to keycombo <%s>.", function.__name__, key_combo.lower()) def unregister_function(self, key_combo): """ Stop tracking function at key_combo """ self.logger.info( "Unregistered function <%s> at keycombo <%s>", self.function_map[key_combo.lower()].__name__, key_combo.lower()) del self.function_map[key_combo.lower()] def _on_key_down(self, event): """ Simply adds the key to keys held. """ try: self.keys_held.add(event.KeyID) except Exception as exc: # Log error but don't do anything; PyHook is prone to throw some exceptions with no consequences self.logger.error("Error in _on_key_down, %s", exc) return True def _on_key_up(self, event): """ If a function for the given key_combo is found, call it """ key_combo = self.get_key_combo_code().lower() try: if key_combo in self.function_map.keys(): self.logger.info( "Shortcut <%s> pressed. Calling function <%s>.", key_combo, self.function_map[key_combo].__name__) self.function_map[key_combo]() finally: if not self.sticky and event.KeyID in self.keys_held: self.keys_held.remove(event.KeyID) return True def shutdown(self): """ Stop following keyboard events. """ self.hook_manager.UnhookKeyboard() def restart(self): """ Clear keys held and rehook keyboard. """ self.keys_held = set() self.hook_manager.HookKeyboard()
class EventManager(object): """ Event manager that runs event loop and calls event handlers. """ def __init__(self): """ Constructor. :return: None. """ # Create hook manager self._hook_manager = HookManager() # Add attributes `mouse_hook` and `keyboard_hook`. # Without the two attributes, the hook manager's method `__del__` # will raise AttributeError if its methods `HookKeyboard` and # `HookMouse` have not been called. self._hook_manager.mouse_hook = False self._hook_manager.keyboard_hook = False def start_event_loop(self): """ Start event loop. This method will not return until the event loop is stopped by \ calling :paramref:`stop_event_loop`. :return: None. """ # Start hooking key events self._hook_manager.HookKeyboard() # Start hooking mouse events self._hook_manager.HookMouse() # Create MSG structure msg = MSG() # Run event loop GetMessageW(byref(msg), 0, 0, 0) # Stop hooking key events self._hook_manager.UnhookKeyboard() # Stop hooking mouse events self._hook_manager.UnhookMouse() def stop_event_loop(self): """ Stop event loop. :return: None. """ # Post a WM_QUIT message to this thread's message queue PostQuitMessage(0) # Map event handler type to handler attribute name _EVENT_HANDLER_TYPE_TO_ATTR_NAME = { 'KeyDown': 'KeyDown', 'KeyUp': 'KeyUp', 'MouseDown': 'MouseAllButtonsDown', 'MouseUp': 'MouseAllButtonsUp', 'MouseMove': 'MouseMove', 'MouseWheel': 'MouseWheel', } def add_handler(self, handler_type, handler): """ Add event handler. :param handler_type: Event handler type. Allowed values: - 'KeyDown' - 'KeyUp' - 'MouseDown' - 'MouseUp' - 'MouseMove' - 'MouseWheel' :param handler: Event handler. :return: None. """ # Get handler attribute name attr_name = self._EVENT_HANDLER_TYPE_TO_ATTR_NAME.get( handler_type, None ) # If handler attribute name is not found, # it means given handler type is not valid. if attr_name is None: # Get error message msg = 'Error: Invalid handler type: {0}'.format( repr(handler_type) ) # Raise error raise ValueError(msg) # If handler attribute name is found. # Set the handler attribute on the hook manager setattr(self._hook_manager, attr_name, handler) def remove_handlers(self): """ Remove all event handlers. :return: None. """ # Set handler attributes on the hook manager be None self._hook_manager.KeyDown = None self._hook_manager.KeyUp = None self._hook_manager.MouseAllButtonsDown = None self._hook_manager.MouseAllButtonsUp = None self._hook_manager.MouseMove = None self._hook_manager.MouseWheel = None