#!/usr/bin/env bash # Salah Point screen for a Raspberry Pi: the mosque's prayer times in full screen, from power on, with or without internet. # For Raspberry Pi OS Bookworm or Trixie with the labwc desktop, or the Lite image. Not yet tried on a real Pi. # # Download it, check it, then run it. Read it first if you like: it is one file, and it is all there is. # curl -fsSLO https://tv.salahpoint.com/pi/install-1.0.sh # sha256sum install-1.0.sh compare with the number on https://salahpoint.com/resources/raspberry-pi # bash install-1.0.sh # # Safe to run again. Afterwards `salahpoint-config` changes the settings, and `salahpoint-config uninstall` puts the Pi back. # # Options, for running it with no questions: # --yes take the defaults for anything not given # --address resume|choose|| # resume (default): the screen asks once, by search or phone, then remembers the mosque # --battery-clock none|pi5|ds3231 # the clock that keeps the time with the power off. A mosque that may run without internet needs one # --rotate 0|90|180|270 an upright screen is 90 or 270 # --resolution 1080p|auto 1080p (default) keeps a 4K TV from making the screen slow # --reduced-motion yes|no stops the slow moving background, for older models (default yes on a Pi 3) # --force-hdmi yes|no keep HDMI0 on when the TV is off at start (default yes) # --user who the screen runs as (default: who ran sudo) # --any-device skip the check for a Raspberry Pi and its OS, for testing elsewhere # --uninstall the same as `salahpoint-config uninstall` set -euo pipefail umask 022 VERSION=1.0 SITE=https://tv.salahpoint.com SHARE=/usr/local/share/salahpoint CONF_DIR=/etc/salahpoint CONF=$CONF_DIR/kiosk.conf STATE_DIR=/var/lib/salahpoint STATE=$STATE_DIR/state BOOT=/boot/firmware JOURNAL_CONF=/etc/systemd/journald.conf.d/90-salahpoint.conf RTC_RULE=/etc/udev/rules.d/90-salahpoint-rtc.rules TIMESYNC_CLOCK=/var/lib/systemd/timesync/clock RTC_SINCE=/sys/class/rtc/rtc0/since_epoch MARK_START="# >>> salahpoint: written by salahpoint-config, removed by its uninstall" MARK_END="# <<< salahpoint" # 1 January 2026: a clock chip that reads earlier than this has lost its time. EARLIEST=1767225600 say() { printf '%s\n' "$*"; } warn() { printf 'Note: %s\n' "$*" >&2; } die() { printf 'Stopped: %s\n' "$*" >&2 exit 1 } # --------------------------------------------------------------------------------------------------------------------------- # Settings. A plain file of KEY=value, read line by line and never run. ADDRESS=resume BATTERY_CLOCK=none ROTATE=0 RESOLUTION=1080p REDUCED_MOTION=no FORCE_HDMI=yes KIOSK_USER= EXTRA_FLAGS= read_conf() { [ -r "$CONF" ] || return 0 local key value while IFS='=' read -r key value; do case "$key" in ADDRESS) ADDRESS=$value ;; BATTERY_CLOCK) BATTERY_CLOCK=$value ;; ROTATE) ROTATE=$value ;; RESOLUTION) RESOLUTION=$value ;; REDUCED_MOTION) REDUCED_MOTION=$value ;; FORCE_HDMI) FORCE_HDMI=$value ;; KIOSK_USER) KIOSK_USER=$value ;; EXTRA_FLAGS) EXTRA_FLAGS=$value ;; esac done <"$CONF" } valid_slug() { [[ $1 =~ ^[a-z0-9][a-z0-9-]{0,99}$ ]]; } valid_url() { [[ $1 =~ ^https://[A-Za-z0-9.-]+(:[0-9]{1,5})?(/[A-Za-z0-9._~%/?=\&+-]*)?$ ]]; } # resume, choose, a slug or an https address. Anything else is refused, so nothing odd ever reaches the browser's command line. check_address() { local value=${1,,} case "$value" in resume | choose) return 0 ;; esac valid_slug "$value" || valid_url "$1" } address_url() { case "$1" in resume) printf '%s/?resume=1' "$SITE" ;; choose) printf '%s/' "$SITE" ;; https://*) printf '%s' "$1" ;; *) printf '%s/%s' "$SITE" "${1,,}" ;; esac } write_conf() { mkdir -p "$CONF_DIR" cat >"$CONF.new" <"$file.salahpoint" cat "$file.salahpoint" >"$file" rm -f "$file.salahpoint" } # Replaces our block at the end of the file with the given lines, keeping the file's owner. put_block() { local file=$1 lines=$2 remove_block "$file" [ -n "$lines" ] || return 0 [ ! -s "$file" ] || [ -z "$(tail -c1 "$file")" ] || printf '\n' >>"$file" printf '%s\n%s\n%s\n' "$MARK_START" "$lines" "$MARK_END" >>"$file" } # True when the file has nothing left but blank lines. is_empty_file() { ! grep -q '[^[:space:]]' "$1" 2>/dev/null; } state_get() { [ -r "$STATE" ] && sed -n "s/^$1=//p" "$STATE" | head -n1 || true; } state_set() { mkdir -p "$STATE_DIR" touch "$STATE" grep -v "^$1=" "$STATE" >"$STATE.new" || true printf '%s=%s\n' "$1" "$2" >>"$STATE.new" mv "$STATE.new" "$STATE" } home_of() { getent passwd "$1" | cut -d: -f6; } as_user() { runuser -u "$KIOSK_USER" -- "$@"; } model() { if [ -n "${SALAHPOINT_MODEL:-}" ]; then printf '%s' "$SALAHPOINT_MODEL" elif [ -r /proc/device-tree/model ]; then tr -d '\0' "$file.salahpoint" [ "$(wc -l <"$file.salahpoint")" -eq 1 ] || die "cmdline.txt would not stay on one line" cat "$file.salahpoint" >"$file" rm -f "$file.salahpoint" added=$(state_get CMDLINE_ADDED) state_set CMDLINE_ADDED "${added:+$added }$token" } cmdline_remove_ours() { local file=$BOOT/cmdline.txt line token [ -f "$file" ] || return 0 line=" $(head -n1 "$file") " for token in $(state_get CMDLINE_ADDED); do line=${line// $token / }; done line=$(printf '%s' "$line" | sed 's/^ *//; s/ *$//') printf '%s\n' "$line" >"$file" } # --------------------------------------------------------------------------------------------------------------------------- # The kiosk itself. labwc starts it at sign-in, as the screen's user. online() { curl -fsI --max-time 8 "$SITE/" >/dev/null 2>&1; } # A clock chip that has kept its time: later than the floor, not behind what the system last saved, and agreeing with the clock # the system now runs on. A cell that died fails one of these, and then the screen says the time is not confirmed. rtc_holds_time() { [ "$BATTERY_CLOCK" != none ] && [ -r "$RTC_SINCE" ] || return 1 local rtc now saved rtc=$(cat "$RTC_SINCE") now=$(date +%s) saved=$(stat -c %Y "$TIMESYNC_CLOCK" 2>/dev/null || echo 0) [ "$rtc" -ge "$EARLIEST" ] && [ "$rtc" -ge $((saved - 120)) ] && [ $((rtc - now)) -lt 300 ] && [ $((now - rtc)) -lt 300 ] } # The address to open: the boot partition's salahpoint.txt wins, so it can be changed from any computer with the card. start_url() { local chosen=$ADDRESS line url if [ -r "$BOOT/salahpoint.txt" ]; then line=$(grep -v '^[[:space:]]*#' "$BOOT/salahpoint.txt" | tr -d '\r' | grep -m1 '[^[:space:]]' | tr -d '[:space:]' || true) if [ -n "$line" ] && check_address "$line"; then chosen=$line; fi fi url=$(address_url "$chosen") if rtc_holds_time; then case "$url" in *\?*) url="$url&clock=battery" ;; *) url="$url?clock=battery" ;; esac fi printf '%s' "$url" } apply_display() { command -v wlr-randr >/dev/null || return 0 local output transform for output in $(wlr-randr 2>/dev/null | awk '/^[^ ]/ { print $1 }'); do if [ "$RESOLUTION" = 1080p ] && wlr-randr 2>/dev/null | awk -v o="$output" '/^[^ ]/ { here = ($1 == o) } here' | grep -q '1920x1080 px, 60'; then wlr-randr --output "$output" --mode 1920x1080@60Hz || true fi transform=$ROTATE [ "$transform" = 0 ] && transform=normal wlr-randr --output "$output" --transform "$transform" || true done } browser() { local name for name in chromium chromium-browser; do if command -v "$name" >/dev/null; then command -v "$name" return 0 fi done return 1 } run_kiosk() { read_conf local url first state_home bin waited=0 local -a flags # A new card with an old clock cannot open HTTPS until the clock is set. Wait for that a little, never for long. while [ ! -e /run/systemd/timesync/synchronized ] && [ "$waited" -lt 30 ]; do sleep 1 waited=$((waited + 1)) done url=$(start_url) first=$url state_home="${XDG_STATE_HOME:-$HOME/.local/state}/salahpoint" mkdir -p "$state_home" if online; then touch "$state_home/online-once" elif [ ! -e "$state_home/online-once" ]; then # Nothing is stored yet, so the browser would only show its own error. This page waits, then goes on by itself. first="file://$SHARE/waiting.html?to=$(printf '%s' "$url" | sed 's/%/%25/g; s/&/%26/g; s/?/%3F/g; s/=/%3D/g')" fi apply_display bin=$(browser) || { warn "Chromium is missing" exit 1 } flags=(--kiosk --noerrdialogs --disable-infobars --no-first-run --password-store=basic --autoplay-policy=no-user-gesture-required --enable-features=OverlayScrollbar --start-maximized "--user-data-dir=$HOME/.local/share/salahpoint/browser" --disk-cache-dir=/dev/shm/salahpoint-cache) [ "$REDUCED_MOTION" = yes ] && flags+=(--force-prefers-reduced-motion) # shellcheck disable=SC2206 # flags for testing, split on purpose and checked in check_settings [ -n "$EXTRA_FLAGS" ] && flags+=($EXTRA_FLAGS) # If the browser ever stops, it starts again, straight at the screen: the app is stored by then, or it shows that it is not. while true; do "$bin" "${flags[@]}" "$first" || true first=$url sleep 3 done } # Root, at boot, before anything reads the time: a battery clock that kept its time sets the system clock, only ever forward. run_rtc() { [ -r "$RTC_SINCE" ] || exit 0 local rtc now saved rtc=$(cat "$RTC_SINCE") now=$(date +%s) saved=$(stat -c %Y "$TIMESYNC_CLOCK" 2>/dev/null || echo 0) if [ "$rtc" -ge "$EARLIEST" ] && [ "$rtc" -ge "$saved" ] && [ "$rtc" -gt $((now + 2)) ]; then date -s "@$rtc" >/dev/null; fi } # --------------------------------------------------------------------------------------------------------------------------- # Installing. preflight() { [ "$(id -u)" -eq 0 ] || die "run it with sudo: sudo bash $0" [ -f "$0" ] || die "download the file first and run it from there, as the guide shows. It is never meant to be piped into a shell." local codename="" ram arch name [ -r /etc/os-release ] && codename=$(sed -n 's/^VERSION_CODENAME=//p' /etc/os-release) name=$(model) if [ "$ANY_DEVICE" != yes ]; then case "$name" in "Raspberry Pi"*) ;; *) die "this is not a Raspberry Pi (it says \"${name:-nothing}\"). --any-device skips this check." ;; esac case "$codename" in bookworm | trixie) ;; *) die "this needs Raspberry Pi OS Trixie or Bookworm (this is \"${codename:-unknown}\"). Write a new card with Raspberry Pi Imager." ;; esac command -v raspi-config >/dev/null || die "raspi-config is missing, so this is not Raspberry Pi OS" ram=$(awk '/^MemTotal/ { print $2 }' /proc/meminfo) [ "$ram" -ge 700000 ] || [ "$FORCE" = yes ] || die "this Pi has under 1 GB of memory, and Raspberry Pi says kiosk mode needs 1 GB or more. --force runs it anyway." fi if grep -qw 'boot=overlay' /proc/cmdline "$BOOT/cmdline.txt" 2>/dev/null; then die "the overlay file system is on. It loses the stored times and the chosen mosque at every start. Turn it off: sudo raspi-config, Performance Options, Overlay File System, No. Then restart and run this again." fi arch=$(dpkg --print-architecture 2>/dev/null || echo unknown) [ "$arch" = arm64 ] || warn "this is $arch. Raspberry Pi OS (64-bit) is the one to use." case "$name" in *"Pi 3"* | *"Zero 2"*) [ -n "$REDUCED_MOTION_GIVEN" ] || REDUCED_MOTION=yes ;; esac } choose_user() { if [ -z "$KIOSK_USER" ]; then KIOSK_USER=${SUDO_USER:-}; fi if [ -z "$KIOSK_USER" ] || [ "$KIOSK_USER" = root ]; then KIOSK_USER=$(getent passwd 1000 | cut -d: -f1 || true); fi [ -n "$KIOSK_USER" ] && [ "$KIOSK_USER" != root ] || die "no user to run the screen as. Use --user ." id "$KIOSK_USER" >/dev/null 2>&1 || die "there is no user called $KIOSK_USER" } ask() { local prompt=$1 default=$2 answer read -r -p "$prompt [$default] " answer /dev/null || missing+=(chromium) command -v labwc >/dev/null || missing+=(labwc) command -v wlr-randr >/dev/null || missing+=(wlr-randr) command -v curl >/dev/null || missing+=(curl) [ "${#missing[@]}" -eq 0 ] && return 0 say "Installing ${missing[*]}. This can take a few minutes." DEBIAN_FRONTEND=noninteractive apt-get update -q DEBIAN_FRONTEND=noninteractive apt-get install -y -q "${missing[@]}" } install_files() { mkdir -p "$SHARE" if [ "$(readlink -f "$0")" != "$SHARE/install.sh" ]; then cp "$0" "$SHARE/install.sh.new" && mv "$SHARE/install.sh.new" "$SHARE/install.sh"; fi chmod 755 "$SHARE/install.sh" printf '#!/bin/sh\nexec bash %s/install.sh --kiosk "$@"\n' "$SHARE" >/usr/local/bin/salahpoint-kiosk printf '#!/bin/sh\nexec bash %s/install.sh --config "$@"\n' "$SHARE" >/usr/local/bin/salahpoint-config chmod 755 /usr/local/bin/salahpoint-kiosk /usr/local/bin/salahpoint-config write_waiting_page } write_waiting_page() { cat >"$SHARE/waiting.html" <<'EOF' Salah Point

Connect this screen to the internet

It needs the internet once, to store the app and your mosque's prayer times. After that it also runs without. Use a network cable, or the Wi-Fi you set in Raspberry Pi Imager. This page goes on by itself.

Diesen Bildschirm mit dem Internet verbinden

Er braucht einmal Internet, um die App und die Gebetszeiten deiner Moschee zu speichern. Danach läuft er auch ohne. Nutze ein Netzwerkkabel oder das WLAN, das du im Raspberry Pi Imager eingetragen hast. Diese Seite geht von selbst weiter.

EOF } setup_autostart() { local home autostart profile lines home=$(home_of "$KIOSK_USER") autostart=$home/.config/labwc/autostart as_user mkdir -p "$home/.config/labwc" if [ -f "$autostart" ] && [ ! -e "$STATE_DIR/autostart.orig" ] && ! grep -qF "$MARK_START" "$autostart"; then mkdir -p "$STATE_DIR" cp -p "$autostart" "$STATE_DIR/autostart.orig" fi [ -f "$autostart" ] || as_user touch "$autostart" # Blanking on this desktop is a swayidle line in this same file (raspi-config, do_blanking). The screen must never go dark. sed -i '/swayidle/d' "$autostart" put_block "$autostart" "/usr/local/bin/salahpoint-kiosk &" chown "$KIOSK_USER:" "$autostart" if [ -e /etc/init.d/lightdm ]; then state_set SESSION desktop # The desktop session must be labwc. Bookworm calls it W3 and Trixie W2: read it from raspi-config's own menu. if ! grep -q '^user-session=.*labwc' /etc/lightdm/lightdm.conf 2>/dev/null; then lines=$(grep -o '"W[0-9] Labwc"' "$(command -v raspi-config)" | head -n1 | tr -d '"' | cut -d' ' -f1 || true) if [ -n "$lines" ]; then SUDO_USER=$KIOSK_USER raspi-config nonint do_wayland "$lines" || warn "could not switch the desktop to labwc"; fi fi boot_behaviour B4 else # Lite: no desktop to sign in to, so the console signs in by itself and starts labwc on the first screen only. state_set SESSION lite profile=$home/.profile [ -f "$home/.bash_profile" ] && profile=$home/.bash_profile state_set PROFILE "$profile" [ -f "$profile" ] || as_user touch "$profile" # shellcheck disable=SC2016 # written as it stands, for the login shell to read put_block "$profile" 'if [ -z "${WAYLAND_DISPLAY:-}" ] && [ "$(tty)" = /dev/tty1 ]; then exec labwc; fi' chown "$KIOSK_USER:" "$profile" boot_behaviour B2 fi } current_boot_behaviour() { command -v raspi-config >/dev/null || return 0 local cli auto cli=$(raspi-config nonint get_boot_cli 2>/dev/null || echo 0) auto=$(SUDO_USER=$KIOSK_USER raspi-config nonint get_autologin 2>/dev/null || echo 1) case "$cli$auto" in 00) echo B2 ;; 01) echo B1 ;; 10) echo B4 ;; 11) echo B3 ;; esac } boot_behaviour() { [ -n "$(state_get BOOT_BEFORE)" ] || state_set BOOT_BEFORE "$(current_boot_behaviour)" if command -v raspi-config >/dev/null; then SUDO_USER=$KIOSK_USER raspi-config nonint do_boot_behaviour "$1" else warn "raspi-config is missing: sign-in by itself was not set" fi } setup_boot_files() { local block="" if [ ! -d "$BOOT" ]; then warn "$BOOT is missing: the start settings were not written" return 0 fi [ -f "$BOOT/cmdline.txt" ] && [ ! -e "$STATE_DIR/cmdline.txt.orig" ] && mkdir -p "$STATE_DIR" && cp "$BOOT/cmdline.txt" "$STATE_DIR/cmdline.txt.orig" [ -f "$BOOT/config.txt" ] && [ ! -e "$STATE_DIR/config.txt.orig" ] && cp "$BOOT/config.txt" "$STATE_DIR/config.txt.orig" # The console never blanks, and HDMI0 stays on for a TV that is off while the Pi starts (Raspberry Pi's configuration docs). cmdline_add consoleblank=0 [ "$FORCE_HDMI" = yes ] && cmdline_add vc4.force_hotplug=1 case "$BATTERY_CLOCK" in # Charging is off by default, and only the official rechargeable cell may be charged (Raspberry Pi's documentation). pi5) block=$'[all]\ndtparam=rtc_bbat_vchg=3000000' ;; ds3231) block=$'[all]\ndtparam=i2c_arm=on\ndtoverlay=i2c-rtc,ds3231' ;; esac [ -f "$BOOT/config.txt" ] && put_block "$BOOT/config.txt" "$block" sync } setup_quiet_writes() { # Logs in memory, so the card is written to less. They are gone after a restart: the price of a card that lasts. mkdir -p "$(dirname "$JOURNAL_CONF")" printf '[Journal]\nStorage=volatile\nRuntimeMaxUse=32M\n' >"$JOURNAL_CONF" [ -d /run/systemd/system ] && systemctl restart systemd-journald 2>/dev/null || true } # The moment the clock chip appears at boot, it sets the system clock if it kept the time. A rule, not a service, because the # chip's driver may load late, and a service waiting for a chip that is not there would hold up the start. setup_rtc_rule() { if [ "$BATTERY_CLOCK" = none ]; then rm -f "$RTC_RULE" return 0 fi mkdir -p "$(dirname "$RTC_RULE")" printf 'ACTION=="add", SUBSYSTEM=="rtc", KERNEL=="rtc0", RUN+="/bin/bash %s/install.sh --rtc"\n' "$SHARE" >"$RTC_RULE" } clock_advice() { local name name=$(model) say "" case "$BATTERY_CLOCK:$name" in pi5:*) say "Battery clock: the Pi 5's own, with charging turned on for the official cell. Check it after the restart with: timedatectl" ;; ds3231:*) say "Battery clock: DS3231 on I2C. Let the Pi go online once so the module learns the time, then check with: timedatectl" ;; none:*"Pi 5"*) say "No battery clock. Without internet, this Pi shows wrong times after a power cut. Fit the official RTC battery to J5, then run: salahpoint-config battery-clock pi5" ;; none:*) say "No battery clock. Without internet, this Pi shows wrong times after a power cut. Fit a DS3231 module, then run: salahpoint-config battery-clock ds3231" ;; esac say "The screen shows \"Time not confirmed, check the clock\" when it cannot be sure of the time." } do_install() { preflight read_conf_keep_given choose_user questions check_settings install_packages install_files write_conf setup_autostart setup_boot_files setup_quiet_writes setup_rtc_rule state_set VERSION "$VERSION" say "" say "Done. Salah Point $VERSION runs as $KIOSK_USER and opens $(address_url "$ADDRESS")." say "Change it later with: salahpoint-config" clock_advice say "Use the HDMI port next to the power socket (HDMI0)." if [ "$YES" != yes ] && [ -t 0 ]; then [ "$(ask "Restart now? yes/no:" yes)" = yes ] && reboot else say "Restart to start the screen: sudo reboot" fi } # Settings already on this Pi count as the defaults, and anything given on the command line wins. read_conf_keep_given() { local address=$ADDRESS battery=$BATTERY_CLOCK rotate=$ROTATE resolution=$RESOLUTION motion=$REDUCED_MOTION hdmi=$FORCE_HDMI user=$KIOSK_USER read_conf [ -n "$ADDRESS_GIVEN" ] && ADDRESS=$address [ -n "$BATTERY_GIVEN" ] && BATTERY_CLOCK=$battery [ -n "$ROTATE_GIVEN" ] && ROTATE=$rotate [ -n "$RESOLUTION_GIVEN" ] && RESOLUTION=$resolution [ -n "$REDUCED_MOTION_GIVEN" ] && REDUCED_MOTION=$motion [ -n "$FORCE_HDMI_GIVEN" ] && FORCE_HDMI=$hdmi [ -n "$user" ] && KIOSK_USER=$user return 0 } do_uninstall() { [ "$(id -u)" -eq 0 ] || die "run it with sudo: sudo salahpoint-config uninstall" read_conf local home before profile if [ -n "$KIOSK_USER" ] && home=$(home_of "$KIOSK_USER") && [ -n "$home" ]; then pkill -u "$KIOSK_USER" -f salahpoint-kiosk 2>/dev/null || true if [ -f "$home/.config/labwc/autostart" ]; then remove_block "$home/.config/labwc/autostart" if [ -e "$STATE_DIR/autostart.orig" ]; then cp -p "$STATE_DIR/autostart.orig" "$home/.config/labwc/autostart" elif is_empty_file "$home/.config/labwc/autostart"; then rm -f "$home/.config/labwc/autostart" fi fi profile=$(state_get PROFILE) [ -n "$profile" ] && remove_block "$profile" rm -rf "$home/.local/share/salahpoint" "$home/.local/state/salahpoint" fi before=$(state_get BOOT_BEFORE) if [ -n "$before" ] && command -v raspi-config >/dev/null; then SUDO_USER=$KIOSK_USER raspi-config nonint do_boot_behaviour "$before" || true; fi if [ -d "$BOOT" ]; then cmdline_remove_ours [ -f "$BOOT/config.txt" ] && remove_block "$BOOT/config.txt" sync fi rm -f "$JOURNAL_CONF" "$RTC_RULE" rm -f /usr/local/bin/salahpoint-kiosk /usr/local/bin/salahpoint-config rm -rf "$SHARE" "$CONF_DIR" "$STATE_DIR" say "Salah Point is removed. Chromium and labwc stay installed. Restart to finish: sudo reboot" } config_usage() { cat < your mosque's link, its last part, resume or choose salahpoint-config rotate 0|90|180|270 turns the picture, for an upright screen salahpoint-config resolution 1080p|auto salahpoint-config reduced-motion yes|no salahpoint-config battery-clock none|pi5|ds3231 salahpoint-config stop closes the screen until the next start, to reach the desktop salahpoint-config uninstall takes it all away again resume opens the mosque this screen last showed, and asks the first time. choose asks at every start. A file salahpoint.txt on the card's boot partition, with a link in it, wins over this: it can be written from any computer. Changes take effect at the next start: sudo reboot EOF } do_config() { local what=${1:-show} case "$what" in show | "") read_conf say "Opens: $(address_url "$ADDRESS")" [ -r "$BOOT/salahpoint.txt" ] && say " (salahpoint.txt on the boot partition wins over that)" say "Battery clock: $BATTERY_CLOCK" say "Rotation: $ROTATE" say "Resolution: $RESOLUTION" say "Reduced motion: $REDUCED_MOTION" say "User: $KIOSK_USER" say "Version: $(state_get VERSION)" say "" config_usage ;; address | rotate | resolution | reduced-motion | battery-clock) [ "$(id -u)" -eq 0 ] || exec sudo bash "$SHARE/install.sh" --config "$@" [ $# -eq 2 ] || die "salahpoint-config $what needs one value. salahpoint-config shows them." read_conf case "$what" in address) ADDRESS=$2 ;; rotate) ROTATE=$2 ;; resolution) RESOLUTION=$2 ;; reduced-motion) REDUCED_MOTION=$2 ;; battery-clock) BATTERY_CLOCK=$2 ;; esac check_settings write_conf if [ "$what" = battery-clock ]; then setup_boot_files setup_rtc_rule clock_advice fi say "Saved. It takes effect at the next start: sudo reboot" ;; stop) read_conf pkill -u "${KIOSK_USER:-$(id -un)}" -f salahpoint-kiosk 2>/dev/null || true pkill -u "${KIOSK_USER:-$(id -un)}" -x chromium 2>/dev/null || pkill -u "${KIOSK_USER:-$(id -un)}" -f chromium 2>/dev/null || true say "The screen is closed until the next start." ;; uninstall) do_uninstall ;; help | -h | --help) config_usage ;; *) die "unknown: $what. salahpoint-config help lists what it can do." ;; esac } # --------------------------------------------------------------------------------------------------------------------------- YES=no ANY_DEVICE=no FORCE=no ADDRESS_GIVEN="" BATTERY_GIVEN="" ROTATE_GIVEN="" RESOLUTION_GIVEN="" REDUCED_MOTION_GIVEN="" FORCE_HDMI_GIVEN="" main() { case "${1:-}" in --kiosk) run_kiosk ;; --rtc) run_rtc ;; --config) shift do_config "$@" ;; --version) say "$VERSION" ;; *) local uninstall=no while [ $# -gt 0 ]; do case "$1" in --yes) YES=yes ;; --any-device) ANY_DEVICE=yes ;; --force) FORCE=yes ;; --uninstall) uninstall=yes ;; --address | --battery-clock | --rotate | --resolution | --reduced-motion | --force-hdmi | --user) [ $# -ge 2 ] || die "$1 needs a value" case "$1" in --address) ADDRESS=$2 ADDRESS_GIVEN=1 ;; --battery-clock) BATTERY_CLOCK=$2 BATTERY_GIVEN=1 ;; --rotate) ROTATE=$2 ROTATE_GIVEN=1 ;; --resolution) RESOLUTION=$2 RESOLUTION_GIVEN=1 ;; --reduced-motion) REDUCED_MOTION=$2 REDUCED_MOTION_GIVEN=1 ;; --force-hdmi) FORCE_HDMI=$2 FORCE_HDMI_GIVEN=1 ;; --user) KIOSK_USER=$2 ;; esac shift ;; -h | --help) sed -n '2,/^set -euo/p' "$0" | sed '$d; s/^# \{0,1\}//' exit 0 ;; *) die "unknown option: $1. --help lists them." ;; esac shift done if [ "$uninstall" = yes ]; then do_uninstall; else do_install; fi ;; esac } main "$@"