Skip to content

Commit 2cf98f7

Browse files
authored
Merge 207910d into 3c6915b
2 parents 3c6915b + 207910d commit 2cf98f7

3 files changed

Lines changed: 307 additions & 78 deletions

File tree

source/config/__init__.py

Lines changed: 197 additions & 39 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
@@ -73,43 +74,106 @@ def saveOnExit():
7374
except:
7475
pass
7576

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

89132

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"
133+
CONFIG_IN_LOCAL_APPDATA_SUBKEY = "configInLocalAppData"
134+
"""
135+
#6864: The name of the subkey stored under RegistryKey.NVDA where the value is stored
136+
which will make an installed NVDA load the user configuration either from the local or from
137+
the roaming application data profile.
138+
The registry value is unset by default.
139+
When setting it manually, a DWORD value is preferred.
140+
A value of 0 will evaluate to loading the configuration from the roaming application data (default).
141+
A value of 1 means loading the configuration from the local application data folder.
142+
"""
143+
144+
145+
def getInstalledUserConfigPath() -> Optional[str]:
146+
try:
147+
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, RegistryKey.NVDA.value)
148+
except FileNotFoundError:
149+
log.debug("Could not find nvda registry key, NVDA is not currently installed")
150+
return None
151+
except WindowsError:
152+
log.error("Could not open nvda registry key", exc_info=True)
153+
return None
98154

99-
def getInstalledUserConfigPath():
100155
try:
101-
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, NVDA_REGKEY)
102156
configInLocalAppData = bool(winreg.QueryValueEx(k, CONFIG_IN_LOCAL_APPDATA_SUBKEY)[0])
157+
except FileNotFoundError:
158+
log.debug("Installed user config is not in local app data")
159+
configInLocalAppData = False
103160
except WindowsError:
104-
configInLocalAppData=False
161+
log.error(
162+
f"Could not query if user config in local app data {CONFIG_IN_LOCAL_APPDATA_SUBKEY}",
163+
exc_info=True
164+
)
165+
configInLocalAppData = False
105166
configParent = shlobj.SHGetKnownFolderPath(
106167
shlobj.FolderId.LOCAL_APP_DATA if configInLocalAppData else shlobj.FolderId.ROAMING_APP_DATA
107168
)
108169
try:
109170
return os.path.join(configParent, "nvda")
110171
except WindowsError:
172+
# (#13242) There is some uncertainty as to how this could be caused
173+
log.debugWarning("Installed user config is not in local app data", exc_info=True)
111174
return None
112175

176+
113177
def getUserDefaultConfigPath(useInstalledPathIfExists=False):
114178
"""Get the default path for the user configuration directory.
115179
This is the default path and doesn't reflect overriding from the command line,
@@ -181,61 +245,145 @@ def initConfigPath(configPath=None):
181245
if not os.path.isdir(subdir):
182246
os.makedirs(subdir)
183247

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

186-
def getStartAfterLogon():
249+
def getStartAfterLogon() -> bool:
250+
"""Not to be confused with getStartOnLogonScreen.
251+
252+
Checks if NVDA is set to start after a logon.
253+
Checks related easeOfAccess current user registry keys on Windows 8 or newer.
254+
Then, checks the registry run key to see if NVDA
255+
has been registered to start after logon on Windows 7
256+
or by earlier NVDA versions.
257+
"""
187258
if (
188259
easeOfAccess.canConfigTerminateOnDesktopSwitch
189-
and easeOfAccess.willAutoStart(winreg.HKEY_CURRENT_USER)
260+
and easeOfAccess.willAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON)
190261
):
191262
return True
192263
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):
264+
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RegistryKey.RUN.value)
265+
except FileNotFoundError:
266+
log.debugWarning(
267+
f"Unable to find run registry key {RegistryKey.RUN}",
268+
exc_info=True
269+
)
270+
return False
271+
except WindowsError:
272+
log.error(
273+
f"Unable to open run registry key {RegistryKey.RUN}",
274+
exc_info=True
275+
)
197276
return False
198277

199-
def setStartAfterLogon(enable):
278+
try:
279+
val = winreg.QueryValueEx(k, "nvda")[0]
280+
except FileNotFoundError:
281+
log.debug("NVDA is not set to start after logon")
282+
return False
283+
except WindowsError:
284+
log.error("Failed to query NVDA key to set start after logon", exc_info=True)
285+
return False
286+
287+
try:
288+
startAfterLogonPath = os.stat(val)
289+
except WindowsError:
290+
log.error(
291+
"Failed to access the start after logon directory.",
292+
exc_info=True
293+
)
294+
return False
295+
296+
try:
297+
currentSourcePath = os.stat(sys.argv[0])
298+
except FileNotFoundError:
299+
log.debug("Failed to access the current running NVDA directory.")
300+
return False
301+
except WindowsError:
302+
log.error(
303+
"Failed to access the current running NVDA directory.",
304+
exc_info=True
305+
)
306+
return False
307+
308+
return currentSourcePath == startAfterLogonPath
309+
310+
311+
def setStartAfterLogon(enable: bool) -> None:
312+
"""Not to be confused with setStartOnLogonScreen.
313+
314+
Toggle if NVDA automatically starts after a logon.
315+
Sets easeOfAccess related registry keys on Windows 8 or newer.
316+
317+
On Windows 7 this sets the registry run key.
318+
319+
When toggling off, always delete the registry run key
320+
in case it was set by an earlier version of NVDA or on Windows 7 or earlier.
321+
"""
200322
if getStartAfterLogon() == enable:
201323
return
202324
if easeOfAccess.canConfigTerminateOnDesktopSwitch:
203-
easeOfAccess.setAutoStart(winreg.HKEY_CURRENT_USER, enable)
325+
easeOfAccess.setAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON, enable)
204326
if enable:
205327
return
206328
# We're disabling, so ensure the run key is cleared,
207329
# as it might have been set by an old version.
208330
run = False
209331
else:
210332
run = enable
211-
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_REGKEY, 0, winreg.KEY_WRITE)
333+
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RegistryKey.RUN.value, 0, winreg.KEY_WRITE)
212334
if run:
213-
winreg.SetValueEx(k, u"nvda", None, winreg.REG_SZ, sys.argv[0])
335+
winreg.SetValueEx(k, "nvda", None, winreg.REG_SZ, sys.argv[0])
214336
else:
215337
try:
216-
winreg.DeleteValue(k, u"nvda")
338+
winreg.QueryValue(k, "nvda")
339+
except FileNotFoundError:
340+
log.debug(
341+
"The run registry key is not set for setStartAfterLogon."
342+
"This is expected for Windows 8+ which uses ease of access"
343+
)
344+
return
345+
try:
346+
winreg.DeleteValue(k, "nvda")
217347
except WindowsError:
218-
pass
219-
348+
log.error(
349+
"Couldn't unset registry key for nvda to start after logon.",
350+
exc_info=True
351+
)
220352

221353

222354
SLAVE_FILENAME = os.path.join(globalVars.appDir, "nvda_slave.exe")
223355

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"
227356

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

237-
def _setStartOnLogonScreen(enable):
238-
easeOfAccess.setAutoStart(winreg.HKEY_LOCAL_MACHINE, enable)
383+
384+
def _setStartOnLogonScreen(enable: bool) -> None:
385+
easeOfAccess.setAutoStart(easeOfAccess.AutoStartContext.ON_LOGON_SCREEN, enable)
386+
239387

240388
def setSystemConfigToCurrentConfig():
241389
fromPath = globalVars.appArgs.configPath
@@ -279,13 +427,23 @@ def _setSystemConfig(fromPath):
279427
destFilePath=os.path.join(curDestDir,f)
280428
installer.tryCopyFile(sourceFilePath,destFilePath)
281429

282-
def setStartOnLogonScreen(enable):
430+
431+
def setStartOnLogonScreen(enable: bool) -> None:
432+
"""
433+
Not to be confused with setStartAfterLogon.
434+
435+
Toggle whether NVDA starts on the logon screen automatically.
436+
On failure to set, retries with escalated permissions.
437+
438+
Raises a RuntimeError on failure.
439+
"""
283440
if getStartOnLogonScreen() == enable:
284441
return
285442
try:
286443
# Try setting it directly.
287444
_setStartOnLogonScreen(enable)
288445
except WindowsError:
446+
log.debugWarning("Failed to set start on logon screen's config.")
289447
# We probably don't have admin privs, so we need to elevate to do this using the slave.
290448
import systemUtils
291449
if systemUtils.execElevated(

0 commit comments

Comments
 (0)