Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn about unknown command line arguments when starting NVDA #13087

Merged
merged 10 commits into from Nov 24, 2021
2 changes: 1 addition & 1 deletion launcher/nvdaLauncher.nsi
Expand Up @@ -90,7 +90,7 @@ file /R "${NVDADistDir}\"
${GetParameters} $0
Banner::destroy
exec:
execWait "$PLUGINSDIR\app\nvda_noUIAccess.exe $0 -r --launcher" $1
execWait "$PLUGINSDIR\app\nvda_noUIAccess.exe $0 --launcher" $1
;If exit code is 3 then execute again (restart)
intcmp $1 3 exec +1
SectionEnd
Expand Down
7 changes: 7 additions & 0 deletions source/addonHandler/__init__.py
Expand Up @@ -29,6 +29,7 @@
import addonAPIVersion
from . import addonVersionCheck
from .addonVersionCheck import isAddonCompatible
import extensionPoints
import buildVersion


Expand All @@ -44,6 +45,12 @@
_blockedAddons=set()


# Allows add-ons to process additional command line arguments when NVDA starts.
# Each handler is called with one keywod argument `cliArgument`
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
# and should return `False` if it is not interested in it, `True` otherwise.
isCLIParamKnown = extensionPoints.AccumulatingDecider(defaultDecision=False)
seanbudd marked this conversation as resolved.
Show resolved Hide resolved


class AddonsState(collections.UserDict):
"""Subclasses `collections.UserDict` to preserve backwards compatibility."""

Expand Down
30 changes: 28 additions & 2 deletions source/core.py
Expand Up @@ -8,7 +8,7 @@


from dataclasses import dataclass
from typing import Optional
from typing import List, Optional
import comtypes
import sys
import winVersion
Expand Down Expand Up @@ -56,7 +56,33 @@
def doStartupDialogs():
import config
import gui
# Translators: The title of the dialog to tell users that there are erros in the configuration file.

def handleReplaceCLIArg(cliArgument: str) -> bool:
"""Since #9827 NVDA replaces a currently running instance
and therefore `--replace` command line argument is redundant and no longer supported.
However for backwards compatibility the desktop shortcut created by installer
still starts NVDA with the now redundant switch.
Its presence in command line arguments should not cause a warning on startup."""
return cliArgument in ("-r", "--replace")

addonHandler.isCLIParamKnown.register(handleReplaceCLIArg)
unknownCLIParams: List[str] = list()
for param in globalVars.unknownAppArgs:
isParamKnown = addonHandler.isCLIParamKnown.decide(cliArgument=param)
if not isParamKnown:
unknownCLIParams.append(param)
if unknownCLIParams:
import wx
gui.messageBox(
# Translators: Shown when NVDA has been started with unknown command line parameters.
_("The following command line parameters are unknown to NVDA: {params}").format(
params=", ".join(unknownCLIParams)
),
# Translators: Title of the dialog letting user know
# that command line parameters they provided are unknown.
_("Unknown command line parameters!"),
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
wx.OK | wx.ICON_ERROR
)
if config.conf.baseConfigError:
import wx
gui.messageBox(
Expand Down
68 changes: 62 additions & 6 deletions source/extensionPoints/__init__.py
@@ -1,18 +1,18 @@
#extensionPoints.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2017 NV Access Limited
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2017-2021 NV Access Limited, Joseph Lee, Łukasz Golonka
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

"""Framework to enable extensibility at specific points in the code.
This allows interested parties to register to be notified when some action occurs
or to modify a specific kind of data.
For example, you might wish to notify about a configuration profile switch
or allow modification of spoken messages before they are passed to the synthesizer.
See the L{Action}, L{Filter}, L{Decider} classes.
See the L{Action}, L{Filter}, L{Decider} and L{AccumulatingDecider} classes.
"""
from logHandler import log
from .util import HandlerRegistrar, callWithSupportedKwargs, BoundMethodWeakref
from typing import Set


class Action(HandlerRegistrar):
Expand Down Expand Up @@ -152,3 +152,59 @@ def decide(self, **kwargs):
if not decision:
return False
return True


class AccumulatingDecider(HandlerRegistrar):
"""Allows interested parties to participate in deciding whether something
should be done.
In contrast with L{Decider} all handlers are executed and then results are returned.
For example, normally user should be warned about all command line parameters
which are unknown to NVDA, but this extension point can be used to pass each unknown parameter
to all add-ons since one of them may want to process some command line arguments.

First, a AccumulatingDecider is created with a default decision :

>>> doSomething = AccumulatingDecider(defaultDecision=True)

Interested parties then register to participate in the decision, see
L{register} docstring for details of the type of handlers that can be
registered:

>>> def shouldDoSomething(someArg=None):
... return False
...
>>> doSomething.register(shouldDoSomething)

When the decision is to be made registered handlers are called and they return values are collected,
see L{util.callWithSupportedKwargs}
for how args passed to notify are mapped to the handler:

>>> doSomething.decide(someArg=42)
False

If there are no handlers or all handlers return defaultDecision,
the return value is the value of the default decision.
"""

def __init__(self, defaultDecision: bool) -> None:
super().__init__()
self.defaultDecision: bool = defaultDecision

def decide(self, **kwargs) -> bool:
"""Call handlers to make a decision.
Results returned from all handlers are collected
and if at least one handler returns value different than the one specifed as default it is returned.
If there are no handlers or all handlers return the default value, the default value is returned.
@param kwargs: Arguments to pass to the handlers.
@return: The decision.
"""
decisions: Set[bool] = set()
for handler in self.handlers:
try:
decisions.add(callWithSupportedKwargs(handler, **kwargs))
except Exception:
log.exception("Error running handler %r for %r" % (handler, self))
continue
if (not self.defaultDecision) in decisions:
return (not self.defaultDecision)
return self.defaultDecision
2 changes: 1 addition & 1 deletion source/globalVars.py
Expand Up @@ -61,7 +61,7 @@ class DefautAppArgs(argparse.Namespace):
reviewPositionObj=None
lastProgressValue=0
appArgs = DefautAppArgs()
appArgsExtra=None
unknownAppArgs: typing.List[str] = []
settingsRing = None
speechDictionaryProcessing=True
exitCode=0
2 changes: 1 addition & 1 deletion source/nvda.pyw
Expand Up @@ -188,7 +188,7 @@ parser.add_argument(
# 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.appArgsExtra)=parser.parse_known_args()
(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 = [
Expand Down