combined configure_fenrir and configure_speechd into a single script. This is the go-to for editing Fenrir settings without doing it by hand.

This commit is contained in:
Storm Dragon
2024-12-05 06:06:03 -05:00
parent 6785fde7c9
commit af857d7976
2 changed files with 153 additions and 279 deletions

View File

@ -4,10 +4,13 @@ import os
import sys
import configparser
import dialog
from typing import Dict, List, Optional
import subprocess
import time
import select
import tempfile
from typing import Dict, List, Optional, Tuple
class FenrirConfig:
class FenrirConfigTool:
def __init__(self):
os.environ['DIALOGOPTS'] = '--no-lines --visit-items'
self.tui = dialog.Dialog(dialog="dialog")
@ -17,24 +20,21 @@ class FenrirConfig:
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
# Configuration presets and help text from original FenrirConfig
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'],
@ -43,7 +43,6 @@ class FenrirConfig:
'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)',
@ -53,23 +52,20 @@ class FenrirConfig:
}
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:]
@ -78,13 +74,6 @@ class FenrirConfig:
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']
@ -144,52 +133,179 @@ class FenrirConfig:
return value
return None
def run(self):
if not self.check_permissions():
def run_command(self, cmd: List[str], needsRoot: bool = False) -> Optional[str]:
try:
if needsRoot and not self.check_root():
tool = self.find_privilege_escalation_tool()
if tool:
cmd = [tool] + cmd
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout.strip() if result.returncode == 0 else None
except Exception as e:
self.tui.msgbox(f"Error running command {' '.join(cmd)}: {e}")
return None
def get_speechd_modules(self) -> List[str]:
output = self.run_command(['spd-say', '-O'], True)
if output:
lines = output.split('\n')
return [line.strip() for line in lines[1:] if line.strip()]
return []
def process_espeak_voice(self, voiceLine: str) -> Optional[str]:
parts = [p for p in voiceLine.split() if p]
if len(parts) < 2:
return None
langCode = parts[-2].lower()
variant = parts[-1].lower()
return f"{langCode}+{variant}" if variant and variant != 'none' else langCode
def get_module_voices(self, moduleName: str) -> List[str]:
output = self.run_command(['spd-say', '-o', moduleName, '-L'], True)
if output:
lines = output.split('\n')
voices = []
for line in lines[1:]:
if not line.strip():
continue
if moduleName.lower() == 'espeak-ng':
voice = self.process_espeak_voice(line)
if voice:
voices.append(voice)
else:
voices.append(line.strip())
return voices
return []
def configure_speech(self) -> None:
moduleList = self.get_speechd_modules()
if not moduleList:
self.tui.msgbox("No speech-dispatcher modules found!")
return
code, moduleChoice = self.tui.menu(
"Select speech module:" + self.instructions['menu'],
choices=[(module, "") for module in moduleList]
)
if code != self.tui.OK:
return
voiceList = self.get_module_voices(moduleChoice)
if not voiceList:
self.tui.msgbox(f"No voices found for module {moduleChoice}")
return
code, voice = self.tui.menu(
f"Select voice for {moduleChoice}:" + self.instructions['menu'],
choices=[(v, "") for v in voiceList]
)
if code != self.tui.OK:
return
# Test voice configuration
if self.test_voice(moduleChoice, voice):
config = configparser.ConfigParser()
config.read(self.settingsFile)
if 'speech' not in config:
config['speech'] = {}
config['speech'].update({
'driver': 'speechdDriver',
'module': moduleChoice,
'voice': voice,
'enabled': 'True',
'rate': '0.25',
'pitch': '0.5',
'volume': '1.0'
})
with open(self.settingsFile, 'w') as configfile:
config.write(configfile)
self.tui.msgbox("Speech configuration updated successfully!\nPlease restart Fenrir for changes to take effect.")
def test_voice(self, moduleName: str, voiceName: str) -> bool:
testMessage = "If you hear this message, press Enter within 30 seconds to confirm."
try:
process = subprocess.Popen(
['spd-say', '-o', moduleName, '-y', voiceName, testMessage]
)
code = self.tui.pause(
"Waiting for voice test...\n"
"Press Enter if you hear the message, or wait for timeout.",
30
)
process.terminate()
return code == self.tui.OK
except Exception as e:
self.tui.msgbox(f"Error testing voice: {e}")
return False
def edit_general_config(self) -> None:
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", " ")]
"Select a section to configure:" + self.instructions['menu'],
choices=[(s, "") for s in sections] + [("Go Back", "")]
)
if section == "Exit":
if code != self.tui.OK or section == "Go Back":
break
while True:
options = config.options(section)
choices = [(o, f"Current: {config.get(section, o)}") for o in options]
choices.append(("Go Back", " "))
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":
if code != self.tui.OK or option == "Go Back":
break
if code == self.tui.OK:
currentValue = config.get(section, option)
newValue = self.get_value_with_presets(section, option, currentValue)
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 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("Setting updated successfully.")
def run(self):
while True:
code, choice = self.tui.menu(
"Fenrir Configuration Tool" + self.instructions['menu'],
choices=[
("speech-dispatcher", "Configure module and voice"),
("Advanced", "Edit Fenrir settings"),
("Exit", "")
]
)
if code != self.tui.OK or choice == "Exit":
break
if choice == "speech-dispatcher":
self.configure_speech()
elif choice == "Advanced":
self.edit_general_config()
if __name__ == "__main__":
configTool = FenrirConfig()
configTool = FenrirConfigTool()
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)