diff --git a/pi4/files/usr/local/bin/configure-stormux b/pi4/files/usr/local/bin/configure-stormux index 0941085..467c194 100755 --- a/pi4/files/usr/local/bin/configure-stormux +++ b/pi4/files/usr/local/bin/configure-stormux @@ -104,6 +104,13 @@ EndSection EOF } +restart_fenrir_speech() { + if systemctl is-active --quiet fenrirscreenreader.service; then + killall speech-dispatcher 2>/dev/null || true + systemctl restart fenrirscreenreader.service + fi +} + set_timezone() { # Get the list of timezones mapfile -t regions < <(timedatectl --no-pager list-timezones | cut -d '/' -f1 | sort -u) @@ -136,8 +143,8 @@ set_timezone() { if read_yes_no "Would you like to switch Fenrir to laptop layout?"; then sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf clear - systemctl restart fenrirscreenreader.service fi +restart_fenrir_speech if command -v i3 > /dev/null 2>&1 && systemctl list-unit-files nodm.service > /dev/null 2>&1; then if read_yes_no "Will this Pi be connected to a physical screen?"; then diff --git a/pi4/files/usr/local/lib/stormux/select-speech-module b/pi4/files/usr/local/lib/stormux/select-speech-module index 44e20b8..ec1b284 100755 --- a/pi4/files/usr/local/lib/stormux/select-speech-module +++ b/pi4/files/usr/local/lib/stormux/select-speech-module @@ -6,9 +6,11 @@ speechConfig="${STORMUX_SPEECH_CONFIG:-/etc/speech-dispatcher/speechd.conf}" selectionFile="" spdSayCommand="${STORMUX_SPD_SAY:-spd-say}" systemctlCommand="${STORMUX_SYSTEMCTL:-systemctl}" -killallCommand="${STORMUX_KILLALL:-killall}" +socatCommand="${STORMUX_SOCAT:-socat}" +fenrirSocket="${STORMUX_FENRIR_SOCKET:-/tmp/fenrirscreenreader-daemon.sock}" scriptPath="$(readlink -f "$0")" runtimeDir="${STORMUX_RUNTIME_DIR:-/run/stormux}" +fenrirSpeechMuted=false is_safe_module() { [[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] @@ -149,16 +151,30 @@ announce_failure() { printf '%s\n' "$message" >&2 } -restart_speech() { - "${sudoCommand[@]}" "$killallCommand" speech-dispatcher +set_fenrir_speech() { + local enabled="$1" + + printf 'setting set speech#enabled=%s\n' "$enabled" | \ + "$socatCommand" - UNIX-CLIENT:"$fenrirSocket" >/dev/null 2>&1 } -restart_fenrir_if_active() { - if "$systemctlCommand" is-active --quiet fenrirscreenreader.service 2>/dev/null; then - "${sudoCommand[@]}" "$systemctlCommand" restart fenrirscreenreader.service +mute_fenrir_speech() { + if "$systemctlCommand" is-active --quiet fenrirscreenreader.service 2>/dev/null && \ + set_fenrir_speech False; then + fenrirSpeechMuted=true fi } +restore_fenrir_speech() { + if [[ "$fenrirSpeechMuted" == true ]]; then + set_fenrir_speech True || true + fenrirSpeechMuted=false + fi +} + +trap restore_fenrir_speech EXIT +trap 'exit 130' HUP INT TERM + discover_modules() { local output line candidate local skippedHeading=false @@ -246,6 +262,7 @@ read_menu_key() { declare -a modules=() previousModule="$(configured_module)" +mute_fenrir_speech if ! discover_modules; then announce_failure "$previousModule" exit 0 @@ -260,7 +277,7 @@ if ! selectedIndex="$(module_index "$previousModule")"; then fi draw_menu -"$spdSayCommand" "Speech engine selection. ${modules[$selectedIndex]} is highlighted. Use up and down to preview engines, Enter to select, or Escape to keep the current engine." >/dev/null 2>&1 || true +"$spdSayCommand" "Use up and down arrows to select the default voice for this computer. ${modules[$selectedIndex]} is highlighted. Press Enter to select, or Escape to keep the current voice." >/dev/null 2>&1 || true while true; do if ! keyName="$(read_menu_key)"; then @@ -303,19 +320,9 @@ if ! "${sudoCommand[@]}" "$scriptPath" --apply-module "$selectedModule" "$speech exit 0 fi -if ! restart_speech || ! restart_fenrir_if_active; then - "${sudoCommand[@]}" "$scriptPath" --restore-config "$speechConfig" "$backupFile" >/dev/null 2>&1 || true - restart_speech >/dev/null 2>&1 || true - restart_fenrir_if_active >/dev/null 2>&1 || true - announce_failure "$previousModule" - exit 0 -fi - if [[ -n "$selectionFile" ]]; then if ! "${sudoCommand[@]}" "$scriptPath" --write-selection "$selectedModule" "$selectionFile"; then "${sudoCommand[@]}" "$scriptPath" --restore-config "$speechConfig" "$backupFile" >/dev/null 2>&1 || true - restart_speech >/dev/null 2>&1 || true - restart_fenrir_if_active >/dev/null 2>&1 || true announce_failure "$previousModule" exit 0 fi diff --git a/tests/test-select-speech-module.sh b/tests/test-select-speech-module.sh index 1b653e3..f18b288 100755 --- a/tests/test-select-speech-module.sh +++ b/tests/test-select-speech-module.sh @@ -36,12 +36,10 @@ fi exit 0 EOF -cat > "$mockBin/killall" <<'EOF' +cat > "$mockBin/socat" <<'EOF' #!/usr/bin/env bash -printf 'killall %s\n' "$*" >> "$MOCK_LOG" -if [[ "${MOCK_KILLALL_FAIL:-no}" == "yes" ]]; then - exit 1 -fi +read -r command +printf 'socat %s: %s\n' "$*" "$command" >> "$MOCK_LOG" EOF cat > "$mockBin/systemctl" <<'EOF' @@ -104,13 +102,12 @@ run_selector() { MOCK_LOG="$mockLog" \ MOCK_MODULES="$modulesFile" \ MOCK_DISCOVERY_STATUS="${MOCK_DISCOVERY_STATUS:-0}" \ - MOCK_KILLALL_FAIL="${MOCK_KILLALL_FAIL:-no}" \ MOCK_FENRIR_ACTIVE="${MOCK_FENRIR_ACTIVE:-no}" \ MOCK_SUDO_FAIL_WRITE="${MOCK_SUDO_FAIL_WRITE:-no}" \ STORMUX_SPEECH_CONFIG="$configFile" \ STORMUX_RUNTIME_DIR="$caseDir/runtime" \ STORMUX_SPD_SAY=spd-say \ - STORMUX_KILLALL=killall \ + STORMUX_SOCAT=socat \ STORMUX_SYSTEMCTL=systemctl \ "$selector" --selection-file "$selectionFile" >/dev/null } @@ -122,7 +119,12 @@ MOCK_FENRIR_ACTIVE=yes run_selector '\n' assert_equals 1 "$(grep -c '^DefaultModule ' "$configFile")" "initial selection should leave one active default" assert_file_contains "$configFile" '^DefaultModule rhvoice$' "initial selection should retain rhvoice" assert_equals rhvoice "$(< "$selectionFile")" "initial selection should be recorded" -assert_file_contains "$mockLog" '^systemctl restart fenrirscreenreader\.service$' "active Fenrir should restart" +assert_file_contains "$mockLog" 'speech#enabled=False' "active Fenrir speech should be muted" +assert_file_contains "$mockLog" 'speech#enabled=True' "active Fenrir speech should be restored" +assert_file_contains "$mockLog" 'Use up and down arrows to select the default voice for this computer\.' "the selector should voice its own instructions" +if grep -q '^systemctl restart ' "$mockLog"; then + fail "the selector should defer the Fenrir restart until after layout selection" +fi # Down previews and selects the next module. reset_case @@ -163,16 +165,10 @@ fi # A missing configuration simulates failed persistence and remains nonfatal. reset_case rm -f "$configFile" -run_selector '\n' +MOCK_FENRIR_ACTIVE=yes run_selector '\n' assert_file_absent "$selectionFile" "failed persistence should not record a selection" assert_file_contains "$mockLog" 'selection failed' "failed persistence should be announced" - -# A restart failure restores the byte-for-byte original configuration. -reset_case -cp "$configFile" "$caseDir/original.conf" -MOCK_KILLALL_FAIL=yes run_selector '\033[B\n' -cmp -s "$configFile" "$caseDir/original.conf" || fail "restart failure should roll back the configuration" -assert_file_absent "$selectionFile" "restart failure should not record a selection" +assert_file_contains "$mockLog" 'speech#enabled=True' "failed persistence should restore Fenrir speech" # Failure to record the live choice also rolls the system configuration back. reset_case diff --git a/x86_64/airootfs/usr/local/bin/configure-stormux b/x86_64/airootfs/usr/local/bin/configure-stormux index 8c7646e..ec55e09 100755 --- a/x86_64/airootfs/usr/local/bin/configure-stormux +++ b/x86_64/airootfs/usr/local/bin/configure-stormux @@ -37,6 +37,14 @@ set_timezone() { ln -sf /usr/share/zoneinfo/"${region}"/"${city}" /etc/localtime timedatectl set-ntp true } + +restart_fenrir_speech() { + if systemctl is-active --quiet fenrirscreenreader.service; then + killall speech-dispatcher 2>/dev/null || true + systemctl restart fenrirscreenreader.service + fi +} + # Offer to switch fenrir layout. echo "Would you like to switch Fenrir to laptop layout?" echo "Press y for yes or n for no followed by enter." @@ -45,8 +53,8 @@ continue="${continue::1}" if [[ "${continue,}" == "y" ]];then sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf clear - systemctl restart fenrirscreenreader.service fi +restart_fenrir_speech if ! ping -c1 stormux.org &> /dev/null ; then echo "No internet connection detected. Press enter to open NetworkManager." diff --git a/x86_64/airootfs/usr/local/lib/stormux/select-speech-module b/x86_64/airootfs/usr/local/lib/stormux/select-speech-module index 44e20b8..ec1b284 100755 --- a/x86_64/airootfs/usr/local/lib/stormux/select-speech-module +++ b/x86_64/airootfs/usr/local/lib/stormux/select-speech-module @@ -6,9 +6,11 @@ speechConfig="${STORMUX_SPEECH_CONFIG:-/etc/speech-dispatcher/speechd.conf}" selectionFile="" spdSayCommand="${STORMUX_SPD_SAY:-spd-say}" systemctlCommand="${STORMUX_SYSTEMCTL:-systemctl}" -killallCommand="${STORMUX_KILLALL:-killall}" +socatCommand="${STORMUX_SOCAT:-socat}" +fenrirSocket="${STORMUX_FENRIR_SOCKET:-/tmp/fenrirscreenreader-daemon.sock}" scriptPath="$(readlink -f "$0")" runtimeDir="${STORMUX_RUNTIME_DIR:-/run/stormux}" +fenrirSpeechMuted=false is_safe_module() { [[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] @@ -149,16 +151,30 @@ announce_failure() { printf '%s\n' "$message" >&2 } -restart_speech() { - "${sudoCommand[@]}" "$killallCommand" speech-dispatcher +set_fenrir_speech() { + local enabled="$1" + + printf 'setting set speech#enabled=%s\n' "$enabled" | \ + "$socatCommand" - UNIX-CLIENT:"$fenrirSocket" >/dev/null 2>&1 } -restart_fenrir_if_active() { - if "$systemctlCommand" is-active --quiet fenrirscreenreader.service 2>/dev/null; then - "${sudoCommand[@]}" "$systemctlCommand" restart fenrirscreenreader.service +mute_fenrir_speech() { + if "$systemctlCommand" is-active --quiet fenrirscreenreader.service 2>/dev/null && \ + set_fenrir_speech False; then + fenrirSpeechMuted=true fi } +restore_fenrir_speech() { + if [[ "$fenrirSpeechMuted" == true ]]; then + set_fenrir_speech True || true + fenrirSpeechMuted=false + fi +} + +trap restore_fenrir_speech EXIT +trap 'exit 130' HUP INT TERM + discover_modules() { local output line candidate local skippedHeading=false @@ -246,6 +262,7 @@ read_menu_key() { declare -a modules=() previousModule="$(configured_module)" +mute_fenrir_speech if ! discover_modules; then announce_failure "$previousModule" exit 0 @@ -260,7 +277,7 @@ if ! selectedIndex="$(module_index "$previousModule")"; then fi draw_menu -"$spdSayCommand" "Speech engine selection. ${modules[$selectedIndex]} is highlighted. Use up and down to preview engines, Enter to select, or Escape to keep the current engine." >/dev/null 2>&1 || true +"$spdSayCommand" "Use up and down arrows to select the default voice for this computer. ${modules[$selectedIndex]} is highlighted. Press Enter to select, or Escape to keep the current voice." >/dev/null 2>&1 || true while true; do if ! keyName="$(read_menu_key)"; then @@ -303,19 +320,9 @@ if ! "${sudoCommand[@]}" "$scriptPath" --apply-module "$selectedModule" "$speech exit 0 fi -if ! restart_speech || ! restart_fenrir_if_active; then - "${sudoCommand[@]}" "$scriptPath" --restore-config "$speechConfig" "$backupFile" >/dev/null 2>&1 || true - restart_speech >/dev/null 2>&1 || true - restart_fenrir_if_active >/dev/null 2>&1 || true - announce_failure "$previousModule" - exit 0 -fi - if [[ -n "$selectionFile" ]]; then if ! "${sudoCommand[@]}" "$scriptPath" --write-selection "$selectedModule" "$selectionFile"; then "${sudoCommand[@]}" "$scriptPath" --restore-config "$speechConfig" "$backupFile" >/dev/null 2>&1 || true - restart_speech >/dev/null 2>&1 || true - restart_fenrir_if_active >/dev/null 2>&1 || true announce_failure "$previousModule" exit 0 fi