-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Hello paulber19,
Here is a patch to fix a critical bug that causes the NVDA Extension Global Plugin to crash.
The Core Problem
The add-on crashes when it tries to load an NVDA configuration profile (.ini file) where boolean settings are saved as strings (e.g., speakTypedCharacters = "True") instead of as proper booleans (speakTypedCharacters = True). This is common in older user profiles.
The crash occurs in two places:
- During Installation: The
installTasks.pyscript fails when trying to clean up old settings from these invalid profiles. - During NVDA Startup: The add-on's main settings module (
settings/__init__.py) fails when it loads at startup for the same reason.
The Solution
The solution is to make the add-on's code more resilient. Instead of crashing, the code should catch this specific error, manually fix the user's invalid profile file on disk, and then proceed with its operation.
Here are the exact code changes needed in the two affected files:
File 1: installTasks.py
This file handles the installation and uninstallation logic. The fix prevents the installation from failing.
1. Add Necessary Imports:
At the top of the file, ensure os and configobj are imported.
from logHandler import log
import os
import configobj```
**2. Replace the `deleteAddonProfilesConfig` function:**
Replace the entire existing `deleteAddonProfilesConfig` function with the following corrected and robust version.
```python
def deleteAddonProfilesConfig(addonName):
import config
import globalVars
conf = config.conf
save = False
# First, handle the default configuration profile
if addonName in conf.profiles[0]:
log.warning("%s section deleted from profile: normal configuration " % addonName)
del conf.profiles[0][addonName]
save = True
# Now, iterate through all named profiles
profileNames = list(config.conf.listProfiles())
for name in profileNames:
try:
# Try to load the profile normally
profile = config.conf._getProfile(name)
# If it loads, clean it in memory
if profile and profile.get(addonName):
log.warning("%s section deleted from memory for profile: %s" % (addonName, name))
del profile[addonName]
config.conf._dirtyProfiles.add(name)
save = True
except ValueError as e:
# If loading fails due to our specific error, handle it on disk
if "VdtTypeError" in str(e) and ("speakTypedCharacters" in str(e) or "speakTypedWords" in str(e)):
log.warning(f"Config for profile '{name}' is invalid. Fixing and cleaning on disk.")
profile_path = os.path.join(globalVars.appArgs.configPath, "profiles", name + ".ini")
if os.path.exists(profile_path):
try:
profile_config = configobj.ConfigObj(profile_path, interpolation=False, encoding='utf-8')
made_changes = False
# Step 1: Fix the known invalid keys
keys_to_fix = ["speakTypedCharacters", "speakTypedWords"]
if 'keyboard' in profile_config:
for key in keys_to_fix:
if key in profile_config['keyboard'] and isinstance(profile_config['keyboard'][key], str):
log.warning(f"Fixing key '{key}' in profile '{name}'.")
profile_config['keyboard'][key] = (profile_config['keyboard'][key].lower() == 'true')
made_changes = True
# Step 2: Remove the addon's configuration section from the file
if addonName in profile_config:
log.warning(f"Removing '{addonName}' section from disk for profile: {name}")
del profile_config[addonName]
made_changes = True
# Step 3: Write all changes back to the file
if made_changes:
profile_config.write()
log.warning(f"Profile '{name}' has been fixed and cleaned on disk.")
except Exception as fix_error:
log.error(f"Failed to manually fix/clean the configuration for profile '{name}': {fix_error}")
raise e
else:
log.warning(f"Cannot fix profile '{name}', .ini file not found at {profile_path}")
else:
# It's a different ValueError, so we should not suppress it.
raise e
# Save any changes made to profiles that were successfully loaded into memory
if save:
config.conf.save()
File 2: globalPlugins/NVDAExtensionGlobalPlugin/settings/__init__.py
This file handles the add-on's settings during normal operation. This fix prevents the add-on from crashing when NVDA starts.
1. Add Necessary Imports:
At the top of the file, add an import for configobj.
# ... other imports
import configobj
from configobj.validate import Validator, VdtTypeError
# ... other imports
2. Replace the restoreAddonProfilesConfig function:
Replace the entire existing restoreAddonProfilesConfig function with the following corrected version.
def restoreAddonProfilesConfig(self, addonName):
conf = config.conf
save = False
# Handle the default configuration profile first
if "%s-temp" % addonName in conf.profiles[0]:
log.warning("restoreAddonProfilesConfig profile[0]")
conf.profiles[0][addonName] = conf.profiles[0]["%s-temp" % addonName].copy()
del conf.profiles[0]["%s-temp" % addonName]
save = True
# Now, iterate through all named profiles
profileNames = list(config.conf.listProfiles())
for name in profileNames:
profile = None
try:
# Try to load the profile normally
profile = config.conf._getProfile(name)
except ValueError as e:
# If loading fails due to our specific error, handle it on disk
if "VdtTypeError" in str(e) and ("speakTypedCharacters" in str(e) or "speakTypedWords" in str(e)):
log.warning(f"Config for profile '{name}' is invalid during startup. Fixing it on disk.")
profile_path = os.path.join(globalVars.appArgs.configPath, "profiles", name + ".ini")
if os.path.exists(profile_path):
try:
profile_config = configobj.ConfigObj(profile_path, interpolation=False, encoding='utf-8')
made_changes = False
# Fix the known invalid keys
keys_to_fix = ["speakTypedCharacters", "speakTypedWords"]
if 'keyboard' in profile_config:
for key in keys_to_fix:
if key in profile_config['keyboard'] and isinstance(profile_config['keyboard'][key], str):
log.warning(f"Fixing key '{key}' in profile '{name}'.")
profile_config['keyboard'][key] = (profile_config['keyboard'][key].lower() == 'true')
made_changes = True
if made_changes:
profile_config.write()
log.warning(f"Profile '{name}' has been fixed on disk.")
# Retry loading the profile now that it's fixed
profile = config.conf._getProfile(name)
except Exception as fix_error:
log.error(f"Failed to manually fix the configuration for profile '{name}': {fix_error}")
else:
log.warning(f"Cannot fix profile '{name}', .ini file not found at {profile_path}")
else:
# It's a different ValueError, re-raise it.
raise e
# Now, with a valid profile, proceed with restoring the addon's config
if profile and profile.get("%s-temp" % addonName):
log.warning("restoreAddonProfilesConfig: profile %s" % name)
profile[addonName] = profile["%s-temp" % addonName].copy()
del profile["%s-temp" % addonName]
config.conf._dirtyProfiles.add(name)
save = True
# We save the configuration if changes were made
if save:
config.conf.save()
By applying these two changes, the add-on will become significantly more stable for all users, especially those who have been using NVDA for a long time.