Compare commits
No commits in common. "0b2536e0e39ddcfb0e00f95e7503053757a07f80" and "7514744cdd726a346046869ee32ed4ace6bc3d4d" have entirely different histories.
0b2536e0e3
...
7514744cdd
@ -1,195 +0,0 @@
|
|||||||
#!/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)
|
|
66
tools/fenrir-conf
Executable file
66
tools/fenrir-conf
Executable file
@ -0,0 +1,66 @@
|
|||||||
|
#!/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.")
|
Loading…
Reference in New Issue
Block a user