Menu modifications.

This commit is contained in:
Storm Dragon
2026-04-22 21:26:05 -04:00
parent 0c4bbed905
commit 2f45f9edab
7 changed files with 710 additions and 248 deletions
+1
View File
@@ -0,0 +1 @@
/home/stormux/.local/etc/speech-dispatcher/speechd.conf
+182 -69
View File
@@ -164,6 +164,7 @@ class VoicedMenu:
# Load downloadable games registry # Load downloadable games registry
self.downloadable_games = self.load_downloadable_games() self.downloadable_games = self.load_downloadable_games()
self.savedMenuState = None self.savedMenuState = None
self.menuStateStack = []
self.activeGameEntry = None self.activeGameEntry = None
def load_downloadable_games(self): def load_downloadable_games(self):
@@ -573,25 +574,65 @@ class VoicedMenu:
except Exception as e: except Exception as e:
self.speak(f"Error reading battery status: {e}") self.speak(f"Error reading battery status: {e}")
def handle_sigint(self, _signum, _frame):
"""Make Ctrl+C behave like Escape inside the menu."""
self.handle_escape_or_restart()
def update_service_menu_items(self): def update_service_menu_items(self):
"""Update all service-related menu items based on their current status""" """Update all service-related menu items based on their current status"""
# Remove any existing service items if self.sectionNames != ["Services"]:
if "System" in self.menuSections: return
# Find and remove any service-related items
self.menuSections["System"] = [item for item in self.menuSections["System"]
if not any(service in item[0] for service in self.systemMenuServices.keys())]
# Add the appropriate items based on current status for each service self.menuSections["Services"] = self.build_services_menu_actions() + [("Back", self.restore_saved_menu)]
def build_service_menu_items(self):
"""Build service toggle items based on current service state."""
items = []
for friendlyName, serviceName in self.systemMenuServices.items(): for friendlyName, serviceName in self.systemMenuServices.items():
isActive = self.check_service_status(serviceName) isActive = self.check_service_status(serviceName)
startLabel = "Start" if friendlyName == "Fenrir Screen Reader" else "Enable" startLabel = "Start" if friendlyName == "Fenrir Screen Reader" else "Enable"
stopLabel = "Stop" if friendlyName == "Fenrir Screen Reader" else "Disable" stopLabel = "Stop" if friendlyName == "Fenrir Screen Reader" else "Disable"
if isActive: if isActive:
self.add_item("System", f"{stopLabel} {friendlyName}", items.append((f"{stopLabel} {friendlyName}", lambda fn=friendlyName: self.toggle_service(fn)))
lambda fn=friendlyName: self.toggle_service(fn))
else: else:
self.add_item("System", f"{startLabel} {friendlyName}", items.append((f"{startLabel} {friendlyName}", lambda fn=friendlyName: self.toggle_service(fn)))
lambda fn=friendlyName: self.toggle_service(fn)) return items
def build_services_menu_actions(self):
"""Build the system services submenu."""
return self.build_service_menu_items() + [
("Enable Screen", lambda: self.toggle_screen("screen")),
("Disable Screen", lambda: self.toggle_screen("headless")),
("Battery Status", self.report_battery_status),
]
def show_services_menu(self):
"""Show service toggles and related runtime controls."""
self.show_action_menu("Services", self.build_services_menu_actions())
def show_settings_menu(self):
"""Show system settings and configuration actions."""
actions = []
if platform.machine() == "x86_64" and not os.path.exists("/home/stormux/.baremetal"):
actions.append(("Install System to Hard Drive", "GAME='Install to Disk' /home/stormux/.clirc"))
actions.extend([
("Internet Configuration", "GAME=\"Network Configuration\" /home/stormux/.clirc"),
("Set System Speech Settings", "/home/stormux/.local/bin/speechd_rate.py"),
("Set Default Voice", "/home/stormux/.local/bin/set-voice.py"),
("Set Timezone", "GAME='Set Timezone' /home/stormux/.clirc"),
("Download Files", "/home/stormux/.local/download_server.py"),
("Resize to fill empty space on disk", "sudo growpartfs $(df --output='source' / | tail -1)"),
])
self.show_action_menu("Settings", actions)
def show_power_menu(self):
"""Show power and update actions."""
self.show_action_menu("Power", [
("System Update", "/home/stormux/.local/bin/system-update.sh"),
("Restart: Can Take Several Minutes", "sudo reboot"),
("Power Off: Wait 2 Minutes Before Disconnecting Power", "sudo poweroff"),
])
def install_and_launch(self, executable_name, launch_mode="gui"): def install_and_launch(self, executable_name, launch_mode="gui"):
"""Launch application via xinitrc (which handles installation if needed)""" """Launch application via xinitrc (which handles installation if needed)"""
@@ -706,7 +747,7 @@ class VoicedMenu:
def save_menu_state(self): def save_menu_state(self):
"""Save the current menu state so a temporary submenu can restore it.""" """Save the current menu state so a temporary submenu can restore it."""
self.savedMenuState = { menuState = {
"sectionNames": self.sectionNames.copy(), "sectionNames": self.sectionNames.copy(),
"currentSection": self.currentSection, "currentSection": self.currentSection,
"currentItemIndices": self.currentItemIndices.copy(), "currentItemIndices": self.currentItemIndices.copy(),
@@ -715,18 +756,22 @@ class VoicedMenu:
for sectionName, items in self.menuSections.items() for sectionName, items in self.menuSections.items()
}, },
} }
self.menuStateStack.append(menuState)
self.savedMenuState = menuState
def restore_saved_menu(self): def restore_saved_menu(self):
"""Restore the menu state saved before entering a temporary submenu.""" """Restore the menu state saved before entering a temporary submenu."""
if self.savedMenuState is None: if not self.menuStateStack:
return return
self.sectionNames = self.savedMenuState["sectionNames"] menuState = self.menuStateStack.pop()
self.currentSection = self.savedMenuState["currentSection"] self.sectionNames = menuState["sectionNames"]
self.currentItemIndices = self.savedMenuState["currentItemIndices"] self.currentSection = menuState["currentSection"]
self.menuSections = self.savedMenuState["menuSections"] self.currentItemIndices = menuState["currentItemIndices"]
self.savedMenuState = None self.menuSections = menuState["menuSections"]
self.activeGameEntry = None self.savedMenuState = self.menuStateStack[-1] if self.menuStateStack else None
if not self.menuStateStack:
self.activeGameEntry = None
self.draw_menu() self.draw_menu()
self.announce_current_section() self.announce_current_section()
time.sleep(0.5) time.sleep(0.5)
@@ -774,12 +819,106 @@ class VoicedMenu:
def handle_escape(self): def handle_escape(self):
"""Handle Escape based on whether a temporary submenu is active.""" """Handle Escape based on whether a temporary submenu is active."""
if self.savedMenuState is None: if not self.menuStateStack:
return False return False
self.restore_saved_menu() self.restore_saved_menu()
return True return True
def handle_escape_or_restart(self):
"""Go back from submenus, otherwise restart the menu."""
if self.handle_escape():
return
try:
# Announce restart first while speech still works
self.speak("Restarting menu")
# Wait for speech to complete
time.sleep(1)
# Now clean up resources
self.cleanup()
# Try to kill speech-dispatcher if needed
subprocess.run(["sudo", "killall", "speech-dispatcher"], check=False)
# Restart the application
os.execv(sys.argv[0], sys.argv)
except Exception as e:
print(f"Error during restart: {e}")
# If restart fails, we need to recover the UI
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
self.stdscr.keypad(True)
self.draw_menu()
def jump_to_item_by_letter(self, letter):
"""Move to the next item in the current menu starting with letter."""
if not letter or not self.sectionNames:
return False
letter = letter.lower()
items = self.get_current_items()
if not items:
return False
currentIndex = self.get_current_item_index()
for offset in range(1, len(items) + 1):
candidateIndex = (currentIndex + offset) % len(items)
itemName = items[candidateIndex][0].strip().lower()
if itemName.startswith(letter):
self.set_current_item_index(candidateIndex)
return True
return False
def jump_to_section_by_letter(self, letter):
"""Move to the next top-level section starting with letter."""
if not letter or self.menuStateStack or not self.sectionNames:
return False
letter = letter.lower()
for offset in range(1, len(self.sectionNames) + 1):
candidateIndex = (self.currentSection + offset) % len(self.sectionNames)
sectionName = self.sectionNames[candidateIndex].strip().lower()
if sectionName.startswith(letter):
self.currentSection = candidateIndex
return True
return False
def handle_letter_navigation(self, key):
"""Handle printable letter navigation and reliable control shortcuts."""
if key == 2: # Ctrl+B
self.report_battery_status()
return True
if key < 0 or key > 255:
return False
keyChar = chr(key)
if not keyChar.isalpha():
return False
moved = False
if keyChar.isupper() and not self.menuStateStack:
moved = self.jump_to_section_by_letter(keyChar)
if moved:
self.draw_menu()
self.play_sound("menu_category")
self.announce_current_section()
time.sleep(0.5)
self.announce_current_item(interrupt=False)
else:
moved = self.jump_to_item_by_letter(keyChar)
if moved:
self.draw_menu()
self.play_sound("menu_move")
self.announce_current_item()
return moved
def speak(self, text, interrupt=True): def speak(self, text, interrupt=True):
"""Speak the given text with option to interrupt existing speech""" """Speak the given text with option to interrupt existing speech"""
if self.speechClient is None: if self.speechClient is None:
@@ -986,17 +1125,20 @@ class VoicedMenu:
Down arrow: Next menu item. Down arrow: Next menu item.
Left arrow: Previous section. Left arrow: Previous section.
Right arrow: Next section. Right arrow: Next section.
Lowercase letters: Jump to menu items.
Uppercase letters: Jump to categories from the main menu.
Enter: Launch selected item. Enter: Launch selected item.
H key: Hear these instructions again. H key: Hear these instructions again.
B key: Report battery status. Control B: Report battery status.
Left bracket: Decrease speech rate. Left bracket: Decrease speech rate.
Right bracket: Increase speech rate. Right bracket: Increase speech rate.
Left brace: Decrease speech pitch. Left brace: Decrease speech pitch.
Right brace: Increase speech pitch. Right brace: Increase speech pitch.
9 key: Decrease volume. 9 key: Decrease volume.
0 key: Increase volume. 0 key: Increase volume.
Escape: Refresh the menu. Escape: Go back from submenus or refresh the main menu.
Q: Exit the menu. Q: Exit the menu.
Control C behaves like Escape.
Any key will interrupt speech. Any key will interrupt speech.
""" """
self.speak(helpText) self.speak(helpText)
@@ -1012,7 +1154,7 @@ class VoicedMenu:
self.stdscr.addstr(1, x, title, curses.A_BOLD) self.stdscr.addstr(1, x, title, curses.A_BOLD)
# Draw help line # Draw help line
helpText = "Navigate | Enter: Select | H: Help | [ ] Rate | { } Pitch | 9 0 Volume | Esc: Refresh" helpText = "Navigate | Letters: Jump | Enter: Select | H: Help | Ctrl+B: Battery | Esc: Back"
x = max(0, w // 2 - len(helpText) // 2) x = max(0, w // 2 - len(helpText) // 2)
self.stdscr.addstr(3, x, helpText) self.stdscr.addstr(3, x, helpText)
@@ -1087,6 +1229,7 @@ class VoicedMenu:
curses.noecho() # Don't echo keypresses curses.noecho() # Don't echo keypresses
curses.cbreak() # React to keys instantly curses.cbreak() # React to keys instantly
self.stdscr.keypad(True) # Enable special keys self.stdscr.keypad(True) # Enable special keys
signal.signal(signal.SIGINT, self.handle_sigint)
# Update all service menu items based on current status # Update all service menu items based on current status
self.update_service_menu_items() self.update_service_menu_items()
@@ -1203,36 +1346,16 @@ class VoicedMenu:
elif key == ord('0'): # Increase volume elif key == ord('0'): # Increase volume
self.increase_volume() self.increase_volume()
elif key == ord('b') or key == ord('B'): # Battery status elif key == ord('q') or key == ord('Q'): # Quit
self.report_battery_status() self.speak("Exiting menu")
time.sleep(0.5)
break
elif self.handle_letter_navigation(key):
pass
elif key == 27: # Esc key - go back from submenus, otherwise restart the application elif key == 27: # Esc key - go back from submenus, otherwise restart the application
if self.handle_escape(): self.handle_escape_or_restart()
continue
try:
# Announce restart first while speech still works
self.speak("Restarting menu")
# Wait for speech to complete
time.sleep(1)
# Now clean up resources
self.cleanup()
# Try to kill speech-dispatcher if needed
subprocess.run(["sudo", "killall", "speech-dispatcher"], check=False)
# Restart the application
os.execv(sys.argv[0], sys.argv)
except Exception as e:
print(f"Error during restart: {e}")
# If restart fails, we need to recover the UI
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
self.stdscr.keypad(True)
self.draw_menu()
except Exception as e: except Exception as e:
# End curses in case of error # End curses in case of error
curses.endwin() curses.endwin()
@@ -1241,6 +1364,14 @@ class VoicedMenu:
# Clean up # Clean up
self.cleanup() self.cleanup()
def configure_system_menu(menu):
"""Add the nested System menu."""
menu.add_section("System")
menu.add_item("System", "Settings", menu.show_settings_menu)
menu.add_item("System", "Services", menu.show_services_menu)
menu.add_item("System", "Power", menu.show_power_menu)
# Example usage # Example usage
if __name__ == "__main__": if __name__ == "__main__":
# Create the menu with sections # Create the menu with sections
@@ -1279,25 +1410,7 @@ if __name__ == "__main__":
menu.add_item("Accessories", "LibreOffice", lambda: menu.install_and_launch("libreoffice", "gui")) menu.add_item("Accessories", "LibreOffice", lambda: menu.install_and_launch("libreoffice", "gui"))
menu.add_item("Accessories", "Thunderbird", lambda: menu.install_and_launch("thunderbird", "gui")) menu.add_item("Accessories", "Thunderbird", lambda: menu.install_and_launch("thunderbird", "gui"))
# Add system section configure_system_menu(menu)
menu.add_section("System")
# Add installer only on x86_64 and if not already installed (no .baremetal file)
if platform.machine() == "x86_64" and not os.path.exists("/home/stormux/.baremetal"):
menu.add_item("System", "Install System to Hard Drive", "GAME='Install to Disk' /home/stormux/.clirc")
menu.add_item("System", "Internet Configuration", "GAME=\"Network Configuration\" /home/stormux/.clirc")
menu.add_item("System", "Enable Screen", lambda: menu.toggle_screen("screen"))
menu.add_item("System", "Disable Screen", lambda: menu.toggle_screen("headless"))
menu.add_item("System", "Set System Speech Settings", "/home/stormux/.local/bin/speechd_rate.py")
menu.add_item("System", "Set Default Voice", "/home/stormux/.local/bin/set-voice.py")
menu.add_item("System", "Set Timezone", "GAME='Set Timezone' /home/stormux/.clirc")
menu.add_item("System", "Download Files", "/home/stormux/.local/download_server.py")
menu.add_item("System", "Update System", "sudo /home/stormux/.local/bin/live-update.sh")
menu.add_item("System", "Restart: Can Take Several Minutes", "sudo reboot")
menu.add_item("System", "Power Off: Wait 2 Minutes Before Disconnecting Power", "sudo poweroff")
# Service menu items will be added dynamically in run() method via update_service_menu_items()
menu.add_item("System", "Resize to fill empty space on disk", "sudo growpartfs $(df --output='source' / | tail -1)")
# Run the menu # Run the menu
menu.run() menu.run()
-99
View File
@@ -1,99 +0,0 @@
#!/usr/bin/env bash
# Stop the screen reader if this closes for any reason
trap 'sudo -n /usr/bin/systemctl stop fenrirscreenreader.service' SIGINT SIGTERM SIGHUP EXIT
# Start Fenrir for interaction with the terminal
sudo -n /usr/bin/systemctl start fenrirscreenreader.service
# Clear the screen before loading
clear
# Setup logging
logDir="/home/stormux/Logs"
logFile="${logDir}/system-updates.log"
mkdir -p "${logDir}"
echo "=== System Update Started: $(date) ===" | tee "${logFile}"
# Track errors
errorCount=0
errorMessages=()
# Clean up old packages keeping currently installed versions only
pacman -Sc --noconfirm --quiet 2>&1 | tee -a "${logFile}"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
((errorCount++))
errorMessages+=("Package cleaning failed")
fi
# Upgrade the system
pacman -Syu --noconfirm --quiet 2>&1 | tee -a "${logFile}"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
((errorCount++))
errorMessages+=("Package update failed")
fi
gitUrl="https://git.stormux.org/storm/gaming-image-files"
gitPath="${gitUrl##*/}"
pushd /tmp || exit
git config --global credential.helper store
git clone --quiet "${gitUrl}" 2>&1 | tee -a "${logFile}"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
((errorCount++))
errorMessages+=("Git clone failed")
fi
pushd "${gitPath}" || exit
git checkout --quiet master 2>&1 | tee -a "${logFile}"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
((errorCount++))
errorMessages+=("Git checkout master failed")
fi
git lfs pull 2>&1 | tee -a "${logFile}"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
((errorCount++))
errorMessages+=("Git LFS pull failed")
fi
# Files and directories to ignore when copying
ignoreFiles=(".git" "./image" ".git*" "/home/stormux/.w3m" "/home/stormux/.irssi")
# Build find command with ignore patterns
findArgs=()
for ignore in "${ignoreFiles[@]}"; do
if [[ "$ignore" == .* && "$ignore" != ./* ]]; then
findArgs+=(-name "$ignore" -prune -o)
else
findArgs+=(-path "$ignore" -prune -o)
fi
done
# Copy all files as root (preserves permissions properly)
find . "${findArgs[@]}" -type f -exec bash -c 'for i ; do cp -a --preserve=all --parents "${i}" /;done' _ "{}" \; 2>&1 | tee -a "${logFile}"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
((errorCount++))
errorMessages+=("File copy failed")
fi
# Fix ownership of home directory files (exclude immutable .baremetal)
find /home/stormux -path /home/stormux/.baremetal -prune -o -exec chown -h stormux:users '{}' \; 2>&1 | tee -a "${logFile}"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
((errorCount++))
errorMessages+=("Ownership fix failed")
fi
popd || exit
rm -rf "${gitPath}"
popd || exit
echo "=== System Update Completed: $(date) ===" | tee -a "${logFile}"
echo | tee -a "${logFile}"
# Display summary
if [[ $errorCount -eq 0 ]]; then
echo "SUCCESS: All update operations completed successfully." | tee -a "${logFile}"
else
echo "ERRORS DETECTED: $errorCount error(s) occurred during update:" | tee -a "${logFile}"
for error in "${errorMessages[@]}"; do
echo " - $error" | tee -a "${logFile}"
done
fi
echo | tee -a "${logFile}"
read -r -p "Press enter to continue."
exit 0
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
set -euo pipefail
repoUrl="${STORMUX_UPDATE_REPO:-https://git.stormux.org/storm/gaming-image-files}"
repoBranch="${STORMUX_UPDATE_BRANCH:-testing}"
homeDir="${STORMUX_UPDATE_HOME:-/home/stormux}"
logDir="${homeDir}/Logs"
logFile="${logDir}/system-updates.log"
menuCommand="${STORMUX_MENU_COMMAND:-${homeDir}/.local/bin/game_launcher.py}"
workRoot=""
beepPid=""
runtimeScript="${0}"
if [[ "${STORMUX_UPDATE_SELF_COPY:-0}" != "1" ]]; then
runtimeScript="$(mktemp /tmp/stormux-system-update.XXXXXX)"
cp -f -- "$0" "${runtimeScript}"
chmod 755 "${runtimeScript}"
export STORMUX_UPDATE_SELF_COPY=1
exec "${runtimeScript}" "$@"
fi
speak() {
local message="$1"
local waitForSpeech="${2:-0}"
printf '%s\n' "${message}"
if [[ "$waitForSpeech" == "1" ]]; then
spd-say -Cw "${message}" 2> /dev/null || true
else
spd-say "${message}" 2> /dev/null || true
fi
}
progress_beep() {
[[ "${STORMUX_UPDATE_BEEPS:-1}" == "1" ]] || return 0
command -v play > /dev/null 2>&1 || return 0
while true; do
play -q -n synth 0.06 sine 520 vol 0.35 > /dev/null 2>&1 || true
sleep 2
done
}
start_progress() {
local message="$1"
speak "${message}"
progress_beep &
beepPid="$!"
}
stop_progress() {
if [[ -n "${beepPid}" ]] && kill -0 "${beepPid}" 2> /dev/null; then
kill "${beepPid}" 2> /dev/null || true
wait "${beepPid}" 2> /dev/null || true
fi
beepPid=""
}
cleanup() {
local exitCode="${1:-$?}"
stop_progress
if [[ -n "${workRoot}" && -d "${workRoot}" ]]; then
rm -rf "${workRoot}"
fi
if [[ "${STORMUX_UPDATE_SELF_COPY:-0}" == "1" && "${runtimeScript}" == /tmp/stormux-system-update.* ]]; then
rm -f "${runtimeScript}"
fi
if [[ "${exitCode}" -ne 0 ]]; then
speak "System update failed. Check ${logFile} for details." 1
read -r -p "Press enter to return to the menu."
exec "${menuCommand}"
fi
}
abort_update() {
trap - EXIT INT TERM HUP
cleanup 130
exit 130
}
run_logged() {
"$@" 2>&1 | tee -a "${logFile}"
return "${PIPESTATUS[0]}"
}
main() {
local sourceHome
mkdir -p "${logDir}"
printf 'System update started [%s]\n' "$(date)" | tee "${logFile}"
workRoot="$(mktemp -d)"
start_progress "Downloading updates."
run_logged git clone --depth 1 --filter=blob:none --sparse --branch "${repoBranch}" "${repoUrl}" "${workRoot}"
run_logged git -C "${workRoot}" sparse-checkout set home
if command -v git-lfs > /dev/null 2>&1; then
run_logged git -C "${workRoot}" lfs pull --include='home/**'
elif git -C "${workRoot}" lfs version > /dev/null 2>&1; then
run_logged git -C "${workRoot}" lfs pull --include='home/**'
fi
stop_progress
sourceHome="${workRoot}/home/stormux"
if [[ ! -d "${sourceHome}" ]]; then
printf 'Missing update home directory: %s\n' "${sourceHome}" | tee -a "${logFile}"
return 1
fi
start_progress "Applying updates."
run_logged rsync -a \
--exclude='.baremetal' \
--exclude='.cache/' \
--exclude='.config/stormux/game_launcher.conf' \
--exclude='.irssi/' \
--exclude='.local/.services/' \
--exclude='.local/etc/NetworkManager/' \
--exclude='.local/games/' \
--exclude='.local/var/' \
--exclude='.w3m/' \
--exclude='Downloads/' \
--exclude='Logs/' \
"${sourceHome}/" "${homeDir}/"
stop_progress
printf 'System update completed [%s]\n' "$(date)" | tee -a "${logFile}"
speak "Finalizing updates." 1
exec "${menuCommand}"
}
trap 'cleanup "$?"' EXIT
trap abort_update INT TERM HUP
main "$@"
@@ -0,0 +1,361 @@
# Global configuration for Speech Dispatcher
# ==========================================
# -----SYSTEM OPTIONS-----
# CommunicationMethod specifies the method to be used by Speech Dispatcher to communicate with
# its clients. Two basic methods are "unix_socket" and "inet_socket".
#
# unix_socket -- communication over Unix sockets represented by a file in the
# filesystem (see SocketPath below). This method works only locally, but is
# preferred for standard session setup, where every user runs his own instance of Speech
# Dispatcher to get voice feedback on his own computer.
#
# inet_socket -- alternatively, you can start Speech Dispatcher on
# a TCP port and connect to it via hostname/port. This allows for a more
# flexible setup, where you can use Speech Dispatcher over network
# from different machines. See also the Port and LocalhostAccessOnly
# configuration variables.
#
# CommunicationMethod "unix_socket"
# SocketPath is either "default" or a full path to the filesystem
# where the driving Unix socket file should be created in case the
# CommunicationMethod is set to "unix_socket". The default is
# $XDG_RUNTIME_DIR/speech-dispatcher/speechd.sock where $XDG_RUNTIME_DIR
# is the directory specified by the XDG Base Directory Specification.
# Do not change this unless you have a reason and know what you are doing.
# SocketPath "default"
# The Port on which Speech Dispatcher should be available to clients if the "inet_socket"
# communication method is used.
# Port 6560
# By default, if "inet_socket" communication method is used, the specified port is opened only
# for connections coming from localhost. If LocalhostAccessOnly is set to 0 it disables this
# access control. It means that the port will be accessible from all computers on the
# network. If you turn off this option, please make sure you set up some system rules on what
# computers are and are not allowed to access the Speech Dispatcher port.
# LocalhostAccessOnly 1
# By default, Speech Dispatcher is configured to shut itself down after a period of
# time if no clients are connected. The timeout value is in seconds, and is started when
# the last client disconnects. A value of 0 disables the timeout.
Timeout 0
# -----LOGGING CONFIGURATION-----
# The LogLevel is a number between 0 and 5 specifying the
# verbosity of information to the logfile or screen
# 0 means nothing, 5 means everything (not recommended).
LogLevel 3
# The LogDir specifies where the Speech Dispatcher logs reside
# Specify "stdout" for standard console output
# or a custom log directory path. 'default' means
# the logs are written to the default destination (e.g. a preconfigured
# system directory or the home directory if .speech-dispatcher is present)
# DO NOT COMMENT OUT THIS OPTION, leave as "default" for standard logging
LogDir "default"
#LogDir "/var/log/speech-dispatcher/"
#LogDir "stdout"
# The CustomLogFile allows logging all messages # regardless of
# priority, to the given destination.
#CustomLogFile "protocol" "/var/log/speech-dispatcher/speech-dispatcher-protocol.log"
# ----- VOICE PARAMETERS -----
# The DefaultRate controls how fast the synthesizer is going to speak.
# The value must be between -100 (slowest) and +100 (fastest), default
# is 0.
# DefaultRate 0
# The DefaultPitch controls the pitch of the synthesized voice. The
# value must be between -100 (lowest) and +100 (highest), default is
# 0.
# DefaultPitch 0
# The DefaultPitchRange controls the pitch range of the synthesized voice. The
# value must be between -100 (lowest) and +100 (highest), default is
# 0.
# DefaultPitchRange 0
# The DefaultVolume controls the default volume of the voice. It is
# a value between -100 (softly) and +100 (loudly). Currently, +100
# maps to the default volume of the synthesizer.
DefaultVolume 100
# The DefaultVoiceType controls which voice type should be used by default.
# Voice types are symbolic names which map to particular voices provided by
# the synthesizer according to the output module configuration.
# Please see the synthesizer-specific configuration
# in etc/speech-dispatcher/modules/ to see which voices are assigned to
# different symbolic names. The following symbolic names are
# currently supported: MALE1, MALE2, MALE3, FEMALE1, FEMALE2, FEMALE3,
# CHILD_MALE, CHILD_FEMALE
# DefaultVoiceType "MALE1"
# The Default language with which to speak
# Note that the spd-say client in particular always sets the language to its
# current locale language, so this particular client will never pick this
# configuration.
# DefaultLanguage "en-US"
# ----- MESSAGE DISPATCHING CONTROL -----
# The DefaultClientName specifies the name of a client who didn't
# introduce himself at the beginning of an SSIP session.
# DefaultClientName "unknown:unknown:unknown"
# The Default Priority. Use with caution, normally this shouldn't be
# changed globally (at this place)
# DefaultPriority "text"
# The DefaultPauseContext specifies by how many index marks a speech
# cursor should return when resuming after a pause. This is roughly
# equivalent to the number of sentences before the place of the
# execution of pause that will be repeated.
# DefaultPauseContext 0
# -----SPELLING/PUNCTUATION/CAPITAL LETTERS CONFIGURATION-----
# The DefaultPunctuationMode sets the way dots, comas, exclamation
# marks, question marks etc. are interpreted. none: they are ignored
# some: some of them are sent to synthesis (see
# DefaultPunctuationSome) all: all punctuation marks are sent to
# synthesis
# DefaultPunctuationMode "none"
# Level of punctuation for which symbol pre-processing should be performed by
# the server rather than by the module.
#
# This is completely independent of what punctuation level is actually asked, it
# controls to which extent the server should do the punctuation work to insert
# the appropriate words or if the output module is responsible for doing it.
#
# Setting this to "no" disables pre-processing completely and leaves all
# punctuation preprocessing to the output module. Setting this to
# "none" enables only the server rules which are always enabled whatever
# the punctuation level. Setting this to "all" enables all server rules for
# punctuation. Setting this to "char" enables all server rules, including
# rules for spelling spaces. Of course, which rules actually take effect depends
# on the requested punctuation level.
SymbolsPreproc "char"
# Which preprocessing files should be loaded, and in which order
#
# This is done first for the most specific localization, then lesser specific
# localization, etc. I.e. for the fr_FR language for instance, fr-fr files are
# loaded first, then fr files, then en files.
SymbolsPreprocFile "gender-neutral.dic"
SymbolsPreprocFile "font-variants.dic"
SymbolsPreprocFile "symbols.dic"
SymbolsPreprocFile "emojis.dic"
SymbolsPreprocFile "orca.dic"
SymbolsPreprocFile "orca-chars.dic"
SymbolsPreprocFile "symbols-fallback.dic"
# The DefaultCapLetRecognition: if set to "spell", capital letters
# should be spelled (e.g. "capital b"), if set to "icon",
# capital letters are indicated by inserting a special sound
# before them but they should be read normally, it set to "none"
# capital letters are not recognized (by default)
# DefaultCapLetRecognition "none"
# The DefaultSpelling: if set to On, all messages will be spelt
# unless set otherwise (this is usually not something you want to do.)
# DefaultSpelling Off
# ----- AUDIO CONFIGURATION -----------
# -- AUDIO OUTPUT --
# Chooses between the possible sound output systems:
# "pulse" - PulseAudio
# "alsa" - Advanced Linux Sound System
# "oss" - Open Sound System
# "pipewire" - the new low-latency sound server, for near realtime speech performance
# "nas" - Network Audio System
# "libao" - A cross platform audio library
#
# Pipe Wire is the newest audio output method in this configuration, but not the default one.
# However, if it works in your deployment, it is recommended because of the
# latency related advantages it has over pulseaudio and others, however enabling
# it should be done with precaution, because it isn't tested to work in all
# configurations supported by speech dispatcher.
# For now, enabling this is at the discression of the users and packagers of
# speech dispatcher. However, because we want to eventually make this the default
# for everyone, trying it in your deployments, if non-critical, is recommended
#
# OSS, Pulse Audio and ALSA are only provided for compatibility with
# architectures that do not include Pipe Wire.
#
# NAS provides network transparency, but is not very well tested.
#
# libao is a cross platform library with plugins for different sound systems
# and provides alternative output for Pulse Audio and ALSA as well as for other
# backends.
# AudioOutputMethod "pipewire"
# -- Pulse Audio parameters --
# Pulse audio device name or "default" for the default pulse device
#AudioPulseDevice "default"
# Latency requested from pulseaudio, in ms. Smaller values make speech
# interruption snappier, but also uses more CPU time thus battery.
# 10ms latency is considered in HCI (Human-computer Interaction) as real-time.
#AudioPulseMinLength 10
# -- ALSA parameters --
# Audio device for ALSA output
#AudioALSADevice "default"
# -- OSS parameters --
# Audio device for OSS output
#AudioOSSDevice "/dev/dsp"
# -- NAS parameters --
# Route to the Network Audio System server when NAS
# is chosen for the audio output. Note that NAS
# server doesn't need to run on your machine,
# you can use it also over network (for instance
# when working on remote machines).
#AudioNASServer "tcp/localhost:5450"
# -----OUTPUT MODULES CONFIGURATION-----
# Each AddModule line loads an output module.
# Syntax: AddModule "name" "binary" "configuration" "logfile"
# - name is the name under which you can access this module
# - binary is the path to the binary executable of this module,
# either relative (to libexec/speech-dispatcher-modules/
# or ~/.local/libexec/speech-dispatcher-modules) or absolute
# - configuration is the path to the config file of this module,
# either relative (to etc/speech-dispatcher/modules/
# or ~/.config/speech-dispatcher) or absolute
#AddModule "espeak" "sd_espeak" "espeak.conf"
#AddModule "espeak-ng" "sd_espeak-ng" "espeak-ng.conf"
#AddModule "festival" "sd_festival" "festival.conf"
#AddModule "flite" "sd_flite" "flite.conf"
#AddModule "ivona" "sd_ivona" "ivona.conf"
#AddModule "pico" "sd_pico" "pico.conf"
AddModule "piper-tts-generic" "sd_generic" "piper-tts-generic.conf"
#AddModule "espeak-ng-mbrola" "sd_espeak-ng-mbrola" "espeak-ng-mbrola.conf"
#AddModule "espeak-ng-mbrola-generic" "sd_generic" "espeak-ng-mbrola-generic.conf"
#AddModule "espeak-mbrola-generic" "sd_generic" "espeak-mbrola-generic.conf"
#AddModule "swift-generic" "sd_generic" "swift-generic.conf"
#AddModule "epos-generic" "sd_generic" "epos-generic.conf"
#AddModule "dtk-generic" "sd_generic" "dtk-generic.conf"
#AddModule "ibmtts" "sd_ibmtts" "ibmtts.conf"
#AddModule "cicero" "sd_cicero" "cicero.conf"
#AddModule "kali" "sd_kali" "kali.conf"
#AddModule "mary-generic" "sd_generic" "mary-generic.conf"
#AddModule "baratinoo" "sd_baratinoo" "baratinoo.conf"
AddModule "rhvoice" "sd_rhvoice" "rhvoice.conf"
AddModule "viavoice" "sd_viavoice" "viavoice.conf"
AddModule "voxin" "sd_voxin" "voxin.conf"
# The output module testing doesn't actually connect to anything. It
# outputs the requested commands to standard output and reads
# responses from stdandard input. This way, Speech Dispatcher's
# communication with output modules can be tested easily.
# AddModule "testing"
# The DefaultModule selects which output module is the default. You
# must use one of the names of the modules loaded with AddModule.
# DefaultModule espeak-ng
DefaultModule rhvoice
# The LanguageDefaultModule selects which output modules are preferred
# for specified languages.
#LanguageDefaultModule "en" "espeak"
#LanguageDefaultModule "cs" "festival"
#LanguageDefaultModule "es" "festival"
# -----CLIENT SPECIFIC CONFIGURATION-----
# Here you can include the files with client-specific configuration
# for different types of clients. They must contain one or more sections with
# this structure:
# BeginClient "emacs:*"
# DefaultPunctuationMode "some"
# ...and/or some other settings
# EndClient
# The parameter of BeginClient tells Speech Dispatcher which clients
# it should apply the settings to (it does glob-style matching, you can use
# * to match any number of characters and ? to match one character)
# There are some sample client settings
Include "clients/*.conf"
# The DisableAutoSpawn option will disable the autospawn mechanism.
# Thus the server will not start automatically on requests from the clients
# DisableAutoSpawn
# Copyright (C) 2001-2009 Brailcom, o.p.s
# Copyright (C) 2009 Rui Batista <ruiandrebatista@gmail.com>
# Copyright (C) 2010 Andrei Kholodnyi <Andrei.Kholodnyi@gmail.com>
# Copyright (C) 2010 William Hubbs <w.d.hubbs@gmail.com>
# Copyright (C) 2010 Trevor Saunders <trev.saunders@gmail.com>
# Copyright (C) 2012 William Jon McCann <jmccann@redhat.com>
# Copyright (C) 2014 Rob Whyte <fudge@thefudge.net>
# Copyright (C) 2014-2016 Luke Yelavich <themuso@ubuntu.com>
# Copyright (C) 2014 Hussain Jasim <hussainmkj@gmail.com>
# Copyright (C) 2017 Colomban Wendling <cwendling@hypra.fr>
# Copyright (C) 2018 Raphaël POITEVIN <rpoitevin@hypra.fr>
# Copyright (C) 2018 Florian Steinhardt <no.known.email@example.com>
# Copyright (C) 2018-2024 Samuel Thibault <samuel.thibault@ens-lyon.org>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public License for more details (file
# COPYING in the root directory).
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
+30 -80
View File
@@ -2,97 +2,47 @@
## Overview ## Overview
The Stormux Gaming Image includes a system update feature that keeps your system current with the latest packages and configuration files. Updates are performed through the main menu system and require minimal user interaction. The Stormux Gaming Image uses a read-only root filesystem on x86_64 images, so the System Update option updates managed Stormux files in your home directory. It refreshes the game menu, launcher scripts, installer recipes, documentation, and other files shipped under `/home/stormux`.
System package updates and root filesystem changes are delivered in new image builds.
## How to Update ## How to Update
1. Open the Game Launcher (automatically starts on boot) 1. Open the Game Launcher.
2. Navigate to the "System" menu (from initial load of the image, press left once) 2. Navigate to System, then Power.
3. Select "System Update" 3. Select System Update.
4. Enter your username and password when prompted (only required when credentials change on the server username is always patron) 4. Wait for the spoken update messages to finish.
5. The update process will run automatically
6. Review the update summary when complete The menu restarts automatically after the update so any menu changes are loaded immediately.
7. Press Enter to return to the menu
## What Gets Updated ## What Gets Updated
The system update performs two main operations: System Update downloads the latest `home/` tree from the Stormux gaming image files repository and applies it to `/home/stormux`.
### 1. Package Updates The update preserves file permissions, executable bits, and symbolic links from the repository.
- Updates all installed system packages using pacman
- Includes kernel updates, applications, and libraries
- Runs non-interactively with automatic confirmations
### 2. Configuration File Sync ## Preserved Local Data
- Downloads the latest custom image files and deploys them
- Updates system configuration files The update skips local state and user data, including:
- Refreshes user utilities and applications
- Updates documentation and resources - `.baremetal`
- Preserves user data and settings - `.cache/`
- `.config/stormux/game_launcher.conf`
- `.irssi/`
- `.local/.services/`
- `.local/etc/NetworkManager/`
- `.local/games/`
- `.local/var/`
- `.w3m/`
- `Downloads/`
- `Logs/`
## Update Logs ## Update Logs
All update operations are logged for troubleshooting and review: Update output is written to:
**Log Location**: `~/Logs/system-updates.log` ```text
~/Logs/system-updates.log
The log file contains:
- Timestamp for each update session
- Complete output from package updates
- Git operations (clone, checkout, LFS pull)
- File copy operations
- Error messages if any operations fail
- Success/failure summary
## Understanding the Summary
At the end of each update, you'll see one of two messages:
### Successful Update
```
SUCCESS: All update operations completed successfully.
``` ```
### Failed Update If the update fails, review that file and contact Stormux with the log if you need help.
```
ERRORS DETECTED: 2 error(s) occurred during update:
- Package update failed
- Git clone failed
```
If errors are detected, review the log file for detailed information about what went wrong.
## Common Issues
### Authentication Required
If the server credentials have changed, you'll be prompted for your username and password. This is normal and only happens when credentials are updated on the server.
### Network Issues
Updates require an internet connection. If git clone or package updates fail, verify your network connection and try again.
### Insufficient Disk Space
Package updates require free disk space. If updates fail, check available space.
## Update Frequency
## Safety Features
- Updates are logged for troubleshooting
- User files and settings are preserved
- File ownership is automatically corrected
- Screen reader remains active throughout the process
- Summary shows if any operations failed
## Getting Help
If you experience persistent update issues:
1. Review the log file at `~/Logs/system-updates.log`
2. Contact Stormux with your log file (email address storm_dragon@stormux.org)
## Notes
- The script runs with root privileges to update system files
- Your personal data in ~/Music, ~/Roms, and other user directories is never modified
- Configuration files like .w3m and .irssi are excluded from updates to preserve your settings
- Updates can take several minutes depending on internet speed and the number of packages