#!/bin/sh
# Bring up the kitchen screen: one compositor, one fullscreen app, nothing else.
#
# WHY SWAY AND NOT CAGE. cage 0.2.1 — the version Ubuntu ships — could not do
# two things this appliance needs, and failed at both SILENTLY:
#   * it never applied the output transform, so a panel mounted portrait drew
#     landscape and there was nothing in any log to say why;
#   * it never bound the digitiser to the output, so touches landed nowhere.
# Both are one line of sway config each. The rotation is what forced the issue —
# these screens hang vertically — and the touch mapping came free with it.
#
# DELIBERATELY NOT `set -e`.
#
# This script's one job is to get the compositor up. Under `set -e` any
# incidental command — a logger call, a console blank — takes the whole kiosk
# down with it, and the unit restart-loops with a bare exit code and nothing
# useful in the journal. That is exactly what happened: status=127 on every
# start and the app never launched. What genuinely prevents launch is checked
# explicitly below and fails loudly; everything else is allowed to not work.

# The runtime dir is created by systemd (RuntimeDirectory= in the unit), fresh
# on every boot and already owned by this user. This script must NOT try to
# make it: as an unprivileged user inside root-owned /run/user that fails, and
# under `set -e` it killed the service on every start.
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/maestro-kds}"
if [ ! -d "$XDG_RUNTIME_DIR" ]; then
    logger -t maestro-kds "runtime dir $XDG_RUNTIME_DIR missing" 2>/dev/null || true
    echo "runtime dir $XDG_RUNTIME_DIR does not exist" >&2
    exit 1
fi

# WAIT FOR A GPU — ANY GPU.
#
# The unit no longer depends on a numbered DRM node (see maestro-kds.service),
# so the wait is here, where it can accept card0, card1 or whatever this board
# enumerates. Bounded at 30s: if there is genuinely no GPU the service should
# fail and say so in the journal, not hang forever pretending to start.
i=0
while [ $i -lt 60 ]; do
    for c in /dev/dri/card*; do
        [ -e "$c" ] && break 2
    done
    i=$((i + 1))
    sleep 0.5
done
if ! ls /dev/dri/card* >/dev/null 2>&1; then
    logger -t maestro-kds "no DRM device after 30s" 2>/dev/null || true
    echo "no /dev/dri/card* — is this board's GPU driver loaded?" >&2
    exit 1
fi
logger -t maestro-kds "using $(ls /dev/dri/card* 2>/dev/null | head -1)" 2>/dev/null || true

# Blank the console before the compositor takes the tty, so the last kernel
# line never flashes between plymouth and the app.
if [ -w /dev/tty1 ]; then
    printf '\033[?25l\033[2J\033[H' > /dev/tty1 2>/dev/null || true
fi

# NO CALIBRATION MATRIX HERE ANY MORE.
#
# The cage-era script wrote a LIBINPUT_CALIBRATION_MATRIX udev rule per
# orientation and re-triggered the device, because cage rotated the picture
# without rotating the touch surface. sway's `map_to_output` does both together,
# and the two mechanisms COMPOUND — a matrix on top of a mapped output rotates
# touch twice and puts every tap somewhere new. Verified on the appliance: with
# the mapping alone, an injected tap aimed at a button 823px across landed at
# 822.66. Leave the matrix out.

APP=/opt/maestro-kds/maestro-kds
if [ ! -x "$APP" ]; then
    echo "the KDS app is not at $APP — is the maestro-kds package installed?" >&2
    exit 1
fi

# ── PREFERRED: gnome-kiosk (mutter) — the compositor under which touch is
# actually touch. GTK3's Wayland backend never converts wl_touch into GTK
# events under wlroots (the entire maestro-touch-pointer shim exists to fake
# around that with an absolute mouse: cursor under the finger, no fling, a
# scroll minefield). Under mutter the same GTK build converts touch natively,
# so Flutter gets PointerDeviceKind.touch — real slop, real kinetic scroll,
# no shim, and --no-cursor removes the pointer at the source. Proven layer by
# layer on the bench unit, 2026-08-19; the shim's unit refuses to start when
# gnome-kiosk is installed (ConditionPathExists in its .service) so the two
# input paths can never fight over the panel.
#
# mutter needs a session D-Bus (dbus-run-session) and takes the seat through
# logind (the unit's PAMName=login). It reads the panel's own orientation, so
# no transform config is needed — sway.conf is not consulted on this path.
# The blank cursor theme is belt-and-braces with --no-cursor: even a client-
# set cursor (GTK I-beams) has nothing to draw.
if [ -x /usr/bin/gnome-kiosk ]; then
    # A leftover monitors.xml would stack its transform on the kernel's
    # panel_orientation at compositor startup (proven in the field: one stale
    # file, one upside-down breakfast shift). Rotation state has two owners
    # only — the kernel cmdline and the live session delta below.
    rm -f "$HOME/.config/monitors.xml" 2>/dev/null || true
    # Re-apply the unit's chosen orientation once the compositor is up: mutter
    # starts at transform normal, and the orientation file is the operator's
    # answer. Backgrounded with a retry loop because the bus only exists a few
    # seconds from now; dies with the session (same cgroup) either way.
    # AS EARLY AS POSSIBLE: poll fast (300ms) from the very first moment, so
    # the transform lands the instant mutter's D-Bus name exists — the wrong-
    # orientation flash at boot lasts however long this loop takes. The apply
    # is persistent (monitors.xml), so on later boots mutter starts already
    # rotated and this loop just confirms.
    if [ -x /usr/local/bin/maestro-kds-rotate-mutter ]; then
        ( i=0; while [ $i -lt 100 ]; do
              /usr/local/bin/maestro-kds-rotate-mutter \
                  "$(cat /etc/maestro-kds/orientation 2>/dev/null || echo normal)" \
                  >/dev/null 2>&1 && break
              i=$((i + 1))
              sleep 0.3
          done ) &
    fi
    # THE INTRO RUNS UNTIL THIS MOMENT — released by the unit's root-level
    # ExecStartPre (`plymouth quit --retain-splash`); an unprivileged quit
    # from this user-level script fails silently and wedges the compositor
    # behind a frozen splash.
    # SOUND: the chime path is app-volume × ALSA hardware mixer, and a fresh
    # install leaves the codec's mixer wherever the driver felt like — 100%%
    # in the app was barely audible on the bench because the hardware half
    # sat low. Max and unmute every common playback control, best-effort:
    # control names vary by codec and a missing one is not an error.
    if command -v amixer >/dev/null 2>&1; then
        for ctl in Master PCM Speaker Headphone Front; do
            amixer -q set "$ctl" 100% unmute 2>/dev/null || true
        done
    fi
    export GDK_BACKEND=wayland
    export XCURSOR_THEME=maestro-blank
    export XCURSOR_PATH=/usr/local/share/icons:/usr/share/icons
    export XCURSOR_SIZE=24
    # Publish the session bus address where the rotation helper can find it:
    # mutter runs non-dumpable (realtime caps), so /proc/<pid>/environ is
    # root-only and no good to the boot-time rotator running as the kiosk
    # user. The unit's RuntimeDirectory is owned by this user and wiped each
    # boot — exactly the lifetime the address has.
    exec dbus-run-session -- /bin/sh -c '
        printf %s "$DBUS_SESSION_BUS_ADDRESS" \
            > /run/maestro-kds/bus-address 2>/dev/null || true
        exec /usr/bin/gnome-kiosk --wayland --no-x11 \
            --display-server --no-cursor -- /opt/maestro-kds/maestro-kds'
fi

# ── FALLBACK: sway (the shim era) — kept whole so a unit without gnome-kiosk
# still boots exactly as before this change.
CONF=/etc/maestro-kds/sway.conf
if [ ! -r "$CONF" ]; then
    # Rendered from the app bundle by `maestro-kds-ctl sync`, which the boot
    # oneshot runs before this. If it is missing the unit is mid-provision;
    # say so rather than starting a compositor with default (landscape) output.
    echo "no $CONF — has maestro-kds-ctl sync run?" >&2
    exit 1
fi

if [ ! -x /usr/bin/sway ]; then
    echo "sway is not installed — the kiosk compositor is missing" >&2
    exit 1
fi

# sway launches the app itself (exec_always in the config) so the compositor is
# already up and the output already transformed before the first frame — the
# app never sees a landscape surface it would have to relayout out of.
exec /usr/bin/sway -c "$CONF"
