Esempio n. 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.GLOBALS = ConfigManager.SETTINGS[SCRIPT_GLOBALS]
     logger.info("Service now marked as running")
Esempio n. 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.GLOBALS = ConfigManager.SETTINGS[SCRIPT_GLOBALS]
     logger.info("Service now marked as running")
Esempio n. 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.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
Esempio n. 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 = []
        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