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.
This commit is contained in:
Claude
2026-08-02 22:51:08 +00:00
parent f01781de41
commit 6f6d34512e
2 changed files with 77 additions and 28 deletions
+14 -7
View File
@@ -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.
+63 -21
View File
@@ -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