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.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.app = app self.inputStack = collections.deque(maxlen=MAX_STACK_LENGTH) 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.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) logger.debug("Service shutdown completed.") def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowTitle): # logger.debug("Received mouse click - resetting buffer") self.inputStack.clear() # 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, window_info): logger.debug("Raw key: %r, modifiers: %r, Key: %s", rawKey, modifiers, key) logger.debug("Window visible title: %r, Window class: %r" % window_info) self.configManager.lock.acquire() # Always check global hotkeys for hotkey in self.configManager.globalHotkeys: hotkey.check_hotkey(modifiers, rawKey, window_info) if self.__shouldProcess(window_info): itemMatch = None menu = None for item in self.configManager.hotKeys: if item.check_hotkey(modifiers, rawKey, window_info): 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, window_info): #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.clear() 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, window_info, True) if not item or menu: item, menu = self.__checkTextMatches( self.configManager.allFolders, self.configManager.allItems, currentInput, window_info) 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.exception("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 try: self.inputStack.pop() except IndexError: # in case self.inputStack is empty pass return False elif len(key) > 1: # non-simple key self.inputStack.clear() self.phraseRunner.clear_last() return False else: # Key is a character self.phraseRunner.clear_last() # if len(self.inputStack) == MAX_STACK_LENGTH, front items will removed for appending new items. self.inputStack.append(key) 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.clear() self.lastStackState = '' if isinstance(item, model.Phrase): self.phraseRunner.execute(item, buffer) else: self.scriptRunner.execute(item, buffer) def __haveMatch(self, data): folder_match, item_matches = data if folder_match is not None: return True if len(item_matches) > 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 = collections.deque(maxlen=MAX_STACK_LENGTH) 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.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) logger.debug("Service shutdown completed.") def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowTitle): # logger.debug("Received mouse click - resetting buffer") self.inputStack.clear() # 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, window_info): logger.debug("Raw key: %r, modifiers: %r, Key: %s", rawKey, modifiers, key) logger.debug("Window visible title: %r, Window class: %r" % window_info) self.configManager.lock.acquire() # Always check global hotkeys for hotkey in self.configManager.globalHotkeys: hotkey.check_hotkey(modifiers, rawKey, window_info) if self.__shouldProcess(window_info): itemMatch = None menu = None for item in self.configManager.hotKeys: if item.check_hotkey(modifiers, rawKey, window_info): itemMatch = item break if itemMatch is not None: logger.info('Matched {} "{}" with hotkey and prompt={}'.format( itemMatch.__class__.__name__, itemMatch.description, itemMatch.prompt )) if itemMatch.prompt: menu = ([], [itemMatch]) else: for folder in self.configManager.hotKeyFolders: if folder.check_hotkey(modifiers, rawKey, window_info): #menu = PopupMenu(self, [folder], []) menu = ([folder], []) if menu is not None: logger.debug("Matched Folder with 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.clear() 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, window_info, True) if not item or menu: item, menu = self.__checkTextMatches( self.configManager.allFolders, self.configManager.allItems, currentInput, window_info) # type: model.Phrase, list if item: self.__tryReleaseLock() logger.info('Matched {} "{}" having abbreviations "{}" against current input'.format( item.__class__.__name__, item.description, item.abbreviations)) 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 queue at end of handle_keypress: %s", self.inputStack) self.__tryReleaseLock() def __tryReleaseLock(self): try: self.configManager.lock.release() except: logger.exception("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 try: self.inputStack.pop() except IndexError: # in case self.inputStack is empty pass return False elif len(key) > 1: # non-simple key self.inputStack.clear() self.phraseRunner.clear_last() return False else: # Key is a character self.phraseRunner.clear_last() # if len(self.inputStack) == MAX_STACK_LENGTH, front items will removed for appending new items. self.inputStack.append(key) 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.clear() self.lastStackState = '' if isinstance(item, model.Phrase): self.phraseRunner.execute(item, buffer) else: self.scriptRunner.execute(item, buffer) def __haveMatch(self, data): folder_match, item_matches = data if folder_match is not None: return True if len(item_matches) > 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