Era la única mitad que quedaba abierta del mapa del §6.10: KDE atiende
`org.freedesktop.Notifications` con plasma-workspace, GNOME con gnome-shell y
COSMIC con cosmic-notifications; sway no tenía a NADIE, así que una página que
pedía notificar mandaba el mensaje al bus y se lo comía el silencio.
`dunst` 1.12.2 y no `mako` —que es el de la casa wlroots— por dos razones
medidas: el nombre `mako` YA ESTÁ OCUPADO en el corpus por el motor de plantillas
de mesa, y el cierre del perfil incluye las herramientas de build, así que la
imagen terminaría con dos artefactos homónimos peleando por las mismas rutas; y
mako habla D-Bus por sd-bus, que en musl es una receta nueva (basu), mientras
dunst habla por GDBus, que ya está sellado. Cero dependencias nuevas.
DOS GUARDIANES EN LA PROPIA RECETA, porque las dos cosas que importan no se ven
en que compile:
- `X11=0` es una intención; el hecho se lee en el ELF. dunst compila los dos
backends por defecto y su config.mk avisa: sin wayland «forzará xwayland». En
esta distro eso sella un binario que arranca, toma el bus y NO PINTA NUNCA. La
fase exige `libwayland-client` entre los NEEDED y prohíbe `libX11`.
- El `.service` de D-Bus es la mitad que importa: sin systemd, al daemon lo
levanta EL BUS. Se comprueba que reclame `org.freedesktop.Notifications` y se
le saca la línea `SystemdService=`, que acá no puede significar nada.
Y `DUNSTIFY=0`: entró primero en 1 y el binario **segfaultea hasta en
`--help`** (rc=139 en las tres pruebas). La causa es el cuadro que este repo ya
tiene escrito: nuestro `libnotify.so.4` es compartido y arrastra la cadena glib
`.so`, mientras esta cola enlaza la GLib estática ⇒ dos copias de GObject en un
proceso. No se pierde nada: el emisor de la casa es `notify-send`, que viene
DENTRO del artefacto de libnotify y ya está en las cuatro imágenes. Hay un
guardián que frena el día que alguien vuelva a poner DUNSTIFY=1 sin leer.
LA PRUEBA, en `scripts/wlr/dunst-headless.sh`: sway headless + bus de sesión +
`notify-send`, y NADIE lanza dunst a mano — lo activa el bus por el .service,
que es el mismo camino de una página web en atuq. La evidencia es un diff de
píxeles antes/después acotado al cuadrante donde dunst dibuja, porque un log en
verde es compatible con una pantalla vacía y el color no distingue (swaynag pinta
su barra de error en el mismo rojo).
positivo 14832 píxeles cambiados · servidor: dunst knopwob 1.12.2 1.2
control 0 píxeles · ServiceUnknown · notify-send rc=1
El control negativo borra el .service dentro del overlay temporal y EXIGE que no
se dibuje nada: probado en los dos sentidos.
Captura en docs/evidencia/dunst-sway-notificacion-2026-09-07.png.
Vigía de sonames: escritorio-sway 203 → 204 nodos, 512 sonames, 0 sin proveedor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
121 lines
7.0 KiB
Bash
Executable File
121 lines
7.0 KiB
Bash
Executable File
#!/bin/sh
|
|
# dunst-headless.sh — ¿la imagen de sway ATIENDE una notificación, y se VE?
|
|
#
|
|
# scripts/wlr/dunst-headless.sh # sale 0 si la notificación se dibuja
|
|
# scripts/wlr/dunst-headless.sh --negative-control # borra el .service y exige que NO se vea
|
|
#
|
|
# ── QUÉ PRUEBA, QUE NO ES LO MISMO QUE «ESTÁ INSTALADO» ───────────────────────────────────────
|
|
# Tener `libnotify` en la imagen es media función: el que la completa es un daemon escuchando
|
|
# `org.freedesktop.Notifications`. KDE lo tiene por plasma-workspace, GNOME por gnome-shell, COSMIC
|
|
# por cosmic-notifications; sway no tenía a nadie, y por eso entró `dunst`. Que la receta selle no
|
|
# prueba nada de eso: hay que ver el bus ACTIVAR el daemon y el daemon DIBUJAR.
|
|
#
|
|
# El camino que se ejercita es el de verdad, no un atajo:
|
|
#
|
|
# notify-send → bus de sesión → activación por el .service → dunst arranca → pinta en sway
|
|
#
|
|
# **Nadie lanza dunst a mano acá**, a propósito: si el `.service` no estuviera, o el `Exec` apuntara
|
|
# a otro lado, o dunst no supiera hablar wayland, este guion no vería nada. Es el mismo camino que
|
|
# recorre una página web pidiendo notificar en `atuq`.
|
|
#
|
|
# ── POR QUÉ notify-send Y NO dunstify ─────────────────────────────────────────────────────────
|
|
# Porque `dunstify` no se shipea: mezcla la GLib ESTÁTICA del corpus con la COMPARTIDA que arrastra
|
|
# `libnotify.so.4` y se cae con SIGSEGV hasta en `--help` (dos copias de GObject en un proceso — el
|
|
# cuadro ya conocido de este repo). El emisor de la casa es `notify-send`, que viene dentro del
|
|
# artefacto de `libnotify` y enlaza toda la cadena compartida. El porqué largo está en la receta.
|
|
#
|
|
# ── LA EVIDENCIA ES UN DIFF DE PÍXELES, NO UN LOG ─────────────────────────────────────────────
|
|
# Se captura ANTES y DESPUÉS de mandar la notificación y se cuenta cuántos píxeles cambiaron en el
|
|
# cuadrante donde dunst dibuja. Un log lleno de líneas verdes es compatible con una pantalla vacía —
|
|
# ya pasó en este mismo andamio— y el color solo tampoco alcanza: swaynag pinta su barra de error en
|
|
# el MISMO rojo que dunst usa para `critical`, así que «hay rojo» no distingue nada. Lo que no se
|
|
# puede falsear es que aparezca algo donde antes no había nada.
|
|
set -eu
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
|
|
RFS="${RFS:-/mnt/cosecha/escritorios/sway-rootfs}"
|
|
ALPINE="$ROOT/.dev-fs/alpine"
|
|
CONTROL=0
|
|
[ "${1:-}" = "--negative-control" ] && CONTROL=1
|
|
|
|
[ -d "$RFS" ] || { echo "✗ no está el rootfs en $RFS — corré antes:" >&2
|
|
echo " scripts/hydrate-profile.py escritorio-sway --into $RFS" >&2; exit 1; }
|
|
command -v bwrap >/dev/null || { echo "✗ falta bwrap" >&2; exit 1; }
|
|
|
|
OUT="$(mktemp -d)"
|
|
trap 'rm -rf "$OUT"' EXIT
|
|
|
|
cat > "$OUT/dentro.sh" <<'ADENTRO'
|
|
#!/bin/sh
|
|
export XDG_RUNTIME_DIR=/run/user/0 HOME=/root LANG=C XDG_DATA_DIRS=/usr/share
|
|
export WLR_BACKENDS=headless WLR_HEADLESS_OUTPUTS=1 WLR_RENDERER=pixman
|
|
mkdir -p "$XDG_RUNTIME_DIR" /root; chmod 700 "$XDG_RUNTIME_DIR"
|
|
# El control negativo saca el fichero que hace posible la activación. La jaula corre sobre un
|
|
# overlay temporal, así que borrar acá no toca el rootfs de verdad.
|
|
[ "${CONTROL:-0}" = "1" ] && rm -f /usr/share/dbus-1/services/org.knopwob.dunst.service
|
|
(
|
|
i=0
|
|
while [ ! -S /run/user/0/wayland-1 ] && [ $i -lt 40 ]; do sleep 1; i=$((i+1)); done
|
|
[ -S /run/user/0/wayland-1 ] || { echo "@@ sin socket wayland"; exit 1; }
|
|
export WAYLAND_DISPLAY=wayland-1
|
|
/usr/bin/swaybg -c '#1a4b8c' & sleep 3
|
|
# El bus se levanta CON el entorno wayland puesto: lo que él active lo hereda, igual que en una
|
|
# sesión de verdad. Al revés, dunst arrancaría sin display y saldría con status 1.
|
|
eval "$(/usr/bin/dbus-daemon --session --fork --print-address=1 | { read a; echo "DBUS_SESSION_BUS_ADDRESS=$a"; })"
|
|
export DBUS_SESSION_BUS_ADDRESS
|
|
/usr/bin/grim /salida/antes.png
|
|
echo "@@ dueño ANTES: $(timeout 10 /usr/bin/dbus-send --session --dest=org.freedesktop.DBus \
|
|
--print-reply=literal /org/freedesktop/DBus org.freedesktop.DBus.NameHasOwner \
|
|
string:org.freedesktop.Notifications 2>&1 | tr -d ' \n')"
|
|
timeout 25 /usr/bin/notify-send -u critical "hammer" "dunst lo activó el bus" 2>&1
|
|
echo "@@ notify-send rc=$?"
|
|
sleep 5
|
|
echo "@@ servidor: $(timeout 10 /usr/bin/dbus-send --session --dest=org.freedesktop.Notifications \
|
|
--print-reply=literal /org/freedesktop/Notifications \
|
|
org.freedesktop.Notifications.GetServerInformation 2>&1 | tr -s ' ')"
|
|
/usr/bin/grim /salida/despues.png && echo "@@ capturas listas"
|
|
sleep 1
|
|
/usr/bin/swaymsg exit 2>/dev/null
|
|
) &
|
|
exec /usr/bin/sway -d 2>&1
|
|
ADENTRO
|
|
|
|
# `--unshare-pid` para que un `ps` de adentro no vea la máquina entera, y el lab de alpine DEBAJO
|
|
# porque el corpus no produce el intérprete musl (`ld-musl-x86_64.so.1`): sin él ni sway arranca.
|
|
timeout 180 bwrap --overlay-src "$ALPINE" --overlay-src "$RFS" --tmp-overlay / \
|
|
--bind "$OUT" /salida --dev /dev --proc /proc --tmpfs /run --tmpfs /tmp --unshare-pid \
|
|
--uid 0 --gid 0 --setenv PATH /usr/bin:/bin:/usr/sbin:/sbin --setenv CONTROL "$CONTROL" \
|
|
/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; cp "$OUT/log.txt" work/dunst-headless.log 2>/dev/null || true; exit 1; }
|
|
|
|
# El diff se hace acá afuera: dentro de la imagen no hay python ni con qué mirar un PNG.
|
|
CAMBIADOS="$(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])
|
|
# Sólo el cuadrante donde dunst dibuja por defecto (arriba a la derecha). Acotarlo saca del medio la
|
|
# barra de swaynag y el reloj de swaybar, que cambian solos y darían un falso positivo.
|
|
w, h = a.size
|
|
caja = (w // 2, h // 12, w, h // 2)
|
|
d = ImageChops.difference(a.crop(caja), b.crop(caja)).convert("L")
|
|
# Por histograma y no recorriendo píxeles: el bin 0 son los que NO cambiaron.
|
|
print(d.width * d.height - d.histogram()[0])
|
|
PY
|
|
)"
|
|
echo " píxeles cambiados en el cuadrante de dunst: $CAMBIADOS"
|
|
|
|
if [ "$CONTROL" = "1" ]; then
|
|
if [ "$CAMBIADOS" -lt 500 ]; then
|
|
echo "✓ control negativo: sin el .service no se dibuja nada (el guardián SÍ lo vería)"; exit 0
|
|
fi
|
|
echo "✗ control negativo: se dibujó una notificación SIN el .service — este guion no prueba lo que dice" >&2
|
|
exit 1
|
|
fi
|
|
[ "$CAMBIADOS" -ge 500 ] || {
|
|
echo "✗ la notificación no se dibujó ($CAMBIADOS píxeles) — capturas en $OUT" >&2
|
|
cp "$OUT"/*.png "$OUT/log.txt" work/ 2>/dev/null || true; exit 1; }
|
|
cp "$OUT/despues.png" work/dunst-headless.png 2>/dev/null || true
|
|
echo "✓ el bus activó dunst y la notificación se dibujó (captura en work/dunst-headless.png)"
|