diff --git a/source/inputCore.py b/source/inputCore.py index 8c43bec16ad..7b5ee55c787 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -30,6 +30,7 @@ import languageHandler import controlTypes import keyLabels +import extensionPoints #: Script category for emulated keyboard keys. # Translators: The name of a category of NVDA commands. @@ -208,7 +209,7 @@ def clear(self): def add(self, gesture, module, className, script,replace=False): """Add a gesture mapping. @param gesture: The gesture identifier. - @type gesture: str + @type gesture: L{InputGesture} @param module: The name of the Python module containing the target script. @type module: str @param className: The name of the class in L{module} containing the target script. @@ -403,6 +404,15 @@ def __init__(self): self.loadLocaleGestureMap() self.loadUserGestureMap() + #: Notifies when a gesture is about to be executed, + #: and allows components or add-ons to decide whether or not to execute a gesture. + #: For example, when controlling a remote system with a connected local braille display, + #: braille display gestures should not be executed locally. + #: Handlers are called with one argument: + #: @param gesture: The gesture that is about to be executed. + #: @type gesture: L{InputGesture} + self.decide_ExecuteGesture = extensionPoints.Decider() + def executeGesture(self, gesture): """Perform the action associated with a gesture. @param gesture: The gesture to execute. @@ -415,6 +425,12 @@ def executeGesture(self, gesture): # as well as stopping a flood of actions when the core revives. raise NoInputGestureAction + if not self.decide_executeGesture.decide(gesture=gesture): + # A registered handler decided that this gesture shouldn't be executed. + # Purposely do not raise a NoInputGestureAction here, as that could lead to unexpected behavior for gesture emulation. + log.debug("Gesture execution canceled by handler registered to decide_executeGesture extension point") + return + script = gesture.script focus = api.getFocusObject() if focus.sleepMode is focus.SLEEP_FULL or (focus.sleepMode and not getattr(script, 'allowInSleepMode', False)): diff --git a/source/nvwave.py b/source/nvwave.py index ca2b524ecf7..48ad193b364 100644 --- a/source/nvwave.py +++ b/source/nvwave.py @@ -1,6 +1,6 @@ #nvwave.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2007-2017 NV Access Limited, Aleksey Sadovoy +#Copyright (C) 2007-2017 NV Access Limited, Aleksey Sadovoy, Babbage B.V. #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -17,6 +17,7 @@ import wave import config from logHandler import log +import extensionPoints __all__ = ( "WavePlayer", "getOutputDeviceNames", "outputDeviceIDToName", "outputDeviceNameToID", @@ -27,6 +28,14 @@ HWAVEOUT = HANDLE LPHWAVEOUT = POINTER(HWAVEOUT) +#: Notifies when a wave file is about to be played, +#: and allows components or add-ons to decide whether the wave file should be played. +#: For example, when controlling a remote system, +#: the remote system must be notified of sounds played on the local system. +#: Also, registrars should be able to suppress playing sounds if desired. +#: Handlers are called with the same arguments as L{playWaveFile} as keyword arguments. +decide_playWaveFile = extensionPoints.Decider() + class WAVEFORMATEX(Structure): _fields_ = [ ("wFormatTag", WORD), @@ -329,16 +338,27 @@ def outputDeviceNameToID(name, useDefaultIfInvalid=False): fileWavePlayer = None fileWavePlayerThread=None -def playWaveFile(fileName, async=True): +def playWaveFile(fileName, async=True, partOfSpeechSequence=False): """plays a specified wave file. -""" + @param fileName: the path to the wave file, usually absolute. + @type fileName: basestring + @param async: whether the file should be played asynchronously. + If C{False}, the calling thread is blocked until the wave has finished playing. + @type async: bool + @param partOfSpeechSequence: whether this beep is created as part of a speech sequence. + @type partOfSpeechSequence: bool + """ global fileWavePlayer, fileWavePlayerThread f = wave.open(fileName,"r") if f is None: raise RuntimeError("can not open file %s"%fileName) if fileWavePlayer is not None: fileWavePlayer.stop() + if not decide_playWaveFile.decide(fileName=fileName, async=async, partOfSpeechSequence=partOfSpeechSequence): + log.debug("Playing wave file canceled by handler registered to decide_playWaveFile extension point") + return fileWavePlayer = WavePlayer(channels=f.getnchannels(), samplesPerSec=f.getframerate(),bitsPerSample=f.getsampwidth()*8, outputDevice=config.conf["speech"]["outputDevice"],wantDucking=False) fileWavePlayer.feed(f.readframes(f.getnframes())) + if async: if fileWavePlayerThread is not None: fileWavePlayerThread.join() diff --git a/source/tones.py b/source/tones.py index 839908a6971..51381cb2198 100644 --- a/source/tones.py +++ b/source/tones.py @@ -1,6 +1,6 @@ #tones.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2007-2017 NV Access Limited, Aleksey Sadovoy +#Copyright (C) 2007-2017 NV Access Limited, Aleksey Sadovoy, Babbage B.V. #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -12,13 +12,14 @@ import globalVars from logHandler import log from ctypes import create_string_buffer, byref +import extensionPoints SAMPLE_RATE = 44100 try: player = nvwave.WavePlayer(channels=2, samplesPerSec=int(SAMPLE_RATE), bitsPerSample=16, outputDevice=config.conf["speech"]["outputDevice"],wantDucking=False) except: - log.warning("Failed to initialize audio for tones") + log.warning("Failed to initialize audio for tones", exc_info=True) player = None # When exiting, ensure player is deleted before modules get cleaned up. @@ -28,18 +29,31 @@ def _cleanup(): global player player = None -def beep(hz,length,left=50,right=50): +#: Notifies when a beep is about to be generated and played, +#: and allows components or add-ons to decide whether the beep should actually be played. +#: For example, when controlling a remote system, +#: the remote system must be notified of beeps played on the local system. +#: Also, registrars should be able to suppress playing beeps if desired. +#: Handlers are called with the same arguments as L{beep} as keyword arguments. +decide_beep = extensionPoints.Decider() + +def beep(hz,length,left=50,right=50,partOfSpeechSequence=False): """Plays a tone at the given hz, length, and stereo balance. - @param hz: pitch in hz of the tone + @param hz: pitch in hz of the tone. @type hz: float - @param length: length of the tone in ms + @param length: length of the tone in ms. @type length: integer - @param left: volume of the left channel (0 to 100) + @param left: volume of the left channel (0 to 100). @type left: integer - @param right: volume of the right channel (0 to 100) + @param right: volume of the right channel (0 to 100). @type right: integer + @param partOfSpeechSequence: whether this beep is created as part of a speech sequence. + @type partOfSpeechSequence: bool """ log.io("Beep at pitch %s, for %s ms, left volume %s, right volume %s"%(hz,length,left,right)) + if not decide_beep.decide(hz=hz, length=length, left=left, right=right, partOfSpeechSequence=partOfSpeechSequence): + log.debug("Beep canceled by handler registered to decide_beep extension point") + return if not player: return from NVDAHelper import generateBeep