diff --git a/addon/globalPlugins/soundmanager/__init__.py b/addon/globalPlugins/soundmanager/__init__.py index 2bd5f19..e65f656 100644 --- a/addon/globalPlugins/soundmanager/__init__.py +++ b/addon/globalPlugins/soundmanager/__init__.py @@ -1,118 +1,197 @@ # *-* coding: utf-8 *-* #addon/globalPlugins/sound-manager/__init__.py -#A part of the NVDA Translate add-on -#Copyright (C) 2018 Yannick PLASSIARD +#A part of the NVDA Sound Manager add-on +#Copyright (C) 2019 Yannick PLASSIARD #This file is covered by the GNU General Public License. #See the file LICENSE for more details. # #This addon uses the following dependencies: # pycaw - see the pycaw.LICENSE file for more details. +# System modules +import sys, os + +# NVDA core requirements import globalPluginHandler +import addonHandler +import api import tones import ui -import sys, os +# Local requirements (Pycaw and its dependencies) + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from . import pycaw from pycaw.pycaw import AudioUtilities del sys.path[-1] +addonHandler.initTranslation() class GlobalPlugin(globalPluginHandler.GlobalPlugin): - scriptCategory = _("Sound Manager") - volumeChangeStep = 0.05 - enabled = False - curAppName = None - - - def getAppNameFromSession(self, session): - name = None - try: - name = session.getDisplayName() - except Exception as e: - name = session.Process.name().replace(".exe", "") - return name - - - def script_muteApp(self, gesture): - session,volume = self.findSessionByName(self.curAppName) - if session == None and self.curAppName is not None: - ui.message(_("Unable to retrieve current application.")) - return - muted = volume.GetMute() - volume.SetMute(not muted, None) - if muted: - ui.message(_("{app} muted").format(app=self.getAppNameFromSession(session))) - else: - ui.message(_("{app} unmuted").format(app=self.getAppNameFromSession(session))) - - def script_volumeUp(self, gesture): - self.changeVolume(self.volumeChangeStep) - def script_volumeDown(self, gesture): - self.changeVolume(-self.volumeChangeStep) - def changeVolume(self, volumeStep): - session,volume = self.findSessionByName(self.curAppName) - if session == None and self.curAppName is not None: - ui.message(_("Unable to retrieve current application.")) - return - newVolume = volume.GetMasterVolume() + volumeStep - if volumeStep > 0 and newVolume > 1: - newVolume = 1.0 - elif volumeStep < 0 and newVolume < 0: - newVolume = 0.0 - - volume.SetMasterVolume(newVolume, None) - ui.message(_("{volume}%".format(volume=int(round(newVolume * 100))))) - - def cycleThroughApps(self, goForward): - audioSessions = AudioUtilities.GetAllSessions() - sessions = [] - for session in audioSessions: - if session.Process is not None: - sessions.append(session) - newSession = None - idx = 0 - nrSessions = len(sessions) - while idx < nrSessions: - session = sessions[idx] - if self.curAppName == session.Process.name(): - if goForward: - newSession = sessions[idx + 1] if idx + 1 < nrSessions else sessions[0] + #. Translators: The name of the add-on presented to the user. + scriptCategory = _("Sound Manager") + volumeChangeStep = 0.05 + enabled = False + curAppName = None + + + + def getAppNameFromSession(self, session): + """Returns an application's name formatted to be presented to the user from a given audio session.""" + + name = None + try: + name = session.getDisplayName() + except Exception as e: + name = session.Process.name().replace(".exe", "") + return name + + + def script_muteApp(self, gesture): + """Mutes or unmute the focused application.""" + session,volume = self.findSessionByName(self.curAppName) + if session == None and self.curAppName is not None: + #. Translators: Spoken message when unablee to change audio volume for the given application. + ui.message(_("Unable to retrieve current application.")) + return + muted = volume.GetMute() + volume.SetMute(not muted, None) + if not muted: + #. Translator: Spoken message indicating that the app's sound is now muted. + ui.message(_("{app} muted").format(app=self.getAppNameFromSession(session))) else: - newSession = sessions[idx - 1] - idx += 1 - if newSession is None: - newSession = sessions[0] - self.curAppName = newSession.Process.name() - ui.message(self.getAppNameFromSession(newSession)) - def script_nextApp(self, gesture): - self.cycleThroughApps(True) - def script_previousApp(self, gesture): - self.cycleThroughApps(False) - def script_soundManager(self, gesture): - self.enabled = not self.enabled - if self.enabled is True: - tones.beep(660, 100) - self.bindGesture("kb:uparrow", "volumeUp") - self.bindGesture("kb:downarrow", "volumeDown") - self.bindGesture("kb:leftarrow", "previousApp") - self.bindGesture("kb:rightarrow", "nextApp") - self.bindGesture("kb:m", "muteApp") - else: - tones.beep(440, 100) - self.clearGestureBindings() - self.bindGestures(self.__gestures) - - script_soundManager.__doc__ = _("""Toggle volume control adjustment on or off""") - - def findSessionByName(self, name): - sessions = AudioUtilities.GetAllSessions() - for session in sessions: - if session.Process and session.Process.name() == name or name is None: - volume = session.SimpleAudioVolume - return session,volume - return None,None - - __gestures = { - "kb:nvda+shift+v": "soundManager", - } + #. Translators: Spoken message indicating that the app's audio is now unmuted. + ui.message(_("{app} unmuted").format(app=self.getAppNameFromSession(session))) + + def focusCurrentApplication(self): + """Selects the audio control for the current alsplication.""" + obj = api.getFocusObject() + appName = None + try: + appName = obj.appModule.appName + except AttributeError: + appName = None + if appName is None: + #. Translators: Unable to determine focused application's name. + ui.message_("Unable to retrieve current application's name.") + return False + session,volume = self.findSessionByName(appName) + if session is None: + #. Translators: The current application does not pay audio. + ui.message(_("{app} is not playing any sound.".format(app=appName))) + return False + self.curAppName = appName + return True + + def script_curAppVolumeUp(self, gesture): + """Increases the volume of focused application if it plays audio.""" + if self.focusCurrentApplication() is False: + return + self.changeVolume(self.volumeChangeStep) + + def script_curAppVolumeDown(self, gesture): + """Decreases the currently focused application's volume.""" + if self.focusCurrentApplication() is False: + return + self.changeVolume(-self.volumeChangeStep) + + def script_curAppMute(self, gesture): + if self.focusCurrentApplication() is False: + return + self.script_muteApp(gesture) + + def script_volumeUp(self, gesture): + """Increases the volume of the selected application.""" + self.changeVolume(self.volumeChangeStep) + def script_volumeDown(self, gesture): + """Decreases the volume of the selected application.""" + self.changeVolume(-self.volumeChangeStep) + + def changeVolume(self, volumeStep): + """Adjusts the volume of the selected application using the given step value.""" + session,volume = self.findSessionByName(self.curAppName) + if session == None and self.curAppName is not None: + #. Translators: Spoken message when unablee to change audio volume for the given application + ui.message(_("Unable to retrieve current application.")) + return + newVolume = volume.GetMasterVolume() + volumeStep + if volumeStep > 0 and newVolume > 1: + newVolume = 1.0 + elif volumeStep < 0 and newVolume < 0: + newVolume = 0.0 + + volume.SetMasterVolume(newVolume, None) + #. Translators: Message indicating the volume's percentage ("95%"). + ui.message(_("{volume}%".format(volume=int(round(newVolume * 100))))) + + def cycleThroughApps(self, goForward): + """Cycles through apps that are currently playing audio. + The goForward parameter indicates whether we want to cycle forward or backward in the ring. + """ + audioSessions = AudioUtilities.GetAllSessions() + sessions = [] + for session in audioSessions: + if session.Process is not None: + sessions.append(session) + newSession = None + idx = 0 + nrSessions = len(sessions) + while idx < nrSessions: + session = sessions[idx] + if self.curAppName == session.Process.name(): + if goForward: + newSession = sessions[idx + 1] if idx + 1 < nrSessions else sessions[0] + else: + newSession = sessions[idx - 1] + idx += 1 + if newSession is None: + newSession = sessions[0] + self.curAppName = newSession.Process.name() + ui.message(self.getAppNameFromSession(newSession)) + + def script_nextApp(self, gesture): + """Focus the next application that is playing audio.""" + self.cycleThroughApps(True) + + def script_previousApp(self, gesture): + """Focus the previous application that is playing audio.""" + self.cycleThroughApps(False) + + def script_soundManager(self, gesture): + """Activates or deactivates the sound manager mode. + When activated, this scripts dynamically binds other gestures to perform sound manager + commands (adjusting the volume for instance). When deactivating, gestures are restored to + their default behavior. + """ + self.enabled = not self.enabled + if self.enabled is True: + tones.beep(660, 100) + self.bindGesture("kb:control+uparrow", "curAppVolumeUp") + self.bindGesture("kb:control+downarrow", "curAppVolumeDown") + self.bindGesture("kb:control+m", "curAppMute") + self.bindGesture("kb:uparrow", "volumeUp") + self.bindGesture("kb:downarrow", "volumeDown") + self.bindGesture("kb:leftarrow", "previousApp") + self.bindGesture("kb:rightarrow", "nextApp") + self.bindGesture("kb:m", "muteApp") + else: + tones.beep(440, 100) + self.clearGestureBindings() + self.bindGestures(self.__gestures) + + #. Translators: Main script help message. + script_soundManager.__doc__ = _("""Toggle volume control adjustment on or off""") + + def findSessionByName(self, name): + """Finds an audio session according to the process's name ("chrome.exe").""" + sessions = AudioUtilities.GetAllSessions() + for session in sessions: + if session.Process is not None: + pName = session.Process.name() + if name is None or name.lower() in pName.lower(): + volume = session.SimpleAudioVolume + return session,volume + return None,None + + __gestures = { + "kb:nvda+shift+v": "soundManager", + } diff --git a/addon/globalPlugins/soundmanager/enum.py b/addon/globalPlugins/soundmanager/enum.py deleted file mode 100644 index 94020fb..0000000 --- a/addon/globalPlugins/soundmanager/enum.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- - -# enum.py -# Part of ‘enum’, a package providing enumerated types for Python. -# -# Copyright © 2007–2018 Ben Finney -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; version 3 of that license or any later version. -# No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details. - -""" Robust enumerated type support in Python. - -This package provides a module for robust enumerations in Python. - -An enumeration object is created with a sequence of string arguments -to the Enum() constructor:: - - >>> from enum import Enum - >>> Colours = Enum('red', 'blue', 'green') - >>> Weekdays = Enum('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun') - -The return value is an immutable sequence object with a value for each -of the string arguments. Each value is also available as an attribute -named from the corresponding string argument:: - - >>> pizza_night = Weekdays[4] - >>> shirt_colour = Colours.green - -The values are constants that can be compared only with values from -the same enumeration; comparison with other values will invoke -Python's fallback comparisons:: - - >>> pizza_night == Weekdays.fri - True - >>> shirt_colour > Colours.red - True - >>> shirt_colour == "green" - False - -Each value from an enumeration exports its sequence index -as an integer, and can be coerced to a simple string matching the -original arguments used to create the enumeration:: - - >>> str(pizza_night) - 'fri' - >>> shirt_colour.index - 2 - -""" - - -__author_name__ = "Ben Finney" -__author_email__ = "ben+python@benfinney.id.au" -__author__ = "%(__author_name__)s <%(__author_email__)s>" % vars() - -_copyright_year_begin = "2007" -__date__ = "2018-08-22" -_copyright_year_latest = __date__.split('-')[0] -_copyright_year_range = _copyright_year_begin -if _copyright_year_latest > _copyright_year_begin: - _copyright_year_range += "–%(_copyright_year_latest)s" % vars() -__copyright__ = ( - "Copyright © %(_copyright_year_range)s" - " %(__author_name__)s") % vars() -__license__ = "GPL-3.0+" - -__url__ = "https://pypi.org/project/enum/" -__version__ = "0.4.7" - - -class EnumException(Exception): - """ Base class for all exceptions in this module. """ - - def __init__(self, *args, **kwargs): - if self.__class__ is EnumException: - class_name = self.__class__.__name__ - raise NotImplementedError( - "%(class_name)s is an abstract base class" % vars()) - super(EnumException, self).__init__(*args, **kwargs) - - -class EnumEmptyError(AssertionError, EnumException): - """ Raised when attempting to create an empty enumeration. """ - - def __str__(self): - return "Enumerations cannot be empty" - - -class EnumBadKeyError(TypeError, EnumException): - """ Raised when creating an Enum with non-string keys. """ - - def __init__(self, key): - self.key = key - - def __str__(self): - return "Enumeration keys must be strings: %(key)r" % vars(self) - - -class EnumImmutableError(TypeError, EnumException): - """ Raised when attempting to modify an Enum. """ - - def __init__(self, *args): - self.args = args - - def __str__(self): - return "Enumeration does not allow modification" - - -def _comparator(func): - """ Decorator for EnumValue rich comparison methods. """ - def comparator_wrapper(self, other): - try: - assert self.enumtype == other.enumtype - result = func(self.index, other.index) - except (AssertionError, AttributeError): - result = NotImplemented - - return result - comparator_wrapper.__name__ = func.__name__ - comparator_wrapper.__doc__ = getattr(float, func.__name__).__doc__ - return comparator_wrapper - -class EnumValue(object): - """ A specific value of an enumerated type. """ - - def __init__(self, enumtype, index, key): - """ Set up a new instance. """ - self._enumtype = enumtype - self._index = index - self._key = key - - @property - def enumtype(self): - return self._enumtype - - @property - def key(self): - return self._key - - def __str__(self): - return str(self.key) - - @property - def index(self): - return self._index - - def __repr__(self): - return "EnumValue(%(_enumtype)r, %(_index)r, %(_key)r)" % vars(self) - - def __hash__(self): - return hash(self._index) - - @_comparator - def __eq__(self, other): - return (self == other) - - @_comparator - def __ne__(self, other): - return (self != other) - - @_comparator - def __lt__(self, other): - return (self < other) - - @_comparator - def __le__(self, other): - return (self <= other) - - @_comparator - def __gt__(self, other): - return (self > other) - - @_comparator - def __ge__(self, other): - return (self >= other) - - -class Enum(object): - """ Enumerated type. """ - - def __init__(self, *keys, **kwargs): - """ Create an enumeration instance. """ - - value_type = kwargs.get('value_type', EnumValue) - - if not keys: - raise EnumEmptyError() - - keys = tuple(keys) - values = [None] * len(keys) - - for i, key in enumerate(keys): - value = value_type(self, i, key) - values[i] = value - try: - super(Enum, self).__setattr__(key, value) - except TypeError: - raise EnumBadKeyError(key) - - self.__dict__['_keys'] = keys - self.__dict__['_values'] = values - - def __setattr__(self, name, value): - raise EnumImmutableError(name) - - def __delattr__(self, name): - raise EnumImmutableError(name) - - def __len__(self): - return len(self._values) - - def __getitem__(self, index): - return self._values[index] - - def __setitem__(self, index, value): - raise EnumImmutableError(index) - - def __delitem__(self, index): - raise EnumImmutableError(index) - - def __iter__(self): - return iter(self._values) - - def __contains__(self, value): - is_member = False - if isinstance(value, basestring): - is_member = (value in self._keys) - else: - is_member = (value in self._values) - return is_member - - -# Local variables: -# mode: python -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-start: "__date__ = \"" -# time-stamp-end: "\"$" -# time-stamp-line-limit: 200 -# End: -# vim: filetype=python fileencoding=utf-8 : diff --git a/addon/locale/fr/LC_MESSAGES/nvda.po b/addon/locale/fr/LC_MESSAGES/nvda.po index 4a2c507..5269949 100644 --- a/addon/locale/fr/LC_MESSAGES/nvda.po +++ b/addon/locale/fr/LC_MESSAGES/nvda.po @@ -1,34 +1,48 @@ -# SoundManager -# Copyright (C) 2019 Yannick Plassiard -# This file is distributed under the same license as the soundmanager package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: soundmanager 2019.05.1\n" -"Report-Msgid-Bugs-To: nvda-translations@freelists.org\n" -"POT-Creation-Date: 2019-05-17 14:33+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Yannick Plassiard \n" -"Language-Team: LANGUAGE \n" -"Language: fr_FR\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" - +#. . Translators: The name of the add-on presented to the user. #. Add-on summary, usually the user visible name of the addon. #. Translators: Summary for this add-on to be shown on installation and add-on information. -#: addon/globalPlugins/soundmanager/__init__.py:14 buildVars.py:17 +#: addon/globalPlugins/soundmanager/__init__.py:30 buildVars.py:17 msgid "Sound Manager" msgstr "Sound Manager" -#: addon/globalPlugins/soundmanager/__init__.py:94 +#. . Translators: Spoken message when unablee to change audio volume for the given application. +#. . Translators: Spoken message when unablee to change audio volume for the given application +#: addon/globalPlugins/soundmanager/__init__.py:53 +#: addon/globalPlugins/soundmanager/__init__.py:113 +msgid "Unable to retrieve current application." +msgstr "Impossible de déterminer l'appliaction en avant-plan." + +#. . Translator: Spoken message indicating that the app's sound is now muted. +#: addon/globalPlugins/soundmanager/__init__.py:59 +#, python-brace-format +msgid "{app} muted" +msgstr "{app} muette" + +#. . Translators: Spoken message indicating that the app's audio is now unmuted. +#: addon/globalPlugins/soundmanager/__init__.py:62 +#, python-brace-format +msgid "{app} unmuted" +msgstr "{app} non muette" + +#. . Translators: The current application does not pay audio. +#: addon/globalPlugins/soundmanager/__init__.py:79 +#, python-brace-format +msgid "{app} is not playing any sound." +msgstr "L'application {app} ne joue aucun son." + +#. . Translators: Message indicating the volume's percentage ("95%"). +#: addon/globalPlugins/soundmanager/__init__.py:123 +#, python-brace-format +msgid "{volume}%" +msgstr "{volume} %" + +#. . Translators: Main script help message. +#: addon/globalPlugins/soundmanager/__init__.py:181 msgid "Toggle volume control adjustment on or off" -msgstr "Activer/désactiver l'ajustement du volume des applications" +msgstr "Active^désactive le contrôle du volume." #. Add-on description #. Translators: Long description to be shown for this add-on on add-on information from add-ons manager #: buildVars.py:20 msgid "Manages Windows and applications volumes directly within NVDA" -msgstr "Ajuster le volume des applications directement depuis NVDA" +msgstr "Gérer le volume des applications directement depuis NVDA." diff --git a/soundmanager.pot b/soundmanager.pot index 3b4d866..2735561 100644 --- a/soundmanager.pot +++ b/soundmanager.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: soundmanager 2019.05.1\n" +"Project-Id-Version: soundmanager 2019.05.4\n" "Report-Msgid-Bugs-To: nvda-translations@freelists.org\n" -"POT-Creation-Date: 2019-05-17 14:33+0200\n" +"POT-Creation-Date: 2019-05-20 15:10+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,13 +17,46 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" +#. . Translators: The name of the add-on presented to the user. #. Add-on summary, usually the user visible name of the addon. #. Translators: Summary for this add-on to be shown on installation and add-on information. -#: addon/globalPlugins/soundmanager/__init__.py:14 buildVars.py:17 +#: addon/globalPlugins/soundmanager/__init__.py:30 buildVars.py:17 msgid "Sound Manager" msgstr "" -#: addon/globalPlugins/soundmanager/__init__.py:94 +#. . Translators: Spoken message when unablee to change audio volume for the given application. +#. . Translators: Spoken message when unablee to change audio volume for the given application +#: addon/globalPlugins/soundmanager/__init__.py:53 +#: addon/globalPlugins/soundmanager/__init__.py:113 +msgid "Unable to retrieve current application." +msgstr "" + +#. . Translator: Spoken message indicating that the app's sound is now muted. +#: addon/globalPlugins/soundmanager/__init__.py:59 +#, python-brace-format +msgid "{app} muted" +msgstr "" + +#. . Translators: Spoken message indicating that the app's audio is now unmuted. +#: addon/globalPlugins/soundmanager/__init__.py:62 +#, python-brace-format +msgid "{app} unmuted" +msgstr "" + +#. . Translators: The current application does not pay audio. +#: addon/globalPlugins/soundmanager/__init__.py:79 +#, python-brace-format +msgid "{app} is not playing any sound." +msgstr "" + +#. . Translators: Message indicating the volume's percentage ("95%"). +#: addon/globalPlugins/soundmanager/__init__.py:123 +#, python-brace-format +msgid "{volume}%" +msgstr "" + +#. . Translators: Main script help message. +#: addon/globalPlugins/soundmanager/__init__.py:181 msgid "Toggle volume control adjustment on or off" msgstr ""