Example #1
0
 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")
Example #2
0
 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")
Example #3
0
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
Example #4
0
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