Skip to content

Commit

Permalink
Allow NVDA to still function if a component changes the current direc…
Browse files Browse the repository at this point in the history
…tory (#11707)

* 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.

* Use the NVDA app dir rather than the current directory for relative paths.

* Fix unit tests.

* Remove all usage of os.getcwd and replace it with globalVars.appDir

* Replace all remaining os.path.join("* calls with os.path.join(globalVars.appDir calls.

* nvda.pyw: provide an absolute path to gettext.translate

* nvda_slave: set globalVars.appDir, and provide an absolute path to gettext.translate

* getDefaultLogFilePath no longer uses the current directory.

* brailleTables: TABLES_DIR is no longer relative to the current directory.

* ui.browsableMessage no longer uses a relative path to get to the html file.

* Change all playWavefile calls to be non-relative

* Fix linting issues

* another relative wave file path

* Fix linting issues

* speechDictHandler: the path to builtin.dic is no longer relative.

* config: slave_fileName is no longer relative

* Lilli braille driver: path to dll is no longer relative.

* Fix linting issues

* nvda_slave: don't load nvdaRemote with a relative path.

* 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.

* Fix translation comments

* Add the ALTERED_LIBRARY_SEARCH_PATH constant to winKernel and use it in NVDAHelper and nvda_slave when loading NvDAHelperRemote.

* Lili  braille dirver: remove unneeded import.

* Update what's new

* addonHandler.getCodeAddon: make sure to normalize paths when comparing them to stop an infinite while loop  introduced in #11650
  • Loading branch information
michaelDCurran authored Sep 30, 2020
1 parent afb8fce commit 40e1d07
Show file tree
Hide file tree
Showing 29 changed files with 182 additions and 76 deletions.
3 changes: 2 additions & 1 deletion source/COMRegistrationFixes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import os
import subprocess
import winVersion
import globalVars
from logHandler import log

# Particular 64 bit / 32 bit system paths
Expand Down Expand Up @@ -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.
Expand Down
18 changes: 13 additions & 5 deletions source/NVDAHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
WINFUNCTYPE,
c_long,
c_wchar,
windll,
)
from ctypes.wintypes import *
from comtypes import BSTR
Expand All @@ -29,11 +30,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)
Expand Down Expand Up @@ -510,8 +511,15 @@ 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
h=windll.kernel32.LoadLibraryExW(os.path.abspath(os.path.join(versionedLibPath,u"nvdaHelperRemote.dll")),0,0x8)
# Load nvdaHelperRemote.dll
h = windll.kernel32.LoadLibraryExW(
os.path.join(versionedLibPath, "nvdaHelperRemote.dll"),
0,
# 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())
return
Expand Down
5 changes: 4 additions & 1 deletion source/NVDAObjects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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):
"""
Expand Down
6 changes: 4 additions & 2 deletions source/NVDAObjects/behaviors.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import ui
import braille
import nvwave
import globalVars


class ProgressBar(NVDAObject):

Expand Down Expand Up @@ -796,15 +798,15 @@ 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.
Subclasses should provide custom implementations if possible.
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):
"""
Expand Down
26 changes: 15 additions & 11 deletions source/addonHandler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -511,19 +511,23 @@ 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.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.abspath(os.path.join(curdir, ".."))
curdir = os.path.normpath(os.path.join(curdir, ".."))
# Not found!
raise AddonError("Code does not belong to an addon")

Expand Down Expand Up @@ -609,7 +613,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/..
Expand Down
6 changes: 4 additions & 2 deletions source/brailleDisplayDrivers/lilli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
#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 *
from ctypes import windll
import inputCore
import wx
import braille

try:
lilliDll=windll.LoadLibrary("brailleDisplayDrivers\\lilli.dll")
lilliDll = windll.LoadLibrary(os.path.join(globalVars.appDir, "brailleDisplayDrivers", "lilli.dll"))
except:
lilliDll=None

Expand Down
5 changes: 4 additions & 1 deletion source/brailleTables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 5 additions & 2 deletions source/browseMode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down
10 changes: 6 additions & 4 deletions source/characterProcessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 5 additions & 4 deletions source/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -227,7 +227,8 @@ 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"
Expand All @@ -252,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:
Expand Down
16 changes: 11 additions & 5 deletions source/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,15 +224,15 @@ 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()
if config.conf['development']['enableScratchpadDir']:
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()
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -410,7 +413,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.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions source/fonts/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand Down
Loading

0 comments on commit 40e1d07

Please sign in to comment.