#!/bin/sh # sway-nested.sh — arranca el escritorio `escritorio-sway` como una VENTANA del compositor que ya # tenés delante, sin QEMU, sin kernel y sin imagen de disco. Hermano de `sway-headless.sh` (que # dibuja contra un backend headless y captura con grim) y de `sway-start.sh` (que arranca dentro de # la VM). # # ══ PARA QUÉ, SI YA ESTÁ EL DE QEMU ════════════════════════════════════════════════════════════ # Porque el ciclo de QEMU es de decenas de minutos —armar rootfs, imagen EFI, arrancar— y el de acá # es de segundos, así que «¿esto se ve bien?» deja de costar una tanda. Y porque hay un tipo de # defecto que SÓLO se ve mirando: el puntero invisible por falta de tema de cursor (arreglado el # 2026-09-04 con `adwaita-cursors`) no lo caza ninguna métrica de clausura, y en una captura # headless tampoco aparece — el cursor no está en el framebuffer que devuelve `grim`. # # NO sustituye al arranque en QEMU: acá no hay kernel nuestro, ni initramfs, ni DRM, ni PID 1. # Prueba el compositor y sus clientes, que es justo lo que la VM tapa detrás de una pantalla negra. # # ══ LAS DOS CAPAS, Y POR QUÉ SON DOS ═══════════════════════════════════════════════════════════ # `targets.toml` describe lo que la IMAGEN añade, no un sistema entero: ningún perfil —ni `base`— # incluye `musl`, porque la libc, busybox y PID 1 salen del bootstrap (`work/metal-rootfs`), no de # una receta del perfil. Medido: el cierre de `escritorio-sway` no trae UN solo `ld-musl`, y sway # es `link = dynamic` con intérprete `/lib/ld-musl-x86_64.so.1`. Por eso acá van dos overlays: la # capa base (por defecto el dev-fs de Alpine, que es lo que ya usan los `nested-*` de KDE) y encima # el rootfs del perfil. Si algún día el perfil declarara su propia base, esto se queda en uno. # # ══ QUÉ MIRAR ═════════════════════════════════════════════════════════════════════════════════ # Se abre una ventana con el fondo azul de la config, la barra de `yambar` y una terminal `foot`. # Lo que hay que comprobar A OJO y ninguna captura headless muestra: **que el puntero se vea** al # entrar el mouse en la ventana. # # uso: scripts/wlr/sway-nested.sh # env: RFS (def /mnt/cosecha/escritorios/sway-rootfs) rootfs del perfil, ya hidratado con # scripts/hydrate-profile.py # BASE (def .dev-fs/alpine) capa con la libc # W/H (def 1280x800) tamaño de la ventana # APP (def foot) cliente que se lanza dentro # OUT (def /mnt/cosecha/escritorios) dónde deja `sway-nested.png` # SECS (def 0 = quedarse abierto) segundos antes de cerrar solo set -u ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT" RFS="${RFS:-/mnt/cosecha/escritorios/sway-rootfs}" BASE="${BASE:-$ROOT/.dev-fs/alpine}" W="${W:-1280}"; H="${H:-800}"; APP="${APP:-foot}" OUT="${OUT:-/mnt/cosecha/escritorios}"; SECS="${SECS:-0}" mkdir -p "$OUT" [ -d "$RFS" ] || { echo "falta el rootfs del perfil: $RFS" >&2 echo " scripts/hydrate-profile.py escritorio-sway --store /mnt/cosecha/store --into $RFS" >&2; exit 1; } [ -d "$BASE" ] || { echo "falta la capa base con la libc: $BASE" >&2; exit 1; } # El socket del compositor del HOST. Es lo único del host que cruza: un socket, no una librería. HOST_XDG="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" HOST_WL="${WAYLAND_DISPLAY:-wayland-0}" case "$HOST_WL" in /*) SOCK="$HOST_WL" ;; *) SOCK="$HOST_XDG/$HOST_WL" ;; esac [ -S "$SOCK" ] || { echo "no hay socket Wayland del host en $SOCK" >&2; exit 1; } echo "== host: $SOCK rootfs: $RFS" exec bwrap \ --overlay-src "$BASE" --overlay-src "$RFS" --tmp-overlay / \ --dev /dev --proc /proc --tmpfs /tmp --tmpfs /run \ --dev-bind-try /dev/dri /dev/dri \ --ro-bind "$SOCK" /run/wl-host \ --bind "$OUT" /salida \ --unshare-pid --unshare-uts --hostname sway-nested \ --uid 0 --gid 0 --die-with-parent \ --setenv PATH /usr/bin:/bin:/usr/sbin:/sbin \ --setenv HOME /root --setenv SHELL /bin/sh --setenv TERM xterm-256color \ --setenv XDG_RUNTIME_DIR /run/user/0 \ --setenv WAYLAND_DISPLAY /run/wl-host \ --setenv WLR_BACKENDS wayland \ --setenv WLR_RENDERER pixman \ --setenv WLR_WL_OUTPUTS 1 \ --setenv XDG_DATA_DIRS /usr/share \ --setenv FONTCONFIG_PATH /etc/fonts \ /bin/sh -c ' set -u mkdir -p /run/user/0 /root/.config/sway /etc; chmod 700 /run/user/0 # /etc/passwd: sin él, cualquier getpwuid() falla y yambar no arranca (cicatriz de headless). [ -s /etc/passwd ] || echo "root:x:0:0:root:/root:/bin/sh" > /etc/passwd cat > /root/.config/sway/config </tmp/sway.log 2>&1 & SWAY=$! i=0 while [ $i -lt 60 ]; do S=$(ls /run/user/0/sway-ipc.* 2>/dev/null | head -1) [ -n "${S:-}" ] && break kill -0 $SWAY 2>/dev/null || { echo " !! sway murió:"; tail -20 /tmp/sway.log; exit 1; } i=$((i+1)); sleep 0.5 done [ -n "${S:-}" ] || { echo " !! sway no abrió su IPC en 30 s:"; tail -20 /tmp/sway.log; exit 1; } echo " sway OK — ventana en tu pantalla" export SWAYSOCK="$S" # El cliente se lanza DESPUÉS del socket: los `exec` de la config disparan antes de que exista # el workspace y mueren con «Failed to create launch context». # # ⚠ `yambar` NO va en un bloque `bar { status_command … }`: es una barra COMPLETA de layer-shell, # no un productor de estado para swaybar. Puesta ahí, sway rechaza la config ENTERA («Error(s) # loading config!») y arranca sin fondo ni bordes — un fallo que se lee como «el escritorio está # roto» cuando lo único mal es una línea. Se lanza como cliente, igual que foot. swaymsg exec '"$APP"' >/dev/null 2>&1 || '"$APP"' & sleep 4 echo "== clientes =="; swaymsg -t get_tree 2>/dev/null | tr "," "\n" | grep "\"app_id\"" | grep -v null | sort -u # La línea que PRUEBA el tema de cursor: sin `adwaita-cursors` wlroots no encuentra el tema # `default` y el puntero no se dibuja. Se reintenta porque el tema se carga cuando el puntero # entra por primera vez, no al arrancar — mirarlo una sola vez daba un ⚠ falso. echo "== cursores ==" j=0; while [ $j -lt 10 ]; do grep -i "cursor theme" /tmp/sway.log && break j=$((j+1)); sleep 1 [ $j -eq 10 ] && echo " ⚠ wlroots NO cargó ningún tema de cursor" done # La captura. ⚠ `grim` hay que apuntarlo al socket de NUESTRO sway: adentro `WAYLAND_DISPLAY` # es el del HOST (es lo que sway necesita para nestear), así que un `grim` a secas captura la # pantalla del host entera — sale una foto perfecta… del escritorio de al lado. Medido. # El cursor NO sale en la captura (no está en el framebuffer de screencopy): para verlo hay que # mirar la ventana. Esto prueba lo otro — que los clientes dibujaron. WLSWAY=$(basename "$(ls /run/user/0/wayland-* 2>/dev/null | grep -v lock | head -1)") if command -v grim >/dev/null 2>&1 && [ -n "${WLSWAY:-}" ]; then WAYLAND_DISPLAY="$WLSWAY" grim /salida/sway-nested.png 2>/tmp/grim.log \ && echo "== captura (por $WLSWAY): '"$OUT"'/sway-nested.png" \ || { echo "== captura FALLÓ:"; tail -3 /tmp/grim.log; } fi echo "== sway.log (cola) =="; grep -vi "Basic output test" /tmp/sway.log | tail -4 if [ '"$SECS"' -gt 0 ]; then sleep '"$SECS"'; kill $SWAY 2>/dev/null; else wait $SWAY; fi '