Skip to content

Commit a18a56c

Browse files
authored
Encapsulate registry keys more consistently (#18608)
Prep work for #16304 Summary of the issue: Registry keys are not consistently encapsulated in the NVDA code base. When migrating to 64bit, we need to be able to fetch legacy 32bit versions of registry keys from the 64bit version. Having more clear encapsulation of registry keys will make this easier. Description of user facing changes: None Description of developer facing changes: Deprecates `easeOfAccess.RegistryKey` for a more unified approach Description of development approach: - Moved all major NVDA registry keys into `config.RegistryKey`. Create an `x86` version of this for use in the 64bit migration. - Removed usages of the [reserved 0 parameter](https://docs.python.org/3/library/winreg.html#winreg.OpenKey). The magic number should either be documented as a constant or not used. it's available as `winreg.REG_OPTION_RESERVED` but this is not documented or part of the official python API. Rather than making a named constant, the value can only be the default value so it seems cleaner to drop it. Testing strategy: Ensure nothing breaks in smoke testing Known issues with pull request: None
1 parent fc1686f commit a18a56c

13 files changed

Lines changed: 167 additions & 116 deletions

File tree

‎sconstruct‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,7 @@ def NVDADistGenerator(target, source, env, for_signature):
401401
with winreg.OpenKey(
402402
winreg.HKEY_LOCAL_MACHINE,
403403
r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0",
404-
0,
405-
winreg.KEY_READ | winreg.KEY_WOW64_32KEY,
404+
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY,
406405
) as SDKKey:
407406
sdk_installationFolder = winreg.QueryValueEx(SDKKey, "InstallationFolder")[0]
408407
sdk_productVersion = winreg.QueryValueEx(SDKKey, "ProductVersion")[0]

‎site_scons/site_tools/doxygen.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ def fetchDoxygenPath():
3232
with winreg.OpenKey(
3333
winreg.HKEY_LOCAL_MACHINE,
3434
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\doxygen_is1",
35-
0,
36-
winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
35+
access=winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
3736
) as doxygenKey:
3837
doxygenPath = '"%s"' % os.path.join(
3938
winreg.QueryValueEx(doxygenKey, "InstallLocation")[0],

‎source/NVDAState.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ def isInitializationComplete() -> bool:
165165

166166

167167
def _forceSecureModeEnabled() -> bool:
168-
from config import RegistryKey
168+
# Avoid circular import
169+
from config.registry import RegistryKey
169170

170171
try:
171172
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, RegistryKey.NVDA.value)
@@ -176,7 +177,8 @@ def _forceSecureModeEnabled() -> bool:
176177

177178

178179
def _serviceDebugEnabled() -> bool:
179-
from config import RegistryKey
180+
# Avoid circular import
181+
from config.registry import RegistryKey
180182

181183
try:
182184
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, RegistryKey.NVDA.value)
@@ -187,7 +189,8 @@ def _serviceDebugEnabled() -> bool:
187189

188190

189191
def _configInLocalAppDataEnabled() -> bool:
190-
from config import RegistryKey
192+
# Avoid circular imports
193+
from config.registry import RegistryKey
191194
from logHandler import log
192195

193196
try:

‎source/_remoteClient/localMachine.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import api
2828
import braille
29+
from config.registry import RegistryKey
2930
import inputCore
3031
import nvwave
3132
import speech
@@ -62,10 +63,10 @@ class SoftwareSASGeneration(IntEnum):
6263
"""both services and Ease of Access applications can simulate the SAS."""
6364

6465
KEY: int = nonmember(winreg.HKEY_LOCAL_MACHINE)
65-
SUBKEY: str = nonmember(r"Software\Microsoft\Windows\CurrentVersion\Policies\System")
66+
SUBKEY: str = nonmember(RegistryKey.SYSTEM_POLICIES.value)
6667
VALUE_NAME: str = nonmember("SoftwareSASGeneration")
6768
DISPLAY_PATH: str = nonmember(
68-
r"HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System!SoftwareSASGeneration",
69+
rf"HKLM\{RegistryKey.SYSTEM_POLICIES.value}!SoftwareSASGeneration",
6970
)
7071

7172

‎source/_remoteClient/urlHandler.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def _deleteRegistryKeyRecursive(baseKey: int, subkeyPath: str):
6868
except WindowsError:
6969
# If that fails, need to do recursive deletion
7070
try:
71-
with winreg.OpenKey(baseKey, subkeyPath, 0, winreg.KEY_READ | winreg.KEY_WRITE) as key:
71+
with winreg.OpenKey(baseKey, subkeyPath, access=winreg.KEY_READ | winreg.KEY_WRITE) as key:
7272
# Enumerate and delete all subkeys
7373
while True:
7474
try:

‎source/config/__init__.py‎

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import globalVars
1515
import winreg
1616
import ctypes
17-
import ctypes.wintypes
1817
import os
1918
import sys
2019
import errno
@@ -40,6 +39,7 @@
4039
_transformSpec_AddFeatureFlagDefault,
4140
_validateConfig_featureFlag,
4241
)
42+
from .registry import RegistryKey as _RegistryKey
4343
from typing import (
4444
Any,
4545
Dict,
@@ -77,12 +77,15 @@
7777

7878
def __getattr__(attrName: str) -> Any:
7979
"""Module level `__getattr__` used to preserve backward compatibility."""
80+
if attrName == "RegistryKey" and NVDAState._allowDeprecatedAPI():
81+
log.warning("Importing RegistryKey from here is deprecated, use config.registry.RegistryKey instead.")
82+
return _RegistryKey
8083
if attrName == "NVDA_REGKEY" and NVDAState._allowDeprecatedAPI():
8184
log.warning("NVDA_REGKEY is deprecated, use RegistryKey.NVDA instead.")
82-
return RegistryKey.NVDA.value
85+
return _RegistryKey.NVDA.value
8386
if attrName == "RUN_REGKEY" and NVDAState._allowDeprecatedAPI():
8487
log.warning("RUN_REGKEY is deprecated, use RegistryKey.RUN instead.")
85-
return RegistryKey.RUN.value
88+
return _RegistryKey.RUN.value
8689
if attrName == "addConfigDirsToPythonPackagePath" and NVDAState._allowDeprecatedAPI():
8790
log.warning(
8891
"addConfigDirsToPythonPackagePath is deprecated, "
@@ -98,7 +101,7 @@ def __getattr__(attrName: str) -> Any:
98101
"Instead use RegistryKey.CONFIG_IN_LOCAL_APPDATA_SUBKEY. ",
99102
stack_info=True,
100103
)
101-
return RegistryKey.CONFIG_IN_LOCAL_APPDATA_SUBKEY.value
104+
return _RegistryKey.CONFIG_IN_LOCAL_APPDATA_SUBKEY.value
102105
raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}")
103106

104107

@@ -119,45 +122,22 @@ def saveOnExit():
119122
pass
120123

121124

122-
class RegistryKey(str, Enum):
123-
INSTALLED_COPY = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NVDA"
124-
RUN = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
125-
NVDA = r"SOFTWARE\NVDA"
126-
r"""
127-
The name of the registry key stored under HKEY_LOCAL_MACHINE where system wide NVDA settings are stored.
128-
Note that NVDA is a 32-bit application, so on X64 systems,
129-
this will evaluate to `r"SOFTWARE\WOW6432Node\nvda"`
130-
"""
131-
CONFIG_IN_LOCAL_APPDATA_SUBKEY = "configInLocalAppData"
132-
"""
133-
#6864: The name of the subkey stored under RegistryKey.NVDA where the value is stored
134-
which will make an installed NVDA load the user configuration either from the local or from
135-
the roaming application data profile.
136-
The registry value is unset by default.
137-
When setting it manually, a DWORD value is preferred.
138-
A value of 0 will evaluate to loading the configuration from the roaming application data (default).
139-
A value of 1 means loading the configuration from the local application data folder.
140-
"""
141-
FORCE_SECURE_MODE_SUBKEY = "forceSecureMode"
142-
SERVICE_DEBUG_SUBKEY = "serviceDebug"
143-
144-
145125
def isInstalledCopy() -> bool:
146126
"""Checks to see if this running copy of NVDA is installed on the system"""
147127
try:
148128
k = winreg.OpenKey(
149129
winreg.HKEY_LOCAL_MACHINE,
150-
RegistryKey.INSTALLED_COPY.value,
130+
_RegistryKey.INSTALLED_COPY.value,
151131
)
152132
except FileNotFoundError:
153133
log.debug(
154-
f"Unable to find isInstalledCopy registry key {RegistryKey.INSTALLED_COPY}"
134+
f"Unable to find isInstalledCopy registry key {_RegistryKey.INSTALLED_COPY}"
155135
"- this is not an installed copy.",
156136
)
157137
return False
158138
except WindowsError:
159139
log.error(
160-
f"Unable to open isInstalledCopy registry key {RegistryKey.INSTALLED_COPY}",
140+
f"Unable to open isInstalledCopy registry key {_RegistryKey.INSTALLED_COPY}",
161141
exc_info=True,
162142
)
163143
return False
@@ -166,7 +146,7 @@ def isInstalledCopy() -> bool:
166146
instDir = winreg.QueryValueEx(k, "UninstallDirectory")[0]
167147
except FileNotFoundError:
168148
log.debug(
169-
f"Unable to find UninstallDirectory value for {RegistryKey.INSTALLED_COPY}"
149+
f"Unable to find UninstallDirectory value for {_RegistryKey.INSTALLED_COPY}"
170150
"- this may not be an installed copy.",
171151
)
172152
return False
@@ -188,7 +168,7 @@ def isInstalledCopy() -> bool:
188168

189169
def getInstalledUserConfigPath() -> Optional[str]:
190170
try:
191-
winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, RegistryKey.NVDA.value)
171+
winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, _RegistryKey.NVDA.value)
192172
except FileNotFoundError:
193173
log.debug("Could not find nvda registry key, NVDA is not currently installed")
194174
return None
@@ -297,16 +277,16 @@ def getStartAfterLogon() -> bool:
297277
if easeOfAccess.willAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON):
298278
return True
299279
try:
300-
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RegistryKey.RUN.value)
280+
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, _RegistryKey.RUN.value)
301281
except FileNotFoundError:
302282
log.debugWarning(
303-
f"Unable to find run registry key {RegistryKey.RUN}",
283+
f"Unable to find run registry key {_RegistryKey.RUN}",
304284
exc_info=True,
305285
)
306286
return False
307287
except WindowsError:
308288
log.error(
309-
f"Unable to open run registry key {RegistryKey.RUN}",
289+
f"Unable to open run registry key {_RegistryKey.RUN}",
310290
exc_info=True,
311291
)
312292
return False
@@ -360,7 +340,7 @@ def setStartAfterLogon(enable: bool) -> None:
360340
return
361341
# We're disabling, so ensure the run key is cleared,
362342
# as it might have been set by an old version.
363-
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, RegistryKey.RUN.value, 0, winreg.KEY_WRITE)
343+
k = winreg.OpenKey(winreg.HKEY_CURRENT_USER, _RegistryKey.RUN.value, access=winreg.KEY_WRITE)
364344
try:
365345
winreg.QueryValue(k, "nvda")
366346
except FileNotFoundError:
@@ -393,19 +373,19 @@ def getStartOnLogonScreen() -> bool:
393373
if easeOfAccess.willAutoStart(easeOfAccess.AutoStartContext.ON_LOGON_SCREEN):
394374
return True
395375
try:
396-
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, RegistryKey.NVDA.value)
376+
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, _RegistryKey.NVDA.value)
397377
except FileNotFoundError:
398-
log.debugWarning(f"Could not find NVDA reg key {RegistryKey.NVDA}", exc_info=True)
378+
log.debugWarning(f"Could not find NVDA reg key {_RegistryKey.NVDA}", exc_info=True)
399379
except WindowsError:
400-
log.error(f"Failed to open NVDA reg key {RegistryKey.NVDA}", exc_info=True)
380+
log.error(f"Failed to open NVDA reg key {_RegistryKey.NVDA}", exc_info=True)
401381
else:
402382
try:
403383
return bool(winreg.QueryValueEx(k, "startOnLogonScreen")[0])
404384
except FileNotFoundError:
405-
log.debug(f"Could not find startOnLogonScreen value for {RegistryKey.NVDA} - likely unset.")
385+
log.debug(f"Could not find startOnLogonScreen value for {_RegistryKey.NVDA} - likely unset.")
406386
return False
407387
except WindowsError:
408-
log.error(f"Failed to query startOnLogonScreen value for {RegistryKey.NVDA}", exc_info=True)
388+
log.error(f"Failed to query startOnLogonScreen value for {_RegistryKey.NVDA}", exc_info=True)
409389
return False
410390
return False
411391

‎source/config/registry.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2006-2025 NV Access Limited
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
5+
6+
from enum import Enum, nonmember
7+
8+
9+
EASE_OF_ACCESS_APP_KEY_NAME = "nvda_nvda_v1"
10+
11+
12+
class RegistryKey(str, Enum):
13+
_SOFTWARE = nonmember("SOFTWARE")
14+
r"""
15+
The name of the registry key stored under HKEY_LOCAL_MACHINE where system wide NVDA settings are stored.
16+
Note that if NVDA is a 32-bit application, on x64 systems,
17+
this will evaluate to `r"SOFTWARE\WOW6432Node"`
18+
"""
19+
CURRENT_VERSION = rf"{_SOFTWARE}\Microsoft\Windows\CurrentVersion"
20+
INSTALLED_COPY = rf"{CURRENT_VERSION}\Uninstall\NVDA"
21+
RUN = rf"{CURRENT_VERSION}\Run"
22+
NVDA = rf"{_SOFTWARE}\NVDA"
23+
APP_PATH = rf"{CURRENT_VERSION}\App Paths\nvda.exe"
24+
EXPLORER_ADVANCED = rf"{CURRENT_VERSION}\Explorer\Advanced"
25+
SYSTEM_POLICIES = rf"{CURRENT_VERSION}\Policies\System"
26+
NT_CURRENT_VERSION = rf"{_SOFTWARE}\Microsoft\Windows NT\CurrentVersion"
27+
EASE_OF_ACCESS = rf"{NT_CURRENT_VERSION}\Accessibility"
28+
EASE_OF_ACCESS_TEMP = rf"{NT_CURRENT_VERSION}\AccessibilityTemp"
29+
EASE_OF_ACCESS_APP = rf"{EASE_OF_ACCESS}\ATs\{EASE_OF_ACCESS_APP_KEY_NAME}"
30+
31+
# Sub keys
32+
33+
CONFIG_IN_LOCAL_APPDATA_SUBKEY = "configInLocalAppData"
34+
"""
35+
#6864: The name of the subkey stored under RegistryKey.NVDA where the value is stored
36+
which will make an installed NVDA load the user configuration either from the local or from
37+
the roaming application data profile.
38+
The registry value is unset by default.
39+
When setting it manually, a DWORD value is preferred.
40+
A value of 0 will evaluate to loading the configuration from the roaming application data (default).
41+
A value of 1 means loading the configuration from the local application data folder.
42+
"""
43+
FORCE_SECURE_MODE_SUBKEY = "forceSecureMode"
44+
SERVICE_DEBUG_SUBKEY = "serviceDebug"
45+
46+
47+
class _RegistryKeyX86(str, Enum): # type: ignore[reportUnusedClass]
48+
"""
49+
Used to access the 32-bit registry view on x64 systems.
50+
For cleaning up legacy 32-bit NVDA copies.
51+
"""
52+
53+
_SOFTWARE = nonmember(r"SOFTWARE\WOW6432Node")
54+
CURRENT_VERSION = rf"{_SOFTWARE}\Microsoft\Windows\CurrentVersion"
55+
INSTALLED_COPY = rf"{CURRENT_VERSION}\Uninstall\NVDA"
56+
RUN = rf"{CURRENT_VERSION}\Run"
57+
NVDA = rf"{_SOFTWARE}\NVDA"
58+
APP_PATH = rf"{CURRENT_VERSION}\App Paths\nvda.exe"
59+
EXPLORER_ADVANCED = rf"{CURRENT_VERSION}\Explorer\Advanced"
60+
SYSTEM_POLICIES = rf"{CURRENT_VERSION}\Policies\System"
61+
NT_CURRENT_VERSION = rf"{_SOFTWARE}\Microsoft\Windows NT\CurrentVersion"
62+
EASE_OF_ACCESS = rf"{NT_CURRENT_VERSION}\Accessibility"
63+
EASE_OF_ACCESS_TEMP = rf"{NT_CURRENT_VERSION}\AccessibilityTemp"
64+
EASE_OF_ACCESS_APP = rf"{EASE_OF_ACCESS}\ATs\{EASE_OF_ACCESS_APP_KEY_NAME}"

0 commit comments

Comments
 (0)