Example #1
0
 def speak(self, speechSequence: SpeechSequence, priority: Spri):
     log._speechManagerUnitTest("speak (priority %r): %r", priority,
                                speechSequence)
     interrupt = self._queueSpeechSequence(speechSequence, priority)
     self._doRemoveCancelledSpeechCommands()
     # If speech isn't already in progress, we need to push the first speech.
     push = self._hasNoMoreSpeech() or not self._synthStillSpeaking()
     log._speechManagerDebug(
         f"Will interrupt: {interrupt}"
         f" Will push: {push}"
         f" | _indexesSpeaking: {self._indexesSpeaking!r}"
         f" | _curPriQueue valid: {not self._hasNoMoreSpeech()}"
         f" | _shouldPushWhenDoneSpeaking: {self._shouldPushWhenDoneSpeaking}"
         f" | _cancelledLastSpeechWithSynth {self._cancelledLastSpeechWithSynth}"
     )
     if interrupt:
         log._speechManagerDebug("Interrupting speech")
         getSynth().cancel()
         self._indexesSpeaking.clear()
         self._cancelCommandsForUtteranceBeingSpokenBySynth.clear()
         push = True
     if push:
         log._speechManagerDebug("Pushing next speech")
         self._pushNextSpeech(True)
     else:
         log._speechManagerDebug("Not pushing speech")
Example #2
0
 def _handleIndex(self, index: int):
     log._speechManagerDebug(f"Handle index: {index}")
     # A synth (such as OneCore) may skip indexes
     # If before another index, with no text content in between.
     # Therefore, detect this and ensure we handle all skipped indexes.
     handleIndexes = []
     for oldIndex in list(self._indexesSpeaking):
         if self._isIndexABeforeIndexB(oldIndex, index):
             log.debugWarning("Handling skipped index %s" % oldIndex)
             handleIndexes.append(oldIndex)
     handleIndexes.append(index)
     valid, endOfUtterance = False, False
     for i in handleIndexes:
         try:
             self._indexesSpeaking.remove(i)
         except ValueError:
             log.debug(
                 "Unknown index %s, speech probably cancelled from main thread."
                 % i)
             break  # try the rest, this is a very unexpected path.
         if i != index:
             log.debugWarning("Handling skipped index %s" % i)
         # we must do the following for each index, any/all of them may be end of utterance, which must
         # trigger _pushNextSpeech
         _valid, _endOfUtterance = self._removeCompletedFromQueue(i)
         valid = valid or _valid
         endOfUtterance = endOfUtterance or _endOfUtterance
         if _valid:
             callbackCommand = self._indexesToCallbacks.pop(i, None)
             if callbackCommand:
                 try:
                     log._speechManagerUnitTest(
                         f"CallbackCommand Start: {callbackCommand!r}")
                     callbackCommand.run()
                     log._speechManagerUnitTest("CallbackCommand End")
                 except Exception:
                     log.exception("Error running speech callback")
     self._doRemoveCancelledSpeechCommands()
     shouldPush = (
         endOfUtterance
         and not self._synthStillSpeaking()  # stops double speaking errors
     )
     if shouldPush:
         if self._indexesSpeaking:
             log._speechManagerDebug(
                 f"Indexes speaking: {self._indexesSpeaking!r},"
                 f" queue: {self._curPriQueue.pendingSequences}")
         # Even if we have many indexes, we should only push next speech once.
         self._pushNextSpeech(False)
Example #3
0
	def _doRemoveCancelledSpeechCommands(self):
		if not _shouldCancelExpiredFocusEvents():
			return
		# Don't delete commands while iterating over _cancelCommandsForUtteranceBeingSpokenBySynth.
		latestCancelledUtteranceIndex = self._getMostRecentlyCancelledUtterance()
		log._speechManagerDebug(f"Last index: {latestCancelledUtteranceIndex}")
		if latestCancelledUtteranceIndex is not None:
			log._speechManagerDebug(f"Cancel and push speech")
			# Minimise the number of calls to _removeCompletedFromQueue by using the most recently cancelled
			# utterance index. This will remove all older queued speech also.
			self._removeCompletedFromQueue(latestCancelledUtteranceIndex)
			getSynth().cancel()
			self._cancelledLastSpeechWithSynth = True
			self._cancelCommandsForUtteranceBeingSpokenBySynth.clear()
			self._indexesSpeaking.clear()
			self._pushNextSpeech(True)
Example #4
0
def doPreGainFocus(obj, sleepMode=False):
    oldForeground = api.getForegroundObject()
    oldFocus = api.getFocusObject()
    oldTreeInterceptor = oldFocus.treeInterceptor if oldFocus else None
    api.setFocusObject(obj)

    if speech.manager._shouldCancelExpiredFocusEvents():
        log._speechManagerDebug(
            "executeEvent: Removing cancelled speech commands.")
        # ask speechManager to check if any of it's queued utterances should be cancelled
        # Note: Removing cancelled speech commands should happen after all dependencies for the isValid check
        # have been updated:
        # - lastQueuedFocusObject
        # - obj.WAS_GAIN_FOCUS_OBJ_ATTR_NAME
        # - api.getFocusAncestors()
        # These are updated:
        # - lastQueuedFocusObject & obj.WAS_GAIN_FOCUS_OBJ_ATTR_NAME
        #   - Set in stack: _trackFocusObject, eventHandler.queueEvent
        #   - Which results in executeEvent being called, then doPreGainFocus
        # - api.getFocusAncestors() via api.setFocusObject() called in doPreGainFocus
        speech._manager.removeCancelledSpeechCommands()

    if globalVars.focusDifferenceLevel <= 1:
        newForeground = api.getDesktopObject().objectInForeground()
        if not newForeground:
            log.debugWarning(
                "Can not get real foreground, resorting to focus ancestors")
            ancestors = api.getFocusAncestors()
            if len(ancestors) > 1:
                newForeground = ancestors[1]
            else:
                newForeground = obj
        api.setForegroundObject(newForeground)
        executeEvent('foreground', newForeground)
    if sleepMode: return True
    #Fire focus entered events for all new ancestors of the focus if this is a gainFocus event
    for parent in globalVars.focusAncestors[globalVars.focusDifferenceLevel:]:
        executeEvent("focusEntered", parent)
    if obj.treeInterceptor is not oldTreeInterceptor:
        if hasattr(oldTreeInterceptor, "event_treeInterceptor_loseFocus"):
            oldTreeInterceptor.event_treeInterceptor_loseFocus()
        if obj.treeInterceptor and obj.treeInterceptor.isReady and hasattr(
                obj.treeInterceptor, "event_treeInterceptor_gainFocus"):
            obj.treeInterceptor.event_treeInterceptor_gainFocus()
    return True
Example #5
0
 def _getMostRecentlyCancelledUtterance(self) -> Optional[_IndexT]:
     # Index of the most recently cancelled utterance.
     latestCancelledUtteranceIndex: Optional[_IndexT] = None
     log._speechManagerDebug(
         f"Length of _cancelCommandsForUtteranceBeingSpokenBySynth: "
         f"{len(self._cancelCommandsForUtteranceBeingSpokenBySynth)} "
         f"Length of _indexesSpeaking: "
         f"{len(self._indexesSpeaking)} ")
     cancelledIndexes = (
         index for command, index in
         self._cancelCommandsForUtteranceBeingSpokenBySynth.items()
         if command.isCancelled)
     for index in cancelledIndexes:
         if (latestCancelledUtteranceIndex is None
                 or self._isIndexABeforeIndexB(
                     latestCancelledUtteranceIndex, index)):
             latestCancelledUtteranceIndex = index
     return latestCancelledUtteranceIndex
Example #6
0
 def _handleIndex(self, index: int):
     log._speechManagerDebug(f"Handle index: {index}")
     # A synth (such as OneCore) may skip indexes
     # If before another index, with no text content in between.
     # Therefore, detect this and ensure we handle all skipped indexes.
     handleIndexes = []
     for oldIndex in list(self._indexesSpeaking):
         if oldIndex < index:
             log.debugWarning("Handling skipped index %s" % oldIndex)
             handleIndexes.append(oldIndex)
     handleIndexes.append(index)
     valid, endOfUtterance = False, False
     for i in handleIndexes:
         try:
             self._indexesSpeaking.remove(i)
         except ValueError:
             log.debug(
                 "Unknown index %s, speech probably cancelled from main thread."
                 % i)
             break  # try the rest, this is a very unexpected path.
         if i != index:
             log.debugWarning("Handling skipped index %s" % i)
         # we must do the following for each index, any/all of them may be end of utterance, which must
         # trigger _pushNextSpeech
         _valid, _endOfUtterance = self._removeCompletedFromQueue(i)
         valid = valid or _valid
         endOfUtterance = endOfUtterance or _endOfUtterance
         if _valid:
             callbackCommand = self._indexesToCallbacks.pop(i, None)
             if callbackCommand:
                 try:
                     callbackCommand.run()
                 except Exception:
                     log.exception("Error running speech callback")
     if endOfUtterance:
         # Even if we have many indexes, we should only push next speech once.
         self._pushNextSpeech(False)
Example #7
0
 def speak(self, speechSequence: SpeechSequence, priority: Spri):
     log._speechManagerDebug(
         "Speak called: %r",
         speechSequence)  # expensive string to build - defer
     interrupt = self._queueSpeechSequence(speechSequence, priority)
     self.removeCancelledSpeechCommands()
     # If speech isn't already in progress, we need to push the first speech.
     push = self._curPriQueue is None or 1 > len(self._indexesSpeaking)
     if interrupt:
         log._speechManagerDebug("Interrupting speech")
         getSynth().cancel()
         self._indexesSpeaking.clear()
         self._cancelCommandsForUtteranceBeingSpokenBySynth.clear()
         push = True
     if push:
         log._speechManagerDebug("Pushing next speech")
         self._pushNextSpeech(True)
     else:
         log._speechManagerDebug("Not pushing speech")
Example #8
0
 def removeCancelledSpeechCommands(self):
     if not _shouldCancelExpiredFocusEvents():
         return
     latestCanceledUtteranceIndex = None
     log._speechManagerDebug(
         f"Length of _cancelCommandsForUtteranceBeingSpokenBySynth: "
         f"{len(self._cancelCommandsForUtteranceBeingSpokenBySynth)} "
         f"Length of _indexesSpeaking: "
         f"{len(self._indexesSpeaking)} ")
     for command, index in self._cancelCommandsForUtteranceBeingSpokenBySynth.items(
     ):
         if command.isCancelled:
             # we must not risk deleting commands while iterating over _cancelCommandsForUtteranceBeingSpokenBySynth
             if not latestCanceledUtteranceIndex or latestCanceledUtteranceIndex < index:
                 latestCanceledUtteranceIndex = index
     log._speechManagerDebug(f"Last index: {latestCanceledUtteranceIndex}")
     if latestCanceledUtteranceIndex is not None:
         log._speechManagerDebug(f"Cancel and push speech")
         self._removeCompletedFromQueue(latestCanceledUtteranceIndex)
         getSynth().cancel()
         self._cancelCommandsForUtteranceBeingSpokenBySynth.clear()
         self._indexesSpeaking.clear()
         self._pushNextSpeech(True)
Example #9
0
    def _queueSpeechSequence(self, inSeq: SpeechSequence,
                             priority: Spri) -> bool:
        """
		@return: Whether to interrupt speech.
		"""
        outSeq = self._processSpeechSequence(inSeq)
        log._speechManagerDebug("Out Seq: %r",
                                outSeq)  # expensive string to build - defer
        queue = self._priQueues.get(priority)
        log._speechManagerDebug(
            f"Current priority: {priority},"
            f" queLen: {0 if queue is None else len(queue.pendingSequences)}")
        if not queue:
            queue = self._priQueues[priority] = _ManagerPriorityQueue(priority)
        else:
            log._speechManagerDebug(
                "current queue: %r",  # expensive string to build - defer
                queue.pendingSequences)
        first = len(queue.pendingSequences) == 0
        queue.pendingSequences.extend(outSeq)
        if priority is Spri.NOW and first:
            # If this is the first sequence at Spri.NOW, interrupt speech.
            return True
        return False
Example #10
0
    def _removeCompletedFromQueue(
            self, index: int) -> Tuple[bool, bool]:  # noqa: C901
        """Removes completed speech sequences from the queue.
		@param index: The index just reached indicating a completed sequence.
		@return: Tuple of (valid, endOfUtterance),
			where valid indicates whether the index was valid and
			endOfUtterance indicates whether this sequence was the end of the current utterance.
		@rtype: (bool, bool)
		"""
        # Find the sequence that just completed speaking.
        if not self._curPriQueue:
            # No speech in progress. Probably from a previous utterance which was cancelled.
            return False, False
        for seqIndex, seq in enumerate(self._curPriQueue.pendingSequences):
            lastCommand = seq[-1] if isinstance(seq, list) else None
            if isinstance(lastCommand, IndexCommand):
                if self._isIndexAAfterIndexB(index, lastCommand.index):
                    log.debugWarning(
                        f"Reached speech index {index :d}, but index {lastCommand.index :d} never handled"
                    )
                elif index == lastCommand.index:
                    endOfUtterance = isinstance(
                        self._curPriQueue.pendingSequences[seqIndex + 1][0],
                        EndUtteranceCommand)
                    if endOfUtterance:
                        # Remove the EndUtteranceCommand as well.
                        seqIndex += 1
                    break  # Found it!
        else:
            log._speechManagerDebug(
                "Unknown index. Probably from a previous utterance which was cancelled."
            )
            return False, False
        if endOfUtterance:
            # These params may not apply to the next utterance if it was queued separately,
            # so reset the tracker.
            # The next utterance will include the commands again if they do still apply.
            self._curPriQueue.paramTracker = ParamChangeTracker()
        else:
            # Keep track of parameters changed so far.
            # This is necessary in case this utterance is preempted by higher priority speech.
            for seqIndex in range(seqIndex + 1):
                seq = self._curPriQueue.pendingSequences[seqIndex]
                for command in seq:
                    if isinstance(command, SynthParamCommand):
                        self._curPriQueue.paramTracker.update(command)
        # This sequence is done, so we don't need to track it any more.
        toRemove = self._curPriQueue.pendingSequences[:seqIndex + 1]
        log._speechManagerDebug("Removing: %r", seq)
        if _shouldCancelExpiredFocusEvents():
            cancellables = (item for seq in toRemove for item in seq
                            if isinstance(item, _CancellableSpeechCommand))
            for item in cancellables:
                if log.isEnabledFor(
                        log.DEBUG) and _shouldDoSpeechManagerLogging():
                    # Debug logging for cancelling expired focus events.
                    log._speechManagerDebug(
                        f"Item is in _cancelCommandsForUtteranceBeingSpokenBySynth: "
                        f"{item in self._cancelCommandsForUtteranceBeingSpokenBySynth.keys()}"
                    )
                self._cancelCommandsForUtteranceBeingSpokenBySynth.pop(
                    item, None)
        del self._curPriQueue.pendingSequences[:seqIndex + 1]

        return True, endOfUtterance