🎥 cosmic: pipewire ARRANCA, y el portal ya figura como cliente suyo
El bloqueo decisivo de ScreenCast no era una receta que faltara: `/usr/bin/pipewire`
estaba en la imagen desde que entró como dep de `cosmic-settings-daemon`, pero NADIE
lo lanzaba. En una distro con systemd de usuario eso lo hace `pipewire.socket`; acá
no hay tal cosa, así que es tarea de `cosmic-start` — el mismo patrón que el bus de
sistema y que login1.
Va justo después de asegurar XDG_RUNTIME_DIR y NO junto al bus de sesión, a
propósito: pipewire no necesita D-Bus para arrancar, sólo el runtime dir donde abre
su socket. Ahí sirve para los dos modos (bare y session) sin duplicar el bloque. Y se
espera EL SOCKET, no el pid: un demonio que muere al instante deja el pid existiendo
un rato, y esperar por él da un OK falso.
Medido dentro de la VM, cada paso una pregunta distinta:
pipewire OK (socket pipewire-0, pid 189) ¿abre socket?
pw-cli info 0 → Core/4, 1.2.7, core.daemon ¿SIRVE a un cliente?
pw-cli ls Factory → "client-node" ¿tiene lo que ScreenCast usa?
pw-cli ls Client → "xdg-desktop-portal" ¡el portal YA está conectado!
La última línea es el veredicto: el frontend del portal aparece como cliente del
demonio, una conexión que antes no podía existir y que es justo la que `Start`
necesita para negociar el nodo. `client-node` es la factory con que el backend lo crea.
Sin regresión, con la cadena entera: `cosmic-screenshot` levanta la UI INTERACTIVA
del portal (región/ventana/pantalla + Capture + Save to Pictures) y el click deja un
PNG de 53.590 bytes. Esa UI no estaba documentada — el registro anterior sólo probaba
la ruta no-interactiva. Escritorio completo: 2414 colores (el panel mutilado da 130).
Lo que sigue faltando son DOS cosas distintas, y ninguna es un one-liner:
· el handshake de punta a punta pide un cliente `ashpd` PERSISTENTE — el portal
asocia la sesión al *sender*, así que CreateSession con un dbus-send y
SelectSources con otro se rechazan por venir de nombres únicos distintos;
· wireplumber, que separa "hay stream de video" de "hay audio". Ni construido está.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Semantic delta
No definition changed: this change is text, documentation or data.
4 files · +94 −9
diff --git a/docs/evidencia/cosmic-escritorio-con-pipewire-2026-08-05.png b/docs/evidencia/cosmic-escritorio-con-pipewire-2026-08-05.png
new file mode 100644
index 00000000..4481724b
Binary files /dev/null and b/docs/evidencia/cosmic-escritorio-con-pipewire-2026-08-05.png differ
diff --git a/docs/evidencia/cosmic-portal-ui-captura-2026-08-05.png b/docs/evidencia/cosmic-portal-ui-captura-2026-08-05.png
new file mode 100644
index 00000000..f80821c8
Binary files /dev/null and b/docs/evidencia/cosmic-portal-ui-captura-2026-08-05.png differ
diff --git a/docs/runbooks/cosmic-desktop.md b/docs/runbooks/cosmic-desktop.md
index 449b831e..0aaa4e2a 100644
--- a/docs/runbooks/cosmic-desktop.md
+++ b/docs/runbooks/cosmic-desktop.md
@@ -966,6 +966,57 @@ aunque se escribiera el cliente `ashpd`, `Start` no tendría con quién negociar
Arreglo: lanzarlo desde `cosmic-start`. (Para AUDIO además falta `wireplumber`, que ni siquiera está
construido — ver la decisión de audio del proyecto.)
+### ✅ HECHO: el demonio arranca y el portal se conecta a él (2026-08-05)
+
+`cosmic-start` lanza `/usr/bin/pipewire` justo después de asegurar `XDG_RUNTIME_DIR`, y espera **el
+socket, no el pid** — la misma disciplina que el compositor, y por el mismo motivo: un demonio que
+muere al instante deja el pid «existiendo» un rato y esperar por él da un OK falso.
+
+Va en ese punto del script a propósito: pipewire **no necesita D-Bus** para arrancar, sólo el
+runtime dir donde abre su socket. Ponerlo ahí lo hace válido para los dos modos —`bare` y
+`session`— sin duplicar el bloque.
+
+Lo medido dentro de la VM, en este orden, porque cada paso responde una pregunta distinta:
+
+```
+== cosmic-qemu :: pipewire OK (socket pipewire-0, pid 189) ← ¿abre socket?
+
+$ pw-cli info 0
+ type: PipeWire:Interface:Core/4 version: "1.2.7" ← ¿SIRVE a un cliente?
+ core.daemon = "true" core.name = "pipewire-0"
+
+$ pw-cli ls Factory | grep factory.name
+ … "client-node" … ← ¿tiene lo que ScreenCast usa?
+
+$ pw-cli ls Client | grep application.name
+ application.name = "pw-cli"
+ application.name = "xdg-desktop-portal" ← ¡EL PORTAL YA ESTÁ CONECTADO!
+```
+
+La última línea es el veredicto. **El frontend del portal aparece como cliente del demonio de
+pipewire** — una conexión que antes no podía existir, y que es exactamente la que `Start` necesita
+para negociar el nodo del stream. `client-node` es la factory con la que el backend lo crea.
+
+**Y sin regresión, medido con la cadena entera**: `cosmic-screenshot` desde `cosmic-term` levanta la
+**UI interactiva del portal** —un toolbar con los tres modos (región, ventana, pantalla), `Capture` y
+`Save to Pictures`—, y el click en `Capture` deja un PNG de 53.590 bytes en
+`/root/Pictures/Screenshots/`. Evidencia: `docs/evidencia/cosmic-portal-ui-captura-2026-08-05.png`.
+Esa UI no estaba documentada: el registro anterior sólo probaba la ruta no-interactiva.
+
+El escritorio completo con pipewire corriendo:
+`docs/evidencia/cosmic-escritorio-con-pipewire-2026-08-05.png` (2414 colores distintos — el panel
+mutilado da 130).
+
+**Lo que sigue faltando, y son dos cosas separadas:**
+
+- **El handshake de ScreenCast de punta a punta** necesita un cliente `ashpd` **persistente**, y no
+ hay atajo: el portal asocia la sesión al *sender* de D-Bus, así que `CreateSession` con un
+ `dbus-send` y `SelectSources` con otro se rechazan por venir de nombres únicos distintos. Un
+ cliente de prueba es una receta nueva, no un one-liner.
+- **`wireplumber`**, que es lo que separa «hay stream de video» de «hay audio». Sin gestor de sesión
+ pipewire arranca y sirve, pero no descubre tarjetas ni enruta. Para ScreenCast alcanza; para sonido
+ no. Ni siquiera está construido.
+
**Y de paso se corrigió un comentario desactualizado de `cosmic-start`** que decía que
`cosmic-settings-daemon` «todavía no se puede construir acá (arrastra pipewire)». Es falso hace
tiempo: ese daemon está sellado y corre. Un comentario viejo manda a buscar un bloqueo que ya no
@@ -973,9 +1024,10 @@ existe, y eso cuesta igual que un bug.
## Lo que falta, en orden
-1. **Arrancar el demonio de pipewire desde `cosmic-start`**, que es lo que destraba ScreenCast de
- verdad; después, un cliente `ashpd` para ejercer el handshake y, de paso, capturar la URI del
- `FileChooser` que `dbus-send` no puede ver.
+1. ~~Arrancar el demonio de pipewire desde `cosmic-start`~~ **✅ hecho (2026-08-05)**, y el portal ya
+ figura como cliente suyo. Queda el **cliente `ashpd` persistente** para ejercer el handshake de
+ ScreenCast y, de paso, capturar la URI del `FileChooser` que `dbus-send` no puede ver — es una
+ receta, no un one-liner, por lo del *sender* de D-Bus explicado arriba. Y `wireplumber` para audio.
2. **Decidir `gvfs`** (ver la corrección de arriba: las piezas ya están selladas, lo que falta es la
decisión sobre la segunda glib). Destrabaría los montajes remotos de cosmic-files y su applet.
3. **Enchufar la tienda a `.swm`**: servir `org.freedesktop.PackageKit` y encender la feature
diff --git a/scripts/cosmic/cosmic-start-qemu.sh b/scripts/cosmic/cosmic-start-qemu.sh
index 91986eff..35d27df8 100755
--- a/scripts/cosmic/cosmic-start-qemu.sh
+++ b/scripts/cosmic/cosmic-start-qemu.sh
@@ -134,6 +134,42 @@ export XDG_DATA_DIRS=/usr/share XDG_CONFIG_DIRS=/etc/xdg
mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR"
[ -w "$XDG_RUNTIME_DIR" ] || say "!! XDG_RUNTIME_DIR se perdió entre el arranque y ahora"
+# ── EL DEMONIO DE PIPEWIRE ──────────────────────────────────────────────────────────────────────
+# Faltaba, y el hueco no se veía: `/usr/bin/pipewire` está en la imagen desde que entró como dep de
+# `cosmic-settings-daemon`, pero NADIE lo arrancaba. En una distro con systemd de usuario lo levanta
+# `pipewire.socket`; acá no hay tal cosa, así que es tarea de este script — el mismo patrón que el
+# bus de sistema y que login1.
+#
+# La consecuencia medida de que faltara: `org.freedesktop.portal.ScreenCast.CreateSession` ACEPTA y
+# devuelve su objeto Request —o sea que la cadena del portal está bien—, pero no puede haber stream,
+# porque el backend negocia el nodo contra un demonio que no existe. Un fallo que se manifiesta como
+# «no pasa nada» tres capas más arriba de la causa.
+#
+# Va ACÁ y no junto al bus de sesión a propósito: pipewire no necesita D-Bus para arrancar, sólo
+# XDG_RUNTIME_DIR (es donde abre su socket). Ponerlo justo después de asegurar ese directorio lo hace
+# válido para los DOS modos —bare y session— sin duplicar el bloque.
+#
+# ⚠ Sin `wireplumber` (el gestor de sesión, que ni siquiera está construido) pipewire arranca y sirve,
+# pero no gestiona dispositivos: no descubre tarjetas ni enruta audio. Para SCREENCAST eso alcanza —
+# el backend del portal crea su propio nodo de video— y para AUDIO no. Son dos preguntas distintas y
+# esta pieza sólo contesta la primera.
+if [ -x /usr/bin/pipewire ]; then
+ /usr/bin/pipewire >/tmp/pipewire.log 2>&1 &
+ PW_PID=$!
+ # El socket, no el pid: misma disciplina que el compositor. `pipewire-0` es el nombre por defecto
+ # (`core.name` en pipewire.conf); si el demonio muere al instante, el pid sigue "existiendo" un
+ # rato y esperar por él daría un OK falso.
+ i=0
+ while [ $i -lt 20 ]; do
+ [ -S "$XDG_RUNTIME_DIR/pipewire-0" ] && { say "pipewire OK (socket pipewire-0, pid $PW_PID)"; break; }
+ alive $PW_PID || { say "!! pipewire MURIÓ antes de abrir el socket:"; dump /tmp/pipewire.log; break; }
+ i=$((i+1)); sleep 0.5
+ done
+ [ -S "$XDG_RUNTIME_DIR/pipewire-0" ] || { say "!! pipewire sin socket en $((i/2))s:"; dump /tmp/pipewire.log; }
+else
+ say "!! sin /usr/bin/pipewire — ScreenCast no va a poder crear stream"
+fi
+
# ── FONDO: color liso, porque la imagen por defecto NO SE PUEDE EMPAQUETAR ────────────────────
# El default de cosmic-bg apunta a `/usr/share/backgrounds/cosmic/orion_nebula_nasa_heic0601a.jpg`,
# que vive en el repo `cosmic-wallpapers`… en **git-lfs**: el tarball de GitHub pesa 20 KB y trae
@@ -180,12 +216,9 @@ fi
# sellado y corre, y `pipewire` está en la imagen. Se deja el rastro porque el comentario viejo
# mandaba a buscar un bloqueo que ya no existe.
#
-# Lo que SÍ falta, y es otra cosa: **nadie ARRANCA el demonio de pipewire**. `/usr/bin/pipewire` y
-# `pipewire-pulse` están instalados, pero `ps ax | grep pipewire` en la sesión no devuelve nada, y
-# este script no lo lanza. Consecuencia medida: el portal acepta
-# `org.freedesktop.portal.ScreenCast.CreateSession` y devuelve su objeto Request, pero **no puede
-# haber stream** porque no hay demonio con quien negociarlo. Sin systemd de usuario, lanzarlo es
-# tarea de este script. (Para audio además falta `wireplumber`, que ni siquiera está construido.)
+# Lo que SÍ faltaba, y era otra cosa, **ya está**: nadie arrancaba el demonio de pipewire. Se
+# corrigió arriba (bloque «EL DEMONIO DE PIPEWIRE», justo después de XDG_RUNTIME_DIR). Queda
+# `wireplumber` sin construir, que es lo que separa «hay stream de video» de «hay audio».
#
# El modo `bare` es la otra mitad que upstream ya soporta (`make install-bare-session`): el
# compositor se lanza SOLO y los clientes se levantan a mano contra su WAYLAND_DISPLAY. Sirve para lo