From 817c4ca790de2b640d8d51231a5732f708fcdb81 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Mon, 21 Sep 2020 11:23:54 +1000 Subject: [PATCH 01/25] nvda.pyw: ensure that all paths coming from commandline arguments are made absolute as soon as possible to protect against the current directory changing later on. Also store NVDA's app dir in globalVars. --- source/nvda.pyw | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/source/nvda.pyw b/source/nvda.pyw index 8d810104e0f..8fd724cd317 100755 --- a/source/nvda.pyw +++ b/source/nvda.pyw @@ -9,16 +9,21 @@ import sys import os +import globalVars + if getattr(sys, "frozen", None): # We are running as an executable. # Append the path of the executable to sys so we can import modules from the dist dir. sys.path.append(sys.prefix) - os.chdir(sys.prefix) + appDir = sys.prefix else: import sourceEnv #We should always change directory to the location of this module (nvda.pyw), don't rely on sys.path[0] - os.chdir(os.path.normpath(os.path.dirname(__file__))) + appDir = os.path.normpath(os.path.dirname(__file__)) +appDir = os.path.abspath(appDir) +os.chdir(appDir) +globalVars.appDir = appDir import ctypes import locale @@ -112,6 +117,18 @@ parser.add_argument('--enable-start-on-logon',metavar="True|False",type=stringTo # 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() +# Make any app args path values absolute +# So as to not be affected by the current directory changing during process lifetime. +pathAppArgs = [ + "configPath", + "logFileName", + "portablePath", +] +for name in pathAppArgs: + origVal = getattr(globalVars.appArgs, name) + if isinstance(origVal, str): + newVal = os.path.abspath(origVal) + setattr(globalVars.appArgs, name, newVal) def terminateRunningNVDA(window): processID,threadID=winUser.getWindowThreadProcessID(window) From cd8e8d67946f263ace78460e22b2d8baabd15f9e Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Mon, 21 Sep 2020 11:25:51 +1000 Subject: [PATCH 02/25] Use the NVDA app dir rather than the current directory for relative paths. --- source/NVDAHelper.py | 6 +++--- source/characterProcessing.py | 10 ++++++---- source/config/__init__.py | 2 +- source/fonts/__init__.py | 4 ++-- source/gui/__init__.py | 4 ++-- source/logHandler.py | 2 +- source/synthDrivers/_espeak.py | 7 ++++--- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/source/NVDAHelper.py b/source/NVDAHelper.py index 1c61eed23ca..4bd712c3af2 100755 --- a/source/NVDAHelper.py +++ b/source/NVDAHelper.py @@ -29,11 +29,11 @@ import time import globalVars -versionedLibPath='lib' +versionedLibPath = os.path.join(globalVars.appDir, 'lib') if os.environ.get('PROCESSOR_ARCHITEW6432') == 'ARM64': - versionedLib64Path = 'libArm64' + versionedLib64Path = os.path.join(globalVars.appDir, 'libArm64') else: - versionedLib64Path = 'lib64' + versionedLib64Path = os.path.join(globalVars.appDir, 'lib64') if getattr(sys,'frozen',None): # Not running from source. Libraries are in a version-specific directory versionedLibPath=os.path.join(versionedLibPath,versionInfo.version) diff --git a/source/characterProcessing.py b/source/characterProcessing.py index cf23e26529b..b725c79bd8c 100644 --- a/source/characterProcessing.py +++ b/source/characterProcessing.py @@ -78,7 +78,7 @@ def __init__(self,locale): @type locale: string """ self._entries = {} - fileName=os.path.join('locale',locale,'characterDescriptions.dic') + fileName = os.path.join(globalVars.appDir, 'locale', locale, 'characterDescriptions.dic') if not os.path.isfile(fileName): raise LookupError(fileName) f = codecs.open(fileName,"r","utf_8_sig",errors="replace") @@ -367,12 +367,14 @@ def _getSpeechSymbolsForLocale(locale): # Load the data before loading other symbols, # in order to allow translators to override them. try: - builtin.load(os.path.join("locale", locale, "cldr.dic"), - allowComplexSymbols=False) + builtin.load( + os.path.join(globalVars.appDir, "locale", locale, "cldr.dic"), + allowComplexSymbols=False + ) except IOError: log.debugWarning("No CLDR data for locale %s" % locale) try: - builtin.load(os.path.join("locale", locale, "symbols.dic")) + builtin.load(os.path.join(globalVars.appDir, "locale", locale, "symbols.dic")) except IOError: _noSymbolLocalesCache.add(locale) raise LookupError("No symbol information for locale %s" % locale) diff --git a/source/config/__init__.py b/source/config/__init__.py index 9b2ca5a594a..ac75ad1c8d9 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -125,7 +125,7 @@ def getUserDefaultConfigPath(useInstalledPathIfExists=False): # Therefore add a suffix to the directory to make it specific to Windows Store application versions. installedUserConfigPath+='_appx' return installedUserConfigPath - return u'.\\userConfig\\' + return os.path.join(globalVars.appDir, 'userConfig') def getSystemConfigPath(): if isInstalledCopy(): diff --git a/source/fonts/__init__.py b/source/fonts/__init__.py index f6b565aec0d..ef01b44cf8f 100644 --- a/source/fonts/__init__.py +++ b/source/fonts/__init__.py @@ -1,10 +1,10 @@ -# brailleViewer.py # A part of NonVisual Desktop Access (NVDA) # Copyright (C) 2019 NV Access Limited # This file is covered by the GNU General Public License. # See the file COPYING for more details. from typing import List +import globalVars from logHandler import log import os from ctypes import WinDLL @@ -13,7 +13,7 @@ Loads custom fonts for use in NVDA. """ -fontsDir = os.path.abspath("fonts") +fontsDir = os.path.join(globalVars.appDir, "fonts") def _isSupportedFontPath(f: str) -> bool: diff --git a/source/gui/__init__.py b/source/gui/__init__.py index fd0d4f7b3c3..5b223aac95c 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -44,7 +44,7 @@ updateCheck = None ### Constants -NVDA_PATH = os.getcwd() +NVDA_PATH = globalVars.appDir ICON_PATH=os.path.join(NVDA_PATH, "images", "nvda.ico") DONATE_URL = "http://www.nvaccess.org/donate/" @@ -57,7 +57,7 @@ def getDocFilePath(fileName, localized=True): if hasattr(sys, "frozen"): getDocFilePath.rootPath = os.path.join(NVDA_PATH, "documentation") else: - getDocFilePath.rootPath = os.path.abspath(os.path.join("..", "user_docs")) + getDocFilePath.rootPath = os.path.join(NVDA_PATH, "..", "user_docs") if localized: lang = languageHandler.getLanguage() diff --git a/source/logHandler.py b/source/logHandler.py index aab62ce11bd..6d9612d6142 100755 --- a/source/logHandler.py +++ b/source/logHandler.py @@ -249,7 +249,7 @@ class RemoteHandler(logging.Handler): def __init__(self): #Load nvdaHelperRemote.dll but with an altered search path so it can pick up other dlls in lib - path=os.path.abspath(os.path.join(u"lib",buildVersion.version,u"nvdaHelperRemote.dll")) + path = os.path.join(globalVars.appDir, "lib", buildVersion.version, "nvdaHelperRemote.dll") h=ctypes.windll.kernel32.LoadLibraryExW(path,0,LOAD_WITH_ALTERED_SEARCH_PATH) if not h: raise OSError("Could not load %s"%path) diff --git a/source/synthDrivers/_espeak.py b/source/synthDrivers/_espeak.py index 0fc98f615c4..c2a5ee197df 100755 --- a/source/synthDrivers/_espeak.py +++ b/source/synthDrivers/_espeak.py @@ -9,6 +9,7 @@ import nvwave import threading import queue +from ctypes import cdll from ctypes import * import config import globalVars @@ -321,7 +322,7 @@ def initialize(indexCallback=None): the number of the index or C{None} when speech stops. """ global espeakDLL, bgThread, bgQueue, player, onIndexReached - espeakDLL=cdll.LoadLibrary(r"synthDrivers\espeak.dll") + espeakDLL = cdll.LoadLibrary(os.path.join(globalVars.appDir, "synthDrivers", "espeak.dll")) espeakDLL.espeak_Info.restype=c_char_p espeakDLL.espeak_Synth.errcheck=espeak_errcheck espeakDLL.espeak_SetVoiceByName.errcheck=espeak_errcheck @@ -331,7 +332,7 @@ def initialize(indexCallback=None): espeakDLL.espeak_ListVoices.restype=POINTER(POINTER(espeak_VOICE)) espeakDLL.espeak_GetCurrentVoice.restype=POINTER(espeak_VOICE) espeakDLL.espeak_SetVoiceByName.argtypes=(c_char_p,) - eSpeakPath=os.path.abspath("synthDrivers") + eSpeakPath = os.path.join(globalVars.appDir, "synthDrivers") sampleRate = espeakDLL.espeak_Initialize( AUDIO_OUTPUT_SYNCHRONOUS, 300, os.fsencode(eSpeakPath), @@ -371,7 +372,7 @@ def info(): return espeakDLL.espeak_Info() def getVariantDict(): - dir='synthDrivers\\espeak-ng-data\\voices\\!v' + dir = os.path.join(globalVars.appDir, "synthDrivers", "espeak-ng-data", "voices", "!v") # Translators: name of the default espeak varient. variantDict={"none": pgettext("espeakVarient", "none")} for fileName in os.listdir(dir): From ffcffc9ab73fe1df64629cc7001708b4ec8de768 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Mon, 21 Sep 2020 13:11:50 +1000 Subject: [PATCH 03/25] Fix unit tests. --- tests/unit/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index ab935110978..4e4d26c05b4 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -35,8 +35,13 @@ # as this module is imported to expand the system path. import sourceEnv # noqa: F401 -# Set options normally taken from the command line. import globalVars + + +# Tell NvDA where its application directory is +globalVars.appDir = SOURCE_DIR + +# Set options normally taken from the command line. class AppArgs: # The path from which to load a configuration file. # Ideally, this would be an in-memory, default configuration. From 347f5c4ef00553c3fbbdf8924d652cdfabdb6b3a Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Tue, 22 Sep 2020 14:24:25 +1000 Subject: [PATCH 04/25] Remove all usage of os.getcwd and replace it with globalVars.appDir --- source/config/__init__.py | 2 +- source/core.py | 2 +- source/installer.py | 2 +- source/updateCheck.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/config/__init__.py b/source/config/__init__.py index ac75ad1c8d9..4481c31099a 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -83,7 +83,7 @@ def isInstalledCopy(): return False winreg.CloseKey(k) try: - return os.stat(instDir)==os.stat(os.getcwd()) + return os.stat(instDir) == os.stat(globalVars.appDir) except WindowsError: return False diff --git a/source/core.py b/source/core.py index 17c8c38e675..a2f1e3ac779 100644 --- a/source/core.py +++ b/source/core.py @@ -410,7 +410,7 @@ def handlePowerStatusChange(self): if not wxLang and '_' in lang: wxLang=locale.FindLanguageInfo(lang.split('_')[0]) if hasattr(sys,'frozen'): - locale.AddCatalogLookupPathPrefix(os.path.join(os.getcwd(),"locale")) + locale.AddCatalogLookupPathPrefix(os.path.join(globalVars.appDir, "locale")) # #8064: Wx might know the language, but may not actually contain a translation database for that language. # If we try to initialize this language, wx will show a warning dialog. # #9089: some languages (such as Aragonese) do not have language info, causing language getter to fail. diff --git a/source/installer.py b/source/installer.py index b450cd1b7f6..fd50bceee4f 100644 --- a/source/installer.py +++ b/source/installer.py @@ -121,7 +121,7 @@ def getDocFilePath(fileName,installDir): return tryPath def copyProgramFiles(destPath): - sourcePath=os.getcwd() + sourcePath = globalVars.appDir detectUserConfig=True detectNVDAExe=True for curSourceDir,subDirs,files in os.walk(sourcePath): diff --git a/source/updateCheck.py b/source/updateCheck.py index d66a993546a..20c787959f5 100644 --- a/source/updateCheck.py +++ b/source/updateCheck.py @@ -204,7 +204,7 @@ def _executeUpdate(destPath): if config.isInstalledCopy(): executeParams = u"--install -m" else: - portablePath = os.getcwd() + portablePath = globalVars.appDir if os.access(portablePath, os.W_OK): executeParams = u'--create-portable --portable-path "{portablePath}" --config-path "{configPath}" -m'.format( portablePath=portablePath, From 6026250ec01f46a186fd175f1c1de584b7a74cd0 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Tue, 22 Sep 2020 14:35:45 +1000 Subject: [PATCH 05/25] Replace all remaining os.path.join("* calls with os.path.join(globalVars.appDir calls. --- source/COMRegistrationFixes/__init__.py | 3 ++- source/inputCore.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/COMRegistrationFixes/__init__.py b/source/COMRegistrationFixes/__init__.py index 6f32f1d9d81..968be869653 100644 --- a/source/COMRegistrationFixes/__init__.py +++ b/source/COMRegistrationFixes/__init__.py @@ -8,6 +8,7 @@ import os import subprocess import winVersion +import globalVars from logHandler import log # Particular 64 bit / 32 bit system paths @@ -53,7 +54,7 @@ def applyRegistryPatch(fileName,wow64=False): log.debug("Applied registry patch: %s with %s"%(fileName,regedit)) -OLEACC_REG_FILE_PATH = os.path.abspath(os.path.join("COMRegistrationFixes", "oleaccProxy.reg")) +OLEACC_REG_FILE_PATH = os.path.join(globalVars.appDir, "COMRegistrationFixes", "oleaccProxy.reg") def fixCOMRegistrations(): """ Registers most common COM proxies, in case they had accidentally been unregistered or overwritten by 3rd party software installs/uninstalls. diff --git a/source/inputCore.py b/source/inputCore.py index 1afaa1969f4..146b6189736 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -565,10 +565,10 @@ def loadLocaleGestureMap(self): self.localeGestureMap.clear() lang = languageHandler.getLanguage() try: - self.localeGestureMap.load(os.path.join("locale", lang, "gestures.ini")) + self.localeGestureMap.load(os.path.join(globalVars.appDir, "locale", lang, "gestures.ini")) except IOError: try: - self.localeGestureMap.load(os.path.join("locale", lang.split('_')[0], "gestures.ini")) + self.localeGestureMap.load(os.path.join(globalVars.appDir, "locale", lang.split('_')[0], "gestures.ini")) except IOError: log.debugWarning("No locale gesture map for language %s" % lang) From 879960cbb2a18d5e0d28f4455bf784629ee3ed1b Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Tue, 22 Sep 2020 21:31:44 +1000 Subject: [PATCH 06/25] nvda.pyw: provide an absolute path to gettext.translate --- source/nvda.pyw | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/nvda.pyw b/source/nvda.pyw index 8fd724cd317..7e8c9bac7bf 100755 --- a/source/nvda.pyw +++ b/source/nvda.pyw @@ -30,7 +30,11 @@ import locale import gettext try: - gettext.translation('nvda',localedir='locale',languages=[locale.getdefaultlocale()[0]]).install(True) + gettext.translation( + 'nvda', + localedir=os.path.join(globalVars.appDir, 'locale'), + languages=[locale.getdefaultlocale()[0]] + ).install(True) except: gettext.install('nvda') From 8bcb48192ca9be880eed559fb55965e26182ef13 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Tue, 22 Sep 2020 21:32:10 +1000 Subject: [PATCH 07/25] nvda_slave: set globalVars.appDir, and provide an absolute path to gettext.translate --- source/nvda_slave.pyw | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/source/nvda_slave.pyw b/source/nvda_slave.pyw index b011e16050b..513b7967f7b 100755 --- a/source/nvda_slave.pyw +++ b/source/nvda_slave.pyw @@ -16,23 +16,37 @@ import comtypes.gen import comInterfaces comtypes.gen.__path__.append(comInterfaces.__path__[0]) +import sys +import os +import globalVars + +if hasattr(sys, "frozen"): + # Error messages (which are only for debugging) should not cause the py2exe log message box to appear. + sys.stderr = sys.stdout + globalVars.appDir = sys.prefix +else: + globalVars.appDir = os.path.abspath(os.path.dirname(__file__)) + +# #2391: some functions may still require the current directory to be set to NVDA's app dir +os.chdir(globalVars.appDir) + + import gettext import locale #Localization settings try: - gettext.translation('nvda',localedir='locale',languages=[locale.getdefaultlocale()[0]]).install() + gettext.translation( + 'nvda', + localedir=os.path.join(globalVars.appDir, 'locale'), + languages=[locale.getdefaultlocale()[0]] + ).install() except: gettext.install('nvda') -import sys -import os + import versionInfo import logHandler -if hasattr(sys, "frozen"): - # Error messages (which are only for debugging) should not cause the py2exe log message box to appear. - sys.stderr = sys.stdout - #Many functions expect the current directory to be where slave is located (#2391) - os.chdir(sys.prefix) + def main(): import installer From e8f840ddda6cb3d09e20f572d92f60a0e6e4b6b1 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Tue, 22 Sep 2020 23:14:19 +1000 Subject: [PATCH 08/25] getDefaultLogFilePath no longer uses the current directory. --- source/logHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/logHandler.py b/source/logHandler.py index 6d9612d6142..289d968c909 100755 --- a/source/logHandler.py +++ b/source/logHandler.py @@ -333,7 +333,7 @@ def _getDefaultLogFilePath(): import tempfile return os.path.join(tempfile.gettempdir(), "nvda.log") else: - return ".\\nvda.log" + return os.path.join(globalVars.appDir, "nvda.log") def _excepthook(*exc_info): log.exception(exc_info=exc_info, codepath="unhandled exception") From fb4affc57913a7c4dec997515240df12589a083d Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Tue, 22 Sep 2020 23:14:47 +1000 Subject: [PATCH 09/25] brailleTables: TABLES_DIR is no longer relative to the current directory. --- source/brailleTables.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/brailleTables.py b/source/brailleTables.py index 010bf6aa380..2b9e5203222 100644 --- a/source/brailleTables.py +++ b/source/brailleTables.py @@ -6,11 +6,14 @@ """Manages information about available braille translation tables. """ +import os import collections from locale import strxfrm +import globalVars + #: The directory in which liblouis braille tables are located. -TABLES_DIR = r"louis\tables" +TABLES_DIR = os.path.join(globalVars.appDir, "louis", "tables") #: Information about a braille table. #: This has the following attributes: From dcecdf295ac48139a242e9ff31e19d9ca1e8e6b1 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Tue, 22 Sep 2020 23:16:52 +1000 Subject: [PATCH 10/25] ui.browsableMessage no longer uses a relative path to get to the html file. --- source/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/ui.py b/source/ui.py index af6b7a80d99..947431d96aa 100644 --- a/source/ui.py +++ b/source/ui.py @@ -18,6 +18,7 @@ import gui import speech import braille +import globalVars from typing import Optional @@ -45,7 +46,7 @@ def browseableMessage(message,title=None,isHtml=False): @param isHtml: Whether the message is html @type isHtml: boolean """ - htmlFileName = os.path.realpath( u'message.html' ) + htmlFileName = os.path.join(globalVars.appDir, 'message.html') if not os.path.isfile(htmlFileName ): raise LookupError(htmlFileName ) moniker = POINTER(IUnknown)() From 102c68e776b092482f08182032ea955f46b81055 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 06:50:09 +1000 Subject: [PATCH 11/25] Change all playWavefile calls to be non-relative --- source/NVDAObjects/__init__.py | 5 ++++- source/NVDAObjects/behaviors.py | 6 ++++-- source/core.py | 12 +++++++++--- source/logHandler.py | 2 +- source/visionEnhancementProviders/screenCurtain.py | 6 ++++-- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index f0fff359c34..96237882e3c 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -8,6 +8,7 @@ """Module that contains the base NVDA object type with dynamic class creation support, as well as the associated TextInfo class.""" +import os import time import re import weakref @@ -31,6 +32,8 @@ import brailleInput import locationHelper import aria +import globalVars + class NVDAObjectTextInfo(textInfos.offsets.OffsetsTextInfo): """A default TextInfo which is used to enable text review of information about widgets that don't support text content. @@ -1030,7 +1033,7 @@ def _reportErrorInPreviousWord(self): # No error. return import nvwave - nvwave.playWaveFile(r"waves\textError.wav") + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "textError.wav")) def event_liveRegionChange(self): """ diff --git a/source/NVDAObjects/behaviors.py b/source/NVDAObjects/behaviors.py index a3dfbe9a549..e09d93ec28e 100755 --- a/source/NVDAObjects/behaviors.py +++ b/source/NVDAObjects/behaviors.py @@ -29,6 +29,8 @@ import ui import braille import nvwave +import globalVars + class ProgressBar(NVDAObject): @@ -796,7 +798,7 @@ def event_suggestionsOpened(self): # Translators: Announced in braille when suggestions appear when search term is entered in various search fields such as Start search box in Windows 10. braille.handler.message(_("Suggestions")) if config.conf["presentation"]["reportAutoSuggestionsWithSound"]: - nvwave.playWaveFile(r"waves\suggestionsOpened.wav") + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "suggestionsOpened.wav")) def event_suggestionsClosed(self): """Called when suggestions list or container is closed. @@ -804,7 +806,7 @@ def event_suggestionsClosed(self): By default NVDA will announce this via speech, braille or via a sound. """ if config.conf["presentation"]["reportAutoSuggestionsWithSound"]: - nvwave.playWaveFile(r"waves\suggestionsClosed.wav") + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "suggestionsClosed.wav")) class WebDialog(NVDAObject): """ diff --git a/source/core.py b/source/core.py index a2f1e3ac779..8d9c8dedff2 100644 --- a/source/core.py +++ b/source/core.py @@ -232,7 +232,7 @@ def main(): log.info("Developer Scratchpad mode enabled") if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]: try: - nvwave.playWaveFile("waves\\start.wav") + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "start.wav")) except: pass logHandler.setLogLevelFromConfig() @@ -298,7 +298,10 @@ def onEndSession(evt): speech.cancelSpeech() if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]: try: - nvwave.playWaveFile("waves\\exit.wav",asynchronous=False) + nvwave.playWaveFile( + os.path.join(globalVars.appDir, "waves", "exit.wav"), + asynchronous=False + ) except: pass log.info("Windows session ending") @@ -591,7 +594,10 @@ def run(self): if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]: try: - nvwave.playWaveFile("waves\\exit.wav",asynchronous=False) + nvwave.playWaveFile( + os.path.join(globalVars.appDir, "waves", "exit.wav"), + asynchronous=False + ) except: pass # #5189: Destroy the message window as late as possible diff --git a/source/logHandler.py b/source/logHandler.py index 289d968c909..064fc8aad05 100755 --- a/source/logHandler.py +++ b/source/logHandler.py @@ -276,7 +276,7 @@ def handle(self,record): elif record.levelno>=logging.ERROR and shouldPlayErrorSound: import nvwave try: - nvwave.playWaveFile("waves\\error.wav") + nvwave.playWaveFile("os.path.join(globalVars.appDir, "waves", "error.wav")) except: pass return super().handle(record) diff --git a/source/visionEnhancementProviders/screenCurtain.py b/source/visionEnhancementProviders/screenCurtain.py index ab77249ac68..e60a9cc3941 100644 --- a/source/visionEnhancementProviders/screenCurtain.py +++ b/source/visionEnhancementProviders/screenCurtain.py @@ -7,6 +7,7 @@ This implementation only works on Windows 8 and above. """ +import os import vision from vision import providerBase import winVersion @@ -19,6 +20,7 @@ from logHandler import log from typing import Optional, Type import nvwave +import globalVars class MAGCOLOREFFECT(Structure): @@ -325,7 +327,7 @@ def __init__(self): raise e if self.getSettings().playToggleSounds: try: - nvwave.playWaveFile(r"waves\screenCurtainOn.wav") + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "screenCurtainOn.wav")) except Exception: log.exception() @@ -338,7 +340,7 @@ def terminate(self): Magnification.MagUninitialize() if self.getSettings().playToggleSounds: try: - nvwave.playWaveFile(r"waves\screenCurtainOff.wav") + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "screenCurtainOff.wav")) except Exception: log.exception() From 1da2d23b9cfa44b5b87ca463facdbd900adac482 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 07:02:09 +1000 Subject: [PATCH 12/25] Fix linting issues --- source/logHandler.py | 2 +- source/nvda_slave.pyw | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/logHandler.py b/source/logHandler.py index 064fc8aad05..6fa34b52a6f 100755 --- a/source/logHandler.py +++ b/source/logHandler.py @@ -276,7 +276,7 @@ def handle(self,record): elif record.levelno>=logging.ERROR and shouldPlayErrorSound: import nvwave try: - nvwave.playWaveFile("os.path.join(globalVars.appDir, "waves", "error.wav")) + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "error.wav")) except: pass return super().handle(record) diff --git a/source/nvda_slave.pyw b/source/nvda_slave.pyw index 513b7967f7b..02aa7349f35 100755 --- a/source/nvda_slave.pyw +++ b/source/nvda_slave.pyw @@ -8,6 +8,11 @@ Performs miscellaneous tasks which need to be performed in a separate process. """ +import sys +import os +import globalVars + + # Initialise comtypes.client.gen_dir and the comtypes.gen search path # and Append our comInterfaces directory to the comtypes.gen search path. import comtypes @@ -16,9 +21,6 @@ import comtypes.gen import comInterfaces comtypes.gen.__path__.append(comInterfaces.__path__[0]) -import sys -import os -import globalVars if hasattr(sys, "frozen"): # Error messages (which are only for debugging) should not cause the py2exe log message box to appear. From b1850be31e15dfe456a3f7c7dc462c29b452d4ae Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 08:03:54 +1000 Subject: [PATCH 13/25] another relative wave file path --- source/browseMode.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/browseMode.py b/source/browseMode.py index 3f377057152..84697f3e30f 100644 --- a/source/browseMode.py +++ b/source/browseMode.py @@ -3,6 +3,7 @@ # This file is covered by the GNU General Public License. # See the file COPYING for more details. +import os import itertools import collections import winsound @@ -39,8 +40,10 @@ from NVDAObjects import NVDAObject import gui.contextHelp from abc import ABCMeta, abstractmethod +import globalVars from typing import Optional + REASON_QUICKNAV = OutputReason.QUICKNAV def reportPassThrough(treeInterceptor,onlyIfChanged=True): @@ -52,8 +55,8 @@ def reportPassThrough(treeInterceptor,onlyIfChanged=True): """ if not onlyIfChanged or treeInterceptor.passThrough != reportPassThrough.last: if config.conf["virtualBuffers"]["passThroughAudioIndication"]: - sound = r"waves\focusMode.wav" if treeInterceptor.passThrough else r"waves\browseMode.wav" - nvwave.playWaveFile(sound) + sound = "focusMode.wav" if treeInterceptor.passThrough else "browseMode.wav" + nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", sound)) else: if treeInterceptor.passThrough: # Translators: The mode to interact with controls in documents From d5501bf8914890b2926bcc1de605864bc2b66b15 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 08:04:46 +1000 Subject: [PATCH 14/25] Fix linting issues --- source/config/__init__.py | 2 +- source/ui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/config/__init__.py b/source/config/__init__.py index 4481c31099a..e6709e3ef2c 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -83,7 +83,7 @@ def isInstalledCopy(): return False winreg.CloseKey(k) try: - return os.stat(instDir) == os.stat(globalVars.appDir) + return os.stat(instDir) == os.stat(globalVars.appDir) except WindowsError: return False diff --git a/source/ui.py b/source/ui.py index 947431d96aa..48661e1231e 100644 --- a/source/ui.py +++ b/source/ui.py @@ -46,7 +46,7 @@ def browseableMessage(message,title=None,isHtml=False): @param isHtml: Whether the message is html @type isHtml: boolean """ - htmlFileName = os.path.join(globalVars.appDir, 'message.html') + htmlFileName = os.path.join(globalVars.appDir, 'message.html') if not os.path.isfile(htmlFileName ): raise LookupError(htmlFileName ) moniker = POINTER(IUnknown)() From 631782d4d84329dffb85fba8368a64843a9165f8 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 21:18:04 +1000 Subject: [PATCH 15/25] speechDictHandler: the path to builtin.dic is no longer relative. --- source/speechDictHandler/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/speechDictHandler/__init__.py b/source/speechDictHandler/__init__.py index e4ba411f5ee..9b2e979173d 100644 --- a/source/speechDictHandler/__init__.py +++ b/source/speechDictHandler/__init__.py @@ -123,7 +123,7 @@ def initialize(): for type in dictTypes: dictionaries[type]=SpeechDict() dictionaries["default"].load(os.path.join(speechDictsPath, "default.dic")) - dictionaries["builtin"].load("builtin.dic") + dictionaries["builtin"].load(os.path.join(globalVars.appDir, "builtin.dic")) def loadVoiceDict(synth): """Loads appropriate dictionary for the given synthesizer. From 8ad7bfc83155c8f693166f7661381f5d643489bc Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 21:32:09 +1000 Subject: [PATCH 16/25] config: slave_fileName is no longer relative --- source/config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/__init__.py b/source/config/__init__.py index e6709e3ef2c..724e42909c7 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -227,7 +227,7 @@ def canStartOnSecureScreens(): # This function will be transformed into a flag in a future release. return isInstalledCopy() -SLAVE_FILENAME = u"nvda_slave.exe" +SLAVE_FILENAME = os.path.join(globalVars.appDir, "nvda_slave.exe") #: The name of the registry key stored under HKEY_LOCAL_MACHINE where system wide NVDA settings are stored. #: Note that NVDA is a 32-bit application, so on X64 systems, this will evaluate to "SOFTWARE\WOW6432Node\nvda" From bc49fe737d4835c19c28693db7481cff442c4abf Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 21:32:42 +1000 Subject: [PATCH 17/25] Lilli braille driver: path to dll is no longer relative. --- source/brailleDisplayDrivers/lilli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/lilli.py b/source/brailleDisplayDrivers/lilli.py index 9158ec304ab..7cafe38967e 100644 --- a/source/brailleDisplayDrivers/lilli.py +++ b/source/brailleDisplayDrivers/lilli.py @@ -5,6 +5,8 @@ #Copyright (C) 2008-2017 NV Access Limited, Gianluca Casalino, Alberto Benassati, Babbage B.V. from typing import Optional, List +import os +import globalVars from logHandler import log from ctypes import * import inputCore @@ -12,7 +14,7 @@ import braille try: - lilliDll=windll.LoadLibrary("brailleDisplayDrivers\\lilli.dll") + lilliDll=windll.LoadLibrary(os.path.join(globalVars.appDir, "brailleDisplayDrivers", "lilli.dll")) except: lilliDll=None From 0413ad0edfc8d55be66015696c8911787c28954b Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 23 Sep 2020 21:37:11 +1000 Subject: [PATCH 18/25] Fix linting issues --- source/brailleDisplayDrivers/lilli.py | 3 ++- source/config/__init__.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/lilli.py b/source/brailleDisplayDrivers/lilli.py index 7cafe38967e..3c497ab8c30 100644 --- a/source/brailleDisplayDrivers/lilli.py +++ b/source/brailleDisplayDrivers/lilli.py @@ -8,13 +8,14 @@ import os import globalVars from logHandler import log +from ctypes import windll from ctypes import * import inputCore import wx import braille try: - lilliDll=windll.LoadLibrary(os.path.join(globalVars.appDir, "brailleDisplayDrivers", "lilli.dll")) + lilliDll = windll.LoadLibrary(os.path.join(globalVars.appDir, "brailleDisplayDrivers", "lilli.dll")) except: lilliDll=None diff --git a/source/config/__init__.py b/source/config/__init__.py index 724e42909c7..24cd5846a56 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -227,6 +227,7 @@ def canStartOnSecureScreens(): # This function will be transformed into a flag in a future release. return isInstalledCopy() + SLAVE_FILENAME = os.path.join(globalVars.appDir, "nvda_slave.exe") #: The name of the registry key stored under HKEY_LOCAL_MACHINE where system wide NVDA settings are stored. From bf4b1b808bf72abdf8d62ec31d99177612286eb4 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Thu, 24 Sep 2020 13:07:50 +1000 Subject: [PATCH 19/25] nvda_slave: don't load nvdaRemote with a relative path. --- source/nvda_slave.pyw | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/nvda_slave.pyw b/source/nvda_slave.pyw index 02aa7349f35..aeb18e639bb 100755 --- a/source/nvda_slave.pyw +++ b/source/nvda_slave.pyw @@ -92,7 +92,11 @@ def main(): raise ValueError("Addon path was not provided.") #Load nvdaHelperRemote.dll but with an altered search path so it can pick up other dlls in lib import ctypes - h=ctypes.windll.kernel32.LoadLibraryExW(os.path.abspath(os.path.join(u"lib",versionInfo.version,u"nvdaHelperRemote.dll")),0,0x8) + h = ctypes.windll.kernel32.LoadLibraryExW( + os.path.join(globalVars.appDir, "lib", versionInfo.version, "nvdaHelperRemote.dll"), + 0, + 0x8 + ) remoteLib=ctypes.WinDLL("nvdaHelperRemote",handle=h) ret = remoteLib.nvdaControllerInternal_installAddonPackageFromPath(addonPath) if ret != 0: From 3be773c7b1377585a761a3e9d64ebb9e88f13106 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Thu, 24 Sep 2020 13:49:26 +1000 Subject: [PATCH 20/25] Remove all usage of os.path.abspath, but add a couple of assertions in places where we can't be completely sure the path is absolute. --- source/NVDAHelper.py | 7 ++++++- source/addonHandler/__init__.py | 17 +++++++++-------- source/config/__init__.py | 2 +- source/core.py | 2 +- source/gui/installerGui.py | 13 +++++++++++-- source/installer.py | 4 ++-- source/systemUtils.py | 2 +- source/updateCheck.py | 2 +- source/watchdog.py | 2 +- 9 files changed, 33 insertions(+), 18 deletions(-) diff --git a/source/NVDAHelper.py b/source/NVDAHelper.py index 4bd712c3af2..0d833ffbbbb 100755 --- a/source/NVDAHelper.py +++ b/source/NVDAHelper.py @@ -17,6 +17,7 @@ WINFUNCTYPE, c_long, c_wchar, + windll, ) from ctypes.wintypes import * from comtypes import BSTR @@ -511,7 +512,11 @@ def initialize(): log.info("Remote injection disabled due to running as a Windows Store Application") return #Load nvdaHelperRemote.dll but with an altered search path so it can pick up other dlls in lib - h=windll.kernel32.LoadLibraryExW(os.path.abspath(os.path.join(versionedLibPath,u"nvdaHelperRemote.dll")),0,0x8) + h = windll.kernel32.LoadLibraryExW( + os.path.join(versionedLibPath, "nvdaHelperRemote.dll"), + 0, + 0x8 + ) if not h: log.critical("Error loading nvdaHelperRemote.dll: %s" % WinError()) return diff --git a/source/addonHandler/__init__.py b/source/addonHandler/__init__.py index 48f127ccf12..d4e1aded665 100644 --- a/source/addonHandler/__init__.py +++ b/source/addonHandler/__init__.py @@ -97,7 +97,7 @@ def getIncompatibleAddons( def completePendingAddonRemoves(): """Removes any add-ons that could not be removed on the last run of NVDA""" - user_addons = os.path.abspath(os.path.join(globalVars.appArgs.configPath, "addons")) + user_addons = os.path.join(globalVars.appArgs.configPath, "addons") pendingRemovesSet=state['pendingRemovesSet'] for addonName in list(pendingRemovesSet): addonPath=os.path.join(user_addons,addonName) @@ -111,7 +111,7 @@ def completePendingAddonRemoves(): pendingRemovesSet.discard(addonName) def completePendingAddonInstalls(): - user_addons = os.path.abspath(os.path.join(globalVars.appArgs.configPath, "addons")) + user_addons = os.path.join(globalVars.appArgs.configPath, "addons") pendingInstallsSet=state['pendingInstallsSet'] for addonName in pendingInstallsSet: newPath=os.path.join(user_addons,addonName) @@ -123,7 +123,7 @@ def completePendingAddonInstalls(): pendingInstallsSet.clear() def removeFailedDeletions(): - user_addons = os.path.abspath(os.path.join(globalVars.appArgs.configPath, "addons")) + user_addons = os.path.join(globalVars.appArgs.configPath, "addons") for p in os.listdir(user_addons): if p.endswith(DELETEDIR_SUFFIX): path=os.path.join(user_addons,p) @@ -170,7 +170,7 @@ def _getDefaultAddonPaths(): @rtype: list(string) """ addon_paths = [] - user_addons = os.path.abspath(os.path.join(globalVars.appArgs.configPath, "addons")) + user_addons = os.path.join(globalVars.appArgs.configPath, "addons") if os.path.isdir(user_addons): addon_paths.append(user_addons) return addon_paths @@ -280,7 +280,7 @@ def __init__(self, path): @param path: the base directory for the addon data. @type path: string """ - self.path = os.path.abspath(path) + self.path = path self._extendedPackages = set() manifest_path = os.path.join(path, MANIFEST_FILENAME) with open(manifest_path, 'rb') as f: @@ -511,7 +511,8 @@ def getCodeAddon(obj=None, frameDist=1): if obj is None: obj = sys._getframe(frameDist) fileName = inspect.getfile(obj) - dir= os.path.abspath(os.path.dirname(fileName)) + assert os.path.isabs(fileName), f"Module file name {fileName} is not absolute" + dir = os.path.dirname(fileName) # if fileName is not a subdir of one of the addon paths # It does not belong to an addon. for p in _getDefaultAddonPaths(): @@ -523,7 +524,7 @@ def getCodeAddon(obj=None, frameDist=1): while curdir not in _getDefaultAddonPaths(): if curdir in _availableAddons: return _availableAddons[curdir] - curdir = os.path.abspath(os.path.join(curdir, "..")) + curdir = os.path.join(curdir, "..") # Not found! raise AddonError("Code does not belong to an addon") @@ -609,7 +610,7 @@ def __repr__(self): def createAddonBundleFromPath(path, destDir=None): """ Creates a bundle from a directory that contains a a addon manifest file.""" - basedir = os.path.abspath(path) + basedir = path # If caller did not provide a destination directory name # Put the bundle at the same level as the add-on's top-level directory, # That is, basedir/.. diff --git a/source/config/__init__.py b/source/config/__init__.py index 24cd5846a56..55c689897e5 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -253,7 +253,7 @@ def _setStartOnLogonScreen(enable): winreg.SetValueEx(k, u"startOnLogonScreen", None, winreg.REG_DWORD, int(enable)) def setSystemConfigToCurrentConfig(): - fromPath=os.path.abspath(globalVars.appArgs.configPath) + fromPath = globalVars.appArgs.configPath if ctypes.windll.shell32.IsUserAnAdmin(): _setSystemConfig(fromPath) else: diff --git a/source/core.py b/source/core.py index 8d9c8dedff2..14efe569747 100644 --- a/source/core.py +++ b/source/core.py @@ -224,7 +224,7 @@ def main(): globalVars.appArgs.configPath=config.getUserDefaultConfigPath(useInstalledPathIfExists=globalVars.appArgs.launcher) #Initialize the config path (make sure it exists) config.initConfigPath() - log.info("Config dir: %s"%os.path.abspath(globalVars.appArgs.configPath)) + log.info(f"Config dir: {globalVars.appArgs.configPath}") log.debug("loading config") import config config.initialize() diff --git a/source/gui/installerGui.py b/source/gui/installerGui.py index 3faeae27424..4f94213ba29 100644 --- a/source/gui/installerGui.py +++ b/source/gui/installerGui.py @@ -307,7 +307,7 @@ def __init__(self, parent): directoryEntryControl = groupHelper.addItem(gui.guiHelper.PathSelectionHelper(self, browseText, dirDialogTitle)) self.portableDirectoryEdit = directoryEntryControl.pathControl if globalVars.appArgs.portablePath: - self.portableDirectoryEdit.Value = os.path.abspath(globalVars.appArgs.portablePath) + self.portableDirectoryEdit.Value = globalVars.appArgs.portablePath # Translators: The label of a checkbox option in the Create Portable NVDA dialog. copyConfText = _("Copy current &user configuration") @@ -343,6 +343,15 @@ def onCreatePortable(self, evt): _("Error"), wx.OK | wx.ICON_ERROR) return + if not os.path.isabs(self.portableDirectoryEdit.Value): + # Translators: The message displayed when the user has not specified an absolute destination directory + # in the Create Portable NVDA dialog. + gui.messageBox( + _("Please specify an absolute path (including drive letter) in which to create the portable copy."), + _("Error"), + wx.OK | wx.ICON_ERROR + ) + return drv=os.path.splitdrive(self.portableDirectoryEdit.Value)[0] if drv and not os.path.isdir(drv): # Translators: The message displayed when the user specifies an invalid destination drive @@ -395,7 +404,7 @@ def doCreatePortable(portableDirectory,copyUserConfig=False,silent=False,startAf shellapi.ShellExecute( None, None, - os.path.join(os.path.abspath(portableDirectory),'nvda.exe'), + os.path.join(portableDirectory, 'nvda.exe'), None, None, winUser.SW_SHOWNORMAL diff --git a/source/installer.py b/source/installer.py index fd50bceee4f..f1ca65e4277 100644 --- a/source/installer.py +++ b/source/installer.py @@ -140,7 +140,7 @@ def copyProgramFiles(destPath): tryCopyFile(sourceFilePath,destFilePath) def copyUserConfig(destPath): - sourcePath=os.path.abspath(globalVars.appArgs.configPath) + sourcePath = globalVars.appArgs.configPath for curSourceDir,subDirs,files in os.walk(sourcePath): curDestDir=os.path.join(destPath,os.path.relpath(curSourceDir,sourcePath)) if not os.path.isdir(curDestDir): @@ -598,7 +598,7 @@ def removeOldLoggedFiles(installPath): tryRemoveFile(filePath,rebootOK=True) def createPortableCopy(destPath,shouldCopyUserConfig=True): - destPath=os.path.abspath(destPath) + assert os.path.isabs(destPath), f"Destination path {destPath} is not absolute" #Remove all the main executables always for f in ("nvda.exe","nvda_noUIAccess.exe","nvda_UIAccess.exe"): f=os.path.join(destPath,f) diff --git a/source/systemUtils.py b/source/systemUtils.py index 672885f7ccf..ae73144c638 100644 --- a/source/systemUtils.py +++ b/source/systemUtils.py @@ -54,7 +54,7 @@ def execElevated(path, params=None, wait=False, handleAlreadyElevated=False): import subprocess if params is not None: params = subprocess.list2cmdline(params) - sei = shellapi.SHELLEXECUTEINFO(lpFile=os.path.abspath(path), lpParameters=params, nShow=winUser.SW_HIDE) + sei = shellapi.SHELLEXECUTEINFO(lpFile=path, lpParameters=params, nShow=winUser.SW_HIDE) # IsUserAnAdmin is apparently deprecated so may not work above Windows 8 if not handleAlreadyElevated or not ctypes.windll.shell32.IsUserAnAdmin(): sei.lpVerb = "runas" diff --git a/source/updateCheck.py b/source/updateCheck.py index 20c787959f5..cb688ca172b 100644 --- a/source/updateCheck.py +++ b/source/updateCheck.py @@ -208,7 +208,7 @@ def _executeUpdate(destPath): if os.access(portablePath, os.W_OK): executeParams = u'--create-portable --portable-path "{portablePath}" --config-path "{configPath}" -m'.format( portablePath=portablePath, - configPath=os.path.abspath(globalVars.appArgs.configPath) + configPath=globalVars.appArgs.configPath ) else: executeParams = u"--launcher" diff --git a/source/watchdog.py b/source/watchdog.py index 6a8f1ff9588..7269ec9a953 100644 --- a/source/watchdog.py +++ b/source/watchdog.py @@ -187,7 +187,7 @@ def _crashHandler(exceptionInfo): ctypes.pythonapi.PyThreadState_SetAsyncExc(threadId, None) # Write a minidump. - dumpPath = os.path.abspath(os.path.join(globalVars.appArgs.logFileName, "..", "nvda_crash.dmp")) + dumpPath = os.path.join(globalVars.appArgs.logFileName, "..", "nvda_crash.dmp") try: # Though we aren't using pythonic functions to write to the dump file, # open it in binary mode as opening it in text mode (the default) doesn't make sense. From dcbbeac8a2527d5d99b948124b2aac035fe97d8a Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Thu, 24 Sep 2020 14:11:59 +1000 Subject: [PATCH 21/25] Fix translation comments --- source/gui/installerGui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/gui/installerGui.py b/source/gui/installerGui.py index 4f94213ba29..fc836c18220 100644 --- a/source/gui/installerGui.py +++ b/source/gui/installerGui.py @@ -344,10 +344,13 @@ def onCreatePortable(self, evt): wx.OK | wx.ICON_ERROR) return if not os.path.isabs(self.portableDirectoryEdit.Value): - # Translators: The message displayed when the user has not specified an absolute destination directory - # in the Create Portable NVDA dialog. gui.messageBox( + # Translators: The message displayed when the user has not specified an absolute destination directory + # in the Create Portable NVDA dialog. _("Please specify an absolute path (including drive letter) in which to create the portable copy."), + # Translators: The message title displayed + # when the user has not specified an absolute destination directory + # in the Create Portable NVDA dialog. _("Error"), wx.OK | wx.ICON_ERROR ) From 3c0f7414f7068d2a2b6b1273ea3c19ae8c7237e0 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Fri, 25 Sep 2020 10:36:17 +1000 Subject: [PATCH 22/25] Add the ALTERED_LIBRARY_SEARCH_PATH constant to winKernel and use it in NVDAHelper and nvda_slave when loading NvDAHelperRemote. --- source/NVDAHelper.py | 7 +++++-- source/nvda_slave.pyw | 6 +++++- source/winKernel.py | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/source/NVDAHelper.py b/source/NVDAHelper.py index 0d833ffbbbb..3ab07dea725 100755 --- a/source/NVDAHelper.py +++ b/source/NVDAHelper.py @@ -511,11 +511,14 @@ def initialize(): if config.isAppX: log.info("Remote injection disabled due to running as a Windows Store Application") return - #Load nvdaHelperRemote.dll but with an altered search path so it can pick up other dlls in lib + # Load nvdaHelperRemote.dll h = windll.kernel32.LoadLibraryExW( os.path.join(versionedLibPath, "nvdaHelperRemote.dll"), 0, - 0x8 + # Using an altered search path is necessary here + # As NVDAHelperRemote needs to locate dependent dlls in the same directory + # such as minhook.dll. + winKernel.LOAD_WITH_ALTERED_SEARCH_PATH ) if not h: log.critical("Error loading nvdaHelperRemote.dll: %s" % WinError()) diff --git a/source/nvda_slave.pyw b/source/nvda_slave.pyw index aeb18e639bb..81412162954 100755 --- a/source/nvda_slave.pyw +++ b/source/nvda_slave.pyw @@ -11,6 +11,7 @@ Performs miscellaneous tasks which need to be performed in a separate process. import sys import os import globalVars +import winKernel # Initialise comtypes.client.gen_dir and the comtypes.gen search path @@ -95,7 +96,10 @@ def main(): h = ctypes.windll.kernel32.LoadLibraryExW( os.path.join(globalVars.appDir, "lib", versionInfo.version, "nvdaHelperRemote.dll"), 0, - 0x8 + # Using an altered search path is necessary here + # As NVDAHelperRemote needs to locate dependent dlls in the same directory + # such as minhook.dll. + winKernel.LOAD_WITH_ALTERED_SEARCH_PATH ) remoteLib=ctypes.WinDLL("nvdaHelperRemote",handle=h) ret = remoteLib.nvdaControllerInternal_installAddonPackageFromPath(addonPath) diff --git a/source/winKernel.py b/source/winKernel.py index 5dfda1880af..8645d295103 100644 --- a/source/winKernel.py +++ b/source/winKernel.py @@ -48,6 +48,9 @@ WAIT_FAILED = 0xffffffff # Image file machine constants IMAGE_FILE_MACHINE_UNKNOWN = 0 +# LoadLibraryEx constants +LOAD_WITH_ALTERED_SEARCH_PATH = 0x8 + def GetStdHandle(handleID): h=kernel32.GetStdHandle(handleID) From 6000aecf38cea7c000a2352f2b9b49039a9b4f49 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Fri, 25 Sep 2020 13:44:15 +1000 Subject: [PATCH 23/25] Lili braille dirver: remove unneeded import. --- source/brailleDisplayDrivers/lilli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/brailleDisplayDrivers/lilli.py b/source/brailleDisplayDrivers/lilli.py index 3c497ab8c30..e94c85a1cd4 100644 --- a/source/brailleDisplayDrivers/lilli.py +++ b/source/brailleDisplayDrivers/lilli.py @@ -9,7 +9,6 @@ import globalVars from logHandler import log from ctypes import windll -from ctypes import * import inputCore import wx import braille From a9baa8ce782e7cd64680254f4c1f082cb11c9038 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 30 Sep 2020 08:35:52 +1000 Subject: [PATCH 24/25] Update what's new --- user_docs/en/changes.t2t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index fa0141223bc..26ea1c721b5 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -29,10 +29,12 @@ What's New in NVDA - When "attempt to cancel expired focus events" is enabled, the title of the tab is now announced again when switching tabs in Firefox. (#11397) - NVDA no longer fails to announce a list item after typing a character in a list when speaking with the SAPI5 Ivona voices. (#11651) - It is again possible to use browse mode when reading emails in Windows 10 Mail 16005.13110 and later. (#11439) +- When using the SAPI5 Ivona voices from harposoftware.com, NvDA is now able to save configuration, switch synthesizers, and no longer will stay silent after restarting. (#11650) == Changes for Developers == - System tests can now send keys using spy.emulateKeyPress, which takes a key identifier that conforms to NVDA's own key names, and by default also blocks until the action is executed. (#11581) +- NVDA no longer requires the current directory to be the NVDA application directory in order to function. (#6491) = 2020.3 = From 2ae3811a1c50edac9e97ab1c38a815d44e00f5f9 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 30 Sep 2020 12:56:12 +1000 Subject: [PATCH 25/25] addonHandler.getCodeAddon: make sure to normalize paths when comparing them to stop an infinite while loop introduced in #11650 --- source/addonHandler/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/source/addonHandler/__init__.py b/source/addonHandler/__init__.py index d4e1aded665..bf9bd6d4421 100644 --- a/source/addonHandler/__init__.py +++ b/source/addonHandler/__init__.py @@ -512,19 +512,22 @@ def getCodeAddon(obj=None, frameDist=1): obj = sys._getframe(frameDist) fileName = inspect.getfile(obj) assert os.path.isabs(fileName), f"Module file name {fileName} is not absolute" - dir = os.path.dirname(fileName) + dir = os.path.normpath(os.path.dirname(fileName)) # if fileName is not a subdir of one of the addon paths # It does not belong to an addon. - for p in _getDefaultAddonPaths(): - if dir.startswith(p): + addonsPath = None + for addonsPath in _getDefaultAddonPaths(): + addonsPath = os.path.normpath(addonsPath) + if dir.startswith(addonsPath): break else: raise AddonError("Code does not belong to an addon package.") + assert addonsPath is not None curdir = dir - while curdir not in _getDefaultAddonPaths(): + while curdir.startswith(addonsPath) and len(curdir) > len(addonsPath): if curdir in _availableAddons: return _availableAddons[curdir] - curdir = os.path.join(curdir, "..") + curdir = os.path.normpath(os.path.join(curdir, "..")) # Not found! raise AddonError("Code does not belong to an addon")