def start(self): self.mediator = IoMediator(self) self.mediator.interface.initialise() self.mediator.interface.start() self.mediator.start() ConfigManager.SETTINGS[SERVICE_RUNNING] = True self.scriptRunner = ScriptRunner(self.mediator, self.app) self.phraseRunner = PhraseRunner(self) scripting.Store.GLOBALS = ConfigManager.SETTINGS[SCRIPT_GLOBALS] logger.info("Service now marked as running")
class Service: """ Handles general functionality and dispatching of results down to the correct execution service (phrase or script). """ def __init__(self, app): logger.info("Starting service") self.configManager = app.configManager ConfigManager.SETTINGS[SERVICE_RUNNING] = False self.mediator = None self.exclusiveGrab = False self.app = app self.inputStack = [] self.lastStackState = '' self.lastMenu = None def start(self): self.mediator = IoMediator(self) self.mediator.interface.initialise() self.mediator.interface.start() self.mediator.start() self.modifiers = [] ConfigManager.SETTINGS[SERVICE_RUNNING] = True self.scriptRunner = ScriptRunner(self.mediator, self.app) self.phraseRunner = PhraseRunner(self) scripting.Store.GLOBALS = ConfigManager.SETTINGS[SCRIPT_GLOBALS] logger.info("Service now marked as running") def unpause(self): ConfigManager.SETTINGS[SERVICE_RUNNING] = True logger.info("Unpausing - service now marked as running") def pause(self): ConfigManager.SETTINGS[SERVICE_RUNNING] = False logger.info("Pausing - service now marked as stopped") def is_running(self): return ConfigManager.SETTINGS[SERVICE_RUNNING] def shutdown(self, save=True): logger.info("Service shutting down") if self.mediator is not None: self.mediator.shutdown() if save: save_config(self.configManager) def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowInfo): logger.debug("Received mouse click - resetting buffer") self.inputStack = [] # If we had a menu and receive a mouse click, means we already # hid the menu. Don't need to do it again self.lastMenu = None # Clear last to prevent undo of previous phrase in unexpected places self.phraseRunner.clear_last() # Call handle_keypress for possible button hotkeys if button not in [1, 2, 3]: rawKey = key = '<button{}>'.format(str(button)) self.handle_keypress(rawKey, self.modifiers, key, windowInfo[0], windowInfo[1], button) def handle_keypress(self, rawKey, modifiers, key, windowName, windowClass, is_button=None): self.modifiers = modifiers logger.debug("Raw key: %r, modifiers: %r, Key: %s", rawKey, modifiers, key.encode("utf-8")) logger.debug("Window visible title: %r, Window class: %r" % (windowName, windowClass)) self.configManager.lock.acquire() windowInfo = (windowName, windowClass) # Always check global hotkeys, unless in an exclusive_grab for setting them # which can result in a match not releasing the grab. if not self.exclusiveGrab: for hotkey in self.configManager.globalHotkeys: hotkey.check_hotkey(modifiers, rawKey, windowInfo) if self.__shouldProcess(windowInfo): itemMatch = None menu = None for item in self.configManager.hotKeys: if item.check_hotkey(modifiers, rawKey, windowInfo): itemMatch = item break if itemMatch is not None: if not itemMatch.prompt: logger.info( "Matched hotkey phrase/script with prompt=False") else: logger.info( "Matched hotkey phrase/script with prompt=True") #menu = PopupMenu(self, [], [itemMatch]) menu = ([], [itemMatch]) else: logger.debug("No phrase/script matched hotkey") for folder in self.configManager.hotKeyFolders: if folder.check_hotkey(modifiers, rawKey, windowInfo): #menu = PopupMenu(self, [folder], []) menu = ([folder], []) if menu is not None: logger.debug("Folder matched hotkey - showing menu") if self.lastMenu is not None: #self.lastMenu.remove_from_desktop() self.app.hide_menu() self.lastStackState = '' self.lastMenu = menu #self.lastMenu.show_on_desktop() self.app.show_popup_menu(*menu) if itemMatch is not None: self.__tryReleaseLock() self.__processItem(itemMatch) if is_button: self.__tryReleaseLock() return ### --- end of hotkey processing --- ### modifierCount = len(modifiers) if modifierCount > 1 or (modifierCount == 1 and Key.SHIFT not in modifiers): self.inputStack = [] self.__tryReleaseLock() return ### --- end of processing if non-printing modifiers are on --- ### if self.__updateStack(key): currentInput = ''.join(self.inputStack) item, menu = self.__checkTextMatches( [], self.configManager.abbreviations, currentInput, windowInfo, True) if not item or menu: item, menu = self.__checkTextMatches( self.configManager.allFolders, self.configManager.allItems, currentInput, windowInfo) if item: self.__tryReleaseLock() self.__processItem(item, currentInput) elif menu: if self.lastMenu is not None: #self.lastMenu.remove_from_desktop() self.app.hide_menu() self.lastMenu = menu #self.lastMenu.show_on_desktop() self.app.show_popup_menu(*menu) logger.debug("Input stack at end of handle_keypress: %s", self.inputStack) self.__tryReleaseLock() def handle_error(self, error): pass def __tryReleaseLock(self): try: self.configManager.lock.release() except: logger.debug("Ignored locking error in handle_keypress") def run_folder(self, name): folder = None for f in self.configManager.allFolders: if f.title == name: folder = f if folder is None: raise Exception("No folder found with name '%s'" % name) self.app.show_popup_menu([folder]) def run_phrase(self, name): phrase = self.__findItem(name, model.Phrase, "phrase") self.phraseRunner.execute(phrase) def run_script(self, name): script = self.__findItem(name, model.Script, "script") self.scriptRunner.execute(script) def __findItem(self, name, objType, typeDescription): for item in self.configManager.allItems: if item.description == name and isinstance(item, objType): return item raise Exception("No %s found with name '%s'" % (typeDescription, name)) @threaded def item_selected(self, item): time.sleep(0.25) # wait for window to be active self.lastMenu = None # if an item has been selected, the menu has been hidden self.__processItem(item, self.lastStackState) def calculate_extra_keys(self, buffer): """ Determine extra keys pressed since the given buffer was built """ extraBs = len(self.inputStack) - len(buffer) if extraBs > 0: extraKeys = ''.join(self.inputStack[len(buffer)]) else: extraBs = 0 extraKeys = '' return (extraBs, extraKeys) def __updateStack(self, key): """ Update the input stack in non-hotkey mode, and determine if anything further is needed. @return: True if further action is needed """ #if self.lastMenu is not None: # if not ConfigManager.SETTINGS[MENU_TAKES_FOCUS]: # self.app.hide_menu() # # self.lastMenu = None if key == Key.ENTER: # Special case - map Enter to \n key = '\n' if key == Key.TAB: # Special case - map Tab to \t key = '\t' if key == Key.BACKSPACE: if ConfigManager.SETTINGS[ UNDO_USING_BACKSPACE] and self.phraseRunner.can_undo(): self.phraseRunner.undo_expansion() else: # handle backspace by dropping the last saved character self.inputStack = self.inputStack[:-1] return False elif len(key) > 1: # non-simple key self.inputStack = [] self.phraseRunner.clear_last() return False else: # Key is a character self.phraseRunner.clear_last() self.inputStack.append(key) if len(self.inputStack) > MAX_STACK_LENGTH: self.inputStack.pop(0) return True def __checkTextMatches(self, folders, items, buffer, windowInfo, immediate=False): """ Check for an abbreviation/predictive match among the given folder and items (scripts, phrases). @return: a tuple possibly containing an item to execute, or a menu to show """ itemMatches = [] folderMatches = [] for item in items: if item.check_input(buffer, windowInfo): if not item.prompt and immediate: return (item, None) else: itemMatches.append(item) for folder in folders: if folder.check_input(buffer, windowInfo): folderMatches.append(folder) break # There should never be more than one folder match anyway if self.__menuRequired(folderMatches, itemMatches, buffer): self.lastStackState = buffer #return (None, PopupMenu(self, folderMatches, itemMatches)) return (None, (folderMatches, itemMatches)) elif len(itemMatches) == 1: self.lastStackState = buffer return (itemMatches[0], None) else: return (None, None) def __shouldProcess(self, windowInfo): """ Return a boolean indicating whether we should take any action on the keypress """ return windowInfo[0] != "Set Abbreviations" and self.is_running( ) and not self.exclusiveGrab def __processItem(self, item, buffer=''): self.inputStack = [] self.lastStackState = '' if isinstance(item, model.Phrase): self.phraseRunner.execute(item, buffer) else: self.scriptRunner.execute(item, buffer) def __haveMatch(self, data): folderMatch, itemMatches = data if folderMatch is not None: return True if len(itemMatches) > 0: return True return False def __menuRequired(self, folders, items, buffer): """ @return: a boolean indicating whether a menu is needed to allow the user to choose """ if len(folders) > 0: # Folders always need a menu return True if len(items) == 1: return items[0].should_prompt(buffer) elif len(items) > 1: # More than one 'item' (phrase/script) needs a menu return True return False
class Service: """ Handles general functionality and dispatching of results down to the correct execution service (phrase or script). """ def __init__(self, app): logger.info("Starting service") self.configManager = app.configManager ConfigManager.SETTINGS[SERVICE_RUNNING] = False self.mediator = None self.app = app self.inputStack = [] self.lastStackState = '' self.lastMenu = None def start(self): self.mediator = IoMediator(self) self.mediator.interface.initialise() self.mediator.interface.start() self.mediator.start() ConfigManager.SETTINGS[SERVICE_RUNNING] = True self.scriptRunner = ScriptRunner(self.mediator, self.app) self.phraseRunner = PhraseRunner(self) scripting.Store.GLOBALS = ConfigManager.SETTINGS[SCRIPT_GLOBALS] logger.info("Service now marked as running") def unpause(self): ConfigManager.SETTINGS[SERVICE_RUNNING] = True logger.info("Unpausing - service now marked as running") def pause(self): ConfigManager.SETTINGS[SERVICE_RUNNING] = False logger.info("Pausing - service now marked as stopped") def is_running(self): return ConfigManager.SETTINGS[SERVICE_RUNNING] def shutdown(self, save=True): logger.info("Service shutting down") if self.mediator is not None: self.mediator.shutdown() if save: save_config(self.configManager) def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowTitle): logger.debug("Received mouse click - resetting buffer") self.inputStack = [] # If we had a menu and receive a mouse click, means we already # hid the menu. Don't need to do it again self.lastMenu = None # Clear last to prevent undo of previous phrase in unexpected places self.phraseRunner.clear_last() def handle_keypress(self, rawKey, modifiers, key, windowName, windowClass): logger.debug("Raw key: %r, modifiers: %r, Key: %s", rawKey, modifiers, key.encode("utf-8")) logger.debug("Window visible title: %r, Window class: %r" % (windowName, windowClass)) self.configManager.lock.acquire() windowInfo = (windowName, windowClass) # Always check global hotkeys for hotkey in self.configManager.globalHotkeys: hotkey.check_hotkey(modifiers, rawKey, windowInfo) if self.__shouldProcess(windowInfo): itemMatch = None menu = None for item in self.configManager.hotKeys: if item.check_hotkey(modifiers, rawKey, windowInfo): itemMatch = item break if itemMatch is not None: if not itemMatch.prompt: logger.info("Matched hotkey phrase/script with prompt=False") else: logger.info("Matched hotkey phrase/script with prompt=True") #menu = PopupMenu(self, [], [itemMatch]) menu = ([], [itemMatch]) else: logger.debug("No phrase/script matched hotkey") for folder in self.configManager.hotKeyFolders: if folder.check_hotkey(modifiers, rawKey, windowInfo): #menu = PopupMenu(self, [folder], []) menu = ([folder], []) if menu is not None: logger.debug("Folder matched hotkey - showing menu") if self.lastMenu is not None: #self.lastMenu.remove_from_desktop() self.app.hide_menu() self.lastStackState = '' self.lastMenu = menu #self.lastMenu.show_on_desktop() self.app.show_popup_menu(*menu) if itemMatch is not None: self.__tryReleaseLock() self.__processItem(itemMatch) ### --- end of hotkey processing --- ### modifierCount = len(modifiers) if modifierCount > 1 or (modifierCount == 1 and Key.SHIFT not in modifiers): self.inputStack = [] self.__tryReleaseLock() return ### --- end of processing if non-printing modifiers are on --- ### if self.__updateStack(key): currentInput = ''.join(self.inputStack) item, menu = self.__checkTextMatches([], self.configManager.abbreviations, currentInput, windowInfo, True) if not item or menu: item, menu = self.__checkTextMatches(self.configManager.allFolders, self.configManager.allItems, currentInput, windowInfo) if item: self.__tryReleaseLock() self.__processItem(item, currentInput) elif menu: if self.lastMenu is not None: #self.lastMenu.remove_from_desktop() self.app.hide_menu() self.lastMenu = menu #self.lastMenu.show_on_desktop() self.app.show_popup_menu(*menu) logger.debug("Input stack at end of handle_keypress: %s", self.inputStack) self.__tryReleaseLock() def __tryReleaseLock(self): try: self.configManager.lock.release() except: logger.debug("Ignored locking error in handle_keypress") def run_folder(self, name): folder = None for f in self.configManager.allFolders: if f.title == name: folder = f if folder is None: raise Exception("No folder found with name '%s'" % name) self.app.show_popup_menu([folder]) def run_phrase(self, name): phrase = self.__findItem(name, model.Phrase, "phrase") self.phraseRunner.execute(phrase) def run_script(self, name): script = self.__findItem(name, model.Script, "script") self.scriptRunner.execute(script) def __findItem(self, name, objType, typeDescription): for item in self.configManager.allItems: if item.description == name and isinstance(item, objType): return item raise Exception("No %s found with name '%s'" % (typeDescription, name)) @threaded def item_selected(self, item): time.sleep(0.25) # wait for window to be active self.lastMenu = None # if an item has been selected, the menu has been hidden self.__processItem(item, self.lastStackState) def calculate_extra_keys(self, buffer): """ Determine extra keys pressed since the given buffer was built """ extraBs = len(self.inputStack) - len(buffer) if extraBs > 0: extraKeys = ''.join(self.inputStack[len(buffer)]) else: extraBs = 0 extraKeys = '' return (extraBs, extraKeys) def __updateStack(self, key): """ Update the input stack in non-hotkey mode, and determine if anything further is needed. @return: True if further action is needed """ #if self.lastMenu is not None: # if not ConfigManager.SETTINGS[MENU_TAKES_FOCUS]: # self.app.hide_menu() # # self.lastMenu = None if key == Key.ENTER: # Special case - map Enter to \n key = '\n' if key == Key.TAB: # Special case - map Tab to \t key = '\t' if key == Key.BACKSPACE: if ConfigManager.SETTINGS[UNDO_USING_BACKSPACE] and self.phraseRunner.can_undo(): self.phraseRunner.undo_expansion() else: # handle backspace by dropping the last saved character self.inputStack = self.inputStack[:-1] return False elif len(key) > 1: # non-simple key self.inputStack = [] self.phraseRunner.clear_last() return False else: # Key is a character self.phraseRunner.clear_last() self.inputStack.append(key) if len(self.inputStack) > MAX_STACK_LENGTH: self.inputStack.pop(0) return True def __checkTextMatches(self, folders, items, buffer, windowInfo, immediate=False): """ Check for an abbreviation/predictive match among the given folder and items (scripts, phrases). @return: a tuple possibly containing an item to execute, or a menu to show """ itemMatches = [] folderMatches = [] for item in items: if item.check_input(buffer, windowInfo): if not item.prompt and immediate: return (item, None) else: itemMatches.append(item) for folder in folders: if folder.check_input(buffer, windowInfo): folderMatches.append(folder) break # There should never be more than one folder match anyway if self.__menuRequired(folderMatches, itemMatches, buffer): self.lastStackState = buffer #return (None, PopupMenu(self, folderMatches, itemMatches)) return (None, (folderMatches, itemMatches)) elif len(itemMatches) == 1: self.lastStackState = buffer return (itemMatches[0], None) else: return (None, None) def __shouldProcess(self, windowInfo): """ Return a boolean indicating whether we should take any action on the keypress """ return windowInfo[0] != "Set Abbreviations" and self.is_running() def __processItem(self, item, buffer=''): self.inputStack = [] self.lastStackState = '' if isinstance(item, model.Phrase): self.phraseRunner.execute(item, buffer) else: self.scriptRunner.execute(item, buffer) def __haveMatch(self, data): folderMatch, itemMatches = data if folder is not None: return True if len(items) > 0: return True return False def __menuRequired(self, folders, items, buffer): """ @return: a boolean indicating whether a menu is needed to allow the user to choose """ if len(folders) > 0: # Folders always need a menu return True if len(items) == 1: return items[0].should_prompt(buffer) elif len(items) > 1: # More than one 'item' (phrase/script) needs a menu return True return False