From b5d37fa6462db65658b885b390c4588bb54a832f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 22:15:15 +0000 Subject: [PATCH 1/5] Install net-tools and ncdu; curl/git already covered apt install already includes curl and git; adds net-tools and ncdu to the same idempotent apt install so they're present on fresh installs without duplicating already-installed packages. --- Readme.md | 4 ++++ ubuntu-based-kiosk-v1.0.3.sh | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 362bcfd..86f802a 100644 --- a/Readme.md +++ b/Readme.md @@ -317,6 +317,9 @@ Both can be used at the same time — they serve different purposes: - **build-essential** - GCC, make, etc. - **Python 3** with evdev for PTT - **jq** - JSON processing +- **curl / git** - Downloading and version control +- **net-tools** - Legacy networking utilities (ifconfig, netstat, etc.) +- **ncdu** - Disk usage analyzer for troubleshooting --- @@ -1119,6 +1122,7 @@ See the LICENSE file in the repository for full terms. **Current Version:** 1.0.3 **Recent Updates (v1.0.3):** +- **Package install:** installer now also installs `net-tools` and `ncdu` (alongside the already-installed `curl` and `git`) - **Touch input fix (keyring):** added `--password-store=basic` to the Electron launch. Under LightDM autologin the GNOME keyring stays locked; when Chromium accessed it, the keyring unlock dialog grabbed all keyboard/touch input — the kiosk rendered fine but ignored every tap and keypress. This flag stops Electron from using the keyring, so the dialog never appears. - **Touch gesture fix (libinput):** any touch screen is now forced to the `libinput` driver via `/etc/X11/xorg.conf.d/99-finger-libinput.conf` (matched by hardware capability, so it works on any brand and never affects keyboards, mice, or the pen/stylus). Some drivers — notably `wacom` — only do single-touch pointer emulation and never pass real multitouch to Chromium, so 1-finger and 2-finger swipe gestures could not fire. libinput delivers proper multitouch. - **Upgrade reliability:** `start.sh` is now refreshed on every upgrade alongside `main.js` and `preload.js` diff --git a/ubuntu-based-kiosk-v1.0.3.sh b/ubuntu-based-kiosk-v1.0.3.sh index 9b0c489..1516ff3 100644 --- a/ubuntu-based-kiosk-v1.0.3.sh +++ b/ubuntu-based-kiosk-v1.0.3.sh @@ -3953,7 +3953,8 @@ first_time_install() { libegl-mesa0 libegl1-mesa-dev libgles2-mesa-dev \ pipewire pipewire-pulse pipewire-alsa wireplumber pipewire-audio-client-libraries alsa-utils libnotify-bin \ gstreamer1.0-pipewire libspa-0.2-bluetooth \ - systemd-timesyncd acpid xbindkeys xdotool python3-evdev unzip + systemd-timesyncd acpid xbindkeys xdotool python3-evdev unzip \ + net-tools ncdu if lspci | grep -i "VGA.*Intel" >/dev/null 2>&1; then sudo apt install -y intel-gpu-tools xserver-xorg-video-intel \ From 68a7345f5beb37b4d1c02c1ed57062b8337d538b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 22:34:19 +0000 Subject: [PATCH 2/5] Mirror external HDMI/monitor output onto primary display automatically Openbox autostart now detects any connected output beyond the primary and mirrors the kiosk content onto it via xrandr, instead of leaving detected-but-unused external displays dark. Also documents the behavior, its limitation (applies at session start, not live hotplug), and adds an HDMI troubleshooting section to the README. --- Readme.md | 19 ++++++++++++++++++- ubuntu-based-kiosk-v1.0.3.sh | 11 +++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 86f802a..e1d178d 100644 --- a/Readme.md +++ b/Readme.md @@ -378,6 +378,21 @@ id kiosk sudo -u kiosk DISPLAY=:0 xdpyinfo ``` +**External monitor/TV (HDMI) shows nothing:** +```bash +# List outputs and check if the external display is detected +sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr + +# Look for your output (e.g. HDMI1/HDMI2/HDMI-1) as "connected" with a mode list. +# If it's "connected" but not active, autostart should mirror it automatically on +# next login/boot. To apply immediately without rebooting: +sudo systemctl restart lightdm + +# To test mirroring manually right now (replace names with what xrandr showed): +sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output HDMI2 --auto --same-as eDP1 +``` +If the output shows `disconnected`, it's a cabling/port/EDID issue, not software — try a different cable/port or a monitor known to work. + **Audio not working:** ```bash # Check PipeWire (use menu: Advanced → Audio Diagnostics) @@ -1122,6 +1137,7 @@ See the LICENSE file in the repository for full terms. **Current Version:** 1.0.3 **Recent Updates (v1.0.3):** +- **HDMI/external display mirroring:** autostart now detects any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) and mirrors the kiosk output onto it automatically — previously the external output was left inactive even when detected by X - **Package install:** installer now also installs `net-tools` and `ncdu` (alongside the already-installed `curl` and `git`) - **Touch input fix (keyring):** added `--password-store=basic` to the Electron launch. Under LightDM autologin the GNOME keyring stays locked; when Chromium accessed it, the keyring unlock dialog grabbed all keyboard/touch input — the kiosk rendered fine but ignored every tap and keypress. This flag stops Electron from using the keyring, so the dialog never appears. - **Touch gesture fix (libinput):** any touch screen is now forced to the `libinput` driver via `/etc/X11/xorg.conf.d/99-finger-libinput.conf` (matched by hardware capability, so it works on any brand and never affects keyboards, mice, or the pen/stylus). Some drivers — notably `wacom` — only do single-touch pointer emulation and never pass real multitouch to Chromium, so 1-finger and 2-finger swipe gestures could not fire. libinput delivers proper multitouch. @@ -1164,7 +1180,8 @@ See the LICENSE file in the repository for full terms. **Known Limitations:** - Raspberry Pi support untested in production - No web-based configuration (CLI menu only) -- Single display only (extended desktop not supported) +- Extended desktop not supported — additional connected displays (e.g. HDMI-out to a monitor/TV) are automatically **mirrored**, not extended +- Display mirroring is applied at kiosk session start only — plugging in a display after boot requires restarting the display (`sudo systemctl restart lightdm`) or logging back in for it to pick up the new output --- diff --git a/ubuntu-based-kiosk-v1.0.3.sh b/ubuntu-based-kiosk-v1.0.3.sh index 1516ff3..78c0f33 100644 --- a/ubuntu-based-kiosk-v1.0.3.sh +++ b/ubuntu-based-kiosk-v1.0.3.sh @@ -7392,6 +7392,17 @@ TOUCHCFG sudo -u "$KIOSK_USER" tee "$KIOSK_HOME/.config/openbox/autostart" > /dev/null <<'AUTOSTART' #!/bin/bash +# Mirror any connected external display (e.g. HDMI-out to a monitor/TV) onto +# the primary display so the kiosk content shows on both. +PRIMARY_OUTPUT=$(xrandr --query | awk '/ primary/{print $1; exit}') +if [ -n "$PRIMARY_OUTPUT" ]; then + for OUT in $(xrandr --query | awk '/ connected/{print $1}'); do + if [ "$OUT" != "$PRIMARY_OUTPUT" ]; then + xrandr --output "$OUT" --auto --same-as "$PRIMARY_OUTPUT" 2>/dev/null + fi + done +fi + # AGGRESSIVE DPMS disable - multiple methods xset s off xset s noblank From f01781de41ffa9b3c4225bfdcc7a262b224c2615 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 22:37:39 +0000 Subject: [PATCH 3/5] Add live HDMI/display hotplug handling via udev + systemd Adds a udev rule (DRM 'change' events) that triggers a new kiosk-hotplug.service, which re-runs the same xrandr mirroring logic as the Openbox autostart script. External displays plugged in after boot now get mirrored without requiring a login or lightdm restart. Named kiosk-hotplug (not kiosk-display-*) to avoid collision with the existing kiosk-display-* wildcard cleanup in remove_all_schedules(). Also removes the udev rule during Complete Uninstall and documents the behavior in the README. --- Readme.md | 18 ++++++++++----- ubuntu-based-kiosk-v1.0.3.sh | 44 +++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index e1d178d..b8848da 100644 --- a/Readme.md +++ b/Readme.md @@ -379,16 +379,23 @@ sudo -u kiosk DISPLAY=:0 xdpyinfo ``` **External monitor/TV (HDMI) shows nothing:** + +Any connected display beyond the primary is mirrored automatically — both at kiosk login/boot (via Openbox autostart) and live when plugged/unplugged afterward (via a udev rule that triggers `kiosk-hotplug.service`). + ```bash # List outputs and check if the external display is detected sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr # Look for your output (e.g. HDMI1/HDMI2/HDMI-1) as "connected" with a mode list. -# If it's "connected" but not active, autostart should mirror it automatically on -# next login/boot. To apply immediately without rebooting: -sudo systemctl restart lightdm -# To test mirroring manually right now (replace names with what xrandr showed): +# Check whether the hotplug handler fired and what it did +sudo journalctl -u kiosk-hotplug.service -n 20 +journalctl | grep "KIOSK: hotplug" | tail -10 + +# Manually re-trigger it +sudo systemctl start kiosk-hotplug.service + +# To test mirroring by hand (replace names with what xrandr showed): sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output HDMI2 --auto --same-as eDP1 ``` If the output shows `disconnected`, it's a cabling/port/EDID issue, not software — try a different cable/port or a monitor known to work. @@ -1137,7 +1144,7 @@ See the LICENSE file in the repository for full terms. **Current Version:** 1.0.3 **Recent Updates (v1.0.3):** -- **HDMI/external display mirroring:** autostart now detects any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) and mirrors the kiosk output onto it automatically — previously the external output was left inactive even when detected by X +- **HDMI/external display mirroring:** any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) is now mirrored automatically, both at kiosk login/boot and live on plug/unplug via a new udev-triggered `kiosk-hotplug.service` — previously the external output was left inactive even when detected by X - **Package install:** installer now also installs `net-tools` and `ncdu` (alongside the already-installed `curl` and `git`) - **Touch input fix (keyring):** added `--password-store=basic` to the Electron launch. Under LightDM autologin the GNOME keyring stays locked; when Chromium accessed it, the keyring unlock dialog grabbed all keyboard/touch input — the kiosk rendered fine but ignored every tap and keypress. This flag stops Electron from using the keyring, so the dialog never appears. - **Touch gesture fix (libinput):** any touch screen is now forced to the `libinput` driver via `/etc/X11/xorg.conf.d/99-finger-libinput.conf` (matched by hardware capability, so it works on any brand and never affects keyboards, mice, or the pen/stylus). Some drivers — notably `wacom` — only do single-touch pointer emulation and never pass real multitouch to Chromium, so 1-finger and 2-finger swipe gestures could not fire. libinput delivers proper multitouch. @@ -1181,7 +1188,6 @@ See the LICENSE file in the repository for full terms. - Raspberry Pi support untested in production - No web-based configuration (CLI menu only) - Extended desktop not supported — additional connected displays (e.g. HDMI-out to a monitor/TV) are automatically **mirrored**, not extended -- Display mirroring is applied at kiosk session start only — plugging in a display after boot requires restarting the display (`sudo systemctl restart lightdm`) or logging back in for it to pick up the new output --- diff --git a/ubuntu-based-kiosk-v1.0.3.sh b/ubuntu-based-kiosk-v1.0.3.sh index 78c0f33..624128b 100644 --- a/ubuntu-based-kiosk-v1.0.3.sh +++ b/ubuntu-based-kiosk-v1.0.3.sh @@ -3844,6 +3844,8 @@ complete_uninstall() { echo "[5/13] Removing scripts..." sudo rm -f /usr/local/bin/kiosk-* sudo rm -f /usr/local/bin/rtc-wake.sh + sudo rm -f /etc/udev/rules.d/99-kiosk-hotplug.rules + sudo udevadm control --reload-rules 2>/dev/null || true # Remove CUPS echo "[6/13] Removing CUPS..." @@ -7539,7 +7541,47 @@ sleep 2 /home/kiosk/kiosk-app/start.sh & AUTOSTART sudo chmod 750 "$KIOSK_HOME/.config/openbox/autostart" - + + # HDMI/display hotplug: re-mirror any newly connected external display + # without waiting for the next login. Triggered by udev on DRM "change" + # events (monitor plugged/unplugged), which starts a oneshot systemd + # service that reapplies the same xrandr mirroring as the autostart script. + sudo tee /usr/local/bin/kiosk-hotplug.sh > /dev/null <<'EOF' +#!/bin/bash +# Give X a moment to finish enumerating the output after the hotplug event +sleep 2 + +PRIMARY_OUTPUT=$(sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --query 2>/dev/null | awk '/ primary/{print $1; exit}') +if [ -n "$PRIMARY_OUTPUT" ]; then + for OUT in $(sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --query 2>/dev/null | awk '/ connected/{print $1}'); do + if [ "$OUT" != "$PRIMARY_OUTPUT" ]; then + sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output "$OUT" --auto --same-as "$PRIMARY_OUTPUT" 2>/dev/null \ + && logger "KIOSK: hotplug mirrored $OUT onto $PRIMARY_OUTPUT" \ + || logger "KIOSK: hotplug mirror of $OUT failed" + fi + done +fi +EOF + sudo chmod +x /usr/local/bin/kiosk-hotplug.sh + + sudo tee /etc/systemd/system/kiosk-hotplug.service > /dev/null <<'EOF' +[Unit] +Description=Kiosk Display Hotplug Handler + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/kiosk-hotplug.sh +StandardOutput=journal +StandardError=journal +EOF + + sudo tee /etc/udev/rules.d/99-kiosk-hotplug.rules > /dev/null <<'EOF' +SUBSYSTEM=="drm", ACTION=="change", TAG+="systemd", ENV{SYSTEMD_WANTS}="kiosk-hotplug.service" +EOF + + sudo systemctl daemon-reload + sudo udevadm control --reload-rules + if lspci | grep -i "VGA.*Intel" >/dev/null 2>&1; then sudo mkdir -p /etc/X11/xorg.conf.d/ sudo tee /etc/X11/xorg.conf.d/20-intel.conf > /dev/null <<'EOF' From 6f6d34512eff873b6108bf793201b0356a6293be Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 22:51:08 +0000 Subject: [PATCH 4/5] Mirror external display at the kiosk's exact resolution, not its own native mode Previously mirroring used 'xrandr --auto', which picks the external output's own native resolution (e.g. a TV's 1920x1080) rather than matching the kiosk/laptop panel's resolution. Extracts the shared mirroring logic (previously duplicated between autostart and the hotplug handler) into /usr/local/bin/kiosk-mirror-display.sh, which now checks whether the external output natively lists the primary's resolution and uses it directly, or generates a matching mode with cvt and forces it via --newmode/--addmode when it doesn't. Documents the behavior and a manual fallback for displays that reject non-native CVT timings in the README. --- Readme.md | 21 ++++++--- ubuntu-based-kiosk-v1.0.3.sh | 84 +++++++++++++++++++++++++++--------- 2 files changed, 77 insertions(+), 28 deletions(-) diff --git a/Readme.md b/Readme.md index b8848da..ddd460f 100644 --- a/Readme.md +++ b/Readme.md @@ -378,9 +378,9 @@ id kiosk sudo -u kiosk DISPLAY=:0 xdpyinfo ``` -**External monitor/TV (HDMI) shows nothing:** +**External monitor/TV (HDMI) shows nothing, or shows a cropped/scaled picture:** -Any connected display beyond the primary is mirrored automatically — both at kiosk login/boot (via Openbox autostart) and live when plugged/unplugged afterward (via a udev rule that triggers `kiosk-hotplug.service`). +Any connected display beyond the primary is mirrored automatically at the **primary's exact resolution** (not the external display's own native resolution) — both at kiosk login/boot (via Openbox autostart) and live when plugged/unplugged afterward (via a udev rule that triggers `kiosk-hotplug.service`). Both paths call the same `/usr/local/bin/kiosk-mirror-display.sh`. If the external display doesn't natively list the primary's resolution (e.g. a 1366x768 laptop panel mirrored to a 1920x1080-native TV), a matching mode is generated on the fly with `cvt` and forced onto the output — most monitors/TVs accept a close, non-native CVT timing without issue, but a few strict ones may reject it (see below). ```bash # List outputs and check if the external display is detected @@ -388,18 +388,25 @@ sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr # Look for your output (e.g. HDMI1/HDMI2/HDMI-1) as "connected" with a mode list. -# Check whether the hotplug handler fired and what it did +# Check what the mirroring logic actually did (native mode vs. forced CVT mode, or failures) +journalctl | grep "KIOSK:" | tail -20 + +# Check whether the hotplug handler fired sudo journalctl -u kiosk-hotplug.service -n 20 -journalctl | grep "KIOSK: hotplug" | tail -10 # Manually re-trigger it sudo systemctl start kiosk-hotplug.service -# To test mirroring by hand (replace names with what xrandr showed): -sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output HDMI2 --auto --same-as eDP1 +# Run the mirroring logic by hand for more verbose output +sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority bash -x /usr/local/bin/kiosk-mirror-display.sh ``` If the output shows `disconnected`, it's a cabling/port/EDID issue, not software — try a different cable/port or a monitor known to work. +If a forced CVT mode is rejected by the display (blank screen only after mirroring runs, works fine before), that display's EDID doesn't accept out-of-spec timings — you'll need to manually pick one of its natively listed modes instead: +```bash +sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output HDMI2 --mode 1360x768 --same-as eDP1 +``` + **Audio not working:** ```bash # Check PipeWire (use menu: Advanced → Audio Diagnostics) @@ -1144,7 +1151,7 @@ See the LICENSE file in the repository for full terms. **Current Version:** 1.0.3 **Recent Updates (v1.0.3):** -- **HDMI/external display mirroring:** any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) is now mirrored automatically, both at kiosk login/boot and live on plug/unplug via a new udev-triggered `kiosk-hotplug.service` — previously the external output was left inactive even when detected by X +- **HDMI/external display mirroring:** any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) is now mirrored automatically at the primary's exact resolution — generating a custom `cvt` mode if the external display doesn't natively list it — both at kiosk login/boot and live on plug/unplug via a new udev-triggered `kiosk-hotplug.service`. Previously the external output was left inactive even when detected by X, and would otherwise mirror at its own native resolution instead of matching the kiosk panel - **Package install:** installer now also installs `net-tools` and `ncdu` (alongside the already-installed `curl` and `git`) - **Touch input fix (keyring):** added `--password-store=basic` to the Electron launch. Under LightDM autologin the GNOME keyring stays locked; when Chromium accessed it, the keyring unlock dialog grabbed all keyboard/touch input — the kiosk rendered fine but ignored every tap and keypress. This flag stops Electron from using the keyring, so the dialog never appears. - **Touch gesture fix (libinput):** any touch screen is now forced to the `libinput` driver via `/etc/X11/xorg.conf.d/99-finger-libinput.conf` (matched by hardware capability, so it works on any brand and never affects keyboards, mice, or the pen/stylus). Some drivers — notably `wacom` — only do single-touch pointer emulation and never pass real multitouch to Chromium, so 1-finger and 2-finger swipe gestures could not fire. libinput delivers proper multitouch. diff --git a/ubuntu-based-kiosk-v1.0.3.sh b/ubuntu-based-kiosk-v1.0.3.sh index 624128b..cf50591 100644 --- a/ubuntu-based-kiosk-v1.0.3.sh +++ b/ubuntu-based-kiosk-v1.0.3.sh @@ -7391,19 +7391,70 @@ TOUCHCFG # Create empty xbindkeysrc to prevent errors sudo -u "$KIOSK_USER" touch "$KIOSK_HOME/.xbindkeysrc" + # Shared logic for mirroring any connected display beyond the primary at + # the primary's exact resolution (forcing a custom CVT mode if the + # external output doesn't natively list it). Called from both the + # Openbox autostart below (already running as kiosk user in the X + # session) and kiosk-hotplug.sh (root, via systemd/udev — wraps the call + # with sudo -u kiosk DISPLAY=:0 XAUTHORITY=...). + sudo tee /usr/local/bin/kiosk-mirror-display.sh > /dev/null <<'MIRRORSCRIPT' +#!/bin/bash +# Assumes it's run with DISPLAY/XAUTHORITY already set for the kiosk user's +# X session (either inherited, as from Openbox autostart, or exported by the +# caller). Mirrors every connected non-primary output at the primary's exact +# current resolution so kiosk content isn't cropped/letterboxed/blank on a +# TV/monitor with a different native resolution than the kiosk panel. + +QUERY=$(xrandr --query 2>/dev/null) +[ -z "$QUERY" ] && exit 0 + +PRIMARY_OUTPUT=$(echo "$QUERY" | awk '/ primary/{print $1; exit}') +[ -z "$PRIMARY_OUTPUT" ] && exit 0 + +PRIMARY_RES=$(echo "$QUERY" | awk -v p="$PRIMARY_OUTPUT" '$1==p{for(i=1;i<=NF;i++) if ($i ~ /^[0-9]+x[0-9]+\+/){split($i,a,"+"); print a[1]; exit}}') +[ -z "$PRIMARY_RES" ] && exit 0 + +for OUT in $(echo "$QUERY" | awk '/ connected/{print $1}'); do + [ "$OUT" = "$PRIMARY_OUTPUT" ] && continue + + HAS_NATIVE=$(echo "$QUERY" | awk -v out="$OUT" -v res="$PRIMARY_RES" ' + $0 ~ "^"out" " {infound=1; next} + /^[^ \t]/ {infound=0} + infound && $1==res {print "yes"; exit} + ') + + if [ "$HAS_NATIVE" = "yes" ]; then + xrandr --output "$OUT" --mode "$PRIMARY_RES" --same-as "$PRIMARY_OUTPUT" 2>/dev/null \ + && logger "KIOSK: mirrored $OUT at native $PRIMARY_RES" \ + || logger "KIOSK: mirror of $OUT at $PRIMARY_RES failed" + continue + fi + + # $OUT doesn't natively list the primary's resolution - force a matching mode + CVT_LINE=$(cvt "${PRIMARY_RES%x*}" "${PRIMARY_RES#*x}" 2>/dev/null | grep Modeline) + MODENAME=$(echo "$CVT_LINE" | sed -n 's/^Modeline "\([^"]*\)".*/\1/p') + TIMINGS=$(echo "$CVT_LINE" | sed -n 's/^Modeline "[^"]*" *//p') + + if [ -z "$MODENAME" ] || [ -z "$TIMINGS" ]; then + logger "KIOSK: could not generate a $PRIMARY_RES mode for $OUT (cvt failed or missing)" + continue + fi + + xrandr --newmode "$MODENAME" $TIMINGS 2>/dev/null + xrandr --addmode "$OUT" "$MODENAME" 2>/dev/null + xrandr --output "$OUT" --mode "$MODENAME" --same-as "$PRIMARY_OUTPUT" 2>/dev/null \ + && logger "KIOSK: mirrored $OUT at forced $PRIMARY_RES ($MODENAME)" \ + || logger "KIOSK: mirror of $OUT at forced $PRIMARY_RES failed" +done +MIRRORSCRIPT + sudo chmod +x /usr/local/bin/kiosk-mirror-display.sh + sudo -u "$KIOSK_USER" tee "$KIOSK_HOME/.config/openbox/autostart" > /dev/null <<'AUTOSTART' #!/bin/bash # Mirror any connected external display (e.g. HDMI-out to a monitor/TV) onto -# the primary display so the kiosk content shows on both. -PRIMARY_OUTPUT=$(xrandr --query | awk '/ primary/{print $1; exit}') -if [ -n "$PRIMARY_OUTPUT" ]; then - for OUT in $(xrandr --query | awk '/ connected/{print $1}'); do - if [ "$OUT" != "$PRIMARY_OUTPUT" ]; then - xrandr --output "$OUT" --auto --same-as "$PRIMARY_OUTPUT" 2>/dev/null - fi - done -fi +# the primary display, forcing it to the primary's exact resolution. +/usr/local/bin/kiosk-mirror-display.sh # AGGRESSIVE DPMS disable - multiple methods xset s off @@ -7545,22 +7596,13 @@ AUTOSTART # HDMI/display hotplug: re-mirror any newly connected external display # without waiting for the next login. Triggered by udev on DRM "change" # events (monitor plugged/unplugged), which starts a oneshot systemd - # service that reapplies the same xrandr mirroring as the autostart script. + # service that re-runs the same kiosk-mirror-display.sh logic used at + # Openbox autostart, as root, so it wraps the call with sudo -u kiosk. sudo tee /usr/local/bin/kiosk-hotplug.sh > /dev/null <<'EOF' #!/bin/bash # Give X a moment to finish enumerating the output after the hotplug event sleep 2 - -PRIMARY_OUTPUT=$(sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --query 2>/dev/null | awk '/ primary/{print $1; exit}') -if [ -n "$PRIMARY_OUTPUT" ]; then - for OUT in $(sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --query 2>/dev/null | awk '/ connected/{print $1}'); do - if [ "$OUT" != "$PRIMARY_OUTPUT" ]; then - sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output "$OUT" --auto --same-as "$PRIMARY_OUTPUT" 2>/dev/null \ - && logger "KIOSK: hotplug mirrored $OUT onto $PRIMARY_OUTPUT" \ - || logger "KIOSK: hotplug mirror of $OUT failed" - fi - done -fi +sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority /usr/local/bin/kiosk-mirror-display.sh EOF sudo chmod +x /usr/local/bin/kiosk-hotplug.sh From 10c4c22f4477faf89cbfc470d4c87a0a27c58a1d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 22:54:23 +0000 Subject: [PATCH 5/5] Route audio to HDMI automatically when an external display is connected Previously nothing switched PipeWire's default sink, so audio always stayed on the laptop/built-in speakers regardless of HDMI mirroring. Adds /usr/local/bin/kiosk-audio-route.sh, which finds a sink whose name contains 'hdmi' and makes it the default (moving any already-playing streams onto it) whenever an external display is connected, falling back to the non-HDMI sink when it isn't. Called from autostart once PipeWire is confirmed ready, and from kiosk-hotplug.sh alongside the existing display mirroring on every plug/unplug event. Documents the behavior and manual fallback in the README. --- Readme.md | 23 ++++++++++++++++ ubuntu-based-kiosk-v1.0.3.sh | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/Readme.md b/Readme.md index ddd460f..234b8df 100644 --- a/Readme.md +++ b/Readme.md @@ -407,6 +407,28 @@ If a forced CVT mode is rejected by the display (blank screen only after mirrori sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output HDMI2 --mode 1360x768 --same-as eDP1 ``` +**No sound over HDMI (audio only from laptop/built-in speakers):** + +Whenever an external display is connected/mirrored, `kiosk-audio-route.sh` switches PipeWire's default sink to whichever sink's name contains `hdmi`, and moves any already-playing audio stream onto it. It's called at kiosk login (after PipeWire is confirmed ready) and by `kiosk-hotplug.service` on every plug/unplug — see `/usr/local/bin/kiosk-mirror-display.sh` above for the display side of the same hotplug event. + +```bash +# List sinks and confirm an HDMI one exists (name will contain "hdmi") +sudo -u kiosk pactl list sinks short + +# Check what the routing logic actually did +journalctl | grep "KIOSK: audio routed\|KIOSK: failed to route" | tail -10 + +# Check current default sink +sudo -u kiosk pactl get-default-sink + +# Manually re-trigger routing +sudo systemctl start kiosk-hotplug.service + +# Force it by hand if needed (replace with your sink name from the list above) +sudo -u kiosk pactl set-default-sink alsa_output.pci-0000_00_1f.3.hdmi-stereo +``` +If no sink name contains `hdmi`, the audio codec on that HDMI port either isn't exposed by ALSA/PipeWire on this hardware, or the monitor/TV doesn't report HDMI audio support in its EDID (common on monitors that only do video) — in that case there's no PipeWire-side fix, audio has to come from the laptop speakers or a separate cable. + **Audio not working:** ```bash # Check PipeWire (use menu: Advanced → Audio Diagnostics) @@ -1152,6 +1174,7 @@ See the LICENSE file in the repository for full terms. **Recent Updates (v1.0.3):** - **HDMI/external display mirroring:** any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) is now mirrored automatically at the primary's exact resolution — generating a custom `cvt` mode if the external display doesn't natively list it — both at kiosk login/boot and live on plug/unplug via a new udev-triggered `kiosk-hotplug.service`. Previously the external output was left inactive even when detected by X, and would otherwise mirror at its own native resolution instead of matching the kiosk panel +- **HDMI audio routing:** audio now follows the same hotplug event — the default PipeWire sink automatically switches to the HDMI audio output when an external display is connected/mirrored, and back to the built-in sink when it's disconnected (`kiosk-audio-route.sh`) - **Package install:** installer now also installs `net-tools` and `ncdu` (alongside the already-installed `curl` and `git`) - **Touch input fix (keyring):** added `--password-store=basic` to the Electron launch. Under LightDM autologin the GNOME keyring stays locked; when Chromium accessed it, the keyring unlock dialog grabbed all keyboard/touch input — the kiosk rendered fine but ignored every tap and keypress. This flag stops Electron from using the keyring, so the dialog never appears. - **Touch gesture fix (libinput):** any touch screen is now forced to the `libinput` driver via `/etc/X11/xorg.conf.d/99-finger-libinput.conf` (matched by hardware capability, so it works on any brand and never affects keyboards, mice, or the pen/stylus). Some drivers — notably `wacom` — only do single-touch pointer emulation and never pass real multitouch to Chromium, so 1-finger and 2-finger swipe gestures could not fire. libinput delivers proper multitouch. diff --git a/ubuntu-based-kiosk-v1.0.3.sh b/ubuntu-based-kiosk-v1.0.3.sh index cf50591..0b42cbb 100644 --- a/ubuntu-based-kiosk-v1.0.3.sh +++ b/ubuntu-based-kiosk-v1.0.3.sh @@ -7449,6 +7449,53 @@ done MIRRORSCRIPT sudo chmod +x /usr/local/bin/kiosk-mirror-display.sh + # Shared logic for routing audio to an HDMI audio sink whenever an + # external (non-primary) display is connected/mirrored, and back to the + # built-in sink when it isn't. Requires PipeWire/pipewire-pulse to + # already be running (pactl needs a live socket), so unlike + # kiosk-mirror-display.sh this is called later in autostart, after the + # "wait for PipeWire/ALSA" steps below — and from kiosk-hotplug.sh, where + # the system is already fully booted by the time it fires. + sudo tee /usr/local/bin/kiosk-audio-route.sh > /dev/null <<'AUDIOSCRIPT' +#!/bin/bash +# Assumes DISPLAY/XAUTHORITY are set (for xrandr) and pactl already has a +# working PipeWire/pulse socket for the invoking context. + +QUERY=$(xrandr --query 2>/dev/null) +[ -z "$QUERY" ] && exit 0 + +PRIMARY_OUTPUT=$(echo "$QUERY" | awk '/ primary/{print $1; exit}') +[ -z "$PRIMARY_OUTPUT" ] && exit 0 + +EXTERNAL_CONNECTED=$(echo "$QUERY" | awk -v p="$PRIMARY_OUTPUT" '/ connected/ && $1!=p{f=1} END{print (f==1)?"yes":"no"}') + +HDMI_SINK=$(pactl list sinks short 2>/dev/null | awk 'tolower($2) ~ /hdmi/{print $2; exit}') +NON_HDMI_SINK=$(pactl list sinks short 2>/dev/null | awk 'tolower($2) !~ /hdmi/{print $2; exit}') + +route_to() { + local sink="$1" label="$2" + if [ -z "$sink" ]; then + logger "KIOSK: no $label audio sink found, leaving routing unchanged" + return + fi + pactl set-default-sink "$sink" 2>/dev/null \ + && logger "KIOSK: audio routed to $label sink ($sink)" \ + || logger "KIOSK: failed to route audio to $label sink ($sink)" + pactl list sink-inputs short 2>/dev/null | awk '{print $1}' | while read -r sid; do + pactl move-sink-input "$sid" "$sink" 2>/dev/null + done + pactl set-sink-volume "$sink" 100% 2>/dev/null + pactl set-sink-mute "$sink" 0 2>/dev/null +} + +if [ "$EXTERNAL_CONNECTED" = "yes" ]; then + route_to "$HDMI_SINK" "HDMI" +else + route_to "$NON_HDMI_SINK" "built-in" +fi +AUDIOSCRIPT + sudo chmod +x /usr/local/bin/kiosk-audio-route.sh + sudo -u "$KIOSK_USER" tee "$KIOSK_HOME/.config/openbox/autostart" > /dev/null <<'AUTOSTART' #!/bin/bash @@ -7525,6 +7572,9 @@ for i in {1..10}; do sleep 1 done +# Route audio to HDMI if an external display is connected/mirrored, else built-in +/usr/local/bin/kiosk-audio-route.sh + # Set audio levels (speakers 100%, mic 100%, mic unmuted) pactl set-sink-volume @DEFAULT_SINK@ 100% pactl set-source-volume @DEFAULT_SOURCE@ 100% @@ -7603,6 +7653,9 @@ AUTOSTART # Give X a moment to finish enumerating the output after the hotplug event sleep 2 sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority /usr/local/bin/kiosk-mirror-display.sh + +kiosk_uid=$(id -u kiosk) +sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority XDG_RUNTIME_DIR="/run/user/${kiosk_uid}" /usr/local/bin/kiosk-audio-route.sh EOF sudo chmod +x /usr/local/bin/kiosk-hotplug.sh