🎥 ScreenCast: el stream SE CREA — nodo cosmic-screencast vivo en pipewire

La receta de la sonda (b3:af49d32d) y lo que midió. Es la única receta de la campaña
cuyo producto no es una pieza del escritorio sino un instrumento para medirlo: entra
a la imagen porque un handshake de portal sólo se puede ejercer DESDE la sesión, con
bus y compositor vivos, y sale ESTÁTICA (0 NEEDED) porque un instrumento no debe
depender de aquello que mide.

  [1/4] CreateSession  → Response 0, session_handle                    ✓
  [2/4] SelectSources  → Response 0                                   ✓
  [3/4] Start          → el backend ABRE "Share your screen", con
                         miniatura EN VIVO del framebuffer y el output
                         Virtual-1; se elige, se pulsa Share…
                         y NO llega Response en 60s                    ✗

El log del backend da la línea exacta:
  screencast_thread: state-changed 'Connecting' -> 'Paused'

Y `pw-cli ls Node` durante la espera da el veredicto INDEPENDIENTE:
  node.name = "cosmic-screencast"   media.class = "Video/Source"

EL NODO DE VIDEO EXISTE EN EL GRAFO DE PIPEWIRE. La cadena entera —cliente →
frontend → backend → compositor → demonio— funciona hasta crear y negociar el stream.
Lo único que no ocurre es el Response de vuelta tras quedar en `Paused`. Eso es un
lugar muy distinto del de esta mañana ("no hay demonio con quien negociar").

Sospecha para el próximo paso, y es HIPÓTESIS no medición: falta `wireplumber`. Sin
gestor de sesión nadie mueve el nodo de Paused a Streaming y el backend parece
esperarlo. Construirlo la confirma o la mata.

Gotchas que costaron corridas y quedan escritos:
 · matar el backend se lleva puesto al frontend (ambos pierden dueño del bus);
 · el lanzador busca por NOMBRE VISIBLE: `cosmic-term` no matchea, `Terminal` sí —
   escribir el nombre del binario abre otra app;
 · `| head -N` bufferiza y deja la terminal en blanco: parece colgada y está esperando;
 · pkg-config OMITE los -L de dirs "estándar" y zig cc cross NO los busca ⇒
   `-ldbus-1` pelado da "unable to find static system library", que suena a librería
   faltante cuando lo que falta es la ruta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-05 10:28:14 -04:00
co-authored by Claude Opus 5
parent ea210ba3cd
commit ee037912a9
6 changed files with 185 additions and 8 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+90 -8
View File
@@ -1009,14 +1009,95 @@ 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.
- **El handshake de ScreenCast de punta a punta** necesita un cliente **persistente** — resuelto
abajo con `portal-probe`.
- **`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.
## 🔌 portal-probe: la SONDA, y hasta dónde llega ScreenCast de verdad (2026-08-05)
`recipes/incoming-cosmic/portal-probe.toml` → **`b3:af49d32d`**. La única receta de la campaña cuyo
producto no es una pieza del escritorio sino **un instrumento para medirlo**.
### Por qué no alcanzaba `dbus-send`, y por qué tampoco hacía falta `ashpd`
Los portales no son RPC: son **asíncronos en dos tiempos**. Cada método devuelve al instante un
object path de `Request`, y el resultado llega DESPUÉS como señal `Request.Response` sobre ese path.
`dbus-send` ya salió para entonces. Y hay un segundo muro, más duro: **el portal ata la sesión al
SENDER**, así que `CreateSession` con un `dbus-send` y `SelectSources` con otro se rechazan por venir
de nombres únicos distintos. Hace falta UNA conexión viva durante todo el intercambio.
`ashpd` era el camino obvio, pero el árbol fuente de una receta de código propio es el repo del
**propio hammer** (no hay modo `path` en `[source]`: sólo git y tarball) ⇒ habría sumado ~150 crates
al `Cargo.lock` de la herramienta de build por una sonda de diagnóstico. En C sobre `libdbus` cuesta
**cero deps nuevas**, sale **estática (0 NEEDED)** —un instrumento no debe depender de lo que mide— y
sobre todo: **el protocolo se ve**. Una sonda cuyo valor es documentar un handshake no debería
esconderlo tras una biblioteca que lo abstrae.
Detalle que la especificación regala y hay que usar: **el path del `Request` se PREDICE**
(`/…/request/<sender>/<token>`, con `sender` = nombre único sin `:` y con `.`→`_`). Existe justo para
poder suscribirse ANTES de llamar; esperar al path devuelto para recién ahí hacer `AddMatch` es una
carrera real, no teórica.
### Lo medido: el stream SE CREA, y el `Response` es lo único que falta
```
portal-probe version ScreenCast v5 · Screenshot v2 · FileChooser v3 · Settings v2
Access y RemoteDesktop AUSENTES
portal-probe screencast
[1/4] CreateSession → Response 0, session_handle = /…/session/1_108/hammersess ✓
[2/4] SelectSources → Response 0 ✓
[3/4] Start → el backend ABRE su diálogo «Share your screen», con
miniatura EN VIVO del framebuffer y el output `Virtual-1`;
se elige, se pulsa Share… y NO llega Response en 60s ✗
```
El log del backend (`RUST_LOG=debug`) da la línea exacta:
```
xdg_desktop_portal_cosmic::screencast_thread: state-changed 'Connecting' -> 'Paused'
```
Y `pw-cli ls Node` durante la espera da el veredicto independiente:
```
node.name = "cosmic-screencast"
media.class = "Video/Source"
```
**El nodo de video EXISTE en el grafo de pipewire.** O sea: la cadena entera —cliente → frontend →
backend → compositor → demonio de pipewire— funciona hasta **crear y negociar el stream**. Lo único
que no ocurre es que el backend emita el `Response` de vuelta al cliente tras quedar en `Paused`.
Ese es un lugar muy distinto del que ocupaba el frente esta mañana («no hay demonio con quien
negociar»). El siguiente paso es el `Paused → Streaming`, y la sospecha razonable es
**`wireplumber`**: sin gestor de sesión nadie mueve el nodo de `Paused`, y el backend parece
esperarlo. Es una hipótesis, no una medición — construir `wireplumber` la confirma o la mata.
### Dos gotchas que costaron corridas
- **`NameHasOwner` no activa nada.** Los portales son servicios ACTIVABLES: el bus los arranca al
pedirlos. La primera versión de la sonda rechazaba con «no hay frontend de portal en el bus» sobre
un sistema donde el portal andaba perfecto, sólo que dormido. Se arregló con `StartServiceByName`
(commit `ea210ba`) — y distingue «arrancado ahora» de «ya estaba corriendo», que no es lo mismo
cuando lo que se investiga es una carrera.
- **Matar el backend se lleva puesto al frontend.** `kill` sobre `xdg-desktop-portal-cosmic` dejó
también sin dueño a `org.freedesktop.portal.Desktop`. Para diagnosticar con log hay que relanzar el
backend Y forzar la reactivación del frontend.
- **El lanzador busca por NOMBRE VISIBLE, no por binario**: `cosmic-term` no matchea nada,
`Terminal` abre COSMIC Terminal. Escribir el nombre del ejecutable abre otra cosa (settings).
- Y uno de shell, al automatizar por QMP: **`| head -N` bufferiza** y deja la terminal en blanco hasta
que el programa termina. Parece que la sonda colgó cuando en realidad está esperando.
### El `-L/usr/lib` que pkg-config no da
`pkg-config` **omite los `-L` de directorios que considera estándar del sistema**, asumiendo que el
linker ya los busca. Cierto para un `cc` nativo; **falso para `zig cc` cruzando a
`x86_64-linux-musl`**, que trae sus propias rutas. Resultado: `--libs --static` devuelve `-ldbus-1`
pelado y zig responde `unable to find static system library 'dbus-1' … searched paths: none` — un
error que suena a «falta la librería» cuando lo que falta es la ruta.
**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
@@ -1024,10 +1105,11 @@ existe, y eso cuesta igual que un bug.
## Lo que falta, en orden
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.
1. ~~Arrancar el demonio de pipewire~~ ✅ y ~~el cliente persistente~~ ✅ (`portal-probe`, `b3:af49d32d`).
**Lo que queda es `wireplumber`**: el stream de ScreenCast se crea y llega a `Paused`, con nodo
`cosmic-screencast` visible en pipewire, pero nadie lo pasa a `Streaming` y el backend no emite el
`Response`. La hipótesis es que falta el gestor de sesión — construirlo la confirma o la mata.
Falta también correr `portal-probe filechooser`, que ya puede capturar la URI.
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
+4
View File
@@ -133,6 +133,10 @@ paquetes = [
# Access/FileChooser/Screenshot/Settings/ScreenCast. Sin ellos no hay captura, ni selector de
# ficheros de portal, ni compartir pantalla.
"xdg-desktop-portal", "xdg-desktop-portal-cosmic",
# Y la SONDA con que se mide esa cadena. No es escritorio: es el instrumento. Se lista como raíz
# porque nadie la declara como dep y sin ella el perfil no incluiría con qué verificarse a sí
# mismo — un handshake de portal sólo se puede ejercer DESDE la sesión, con bus y compositor vivos.
"portal-probe",
# Datos, no binarios: el tema de iconos (y el hicolor del que hereda), las tablas de xkb y una
# tipografía. Ninguno es dep de build de nadie y sin ellos el escritorio arranca roto — sin puntero,
# sin teclado o con tofu.