#!/bin/sh
# Live-apply the unit's orientation to a RUNNING gnome-kiosk (mutter) session.
# Rotation in a mutter kiosk is a transform applied over the session D-Bus
# (org.gnome.Mutter.DisplayConfig) — what GNOME's own display settings do.
# Called by maestro-kds-ctl (rotate / sync) as root and by the session script
# at boot as the kiosk user. The bus address comes from the file the session
# script publishes at launch (/run/maestro-kds/bus-address): mutter runs
# non-dumpable, so reading its environ needs root, which the boot-time caller
# doesn't have. Transform map: normal→0  90→1  180→2  270→3.
# The transform IS the orientation — direct, no kernel base, no delta. The
# cmdline is pinned to normal on mutter units (see maestro-kds-ctl), because
# DRM panel_orientation on the RTK panels compounds with their own built-in
# orientation quirk in ways that measured 90° off from every documented
# meaning. Eyewitness-calibrated ground truth on this fleet: transform 0 =
# upright portrait, 2 = flipped portrait, 1/3 = the landscapes.
v="${1:-normal}"
case "$v" in
    normal) t=0 ;;
    90)     t=1 ;;
    180)    t=2 ;;
    270)    t=3 ;;
    *) echo "rotate-mutter: bad orientation '$v'" >&2; exit 2 ;;
esac
pid="$(pgrep -x gnome-kiosk | head -n1)"
[ -n "$pid" ] || { echo "rotate-mutter: gnome-kiosk not running" >&2; exit 1; }
user="$(ps -o user= -p "$pid" | tr -d ' ')"
uid="$(id -u "$user" 2>/dev/null)" || exit 1
addr="$(cat /run/maestro-kds/bus-address 2>/dev/null)"
[ -n "$addr" ] || { echo "rotate-mutter: no session bus address yet" >&2; exit 1; }
if [ "$(id -u)" = "0" ] && [ "$user" != "root" ]; then
    exec sudo -u "$user" env DBUS_SESSION_BUS_ADDRESS="$addr" \
        XDG_RUNTIME_DIR="/run/user/$uid" "$0" "$v"
fi
export DBUS_SESSION_BUS_ADDRESS="$addr"
export XDG_RUNTIME_DIR="/run/user/$uid"
exec python3 - "$t" <<'PY'
import sys
from gi.repository import Gio, GLib
t = int(sys.argv[1])
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
p = Gio.DBusProxy.new_sync(bus, 0, None, "org.gnome.Mutter.DisplayConfig",
                           "/org/gnome/Mutter/DisplayConfig",
                           "org.gnome.Mutter.DisplayConfig", None)
serial, monitors, logical, _props = p.call_sync(
    "GetCurrentState", None, 0, -1, None).unpack()
new_logical = []
for x, y, scale, _transform, primary, mons, _lp in logical:
    specs = []
    for (conn, _v, _pr, _sn), modes, _mp in monitors:
        if any(conn == m[0] for m in mons):
            cur = next((m[0] for m in modes if m[6].get("is-current", False)),
                       modes[0][0])
            specs.append((conn, cur, {}))
    new_logical.append((x, y, scale, t, primary, specs))
# method=1: TEMPORARY, deliberately. A persistent apply writes monitors.xml,
# which mutter loads at its NEXT startup and stacks on top of the kernel's
# panel_orientation — the double-rotation that ate a reboot cycle in the
# field. State has exactly two owners: the kernel base (persists via the
# cmdline) and this session-scoped delta (re-applied at every session start).
v = GLib.Variant("(uua(iiduba(ssa{sv}))a{sv})", (serial, 1, new_logical, {}))
p.call_sync("ApplyMonitorsConfig", v, 0, -1, None)
print("rotate-mutter: transform", t, "applied")
PY
