Compare commits
	
		
			3 Commits
		
	
	
		
			7514744cdd
			...
			0b4b2c6a84
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0b4b2c6a84 | ||
|  | 0b2536e0e3 | ||
|  | 8d8f5dc613 | 
							
								
								
									
										195
									
								
								tools/configure_fenrir.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										195
									
								
								tools/configure_fenrir.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,195 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  | import configparser | ||||||
|  | import dialog | ||||||
|  | from typing import Dict, List, Optional | ||||||
|  | import subprocess | ||||||
|  |  | ||||||
|  | class FenrirConfig: | ||||||
|  |     def __init__(self): | ||||||
|  |         os.environ['DIALOGOPTS'] = '--no-lines --visit-items' | ||||||
|  |         self.tui = dialog.Dialog(dialog="dialog") | ||||||
|  |         self.settingsFile = '/etc/fenrirscreenreader/settings/settings.conf' | ||||||
|  |          | ||||||
|  |         # Check if we need to re-run with elevated privileges | ||||||
|  |         if not self.check_root(): | ||||||
|  |             self.escalate_privileges() | ||||||
|  |             sys.exit(0) | ||||||
|  |          | ||||||
|  |         # Navigation instructions for different dialog types | ||||||
|  |         self.instructions = { | ||||||
|  |             'menu': "\nNavigation: Use Up/Down arrows to move, Enter to select, Escape to go back", | ||||||
|  |             'radiolist': "\nNavigation: Use Up/Down arrows to move, Space to select option, Enter to confirm, Escape to cancel", | ||||||
|  |             'inputbox': "\nEnter your value and press Enter to confirm, or Escape to cancel" | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         # Predefined options for certain settings | ||||||
|  |         self.presetOptions = { | ||||||
|  |             # Drivers | ||||||
|  |             'sound.driver': ['genericDriver', 'gstreamerDriver'], | ||||||
|  |             'speech.driver': ['speechdDriver', 'genericDriver'], | ||||||
|  |             'braille.driver': ['dummyDriver', 'brailttyDriver', 'brlapiDriver'], | ||||||
|  |             'screen.driver': ['vcsaDriver', 'dummyDriver', 'ptyDriver', 'debugDriver'], | ||||||
|  |             'keyboard.driver': ['evdevDriver', 'dummyDriver'], | ||||||
|  |             'remote.driver': ['unixDriver', 'tcpDriver'], | ||||||
|  |             # Other preset options | ||||||
|  |             'braille.flushMode': ['word', 'char', 'fix', 'none'], | ||||||
|  |             'braille.cursorFocusMode': ['page', 'fixCell'], | ||||||
|  |             'braille.cursorFollowMode': ['review', 'last', 'none'], | ||||||
|  |             'keyboard.charEchoMode': ['0', '1', '2'], | ||||||
|  |             'general.punctuationLevel': ['none', 'some', 'most', 'all'], | ||||||
|  |             'general.debugMode': ['File', 'Print'] | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         # Help text for certain options | ||||||
|  |         self.helpText = { | ||||||
|  |             'sound.volume': 'Volume level from 0 (quietest) to 1.0 (loudest)', | ||||||
|  |             'speech.rate': 'Speech rate from 0 (slowest) to 1.0 (fastest)', | ||||||
|  |             'speech.pitch': 'Voice pitch from 0 (lowest) to 1.0 (highest)', | ||||||
|  |             'keyboard.charEchoMode': '0 = None, 1 = always, 2 = only while capslock', | ||||||
|  |             'braille.flushMode': 'word = flush after words, char = flush after chars, fix = flush after time, none = manual flush' | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     def check_root(self) -> bool: | ||||||
|  |         """Check if the script is running with root privileges""" | ||||||
|  |         return os.geteuid() == 0 | ||||||
|  |  | ||||||
|  |     def find_privilege_escalation_tool(self) -> Optional[str]: | ||||||
|  |         """Find available privilege escalation tool (sudo or doas)""" | ||||||
|  |         for tool in ['sudo', 'doas']: | ||||||
|  |             if subprocess.run(['which', tool], stdout=subprocess.PIPE).returncode == 0: | ||||||
|  |                 return tool | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  |     def escalate_privileges(self): | ||||||
|  |         """Re-run the script with elevated privileges""" | ||||||
|  |         tool = self.find_privilege_escalation_tool() | ||||||
|  |         if not tool: | ||||||
|  |             self.tui.msgbox("Error: Neither sudo nor doas found. Please run this script as root.") | ||||||
|  |             sys.exit(1) | ||||||
|  |              | ||||||
|  |         try: | ||||||
|  |             scriptPath = os.path.abspath(sys.argv[0]) | ||||||
|  |             command = [tool, sys.executable, scriptPath] + sys.argv[1:] | ||||||
|  |             os.execvp(tool, command) | ||||||
|  |         except Exception as e: | ||||||
|  |             self.tui.msgbox(f"Error escalating privileges: {str(e)}") | ||||||
|  |             sys.exit(1) | ||||||
|  |  | ||||||
|  |     def check_permissions(self) -> bool: | ||||||
|  |         """Check if we have write permissions to the settings file""" | ||||||
|  |         if not os.access(self.settingsFile, os.W_OK): | ||||||
|  |             self.tui.msgbox("Error: Insufficient permissions to modify the settings file even with root privileges.") | ||||||
|  |             return False | ||||||
|  |         return True | ||||||
|  |  | ||||||
|  |     def is_boolean_option(self, value: str) -> bool: | ||||||
|  |         """Check if the current value is likely a boolean option""" | ||||||
|  |         return value.lower() in ['true', 'false'] | ||||||
|  |  | ||||||
|  |     def validate_input(self, section: str, option: str, value: str) -> tuple[bool, str]: | ||||||
|  |         """Validate user input based on the option type and constraints""" | ||||||
|  |         try: | ||||||
|  |             if option.endswith('volume') or option.endswith('rate') or option.endswith('pitch'): | ||||||
|  |                 floatVal = float(value) | ||||||
|  |                 if not 0 <= floatVal <= 1.0: | ||||||
|  |                     return False, "Value must be between 0 and 1.0" | ||||||
|  |             return True, value | ||||||
|  |         except ValueError: | ||||||
|  |             return False, "Invalid number format" | ||||||
|  |  | ||||||
|  |     def get_value_with_presets(self, section: str, option: str, currentValue: str) -> Optional[str]: | ||||||
|  |         """Get value using appropriate input method based on option type""" | ||||||
|  |         key = f"{section}.{option}" | ||||||
|  |          | ||||||
|  |         # Handle boolean options | ||||||
|  |         if self.is_boolean_option(currentValue): | ||||||
|  |             choices = [ | ||||||
|  |                 ('True', '', currentValue.lower() == 'true'), | ||||||
|  |                 ('False', '', currentValue.lower() == 'false') | ||||||
|  |             ] | ||||||
|  |             code, tag = self.tui.radiolist( | ||||||
|  |                 f"Select value for '{option}'" + self.instructions['radiolist'], | ||||||
|  |                 choices=choices | ||||||
|  |             ) | ||||||
|  |             return tag if code == self.tui.OK else None | ||||||
|  |          | ||||||
|  |         # Handle other preset options | ||||||
|  |         elif key in self.presetOptions: | ||||||
|  |             choices = [(opt, "", opt == currentValue) for opt in self.presetOptions[key]] | ||||||
|  |             code, tag = self.tui.radiolist( | ||||||
|  |                 f"Select value for '{option}'" + | ||||||
|  |                 (f"\n{self.helpText[key]}" if key in self.helpText else "") + | ||||||
|  |                 self.instructions['radiolist'], | ||||||
|  |                 choices=choices | ||||||
|  |             ) | ||||||
|  |             return tag if code == self.tui.OK else None | ||||||
|  |          | ||||||
|  |         # Handle free-form input | ||||||
|  |         else: | ||||||
|  |             helpText = self.helpText.get(key, "") | ||||||
|  |             code, value = self.tui.inputbox( | ||||||
|  |                 f"Enter value for '{option}'" + | ||||||
|  |                 (f"\n{helpText}" if helpText else "") + | ||||||
|  |                 self.instructions['inputbox'], | ||||||
|  |                 init=currentValue | ||||||
|  |             ) | ||||||
|  |             if code == self.tui.OK: | ||||||
|  |                 isValid, message = self.validate_input(section, option, value) | ||||||
|  |                 if not isValid: | ||||||
|  |                     self.tui.msgbox(f"Invalid input: {message}") | ||||||
|  |                     return None | ||||||
|  |                 return value | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         if not self.check_permissions(): | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         while True: | ||||||
|  |             config = configparser.ConfigParser() | ||||||
|  |             config.read(self.settingsFile) | ||||||
|  |             sections = config.sections() | ||||||
|  |  | ||||||
|  |             code, section = self.tui.menu( | ||||||
|  |                 "Select a section:" + self.instructions['menu'], | ||||||
|  |                 choices=[(s, "") for s in sections] + [("Exit", " ")] | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             if section == "Exit": | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |             while True: | ||||||
|  |                 options = config.options(section) | ||||||
|  |                 choices = [(o, f"Current: {config.get(section, o)}") for o in options] | ||||||
|  |                 choices.append(("Go Back", " ")) | ||||||
|  |  | ||||||
|  |                 code, option = self.tui.menu( | ||||||
|  |                     f"Select option to edit in '{section}':" + self.instructions['menu'], | ||||||
|  |                     choices=choices | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |                 if option == "Go Back": | ||||||
|  |                     break | ||||||
|  |  | ||||||
|  |                 if code == self.tui.OK: | ||||||
|  |                     currentValue = config.get(section, option) | ||||||
|  |                     newValue = self.get_value_with_presets(section, option, currentValue) | ||||||
|  |  | ||||||
|  |                     if newValue is not None and newValue != currentValue: | ||||||
|  |                         config.set(section, option, newValue) | ||||||
|  |                         with open(self.settingsFile, 'w') as configfile: | ||||||
|  |                             config.write(configfile) | ||||||
|  |                         self.tui.msgbox("Settings saved successfully.") | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     configTool = FenrirConfig() | ||||||
|  |     try: | ||||||
|  |         configTool.run() | ||||||
|  |     except (configparser.Error, dialog.error) as e: | ||||||
|  |         sys.exit(0) | ||||||
|  |     except Exception as e: | ||||||
|  |         print(f"Unexpected error occurred: {str(e)}", file=sys.stderr) | ||||||
|  |         sys.exit(1) | ||||||
| @@ -1,66 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
|                                                                                                                                                                  |  | ||||||
| # Fenrir TTY screen reader |  | ||||||
| # By Chrys, Storm Dragon, and contributers. |  | ||||||
|  |  | ||||||
| import os |  | ||||||
| import configparser |  | ||||||
| import dialog |  | ||||||
|  |  | ||||||
| # Make sure dialog is accessible |  | ||||||
| os.environ['DIALOGOPTS'] = '--no-lines --visit-items' |  | ||||||
| # Initialize the dialog |  | ||||||
| tui = dialog.Dialog(dialog="dialog") |  | ||||||
|  |  | ||||||
| # Define the path to the settings file |  | ||||||
| settings_file = '/etc/fenrirscreenreader/settings/settings.conf' |  | ||||||
|  |  | ||||||
| # Check write permissions for the settings file |  | ||||||
| if not os.access(settings_file, os.W_OK): |  | ||||||
|     tui.msgbox("Error: Insufficient permissions to modify the settings file. Please run as root or with sudo.") |  | ||||||
|     exit() |  | ||||||
|  |  | ||||||
| while True: |  | ||||||
|     # Load the settings file |  | ||||||
|     config = configparser.ConfigParser() |  | ||||||
|     config.read(settings_file) |  | ||||||
|  |  | ||||||
|     # Get a list of sections in the settings file |  | ||||||
|     sections = config.sections() |  | ||||||
|  |  | ||||||
|     # Select a section. |  | ||||||
|     code, section = tui.menu("Select a section:", choices=[(s, "") for s in sections] + [("Exit", " ")]) |  | ||||||
|  |  | ||||||
|     # Exit if the "Exit" option is chosen |  | ||||||
|     if section == "Exit": |  | ||||||
|         break |  | ||||||
|  |  | ||||||
|     while True: |  | ||||||
|         # Get the options in the selected section |  | ||||||
|         options = config.options(section) |  | ||||||
|  |  | ||||||
|         # Select a value to edit using dialog |  | ||||||
|         code, option = tui.menu(f"Select a value to edit in '{section}':", choices=[(o, "") for o in options] + [("Go Back", " ")]) |  | ||||||
|  |  | ||||||
|         # Go back to the section menu if the "Go Back" option is chosen |  | ||||||
|         if option == "Go Back": |  | ||||||
|             break |  | ||||||
|  |  | ||||||
|         # If something is selected, prompt for a new value. |  | ||||||
|         if code == tui.OK: |  | ||||||
|             value = config.get(section, option) |  | ||||||
|             code, new_value = tui.inputbox(f"Enter a new value for '{option}':", init=value) |  | ||||||
|  |  | ||||||
|             # If a new setting is provided, update the configuration |  | ||||||
|             if code == tui.OK: |  | ||||||
|                 config.set(section, option, new_value) |  | ||||||
|  |  | ||||||
|                 # Save changes. |  | ||||||
|                 with open(settings_file, 'w') as configfile: |  | ||||||
|                     config.write(configfile) |  | ||||||
|  |  | ||||||
|                 tui.msgbox("Fenrir settings saved.") |  | ||||||
|             else: |  | ||||||
|                 tui.msgbox("Changes discarded. Your Fenrir configuration has not been modified.") |  | ||||||
|         else: |  | ||||||
|             tui.msgbox("Canceled.") |  | ||||||
		Reference in New Issue
	
	Block a user