#!/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