Menu modifications.
This commit is contained in:
+1
@@ -0,0 +1 @@
|
|||||||
|
/home/stormux/.local/etc/speech-dispatcher/speechd.conf
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
|
||||||
Executable
+136
@@ -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/>.
|
||||||
@@ -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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user