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:
@ -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)
|
||||
|
Reference in New Issue
Block a user