#!/bin/sh
# atuq-en-cosmic.sh — ¿el navegador PINTA SU VENTANA bajo `cosmic-comp`? (SDD 26 §6.10.ter)
#
# scripts/cosmic/atuq-en-cosmic.sh # atuq dentro de cosmic-comp
# scripts/cosmic/atuq-en-cosmic.sh --control # el MISMO atuq, pero directo sobre sway
#
# ── POR QUÉ EXISTE ────────────────────────────────────────────────────────────────────────────
# El 2026-09-14 se arrancó por primera vez una IMAGEN DE DISCO con COSMIC y `atuq` adentro: el
# navegador arranca, sus extensiones corren, WebRender inicializa… y la ventana no aparece nunca.
# Bajo `sway` headless el mismo artefacto pinta perfecto. Entre los dos casos cambian DOS cosas a la
# vez —el compositor (cosmic-comp+llvmpipe contra sway+pixman) y el arranque real contra `bwrap`—,
# así que ninguna medición de las que había separa una de la otra.
#
# Este arnés cambia UNA sola: el compositor. Todo lo demás —el mismo cierre `escritorio-cosmic`
# hidratado, el mismo binario de `atuq`, la misma jaula— queda fijo, y `--control` corre ese mismo
# `atuq` sobre sway sin tocar nada más. Cuesta ~2 min por vuelta en vez de armar una imagen de 12 G
# y arrancarla en QEMU sin KVM.
#
# ── LA CADENA ─────────────────────────────────────────────────────────────────────────────────
# sway (headless, pixman) → cosmic-comp (winit, cliente de sway) → atuq
# `grim` captura del lado de SWAY, o sea que fotografía la ventana de cosmic-comp con lo que haya
# dentro. sway es andamio: no lo prueba nada, sólo le da a cosmic-comp una pantalla donde vivir.
#
# ── LA EVIDENCIA ES UN DIFF DE PÍXELES, Y ADEMÁS SE MIRA EL ÁRBOL ─────────────────────────────
# Se captura ANTES de lanzar el navegador y DESPUÉS. Un log en verde es compatible con una pantalla
# vacía —es exactamente lo que pasó en la imagen— así que el veredicto es cuántos píxeles cambiaron.
# Y se guarda el `MOZ_LOG` del widget: si la ventana se crea pero nunca recibe su `configure`, eso
# sólo se ve ahí.
#
# env: RFS_COSMIC (def /mnt/cosecha/cosmic-rootfs) cierre de escritorio-cosmic
# RFS_SWAY (def /mnt/cosecha/escritorios/sway-rootfs) el andamio
# MESA (def el artefacto vigente de mesa-llvmpipe) GL por software para cosmic-comp
# ESPERA (def 60) segundos para que el navegador pinte
# OUT (def un temporal) dónde quedan capturas y logs
set -eu
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
RFS_COSMIC="${RFS_COSMIC:-/mnt/cosecha/cosmic-rootfs}"
RFS_SWAY="${RFS_SWAY:-/mnt/cosecha/escritorios/sway-rootfs}"
ALPINE="$ROOT/.dev-fs/alpine"
ESPERA="${ESPERA:-60}"
CONTROL=0
for a in "$@"; do
case "$a" in
--control) CONTROL=1 ;;
*) echo "✗ opción desconocida: $a" >&2; exit 1 ;;
esac
done
# mesa-llvmpipe por HASH VIGENTE, no por el más reciente del store (con dos artefactos del mismo
# paquete conviviendo, la fecha miente). Es el mismo criterio de cosmic-nested.sh.
if [ -z "${MESA+x}" ]; then
MH=$(./target/release/takana --store /mnt/cosecha/store hash recipes/mesa-llvmpipe.toml 2>/dev/null | tr -d '\n' | sed 's/b3://')
MESA=$(ls -d /mnt/cosecha/store/"$MH"-mesa-llvmpipe 2>/dev/null || true)
fi
[ -d "$RFS_COSMIC" ] || { echo "✗ falta el cierre de cosmic en $RFS_COSMIC" >&2
echo " scripts/hydrate-profile.py escritorio-cosmic --store /mnt/cosecha/store --into $RFS_COSMIC" >&2; exit 1; }
[ -d "$RFS_SWAY" ] || { echo "✗ falta el rootfs de sway en $RFS_SWAY" >&2; exit 1; }
[ -x "$RFS_COSMIC/usr/bin/atuq" ] || { echo "✗ el cierre no trae /usr/bin/atuq" >&2; exit 1; }
[ -n "${MESA:-}" ] || echo " ⚠ sin mesa-llvmpipe sellado: cosmic-comp no va a poder iniciar EGL" >&2
OUT="${OUT:-$(mktemp -d)}"; mkdir -p "$OUT"
echo "== salida: $OUT"
# Una página de UN SOLO COLOR imposible de confundir con nada del escritorio: si aparece, apareció
# la ventana del navegador y no un repintado de cosmic.
cat > "$OUT/pagina.html" <<'HTML'
atuq en cosmic
ATUQ PINTA
HTML
mkdir -p "$OUT/perfil"
cat > "$OUT/perfil/user.js" <<'JS'
user_pref("browser.dom.window.dump.enabled", true);
user_pref("browser.shell.checkDefaultBrowser", false);
JS
cat > "$OUT/dentro.sh" <<'ADENTRO'
#!/bin/sh
export XDG_RUNTIME_DIR=/run/user/0 HOME=/root LANG=C XDG_DATA_DIRS=/usr/share
export XDG_CURRENT_DESKTOP=COSMIC XDG_SESSION_TYPE=wayland RUST_BACKTRACE=1
export LIBGL_ALWAYS_SOFTWARE=1 LIBGL_DRIVERS_PATH=/usr/lib/dri
mkdir -p "$XDG_RUNTIME_DIR" /root /dev/shm /etc; chmod 700 "$XDG_RUNTIME_DIR"
[ -s /etc/passwd ] || echo "root:x:0:0:root:/root:/bin/sh" > /etc/passwd
[ -s /etc/machine-id ] || tr -d - < /proc/sys/kernel/random/uuid > /etc/machine-id
lanzar_atuq() { # $1 = WAYLAND_DISPLAY donde tiene que pintar
WAYLAND_DISPLAY="$1" \
MOZ_ENABLE_WAYLAND=1 GDK_BACKEND=wayland \
MOZ_DISABLE_CONTENT_SANDBOX=1 MOZ_DISABLE_RDD_SANDBOX=1 MOZ_DISABLE_UTILITY_SANDBOX=1 \
MOZ_DISABLE_GMP_SANDBOX=1 MOZ_DISABLE_SOCKET_PROCESS_SANDBOX=1 \
MOZ_LOG='Widget:5,WidgetWayland:5,WaylandBackend:5,timestamp' \
MOZ_LOG_FILE=/salida/moz \
/usr/bin/atuq --no-remote --profile /salida/perfil file:///salida/pagina.html \
> /salida/atuq.log 2>&1 &
echo "@@ atuq lanzado (pid $!) contra $1"
}
(
i=0
while [ ! -S /run/user/0/wayland-1 ] && [ $i -lt 60 ]; do sleep 1; i=$((i+1)); done
[ -S /run/user/0/wayland-1 ] || { echo "@@ sin socket de sway"; exit 1; }
export WAYLAND_DISPLAY=wayland-1
/usr/bin/swaybg -c '#1a4b8c' >/dev/null 2>&1 &
sleep 2
export DBUS_SESSION_BUS_ADDRESS=$(/usr/bin/dbus-daemon --session --fork --print-address 2>/dev/null)
echo "@@ dbus=${DBUS_SESSION_BUS_ADDRESS:-VACIO}"
if [ "${CONTROL:-0}" = "1" ]; then
echo "@@ CONTROL: el navegador va directo sobre sway (sin cosmic-comp)"
DEST=wayland-1
else
echo "@@ arrancando cosmic-comp nesteado sobre sway…"
cosmic-comp >/salida/cosmic.log 2>&1 &
CC=$!
i=0; WD=""
while [ $i -lt 60 ]; do
WD=$(ls /run/user/0/wayland-* 2>/dev/null | grep -v lock | grep -v 'wayland-1$' | head -1)
[ -n "$WD" ] && break
kill -0 $CC 2>/dev/null || { echo "@@ cosmic-comp murió antes del socket:"; tail -25 /salida/cosmic.log; exit 1; }
i=$((i+1)); sleep 1
done
[ -n "$WD" ] || { echo "@@ cosmic-comp sin socket en 60 s:"; tail -25 /salida/cosmic.log; exit 1; }
DEST=$(basename "$WD")
echo "@@ cosmic-comp OK ($DEST)"
sleep 5
fi
/usr/bin/grim /salida/antes.png && echo "@@ captura ANTES"
lanzar_atuq "$DEST"
sleep "${ESPERA:-60}"
/usr/bin/grim /salida/despues.png && echo "@@ captura DESPUES"
echo "@@ procesos atuq: $(ps -o comm= 2>/dev/null | grep -c atuq)"
sleep 1
/usr/bin/swaymsg exit 2>/dev/null
) &
export WLR_BACKENDS=headless WLR_HEADLESS_OUTPUTS=1 WLR_RENDERER=pixman
exec /usr/bin/sway -d 2>&1
ADENTRO
# El ORDEN de los overlays manda: alpine trae la libc, sway el andamio, el cierre de cosmic pisa lo
# que comparta con él, y mesa-llvmpipe va ÚLTIMO porque el cierre trae la mesa de Intel real
# (iris_dri) y en una máquina sin GPU eso no inicia EGL.
TIEMPO=$((ESPERA + 180))
timeout "$TIEMPO" bwrap \
--overlay-src "$ALPINE" --overlay-src "$RFS_SWAY" --overlay-src "$RFS_COSMIC" \
${MESA:+--overlay-src "$MESA"} --tmp-overlay / \
--bind "$OUT" /salida --dev /dev --proc /proc --tmpfs /run --tmpfs /tmp \
--dev-bind-try /dev/dri /dev/dri \
--unshare-pid --unshare-uts --hostname atuq-en-cosmic \
--uid 0 --gid 0 --die-with-parent \
--setenv PATH /usr/bin:/bin:/usr/sbin:/sbin \
--setenv CONTROL "$CONTROL" --setenv ESPERA "$ESPERA" \
/bin/sh /salida/dentro.sh > "$OUT/log.txt" 2>&1 || true
grep -a '@@' "$OUT/log.txt" || true
[ -f "$OUT/antes.png" ] && [ -f "$OUT/despues.png" ] || {
echo "✗ no hubo capturas — mirá $OUT/log.txt" >&2; tail -20 "$OUT/log.txt" >&2; exit 1; }
python3 - "$OUT/antes.png" "$OUT/despues.png" <<'PY'
import sys
from PIL import Image, ImageChops
a, b = (Image.open(p).convert("RGB") for p in sys.argv[1:3])
d = ImageChops.difference(a, b).convert("L")
cambiados = d.width * d.height - d.histogram()[0]
magenta = sum(n for n, c in b.getcolors(maxcolors=1 << 24) if c == (255, 0, 255))
print(f" píxeles cambiados: {cambiados} magenta de la página: {magenta}")
PY
echo " capturas: $OUT/antes.png $OUT/despues.png"
echo " logs: $OUT/atuq.log $OUT/cosmic.log $OUT/moz.*"