portal de ficheros en sway: MEDIDO de punta a punta — el diálogo abre, y el núcleo no arranca sin json-glib (SIGSEGV mudo en 1.18.4)
Semántico sayk'uy
Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.
2 qillqakuna · +177 −5
diff --git a/docs/plan-apps-usuario-final.md b/docs/plan-apps-usuario-final.md
index 7fda9487..3c235036 100644
--- a/docs/plan-apps-usuario-final.md
+++ b/docs/plan-apps-usuario-final.md
@@ -504,11 +504,44 @@ desbloqueo es el mismo que ya pedía el ScreenCast de COSMIC: **lavapipe** (`-Dv
## Tres huecos de la IMAGEN que estas apps destaparon (ninguno es de ellas)
-1. **No hay diálogo de ficheros.** `photocraft` lo dice entero:
- `file dialog backends: Findings { libdbus: None, file_chooser_portal: Some(false), zenity: false }`
- ⇒ Abrir y Guardar no tienen con qué. `xdg-desktop-portal` existe en las colas de kde, gnome y
- cosmic —los tres perfiles lo declaran— y **no hay ninguno en `incoming-wlr` ni en el corpus**.
- Es la receta que destraba a las doce de un solo tiro.
+1. **No hay diálogo de ficheros EN SWAY** —y esta entrada se corrigió dos veces, que es casi todo
+ lo que tiene de interesante—. Lo que `photocraft` dice es
+ `file dialog backends: Findings { libdbus: None, file_chooser_portal: Some(false), zenity: false }`,
+ y de ahí salió primero un «no hay diálogo de ficheros» a secas: **generalización de UNA imagen**.
+ Medido después: los tres escritorios grandes **sí declaran un backend con `FileChooser`**
+ (`xdg-desktop-portal-{kde,gnome,cosmic}`, los tres sellados, con
+ `org.freedesktop.impl.portal.FileChooser` en su `.portal`). El hueco es de **sway y del corpus**.
+ ⚠ Y la sonda de la app **no sirve como medida**: contesta `libdbus: None` aunque `libdbus-1.so`
+ esté presente, o sea que aplasta «no hay portal» y «no supe preguntar» en el mismo valor.
+
+ **La salida está MEDIDA de punta a punta** (`scripts/test-portal-ficheros.sh`, 2026-10-13): sobre
+ el rootfs de sway se montan `xdg-desktop-portal` + `xdg-desktop-portal-cosmic` —el backend de
+ COSMIC es Rust sobre wgpu y **no arrastra ni GTK ni Qt**, a diferencia del de GNOME (gtk4 +
+ libadwaita) y el de KDE (Qt6)— y se llama al método real:
+
+ gdbus call … org.freedesktop.portal.FileChooser.OpenFile "" "Elegi un fichero" "{}"
+ → (objectpath '/org/freedesktop/portal/desktop/request/1_5/t')
+ árbol de sway → "app_id": "com.system76.CosmicFilesDialog" · captura: 1.098 colores
+
+ O sea: **el diálogo abre, se ve y lista ficheros**. Lo que hace falta para llevarlo a la imagen:
+ (a) que las dos recetas sean alcanzables desde `incoming-wlr` (hoy viven en `incoming-cosmic`, y
+ una cola no ve a su hermana), (b) **`json-glib`**, y (c) cuatro líneas de
+ `/usr/share/xdg-desktop-portal/sway-portals.conf`, porque el `.portal` del backend trae
+ `UseIn=COSMIC` y en una sesión `sway` no lo elegiría solo:
+
+ [preferred]
+ default=cosmic
+ org.freedesktop.impl.portal.FileChooser=cosmic
+
+ ⚠⚠ **El punto (b) es el que casi me hace publicar un veredicto falso.** Sin `json-glib` el núcleo
+ del portal NO ARRANCA, y cada versión lo disfraza distinto: la 1.19.4 de la cola gnome dice
+ `Error relocating … json_parser_get_root: symbol not found` (rc=127) y la 1.18.4 de kde/cosmic se
+ cae con **SIGSEGV pelado** después de listar los backends, sin nombrar nada. Llegué a escribir que
+ el artefacto estaba roto; con la capa de `json-glib` montada, **las dos corren**. Es la figura de
+ siempre —sellado, presente, reproducible y sin arrancar— y la lección de método es que un arnés al
+ que le falta la clausura de RUNTIME de lo que mide produce un veredicto que parece del artefacto.
+ En las tres imágenes de escritorio esto no muerde porque `json-glib` entra por ser dep declarada
+ del propio portal; en sway no entra nada de esto.
2. **OCHO de las doce se quedan sin portapapeles**: su `arboard` no trae `wayland-data-control`,
intenta X11 y expira (`cadcraft`, `deckcraft`, `designcraft`, `effectcraft`, `filmcraft`,
`lightcraft`, `pdfcraft`, `soundcraft`). Las cuatro que sí lo traen: `gridcraft`, `photocraft`,
diff --git a/scripts/test-portal-ficheros.sh b/scripts/test-portal-ficheros.sh
new file mode 100644
index 00000000..acd0ec56
--- /dev/null
+++ b/scripts/test-portal-ficheros.sh
@@ -0,0 +1,139 @@
+#!/bin/sh
+# test-portal-ficheros.sh <app> — ¿se le puede dar DIÁLOGO DE FICHEROS a la imagen de sway?
+#
+# El 2026-10-13 el arnés de ventana destapó que las apps de esta familia abren, pintan y **no pueden
+# abrir un fichero**: `photocraft` lo dice entero —`file dialog backends: Findings { libdbus: None,
+# file_chooser_portal: Some(false), zenity: false }`—. Esto MIDE la salida propuesta: montar sobre el
+# mismo rootfs de sway el `xdg-desktop-portal` (núcleo) y el backend `-cosmic` —que es Rust+wgpu y no
+# arrastra ni GTK ni Qt— más un `sway-portals.conf` que le diga al núcleo quién implementa qué.
+#
+# ⚠ El `.portal` del backend trae `UseIn=COSMIC`, así que en una sesión `sway` NO lo elegiría solo:
+# esa es exactamente la línea que el fichero de configuración existe para suplir.
+set -e
+APP=${1:-photocraft}
+ROOT=${ROOT:-/opt/takana}
+STORE=${STORE:-$ROOT/store}
+RFS=${RFS:-$ROOT/work/sway-rootfs}
+ALPINE=${ALPINE:-$ROOT/.dev-fs/alpine}
+SAL=${SAL:-$ROOT/work/portal-$APP}
+
+hash_de() { "$ROOT/target/release/takana" --store "$STORE" hash "$1" | tail -1 | sed 's/^b3://'; }
+capa() {
+ d="$STORE/$(cd "$ROOT" && hash_de "$2")-$1"
+ [ -d "$d" ] || { echo "!! falta el artefacto vigente de $1 ($d)" >&2; exit 1; }
+ echo "$d"
+}
+
+GC=$(capa "$APP" "recipes/$APP.toml")
+L18=$(capa llvm18 recipes/llvm18.toml)
+MLP=$(capa mesa-llvmpipe recipes/mesa-llvmpipe.toml)
+XDP=$(capa xdg-desktop-portal recipes/incoming-cosmic/xdg-desktop-portal.toml)
+XDPC=$(capa xdg-desktop-portal-cosmic recipes/incoming-cosmic/xdg-desktop-portal-cosmic.toml)
+# ⚠ Sin estas dos capas el NÚCLEO del portal no corre, y cada versión lo dice distinto: la 1.19.4
+# con `Error relocating … json_parser_get_root: symbol not found` y la 1.18.4 con un SIGSEGV pelado
+# después de cargar los backends. Las dos son la misma causa — `json-glib` no está en el perfil de
+# sway— y es la figura de siempre: artefacto sellado, presente, reproducible, y que no arranca.
+JG=$(capa json-glib recipes/incoming-gnome/json-glib.toml)
+FU=$(capa fuse3 recipes/fuse3.toml)
+echo "capas: alpine + sway-rootfs + llvm18 + mesa-llvmpipe + xdg-desktop-portal + -cosmic + $APP"
+
+rm -rf "$SAL"; mkdir -p "$SAL"
+
+cat > "$SAL/dentro.sh" <<EOF
+#!/bin/sh
+# OJO: este heredoc va SIN comillas (necesita expandir \$APP), asi que NADA de backticks aca dentro
+# ni en los comentarios: en un heredoc sin comillas son SUSTITUCION DE COMANDOS. Ya mordio dos veces.
+export XDG_RUNTIME_DIR=/run/user/0 HOME=/salida/hogar LANG=C XDG_DATA_DIRS=/usr/share
+export XDG_CURRENT_DESKTOP=sway
+export WLR_BACKENDS=headless WLR_HEADLESS_OUTPUTS=1 WLR_RENDERER=pixman
+export LIBGL_ALWAYS_SOFTWARE=1 LIBGL_DRIVERS_PATH=/usr/lib/dri GALLIUM_DRIVER=llvmpipe
+export RUST_BACKTRACE=1 RUST_LOG=info
+mkdir -p "\$XDG_RUNTIME_DIR" /salida/hogar; chmod 700 "\$XDG_RUNTIME_DIR"
+
+# El fichero que hace elegible al backend fuera de COSMIC. Son cuatro líneas y es TODO lo que falta
+# del lado de la configuracion.
+mkdir -p /usr/share/xdg-desktop-portal
+cat > /usr/share/xdg-desktop-portal/sway-portals.conf <<'CONF'
+[preferred]
+default=cosmic
+org.freedesktop.impl.portal.FileChooser=cosmic
+CONF
+
+echo "== inventario ==" >> /salida/log.txt
+for f in /usr/libexec/xdg-desktop-portal /usr/libexec/xdg-desktop-portal-cosmic \\
+ /usr/share/dbus-1/services/org.freedesktop.portal.Desktop.service \\
+ /usr/share/dbus-1/services/org.freedesktop.impl.portal.desktop.cosmic.service; do
+ [ -e "\$f" ] && echo " ✓ \$f" >> /salida/log.txt || echo " ✗ FALTA \$f" >> /salida/log.txt
+done
+ls /usr/lib/libdbus-1.so* >/dev/null 2>&1 && echo " ✓ libdbus-1.so presente" >> /salida/log.txt || echo " ✗ NO hay libdbus-1.so (el corpus sólo saca .a)" >> /salida/log.txt
+
+# Bus de sesión: sin él no hay activación por D-Bus y el portal no existe para nadie.
+eval "\$(dbus-launch --sh-syntax 2>/dev/null)" || {
+ dbus-daemon --session --fork --print-address > /salida/dbusaddr 2>>/salida/log.txt
+ export DBUS_SESSION_BUS_ADDRESS="\$(cat /salida/dbusaddr)"
+}
+echo "== bus: \$DBUS_SESSION_BUS_ADDRESS" >> /salida/log.txt
+dbus-send --session --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames 2>/dev/null | grep -i portal >> /salida/log.txt || echo " (ningún nombre de portal activable)" >> /salida/log.txt
+
+(
+ i=0
+ while [ ! -S /run/user/0/wayland-1 ] && [ \$i -lt 40 ]; do sleep 1; i=\$((i+1)); done
+ export WAYLAND_DISPLAY=wayland-1
+ export SWAYSOCK=\$(ls /run/user/0/sway-ipc.* 2>/dev/null | head -1)
+ /usr/bin/swaybg -c '#1a4b8c' >/dev/null 2>&1 &
+ sleep 2
+ # ⚠ EL PASO QUE FALTABA, y es el que explica el SIGSEGV de la corrida anterior: un proceso
+ # ACTIVADO POR D-BUS no hereda el entorno de la sesión —ni WAYLAND_DISPLAY, ni
+ # XDG_RUNTIME_DIR, ni XDG_CURRENT_DESKTOP— salvo que alguien se lo pase al bus. Eso es
+ # dbus-update-activation-environment, y es exactamente lo que una sesión sway de verdad corre
+ # al arrancar. Sin esto el portal se activa ciego y se cae.
+ /usr/bin/dbus-update-activation-environment --all >> /salida/log.txt 2>&1 || echo " ⚠ dbus-update-activation-environment falló" >> /salida/log.txt
+ # El backend se arranca a mano (en una sesión lo lanza el compositor); el NÚCLEO se deja activar
+ # por D-Bus a propósito, que es el camino real que usa una app.
+ /usr/libexec/xdg-desktop-portal-cosmic >> /salida/portal-cosmic.log 2>&1 &
+ sleep 3
+ dbus-send --session --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames 2>/dev/null | grep -i portal >> /salida/log.txt || echo " (ningún portal EN el bus)" >> /salida/log.txt
+ # ── LA PRUEBA DE VERDAD: pedirle un diálogo al portal y ver si APARECE UNA VENTANA ──────────
+ # La sonda de la app no alcanza: contesta libdbus: None y eso no distingue «no hay portal» de
+ # «no supe preguntar». Acá se llama al método REAL que usaría cualquier app (OpenFile) y la
+ # evidencia es la misma de siempre: un toplevel nuevo en el árbol de sway y una captura con
+ # colores. gdbus y no dbus-send porque el tercer argumento es un diccionario vacío.
+ timeout 25 /usr/bin/gdbus call --session --dest org.freedesktop.portal.Desktop \
+ --object-path /org/freedesktop/portal/desktop \
+ --method org.freedesktop.portal.FileChooser.OpenFile "" "Elegi un fichero" "{}" \
+ >> /salida/openfile.log 2>&1 &
+ sleep 12
+ /usr/bin/swaymsg -t get_tree -r > /salida/arbol.json 2>/dev/null
+ /usr/bin/grim /salida/captura.png 2>/dev/null
+ # Sin esto el bwrap NO vuelve: los dos portales son demonios y --unshare-pid espera a TODOS los
+ # procesos del namespace, no sólo a sway. Costó un timeout de 500 s descubrirlo.
+ pkill -f xdg-desktop-portal 2>/dev/null
+ sleep 1
+ /usr/bin/swaymsg exit 2>/dev/null
+) &
+exec /usr/bin/sway -d > /salida/sway.log 2>&1
+EOF
+
+bwrap \
+ --overlay-src "$ALPINE" --overlay-src "$RFS" \
+ --overlay-src "$L18" --overlay-src "$MLP" \
+ --overlay-src "$JG" --overlay-src "$FU" --overlay-src "$XDP" --overlay-src "$XDPC" --overlay-src "$GC" \
+ --tmp-overlay / \
+ --dev-bind /dev /dev --proc /proc --bind /sys /sys \
+ --tmpfs /tmp --tmpfs /run --unshare-pid \
+ --bind "$SAL" /salida \
+ --uid 0 --gid 0 \
+ --setenv PATH /usr/bin:/bin:/usr/sbin:/sbin \
+ /bin/sh /salida/dentro.sh || echo "(bwrap salió con $?)"
+
+echo "── ¿APARECIÓ el diálogo? ─────────────────────────────────"
+grep -c '"app_id"' "$SAL/arbol.json" 2>/dev/null | sed 's/^/ toplevels en el árbol: /'
+grep -oE '"app_id": "[^"]*"' "$SAL/arbol.json" 2>/dev/null | sort -u | sed 's/^/ /'
+cat "$SAL/openfile.log" 2>/dev/null | sed 's/^/ OpenFile: /'
+echo "── lo que vio la sonda de la app ─────────────────────────"
+grep -i "file dialog backends" "$SAL/app.log" 2>/dev/null || echo " (la app no llegó a sondear)"
+echo "── el bus y los ficheros ─────────────────────────────────"
+cat "$SAL/log.txt" 2>/dev/null | sed 's/^/ /'
+echo "── el portal dijo ────────────────────────────────────────"
+tail -6 "$SAL/portal-core.log" 2>/dev/null | sed 's/^/ core: /'
+tail -4 "$SAL/portal-cosmic.log" 2>/dev/null | sed 's/^/ cosmic: /'