Files
fenrir/src/fenrirscreenreader/core/settingsManager.py
2025-07-09 18:31:58 -04:00

785 lines
31 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import inspect
import os
from configparser import ConfigParser
from fenrirscreenreader.core import applicationManager
from fenrirscreenreader.core import attributeManager
from fenrirscreenreader.core import barrierManager
from fenrirscreenreader.core import byteManager
from fenrirscreenreader.core import commandManager
from fenrirscreenreader.core import cursorManager
from fenrirscreenreader.core import debug
from fenrirscreenreader.core import debugManager
from fenrirscreenreader.core import environment
from fenrirscreenreader.core import eventManager
from fenrirscreenreader.core import helpManager
from fenrirscreenreader.core import inputManager
from fenrirscreenreader.core import memoryManager
from fenrirscreenreader.core import outputManager
from fenrirscreenreader.core import processManager
from fenrirscreenreader.core import punctuationManager
from fenrirscreenreader.core import quickMenuManager
from fenrirscreenreader.core import remoteManager
from fenrirscreenreader.core import sayAllManager
from fenrirscreenreader.core import screenManager
from fenrirscreenreader.core import tableManager
from fenrirscreenreader.core import textManager
from fenrirscreenreader.core import vmenuManager
from fenrirscreenreader.core.settingsData import settings_data
from fenrirscreenreader.utils import module_utils
currentdir = os.path.dirname(
os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
fenrir_path = os.path.dirname(currentdir)
class SettingsManager:
def __init__(self):
self.settings = settings_data
self.settingArgDict = {}
self.bindingsBackup = None
self.settings_file = ""
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_binding_backup(self):
return self.bindingsBackup.copy()
def load_sound_icons(self, soundIconPath, environment=None):
# Use passed environment or fall back to self.env
env = environment if environment is not None else self.env
try:
with open(soundIconPath + "/soundicons.conf", "r") as siConfig:
while True:
line = siConfig.readline()
if not line:
break
line = line.replace("\n", "")
if line.replace(" ", "") == "":
continue
if line.replace(" ", "").startswith("#"):
continue
if line.count("=") != 1:
continue
values = line.split("=")
sound_icon = values[0].upper()
values[1] = values[1].replace("'", "")
values[1] = values[1].replace('"', "")
sound_icon_file = ""
if os.path.exists(values[1]):
sound_icon_file = values[1]
else:
if not soundIconPath.endswith("/"):
soundIconPath += "/"
if os.path.exists(soundIconPath + values[1]):
sound_icon_file = soundIconPath + values[1]
env["soundIcons"][sound_icon] = sound_icon_file
env["runtime"]["DebugManager"].write_debug_out(
"SoundIcon: " + sound_icon + "." + sound_icon_file,
debug.DebugLevel.INFO,
on_any_level=True,
)
except (IOError, OSError) as e:
env["runtime"]["DebugManager"].write_debug_out(
"load_sound_icons: failed to load sound icons from "
+ soundIconPath
+ ". Error: "
+ str(e),
debug.DebugLevel.ERROR,
)
def get_settings_file(self):
return self.settings_file
def set_settings_file(self, settings_file):
if not os.path.exists(settings_file):
return
if not os.access(settings_file, os.R_OK):
return
self.settings_file = settings_file
def load_settings(self, setting_config_path):
if not os.path.exists(setting_config_path):
return False
if not os.access(setting_config_path, os.R_OK):
return False
self.env["settings"] = ConfigParser()
try:
self.env["settings"].read(setting_config_path)
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
"settings_manager load_settings: Error reading config file: "
+ str(e),
debug.DebugLevel.ERROR,
)
return False
self.set_settings_file(setting_config_path)
return True
def save_settings(self, setting_config_path):
# set opt dict here
# save file
try:
# print('file: ',setting_config_path)
for section, settings in self.settingArgDict.items():
for setting, value in settings.items():
# print(section, setting, value)
self.env["settings"].set(section, setting, value)
# print('full',self.env['settings'])
config_file = open(setting_config_path, "w")
self.env["settings"].write(config_file)
config_file.close()
os.chmod(setting_config_path, 0o644)
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
"save_settings: save settingsfile:"
+ setting_config_path
+ "failed. Error:"
+ str(e),
debug.DebugLevel.ERROR,
)
def set_setting(self, section, setting, value):
self.set_option_arg_dict(section, setting, value)
# self.env['settings'].set(section, setting, value)
def get_setting(self, section, setting):
value = ""
try:
value = self.settingArgDict[section][setting]
return value
except Exception as e:
pass
try:
value = self.env["settings"].get(section, setting)
except Exception as e:
value = str(self.settings[section][setting])
return value
def get_setting_as_int(self, section, setting):
value = 0
try:
value = int(self.settingArgDict[section][setting])
return value
except Exception as e:
pass
try:
value = self.env["settings"].getint(section, setting)
except Exception as e:
value = self.settings[section][setting]
return value
def get_setting_as_float(self, section, setting):
value = 0.0
try:
value = float(self.settingArgDict[section][setting])
return value
except Exception as e:
pass
try:
value = self.env["settings"].getfloat(section, setting)
except Exception as e:
value = self.settings[section][setting]
return value
def get_setting_as_bool(self, section, setting):
value = False
try:
value = self.settingArgDict[section][setting].upper() in [
"1",
"YES",
"JA",
"TRUE",
]
return value
except Exception as e:
pass
try:
value = self.env["settings"].getboolean(section, setting)
except Exception as e:
value = self.settings[section][setting]
return value
def load_driver(self, driverName, driverType):
# Map runtime keys to actual directory names
driver_dir_map = {
"InputDriver": "inputDriver",
"ScreenDriver": "screenDriver",
"SpeechDriver": "speechDriver",
"SoundDriver": "soundDriver",
"RemoteDriver": "remoteDriver",
}
driver_dir = driver_dir_map.get(driverType, driverType)
try:
if self.env["runtime"][driverType] is not None:
self.env["runtime"][driverType].shutdown(self.env)
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
"settings_manager load_driver: Error shutting down driver: "
+ str(e),
debug.DebugLevel.ERROR,
)
try:
driver_mod = module_utils.import_module(
driverName,
fenrir_path + "/" + driver_dir + "/" + driverName + ".py",
)
self.env["runtime"][driverType] = driver_mod.driver()
self.env["runtime"][driverType].initialize(self.env)
self.env["runtime"]["DebugManager"].write_debug_out(
"Loading Driver " + driverType + " (" + driverName + ") OK",
debug.DebugLevel.INFO,
on_any_level=True,
)
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
"Loading Driver "
+ driverType
+ " ("
+ driverName
+ ") FAILED:"
+ str(e),
debug.DebugLevel.ERROR,
)
try:
driver_mod = module_utils.import_module(
driverName,
fenrir_path + "/" + driver_dir + "/dummyDriver.py",
)
self.env["runtime"][driverType] = driver_mod.driver()
self.env["runtime"][driverType].initialize(self.env)
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
"(fallback) Loading Driver "
+ driverType
+ " (dummyDriver) FAILED:"
+ str(e),
debug.DebugLevel.ERROR,
)
def shutdown_driver(self, driverType):
try:
self.env["runtime"][driverType].shutdown()
except Exception as e:
pass
del self.env["runtime"][driverType]
def set_fenrir_keys(self, keys):
keys = keys.upper()
key_list = keys.split(",")
for key in key_list:
if key not in self.env["input"]["fenrirKey"]:
self.env["input"]["fenrirKey"].append(key)
def set_script_keys(self, keys):
keys = keys.upper()
key_list = keys.split(",")
for key in key_list:
if key not in self.env["input"]["scriptKey"]:
self.env["input"]["scriptKey"].append(key)
def reset_setting_arg_dict(self):
self.settingArgDict = {}
self.env["runtime"]["OutputManager"].reset_SpeechDriver()
def set_option_arg_dict(self, section, setting, value):
# section = section.lower()
# setting = setting.lower()
try:
e = self.settingArgDict[section]
except KeyError:
self.settingArgDict[section] = {}
try:
t = self.settings[section][setting]
except Exception as e:
print(section, setting, "not found")
return
try:
v = value # Initialize v with the original value
if isinstance(self.settings[section][setting], str):
v = str(value)
elif isinstance(self.settings[section][setting], bool):
if value not in ["True", "False"]:
raise ValueError(
"could not convert string to bool: " + value
)
v = value == "True"
elif isinstance(self.settings[section][setting], int):
v = int(value)
elif isinstance(self.settings[section][setting], float):
v = float(value)
# Content validation for critical settings
self._validate_setting_value(section, setting, v)
self.settingArgDict[section][setting] = str(value)
except Exception as e:
print(
"settings_manager:set_option_arg_dict:Datatype missmatch: "
+ section
+ "#"
+ setting
+ "="
+ str(value)
+ " Error:"
+ str(e)
)
# self.env['runtime']['DebugManager'].write_debug_out('settings_manager:set_option_arg_dict:Datatype
# missmatch: '+ section + '#' + setting + '=' + value + ' Error:'
# + str(e), debug.DebugLevel.ERROR)
return
def _validate_setting_value(self, section, setting, value):
"""Validate setting values for critical screen reader functionality.
Only validates settings that could cause crashes or accessibility issues.
Invalid values raise ValueError which is caught by the calling method.
"""
# Speech settings validation - critical for accessibility
if section == "speech":
if setting == "rate":
if not (0.0 <= value <= 3.0):
raise ValueError(
f"Speech rate must be between 0.0 and 3.0, got {value}"
)
elif setting == "pitch":
if not (0.0 <= value <= 2.0):
raise ValueError(
f"Speech pitch must be between 0.0 and 2.0, got {value}"
)
elif setting == "volume":
if not (0.0 <= value <= 1.5):
raise ValueError(
f"Speech volume must be between 0.0 and 1.5, got {value}"
)
elif setting == "driver":
valid_drivers = [
"speechdDriver",
"genericDriver",
"dummyDriver",
]
if value not in valid_drivers:
raise ValueError(
f"Invalid speech driver: {value}. Valid options: {valid_drivers}"
)
# Sound settings validation
elif section == "sound":
if setting == "volume":
if not (0.0 <= value <= 1.5):
raise ValueError(
f"Sound volume must be between 0.0 and 1.5, got {value}"
)
elif setting == "driver":
valid_drivers = [
"genericDriver",
"gstreamerDriver",
"dummyDriver",
]
if value not in valid_drivers:
raise ValueError(
f"Invalid sound driver: {value}. Valid options: {valid_drivers}"
)
# Screen settings validation
elif section == "screen":
if setting == "driver":
valid_drivers = ["vcsaDriver", "ptyDriver", "dummyDriver"]
if value not in valid_drivers:
raise ValueError(
f"Invalid screen driver: {value}. Valid options: {valid_drivers}"
)
# Input settings validation
elif section == "keyboard":
if setting == "driver":
valid_drivers = [
"evdevDriver",
"ptyDriver",
"atspiDriver",
"dummyDriver",
]
if value not in valid_drivers:
raise ValueError(
f"Invalid input driver: {value}. Valid options: {valid_drivers}"
)
# General settings validation
elif section == "general":
if setting == "debug_level":
if not (0 <= value <= 3):
raise ValueError(
f"Debug level must be between 0 and 3, got {value}"
)
def parse_setting_args(self, settingArgs):
if settingArgs is None:
return
for optionElem in settingArgs.split(";"):
setting_val_list = []
section_option_list = []
section = ""
option = ""
value = ""
setting_val_list = optionElem.split("=", 1)
if len(setting_val_list) != 2:
continue
if "#" in setting_val_list[0]:
section_option_list = setting_val_list[0].split("#", 1)
elif "." in setting_val_list[0]:
section_option_list = setting_val_list[0].split(".", 1)
elif "," in setting_val_list[0]:
section_option_list = setting_val_list[0].split(",", 1)
elif "!" in setting_val_list[0]:
section_option_list = setting_val_list[0].split("!", 1)
else:
continue
if len(section_option_list) != 2:
continue
section = str(section_option_list[0])
option = str(section_option_list[1])
value = str(setting_val_list[1])
self.set_option_arg_dict(section, option, value)
def init_fenrir_config(
self, cliArgs, fenrir_manager=None, environment=environment.environment
):
settings_root = "/etc/fenrirscreenreader/"
settings_file = cliArgs.setting
sound_root = "/usr/share/sounds/fenrirscreenreader/"
# get fenrir settings root
if not os.path.exists(settings_root):
if os.path.exists(fenrir_path + "/../../config/"):
settings_root = fenrir_path + "/../../config/"
else:
return None
# get settings file
if settings_file is None or not os.path.exists(settings_file):
if os.path.exists(settings_root + "/settings/settings.conf"):
settings_file = settings_root + "/settings/settings.conf"
else:
return None
# get sound themes root
if not os.path.exists(sound_root):
if os.path.exists(fenrir_path + "/../../config/sound/"):
sound_root = fenrir_path + "/../../config/sound/"
environment["runtime"]["SettingsManager"] = self
environment["runtime"]["SettingsManager"].initialize(environment)
valid_config = environment["runtime"]["SettingsManager"].load_settings(
settings_file
)
if not valid_config:
return None
if cliArgs.options != "":
self.parse_setting_args(cliArgs.options)
if cliArgs.debug:
self.set_setting("general", "debug_level", 3)
if cliArgs.print:
self.set_setting("general", "debug_level", 3)
self.set_setting("general", "debug_mode", "PRINT")
if cliArgs.emulated_pty:
# Set PTY driver settings
pty_settings = {
"screen": {"driver": "ptyDriver"},
"keyboard": {"driver": "ptyDriver", "keyboardLayout": "pty"}
}
for section, settings in pty_settings.items():
for key, value in settings.items():
self.set_setting(section, key, value)
if cliArgs.emulated_evdev:
self.set_setting("screen", "driver", "ptyDriver")
self.set_setting("keyboard", "driver", "evdevDriver")
self.set_fenrir_keys(self.get_setting("general", "fenrirKeys"))
self.set_script_keys(self.get_setting("general", "scriptKeys"))
environment["runtime"]["DebugManager"] = debugManager.DebugManager(
self.env["runtime"]["SettingsManager"].get_setting(
"general", "debugFile"
)
)
environment["runtime"]["DebugManager"].initialize(environment)
if cliArgs.force_all_screens:
environment["runtime"]["force_all_screens"] = True
if cliArgs.ignore_screen:
current_ignore_screen = self.get_setting("screen", "ignoreScreen")
if current_ignore_screen:
ignore_screens = (
current_ignore_screen.split(",") + cliArgs.ignore_screen
)
else:
ignore_screens = cliArgs.ignore_screen
self.set_setting(
"screen", "ignoreScreen", ",".join(ignore_screens)
)
if not os.path.exists(
self.get_setting("sound", "theme") + "/soundicons.conf"
):
if os.path.exists(sound_root + self.get_setting("sound", "theme")):
self.set_setting(
"sound",
"theme",
sound_root + self.get_setting("sound", "theme"),
)
if os.path.exists(
self.get_setting("sound", "theme") + "/soundicons.conf"
):
environment["runtime"]["SettingsManager"].load_sound_icons(
self.get_setting("sound", "theme"), environment
)
else:
environment["runtime"]["SettingsManager"].load_sound_icons(
self.get_setting("sound", "theme"), environment
)
environment["runtime"][
"PunctuationManager"
] = punctuationManager.PunctuationManager()
environment["runtime"]["PunctuationManager"].initialize(environment)
environment["runtime"]["TextManager"] = textManager.TextManager()
environment["runtime"]["TextManager"].initialize(environment)
if not os.path.exists(
self.get_setting("general", "punctuationProfile")
):
if os.path.exists(
settings_root
+ "punctuation/"
+ self.get_setting("general", "punctuationProfile")
):
self.set_setting(
"general",
"punctuationProfile",
settings_root
+ "punctuation/"
+ self.get_setting("general", "punctuationProfile"),
)
environment["runtime"]["PunctuationManager"].load_dicts(
self.get_setting("general", "punctuationProfile")
)
if os.path.exists(
settings_root
+ "punctuation/"
+ self.get_setting("general", "punctuationProfile")
+ ".conf"
):
self.set_setting(
"general",
"punctuationProfile",
settings_root
+ "punctuation/"
+ self.get_setting("general", "punctuationProfile")
+ ".conf",
)
environment["runtime"]["PunctuationManager"].load_dicts(
self.get_setting("general", "punctuationProfile")
)
else:
environment["runtime"]["PunctuationManager"].load_dicts(
self.get_setting("general", "punctuationProfile")
)
if fenrir_manager:
environment["runtime"]["FenrirManager"] = fenrir_manager
environment["runtime"]["MemoryManager"] = memoryManager.MemoryManager()
environment["runtime"]["MemoryManager"].initialize(environment)
environment["runtime"][
"AttributeManager"
] = attributeManager.AttributeManager()
environment["runtime"]["AttributeManager"].initialize(environment)
environment["runtime"]["EventManager"] = eventManager.EventManager()
environment["runtime"]["EventManager"].initialize(environment)
environment["runtime"][
"ProcessManager"
] = processManager.ProcessManager()
environment["runtime"]["ProcessManager"].initialize(environment)
environment["runtime"]["OutputManager"] = outputManager.OutputManager()
environment["runtime"]["OutputManager"].initialize(environment)
environment["runtime"]["ByteManager"] = byteManager.ByteManager()
environment["runtime"]["ByteManager"].initialize(environment)
environment["runtime"]["InputManager"] = inputManager.InputManager()
environment["runtime"]["InputManager"].initialize(environment)
environment["runtime"]["ScreenManager"] = screenManager.ScreenManager()
environment["runtime"]["ScreenManager"].initialize(environment)
environment["runtime"][
"CommandManager"
] = commandManager.CommandManager()
environment["runtime"]["CommandManager"].initialize(environment)
environment["runtime"]["HelpManager"] = helpManager.HelpManager()
environment["runtime"]["HelpManager"].initialize(environment)
environment["runtime"]["RemoteManager"] = remoteManager.RemoteManager()
environment["runtime"]["RemoteManager"].initialize(environment)
if environment["runtime"]["InputManager"].get_shortcut_type() == "KEY":
if not os.path.exists(
self.get_setting("keyboard", "keyboardLayout")
):
if os.path.exists(
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout")
):
self.set_setting(
"keyboard",
"keyboardLayout",
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout"),
)
environment["runtime"]["InputManager"].load_shortcuts(
self.get_setting("keyboard", "keyboardLayout")
)
if os.path.exists(
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout")
+ ".conf"
):
self.set_setting(
"keyboard",
"keyboardLayout",
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout")
+ ".conf",
)
environment["runtime"]["InputManager"].load_shortcuts(
self.get_setting("keyboard", "keyboardLayout")
)
else:
environment["runtime"]["InputManager"].load_shortcuts(
self.get_setting("keyboard", "keyboardLayout")
)
elif (
environment["runtime"]["InputManager"].get_shortcut_type()
== "BYTE"
):
if not os.path.exists(
self.get_setting("keyboard", "keyboardLayout")
):
if os.path.exists(
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout")
):
self.set_setting(
"keyboard",
"keyboardLayout",
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout"),
)
environment["runtime"]["ByteManager"].load_byte_shortcuts(
self.get_setting("keyboard", "keyboardLayout")
)
if os.path.exists(
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout")
+ ".conf"
):
self.set_setting(
"keyboard",
"keyboardLayout",
settings_root
+ "keyboard/"
+ self.get_setting("keyboard", "keyboardLayout")
+ ".conf",
)
environment["runtime"]["ByteManager"].load_byte_shortcuts(
self.get_setting("keyboard", "keyboardLayout")
)
else:
environment["runtime"]["ByteManager"].load_byte_shortcuts(
self.get_setting("keyboard", "keyboardLayout")
)
environment["runtime"]["CursorManager"] = cursorManager.CursorManager()
environment["runtime"]["CursorManager"].initialize(environment)
environment["runtime"][
"ApplicationManager"
] = applicationManager.ApplicationManager()
environment["runtime"]["ApplicationManager"].initialize(environment)
environment["runtime"]["TextManager"] = textManager.TextManager()
environment["runtime"]["TextManager"].initialize(environment)
environment["runtime"]["TableManager"] = tableManager.TableManager()
environment["runtime"]["TableManager"].initialize(environment)
environment["runtime"][
"BarrierManager"
] = barrierManager.BarrierManager()
environment["runtime"]["BarrierManager"].initialize(environment)
environment["runtime"]["SayAllManager"] = sayAllManager.SayAllManager()
environment["runtime"]["SayAllManager"].initialize(environment)
environment["runtime"]["VmenuManager"] = vmenuManager.VmenuManager()
environment["runtime"]["VmenuManager"].initialize(environment)
environment["runtime"][
"QuickMenuManager"
] = quickMenuManager.QuickMenuManager()
environment["runtime"]["QuickMenuManager"].initialize(environment)
# only possible after having input and screen managers with clean
# buffer
environment["runtime"]["InputManager"].write_event_buffer()
environment["runtime"]["InputManager"].handle_device_grab(force=True)
environment["runtime"]["DebugManager"].write_debug_out(
r"/-------environment-------/",
debug.DebugLevel.INFO,
on_any_level=True,
)
environment["runtime"]["DebugManager"].write_debug_out(
str(environment), debug.DebugLevel.INFO, on_any_level=True
)
environment["runtime"]["DebugManager"].write_debug_out(
r"/-------settings.conf-------/",
debug.DebugLevel.INFO,
on_any_level=True,
)
environment["runtime"]["DebugManager"].write_debug_out(
str(environment["settings"]._sections),
debug.DebugLevel.INFO,
on_any_level=True,
)
environment["runtime"]["DebugManager"].write_debug_out(
r"/-------self.settingArgDict-------/",
debug.DebugLevel.INFO,
on_any_level=True,
)
environment["runtime"]["DebugManager"].write_debug_out(
str(self.settingArgDict), debug.DebugLevel.INFO, on_any_level=True
)
self.bindingsBackup = environment["bindings"].copy()
return environment