def script_switchTo(self, gesture: InputGesture) -> None: """Switch NVDA audio output to the selected sound device. @param gesture: gesture assigned to this method @type gesture: InputGesture """ index = int(gesture.displayName.lower()[-2:].replace('f',''))-1 self.setOutputDevice(name=getOutputDeviceNames()[index])
def getDeviceNames(): deviceNames = nvwave.getOutputDeviceNames() # #11349: On Windows 10 20H1 and 20H2, Microsoft Sound Mapper returns an empty string. if deviceNames[0] in ("", "Microsoft Sound Mapper"): # Translators: name for default (Microsoft Sound Mapper) audio output device. deviceNames[0] = NVDAString("Microsoft Sound Mapper") return deviceNames
def bindSwitchingMethods(self) -> None: """Bind all created switching methods to the current global plugin class instance, should be called in the class constructor. """ outputDevices = getOutputDeviceNames() for i in range(len(outputDevices)): name = f"script_switchToDevice{i}" setattr(self.__class__, name, self.switchingMethodsFactory(i, outputDevices[i])) config.conf[addonName]['gestures'] and i<12 and self.bindGesture("kb:NVDA+windows+f%d" % (i+1), name.split('_', 1)[1])
def selectOutputDevice(self, step: int) -> str: """Select an audio device from the list with an offset to the specified step. @param step: offset step in the list of available audio devices @type step: int (usually 1 or -1) @return: the name of the selected audio device @rtype: str """ devices: List[str] = getOutputDeviceNames() if devices[0] in ("", "Microsoft Sound Mapper"): # Translators: name for default (Microsoft Sound Mapper) audio output device. devices[0] = _("Microsoft Sound Mapper") try: current: int = devices.index(config.conf["speech"]["outputDevice"]) except ValueError: current = 0 return devices[(current+step)%len(devices)]
def getDefaultDeviceName(self) -> str: """Obtain the default output audio device name. @return: default output audio device name @rtype: str """ try: devices: List[AudioDevice] = AudioUtilities.GetAllDevices() except Exception: devices = [] defaultDevice = next(filter(lambda dev: dev.id == self._device.GetId(), devices), None) defaultDeviceName = defaultDevice.FriendlyName if defaultDevice else '[undefined device]' outputDevices = nvwave.getOutputDeviceNames() if outputDevices[0] in ("", "Microsoft Sound Mapper"): outputDevices[0] = "Microsoft Sound Mapper" return next( filter(lambda name: name in defaultDeviceName or defaultDeviceName in name, outputDevices), outputDevices[0])
class GlobalPlugin(globalPluginHandler.GlobalPlugin): """Implementation global commands of NVDA Volume Adjustment add-on.""" scriptCategory: str = addonSummary def __init__(self, *args, **kwargs) -> None: """Initializing initial configuration values and other fields.""" super(GlobalPlugin, self).__init__(*args, **kwargs) if appArgs.secure or config.isAppX: return confspec = { "step": "integer(default=1,min=1,max=20)", "focus": "boolean(default=true)", "duplicates": "boolean(default=true)", "advanced": "boolean(default=false)", "muteCompletely": "boolean(default=false)", "mutePercentage": "integer(default=75,min=1,max=99)", "unmuteOnExit": "boolean(default=true)", "gestures": "boolean(default=true)" } config.conf.spec[addonName] = confspec gui.settingsDialogs.NVDASettingsDialog.categoryClasses.append( VASettingsPanel) # Switching between processes self._index: int = 0 # index of current audio source # Remember the name of the audio session of the previous process self._previous: str = '' # Name of the current process self._process: str = '' # Bind default gestures if necessary config.conf[addonName]['gestures'] and self.bindGestures( self.__defaultGestures) devices.scan(cfg.devices) def terminate(self, *args, **kwargs) -> None: """This will be called when NVDA is finished with this global plugin.""" if config.conf[addonName]["unmuteOnExit"]: Thread(target=self.unmuteAllAudioSources).start() try: gui.settingsDialogs.NVDASettingsDialog.categoryClasses.remove( VASettingsPanel) except IndexError: log.warning( "Can't remove %s Settings panel from NVDA settings dialogs", addonSummary) super(GlobalPlugin, self).terminate(*args, **kwargs) def event_gainFocus(self, obj: NVDAObject, NextHandler: Callable) -> None: """Track the application in focus if the corresponding option is enabled. @param obj: the object to track if focused @type obj: NVDAObject @param nextHandler: next event handler @type nextHandler: Callable """ if config.conf[addonName]['focus']: self._index = -1 self._previous = UNDEFINED_APP NextHandler() def selectProcessInFocus(self) -> bool: """Select the name of the process that is in system focus. @return: whether the current process is in the list of audio sessions @rtype: bool """ obj = getFocusObject() try: appName = obj.appModule.appName except AttributeError: appName = UNDEFINED_APP session = AudioSession(appName) if not session.name: # Translators: The current application does not pay audio ui.message( _("{app} is not playing any sound.").format(app=appName)) return False self._process = session.name return True def announceVolumeLevel(self, volumeLevel: float) -> None: """Announce the current volume level. @param volumeLevel: value of volume level @type volumeLevel: float, from 0.0 to 1.0 """ # Translators: The message is announced during volume control ui.message("%s %d" % (_("Volume"), int(volumeLevel * 100))) def announceMuted(self) -> None: """Announce that the sound was muted.""" # Translators: The message is announced during volume control ui.message(_("The sound is muted")) def getAllSessions(self) -> List[str]: """List of all running processes that available in the list of audio sessions excluding hidden sessions and duplicate items if the corresponding option is enabled. @return: list of currently running processes @rtype: List[str] """ procs = [ s.Process.name() for s in ExtendedAudioUtilities.GetAllSessions() if s.Process and s.Process.name() and s.Process.name() not in cfg.processes ] return list( set(procs)) if config.conf[addonName]['duplicates'] else procs def unmuteAllAudioSources(self) -> None: """Unmute all muted audio devices and audio sessions.""" for device in devices: device.isMuted and device.unmute() for sessionName in self.getAllSessions(): session = AudioSession(sessionName) session.isMuted and session.unmute() def selectAudioSource(self, sessions: List[str]) -> None: """Select audio source to adjust its volume level. This can be a physical audio device or a running process. @param sessions: filtered list of all running processes, changes dynamically @type sessions: List[str] """ if 0 <= self._index < len(devices): title: str = devices[self._index].name if devices[self._index].default: # Translators: Used as the prefix to default audio device name title = "{default}: {title}".format( default=_("Default audio device"), title=title) else: try: self._process = sessions[self._index - len(devices)] except IndexError: try: self._process = sessions[-1] except IndexError: self._process = UNDEFINED_APP self._previous = self._process title = AudioSession(self._process).title ui.message(title) def getAudioSource(self) -> Union[AudioDevice, AudioSession]: """Get the object of selected audio source (device or process), this can be instance inherited from the audiocore.AudioSource class. Also announces the name of the audio session at the first treatment. @return: instance of selected audio source @rtype: Union[AudioDevice, AudioSession] """ if 0 <= self._index < len(devices): source: Union[AudioDevice, AudioSession] = devices[self._index] self._previous = UNDEFINED_APP else: source = AudioSession(self._process) if source.name != self._previous: ui.message(source.title) self._previous = source.name return source # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Increase the volume")) def script_volumeUp(self, gesture: InputGesture) -> None: """Increase the volume of the selected audio source. @param gesture: the input gesture in question @type gesture: InputGesture """ if self._index < 0 and not self.selectProcessInFocus(): return self.announceVolumeLevel(self.getAudioSource().volumeUp()) # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Decrease the volume")) def script_volumeDown(self, gesture: InputGesture) -> None: """Decrease the volume of the selected audio source. @param gesture: the input gesture in question @type gesture: InputGesture """ if self._index < 0 and not self.selectProcessInFocus(): return source = self.getAudioSource() volumeLevel = source.volumeDown() if volumeLevel > 0.0: self.announceVolumeLevel(volumeLevel) return source.mute() self.announceMuted() # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Set maximum volume level")) def script_volumeMax(self, gesture: InputGesture) -> None: """Set the maximum volume level for the selected audio source. @param gesture: the input gesture in question @type gesture: InputGesture """ if self._index < 0 and not self.selectProcessInFocus(): return self.announceVolumeLevel(self.getAudioSource().volumeMax()) # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Set minimum volume level")) def script_volumeMin(self, gesture: InputGesture) -> None: """Set the minimum volume level for the selected audio source. @param gesture: the input gesture in question @type gesture: InputGesture """ if self._index < 0 and not self.selectProcessInFocus(): return self.announceVolumeLevel(self.getAudioSource().volumeMin()) # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Mute selected audio source")) def script_mute(self, gesture: InputGesture) -> None: """Mute or unmute the selected audio source. @param gesture: gesture assigned to this method @type gesture: InputGesture """ if self._index < 0 and not self.selectProcessInFocus(): return source = self.getAudioSource() if source.isMuted: source.unmute() self.announceVolumeLevel(source.volumeLevel) else: source.mute() self.announceMuted() # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Switch to the next audio source")) def script_next(self, gesture: InputGesture) -> None: """Switch to the next audio source (audio device or process). @param gesture: the input gesture in question @type gesture: InputGesture """ sessions: List[str] = self.getAllSessions() if self._index < 0: try: self._index = len(devices) + sessions.index( next(filter(lambda s: self._process in s, sessions), '')) except (ValueError, TypeError): self._index = len(devices) - 1 self._index = self._index + 1 if self._index < ( len(devices) + len(sessions) - 1) else 0 self.selectAudioSource(sessions) # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Switch to the previous audio source")) def script_prev(self, gesture: InputGesture) -> None: """Switch to the previous audio source (audio device or process). @param gesture: the input gesture in question @type gesture: InputGesture """ sessions: List[str] = self.getAllSessions() if self._index < 0: try: self._index = len(devices) + sessions.index( next(filter(lambda s: self._process in s, sessions), '')) except (ValueError, TypeError): pass self._index = self._index - 1 if self._index > 0 else len( devices) + len(sessions) - 1 self.selectAudioSource(sessions) def setOutputDevice(self, name: str) -> None: """Switche the NVDA output to the audio device with the specified name. @param name: name of the audio output device @type name: str """ config.conf['speech']['outputDevice'] = name status: bool = setSynth(getSynth().name) if status: tones.terminate() tones.initialize() ui.message(name) def selectOutputDevice(self, step: int) -> str: """Select an audio device from the list with an offset to the specified step. @param step: offset step in the list of available audio devices @type step: int (usually 1 or -1) @return: the name of the selected audio device @rtype: str """ devices: List[str] = getOutputDeviceNames() if devices[0] in ("", "Microsoft Sound Mapper"): # Translators: name for default (Microsoft Sound Mapper) audio output device. devices[0] = _("Microsoft Sound Mapper") try: current: int = devices.index(config.conf["speech"]["outputDevice"]) except ValueError: current = 0 return devices[(current + step) % len(devices)] # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Next audio output device")) def script_nextOutputDevice(self, gesture: InputGesture) -> None: """Switch the output to the next available audio device. @param gesture: the input gesture in question @type gesture: InputGesture """ device: str = self.selectOutputDevice(step=1) self.setOutputDevice(name=device) # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Previous audio output device")) def script_prevOutputDevice(self, gesture: InputGesture) -> None: """Switch the output to the previous available audio device. @param gesture: the input gesture in question @type gesture: InputGesture """ device: str = self.selectOutputDevice(step=-1) self.setOutputDevice(name=device) # Translators: The name of the method that displayed in the NVDA input gestures dialog @script(description=_("Switch the output to the selected audio device")) def script_switchTo(self, gesture: InputGesture) -> None: """Switch NVDA audio output to the selected sound device. @param gesture: gesture assigned to this method @type gesture: InputGesture """ index = int(gesture.displayName.lower()[-2:].replace('f', '')) - 1 self.setOutputDevice(name=getOutputDeviceNames()[index]) __defaultGestures = { "kb:NVDA+windows+upArrow": "volumeUp", "kb:NVDA+windows+downArrow": "volumeDown", "kb:NVDA+windows+home": "volumeMax", "kb:NVDA+windows+end": "volumeMin", "kb:NVDA+windows+escape": "mute", "kb:NVDA+windows+rightArrow": "next", "kb:NVDA+windows+leftArrow": "prev", "kb:NVDA+windows+pageUp": "nextOutputDevice", "kb:NVDA+windows+pageDown": "prevOutputDevice" } for key in range(1, min(len(getOutputDeviceNames()), 12) + 1): __defaultGestures["kb:NVDA+windows+f%d" % key] = "switchTo"