From db6458a566e4727f914504531fa13d5c940ff095 Mon Sep 17 00:00:00 2001 From: Cyrille Bougot Date: Fri, 13 Dec 2024 03:02:06 +0100 Subject: [PATCH] Added short version for most commonly used command line options (#17486) Fixes #11644 Fixes #17485 Summary of the issue: Most common command line options need a short version. Command line options is not robust, leading to undesirable effects when restarting NVDA Description of user facing changes Short versions of two command line flags have been added: -d for --disable-addons and -n for --lang Using incomplete command line flags or duplicated command line options should not produce unexpected effects when restarting NVDA. Description of development approach Added new flags Check parsed app args rather than raw sys.argv to know the options that NVDA has actually taken into account. Removed languageHandler.getLanguageCliArgs since we do not use this function anymore and it was not correctly working as expected for some corner case (duplicated flag, incomplete flag) --- source/argsParsing.py | 272 ++++++++++++++++++++++++++ source/core.py | 42 ++-- source/globalVars.py | 4 +- source/gui/settingsDialogs.py | 13 +- source/languageHandler.py | 44 ++--- source/nvda.pyw | 244 ++--------------------- tests/unit/test_synthDriverHandler.py | 9 +- user_docs/en/changes.md | 7 + 8 files changed, 351 insertions(+), 284 deletions(-) create mode 100644 source/argsParsing.py diff --git a/source/argsParsing.py b/source/argsParsing.py new file mode 100644 index 00000000000..a1e2ae59fab --- /dev/null +++ b/source/argsParsing.py @@ -0,0 +1,272 @@ +# -*- coding: UTF-8 -*- +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2024 NV Access Limited, Cyrille Bougot +# This file is covered by the GNU General Public License. +# See the file COPYING for more details. + +import argparse +import sys +import winUser + +from typing import IO + + +class _WideParserHelpFormatter(argparse.RawTextHelpFormatter): + def __init__(self, prog: str, indent_increment: int = 2, max_help_position: int = 50, width: int = 1000): + """ + A custom formatter for argparse help messages that uses a wider width. + :param prog: The program name. + :param indent_increment: The number of spaces to indent for each level of nesting. + :param max_help_position: The maximum starting column of the help text. + :param width: The width of the help text. + """ + + super().__init__(prog, indent_increment, max_help_position, width) + + +class NoConsoleOptionParser(argparse.ArgumentParser): + """ + A commandline option parser that shows its messages using dialogs, + as this pyw file has no dos console window associated with it. + """ + + def print_help(self, file: IO[str] | None = None): + """Shows help in a standard Windows message dialog""" + winUser.MessageBox(0, self.format_help(), "Help", 0) + + def error(self, message: str): + """Shows an error in a standard Windows message dialog, and then exits NVDA""" + out = "" + out = self.format_usage() + out += f"\nerror: {message}" + winUser.MessageBox(0, out, "Command-line Argument Error", winUser.MB_ICONERROR) + sys.exit(2) + + +def stringToBool(string): + """Wrapper for configobj.validate.is_boolean to raise the proper exception for wrong values.""" + from configobj.validate import is_boolean, ValidateError + + try: + return is_boolean(string) + except ValidateError as e: + raise argparse.ArgumentTypeError(e.message) + + +def stringToLang(value: str) -> str: + """Perform basic case normalization for ease of use.""" + import languageHandler + + if value.casefold() == "Windows".casefold(): + normalizedLang = "Windows" + else: + normalizedLang = languageHandler.normalizeLanguage(value) + possibleLangNames = languageHandler.listNVDALocales() + if normalizedLang is not None and normalizedLang in possibleLangNames: + return normalizedLang + raise argparse.ArgumentTypeError(f"Language code should be one of:\n{', '.join(possibleLangNames)}.") + + +_parser: NoConsoleOptionParser | None = None +"""The arguments parser used by NVDA. +""" + + +def _createNVDAArgParser() -> NoConsoleOptionParser: + """Create a parser to process NVDA option arguments.""" + + parser = NoConsoleOptionParser(formatter_class=_WideParserHelpFormatter, allow_abbrev=False) + quitGroup = parser.add_mutually_exclusive_group() + quitGroup.add_argument( + "-q", + "--quit", + action="store_true", + dest="quit", + default=False, + help="Quit already running copy of NVDA", + ) + parser.add_argument( + "-k", + "--check-running", + action="store_true", + dest="check_running", + default=False, + help="Report whether NVDA is running via the exit code; 0 if running, 1 if not running", + ) + parser.add_argument( + "-f", + "--log-file", + dest="logFileName", + type=str, + help="The file to which log messages should be written.\n" + 'Default destination is "%%TEMP%%\\nvda.log".\n' + "Logging is always disabled if secure mode is enabled.\n", + ) + parser.add_argument( + "-l", + "--log-level", + dest="logLevel", + type=int, + default=0, # 0 means unspecified in command line. + choices=[10, 12, 15, 20, 100], + help="The lowest level of message logged (debug 10, input/output 12, debugwarning 15, info 20, off 100).\n" + "Default value is 20 (info) or the user configured setting.\n" + "Logging is always disabled if secure mode is enabled.\n", + ) + parser.add_argument( + "-c", + "--config-path", + dest="configPath", + default=None, + type=str, + help="The path where all settings for NVDA are stored.\n" + "The default value is forced if secure mode is enabled.\n", + ) + parser.add_argument( + "-n", + "--lang", + dest="language", + default=None, + type=stringToLang, + help=( + "Override the configured NVDA language.\n" + 'Set to "Windows" for current user default, "en" for English, etc.' + ), + ) + parser.add_argument( + "-m", + "--minimal", + action="store_true", + dest="minimal", + default=False, + help="No sounds, no interface, no start message etc", + ) + # --secure is used to force secure mode. + # Documented in the userGuide in #SecureMode. + parser.add_argument( + "-s", + "--secure", + action="store_true", + dest="secure", + default=False, + help="Starts NVDA in secure mode", + ) + parser.add_argument( + "-d", + "--disable-addons", + action="store_true", + dest="disableAddons", + default=False, + help="Disable all add-ons", + ) + parser.add_argument( + "--debug-logging", + action="store_true", + dest="debugLogging", + default=False, + help="Enable debug level logging just for this run.\n" + "This setting will override any other log level (--loglevel, -l) argument given, " + "as well as no logging option.", + ) + parser.add_argument( + "--no-logging", + action="store_true", + dest="noLogging", + default=False, + help="Disable logging completely for this run.\n" + "This setting can be overwritten with other log level (--loglevel, -l) " + "switch or if debug logging is specified.", + ) + parser.add_argument( + "--no-sr-flag", + action="store_false", + dest="changeScreenReaderFlag", + default=True, + help="Don't change the global system screen reader flag", + ) + installGroup = parser.add_mutually_exclusive_group() + installGroup.add_argument( + "--install", + action="store_true", + dest="install", + default=False, + help="Installs NVDA (starting the new copy after installation)", + ) + installGroup.add_argument( + "--install-silent", + action="store_true", + dest="installSilent", + default=False, + help="Installs NVDA silently (does not start the new copy after installation).", + ) + installGroup.add_argument( + "--create-portable", + action="store_true", + dest="createPortable", + default=False, + help="Creates a portable copy of NVDA (and starts the new copy).\n" + "Requires `--portable-path` to be specified.\n", + ) + installGroup.add_argument( + "--create-portable-silent", + action="store_true", + dest="createPortableSilent", + default=False, + help="Creates a portable copy of NVDA (without starting the new copy).\n" + "This option suppresses warnings when writing to non-empty directories " + "and may overwrite files without warning.\n" + "Requires --portable-path to be specified.\n", + ) + parser.add_argument( + "--portable-path", + dest="portablePath", + default=None, + type=str, + help="The path where a portable copy will be created", + ) + parser.add_argument( + "--launcher", + action="store_true", + dest="launcher", + default=False, + help="Started from the launcher", + ) + parser.add_argument( + "--enable-start-on-logon", + metavar="True|False", + type=stringToBool, + dest="enableStartOnLogon", + default=None, + help="When installing, enable NVDA's start on the logon screen", + ) + parser.add_argument( + "--copy-portable-config", + action="store_true", + dest="copyPortableConfig", + default=False, + help=( + "When installing, copy the portable configuration " + "from the provided path (--config-path, -c) to the current user account" + ), + ) + # This option is passed by Ease of Access so that if someone downgrades without uninstalling + # (despite our discouragement), the downgraded copy won't be started in non-secure mode on secure desktops. + # (Older versions always required the --secure option to start in secure mode.) + # If this occurs, the user will see an obscure error, + # but that's far better than a major security hazzard. + # If this option is provided, NVDA will not replace an already running instance (#10179) + parser.add_argument( + "--ease-of-access", + action="store_true", + dest="easeOfAccess", + default=False, + help="Started by Windows Ease of Access", + ) + return parser + + +def getParser() -> NoConsoleOptionParser: + global _parser + if not _parser: + _parser = _createNVDAArgParser() + return _parser diff --git a/source/core.py b/source/core.py index 7cbe4fa7a12..d897b61d6ae 100644 --- a/source/core.py +++ b/source/core.py @@ -24,6 +24,7 @@ import logHandler import languageHandler import globalVars +import argsParsing from logHandler import log import addonHandler import extensionPoints @@ -214,6 +215,28 @@ class NewNVDAInstance: directory: Optional[str] = None +def computeRestartCLIArgs(removeArgsList: list[str] | None = None) -> list[str]: + """Generate an equivalent list of CLI arguments from the values in globalVars.appArgs. + :param removeArgsList: A list of values to ignore when looking in globalVars.appArgs. + """ + + parser = argsParsing.getParser() + if not removeArgsList: + removeArgsList = [] + args = [] + for arg, val in globalVars.appArgs._get_kwargs(): + if val == parser.get_default(arg): + continue + if arg in removeArgsList: + continue + flag = [a.option_strings[0] for a in parser._actions if a.dest == arg][0] + args.append(flag) + if isinstance(val, bool): + continue + args.append(f"{val}") + return args + + def restartUnsafely(): """Start a new copy of NVDA immediately. Used as a last resort, in the event of a serious error to immediately restart NVDA without running any @@ -239,13 +262,16 @@ def restartUnsafely(): sys.argv.remove(paramToRemove) except ValueError: pass + restartCLIArgs = computeRestartCLIArgs( + removeArgsList=["easeOfAccess"], + ) options = [] if NVDAState.isRunningAsSource(): options.append(os.path.basename(sys.argv[0])) _startNewInstance( NewNVDAInstance( sys.executable, - subprocess.list2cmdline(options + sys.argv[1:]), + subprocess.list2cmdline(options + restartCLIArgs), globalVars.appDir, ), ) @@ -260,15 +286,9 @@ def restart(disableAddons=False, debugLogging=False): return import subprocess - for paramToRemove in ( - "--disable-addons", - "--debug-logging", - "--ease-of-access", - ) + languageHandler.getLanguageCliArgs(): - try: - sys.argv.remove(paramToRemove) - except ValueError: - pass + restartCLIArgs = computeRestartCLIArgs( + removeArgsList=["disableAddons", "debugLogging", "language", "easeOfAccess"], + ) options = [] if NVDAState.isRunningAsSource(): options.append(os.path.basename(sys.argv[0])) @@ -280,7 +300,7 @@ def restart(disableAddons=False, debugLogging=False): if not triggerNVDAExit( NewNVDAInstance( sys.executable, - subprocess.list2cmdline(options + sys.argv[1:]), + subprocess.list2cmdline(options + restartCLIArgs), globalVars.appDir, ), ): diff --git a/source/globalVars.py b/source/globalVars.py index 2b92f7f38b1..7f569bd20f7 100644 --- a/source/globalVars.py +++ b/source/globalVars.py @@ -1,5 +1,5 @@ # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2006-2022 NV Access Limited, Łukasz Golonka, Leonard de Ruijter, Babbage B.V., +# Copyright (C) 2006-2024 NV Access Limited, Łukasz Golonka, Leonard de Ruijter, Babbage B.V., # Aleksey Sadovoy, Peter Vágner # This file may be used under the terms of the GNU General Public License, version 2 or later. # For more details see: https://www.gnu.org/licenses/gpl-2.0.html @@ -41,7 +41,7 @@ class DefaultAppArgs(argparse.Namespace): logFileName: Optional[os.PathLike] = "" logLevel: int = 0 configPath: Optional[os.PathLike] = None - language: str = "en" + language: str | None = None minimal: bool = False secure: bool = False """ diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 61a804f9fb1..af4779ba0f0 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -800,16 +800,9 @@ def makeSettings(self, settingsSizer): self.languageNames = languageHandler.getAvailableLanguages(presentational=True) languageChoices = [x[1] for x in self.languageNames] if languageHandler.isLanguageForced(): - try: - cmdLangDescription = next( - ld for code, ld in self.languageNames if code == globalVars.appArgs.language - ) - except StopIteration: - # In case --lang=Windows is passed to the command line, globalVars.appArgs.language is the current - # Windows language,, which may not be in the list of NVDA supported languages, e.g. Windows language may - # be 'fr_FR' but NVDA only supports 'fr'. - # In this situation, only use language code as a description. - cmdLangDescription = globalVars.appArgs.language + cmdLangDescription = next( + ld for code, ld in self.languageNames if code == globalVars.appArgs.language + ) languageChoices.append( # Translators: Shown for a language which has been provided from the command line # 'langDesc' would be replaced with description of the given locale. diff --git a/source/languageHandler.py b/source/languageHandler.py index 458cb89350e..53d45ba09f0 100644 --- a/source/languageHandler.py +++ b/source/languageHandler.py @@ -9,7 +9,6 @@ """ import os -import sys import ctypes import weakref @@ -25,7 +24,6 @@ List, Optional, Tuple, - Union, ) # a few Windows locale constants @@ -44,6 +42,10 @@ LANGS_WITHOUT_TRANSLATIONS: FrozenSet[str] = frozenset(("en",)) +_language: str | None = None +"""Language of NVDA's UI. +""" + installedTranslation: Optional[weakref.ReferenceType] = None """Saved copy of the installed translation for ease of wrapping. """ @@ -291,24 +293,9 @@ def getAvailableLanguages(presentational: bool = False) -> List[Tuple[str, str]] return langs -def getLanguageCliArgs() -> Tuple[str, ...]: - """Returns all command line arguments which were used to set current NVDA language - or an empty tuple if language has not been specified from the CLI.""" - for argIndex, argValue in enumerate(sys.argv): - if argValue == "--lang": - # Language was provided in a form `--lang lang_CODE`. The next position in `sys.argv` is a language code. - # It is impossible not to provide it in this case as it would be flagged as an error - # during arguments validation. - return (argValue, sys.argv[argIndex + 1]) - if argValue.startswith("--lang="): - # Language in a form `--lang=lang_CODE` - return (argValue,) - return tuple() - - def isLanguageForced() -> bool: """Returns `True` if language is provided from the command line - `False` otherwise.""" - return bool(getLanguageCliArgs()) + return globalVars.appArgs.language is not None def getWindowsLanguage(): @@ -326,17 +313,15 @@ def getWindowsLanguage(): def _createGettextTranslation( localeName: str, -) -> Union[None, gettext.GNUTranslations, gettext.NullTranslations]: +) -> tuple[(None | gettext.GNUTranslations | gettext.NullTranslations), (str | None)]: if localeName in LANGS_WITHOUT_TRANSLATIONS: - globalVars.appArgs.language = localeName - return gettext.translation("nvda", fallback=True) + return gettext.translation("nvda", fallback=True), localeName try: trans = gettext.translation("nvda", localedir="locale", languages=[localeName]) - globalVars.appArgs.language = localeName - return trans + return trans, localeName except IOError: log.debugWarning(f"couldn't set the translation service locale to {localeName}") - return None + return None, None def setLanguage(lang: str) -> None: @@ -347,6 +332,8 @@ def setLanguage(lang: str) -> None: - Current NVDA language (match the translation service) - the python locale for the thread (match the translation service, fallback to system default) """ + global _language + if lang == "Windows": localeName = getWindowsLanguage() else: @@ -356,12 +343,13 @@ def setLanguage(lang: str) -> None: if winKernel.kernel32.SetThreadLocale(LCID) == 0: log.debugWarning(f"couldn't set windows thread locale to {lang}") - trans = _createGettextTranslation(localeName) + trans, validatedLocalName = _createGettextTranslation(localeName) if trans is None and "_" in localeName: localeName = localeName.split("_")[0] - trans = _createGettextTranslation(localeName) + trans, validatedLocalName = _createGettextTranslation(localeName) if trans is None: - trans = _createGettextTranslation("en") + trans, validatedLocalName = _createGettextTranslation("en") + _language = validatedLocalName trans.install(names=["pgettext", "npgettext", "ngettext"]) setLocale(getLanguage()) @@ -451,7 +439,7 @@ def setLocale(localeName: str) -> None: def getLanguage() -> str: - return globalVars.appArgs.language + return _language def normalizeLanguage(lang: str) -> Optional[str]: diff --git a/source/nvda.pyw b/source/nvda.pyw index fc12d32270f..ae442e1ca59 100755 --- a/source/nvda.pyw +++ b/source/nvda.pyw @@ -14,9 +14,10 @@ import logging import sys import os -from typing import IO +from typing import Any import globalVars +from argsParsing import getParser import ctypes from ctypes import wintypes import monkeyPatches @@ -59,7 +60,6 @@ globalVars.appDir = appDir globalVars.appPid = os.getpid() -import argparse # noqa: E402 import config # noqa: E402 import logHandler # noqa: E402 from logHandler import log # noqa: E402 @@ -79,38 +79,6 @@ else: config.isAppX = GetCurrentPackageFullName(ctypes.byref(bufLen), None) != 15700 -class _WideParserHelpFormatter(argparse.RawTextHelpFormatter): - def __init__(self, prog: str, indent_increment: int = 2, max_help_position: int = 50, width: int = 1000): - """ - A custom formatter for argparse help messages that uses a wider width. - :param prog: The program name. - :param indent_increment: The number of spaces to indent for each level of nesting. - :param max_help_position: The maximum starting column of the help text. - :param width: The width of the help text. - """ - - super().__init__(prog, indent_increment, max_help_position, width) - - -class NoConsoleOptionParser(argparse.ArgumentParser): - """ - A commandline option parser that shows its messages using dialogs, - as this pyw file has no dos console window associated with it. - """ - - def print_help(self, file: IO[str] | None = None): - """Shows help in a standard Windows message dialog""" - winUser.MessageBox(0, self.format_help(), "Help", 0) - - def error(self, message: str): - """Shows an error in a standard Windows message dialog, and then exits NVDA""" - out = "" - out = self.format_usage() - out += f"\nerror: {message}" - winUser.MessageBox(0, out, "Command-line Argument Error", winUser.MB_ICONERROR) - sys.exit(2) - - NVDAState._initializeStartTime() @@ -122,204 +90,24 @@ if not winVersion.isSupportedOS(): sys.exit(1) -def stringToBool(string): - """Wrapper for configobj.validate.is_boolean to raise the proper exception for wrong values.""" - from configobj.validate import is_boolean, ValidateError +def __getattr__(attrName: str) -> Any: + """Module level `__getattr__` used to preserve backward compatibility.""" + if NVDAState._allowDeprecatedAPI(): + if attrName in ("NoConsoleOptionParser", "stringToBool", "stringToLang"): + import argsParsing - try: - return is_boolean(string) - except ValidateError as e: - raise argparse.ArgumentTypeError(e.message) + log.warning(f"__main__.{attrName} is deprecated, use argsParsing.{attrName} instead.") + return getattr(argsParsing, attrName) + if attrName == "parser": + import argsParsing + log.warning(f"__main__.{attrName} is deprecated, use argsParsing.getParser() instead.") + return argsParsing.getParser() + raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}") -def stringToLang(value: str) -> str: - """Perform basic case normalization for ease of use.""" - import languageHandler - if value.casefold() == "Windows".casefold(): - normalizedLang = "Windows" - else: - normalizedLang = languageHandler.normalizeLanguage(value) - possibleLangNames = languageHandler.listNVDALocales() - if normalizedLang is not None and normalizedLang in possibleLangNames: - return normalizedLang - raise argparse.ArgumentTypeError(f"Language code should be one of:\n{', '.join(possibleLangNames)}.") - - -# Process option arguments -parser = NoConsoleOptionParser(formatter_class=_WideParserHelpFormatter) -quitGroup = parser.add_mutually_exclusive_group() -quitGroup.add_argument( - "-q", "--quit", action="store_true", dest="quit", default=False, help="Quit already running copy of NVDA" -) -parser.add_argument( - "-k", - "--check-running", - action="store_true", - dest="check_running", - default=False, - help="Report whether NVDA is running via the exit code; 0 if running, 1 if not running", -) -parser.add_argument( - "-f", - "--log-file", - dest="logFileName", - type=str, - help="The file to which log messages should be written.\n" - 'Default destination is "%%TEMP%%\\nvda.log".\n' - "Logging is always disabled if secure mode is enabled.\n", -) -parser.add_argument( - "-l", - "--log-level", - dest="logLevel", - type=int, - default=0, # 0 means unspecified in command line. - choices=[10, 12, 15, 20, 100], - help="The lowest level of message logged (debug 10, input/output 12, debugwarning 15, info 20, off 100).\n" - "Default value is 20 (info) or the user configured setting.\n" - "Logging is always disabled if secure mode is enabled.\n", -) -parser.add_argument( - "-c", - "--config-path", - dest="configPath", - default=None, - type=str, - help="The path where all settings for NVDA are stored.\n" - "The default value is forced if secure mode is enabled.\n", -) -parser.add_argument( - "--lang", - dest="language", - default="en", - type=stringToLang, - help=( - "Override the configured NVDA language.\n" - 'Set to "Windows" for current user default, "en" for English, etc.' - ), -) -parser.add_argument( - "-m", - "--minimal", - action="store_true", - dest="minimal", - default=False, - help="No sounds, no interface, no start message etc", -) -# --secure is used to force secure mode. -# Documented in the userGuide in #SecureMode. -parser.add_argument( - "-s", - "--secure", - action="store_true", - dest="secure", - default=False, - help="Starts NVDA in secure mode", -) -parser.add_argument( - "--disable-addons", action="store_true", dest="disableAddons", default=False, help="Disable all add-ons" -) -parser.add_argument( - "--debug-logging", - action="store_true", - dest="debugLogging", - default=False, - help="Enable debug level logging just for this run.\n" - "This setting will override any other log level (--loglevel, -l) argument given, " - "as well as no logging option.", -) -parser.add_argument( - "--no-logging", - action="store_true", - dest="noLogging", - default=False, - help="Disable logging completely for this run.\n" - "This setting can be overwritten with other log level (--loglevel, -l) " - "switch or if debug logging is specified.", -) -parser.add_argument( - "--no-sr-flag", - action="store_false", - dest="changeScreenReaderFlag", - default=True, - help="Don't change the global system screen reader flag", -) -installGroup = parser.add_mutually_exclusive_group() -installGroup.add_argument( - "--install", - action="store_true", - dest="install", - default=False, - help="Installs NVDA (starting the new copy after installation)", -) -installGroup.add_argument( - "--install-silent", - action="store_true", - dest="installSilent", - default=False, - help="Installs NVDA silently (does not start the new copy after installation).", -) -installGroup.add_argument( - "--create-portable", - action="store_true", - dest="createPortable", - default=False, - help="Creates a portable copy of NVDA (and starts the new copy).\n" - "Requires `--portable-path` to be specified.\n", -) -installGroup.add_argument( - "--create-portable-silent", - action="store_true", - dest="createPortableSilent", - default=False, - help="Creates a portable copy of NVDA (without starting the new copy).\n" - "This option suppresses warnings when writing to non-empty directories " - "and may overwrite files without warning.\n" - "Requires --portable-path to be specified.\n", -) -parser.add_argument( - "--portable-path", - dest="portablePath", - default=None, - type=str, - help="The path where a portable copy will be created", -) -parser.add_argument( - "--launcher", action="store_true", dest="launcher", default=False, help="Started from the launcher" -) -parser.add_argument( - "--enable-start-on-logon", - metavar="True|False", - type=stringToBool, - dest="enableStartOnLogon", - default=None, - help="When installing, enable NVDA's start on the logon screen", -) -parser.add_argument( - "--copy-portable-config", - action="store_true", - dest="copyPortableConfig", - default=False, - help=( - "When installing, copy the portable configuration " - "from the provided path (--config-path, -c) to the current user account" - ), -) -# This option is passed by Ease of Access so that if someone downgrades without uninstalling -# (despite our discouragement), the downgraded copy won't be started in non-secure mode on secure desktops. -# (Older versions always required the --secure option to start in secure mode.) -# If this occurs, the user will see an obscure error, -# but that's far better than a major security hazzard. -# If this option is provided, NVDA will not replace an already running instance (#10179) -parser.add_argument( - "--ease-of-access", - action="store_true", - dest="easeOfAccess", - default=False, - help="Started by Windows Ease of Access", -) -(globalVars.appArgs, globalVars.unknownAppArgs) = parser.parse_known_args() +_parser = getParser() +(globalVars.appArgs, globalVars.unknownAppArgs) = _parser.parse_known_args() # Make any app args path values absolute # So as to not be affected by the current directory changing during process lifetime. pathAppArgs = [ diff --git a/tests/unit/test_synthDriverHandler.py b/tests/unit/test_synthDriverHandler.py index b7732215780..e345672fcd8 100644 --- a/tests/unit/test_synthDriverHandler.py +++ b/tests/unit/test_synthDriverHandler.py @@ -1,12 +1,11 @@ # A part of NonVisual Desktop Access (NVDA) # This file is covered by the GNU General Public License. # See the file COPYING for more details. -# Copyright (C) 2021-2023 NV Access Limited, Leonard de RUijter +# Copyright (C) 2021-2024 NV Access Limited, Leonard de Ruijter, Cyrille Bougot """Unit tests for the synthDriverHandler""" import config -import globalVars import languageHandler import synthDriverHandler from synthDrivers.oneCore import SynthDriver as OneCoreSynthDriver @@ -48,7 +47,7 @@ def setUp(self) -> None: config.conf["speech"]["synth"] = FAKE_DEFAULT_LANG synthDriverHandler._curSynth = MockSynth(FAKE_DEFAULT_SYNTH_NAME) synthDriverHandler._getSynthDriver = self._mock_getSynthDriver - globalVars.appArgs.language = FAKE_DEFAULT_LANG + languageHandler._language = FAKE_DEFAULT_LANG @staticmethod def _mock_getSynthDriver(synthName: str) -> Callable[[], MockSynth]: @@ -58,7 +57,7 @@ def tearDown(self) -> None: config.conf["speech"]["synth"] = self._oldSynthConfig synthDriverHandler._curSynth = self._originalSynth synthDriverHandler._getSynthDriver = self._originalGetSynthDriver - globalVars.appArgs.language = self._oldLang + languageHandler._language = self._oldLang def test_setSynth_auto(self): """ @@ -113,7 +112,7 @@ def test_setSynth_auto_fallback_ifOneCoreDoesntSupportDefaultLanguage(self): Ensures that if oneCore doesn't support the current language, setSynth("auto") falls back to the current synth, or espeak if there is no current synth. """ - globalVars.appArgs.language = "bar" # set the lang so it is not supported + languageHandler._language = "bar" # set the lang so it is not supported synthDriverHandler.setSynth("auto") self.assertEqual(synthDriverHandler.getSynth().name, FAKE_DEFAULT_SYNTH_NAME) synthDriverHandler.setSynth(None) # reset the synth so there is no fallback diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index a1ef6015dbd..cc522fb389f 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -47,6 +47,8 @@ To use this feature, "allow NVDA to control the volume of other applications" mu * Component updates: * Updated LibLouis Braille translator to [3.32.0](https://github.com/liblouis/liblouis/releases/tag/v3.32.0). (#17469, @LeonarddeR) * Updated CLDR to version 46.0. (#17484, @OzancanKaratas) +* Short versions of the most commonly used command line options have been added: `-d` for `--disable-addons` and `-n` for `--lang`. +Prefix matching on command line flags, e.g. using `--di` for `--disable-addons` is no longer supported. (#11644, @CyrilleB79) ### Bug Fixes @@ -144,11 +146,16 @@ As the NVDA update check URL is now configurable directly within NVDA, no replac * The `WASAPI` key has been removed from the `audio` section of the config spec. * The output from `nvwave.outputDeviceNameToID`, and input to `nvwave.outputDeviceIDToName` are now string identifiers. * In `NVDAObjects.window.scintilla.ScintillaTextInfo`, if no text is selected, the `collapse` method is overriden to expand to line if the `end` parameter is set to `True` (#17431, @nvdaes) +* The following symbols have been removed with no replacement: `languageHandler.getLanguageCliArgs`, `__main__.quitGroup` and `__main__.installGroup` . (#17486, @CyrilleB79) +* Prefix matching on command line flags, e.g. using `--di` for `--disable-addons` is no longer supported. (#11644, @CyrilleB79) #### Deprecations * The `braille.filter_displaySize` extension point is deprecated. Please use `braille.filter_displayDimensions` instead. (#17011) +* The following symbols are deprecated (#17486, @CyrilleB79): + * `NoConsoleOptionParser`, `stringToBool`, `stringToLang` in `__main__`; use the same symbols in `argsParsing` instead. + * `__main__.parser`; use `argsParsing.getParser()` instead. ## 2024.4.1