Skip to content

Commit b7da7b6

Browse files
authored
Merge fde810b into 9092227
2 parents 9092227 + fde810b commit b7da7b6

3 files changed

Lines changed: 286 additions & 81 deletions

File tree

source/config/__init__.py

Lines changed: 196 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2006-2021 NV Access Limited, Aleksey Sadovoy, Peter Vágner, Rui Batista, Zahari Yurukov,
2+
# Copyright (C) 2006-2022 NV Access Limited, Aleksey Sadovoy, Peter Vágner, Rui Batista, Zahari Yurukov,
33
# Joseph Lee, Babbage B.V., Łukasz Golonka, Julien Cochuyt
44
# This file is covered by the GNU General Public License.
55
# See the file COPYING for more details.
@@ -10,6 +10,7 @@
1010
For the latter two actions, one can perform actions prior to and/or after they take place.
1111
"""
1212

13+
from enum import Enum
1314
import globalVars
1415
import winreg
1516
import ctypes
@@ -36,11 +37,10 @@
3637
from typing import Any, Dict, List, Optional, Set
3738

3839
#: True if NVDA is running as a Windows Store Desktop Bridge application
39-
isAppX=False
40+
isAppX: bool = False
4041

4142
#: The active configuration, C{None} if it has not yet been loaded.
42-
#: @type: ConfigManager
43-
conf = None
43+
conf: Optional["ConfigManager"] = None
4444

4545
#: Notifies when the configuration profile is switched.
4646
#: This allows components and add-ons to apply changes required by the new configuration.
@@ -73,43 +73,103 @@ def saveOnExit():
7373
except:
7474
pass
7575

76-
def isInstalledCopy():
76+
77+
class RegistryKey(str, Enum):
78+
INSTALLED_COPY = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NVDA"
79+
RUN = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
80+
NVDA = r"SOFTWARE\NVDA"
81+
r"""
82+
The name of the registry key stored under HKEY_LOCAL_MACHINE where system wide NVDA settings are stored.
83+
Note that NVDA is a 32-bit application, so on X64 systems,
84+
this will evaluate to `r"SOFTWARE\WOW6432Node\nvda"`
85+
"""
86+
87+
88+
def isInstalledCopy() -> bool:
7789
"""Checks to see if this running copy of NVDA is installed on the system"""
7890
try:
79-
k=winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NVDA")
80-
instDir=winreg.QueryValueEx(k,"UninstallDirectory")[0]
91+
k = winreg.OpenKey(
92+
winreg.HKEY_LOCAL_MACHINE,
93+
RegistryKey.INSTALLED_COPY
94+
)
95+
except FileNotFoundError:
96+
log.debug(
97+
f"Unable to find isInstalledCopy registry key {RegistryKey.INSTALLED_COPY}"
98+
"- this is not an installed copy."
99+
)
100+
return False
81101
except WindowsError:
102+
log.error(
103+
f"Unable to open isInstalledCopy registry key {RegistryKey.INSTALLED_COPY}",
104+
exc_info=True
105+
)
106+
return False
107+
108+
try:
109+
instDir = winreg.QueryValueEx(k, "UninstallDirectory")[0]
110+
except FileNotFoundError:
111+
log.debug(
112+
f"Unable to find UninstallDirectory value for {RegistryKey.INSTALLED_COPY}"
113+
"- this may not be an installed copy."
114+
)
82115
return False
116+
except WindowsError:
117+
log.error("Unable to query isInstalledCopy registry key", exc_info=True)
118+
return False
119+
83120
winreg.CloseKey(k)
84121
try:
85122
return os.stat(instDir) == os.stat(globalVars.appDir)
86123
except WindowsError:
124+
log.error(
125+
"Failed to access the installed NVDA directory,"
126+
"or, a portable copy failed to access the current NVDA app directory",
127+
exc_info=True
128+
)
87129
return False
88130

89131

90-
#: #6864: The name of the subkey stored under NVDA_REGKEY where the value is stored
91-
#: which will make an installed NVDA load the user configuration either from the local or from the roaming application data profile.
92-
#: The registry value is unset by default.
93-
#: When setting it manually, a DWORD value is prefered.
94-
#: A value of 0 will evaluate to loading the configuration from the roaming application data (default).
95-
#: A value of 1 means loading the configuration from the local application data folder.
96-
#: @type: str
97-
CONFIG_IN_LOCAL_APPDATA_SUBKEY=u"configInLocalAppData"
132+
CONFIG_IN_LOCAL_APPDATA_SUBKEY = "configInLocalAppData"
133+
"""
134+
#6864: The name of the subkey stored under RegistryKey.NVDA where the value is stored
135+
which will make an installed NVDA load the user configuration either from the local or from
136+
the roaming application data profile.
137+
The registry value is unset by default.
138+
When setting it manually, a DWORD value is preferred.
139+
A value of 0 will evaluate to loading the configuration from the roaming application data (default).
140+
A value of 1 means loading the configuration from the local application data folder.
141+
"""
142+
143+
144+
def getInstalledUserConfigPath() -> Optional[str]:
145+
try:
146+
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, RegistryKey.NVDA)
147+
except WindowsError:
148+
log.error("Could not find nvda registry key", exc_info=True)
149+
configInLocalAppData = False
98150

99-
def getInstalledUserConfigPath():
100151
try:
101-
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, NVDA_REGKEY)
102152
configInLocalAppData = bool(winreg.QueryValueEx(k, CONFIG_IN_LOCAL_APPDATA_SUBKEY)[0])
153+
except FileNotFoundError:
154+
log.debug("Installed user config is not in local app data")
155+
configInLocalAppData = False
103156
except WindowsError:
104-
configInLocalAppData=False
157+
log.error(
158+
f"Could not query if user config in local app data {CONFIG_IN_LOCAL_APPDATA_SUBKEY}",
159+
exc_info=True
160+
)
161+
configInLocalAppData = False
105162
configParent = shlobj.SHGetKnownFolderPath(
106163
shlobj.FolderId.LOCAL_APP_DATA if configInLocalAppData else shlobj.FolderId.ROAMING_APP_DATA
107164
)
108165
try:
109166
return os.path.join(configParent, "nvda")
110167
except WindowsError:
168+
# (#13242) There is some uncertainty as to how this could be caused
169+
log.debugWarning("Installed user config is not in local app data", exc_info=True)
111170
return None
112171

172+
113173
def getUserDefaultConfigPath(useInstalledPathIfExists=False):
114174
"""Get the default path for the user configuration directory.
115175
This is the default path and doesn't reflect overriding from the command line,
@@ -181,61 +241,145 @@ def initConfigPath(configPath=None):
181241
if not os.path.isdir(subdir):
182242
os.makedirs(subdir)
183243

184-
RUN_REGKEY = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
185244

186-
def getStartAfterLogon():
245+
def getStartAfterLogon() -> bool:
246+
"""Not to be confused with getStartOnLogonScreen.
247+
248+
Checks if NVDA is set to start after a logon.
249+
Checks related easeOfAccess current user registry keys on Windows 8 or newer.
250+
Then, checks the registry run key to see if NVDA
251+
has been registered to start after logon on Windows 7
252+
or by earlier NVDA versions.
253+
"""
187254
if (
188255
easeOfAccess.canConfigTerminateOnDesktopSwitch
189-
and easeOfAccess.willAutoStart(winreg.HKEY_CURRENT_USER)
256+
and easeOfAccess.willAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON)
190257
):
191258
return True
192259
try:
193-
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_REGKEY)
194-
val = winreg.QueryValueEx(k, u"nvda")[0]
195-
return os.stat(val) == os.stat(sys.argv[0])
196-
except (WindowsError, OSError):
260+
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RegistryKey.RUN)
261+
except FileNotFoundError:
262+
log.debugWarning(
263+
f"Unable to find run registry key {RegistryKey.RUN}",
264+
exc_info=True
265+
)
266+
return False
267+
except WindowsError:
268+
log.error(
269+
f"Unable to open run registry key {RegistryKey.RUN}",
270+
exc_info=True
271+
)
272+
return False
273+
274+
try:
275+
val = winreg.QueryValueEx(k, "nvda")[0]
276+
except FileNotFoundError:
277+
log.debug("NVDA is not set to start after logon")
278+
return False
279+
except WindowsError:
280+
log.error("Failed to query NVDA key to set start after logon", exc_info=True)
197281
return False
198282

199-
def setStartAfterLogon(enable):
283+
try:
284+
startAfterLogonPath = os.stat(val)
285+
except WindowsError:
286+
log.error(
287+
"Failed to access the start after logon directory.",
288+
exc_info=True
289+
)
290+
return False
291+
292+
try:
293+
currentSourcePath = os.stat(sys.argv[0])
294+
except FileNotFoundError:
295+
log.debug("Failed to access the current running NVDA directory.")
296+
return False
297+
except WindowsError:
298+
log.error(
299+
"Failed to access the current running NVDA directory.",
300+
exc_info=True
301+
)
302+
return False
303+
304+
return currentSourcePath == startAfterLogonPath
305+
306+
307+
def setStartAfterLogon(enable: bool) -> None:
308+
"""Not to be confused with setStartOnLogonScreen.
309+
310+
Toggle if NVDA automatically starts after a logon.
311+
Sets easeOfAccess related registry keys on Windows 8 or newer.
312+
313+
On Windows 7 this sets the registry run key.
314+
315+
When toggling off, always delete the registry run key to
316+
in case it was set by an earlier version of NVDA or on Windows 7 or earlier.
317+
"""
200318
if getStartAfterLogon() == enable:
201319
return
202320
if easeOfAccess.canConfigTerminateOnDesktopSwitch:
203-
easeOfAccess.setAutoStart(winreg.HKEY_CURRENT_USER, enable)
321+
easeOfAccess.setAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON, enable)
204322
if enable:
205323
return
206324
# We're disabling, so ensure the run key is cleared,
207325
# as it might have been set by an old version.
208326
run = False
209327
else:
210328
run = enable
211-
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_REGKEY, 0, winreg.KEY_WRITE)
329+
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RegistryKey.RUN, 0, winreg.KEY_WRITE)
212330
if run:
213-
winreg.SetValueEx(k, u"nvda", None, winreg.REG_SZ, sys.argv[0])
331+
winreg.SetValueEx(k, "nvda", None, winreg.REG_SZ, sys.argv[0])
214332
else:
215333
try:
216-
winreg.DeleteValue(k, u"nvda")
334+
winreg.QueryValue(k, "nvda")
335+
except FileNotFoundError:
336+
log.debug(
337+
"The run registry key is not set for setStartAfterLogon."
338+
"This is expected for Windows 8+ which uses ease of access"
339+
)
340+
return
341+
try:
342+
winreg.DeleteValue(k, "nvda")
217343
except WindowsError:
218-
pass
219-
344+
log.error(
345+
"Couldn't unset registry key for nvda to start after logon.",
346+
exc_info=True
347+
)
220348

221349

222350
SLAVE_FILENAME = os.path.join(globalVars.appDir, "nvda_slave.exe")
223351

224-
#: The name of the registry key stored under HKEY_LOCAL_MACHINE where system wide NVDA settings are stored.
225-
#: Note that NVDA is a 32-bit application, so on X64 systems, this will evaluate to "SOFTWARE\WOW6432Node\nvda"
226-
NVDA_REGKEY = r"SOFTWARE\NVDA"
227352

228-
def getStartOnLogonScreen():
229-
if easeOfAccess.willAutoStart(winreg.HKEY_LOCAL_MACHINE):
353+
def getStartOnLogonScreen() -> bool:
354+
"""Not to be confused with getStartAfterLogon.
355+
356+
Checks if NVDA is set to start on the logon screen.
357+
358+
Checks related easeOfAccess local machine registry keys.
359+
Then, checks a NVDA registry key to see if NVDA
360+
has been registered to start on logon by earlier NVDA versions.
361+
"""
362+
if easeOfAccess.willAutoStart(easeOfAccess.AutoStartContext.ON_LOGON):
230363
return True
231364
try:
232-
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, NVDA_REGKEY)
233-
return bool(winreg.QueryValueEx(k, u"startOnLogonScreen")[0])
365+
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, RegistryKey.NVDA)
366+
except FileNotFoundError:
367+
log.debugWarning(f"Could not find NVDA reg key {RegistryKey.NVDA}", exc_info=True)
368+
except WindowsError:
369+
log.error(f"Failed to open NVDA reg key {RegistryKey.NVDA}", exc_info=True)
370+
try:
371+
return bool(winreg.QueryValueEx(k, "startOnLogonScreen")[0])
372+
except FileNotFoundError:
373+
log.debug(f"Could not find startOnLogonScreen value for {RegistryKey.NVDA} - likely unset.")
374+
return False
234375
except WindowsError:
376+
log.error(f"Failed to query startOnLogonScreen value for {RegistryKey.NVDA}", exc_info=True)
235377
return False
236378

237-
def _setStartOnLogonScreen(enable):
238-
easeOfAccess.setAutoStart(winreg.HKEY_LOCAL_MACHINE, enable)
379+
380+
def _setStartOnLogonScreen(enable: bool) -> None:
381+
easeOfAccess.setAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON, enable)
382+
239383

240384
def setSystemConfigToCurrentConfig():
241385
fromPath = globalVars.appArgs.configPath
@@ -279,13 +423,23 @@ def _setSystemConfig(fromPath):
279423
destFilePath=os.path.join(curDestDir,f)
280424
installer.tryCopyFile(sourceFilePath,destFilePath)
281425

282-
def setStartOnLogonScreen(enable):
426+
427+
def setStartOnLogonScreen(enable: bool) -> None:
428+
"""
429+
Not to be confused with setStartAfterLogon.
430+
431+
Toggle whether NVDA starts on the logon screen automatically.
432+
On failure to set, retries with escalated permissions.
433+
434+
Raises a RuntimeError on failure.
435+
"""
283436
if getStartOnLogonScreen() == enable:
284437
return
285438
try:
286439
# Try setting it directly.
287440
_setStartOnLogonScreen(enable)
288441
except WindowsError:
442+
log.debugWarning("Failed to set start on logon screen's config.")
289443
# We probably don't have admin privs, so we need to elevate to do this using the slave.
290444
import systemUtils
291445
if systemUtils.execElevated(

0 commit comments

Comments
 (0)