|
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 |
@@ -73,43 +74,106 @@ def saveOnExit(): |
73 | 74 | except: |
74 | 75 | pass |
75 | 76 |
|
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: |
77 | 90 | """Checks to see if this running copy of NVDA is installed on the system""" |
78 | 91 | 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 |
81 | 102 | 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 | + ) |
82 | 116 | return False |
| 117 | + except WindowsError: |
| 118 | + log.error("Unable to query isInstalledCopy registry key", exc_info=True) |
| 119 | + return False |
| 120 | + |
83 | 121 | winreg.CloseKey(k) |
84 | 122 | try: |
85 | 123 | return os.stat(instDir) == os.stat(globalVars.appDir) |
86 | 124 | 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 | + ) |
87 | 130 | return False |
88 | 131 |
|
89 | 132 |
|
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 |
98 | 154 |
|
99 | | -def getInstalledUserConfigPath(): |
100 | 155 | try: |
101 | | - k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, NVDA_REGKEY) |
102 | 156 | 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 |
103 | 160 | 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 |
105 | 166 | configParent = shlobj.SHGetKnownFolderPath( |
106 | 167 | shlobj.FolderId.LOCAL_APP_DATA if configInLocalAppData else shlobj.FolderId.ROAMING_APP_DATA |
107 | 168 | ) |
108 | 169 | try: |
109 | 170 | return os.path.join(configParent, "nvda") |
110 | 171 | 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) |
111 | 174 | return None |
112 | 175 |
|
| 176 | + |
113 | 177 | def getUserDefaultConfigPath(useInstalledPathIfExists=False): |
114 | 178 | """Get the default path for the user configuration directory. |
115 | 179 | This is the default path and doesn't reflect overriding from the command line, |
@@ -181,61 +245,145 @@ def initConfigPath(configPath=None): |
181 | 245 | if not os.path.isdir(subdir): |
182 | 246 | os.makedirs(subdir) |
183 | 247 |
|
184 | | -RUN_REGKEY = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run" |
185 | 248 |
|
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 | + """ |
187 | 258 | if ( |
188 | 259 | easeOfAccess.canConfigTerminateOnDesktopSwitch |
189 | | - and easeOfAccess.willAutoStart(winreg.HKEY_CURRENT_USER) |
| 260 | + and easeOfAccess.willAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON) |
190 | 261 | ): |
191 | 262 | return True |
192 | 263 | 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 | + ) |
197 | 276 | return False |
198 | 277 |
|
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 | + """ |
200 | 322 | if getStartAfterLogon() == enable: |
201 | 323 | return |
202 | 324 | if easeOfAccess.canConfigTerminateOnDesktopSwitch: |
203 | | - easeOfAccess.setAutoStart(winreg.HKEY_CURRENT_USER, enable) |
| 325 | + easeOfAccess.setAutoStart(easeOfAccess.AutoStartContext.AFTER_LOGON, enable) |
204 | 326 | if enable: |
205 | 327 | return |
206 | 328 | # We're disabling, so ensure the run key is cleared, |
207 | 329 | # as it might have been set by an old version. |
208 | 330 | run = False |
209 | 331 | else: |
210 | 332 | 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) |
212 | 334 | 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]) |
214 | 336 | else: |
215 | 337 | 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") |
217 | 347 | 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 | + ) |
220 | 352 |
|
221 | 353 |
|
222 | 354 | SLAVE_FILENAME = os.path.join(globalVars.appDir, "nvda_slave.exe") |
223 | 355 |
|
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 | 356 |
|
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): |
230 | 367 | return True |
231 | 368 | 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 |
234 | 379 | except WindowsError: |
| 380 | + log.error(f"Failed to query startOnLogonScreen value for {RegistryKey.NVDA}", exc_info=True) |
235 | 381 | return False |
236 | 382 |
|
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 | + |
239 | 387 |
|
240 | 388 | def setSystemConfigToCurrentConfig(): |
241 | 389 | fromPath = globalVars.appArgs.configPath |
@@ -279,13 +427,23 @@ def _setSystemConfig(fromPath): |
279 | 427 | destFilePath=os.path.join(curDestDir,f) |
280 | 428 | installer.tryCopyFile(sourceFilePath,destFilePath) |
281 | 429 |
|
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 | + """ |
283 | 440 | if getStartOnLogonScreen() == enable: |
284 | 441 | return |
285 | 442 | try: |
286 | 443 | # Try setting it directly. |
287 | 444 | _setStartOnLogonScreen(enable) |
288 | 445 | except WindowsError: |
| 446 | + log.debugWarning("Failed to set start on logon screen's config.") |
289 | 447 | # We probably don't have admin privs, so we need to elevate to do this using the slave. |
290 | 448 | import systemUtils |
291 | 449 | if systemUtils.execElevated( |
|
0 commit comments