tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1378Shell 129Rust 79Markdown 732463 Definiciones

🎥 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>

ee037912a98475e263cfa4d5abe16b8a39945b25 · por sergio · (hace 2 meses) · padre ea210ba3c · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

6 archivos · +185 −8

diff --git a/docs/evidencia/cosmic-screencast-dialogo-2026-08-05.png b/docs/evidencia/cosmic-screencast-dialogo-2026-08-05.png
new file mode 100644
index 00000000..a40df6f1
Binary files /dev/null and b/docs/evidencia/cosmic-screencast-dialogo-2026-08-05.png differ
diff --git a/docs/evidencia/cosmic-screencast-nodo-pipewire-2026-08-05.png b/docs/evidencia/cosmic-screencast-nodo-pipewire-2026-08-05.png
new file mode 100644
index 00000000..36f12110
Binary files /dev/null and b/docs/evidencia/cosmic-screencast-nodo-pipewire-2026-08-05.png differ
diff --git a/docs/runbooks/cosmic-desktop.md b/docs/runbooks/cosmic-desktop.md
index 0aaa4e2a..5dab1c7d 100644
--- a/docs/runbooks/cosmic-desktop.md
+++ b/docs/runbooks/cosmic-desktop.md
@@ -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
diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index 36ce1a3c..d59ce729 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -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.
diff --git a/recipes/incoming-cosmic/portal-probe.toml b/recipes/incoming-cosmic/portal-probe.toml
new file mode 100644
index 00000000..60f04466
--- /dev/null
+++ b/recipes/incoming-cosmic/portal-probe.toml
@@ -0,0 +1,86 @@
+# portal-probe — la SONDA del portal XDG. La única receta de la campaña cuyo producto no es una
+# pieza del escritorio sino un INSTRUMENTO para medirlo.
+#
+# ── QUÉ MIDE, Y POR QUÉ NINGUNA HERRAMIENTA EXISTENTE SIRVE ─────────────────────────────────────
+# Los portales no son RPC: son un protocolo asíncrono en dos tiempos. Cada método devuelve al
+# instante un object path de `Request`, y el resultado de verdad llega DESPUÉS como señal
+# `org.freedesktop.portal.Request.Response` sobre ese path. `dbus-send` ya salió para entonces ⇒
+# devuelve el path y nunca el resultado. Eso es lo que impidió capturar la URI del `FileChooser`.
+#
+# Y hay un segundo muro, más duro: **el portal ata la sesión al SENDER**. `CreateSession` con un
+# `dbus-send` y `SelectSources` con otro llegan desde nombres únicos DISTINTOS y el portal rechaza
+# el segundo. No hay forma de encadenar el handshake desde el shell; hace falta UNA conexión que
+# viva todo el intercambio. Por eso esto es un binario y no un script.
+#
+# ── POR QUÉ EN C Y NO CON `ashpd` ───────────────────────────────────────────────────────────────
+# `ashpd` es el cliente de portales del ecosistema Rust —es lo que usan `cosmic-screenshot` y
+# `cosmic-files`— y era el camino obvio. Pero el árbol fuente de esta receta es el repo del PROPIO
+# hammer (no hay modo `path` en `[source]`: sólo git y tarball), así que usar ashpd significaría
+# sumar ~150 crates al `Cargo.lock` de la herramienta de build por una sonda de diagnóstico.
+#
+# `libdbus` ya está en el corpus, cuesta cero deps nuevas, y `dbus` provee `libdbus-1.a` ⇒ la sonda
+# sale **estática, sin un solo NEEDED**, que es justo lo que uno quiere de un instrumento: que no
+# dependa de nada de aquello que va a medir. Y sobre todo: en C el protocolo SE VE. Una sonda cuyo
+# valor es documentar un handshake no debería esconderlo tras una biblioteca que lo abstrae.
+#
+# ── EL VENDOREO QUE SE PAGA IGUAL ───────────────────────────────────────────────────────────────
+# El árbol fuente es el repo hammer entero, cuya raíz tiene `Cargo.toml` ⇒ `detect_build_system`
+# dice Cargo y el fetch vendorea el workspace. No se puede apagar (`cargo_vendor` sólo FUERZA, no
+# desactiva). Es un costo de tiempo en el fetch, no de corrección: la fase `compile` de acá abajo
+# ignora cargo por completo y llama a `zig cc` sobre un único `.c`. Mismo peaje que paga `hammerd`.
+name    = "portal-probe"
+version = "0.1.0"
+
+[source]
+# El repo del propio hammer. El `commit` es el identificador inmutable (la URL es sólo locator y no
+# entra al hash). Subir el commit cuando la sonda avance — y sólo entonces: fijar el commit es lo
+# que hace que un instrumento de medición sea él mismo reproducible.
+repo   = "ssh://gitea@git.gioser.net:2345/sergio/hammer.git"
+commit = "ea210ba3cd40f20325eae1b7023db280814e734d"
+
+[build]
+compiler = "zig-cc"
+target   = "x86_64-linux-musl"
+# Estático de verdad, y acá SÍ es la decisión correcta en vez de la coherencia con la suite: el
+# resto de los clientes van dinámicos porque `wayland-client` hace `dlopen` (ver el runbook). Esta
+# sonda no toca wayland, y un instrumento que arrastra NEEDED puede fallar por una razón que no es
+# la que está midiendo.
+link     = "static"
+
+[build.phases]
+# Sin configure: es un fichero.
+configure = "true"
+
+# `zig cc` directo. Los flags de dbus salen de su `.pc`, que vive en el artefacto del store y el
+# sandbox proyecta en /usr — de ahí el PKG_CONFIG_PATH. Los dos `-I` de dbus son SIEMPRE dos y no
+# uno: `dbus-arch-deps.h` es generado y vive bajo `lib/dbus-1.0/include`, no junto al resto de los
+# headers. Un `-I` solo compila hasta que algo incluye `dbus.h` de verdad.
+#
+# ⚠ EL `-L/usr/lib` HAY QUE PONERLO A MANO, y el motivo es un choque de supuestos entre dos
+# herramientas: **pkg-config OMITE los `-L` de directorios que considera estándar del sistema**
+# (`/usr/lib` lo es), 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 y no da nada por sentado.
+# 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 la librería está ahí y lo que falta es la ruta.
+compile = '''
+export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig
+CF=$(pkg-config --cflags dbus-1)
+LF=$(pkg-config --libs --static dbus-1)
+echo "cflags: $CF"
+echo "libs  : $LF"
+zig cc -target x86_64-linux-musl -static -O2 -Wall -Wextra \
+  -o portal-probe tools/portal-probe/portal-probe.c $CF -L/usr/lib $LF
+'''
+
+install = '''
+mkdir -p /out/usr/bin
+cp portal-probe /out/usr/bin/portal-probe
+chmod 755 /out/usr/bin/portal-probe
+'''
+
+[deps]
+# `dbus` por la librería y los headers; `pkgconf` para que el `.pc` se pueda leer. Nada más: la
+# sonda no dibuja, no abre ventana y no habla wayland — igual que `cosmic-screenshot`, que también
+# es un cliente puro de portal.
+build = ["dbus", "pkgconf"]
diff --git a/scripts/cosmic/hydrate-cosmic.sh b/scripts/cosmic/hydrate-cosmic.sh
index 30b90616..2e6ce37e 100755
--- a/scripts/cosmic/hydrate-cosmic.sh
+++ b/scripts/cosmic/hydrate-cosmic.sh
@@ -79,6 +79,11 @@ else
     # por D-Bus en runtime, así que el grafo no los ve — igual que los componentes de la sesión.
     recipes/incoming-cosmic/xdg-desktop-portal.toml
     recipes/incoming-cosmic/xdg-desktop-portal-cosmic.toml
+    # …y la SONDA con la que se mide esa cadena. No es parte del escritorio: es el instrumento.
+    # Entra a la imagen a propósito —un handshake de portal sólo se puede ejercer DESDE la sesión,
+    # con el bus y el compositor vivos— y sale estática, sin un NEEDED, justamente para que nunca
+    # falle por algo del sistema que está midiendo.
+    recipes/incoming-cosmic/portal-probe.toml
     recipes/incoming-cosmic/cosmic-app-library.toml
     recipes/incoming-cosmic/cosmic-workspaces-epoch.toml
     # Ni binario ni librería: DATOS. El tema de iconos de COSMIC — sin él el escritorio no se ve