#!/bin/sh
# Plasma on X11, started straight from pid 1: no display manager, no login.
#
# The X server is rootful and fullscreen, and KWin is its window manager, so
# the frames are Breeze's. In the page the X server is the page's own
# (hostx rootful) and already listening on :0; natively (GEX_KDE_XWAYLAND=1)
# it is Debian's Xwayland, started here as a client of gex's compositor.
#
# No GL: KWin's only X11 compositor is OpenGL, and the X server has none to
# give it, so compositing is off; Qt Quick (plasmashell, the KCMs) draws in
# software.
# The compositor's socket, as gexc gave it, before the session's own
# XDG_RUNTIME_DIR replaces the directory it is named in.
case ${WAYLAND_DISPLAY:-} in
    /*|'') wl=${WAYLAND_DISPLAY:-} ;;
    *) wl=${XDG_RUNTIME_DIR:-/tmp}/$WAYLAND_DISPLAY ;;
esac
export HOME=/root USER=root LOGNAME=root SHELL=/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export XDG_RUNTIME_DIR=/run/user/0
# dpkg's database put right if the last install stopped part way (a page
# closed, a session killed, in an apt install): the next apt then works.
# When nothing stopped (always, in a fresh page) it is one grep.
[ -x /usr/lib/gex/apt-heal ] && /usr/lib/gex/apt-heal
# A fresh one each boot, as a login's is: a session that was killed leaves
# its sockets, ICE authority and temporary files behind in a tree that is
# kept (natively)
case $wl in "$XDG_RUNTIME_DIR"/*) ;; *) rm -rf "$XDG_RUNTIME_DIR" ;; esac
mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR"
# The system bus (/etc/dbus-1/gex-system.conf), on which Debian's services
# start on demand: polkitd (which grants root everything, so the KCMs' KAuth
# actions, the Users page's AccountsService, pkcheck work), AccountsService,
# the KCMs' helpers. Nothing runs on it until asked. There is no logind on
# it: Plasma's session backend is its no-op one (no shutdown, restart,
# suspend or user switching, which a page has no use for), and polkit's KDE
# agent, which needs a logind session to register, is not started (root's
# autostart override): it is for asking a user's password, and root is
# never asked.
if [ ! -S /run/dbus/system_bus_socket ] || ! dbus-send --system --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.GetId >/dev/null 2>&1; then
    mkdir -p /run/dbus && rm -f /run/dbus/pid /run/dbus/system_bus_socket
    # (not --fork, which sends what its services say to /dev/null)
    dbus-daemon --config-file=/etc/dbus-1/gex-system.conf --nofork &
    i=0
    until [ -S /run/dbus/system_bus_socket ] || [ $i -ge 600 ]; do sleep 0.1; i=$((i + 1)); done
fi
export XDG_SESSION_TYPE=x11 XDG_CURRENT_DESKTOP=KDE XDG_SESSION_DESKTOP=KDE DESKTOP_SESSION=plasma
export XDG_CONFIG_DIRS=/etc/xdg XDG_DATA_DIRS=/usr/local/share:/usr/share
export KWIN_COMPOSE=N QT_QUICK_BACKEND=software QT_XCB_GL_INTEGRATION=none
export LANG=C.UTF-8
# GEX_KDE_TZ=ZONE: the visitor's time zone (the page passes the browser's
# IANA name), set as a Debian system's is: /etc/localtime a link into
# tzdata's zoneinfo and /etc/timezone its name. Qt's system zone is TZ when
# set, else that link's target, else /etc/timezone; libc reads the link. A
# browser may name a zone by its older alias (Asia/Calcutta), which only
# tzdata-legacy has: tzdata.zi's link lines give its current name. TZ is
# unset once the zone is set, as a rule string there (a page's POSIX TZ)
# is no zone Qt or the clock's settings know by name.
zi=/usr/share/zoneinfo
z=${GEX_KDE_TZ:-}
case $z in *[!A-Za-z0-9_+/-]*|*..*|/*) z= ;; esac
if [ -n "$z" ] && [ ! -f "$zi/$z" ] && [ -f "$zi/tzdata.zi" ]; then
    z=$(sed -n "s|^L \([^ ]*\) $z\$|\1|p" "$zi/tzdata.zi" | head -n 1)
fi
if [ -n "$z" ] && [ -f "$zi/$z" ]; then
    ln -sf "$zi/$z" /etc/localtime
    echo "$z" > /etc/timezone
    unset TZ
fi
unset GEX_KDE_TZ
# The usual folders in the home directory, as a desktop login's first run
# makes them (xdg-user-dirs is not in the image): Dolphin's Places lists
# them, and the file manager does not open on an empty home
mkdir -p "$HOME/Documents" "$HOME/Downloads" "$HOME/Music" "$HOME/Pictures" "$HOME/Videos" 2>/dev/null
# Two malloc arenas, not glibc's eight per CPU: each thread's arena reserves
# 128 MiB of address space, and the guest has 2 GiB; plasmashell's thread
# pools (a search in the launcher starts a dozen threads) ran it out, and its
# forked helpers died in malloc
export MALLOC_ARENA_MAX=2
# plasmashell says "Plasma Shell startup completed" (a debug line) when the
# desktop's UI is loaded: the page's loading screen waits for it, since the
# X server's first frame comes minutes before the desktop is drawn
export QT_LOGGING_RULES="${QT_LOGGING_RULES:+$QT_LOGGING_RULES;}kde.plasmashell.debug=true"
unset WAYLAND_DISPLAY
export DISPLAY=${DISPLAY:-:0}
n=${DISPLAY#:}
if [ "${GEX_KDE_XWAYLAND:-0}" = 1 ]; then
    rm -f "/tmp/.X$n-lock" "/tmp/.X11-unix/X$n"
    WAYLAND_DISPLAY=$wl XWAYLAND_NO_GLAMOR=1 \
        Xwayland "$DISPLAY" -fullscreen ${GEX_KDE_GEOMETRY:+-geometry $GEX_KDE_GEOMETRY} -shm -ac -nolisten tcp -noreset > "${GEX_KDE_LOG:-/tmp}/xwayland.log" 2>&1 &
    i=0
    while [ ! -S "/tmp/.X11-unix/X$n" ]; do
        [ $i -ge 600 ] && { echo "gex-kde-session: no X server after 600 s"; exit 1; }
        sleep 1; i=$((i + 1))
    done
    echo "gex-kde-session: X $DISPLAY up after $i s"
fi
# GEX_KDE_STOP_AFTER=SECONDS (debugging): the session ends then, and gex
# with it, normally (a recorded profile is written at exit)
if [ -n "${GEX_KDE_STOP_AFTER:-}" ]; then
    # by the clock, in short sleeps (a long one ends early on an older gex)
    end=$(( $(date +%s) + GEX_KDE_STOP_AFTER ))
    (while [ "$(date +%s)" -lt "$end" ]; do sleep 10; done; kill $$) &
fi
# GEX_KDE_STOP_FILE=PATH (debugging): the session ends the same way once PATH
# exists, so a recorded run can stop when what it recorded is done
if [ -n "${GEX_KDE_STOP_FILE:-}" ]; then
    (while [ ! -e "$GEX_KDE_STOP_FILE" ]; do sleep 10; done; kill $$) &
fi
# GEX_KDE_XLOG=FILE (debugging): the X server's top-level windows, mapped
# or not, once a minute
if [ -n "${GEX_KDE_XLOG:-}" ]; then
    (while :; do sleep 60; { date +%T; xwininfo -root -tree; } >> "$GEX_KDE_XLOG" 2>&1; done) &
fi
# GEX_KDE_XACTIVE=1 (debugging): each window that becomes the active one, by
# its class, when it does (a new window is made active as it is mapped), so
# a launch can be timed to its window
if [ "${GEX_KDE_XACTIVE:-0}" = 1 ]; then
    (until xprop -root _NET_ACTIVE_WINDOW >/dev/null 2>&1; do sleep 5; done
     # line by line: xprop -spy's stdio holds a pipe's output in a block
     # (and stdbuf's preload does nothing for a wasm32 program); xev flushes
     # each event, so where there is one it says when the property changes
     # and xprop reads the new value
     if command -v xev >/dev/null; then
         xev -root -event property | while read -r line; do
             case $line in *'(_NET_ACTIVE_WINDOW)'*) ;; *) continue ;; esac
             at=${EPOCHREALTIME:-$(date +%s.%N)}
             id=$(xprop -root _NET_ACTIVE_WINDOW 2>/dev/null); id=${id##* }
             echo "$at $id"
         done
     else
         xprop -root -spy _NET_ACTIVE_WINDOW | while read -r line; do
             echo "${EPOCHREALTIME:-$(date +%s.%N)} ${line##* }"
         done
     fi | while read -r at id; do
         case $id in 0x*) ;; *) continue ;; esac
         [ "$id" = 0x0 ] && continue
         echo "gex-kde-session: active $(xprop -id "$id" WM_CLASS 2>/dev/null | sed 's/.*= //') at $at"
     done) &
fi
# GEX_KDE_LISTHOME=SECONDS (debugging): what the session has made in the
# home directory's config and data then (shell globs: an ejected tree may
# have no ls)
if [ -n "${GEX_KDE_LISTHOME:-}" ]; then
    (sleep "$GEX_KDE_LISTHOME"; echo "gex-kde-session: ~/.config:" "$HOME"/.config/*; echo "gex-kde-session: ~/.local/share:" "$HOME"/.local/share/*; echo "gex-kde-session: ~/.cache:" "$HOME"/.cache/*) &
fi
# The image's KService database (ksycoca), built with it: one rebuilt at
# session start, because a directory or file it indexes looks newer, costs
# minutes under translation. Said if it is missing or changes in the first
# 10 minutes (kde/test-page.mjs fails on it).
# Shell builtins alone: an ejected tree has only the programs the session
# ran (no ls, stat or head).
db=
for f in "$HOME"/.cache/ksycoca6_*; do [ -e "$f" ] && { db=$f; break; }; done
if [ -z "$db" ]; then
    echo "gex-kde-session: no ksycoca in the image"
else
    mark=$XDG_RUNTIME_DIR/.gex-sycoca-start
    : > "$mark"
    (i=0
     while [ $i -lt 10 ]; do
         sleep 60; i=$((i + 1))
         [ "$db" -nt "$mark" ] && { echo "gex-kde-session: ksycoca rebuilt at session start"; break; }
     done) &
fi
# The global shortcuts' daemon started with the bus, ahead of KWin: its only
# start is an autostart entry of the last phase (a systemd unit on a systemd
# login, and no D-Bus activation), so KWin's shortcuts (Alt+Tab, Alt+F4,
# Meta) were asked for before it ran and never had keys (root's autostart
# override keeps that entry from starting a second)
kga=$(echo /usr/lib/*/libexec/kglobalacceld)
# GEX_KDE_DBUS_LOG=FILE (debugging): every session bus message, timed
if [ -n "${GEX_KDE_DBUS_LOG:-}" ]; then
    export GEX_KDE_DBUS_LOG
    exec dbus-run-session --config-file=/etc/dbus-1/gex-session.conf -- sh -c \
        'dbus-monitor --session > "$GEX_KDE_DBUS_LOG" 2>&1 & '"$kga"' & exec startplasma-x11'
fi
# GEX_KDE_LAUNCH=CMD (timing): CMD started in the session twice, as a
# person would from the launcher: GEX_KDE_LAUNCH_AFTER seconds (default 90)
# after the desktop is first the active window, then killed 120 s later,
# and started again 30 s after that; each start's time is said, to set
# against GEX_KDE_XACTIVE's line for its window
if [ -n "${GEX_KDE_LAUNCH:-}" ]; then
    export GEX_KDE_LAUNCH GEX_KDE_LAUNCH_AFTER
    # (bash where there is one, for its clock)
    exec dbus-run-session --config-file=/etc/dbus-1/gex-session.conf -- "$(command -v bash || echo sh)" -c "$kga"' &
        (until [ "$(xprop -root _NET_ACTIVE_WINDOW 2>/dev/null | sed "s/.* //")" != 0x0 ] && xprop -root _NET_ACTIVE_WINDOW >/dev/null 2>&1; do sleep 2; done
         sleep "${GEX_KDE_LAUNCH_AFTER:-90}"
         for k in 1 2; do
             echo "gex-kde-session: launch $k at ${EPOCHREALTIME:-$(date +%s.%N)}"
             $GEX_KDE_LAUNCH & p=$!
             sleep 120; kill $p 2>/dev/null; sleep 30
         done) &
        exec startplasma-x11'
fi
exec dbus-run-session --config-file=/etc/dbus-1/gex-session.conf -- sh -c \
    "$kga"' & exec startplasma-x11'
