Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4134a16ecd | ||
|
|
0df6d7b0fc | ||
|
|
a8d4fc1fc1 | ||
|
|
7ee829d56c | ||
|
|
200c24e2a4 | ||
|
|
92bfe467e4 | ||
|
|
b2d6cdc25d | ||
|
|
da26909527 | ||
|
|
99a944f903 | ||
|
|
1f7aea6c7c |
+3
-2
@@ -29,9 +29,10 @@ The script is designed for an x86_64 Arch host so it can emulate ARM binaries wi
|
|||||||
|
|
||||||
## Building Your First Image
|
## Building Your First Image
|
||||||
1. Become root (`sudo -i`) so the script can create loop devices and mount them.
|
1. Become root (`sudo -i`) so the script can create loop devices and mount them.
|
||||||
2. From `/path/to/stormux`, run:
|
2. From the Pi builder directory, run:
|
||||||
```bash
|
```bash
|
||||||
sudo ./pi4/build/build-stormux.sh -l en_US -s 8
|
cd /path/to/stormux/pi4/build
|
||||||
|
sudo ./build-stormux.sh -l en_US -s 8
|
||||||
```
|
```
|
||||||
- The builder creates an aarch64 image for Raspberry Pi 4/5-class systems.
|
- The builder creates an aarch64 image for Raspberry Pi 4/5-class systems.
|
||||||
- `-l en_US` selects the locale (use `es_ES`, `fr_FR`, etc.).
|
- `-l en_US` selects the locale (use `es_ES`, `fr_FR`, etc.).
|
||||||
|
|||||||
@@ -121,14 +121,28 @@ finish_build() {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_rhvoice_english_fixes() {
|
install_rhvoice_english_fixes() {
|
||||||
local sourceDir="../files/etc/RHVoice/dicts/English"
|
local rhvoiceRepo="https://git.stormux.org/storm/rhvoice-english"
|
||||||
local targetDir="/mnt/etc/RHVoice/dicts/English"
|
local targetDir="/mnt/etc/RHVoice/dicts/English"
|
||||||
|
local tempDir
|
||||||
|
local installStatus=0
|
||||||
|
|
||||||
if [[ -d "$sourceDir" ]]; then
|
tempDir="$(mktemp -d)"
|
||||||
mkdir -p "$targetDir"
|
|
||||||
cp -a "$sourceDir/." "$targetDir/"
|
echo "Installing latest RHVoice English dictionary fixes..."
|
||||||
|
if ! git clone --depth 1 "$rhvoiceRepo" "$tempDir"; then
|
||||||
|
rm -rf "$tempDir"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$targetDir"
|
||||||
|
rm -f "$targetDir/namefix.txt" "$targetDir/wordfix.txt"
|
||||||
|
if ! cp -a "$tempDir/English/." "$targetDir/"; then
|
||||||
|
installStatus=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$tempDir"
|
||||||
|
return "$installStatus"
|
||||||
}
|
}
|
||||||
|
|
||||||
# shellcheck disable=SC2329 # install_sas is emitted into the chroot heredoc with declare -f
|
# shellcheck disable=SC2329 # install_sas is emitted into the chroot heredoc with declare -f
|
||||||
@@ -222,6 +236,38 @@ if [ "$(whoami)" != "root" ] ; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Refuse to mount the image over an unrelated filesystem already using /mnt.
|
||||||
|
if ! command -v mountpoint > /dev/null 2>&1; then
|
||||||
|
echo "Error: mountpoint is required to verify that /mnt is available."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d /mnt || ! -w /mnt ]]; then
|
||||||
|
echo "Error: /mnt is missing or not writable. Please create or fix permissions."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if mountpoint -q /mnt; then
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
read -rp "/mnt is already mounted. Unmount it to continue? [y/N] " answerText
|
||||||
|
case "$answerText" in
|
||||||
|
y|Y|yes|YES)
|
||||||
|
if ! umount /mnt; then
|
||||||
|
echo "Error: Failed to unmount /mnt."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Refusing to proceed while /mnt is mounted."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
echo "Error: /mnt is already mounted. Refusing to proceed in non-interactive mode."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# make sure the needed tools are installed
|
# make sure the needed tools are installed
|
||||||
if [[ "$(uname -m)" == "x86_64" ]]; then
|
if [[ "$(uname -m)" == "x86_64" ]]; then
|
||||||
if ! pacman -Q qemu-user-static &> /dev/null ; then
|
if ! pacman -Q qemu-user-static &> /dev/null ; then
|
||||||
@@ -396,6 +442,7 @@ packages=(
|
|||||||
man-pages
|
man-pages
|
||||||
networkmanager
|
networkmanager
|
||||||
nodm-dgw
|
nodm-dgw
|
||||||
|
openevv-git
|
||||||
openssh
|
openssh
|
||||||
parted
|
parted
|
||||||
pipewire
|
pipewire
|
||||||
@@ -502,10 +549,13 @@ if command -v spd-conf > /dev/null 2>&1; then
|
|||||||
spd-conf -n -C || echo "Warning: Speech Dispatcher system configuration failed."
|
spd-conf -n -C || echo "Warning: Speech Dispatcher system configuration failed."
|
||||||
fi
|
fi
|
||||||
if [[ -f /etc/speech-dispatcher/speechd.conf ]]; then
|
if [[ -f /etc/speech-dispatcher/speechd.conf ]]; then
|
||||||
if grep -qE '^[[:space:]]*#?[[:space:]]*DefaultModule[[:space:]]+' /etc/speech-dispatcher/speechd.conf; then
|
speechBackup="\$(mktemp)"
|
||||||
sed -i -E 's/^[[:space:]]*#?[[:space:]]*DefaultModule[[:space:]]+.*/DefaultModule rhvoice/' /etc/speech-dispatcher/speechd.conf
|
if /usr/local/lib/stormux/select-speech-module \
|
||||||
|
--apply-module rhvoice /etc/speech-dispatcher/speechd.conf "\$speechBackup"; then
|
||||||
|
rm -f "\$speechBackup"
|
||||||
else
|
else
|
||||||
printf '\nDefaultModule rhvoice\n' >> /etc/speech-dispatcher/speechd.conf
|
echo "Warning: Could not set RHVoice as the default speech engine."
|
||||||
|
rm -f "\$speechBackup"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Enable linger so that hopefully sound will start at login.
|
# Enable linger so that hopefully sound will start at login.
|
||||||
@@ -549,7 +599,7 @@ sed -i '/^DisableSandbox/d' /etc/pacman.conf
|
|||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
copy_rhvoice_english_fixes
|
install_rhvoice_english_fixes
|
||||||
|
|
||||||
# Copy skel files to stormux user home (after user rename in chroot)
|
# Copy skel files to stormux user home (after user rename in chroot)
|
||||||
find ../files/etc/skel/ -mindepth 1 -exec cp -rv "{}" /mnt/home/stormux/ \;
|
find ../files/etc/skel/ -mindepth 1 -exec cp -rv "{}" /mnt/home/stormux/ \;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
bifrost=buyfraust
|
|
||||||
danestange=dainstanggey
|
|
||||||
stange=stangey
|
|
||||||
kde=kaydeee
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
asus=aysus
|
|
||||||
certificate=cirtifficate
|
|
||||||
douche=doosh*
|
|
||||||
espeak=easpeak*
|
|
||||||
freenginx=freeenginex
|
|
||||||
git=ghit*
|
|
||||||
github=ghittehub
|
|
||||||
gitea=ghittee
|
|
||||||
jolla=yolla
|
|
||||||
jenux=jennux
|
|
||||||
lightnin=lighttnin
|
|
||||||
nginx=enginex
|
|
||||||
rhvoice=ahraychvoice
|
|
||||||
shit=shitt
|
|
||||||
sync=sink*
|
|
||||||
timezone=timezoan
|
|
||||||
vinux=vinnux
|
|
||||||
wench=wentch*
|
|
||||||
youngin=younggin*
|
|
||||||
@@ -60,6 +60,8 @@ if [[ -x /etc/audibleprompt.sh ]]; then
|
|||||||
export sudoFlags=("-A")
|
export sudoFlags=("-A")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
/usr/local/lib/stormux/select-speech-module
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Starting Fenrir..."
|
echo "Starting Fenrir..."
|
||||||
spd-say "Starting Fenrir."
|
spd-say "Starting Fenrir."
|
||||||
|
|||||||
@@ -104,6 +104,13 @@ EndSection
|
|||||||
EOF
|
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() {
|
set_timezone() {
|
||||||
# Get the list of timezones
|
# Get the list of timezones
|
||||||
mapfile -t regions < <(timedatectl --no-pager list-timezones | cut -d '/' -f1 | sort -u)
|
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
|
if read_yes_no "Would you like to switch Fenrir to laptop layout?"; then
|
||||||
sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf
|
sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf
|
||||||
clear
|
clear
|
||||||
systemctl restart fenrirscreenreader.service
|
|
||||||
fi
|
fi
|
||||||
|
restart_fenrir_speech
|
||||||
|
|
||||||
if command -v i3 > /dev/null 2>&1 && systemctl list-unit-files nodm.service > /dev/null 2>&1; then
|
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
|
if read_yes_no "Will this Pi be connected to a physical screen?"; then
|
||||||
|
|||||||
+334
@@ -0,0 +1,334 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
speechConfig="${STORMUX_SPEECH_CONFIG:-/etc/speech-dispatcher/speechd.conf}"
|
||||||
|
selectionFile=""
|
||||||
|
spdSayCommand="${STORMUX_SPD_SAY:-spd-say}"
|
||||||
|
systemctlCommand="${STORMUX_SYSTEMCTL:-systemctl}"
|
||||||
|
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_.-]*$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_module() {
|
||||||
|
local moduleName="$1"
|
||||||
|
local configPath="$2"
|
||||||
|
local backupPath="$3"
|
||||||
|
local tempPath
|
||||||
|
|
||||||
|
is_safe_module "$moduleName" || return 1
|
||||||
|
[[ -f "$configPath" ]] || return 1
|
||||||
|
|
||||||
|
mkdir -p "${backupPath%/*}" || return 1
|
||||||
|
cp --preserve=mode,ownership,timestamps "$configPath" "$backupPath" || return 1
|
||||||
|
tempPath="$(mktemp "${configPath}.stormux.XXXXXX")" || return 1
|
||||||
|
|
||||||
|
if ! awk -v moduleName="$moduleName" '
|
||||||
|
BEGIN { wroteDefault = 0 }
|
||||||
|
/^[[:space:]]*#?[[:space:]]*DefaultModule[[:space:]]+/ {
|
||||||
|
if (!wroteDefault) {
|
||||||
|
print "DefaultModule " moduleName
|
||||||
|
wroteDefault = 1
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (!wroteDefault) {
|
||||||
|
print ""
|
||||||
|
print "DefaultModule " moduleName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$configPath" > "$tempPath"; then
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chown --reference="$configPath" "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
chmod --reference="$configPath" "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
mv -f "$tempPath" "$configPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_config() {
|
||||||
|
local configPath="$1"
|
||||||
|
local backupPath="$2"
|
||||||
|
local tempPath
|
||||||
|
|
||||||
|
[[ -f "$backupPath" ]] || return 1
|
||||||
|
tempPath="$(mktemp "${configPath}.stormux.XXXXXX")" || return 1
|
||||||
|
if ! cp --preserve=mode,ownership,timestamps "$backupPath" "$tempPath"; then
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
mv -f "$tempPath" "$configPath" || return 1
|
||||||
|
rm -f "$backupPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_selection() {
|
||||||
|
local moduleName="$1"
|
||||||
|
local targetPath="$2"
|
||||||
|
local tempPath
|
||||||
|
|
||||||
|
is_safe_module "$moduleName" || return 1
|
||||||
|
mkdir -p "${targetPath%/*}" || return 1
|
||||||
|
tempPath="$(mktemp "${targetPath}.stormux.XXXXXX")" || return 1
|
||||||
|
printf '%s\n' "$moduleName" > "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
chmod 644 "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
mv -f "$tempPath" "$targetPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
--apply-module)
|
||||||
|
[[ $# -eq 4 ]] || exit 2
|
||||||
|
apply_module "$2" "$3" "$4"
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
--restore-config)
|
||||||
|
[[ $# -eq 3 ]] || exit 2
|
||||||
|
restore_config "$2" "$3"
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
--write-selection)
|
||||||
|
[[ $# -eq 3 ]] || exit 2
|
||||||
|
write_selection "$2" "$3"
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--config)
|
||||||
|
[[ $# -ge 2 ]] || exit 2
|
||||||
|
speechConfig="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--selection-file)
|
||||||
|
[[ $# -ge 2 ]] || exit 2
|
||||||
|
selectionFile="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Unknown option: %s\n' "$1" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
sudoCommand=(sudo)
|
||||||
|
if [[ -n "${SUDO_ASKPASS:-}" ]]; then
|
||||||
|
sudoCommand+=(-A)
|
||||||
|
fi
|
||||||
|
|
||||||
|
announce_failure() {
|
||||||
|
local previousModule="$1"
|
||||||
|
local message="Speech engine selection failed. Your previous speech engine is still selected."
|
||||||
|
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
if is_safe_module "$previousModule"; then
|
||||||
|
"$spdSayCommand" -o "$previousModule" "$message" >/dev/null 2>&1 || \
|
||||||
|
"$spdSayCommand" "$message" >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
"$spdSayCommand" "$message" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$message" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
set_fenrir_speech() {
|
||||||
|
local enabled="$1"
|
||||||
|
|
||||||
|
printf 'setting set speech#enabled=%s\n' "$enabled" | \
|
||||||
|
"$socatCommand" - UNIX-CLIENT:"$fenrirSocket" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if ! output="$("$spdSayCommand" -O 2>/dev/null)"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
modules=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
candidate="${line%$'\r'}"
|
||||||
|
candidate="${candidate#"${candidate%%[![:space:]]*}"}"
|
||||||
|
candidate="${candidate%"${candidate##*[![:space:]]}"}"
|
||||||
|
[[ -n "$candidate" ]] || continue
|
||||||
|
if [[ "$skippedHeading" == false ]]; then
|
||||||
|
skippedHeading=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
is_safe_module "$candidate" || continue
|
||||||
|
modules+=("$candidate")
|
||||||
|
done <<< "$output"
|
||||||
|
|
||||||
|
[[ ${#modules[@]} -gt 0 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
configured_module() {
|
||||||
|
local configured=""
|
||||||
|
|
||||||
|
if [[ -r "$speechConfig" ]]; then
|
||||||
|
configured="$(awk '$1 == "DefaultModule" { print $2; exit }' "$speechConfig")"
|
||||||
|
fi
|
||||||
|
if ! is_safe_module "$configured"; then
|
||||||
|
configured="rhvoice"
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$configured"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_index() {
|
||||||
|
local wanted="$1"
|
||||||
|
local index
|
||||||
|
|
||||||
|
for index in "${!modules[@]}"; do
|
||||||
|
if [[ "${modules[$index]}" == "$wanted" ]]; then
|
||||||
|
printf '%s\n' "$index"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_menu() {
|
||||||
|
local index
|
||||||
|
|
||||||
|
printf '\033[H\033[2J'
|
||||||
|
printf 'Select the default speech engine. Use Up and Down, Enter to select, or Escape to keep the current engine.\n\n'
|
||||||
|
for index in "${!modules[@]}"; do
|
||||||
|
if [[ $index -eq $selectedIndex ]]; then
|
||||||
|
printf '> %s\n' "${modules[$index]}"
|
||||||
|
else
|
||||||
|
printf ' %s\n' "${modules[$index]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
read_menu_key() {
|
||||||
|
local first rest=""
|
||||||
|
|
||||||
|
IFS= read -rsn1 first || return 1
|
||||||
|
if [[ -z "$first" ]]; then
|
||||||
|
printf 'enter\n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$first" == $'\033' ]]; then
|
||||||
|
IFS= read -rsn2 -t 0.1 rest || true
|
||||||
|
case "$rest" in
|
||||||
|
'[A') printf 'up\n' ;;
|
||||||
|
'[B') printf 'down\n' ;;
|
||||||
|
'') printf 'escape\n' ;;
|
||||||
|
*) printf 'other\n' ;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
printf 'other\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -a modules=()
|
||||||
|
previousModule="$(configured_module)"
|
||||||
|
mute_fenrir_speech
|
||||||
|
if ! discover_modules; then
|
||||||
|
announce_failure "$previousModule"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! selectedIndex="$(module_index "$previousModule")"; then
|
||||||
|
if selectedIndex="$(module_index rhvoice)"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
selectedIndex=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
draw_menu
|
||||||
|
"$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
|
||||||
|
announce_failure "$previousModule"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
case "$keyName" in
|
||||||
|
up)
|
||||||
|
selectedIndex=$(( (selectedIndex - 1 + ${#modules[@]}) % ${#modules[@]} ))
|
||||||
|
;;
|
||||||
|
down)
|
||||||
|
selectedIndex=$(( (selectedIndex + 1) % ${#modules[@]} ))
|
||||||
|
;;
|
||||||
|
enter)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
escape)
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
"$spdSayCommand" -o "$previousModule" "Keeping ${previousModule} as the default speech engine." >/dev/null 2>&1 || true
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
draw_menu
|
||||||
|
moduleName="${modules[$selectedIndex]}"
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
"$spdSayCommand" -o "$moduleName" "This is ${moduleName}. If selected, I will be your default text-to-speech voice." >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
|
||||||
|
selectedModule="${modules[$selectedIndex]}"
|
||||||
|
backupFile="${runtimeDir}/speechd.conf.$$.backup"
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if ! "${sudoCommand[@]}" "$scriptPath" --apply-module "$selectedModule" "$speechConfig" "$backupFile"; then
|
||||||
|
"${sudoCommand[@]}" "$scriptPath" --restore-config "$speechConfig" "$backupFile" >/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
|
||||||
|
announce_failure "$previousModule"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$backupFile" 2>/dev/null || "${sudoCommand[@]}" rm -f "$backupFile" 2>/dev/null || true
|
||||||
|
|
||||||
|
"$spdSayCommand" -o "$selectedModule" "${selectedModule} is now the default speech engine." >/dev/null 2>&1 || true
|
||||||
|
printf 'Default speech engine set to %s.\n' "$selectedModule"
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
pkgbase = xlibre-video-dummy-with-vt
|
|
||||||
pkgdesc = XLibre dummy video driver with an allocated vt
|
|
||||||
pkgver = 25.0.0
|
|
||||||
pkgrel = 5
|
|
||||||
url = https://github.com/X11Libre/xf86-video-dummy
|
|
||||||
arch = x86_64
|
|
||||||
arch = aarch64
|
|
||||||
groups = xlibre-drivers
|
|
||||||
license = MIT
|
|
||||||
license = X11
|
|
||||||
makedepends = xlibre-xserver-devel>=25.0
|
|
||||||
makedepends = xorgproto
|
|
||||||
depends = xlibre-xserver>=25.0
|
|
||||||
depends = glibc
|
|
||||||
provides = xf86-video-dummy
|
|
||||||
provides = x11win-video-dummy
|
|
||||||
conflicts = xf86-video-dummy
|
|
||||||
source = https://github.com/X11Libre/xf86-video-dummy/archive/refs/tags/xlibre-xf86-video-dummy-25.0.0.tar.gz
|
|
||||||
source = dummy_driver.patch
|
|
||||||
sha256sums = b56e610705cd3d4d86422a11c6b0d93357e4d4749a05178a85fd250301d357b9
|
|
||||||
sha256sums = 68cdcf21e9b54a7fdb8e968292e1ef9ad154ddb1361b141a0a635c2a13c92bfa
|
|
||||||
|
|
||||||
pkgname = xlibre-video-dummy-with-vt
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
*
|
|
||||||
!PKGBUILD
|
|
||||||
!.SRCINFO
|
|
||||||
!.gitignore
|
|
||||||
!.nvchecker.toml
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[xlibre-video-dummy]
|
|
||||||
source = "git"
|
|
||||||
git = "https://github.com/x11libre/xf86-video-dummy.git"
|
|
||||||
include_regex = "xlibre-xf86-video-dummy-.*"
|
|
||||||
prefix = "xlibre-xf86-video-dummy-"
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
# Maintainer: Storm Dragon <storm_dragon@linux-a11y.org>
|
|
||||||
|
|
||||||
pkgname=xlibre-video-dummy-with-vt
|
|
||||||
pkgver=25.0.0
|
|
||||||
pkgrel=5
|
|
||||||
pkgdesc="XLibre dummy video driver with an allocated vt"
|
|
||||||
arch=(x86_64 aarch64)
|
|
||||||
_pkgname=xf86-video-dummy
|
|
||||||
url="https://github.com/X11Libre/${_pkgname}"
|
|
||||||
license=('MIT' 'X11')
|
|
||||||
depends=("xlibre-xserver>=${pkgver%.*}" 'glibc')
|
|
||||||
makedepends=("xlibre-xserver-devel>=${pkgver%.*}" 'xorgproto')
|
|
||||||
conflicts=("${_pkgname}")
|
|
||||||
provides=("${_pkgname}" 'x11win-video-dummy')
|
|
||||||
source=("${url}/archive/refs/tags/xlibre-${_pkgname}-${pkgver}.tar.gz"
|
|
||||||
"dummy_driver.patch")
|
|
||||||
groups=('xlibre-drivers')
|
|
||||||
sha256sums=('b56e610705cd3d4d86422a11c6b0d93357e4d4749a05178a85fd250301d357b9'
|
|
||||||
'68cdcf21e9b54a7fdb8e968292e1ef9ad154ddb1361b141a0a635c2a13c92bfa')
|
|
||||||
|
|
||||||
prepare() {
|
|
||||||
cd "${srcdir}/${_pkgname}-xlibre-${_pkgname}-${pkgver}/src"
|
|
||||||
patch -i "${srcdir}/dummy_driver.patch"
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
case "$CARCH" in
|
|
||||||
"x86_64")
|
|
||||||
CFLAGS=" -march=x86-64"
|
|
||||||
;;
|
|
||||||
"aarch64")
|
|
||||||
CFLAGS=" -march=armv8-a"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
CFLAGS=" -march=native"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
CFLAGS+=" -mtune=generic -O2 -pipe -fexceptions -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security"
|
|
||||||
CFLAGS+=" -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
|
|
||||||
LDFLAGS=" -Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,lazy -Wl,-z,relro -Wl,-z,pack-relative-relocs"
|
|
||||||
if [[ $CARCH != 'aarch64' ]]; then
|
|
||||||
CFLAGS+=" -fcf-protection"
|
|
||||||
fi
|
|
||||||
CXXFLAGS="${CFLAGS} -Wp,-D_GLIBCXX_ASSERTIONS"
|
|
||||||
export CFLAGS="${CFLAGS}"
|
|
||||||
export CXXFLAGS="${CXXFLAGS}"
|
|
||||||
export LDFLAGS="${LDFLAGS}"
|
|
||||||
|
|
||||||
cd "${srcdir}/${_pkgname}-xlibre-${_pkgname}-${pkgver}"
|
|
||||||
./autogen.sh
|
|
||||||
./configure --prefix=/usr
|
|
||||||
make
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
cd "${srcdir}/${_pkgname}-xlibre-${_pkgname}-${pkgver}"
|
|
||||||
make DESTDIR="${pkgdir}" install
|
|
||||||
install -Dm644 COPYING "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
--- a/src/dummy_driver.c
|
|
||||||
+++ b/src/dummy_driver.c
|
|
||||||
@@ -1016,10 +1016,10 @@ dummyDriverFunc(ScrnInfoPtr pScrn, xorgDriverFuncOp op, pointer ptr)
|
|
||||||
CARD32 *flag;
|
|
||||||
|
|
||||||
switch (op) {
|
|
||||||
- case GET_REQUIRED_HW_INTERFACES:
|
|
||||||
- flag = (CARD32*)ptr;
|
|
||||||
- (*flag) = HW_SKIP_CONSOLE;
|
|
||||||
- return TRUE;
|
|
||||||
+ case GET_REQUIRED_HW_INTERFACES:
|
|
||||||
+ flag = (CARD32*)ptr;
|
|
||||||
+ /* Allow the driver to allocate a VT instead of skipping the console. */
|
|
||||||
+ return TRUE;
|
|
||||||
default:
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
Executable
+180
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repoRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
selector="$repoRoot/pi4/files/usr/local/lib/stormux/select-speech-module"
|
||||||
|
tempDir="$(mktemp -d)"
|
||||||
|
mockBin="$tempDir/bin"
|
||||||
|
testCount=0
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$tempDir"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$mockBin" "$tempDir/runtime"
|
||||||
|
|
||||||
|
cat > "$mockBin/sudo" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
if [[ "${1:-}" == "-A" ]]; then
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
if [[ "${MOCK_SUDO_FAIL_WRITE:-no}" == "yes" && "$*" == *"--write-selection"* ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exec "$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$mockBin/spd-say" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
printf '%s\n' "$*" >> "$MOCK_LOG"
|
||||||
|
if [[ "${1:-}" == "-O" ]]; then
|
||||||
|
cat "$MOCK_MODULES"
|
||||||
|
exit "${MOCK_DISCOVERY_STATUS:-0}"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$mockBin/socat" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
read -r command
|
||||||
|
printf 'socat %s: %s\n' "$*" "$command" >> "$MOCK_LOG"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$mockBin/systemctl" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
printf 'systemctl %s\n' "$*" >> "$MOCK_LOG"
|
||||||
|
if [[ "${1:-}" == "is-active" ]]; then
|
||||||
|
[[ "${MOCK_FENRIR_ACTIVE:-no}" == "yes" ]]
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod 755 "$mockBin"/*
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equals() {
|
||||||
|
local expected="$1"
|
||||||
|
local actual="$2"
|
||||||
|
local description="$3"
|
||||||
|
|
||||||
|
[[ "$actual" == "$expected" ]] || fail "$description (expected '$expected', got '$actual')"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_file_contains() {
|
||||||
|
local filePath="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
local description="$3"
|
||||||
|
|
||||||
|
grep -qE -- "$pattern" "$filePath" || fail "$description"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_file_absent() {
|
||||||
|
local filePath="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
[[ ! -e "$filePath" ]] || fail "$description"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_case() {
|
||||||
|
caseDir="$tempDir/case-$testCount"
|
||||||
|
testCount=$((testCount + 1))
|
||||||
|
mkdir -p "$caseDir/runtime"
|
||||||
|
configFile="$caseDir/speechd.conf"
|
||||||
|
selectionFile="$caseDir/selected"
|
||||||
|
modulesFile="$caseDir/modules"
|
||||||
|
mockLog="$caseDir/mock.log"
|
||||||
|
: > "$mockLog"
|
||||||
|
printf 'OUTPUT MODULES\nrhvoice\nopenevv\nespeak-ng\n' > "$modulesFile"
|
||||||
|
printf '# Speech Dispatcher\nDefaultModule rhvoice\n' > "$configFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_selector() {
|
||||||
|
local input="$1"
|
||||||
|
|
||||||
|
printf '%b' "$input" | env \
|
||||||
|
PATH="$mockBin:$PATH" \
|
||||||
|
MOCK_LOG="$mockLog" \
|
||||||
|
MOCK_MODULES="$modulesFile" \
|
||||||
|
MOCK_DISCOVERY_STATUS="${MOCK_DISCOVERY_STATUS:-0}" \
|
||||||
|
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_SOCAT=socat \
|
||||||
|
STORMUX_SYSTEMCTL=systemctl \
|
||||||
|
"$selector" --selection-file "$selectionFile" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enter accepts the configured initial module and collapses duplicate directives.
|
||||||
|
reset_case
|
||||||
|
printf 'DefaultModule rhvoice\n# DefaultModule espeak-ng\nDefaultModule openevv\n' > "$configFile"
|
||||||
|
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" '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
|
||||||
|
run_selector 'x\033[B\n'
|
||||||
|
assert_file_contains "$mockLog" '^-o openevv This is openevv\.' "Down should preview OpenEVV"
|
||||||
|
assert_file_contains "$configFile" '^DefaultModule openevv$' "Down then Enter should select OpenEVV"
|
||||||
|
|
||||||
|
# Up wraps from the first module to the last.
|
||||||
|
reset_case
|
||||||
|
run_selector '\033[A\n'
|
||||||
|
assert_file_contains "$mockLog" '^-o espeak-ng This is espeak-ng\.' "Up should wrap and preview eSpeak NG"
|
||||||
|
assert_file_contains "$configFile" '^DefaultModule espeak-ng$' "wrapped module should persist"
|
||||||
|
|
||||||
|
# Escape retains the prior file and does not create an installer selection.
|
||||||
|
reset_case
|
||||||
|
cp "$configFile" "$caseDir/original.conf"
|
||||||
|
run_selector '\033'
|
||||||
|
cmp -s "$configFile" "$caseDir/original.conf" || fail "Escape should retain the configuration"
|
||||||
|
assert_file_absent "$selectionFile" "Escape should not record an installer selection"
|
||||||
|
|
||||||
|
# Empty discovery fails safely.
|
||||||
|
reset_case
|
||||||
|
printf 'OUTPUT MODULES\n' > "$modulesFile"
|
||||||
|
cp "$configFile" "$caseDir/original.conf"
|
||||||
|
run_selector '\n'
|
||||||
|
cmp -s "$configFile" "$caseDir/original.conf" || fail "empty discovery should retain the configuration"
|
||||||
|
assert_file_absent "$selectionFile" "empty discovery should not record a selection"
|
||||||
|
|
||||||
|
# Unsafe names are rejected rather than passed to spd-say or configuration.
|
||||||
|
reset_case
|
||||||
|
printf 'OUTPUT MODULES\nbad name\n;command\nopenevv\n' > "$modulesFile"
|
||||||
|
run_selector '\n'
|
||||||
|
assert_file_contains "$configFile" '^DefaultModule openevv$' "the remaining safe module should be selected"
|
||||||
|
if grep -qE 'bad name|;command' "$mockLog"; then
|
||||||
|
fail "unsafe module names should not reach spd-say"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A missing configuration simulates failed persistence and remains nonfatal.
|
||||||
|
reset_case
|
||||||
|
rm -f "$configFile"
|
||||||
|
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"
|
||||||
|
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
|
||||||
|
cp "$configFile" "$caseDir/original.conf"
|
||||||
|
MOCK_SUDO_FAIL_WRITE=yes run_selector '\033[B\n'
|
||||||
|
cmp -s "$configFile" "$caseDir/original.conf" || fail "selection-file failure should roll back the configuration"
|
||||||
|
assert_file_absent "$selectionFile" "selection-file failure should leave no selection"
|
||||||
|
|
||||||
|
printf 'PASS: %s speech selector cases\n' "$testCount"
|
||||||
+2
-2
@@ -86,7 +86,7 @@ The Stormux repository key is also embedded in the ISO at `/usr/share/stormux/st
|
|||||||
- **Fenrir screen reader** starts automatically (not speakup)
|
- **Fenrir screen reader** starts automatically (not speakup)
|
||||||
- **Pipewire audio** properly initialized before Fenrir starts
|
- **Pipewire audio** properly initialized before Fenrir starts
|
||||||
- **Speech-dispatcher** integration for speech synthesis
|
- **Speech-dispatcher** integration for speech synthesis
|
||||||
- GRUB plays an audible tune on boot for accessibility
|
- The UEFI systemd-boot menu enables its audible beep
|
||||||
- Boot menu defaults to accessible entry
|
- Boot menu defaults to accessible entry
|
||||||
- Service startup order ensures audio is ready before screen reader
|
- Service startup order ensures audio is ready before screen reader
|
||||||
- First-login live-environment setup calibrates volume, checks networking and time, then offers to run the installer
|
- First-login live-environment setup calibrates volume, checks networking and time, then offers to run the installer
|
||||||
@@ -165,7 +165,7 @@ Edit `packages.x86_64` and add package names (one per line).
|
|||||||
### Modifying Boot Configuration
|
### Modifying Boot Configuration
|
||||||
|
|
||||||
- BIOS: Edit `syslinux/archiso_sys-linux.cfg`
|
- BIOS: Edit `syslinux/archiso_sys-linux.cfg`
|
||||||
- UEFI: Edit `grub/grub.cfg`
|
- UEFI: Edit `efiboot/loader/loader.conf` and entries under `efiboot/loader/entries/`
|
||||||
|
|
||||||
### Adding Overlay Files
|
### Adding Overlay Files
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
bifrost=buyfraust
|
|
||||||
danestange=dainstanggey
|
|
||||||
stange=stangey
|
|
||||||
kde=kaydeee
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
asus=aysus
|
|
||||||
certificate=cirtifficate
|
|
||||||
douche=doosh*
|
|
||||||
espeak=easpeak*
|
|
||||||
freenginx=freeenginex
|
|
||||||
git=ghit*
|
|
||||||
github=ghittehub
|
|
||||||
gitea=ghittee
|
|
||||||
jolla=yolla
|
|
||||||
jenux=jennux
|
|
||||||
lightnin=lighttnin
|
|
||||||
nginx=enginex
|
|
||||||
rhvoice=ahraychvoice
|
|
||||||
shit=shitt
|
|
||||||
sync=sink*
|
|
||||||
timezone=timezoan
|
|
||||||
vinux=vinnux
|
|
||||||
wench=wentch*
|
|
||||||
youngin=younggin*
|
|
||||||
@@ -76,6 +76,10 @@ LocalFileSigLevel = Optional
|
|||||||
#[core-testing]
|
#[core-testing]
|
||||||
#Include = /etc/pacman.d/mirrorlist
|
#Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
[xlibre-stable]
|
||||||
|
SigLevel = Required DatabaseOptional
|
||||||
|
Server = https://packages.xlibre.net/arch/stable/$arch
|
||||||
|
|
||||||
[stormux]
|
[stormux]
|
||||||
SigLevel = Required DatabaseOptional
|
SigLevel = Required DatabaseOptional
|
||||||
Server = https://packages.stormux.org/$arch
|
Server = https://packages.stormux.org/$arch
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ while [[ $volume -le 130 ]]; do
|
|||||||
clear
|
clear
|
||||||
pactl set-sink-volume @DEFAULT_SINK@ "${volume}%" 2>/dev/null
|
pactl set-sink-volume @DEFAULT_SINK@ "${volume}%" 2>/dev/null
|
||||||
spd-say "If this is loud enough, press enter."
|
spd-say "If this is loud enough, press enter."
|
||||||
if read -t4 ; then
|
if read -r -t4 ; then
|
||||||
echo "Volume set to ${volume}%"
|
echo "Volume set to ${volume}%"
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
@@ -47,6 +47,9 @@ if [[ -x /etc/audibleprompt.sh ]]; then
|
|||||||
export sudoFlags=("-A")
|
export sudoFlags=("-A")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
/usr/local/lib/stormux/select-speech-module \
|
||||||
|
--selection-file /run/stormux/selected-speech-module
|
||||||
|
|
||||||
clear
|
clear
|
||||||
cat << "EOF"
|
cat << "EOF"
|
||||||
Hello, and welcome to Stormux!
|
Hello, and welcome to Stormux!
|
||||||
|
|||||||
@@ -28,10 +28,53 @@ install_sas() {
|
|||||||
return "$installStatus"
|
return "$installStatus"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_rhvoice_english_fixes() {
|
||||||
|
local rhvoiceRepo="https://git.stormux.org/storm/rhvoice-english"
|
||||||
|
local targetDir="/etc/RHVoice/dicts/English"
|
||||||
|
local tempDir
|
||||||
|
local installStatus=0
|
||||||
|
|
||||||
|
tempDir="$(mktemp -d)"
|
||||||
|
|
||||||
|
echo "Installing latest RHVoice English dictionary fixes..."
|
||||||
|
if ! git clone --depth 1 "$rhvoiceRepo" "$tempDir"; then
|
||||||
|
rm -rf "$tempDir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$targetDir"
|
||||||
|
rm -f "$targetDir/namefix.txt" "$targetDir/wordfix.txt"
|
||||||
|
if ! cp -a "$tempDir/English/." "$targetDir/"; then
|
||||||
|
installStatus=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$tempDir"
|
||||||
|
return "$installStatus"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_xlibre_key() {
|
||||||
|
local keyUrl="https://xlibre-arch.github.io/xlibre-archlinux.asc"
|
||||||
|
local keyId="B97F7C613F359424"
|
||||||
|
local keyFile
|
||||||
|
|
||||||
|
keyFile="$(mktemp)"
|
||||||
|
|
||||||
|
echo "Adding XLibre repository key..."
|
||||||
|
if ! curl -fsSL "$keyUrl" -o "$keyFile"; then
|
||||||
|
rm -f "$keyFile"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pacman-key --add "$keyFile"
|
||||||
|
pacman-key --lsign-key "$keyId"
|
||||||
|
rm -f "$keyFile"
|
||||||
|
}
|
||||||
|
|
||||||
# Initialize pacman keyring
|
# Initialize pacman keyring
|
||||||
echo "Initializing pacman keyring..."
|
echo "Initializing pacman keyring..."
|
||||||
pacman-key --init
|
pacman-key --init
|
||||||
pacman-key --populate archlinux
|
pacman-key --populate archlinux
|
||||||
|
install_xlibre_key
|
||||||
|
|
||||||
# Add Stormux repository key
|
# Add Stormux repository key
|
||||||
echo "Adding Stormux repository key..."
|
echo "Adding Stormux repository key..."
|
||||||
@@ -49,6 +92,21 @@ echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
|
|||||||
locale-gen
|
locale-gen
|
||||||
|
|
||||||
install_sas
|
install_sas
|
||||||
|
install_rhvoice_english_fixes
|
||||||
|
|
||||||
|
if command -v spd-conf > /dev/null 2>&1; then
|
||||||
|
spd-conf -n -C || echo "Warning: Speech Dispatcher system configuration failed."
|
||||||
|
fi
|
||||||
|
if [[ -f /etc/speech-dispatcher/speechd.conf ]]; then
|
||||||
|
speechBackup="$(mktemp)"
|
||||||
|
if /usr/local/lib/stormux/select-speech-module \
|
||||||
|
--apply-module rhvoice /etc/speech-dispatcher/speechd.conf "$speechBackup"; then
|
||||||
|
rm -f "$speechBackup"
|
||||||
|
else
|
||||||
|
echo "Warning: Could not set RHVoice as the default speech engine."
|
||||||
|
rm -f "$speechBackup"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Enable system services
|
# Enable system services
|
||||||
systemctl enable NetworkManager.service
|
systemctl enable NetworkManager.service
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ set_timezone() {
|
|||||||
ln -sf /usr/share/zoneinfo/"${region}"/"${city}" /etc/localtime
|
ln -sf /usr/share/zoneinfo/"${region}"/"${city}" /etc/localtime
|
||||||
timedatectl set-ntp true
|
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.
|
# Offer to switch fenrir layout.
|
||||||
echo "Would you like to switch Fenrir to laptop layout?"
|
echo "Would you like to switch Fenrir to laptop layout?"
|
||||||
echo "Press y for yes or n for no followed by enter."
|
echo "Press y for yes or n for no followed by enter."
|
||||||
@@ -45,8 +53,8 @@ continue="${continue::1}"
|
|||||||
if [[ "${continue,}" == "y" ]];then
|
if [[ "${continue,}" == "y" ]];then
|
||||||
sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf
|
sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf
|
||||||
clear
|
clear
|
||||||
systemctl restart fenrirscreenreader.service
|
|
||||||
fi
|
fi
|
||||||
|
restart_fenrir_speech
|
||||||
|
|
||||||
if ! ping -c1 stormux.org &> /dev/null ; then
|
if ! ping -c1 stormux.org &> /dev/null ; then
|
||||||
echo "No internet connection detected. Press enter to open NetworkManager."
|
echo "No internet connection detected. Press enter to open NetworkManager."
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ errorCount=0
|
|||||||
warningCount=0
|
warningCount=0
|
||||||
currentStep="initialization"
|
currentStep="initialization"
|
||||||
cleanupOnError=false
|
cleanupOnError=false
|
||||||
|
selectedSpeechModule="rhvoice"
|
||||||
|
|
||||||
|
if [[ -r /run/stormux/selected-speech-module ]]; then
|
||||||
|
mapfile -t selectedSpeechLines < /run/stormux/selected-speech-module
|
||||||
|
if [[ ${#selectedSpeechLines[@]} -eq 1 && "${selectedSpeechLines[0]}" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]]; then
|
||||||
|
selectedSpeechModule="${selectedSpeechLines[0]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -1286,6 +1294,35 @@ enable_multilib_repo() {
|
|||||||
"$pacmanConf"
|
"$pacmanConf"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_xlibre_key() {
|
||||||
|
local keyUrl="https://xlibre-arch.github.io/xlibre-archlinux.asc"
|
||||||
|
local keyId="B97F7C613F359424"
|
||||||
|
local keyFile
|
||||||
|
|
||||||
|
keyFile="$(mktemp)"
|
||||||
|
|
||||||
|
log_info "Adding XLibre repository key"
|
||||||
|
if ! curl -fsSL "$keyUrl" -o "$keyFile"; then
|
||||||
|
rm -f "$keyFile"
|
||||||
|
log_error "Could not download XLibre repository key"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pacman-key --add "$keyFile"; then
|
||||||
|
rm -f "$keyFile"
|
||||||
|
log_error "Could not add XLibre repository key"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pacman-key --lsign-key "$keyId"; then
|
||||||
|
rm -f "$keyFile"
|
||||||
|
log_error "Could not locally sign XLibre repository key"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$keyFile"
|
||||||
|
}
|
||||||
|
|
||||||
copy_rhvoice_english_fixes() {
|
copy_rhvoice_english_fixes() {
|
||||||
local sourceDir="/etc/RHVoice/dicts/English"
|
local sourceDir="/etc/RHVoice/dicts/English"
|
||||||
local targetDir="$mountPoint/etc/RHVoice/dicts/English"
|
local targetDir="$mountPoint/etc/RHVoice/dicts/English"
|
||||||
@@ -1302,8 +1339,8 @@ copy_rhvoice_english_fixes() {
|
|||||||
install_base_system() {
|
install_base_system() {
|
||||||
log_info "=== Installing Base System ==="
|
log_info "=== Installing Base System ==="
|
||||||
|
|
||||||
# Copy pacman config with Stormux repo
|
# Copy pacman config with XLibre and Stormux repositories
|
||||||
log_info "Configuring pacman with Stormux repository"
|
log_info "Configuring pacman with XLibre and Stormux repositories"
|
||||||
log_info "Configuring package manager"
|
log_info "Configuring package manager"
|
||||||
|
|
||||||
local copyItems=(
|
local copyItems=(
|
||||||
@@ -1345,37 +1382,45 @@ install_base_system() {
|
|||||||
enable_multilib_repo "$mountPoint/etc/pacman.conf"
|
enable_multilib_repo "$mountPoint/etc/pacman.conf"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! install_xlibre_key; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Define package groups
|
# Define package groups
|
||||||
local basePackages=(
|
local basePackages=(
|
||||||
base base-devel linux linux-firmware
|
base base-devel linux linux-firmware
|
||||||
networkmanager sudo dialog git ii rsync wget
|
networkmanager sudo dialog git ii rsync wget curl
|
||||||
bash-completion cronie openssh
|
bash-completion cronie openssh rng-tools
|
||||||
)
|
)
|
||||||
|
|
||||||
local audioPackages=(
|
local audioPackages=(
|
||||||
pipewire pipewire-alsa pipewire-pulse pipewire-jack
|
pipewire pipewire-alsa pipewire-pulse pipewire-jack
|
||||||
wireplumber alsa-utils sox
|
wireplumber alsa-utils sox gstreamer
|
||||||
|
gst-plugins-base gst-plugins-good
|
||||||
alsa-firmware sof-firmware
|
alsa-firmware sof-firmware
|
||||||
bluez bluez-utils
|
bluez bluez-utils
|
||||||
)
|
)
|
||||||
|
|
||||||
local accessibilityPackages=(
|
local accessibilityPackages=(
|
||||||
espeak-ng rhvoice-voice-bdl speech-dispatcher
|
espeak-ng rhvoice-voice-bdl speech-dispatcher
|
||||||
fenrir brltty
|
fenrir brltty cthulhu
|
||||||
)
|
)
|
||||||
|
|
||||||
local utilityPackages=(
|
local utilityPackages=(
|
||||||
vi vim nano screen magic-wormhole
|
vi vim nano screen magic-wormhole
|
||||||
man man-pages
|
man-db man-pages
|
||||||
xdg-user-dirs xdg-utils
|
xdg-user-dirs xdg-utils
|
||||||
poppler socat parted
|
poppler socat parted
|
||||||
realtime-privileges
|
realtime-privileges wireless-regdb
|
||||||
|
python-dbus python-gobject python-msgpack
|
||||||
|
python-pyenchant python-pyte python-pyperclip
|
||||||
|
python-tornado
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stormux-specific packages (installed later in chroot after keyring setup)
|
# Stormux-specific packages (installed later in chroot after keyring setup)
|
||||||
# Note: NOT local so it's available in configure_system()
|
# Note: NOT local so it's available in configure_system()
|
||||||
stormuxPackages=(
|
stormuxPackages=(
|
||||||
yay w3m-git
|
openevv-git yay w3m-git
|
||||||
)
|
)
|
||||||
|
|
||||||
# Combine all packages
|
# Combine all packages
|
||||||
@@ -1385,18 +1430,17 @@ install_base_system() {
|
|||||||
if [[ "$hasDesktop" == true ]]; then
|
if [[ "$hasDesktop" == true ]]; then
|
||||||
case "$desktopEnvironment" in
|
case "$desktopEnvironment" in
|
||||||
*)
|
*)
|
||||||
allPackages+=(xlibre-xserver xlibre-input-libinput nodm-dgw brave-bin)
|
allPackages+=(
|
||||||
|
i38 nodm-dgw xorg-xinit
|
||||||
|
xlibre-xserver xlibre-input-libinput
|
||||||
|
xlibre-video-dummy-with-vt xlibre-video-fbdev
|
||||||
|
)
|
||||||
if [[ "$installSteam" == "yes" ]]; then
|
if [[ "$installSteam" == "yes" ]]; then
|
||||||
allPackages+=(steam)
|
allPackages+=(steam)
|
||||||
fi
|
fi
|
||||||
;;&
|
;;&
|
||||||
i3)
|
i3)
|
||||||
allPackages+=(i3-wm orca python-psutil lxterminal pluma)
|
allPackages+=(xdotool)
|
||||||
allPackages+=(discount jq libnotify xfce4-notifyd pamixer playerctl)
|
|
||||||
allPackages+=(python-i3ipc python-wxpython sox yad)
|
|
||||||
allPackages+=(lxsession magic-wormhole pcmanfm)
|
|
||||||
allPackages+=(python-gobject python-pillow python-pytesseract scrot tesseract)
|
|
||||||
allPackages+=(tesseract-data-eng udiskie xorg-setxkbmap xdotool)
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
@@ -1425,10 +1469,10 @@ install_base_system() {
|
|||||||
|
|
||||||
# Generate or preserve fstab
|
# Generate or preserve fstab
|
||||||
if [[ "$useExistingMount" == true ]]; then
|
if [[ "$useExistingMount" == true ]]; then
|
||||||
if [[ -s "$mountPoint/etc/fstab" ]]; then
|
if grep -Eq '^[[:space:]]*[^#[:space:]]' "$mountPoint/etc/fstab" 2>/dev/null; then
|
||||||
log_info "Manual mount mode: preserving existing /etc/fstab"
|
log_info "Manual mount mode: preserving existing /etc/fstab"
|
||||||
else
|
else
|
||||||
log_warning "Manual mount mode: /etc/fstab not found, generating one"
|
log_warning "Manual mount mode: /etc/fstab has no filesystem entries, generating one"
|
||||||
genfstab -U "$mountPoint" > "$mountPoint/etc/fstab"
|
genfstab -U "$mountPoint" > "$mountPoint/etc/fstab"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
@@ -1750,7 +1794,6 @@ systemctl enable NetworkManager.service
|
|||||||
systemctl enable brltty.path
|
systemctl enable brltty.path
|
||||||
systemctl enable fenrirscreenreader.service
|
systemctl enable fenrirscreenreader.service
|
||||||
systemctl enable cronie.service
|
systemctl enable cronie.service
|
||||||
systemctl enable ssh-login-monitor.service
|
|
||||||
|
|
||||||
# Enable bluetooth if present
|
# Enable bluetooth if present
|
||||||
if ! systemctl enable bluetooth.service; then
|
if ! systemctl enable bluetooth.service; then
|
||||||
@@ -1759,56 +1802,65 @@ fi
|
|||||||
|
|
||||||
# Enable SSH if requested
|
# Enable SSH if requested
|
||||||
if [[ "${enableSsh}" == "yes" ]]; then
|
if [[ "${enableSsh}" == "yes" ]]; then
|
||||||
systemctl enable sshd.service
|
systemctl enable sshd.service ssh-login-monitor.service
|
||||||
echo "SSH server enabled"
|
echo "SSH server enabled"
|
||||||
|
else
|
||||||
|
systemctl disable sshd.service ssh-login-monitor.service
|
||||||
|
systemctl disable sshd.socket 2> /dev/null || true
|
||||||
|
echo "SSH server disabled"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Setup desktop environment configurations
|
# Setup desktop environment configurations
|
||||||
if [[ "${desktopEnvironment}" == "i3" ]]; then
|
if [[ "${desktopEnvironment}" == "i3" ]]; then
|
||||||
firstUser="${userNames[0]}"
|
desktopUser="${autoLoginUser}"
|
||||||
|
|
||||||
# Create git directory for better organization
|
# Create git directory for better organization
|
||||||
mkdir -p /home/\$firstUser/git
|
mkdir -p /home/\$desktopUser/git
|
||||||
chown \$firstUser:users /home/\$firstUser/git
|
chown \$desktopUser:users /home/\$desktopUser/git
|
||||||
|
|
||||||
# Clone I38 to ~/git/I38
|
# Clone I38 to ~/git/I38
|
||||||
echo "Cloning I38 accessibility configuration..."
|
echo "Cloning I38 accessibility configuration..."
|
||||||
if ! sudo -u \$firstUser git clone https://git.stormux.org/storm/I38 /home/\$firstUser/git/I38; then
|
if ! sudo -u \$desktopUser git clone https://git.stormux.org/storm/I38 /home/\$desktopUser/git/I38; then
|
||||||
echo "WARNING: Failed to clone I38 repository"
|
echo "WARNING: Failed to clone I38 repository"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run I38 setup to generate accessible i3 configuration
|
# Run I38 setup to generate accessible i3 configuration
|
||||||
if [[ -d /home/\$firstUser/git/I38 ]]; then
|
if [[ -d /home/\$desktopUser/git/I38 ]]; then
|
||||||
echo "Configuring I38 for accessibility..."
|
echo "Configuring I38 for accessibility..."
|
||||||
cd /home/\$firstUser/git/I38 || exit 1
|
cd /home/\$desktopUser/git/I38 || exit 1
|
||||||
|
|
||||||
# Ensure xdotool is available for I38 setup
|
if ! sudo -u \$desktopUser ./i38.sh -D; then
|
||||||
if ! command -v xdotool >/dev/null 2>&1; then
|
echo "WARNING: I38 default setup reported an error"
|
||||||
echo "Installing xdotool for I38..."
|
|
||||||
if ! pacman -Sy --noconfirm --needed xdotool; then
|
|
||||||
echo "WARNING: Failed to install xdotool for I38 setup"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run I38 setup scripts as the user
|
|
||||||
# -x generates xinitrc and xprofile
|
|
||||||
# Main script generates i3 config with accessibility features
|
|
||||||
play -qnV0 synth 3 pluck D3 pluck A3 pluck D4 pluck F4 pluck A4 delay 0 .1 .2 .3 .4 remix - chorus 0.9 0.9 38 0.75 0.3 0.5 -t 2>/dev/null || true
|
|
||||||
if ! sudo -u \$firstUser ./i38.sh -x; then
|
|
||||||
echo "WARNING: I38 xinit/xprofile setup reported an error"
|
|
||||||
fi
|
|
||||||
play -qnV0 synth 3 pluck D3 pluck A3 pluck D4 pluck F4 pluck A4 delay 0 .1 .2 .3 .4 remix - chorus 0.9 0.9 38 0.75 0.3 0.5 -t 2>/dev/null || true
|
|
||||||
if ! sudo -u \$firstUser ./i38.sh; then
|
|
||||||
echo "WARNING: I38 main setup reported an error"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd - > /dev/null || exit 1
|
cd - > /dev/null || exit 1
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Initialize pacman keyring
|
# Initialize pacman keyring
|
||||||
pacman-key --init
|
pacman-key --init
|
||||||
pacman-key --populate archlinux
|
pacman-key --populate archlinux
|
||||||
|
|
||||||
|
# Import XLibre key so installed systems keep using the official XLibre repository.
|
||||||
|
keyFile="\$(mktemp)"
|
||||||
|
if curl -fsSL https://xlibre-arch.github.io/xlibre-archlinux.asc -o "\$keyFile"; then
|
||||||
|
if ! pacman-key --add "\$keyFile"; then
|
||||||
|
echo "ERROR: Could not add XLibre signing key to pacman keyring"
|
||||||
|
rm -f "\$keyFile"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! pacman-key --lsign-key B97F7C613F359424; then
|
||||||
|
echo "ERROR: Could not locally sign XLibre pacman key"
|
||||||
|
rm -f "\$keyFile"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ERROR: Could not download XLibre signing key"
|
||||||
|
rm -f "\$keyFile"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -f "\$keyFile"
|
||||||
|
|
||||||
# Import Stormux key if present
|
# Import Stormux key if present
|
||||||
if [[ -f /usr/share/pacman/keyrings/stormux.gpg ]]; then
|
if [[ -f /usr/share/pacman/keyrings/stormux.gpg ]]; then
|
||||||
if ! pacman-key --add /usr/share/pacman/keyrings/stormux.gpg; then
|
if ! pacman-key --add /usr/share/pacman/keyrings/stormux.gpg; then
|
||||||
@@ -1841,7 +1893,11 @@ $pipewireConfigCmd
|
|||||||
# Configure nodm for automatic login (only after nodm package is installed)
|
# Configure nodm for automatic login (only after nodm package is installed)
|
||||||
if [[ "${desktopEnvironment}" == "i3" ]]; then
|
if [[ "${desktopEnvironment}" == "i3" ]]; then
|
||||||
if [[ -f /etc/nodm.conf ]]; then
|
if [[ -f /etc/nodm.conf ]]; then
|
||||||
sed -i "s/{user}/${autoLoginUser}/g" /etc/nodm.conf
|
sed -i \
|
||||||
|
-e "s/{user}/${autoLoginUser}/g" \
|
||||||
|
-e "s#^NODM_USER=.*#NODM_USER='${autoLoginUser}'#" \
|
||||||
|
-e "s#^NODM_XSESSION=.*#NODM_XSESSION='/home/${autoLoginUser}/.xinitrc'#" \
|
||||||
|
/etc/nodm.conf
|
||||||
systemctl enable nodm.service
|
systemctl enable nodm.service
|
||||||
echo "Display manager (nodm) configured for ${autoLoginUser}"
|
echo "Display manager (nodm) configured for ${autoLoginUser}"
|
||||||
else
|
else
|
||||||
@@ -1849,6 +1905,35 @@ if [[ "${desktopEnvironment}" == "i3" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if command -v spd-conf > /dev/null 2>&1; then
|
||||||
|
spd-conf -n -C || echo "WARNING: Speech Dispatcher system configuration failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f /etc/speech-dispatcher/speechd.conf ]]; then
|
||||||
|
speechConfig=/etc/speech-dispatcher/speechd.conf
|
||||||
|
speechConfigTemp="\$(mktemp "\${speechConfig}.stormux.XXXXXX")"
|
||||||
|
awk -v moduleName="${selectedSpeechModule}" '
|
||||||
|
BEGIN { wroteDefault = 0 }
|
||||||
|
/^[[:space:]]*#?[[:space:]]*DefaultModule[[:space:]]+/ {
|
||||||
|
if (!wroteDefault) {
|
||||||
|
print "DefaultModule " moduleName
|
||||||
|
wroteDefault = 1
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (!wroteDefault) {
|
||||||
|
print ""
|
||||||
|
print "DefaultModule " moduleName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "\$speechConfig" > "\$speechConfigTemp"
|
||||||
|
chown --reference="\$speechConfig" "\$speechConfigTemp"
|
||||||
|
chmod --reference="\$speechConfig" "\$speechConfigTemp"
|
||||||
|
mv -f "\$speechConfigTemp" "\$speechConfig"
|
||||||
|
fi
|
||||||
|
|
||||||
# Create system-wide accessibility configuration
|
# Create system-wide accessibility configuration
|
||||||
cat > /etc/profile.d/stormux-accessibility.sh <<'PROFILE_EOF'
|
cat > /etc/profile.d/stormux-accessibility.sh <<'PROFILE_EOF'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
@@ -2231,7 +2316,6 @@ else
|
|||||||
if [[ "$desktopEnvironment" == "i3" ]]; then
|
if [[ "$desktopEnvironment" == "i3" ]]; then
|
||||||
log " 4. Your i3 desktop is configured with I38 for accessibility"
|
log " 4. Your i3 desktop is configured with I38 for accessibility"
|
||||||
log " - I38 source is available in ~/git/I38 for customization"
|
log " - I38 source is available in ~/git/I38 for customization"
|
||||||
log " - Press Alt+Shift+F1 for I38 help after login"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log ""
|
log ""
|
||||||
@@ -2241,7 +2325,21 @@ else
|
|||||||
# Play success sound
|
# Play success sound
|
||||||
play_success_sound
|
play_success_sound
|
||||||
finalExitCode=0
|
finalExitCode=0
|
||||||
|
promptPoweroff=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${promptPoweroff:-false}" == true ]]; then
|
||||||
|
echo ""
|
||||||
|
read -rp "Power off this installation media now? [y/N]: " poweroffChoice
|
||||||
|
if [[ "$poweroffChoice" =~ ^[Yy]$ ]]; then
|
||||||
|
log_info "Powering off the installation media"
|
||||||
|
poweroff
|
||||||
|
log_warning "Poweroff command failed. To power off later, type: poweroff"
|
||||||
|
else
|
||||||
|
log "Leaving the installer. To power off later, type: poweroff"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
read -rp "Press Enter to exit..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
read -rp "Press Enter to exit..."
|
|
||||||
exit "$finalExitCode"
|
exit "$finalExitCode"
|
||||||
|
|||||||
+334
@@ -0,0 +1,334 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
speechConfig="${STORMUX_SPEECH_CONFIG:-/etc/speech-dispatcher/speechd.conf}"
|
||||||
|
selectionFile=""
|
||||||
|
spdSayCommand="${STORMUX_SPD_SAY:-spd-say}"
|
||||||
|
systemctlCommand="${STORMUX_SYSTEMCTL:-systemctl}"
|
||||||
|
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_.-]*$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_module() {
|
||||||
|
local moduleName="$1"
|
||||||
|
local configPath="$2"
|
||||||
|
local backupPath="$3"
|
||||||
|
local tempPath
|
||||||
|
|
||||||
|
is_safe_module "$moduleName" || return 1
|
||||||
|
[[ -f "$configPath" ]] || return 1
|
||||||
|
|
||||||
|
mkdir -p "${backupPath%/*}" || return 1
|
||||||
|
cp --preserve=mode,ownership,timestamps "$configPath" "$backupPath" || return 1
|
||||||
|
tempPath="$(mktemp "${configPath}.stormux.XXXXXX")" || return 1
|
||||||
|
|
||||||
|
if ! awk -v moduleName="$moduleName" '
|
||||||
|
BEGIN { wroteDefault = 0 }
|
||||||
|
/^[[:space:]]*#?[[:space:]]*DefaultModule[[:space:]]+/ {
|
||||||
|
if (!wroteDefault) {
|
||||||
|
print "DefaultModule " moduleName
|
||||||
|
wroteDefault = 1
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (!wroteDefault) {
|
||||||
|
print ""
|
||||||
|
print "DefaultModule " moduleName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$configPath" > "$tempPath"; then
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chown --reference="$configPath" "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
chmod --reference="$configPath" "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
mv -f "$tempPath" "$configPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_config() {
|
||||||
|
local configPath="$1"
|
||||||
|
local backupPath="$2"
|
||||||
|
local tempPath
|
||||||
|
|
||||||
|
[[ -f "$backupPath" ]] || return 1
|
||||||
|
tempPath="$(mktemp "${configPath}.stormux.XXXXXX")" || return 1
|
||||||
|
if ! cp --preserve=mode,ownership,timestamps "$backupPath" "$tempPath"; then
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
mv -f "$tempPath" "$configPath" || return 1
|
||||||
|
rm -f "$backupPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_selection() {
|
||||||
|
local moduleName="$1"
|
||||||
|
local targetPath="$2"
|
||||||
|
local tempPath
|
||||||
|
|
||||||
|
is_safe_module "$moduleName" || return 1
|
||||||
|
mkdir -p "${targetPath%/*}" || return 1
|
||||||
|
tempPath="$(mktemp "${targetPath}.stormux.XXXXXX")" || return 1
|
||||||
|
printf '%s\n' "$moduleName" > "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
chmod 644 "$tempPath" || {
|
||||||
|
rm -f "$tempPath"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
mv -f "$tempPath" "$targetPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
--apply-module)
|
||||||
|
[[ $# -eq 4 ]] || exit 2
|
||||||
|
apply_module "$2" "$3" "$4"
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
--restore-config)
|
||||||
|
[[ $# -eq 3 ]] || exit 2
|
||||||
|
restore_config "$2" "$3"
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
--write-selection)
|
||||||
|
[[ $# -eq 3 ]] || exit 2
|
||||||
|
write_selection "$2" "$3"
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--config)
|
||||||
|
[[ $# -ge 2 ]] || exit 2
|
||||||
|
speechConfig="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--selection-file)
|
||||||
|
[[ $# -ge 2 ]] || exit 2
|
||||||
|
selectionFile="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Unknown option: %s\n' "$1" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
sudoCommand=(sudo)
|
||||||
|
if [[ -n "${SUDO_ASKPASS:-}" ]]; then
|
||||||
|
sudoCommand+=(-A)
|
||||||
|
fi
|
||||||
|
|
||||||
|
announce_failure() {
|
||||||
|
local previousModule="$1"
|
||||||
|
local message="Speech engine selection failed. Your previous speech engine is still selected."
|
||||||
|
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
if is_safe_module "$previousModule"; then
|
||||||
|
"$spdSayCommand" -o "$previousModule" "$message" >/dev/null 2>&1 || \
|
||||||
|
"$spdSayCommand" "$message" >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
"$spdSayCommand" "$message" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$message" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
set_fenrir_speech() {
|
||||||
|
local enabled="$1"
|
||||||
|
|
||||||
|
printf 'setting set speech#enabled=%s\n' "$enabled" | \
|
||||||
|
"$socatCommand" - UNIX-CLIENT:"$fenrirSocket" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if ! output="$("$spdSayCommand" -O 2>/dev/null)"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
modules=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
candidate="${line%$'\r'}"
|
||||||
|
candidate="${candidate#"${candidate%%[![:space:]]*}"}"
|
||||||
|
candidate="${candidate%"${candidate##*[![:space:]]}"}"
|
||||||
|
[[ -n "$candidate" ]] || continue
|
||||||
|
if [[ "$skippedHeading" == false ]]; then
|
||||||
|
skippedHeading=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
is_safe_module "$candidate" || continue
|
||||||
|
modules+=("$candidate")
|
||||||
|
done <<< "$output"
|
||||||
|
|
||||||
|
[[ ${#modules[@]} -gt 0 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
configured_module() {
|
||||||
|
local configured=""
|
||||||
|
|
||||||
|
if [[ -r "$speechConfig" ]]; then
|
||||||
|
configured="$(awk '$1 == "DefaultModule" { print $2; exit }' "$speechConfig")"
|
||||||
|
fi
|
||||||
|
if ! is_safe_module "$configured"; then
|
||||||
|
configured="rhvoice"
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$configured"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_index() {
|
||||||
|
local wanted="$1"
|
||||||
|
local index
|
||||||
|
|
||||||
|
for index in "${!modules[@]}"; do
|
||||||
|
if [[ "${modules[$index]}" == "$wanted" ]]; then
|
||||||
|
printf '%s\n' "$index"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_menu() {
|
||||||
|
local index
|
||||||
|
|
||||||
|
printf '\033[H\033[2J'
|
||||||
|
printf 'Select the default speech engine. Use Up and Down, Enter to select, or Escape to keep the current engine.\n\n'
|
||||||
|
for index in "${!modules[@]}"; do
|
||||||
|
if [[ $index -eq $selectedIndex ]]; then
|
||||||
|
printf '> %s\n' "${modules[$index]}"
|
||||||
|
else
|
||||||
|
printf ' %s\n' "${modules[$index]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
read_menu_key() {
|
||||||
|
local first rest=""
|
||||||
|
|
||||||
|
IFS= read -rsn1 first || return 1
|
||||||
|
if [[ -z "$first" ]]; then
|
||||||
|
printf 'enter\n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$first" == $'\033' ]]; then
|
||||||
|
IFS= read -rsn2 -t 0.1 rest || true
|
||||||
|
case "$rest" in
|
||||||
|
'[A') printf 'up\n' ;;
|
||||||
|
'[B') printf 'down\n' ;;
|
||||||
|
'') printf 'escape\n' ;;
|
||||||
|
*) printf 'other\n' ;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
printf 'other\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -a modules=()
|
||||||
|
previousModule="$(configured_module)"
|
||||||
|
mute_fenrir_speech
|
||||||
|
if ! discover_modules; then
|
||||||
|
announce_failure "$previousModule"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! selectedIndex="$(module_index "$previousModule")"; then
|
||||||
|
if selectedIndex="$(module_index rhvoice)"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
selectedIndex=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
draw_menu
|
||||||
|
"$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
|
||||||
|
announce_failure "$previousModule"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
case "$keyName" in
|
||||||
|
up)
|
||||||
|
selectedIndex=$(( (selectedIndex - 1 + ${#modules[@]}) % ${#modules[@]} ))
|
||||||
|
;;
|
||||||
|
down)
|
||||||
|
selectedIndex=$(( (selectedIndex + 1) % ${#modules[@]} ))
|
||||||
|
;;
|
||||||
|
enter)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
escape)
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
"$spdSayCommand" -o "$previousModule" "Keeping ${previousModule} as the default speech engine." >/dev/null 2>&1 || true
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
draw_menu
|
||||||
|
moduleName="${modules[$selectedIndex]}"
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
"$spdSayCommand" -o "$moduleName" "This is ${moduleName}. If selected, I will be your default text-to-speech voice." >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
|
||||||
|
selectedModule="${modules[$selectedIndex]}"
|
||||||
|
backupFile="${runtimeDir}/speechd.conf.$$.backup"
|
||||||
|
"$spdSayCommand" -C >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if ! "${sudoCommand[@]}" "$scriptPath" --apply-module "$selectedModule" "$speechConfig" "$backupFile"; then
|
||||||
|
"${sudoCommand[@]}" "$scriptPath" --restore-config "$speechConfig" "$backupFile" >/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
|
||||||
|
announce_failure "$previousModule"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$backupFile" 2>/dev/null || "${sudoCommand[@]}" rm -f "$backupFile" 2>/dev/null || true
|
||||||
|
|
||||||
|
"$spdSayCommand" -o "$selectedModule" "${selectedModule} is now the default speech engine." >/dev/null 2>&1 || true
|
||||||
|
printf 'Default speech engine set to %s.\n' "$selectedModule"
|
||||||
@@ -13,7 +13,7 @@ bluez
|
|||||||
bluez-utils
|
bluez-utils
|
||||||
bolt
|
bolt
|
||||||
brltty
|
brltty
|
||||||
broadcom-wl
|
broadcom-wl-dkms
|
||||||
btrfs-progs
|
btrfs-progs
|
||||||
clonezilla
|
clonezilla
|
||||||
cloud-init
|
cloud-init
|
||||||
@@ -59,6 +59,7 @@ lftp
|
|||||||
libfido2
|
libfido2
|
||||||
libusb-compat
|
libusb-compat
|
||||||
linux
|
linux
|
||||||
|
linux-headers
|
||||||
linux-atm
|
linux-atm
|
||||||
linux-firmware
|
linux-firmware
|
||||||
linux-firmware-marvell
|
linux-firmware-marvell
|
||||||
@@ -90,6 +91,7 @@ nvme-cli
|
|||||||
open-iscsi
|
open-iscsi
|
||||||
open-vm-tools
|
open-vm-tools
|
||||||
openconnect
|
openconnect
|
||||||
|
openevv-git
|
||||||
openpgp-card-tools
|
openpgp-card-tools
|
||||||
openssh
|
openssh
|
||||||
openvpn
|
openvpn
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ LocalFileSigLevel = Optional
|
|||||||
#[core-testing]
|
#[core-testing]
|
||||||
#Include = /etc/pacman.d/mirrorlist
|
#Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
|
[xlibre-stable]
|
||||||
|
SigLevel = Required DatabaseOptional
|
||||||
|
Server = https://packages.xlibre.net/arch/stable/$arch
|
||||||
|
|
||||||
[stormux]
|
[stormux]
|
||||||
SigLevel = Required DatabaseOptional
|
SigLevel = Required DatabaseOptional
|
||||||
Server = https://packages.stormux.org/$arch
|
Server = https://packages.stormux.org/$arch
|
||||||
|
|||||||
@@ -18,15 +18,13 @@ bootstrap_tarball_compression=('zstd' '-c' '-T0' '--auto-threads=logical' '--lon
|
|||||||
file_permissions=(
|
file_permissions=(
|
||||||
["/etc/shadow"]="0:0:400"
|
["/etc/shadow"]="0:0:400"
|
||||||
["/root"]="0:0:750"
|
["/root"]="0:0:750"
|
||||||
["/root/.automated_script.sh"]="0:0:755"
|
|
||||||
["/root/customize_airootfs.sh"]="0:0:755"
|
["/root/customize_airootfs.sh"]="0:0:755"
|
||||||
["/root/.gnupg"]="0:0:700"
|
["/root/.gnupg"]="0:0:700"
|
||||||
["/root/.config"]="0:0:700"
|
["/root/.config"]="0:0:700"
|
||||||
["/usr/local/bin/choose-mirror"]="0:0:755"
|
|
||||||
["/usr/local/bin/Installation_guide"]="0:0:755"
|
|
||||||
["/usr/local/bin/livecd-sound"]="0:0:755"
|
["/usr/local/bin/livecd-sound"]="0:0:755"
|
||||||
["/usr/local/bin/configure-stormux"]="0:0:755"
|
["/usr/local/bin/configure-stormux"]="0:0:755"
|
||||||
["/usr/local/bin/install-stormux"]="0:0:755"
|
["/usr/local/bin/install-stormux"]="0:0:755"
|
||||||
|
["/usr/local/lib/stormux/select-speech-module"]="0:0:755"
|
||||||
["/usr/local/bin/stormux-setup.sh"]="0:0:755"
|
["/usr/local/bin/stormux-setup.sh"]="0:0:755"
|
||||||
["/usr/local/bin/init-pipewire-sound.sh"]="0:0:755"
|
["/usr/local/bin/init-pipewire-sound.sh"]="0:0:755"
|
||||||
["/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh"]="0:0:755"
|
["/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh"]="0:0:755"
|
||||||
|
|||||||
Reference in New Issue
Block a user