|
1 | 1 | # 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, |
3 | 3 | # Joseph Lee, Babbage B.V., Łukasz Golonka, Julien Cochuyt |
4 | 4 | # This file is covered by the GNU General Public License. |
5 | 5 | # See the file COPYING for more details. |
|
10 | 10 | For the latter two actions, one can perform actions prior to and/or after they take place. |
11 | 11 | """ |
12 | 12 |
|
| 13 | +from enum import Enum |
13 | 14 | import globalVars |
14 | 15 | import winreg |
15 | 16 | import ctypes |
|
36 | 37 | from typing import Any, Dict, List, Optional, Set |
37 | 38 |
|
38 | 39 | #: True if NVDA is running as a Windows Store Desktop Bridge application |
39 | | -isAppX=False |
| 40 | +isAppX: bool = False |
40 | 41 |
|
41 | 42 | #: The active configuration, C{None} if it has not yet been loaded. |
42 | | -#: @type: ConfigManager |
43 | | -conf = None |
| 43 | +conf: Optional["ConfigManager"] = None |
44 | 44 |
|
45 | 45 | #: Notifies when the configuration profile is switched. |
46 | 46 | #: This allows components and add-ons to apply changes required by the new configuration. |
@@ -73,43 +73,103 @@ def saveOnExit(): |
73 | 73 | except: |
74 | 74 | pass |
75 | 75 |
|
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: |
77 | 89 | """Checks to see if this running copy of NVDA is installed on the system""" |
78 | 90 | 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 |
81 | 101 | 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 | + ) |
82 | 115 | return False |
| 116 | + except WindowsError: |
| 117 | + log.error("Unable to query isInstalledCopy registry key", exc_info=True) |
| 118 | + return False |
| 119 | + |
83 | 120 | winreg.CloseKey(k) |
84 | 121 | try: |
85 | 122 | return os.stat(instDir) == os.stat(globalVars.appDir) |
86 | 123 | 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 | + ) |
87 | 129 | return False |
88 | 130 |
|
89 | 131 |
|
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 |
98 | 150 |
|
99 | | -def getInstalledUserConfigPath(): |
100 | 151 | try: |
101 | | - k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, NVDA_REGKEY) |
102 | 152 | 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 |
103 | 156 | 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 |
105 | 162 | configParent = shlobj.SHGetKnownFolderPath( |
106 | 163 | shlobj.FolderId.LOCAL_APP_DATA if configInLocalAppData else shlobj.FolderId.ROAMING_APP_DATA |
107 | 164 | ) |
108 | 165 | try: |
109 | 166 | return os.path.join(configParent, "nvda") |
110 | 167 | 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) |
111 | 170 | return None |
112 | 171 |
|
| 172 | + |
113 | 173 | def getUserDefaultConfigPath(useInstalledPathIfExists=False): |
114 | 174 | """Get the default path for the user configuration directory. |
115 | 175 | This is the default path and doesn't reflect overriding from the command line, |
@@ -181,61 +241,145 @@ def initConfigPath(configPath=None): |
181 | 241 | if not os.path.isdir(subdir): |
182 | 242 | os.makedirs(subdir) |
183 | 243 |
|
184 | | -RUN_REGKEY = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run" |
185 | 244 |
|
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 | + """ |
187 | 254 | if ( |
188 | 255 | easeOfAccess.canConfigTerminateOnDesktopSwitch |
189 | | - and easeOfAccess.willAutoStart(winreg.HKEY_CURRENT_USER) |
| 256 | + and easeOfAccess.willAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON) |
190 | 257 | ): |
191 | 258 | return True |
192 | 259 | 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) |
197 | 281 | return False |
198 | 282 |
|
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 | + """ |
200 | 318 | if getStartAfterLogon() == enable: |
201 | 319 | return |
202 | 320 | if easeOfAccess.canConfigTerminateOnDesktopSwitch: |
203 | | - easeOfAccess.setAutoStart(winreg.HKEY_CURRENT_USER, enable) |
| 321 | + easeOfAccess.setAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON, enable) |
204 | 322 | if enable: |
205 | 323 | return |
206 | 324 | # We're disabling, so ensure the run key is cleared, |
207 | 325 | # as it might have been set by an old version. |
208 | 326 | run = False |
209 | 327 | else: |
210 | 328 | 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) |
212 | 330 | 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]) |
214 | 332 | else: |
215 | 333 | 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") |
217 | 343 | 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 | + ) |
220 | 348 |
|
221 | 349 |
|
222 | 350 | SLAVE_FILENAME = os.path.join(globalVars.appDir, "nvda_slave.exe") |
223 | 351 |
|
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" |
227 | 352 |
|
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): |
230 | 363 | return True |
231 | 364 | 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 |
234 | 375 | except WindowsError: |
| 376 | + log.error(f"Failed to query startOnLogonScreen value for {RegistryKey.NVDA}", exc_info=True) |
235 | 377 | return False |
236 | 378 |
|
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 | + |
239 | 383 |
|
240 | 384 | def setSystemConfigToCurrentConfig(): |
241 | 385 | fromPath = globalVars.appArgs.configPath |
@@ -279,13 +423,23 @@ def _setSystemConfig(fromPath): |
279 | 423 | destFilePath=os.path.join(curDestDir,f) |
280 | 424 | installer.tryCopyFile(sourceFilePath,destFilePath) |
281 | 425 |
|
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 | + """ |
283 | 436 | if getStartOnLogonScreen() == enable: |
284 | 437 | return |
285 | 438 | try: |
286 | 439 | # Try setting it directly. |
287 | 440 | _setStartOnLogonScreen(enable) |
288 | 441 | except WindowsError: |
| 442 | + log.debugWarning("Failed to set start on logon screen's config.") |
289 | 443 | # We probably don't have admin privs, so we need to elevate to do this using the slave. |
290 | 444 | import systemUtils |
291 | 445 | if systemUtils.execElevated( |
|
0 commit comments