# Runbook — COSMIC, el cuarto escritorio Estado al 2026-08-03: **LA SESIÓN COSMIC ARRANCA ENTERA.** `cosmic-session` —el camino de producción, no el andamio— levanta la cadena completa sobre el kernel de hammer y arje-zero como PID1: ``` cosmic-comp → cosmic-settings-daemon → cosmic-notifications → cosmic-panel → cosmic-osd → cosmic-bg ``` Los que todavía no existen (`cosmic-app-library`, `cosmic-launcher`, `cosmic-workspaces`, `cosmic-greeter`) sólo dejan una línea de `error!` y **la sesión sigue viva** — que es exactamente lo que la tabla de `.expect` de más abajo predijo, verificado en vez de supuesto. ![sesión COSMIC en QEMU](../evidencia/cosmic-session-qemu-2026-08-03.png) Y el paso anterior: `cosmic-comp` arranca sobre el kernel de hammer con arje-zero como PID1, toma el DRM, expone `wayland-1` y presenta frames; y `cosmic-bg` se conecta, entrega su buffer y el fondo aparece **con el color exacto que se le configuró** — `(0.13,0.29,0.53)` → `(33,74,135)`. O sea que la cadena cliente→compositor→KMS funciona de punta a punta. Este documento se escribe mientras se hace, no después. ![cosmic-bg pintando en QEMU](../evidencia/cosmic-bg-qemu-2026-08-03.png) Que el color sea **el que se pidió** y no uno cualquiera es lo que hace de esto una medición y no una impresión: descarta que se esté viendo un buffer sin inicializar o el borrado del compositor. El paso anterior —el compositor solo, gris `(39,41,42)` y el cursor, 134 colores— quedó en [`cosmic-comp-qemu-2026-08-03.png`](../evidencia/cosmic-comp-qemu-2026-08-03.png), porque la diferencia entre las dos imágenes es exactamente la pregunta que el modo `bare` separa. COSMIC es el escritorio de System76, en Rust sobre **smithay** (compositor) e **iced/libcosmic** (clientes). Es el cuarto de hammer, tras [mirada](mirada-usb-nvidia.md), [KDE Plasma 6](kde-qemu-desktop.md) y [GNOME](gnome-qemu-desktop.md). ## Por qué NO se parece a los dos anteriores KDE y GNOME costaron lo que costaron por la misma razón: una torre de C con Qt o glib/GObject/introspección debajo, donde cada capa se paga entera antes de ver un píxel. COSMIC no tiene esa torre. La primera receta —`cosmic-session`— salió **a la primera y sin una sola dep en C**: ELF estático, cero `NEEDED`. Lo que sí hereda es lo que ya está pago: | lo que COSMIC necesita | de dónde sale | costo | |---|---|---| | las deps en C de smithay (drm, gbm, seat, input, udev, xkb, pixman) | `mirada-compositor`, que ya es un compositor smithay de este lab | **cero** — misma lista | | `libdisplay-info` + `hwdata` | copiadas byte a byte de la cola GNOME | **cero** — mismo hash, artefacto ya sellado | | `bash`, `dbus-run-session`, `hicolor-icon-theme`, mesa-llvmpipe | corpus | cero | | el andamiaje de arranque en QEMU (`run-qemu-desktop.sh`, base rootfs, console-getty, screendump) | campañas KDE/GNOME | cero | La deuda propia que sí trae, y que ninguna de las otras campañas tocó, es la del **unwinder de la std de rustc** cuando el enlace no es estático — la misma que ya está documentada para las recetas Cargo del corpus. ## El pin es de la SUITE, no de cada paquete COSMIC versiona **todos** sus repos en lockstep con el tag `epoch-N`. Comprobado repo por repo: los doce componentes están en `epoch-1.5.0`. **No mezclar epochs entre piezas.** Comparten protocolos propios (`cosmic-protocols`) y el formato de configuración (`cosmic-config`); un desajuste ahí no da error de compilación, da un escritorio que arranca y no se entiende consigo mismo — que es mucho más caro de diagnosticar. ## La cola `recipes/incoming-cosmic/`, aislada (otro agente comparte el repo; nunca `git add -A`). Las deps se resuelven **hermano → padre**: desde `incoming-cosmic/` se ven las recetas de `recipes/`, pero **no** las de `incoming-gnome/` ni `incoming-kde/`. Por eso `libdisplay-info` y `hwdata` entran copiadas. **Copiar es gratis sólo si TODA la clausura transitiva resuelve a lo mismo.** El ArtifactHash no depende de la ruta de la receta, así que un fichero idéntico *cuyas deps resuelven igual* da el mismo hash ⇒ cache hit sobre el artefacto que ya existe. Se cumplió con `libdisplay-info`, `hwdata` y `nasm`, y se verificó con `hammer hash` **antes** de copiar (`b3:260f0519`, `b3:bdc36cf9`, `b3:33383070`), no después. Dónde deja de cumplirse —y por qué importa— está en la sección de `cosmic-settings-daemon`, más abajo. ## Qué levanta la sesión, y por lo tanto el orden de ataque `cosmic-session/src/main.rs` es la lista autoritativa —el mismo método que cerró la capa JS de GNOME: *leé el fichero donde el programa declara lo que arranca, no lo descubras de a uno*: ``` cosmic-comp → cosmic-notifications · cosmic-panel · cosmic-app-library → cosmic-launcher · cosmic-workspaces · cosmic-osd · cosmic-bg · cosmic-idle ``` Más `cosmic-settings-daemon` y `xdg-desktop-portal-cosmic` fuera de esa lista. | pieza | estado | hash | |---|---|---| | `cosmic-session` | ✅ sellado | `b3:81f02351` | | `cosmic-icons` | ✅ sellado (671 SVGs) | `b3:94d06f77` | | `dav1d` 1.5.4 | ✅ sellado (dep arrastrada, ver gotcha 8) | `b3:aab7a874` | | **`cosmic-comp`** | ✅ **SELLADO — el gate** (filtra `ScreenReader`, ver atajos) | `b3:e871eee4` | | `cosmic-bg` (dinámico, PINTA) | ✅ sellado | `b3:b2cfc410` | | `cosmic-idle` (dinámico) | ✅ sellado | `b3:e1806acc` | | `pulseaudio` · `pipewire` (cadena del settings-daemon) | ✅ sellados | `b3:e1ff4736` · `b3:338d1d8c` | | `cosmic-panel` | ✅ sellado | `b3:ac5e48c5` | | `cosmic-osd` | ✅ sellado | `b3:f15cb9c1` | | `cosmic-settings-daemon` | ✅ sellado (filtra `ScreenReader`, ver atajos) | `b3:642919a5` | | `cosmic-notifications` | ✅ sellado | `b3:3e2a5478` | | `cosmic-launcher` | ✅ sellado | `b3:c17b4956` | | `cosmic-app-library` | ✅ sellado | `b3:b2a163e7` | | `cosmic-workspaces-epoch` | ✅ sellado (necesitó `mesa`, ver gotcha 11) | `b3:a031249e` | | **`cosmic-applets`** (19 binarios en uno) | ✅ **sellado** (necesitó `dbus-shared`, ver gotcha 10) | `b3:3e53a325` | | `pop-launcher` 1.2.7 (otro repo, ver abajo) | ✅ sellado a la primera | `b3:64a5cbaa` | | `gmp` 6.3.0 · `mpfr` 4.2.2 (cadena de la calculadora) | ✅ sellados a la primera | `b3:d99a0a5d` · `b3:13982d20` | | `libqalculate` 5.12.0 (`qalc`) | ✅ sellado — la calculadora del lanzador ANDA (ver abajo) | `b3:edb8d14d` | | **`cosmic-term`** 1.5.0 — la 1ª APLICACIÓN | ✅ **sellada a la primera** (arrastra `cosmic-files` y `wgpu`) | `b3:d0ea1c1e` | | `cosmic-files` 1.5.0 — la 2ª aplicación | ✅ sellada, **sin `gvfs` ni `wgpu`** (ver abajo) | `b3:48d2b072` | | `cosmic-settings` 1.5.0 — la 3ª aplicación | ✅ sellada (necesitó el parche de la doble barra) | `b3:716dac53` | **La cola está COMPLETA: 33 recetas, `escritorio-cosmic` 71/71.** **El mínimo viable de una sesión son cuatro**: `cosmic-comp` + `cosmic-settings-daemon` + `cosmic-notifications` + `cosmic-panel` (los tres `.expect` de la tabla de arriba). **El orden no es el de la lista de arranque.** `cosmic-bg` va antes que el panel a propósito: habla Wayland con `smithay-client-toolkit` y **no toca libcosmic ni iced**, mientras que panel, launcher, osd y workspaces son todos clientes de iced. Con el fondo sellado, «pinta el fondo pero no el panel» separa la capa de UI del transporte; sin él, las dos preguntas llegan juntas. ### `cosmic-settings-daemon`: por qué queda al final y NO se resuelve copiando Es el único con cadena propia. Su workspace incluye `audio-server` como dep de path **no opcional**, que arrastra `cosmic-pipewire` → `pipewire-sys` → `libpipewire-0.3` por pkg-config. Y `pipewire` hoy vive sólo en las colas GNOME y KDE. **Acá el truco de copiar deja de ser gratis, y eso corrige lo que dice más arriba.** La regla completa es: *copiar una receta entre colas es gratis sólo si **toda su clausura transitiva** resuelve a lo mismo.* Se cumplió con `libdisplay-info`, `hwdata` y `nasm` —sus deps están todas en el corpus—, y **no** se cumple con pipewire: de sus catorce deps, cinco viven sólo en colas ajenas (`dbus-shared`, `alsa-lib`, `libsndfile`, `pulseaudio`, `zlib-shared`) y —lo decisivo— **`glib` resolvería distinto**. Desde `incoming-gnome`, `glib` es la sombra de esa cola (`b3:f6ccdf98`); desde `incoming-cosmic` sería la del corpus (`b3:93d2cad0`). Son recetas distintas, no la misma en dos lugares. O sea que copiar produciría un **segundo pipewire** construido contra otra glib, arrastrando su cadena entera. Y eso no es sólo caro: es exactamente el cuadro que la campaña GNOME ya midió y evitó —dos glib distintas con 370 rutas solapadas y dos registros de GType en un proceso—. Antes de tocar nada se midió con `yupana radio` (la puerta única, nunca `grep`): `pipewire` tiene radio 1 en GNOME y 2 en KDE, pero `glib` del corpus tiene **21 dependientes transitivos y afecta la imagen escritorio-gnome**. **Y después se midió el costo real, que resultó mucho menor que la estimación.** Copiar pipewire a `incoming-cosmic` necesita **cuatro** recetas hermanas —`alsa-lib`, `dbus-shared`, `libsndfile`, `pulseaudio`—, no las seis contadas a ojo, y **tres de ellas dan hash idéntico y ya están selladas**. Sólo `pulseaudio` y `pipewire` hay que construir. Eso refina la advertencia de arriba en el punto que importa: **el peligro de las dos glib es mezclarlas en UNA IMAGEN**, y la imagen COSMIC tiene una sola. Acá el costo es de builds, no de corrección. Lo que sigue vigente es no promover a ciegas entre colas. Gotcha del copiado, que cuesta un minuto de confusión: `[source] patches` se resuelve **relativo a la receta**, así que copiar el `.toml` sin su `.patch` da «no pude leer patch» y el hash ni siquiera se calcula. ## 🚪 EL GATE PASADO: cosmic-comp compila (2026-08-03) `b3:b8abc52f`, 51 min 39 s de cargo con `CARGO_BUILD_JOBS=1` y `lto = "fat"`. **A la primera**, salvo el `--bin` del gotcha 1. Sin un solo parche a la fuente. Lo que dice el enlace es la parte que vale, porque es la medición y no la impresión: ``` NEEDED: libdisplay-info.so.2 · libgbm.so.1 · libseat.so.1 · libudev.so.1 libinput.so.10 · libpixman-1.so.0 · libxkbcommon.so.0 · libc.so ``` Ocho, y las siete primeras son **exactamente** los backends de smithay que la receta declara. La octava es musl. **No hay `libgcc_s`** — o sea que el compositor no arrastra la deuda del unwinder que sí tienen varias recetas Cargo del corpus, porque acá el enlace es dinámico contra musl y no hay `+crt-static` que forzar. Y **no aparece nada que no se haya declarado**: la clausura de build y la de runtime coinciden, que es la propiedad que uno quiere y casi nunca se cumple gratis. Que un compositor Wayland completo —DRM, GBM, EGL, libinput, seat, Vulkan, XWayland, un renderer multi-GPU y una capa de UI en iced— cierre con ocho librerías compartidas y ningún parche es el resultado más limpio de las cuatro campañas de escritorio. ## 🖥 EL PRIMER ARRANQUE: qué falló y qué no (2026-08-03) Tres imágenes y tres causas distintas, todas medidas. Vale dejarlas porque **ninguna estaba en el camino que uno anticipa**, y las tres se manifestaron a varias capas de distancia de su causa. **1. `cosmic-session` no es opcional en el sentido que yo asumí — y `cosmic-settings-daemon` tampoco.** El compositor arrancó perfecto y le devolvió a la sesión su `WAYLAND_DISPLAY=wayland-1`; el gestor entonces intentó lanzar `cosmic-settings-daemon`, no lo encontró, y **panickeó**: ``` INFO cosmic_session: got environmental variables from cosmic-comp: [("WAYLAND_DISPLAY", "wayland-1")] ERROR panic: 'failed to start settings daemon: NotFound' src/main.rs:255 ``` Es un `.expect()`, sin feature que lo apague. **Y NO es el único — son TRES**, y esto corrige lo que esta misma sección afirmaba hace unas horas: | línea | componente | qué pasa si falta | |---|---|---| | `main.rs:255` | `cosmic-settings-daemon` | panic | | `main.rs:306` | `cosmic-notifications` | panic | | `main.rs:325` | `cosmic-panel` | panic | El resto (app-library, launcher, workspaces, osd, bg, idle) va por `start_component()`, que ante un fallo sólo hace `error!("failed to start …")` y sigue (`main.rs:558`). O sea que **el mínimo viable de una sesión COSMIC son cuatro binarios**: compositor + esos tres. **Cómo se equivocó el diagnóstico, que es la parte reusable**: leí `start_component()`, vi que sólo logueaba, y generalicé a «todos menos el settings-daemon». Pero notifications y panel **no pasan por esa función** — tienen su propio `.expect()` unas líneas antes. Buscar la función que lanza no es lo mismo que buscar los `\.expect(` del fichero; **grepear la construcción que mata cuesta lo mismo y no generaliza de más**. **Esto corrige lo que decía la sección de cosmic-settings-daemon**: no es que sin él se pierdan el brillo y el tema, es que **no hay sesión**. Queda por eso el modo `bare` en `cosmic-start` —el camino que upstream ya soporta como `install-bare-session`—: se lanza el compositor solo y los clientes a mano contra su WAYLAND_DISPLAY. Separa «¿compone?» de «¿arranca la sesión?», que son dos preguntas y estaban viniendo juntas. **2. La pantalla negra era `libz.so.1`.** El corpus trae `zlib` estática, que le alcanza a todo el cierre de COSMIC. Pero `kms_swrast_dri.so` es un `.so` que mesa abre con `dlopen` y NEEDea `libz.so.1`: ``` MESA-LOADER: failed to open kms_swrast: Error loading shared library libz.so.1 WARN cosmic_comp::backend::kms: Failed to add device /dev/dri/card0: Failed to initialize GBM device ``` Sin driver de software no hay GBM, y **el compositor arranca igual y presenta una pantalla negra**. La causa está a tres capas del síntoma. Se arregla agregando `zlib-shared` como raíz del hidratador. **3. Los clientes NO pueden ser estáticos.** `cosmic-bg` moría con «The wayland library could not be loaded» **teniendo `libwayland-client.so.0` en el rootfs**. `wayland-client` entra con la feature `dlopen`: carga libwayland en runtime en vez de enlazarla, y **un binario musl estático no tiene dlopen funcional**. No faltaba la librería: faltaba poder abrirla. Todos los clientes pasan a `link = "dynamic"`. **Lo que NO falló, y conviene decirlo:** el compositor no necesitó un solo parche, ni una perilla de KMS, ni forzar modeset legacy. Las cuatro perillas de render por software heredadas de KDE y GNOME (`GBM_ALWAYS_SOFTWARE`, `kms_swrast`, `LP_NUM_THREADS=1`) alcanzaron. GNOME costó cinco muros para llegar a este punto; COSMIC costó tres, y ninguno era del compositor. ### Dos carreras con /run, la misma causa `dbus-daemon --system` falló con «Failed to bind socket /run/dbus/system_bus_socket: No such file or directory» **aunque el directorio se creaba al principio del script**, y dbus-run-session se quejó por su lado de que `XDG_RUNTIME_DIR` no existía. Entre el arranque del getty y el uso pasan varios segundos en los que arje-zero sigue montando, y un tmpfs sobre `/run` se lleva puesto lo que hubiera. **Crear justo antes de usar** es la única forma de no depender de ese orden; comprobar al principio y usar al final es exactamente cómo se cuela una carrera — la misma forma del bug de PolicyKit1 que se cerró hoy en GNOME. ## Gotchas medidos (no previstos) 1. **`cargo rustc --` exige UN target.** La fase compile del lab pasa flags tras el `--`, y cualquier paquete con binario + lib + ejemplos corta con «extra arguments to `rustc` can only be passed to one target». Se arregla con `flags = ["--bin", ""]`. Le pasa a cosmic-comp y a cosmic-bg (workspace con `config/`), y le va a pasar a casi todos: **ponelo desde el principio.** 2. **`start-cosmic` es bash de verdad, no sh.** Usa `mapfile`, `[[ ]]` y expansión indirecta `${!var}`; con el busybox del rootfs no arranca. `bash` ya está en el corpus, pero **tiene que entrar en la imagen** — y no lo pide ningún `[deps]`, así que no lo va a traer nadie por accidente. Lo mismo con `dbus-run-session` (de `dbus`), que es lo que el script exec-uta al final. 3. **`rust-toolchain.toml` con `channel = "1.93"` es inerte acá** — es una perilla de rustup, y el sandbox tiene el rustc de Alpine (1.96) pelado. Conviene saberlo antes de «arreglarlo». 4. **`cosmic.desktop` existe DOS veces** (en cosmic-session y en cosmic-comp) con contenido distinto y en la misma ruta. El de cosmic-comp es el de la «bare session» y apunta a `/usr/bin/cosmic-service`, que vive en la rama de systemd que acá no va. Se instala **sólo** el de cosmic-session; si entraran los dos, quién gana depende del orden de hidratación y el que pierde es el bueno. 5. **`libsystemd` no es systemd.** La feature `default = ["systemd"]` de cosmic-comp trae el crate `libsystemd`, que es una **reimplementación en Rust** del protocolo, no un binding a la C. Y `logind` habla `org.freedesktop.login1`, que acá lo sirve `arje-logind-compat`. No hay que apagar nada: el nombre de la feature es el del protocolo, no el del programa que lo contesta. 6. **Las features de smithay que asustan no cuestan deps de build**: `backend_vulkan` (crate `ash`), `backend_x11` (`x11rb` con `dl-libxcb`) y `xwayland` cargan todo por **dlopen**. Son deuda de RUNTIME —libxcb y vulkan-loader viven hoy sólo en la cola KDE—, no un muro de compilación. 7. **Todo cliente de la suite lleva las MISMAS CUATRO deps: `libxkbcommon`, `pkgconf`, `libudev-zero`, `libinput`** — incluso las piezas sin interfaz. Las tres primeras se descubrieron de a una, un build por dep, hasta que la línea de enlace las mostró juntas: ``` -Wl,-Bstatic -ludev -linput -lxkbcommon … ``` **El error dice la que falta primero; el COMANDO dice todas.** Leerlo entero la primera vez habría ahorrado tres builds. Los tres llegan por la cadena de libcosmic —enumeración de monitores y de dispositivos de entrada—, no por nada que el cliente haga a la vista. Y el camino hasta ahí fue de razonamientos buenos y falsos, que es lo que vale dejar escrito: - `cosmic-bg` fue sin `[deps]` porque «habla el protocolo en Rust y no toca C». Reventó en el `build.rs` de `smithay-client-toolkit` pidiendo `xkbcommon.pc`: **sctk ENLAZA libxkbcommon**; el que la dlopea es `winit`, que es otra capa. Hablar Wayland en Rust dice cómo viaja el byte, no con qué se interpreta un teclado. - `cosmic-idle` fue sin `[deps]` porque *ese* argumento ya no aplicaba —no usa sctk— y un demonio de inactividad no interpreta teclas. Reventó en el ENLACE con `-lxkbcommon`, arrastrado por **`cosmic-settings-config`**: de ahí sale la tabla de atajos y la usa **todo** componente de la suite. La dep no viene de lo que el paquete hace, sino de la librería de configuración común. La lección de método vale más que la dep: **la dep no se deduce de la función del paquete.** Está escrita en el `build.rs` que panickea o en la línea de enlace, y las dos veces el error dijo exactamente quién y por qué. El `link = "static"` sobrevive porque el artefacto de libxkbcommon trae `.a` además de `.so`. 8. **Una feature apagada en el paquete puede estar prendida por otro.** `cosmic-bg` declara `image` con `default-features = false` y sin AVIF, y aun así compila `dav1d-sys`: **las features de cargo se UNIFICAN en el grafo**. Se resolvió trayendo `dav1d` (b3:aab7a874) en vez de parchear el Cargo.toml de upstream — si el grafo dice que sabe decodificar AVIF, que lo sepa de verdad. Mismo modo de falla que un `.pc Requires` arrastrando una dep que nadie declaró. 9. **`cosmic-wallpapers` no se puede empaquetar del tarball: está en git-lfs.** El default de cosmic-bg apunta a `/usr/share/backgrounds/cosmic/orion_nebula_nasa_heic0601a.jpg`, y el archivo de GitHub de ese repo pesa **20 KB** — son punteros LFS, no imágenes. Una receta escrita del modo normal produciría un paquete de ficheros de texto que además *parecería* correcto: se instalarían con el nombre bueno en la ruta buena. Mientras tanto `cosmic-start` escribe la config de USUARIO con una fuente `Color`, que cosmic-bg soporta de fábrica — y va en el lanzador, no en el artefacto, porque es política de la imagen de prueba y no algo que el paquete prometa. 10. **`build.rs` contento ≠ linker contento: `cosmic-applets` necesita la libdbus COMPARTIDA.** Los applets de red, bluetooth y status-area hablan D-Bus por `libdbus-sys` (FFI a la C, no zbus como el resto de la suite). Declarar `dbus` a secas alcanza para que su `build.rs` pase —le basta el `.pc`, que el paquete del daemon trae— y el fallo aparece **una hora después, en el enlace final del multiplexor**: `unable to find dynamic system library 'dbus-1'`. El corpus sólo empaqueta `libdbus-1.a`; el `.so` vive en `dbus-shared`. Son dos preguntas distintas y las contestan dos herramientas distintas: pkg-config dice si la dep *existe*, el linker si hay algo que *enlazar*. Era la única receta de la cola que pedía la estática. 11. **`cosmic-workspaces` necesita `mesa`, y la dep sale de lo que el programa MUESTRA.** La vista de espacios de trabajo dibuja miniaturas vivas de las ventanas; el compositor se las pasa como DMA-BUF por `zcosmic-toplevel-info` y el cliente importa el fd con GBM ⇒ `-lgbm`. Es el único cliente de la suite que toca el pipeline gráfico además del compositor, así que el juego fijo de cuatro (`libxkbcommon`, `pkgconf`, `libudev-zero`, `libinput`) no alcanza. Corolario del gotcha de siempre: la dep no se deduce de la familia del paquete, se lee de lo que hace. 12. **DOS GPUs en QEMU = el panel dibuja en la pantalla que no mirás.** `-device virtio-gpu-pci` no reemplaza a la VGA por defecto de QEMU: la SUMA. El invitado ve `card0` y `card1`, cosmic-comp crea dos outputs (`Virtual-1` y `Unknown-1`), `cosmic-bg` pinta el fondo en ambos y el panel se ancla en uno solo. El `screendump` captura el otro ⇒ **fondo perfecto, panel invisible, y ni un solo error en el log**. Con `-vga none` el escritorio aparece entero a la primera. **Y esto corrige un diagnóstico anterior de este mismo runbook**: cuando el panel selló y no dibujó, lo atribuí a que le faltaban los applets. Los applets hacían falta —sin ellos el panel está vacío— pero **no eran la causa de la pantalla lisa**; la causa era la segunda GPU, que ya estaba ahí. Un síntoma puede tener dos causas suficientes y arreglar la primera no prueba nada: la prueba es la pantalla, no el razonamiento. 13. **`mkdir: not found` en `cosmic-start`, y la sesión arrancaba igual.** El getty entrega un PATH sin `/usr/bin`, y en la imagen fundida `mkdir` sólo vive ahí (`/usr/bin/mkdir → coreutils`; la base metal no tendió el symlink `/bin/mkdir → busybox` que sí tienen chmod, mount, sed y tee). Los cuatro `mkdir -p` de la preparación fallaban y **no se notaba**, porque los directorios venían horneados en la imagen. Es la forma más traicionera de bug: gratis hoy, fatal el día que alguien cambie cómo se arma la imagen. Fix: exportar `PATH` como primera línea del script. ## Cómo se construye ```sh cd ~/hammer ./target/release/hammer --store store hash recipes/incoming-cosmic/.toml # dry-run, ~2ms ./target/release/hammer build recipes/incoming-cosmic/.toml --store store ``` ## 🏔 EL ESCRITORIO ENTERO, DIBUJADO (2026-08-04) `docs/evidencia/cosmic-panel-applets-qemu-2026-08-04.png` — 1280×800, **895 colores distintos**: - **panel superior** (banda `(27,27,27)`, 32 px): botones *Workspaces* y *Applications* a la izquierda, reloj al centro, y a la derecha los applets de accesibilidad, audio, red, batería y encendido; - **dock inferior** centrado y redondeado, con lanzador, workspaces, biblioteca de aplicaciones y las entradas `.desktop` del rootfs; - fondo `(33,74,135)` = exactamente el `Color(Single((0.13,0.29,0.53)))` que escribe `cosmic-start`. 48 procesos `cosmic-*` estables a los 30 s, sobre el kernel de hammer con arje-zero como PID1. Se reproduce así (headless y automatizable; **el `-vga none` no es opcional**, ver gotcha 12): ```sh COSMIC_MODE=session bash scripts/cosmic/qemu-desktop-image.sh qemu-system-x86_64 -machine q35,accel=kvm -cpu host -smp 8 -m 8192 \ # 8 vCPU: ver el deadline del panel -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \ -drive if=pflash,format=raw,file=/tmp/OVMF_VARS.fd \ -drive file=work/hammer-cosmic-qemu.img,format=raw,if=virtio \ -vga none -device virtio-gpu-pci,xres=1280,yres=800 \ -device qemu-xhci,id=xhci -device usb-kbd,bus=xhci.0 -device usb-tablet,bus=xhci.0 \ -display none -serial file:/tmp/cosmic.serial -qmp unix:/tmp/qmp.sock,server,nowait # y después, por QMP: {"execute":"screendump","arguments":{"filename":"/tmp/shot.ppm"}} ``` El veredicto es **contar colores distintos del PPM**, no leer el log: con dos GPUs el log salía impecable y la pantalla estaba lisa. ## 🖱 LA ENTRADA: se usa, no sólo se ve (2026-08-04) **Hay que usar USB HID, no virtio-input.** El kernel de hammer no trae `VIRTIO_INPUT` (mirá el `-e …` de `recipes/linux-generic.toml`), así que `-device virtio-keyboard` no produce ni un `/dev/input/event*`. Sí trae `USB_XHCI_HCD` + `USB_HID` + `INPUT_EVDEV`, y además es lo que se va a usar en metal, así que la prueba es más fiel: ``` -device qemu-xhci,id=xhci -device usb-kbd,bus=xhci.0 -device usb-tablet,bus=xhci.0 ``` `usb-tablet` y no `usb-mouse`: reporta coordenadas ABSOLUTAS, y QMP sólo sabe mandar eventos `abs` (el eje va de 0 a 32767 sobre el ancho/alto de la pantalla, no en píxeles). **Se maneja entero por QMP**, sin ver la pantalla y sin humano: ```jsonc // mover el puntero a (155,16) — el botón «Applications» {"execute":"input-send-event","arguments":{"events":[ {"type":"abs","data":{"axis":"x","value":3968}}, {"type":"abs","data":{"axis":"y","value":655}}]}} // click {"execute":"input-send-event","arguments":{"events":[{"type":"btn","data":{"down":true,"button":"left"}}]}} {"execute":"input-send-event","arguments":{"events":[{"type":"btn","data":{"down":false,"button":"left"}}]}} // una tecla {"execute":"input-send-event","arguments":{"events":[ {"type":"key","data":{"down":true,"key":{"type":"qcode","data":"c"}}}, {"type":"key","data":{"down":false,"key":{"type":"qcode","data":"c"}}}]}} ``` **El veredicto también es de píxeles, y conviene medirlo por REGIÓN.** El click en *Applications* llevó el color dominante de `(33,74,135)` (fondo, 91 %) a `(27,27,27)` (84 %): la biblioteca de aplicaciones se abrió a pantalla casi completa. Y para el teclado, contar colores del rectángulo del buscador (440..840 × 80..112) bastó: 155 → 118 colores al reemplazarse el placeholder por lo tecleado. Evidencia: `cosmic-app-library-click-qemu-2026-08-04.png` y `cosmic-teclado-busqueda-qemu-2026-08-04.png`. **La biblioteca sale vacía y está BIEN.** Los 20 `.desktop` de la imagen son `NoDisplay=true` —son applets y componentes de sesión, no aplicaciones—, así que no hay nada que listar. Que la búsqueda no devuelva resultados no es un fallo del escritorio: es que la distro todavía no tiene ni una sola aplicación gráfica. Se sabrá arreglado cuando entre `cosmic-files` o un terminal. ### 🔑 Los atajos: dos ficheros envenenados por la misma línea `Super+A` abre la biblioteca y `Super+W` la vista de espacios de trabajo (`cosmic-super-w-workspaces-qemu-2026-08-04.png`, con **miniatura viva** del escritorio dentro — o sea que el camino DMA-BUF→GBM del gotcha 11 anda de verdad). Para llegar ahí hubo que sanear DOS ficheros de datos, no uno: | fichero | lo instala | qué liga | |---|---|---| | `…Shortcuts/v1/defaults` | `cosmic-comp` | tecla → acción | | `…Shortcuts/v1/system_actions` | `cosmic-settings-daemon` | acción → comando | Los dos traen una entrada `ScreenReader`, una variante que `cosmic-settings-config` —el crate que los parsea, pineado por cargo en la rev `8c54bbbc`— todavía no tiene en su enum. **RON no ignora lo que no conoce: falla la deserialización del MAPA COMPLETO.** Con `defaults` roto no hay ligaduras; con `system_actions` roto hay ligadura pero ningún comando que ejecutar. Hacen falta los dos `grep -v`. **Y acá me equivoqué de fichero teniendo el dato exacto delante.** El error decía `3:5-3:17: Unexpected variant named 'ScreenReader'` — con línea y columna. Busqué el *token* en vez de preguntarme *qué fichero lo tiene en esa posición*, di con `keybindings.ron` (que también lo traía, en la línea 94), lo arreglé, reconstruí `cosmic-comp` **cuarenta minutos** y el error salió igualito. Era otro fichero, de otro paquete. *Cuando el error trae coordenadas, la primera pregunta es de qué fichero son, no dónde más aparece esa palabra.* **Es el riesgo del pin lockstep asomando desde adentro de upstream**: el dato y el parser del *mismo* epoch no coinciden. **`Super` abre el lanzador** — pero hizo falta un paquete más, y de otro repo: ver abajo. ### 🔎 `pop-launcher`: el lanzador es DOS programas `Super` no abría nada y el único rastro era una línea que no menciona ninguna tecla: `ERROR pop-launcher failed to start: No such file or directory`. **`cosmic-launcher` es sólo la interfaz**: dibuja la lista y manda lo tecleado por stdin a un proceso hijo, `pop-launcher`, que es quien busca. Sin ese binario la ventana no tiene qué mostrar, así que no se muestra — el mismo modo de falla que el panel sin applets: *lo que falta es un HIJO, no una librería, y el log del padre se lee sano.* Vive en **otro repo** (`pop-os/launcher`), no versiona por `epoch-N` y no entra en el pin lockstep. **La versión la fija el `Cargo.lock` de cosmic-launcher** —rev `a332a3a7` de la 1.2.7—, no el último tag del repo: el lock es lo que garantiza que hablen el mismo protocolo. El tarball se pide por commit (`/archive/.tar.gz`), porque no hay tag que nombre esa rev. Selló a la primera (`b3:64a5cbaa`) con **un solo `NEEDED`: `libc.so`**. Es otro **multiplexor por symlink**, como cosmic-applets: un binario, y cada plugin es un enlace con su nombre dentro de su propio directorio junto al `plugin.ron` que lo describe. El servicio enumera `plugins/*/`; si está el `.ron` y falta el enlace —o al revés— el plugin no existe para él, **sin error**. Ojo con el `sed 's/_/-/'` de upstream: cambia sólo el primer guión bajo, así que el directorio es `desktop_entries` y el ejecutable de adentro `desktop-entries`. Son dos nombres distintos a propósito. Verificado de punta a punta (`cosmic-super-lanzador-`, `cosmic-pop-launcher-plugins-`, `cosmic-pop-launcher-calc-qemu-2026-08-04.png`): `Super` abre el lanzador, `?` lista los seis plugins con su sintaxis —o sea que la enumeración y el IPC JSON-sobre-stdio andan— y `=2+2` **lanza el plugin, que contesta «qalc command is not installed»**. Esa respuesta es la mejor prueba que había disponible: el proceso hijo corrió, evaluó la consulta y devolvió una fila. Lo que falta ahora es `libqalculate` (el binario `qalc`), que es un paquete, no un problema. ### 🧮 `libqalculate`: la calculadora del lanzador, y un `stdin` que era `stdout` **`=123*456` → `56088` en el lanzador** (`cosmic-calculadora-qemu-2026-08-04.png`). La cadena entera desde una tecla está en el catálogo: `Super` → cosmic-launcher → pop-launcher → plugin `calc` → `qalc` → **mpfr → gmp**. Pero el camino hasta ahí tuvo dos muros que vale la pena dejar escritos, porque ninguno se parecía a lo que era. **1) `stdin` apuntaba al descriptor 1.** El plugin no le pasa la cuenta por argv: arranca `qalc` SIN expresión, con stdin en tubería, le escribe la expresión y cierra. En ese modo el binario **no leía nada** — ni error ni resultado, la fila del lanzador salía en blanco. El síntoma acusaba a readline, al toolchain y a la versión, y no era ninguno: ``` DIAG-main isatty=0 feof=0 ferror=0 fd=1 ← fileno(stdin) == 1 nm -S qalc | grep -E ' (stdin|stdout|stderr)$' 1847148 8 B stderr 1847148 8 B stdin ← las TRES en la misma dirección 1847148 8 B stdout ``` La causa: **el binario salía DINÁMICO aunque la receta dice `link = "static"`, porque libtool se come el `-static` que inyecta el harness** ([[link-static-mentira-libtool]] otra vez, cuarto caso). Y en un musl dinámico las tres variables de stdio se resolvieron por *copy relocation* a **una sola ranura de BSS compartida**: la última que escribe el loader gana, así que el `stdin` del programa era el `stdout`. De ahí la asimetría que parecía absurda: `qalc -f /dev/stdin` funcionaba —abre el descriptor por RUTA, sin tocar el `FILE*` roto— y `qalc` leyendo `stdin` no. El fix es el par que ya usa `libxml2`: `make LDFLAGS="-all-static -no-pie"`. Con él el binario queda estático de verdad y los tres símbolos vuelven a tener direcciones distintas, en `.rodata`: ``` 11e71c8 8 R stderr 11e71d0 8 R stdin 11e71d8 8 R stdout ``` **La regla, y es de las caras: `link = "static"` en la receta no garantiza un binario estático.** Cuando algo se comporta como si su `stdin`/`stdout` estuvieran cruzados, `readelf -d` y `nm -S | grep ' std'` contestan en dos segundos lo que el log no dice nunca. Barrido de la cola entera con ese criterio, después del fix: de las 7 recetas que declaran `link = "static"` (`cosmic-session`, `gmp`, `mpfr`, `nasm`, `libqalculate` y las dos que en realidad son `dynamic`), **ninguna tiene `NEEDED` indebido y ninguna tiene los símbolos de stdio aliasados**. Era un caso, no un patrón — pero el patrón que lo causa (libtool comiéndose el `-static`) ya tiene cuatro víctimas anteriores en el corpus. **2) La pregunta de primer arranque se come la cuenta, SIEMPRE.** Antes de leer nada, `qalc` pregunta si activar `autocalc`. El plugin le manda la expresión y cierra: la expresión se consume como respuesta, no es un sí/no válido, y qalc **no llega a guardar config** ⇒ la próxima vez vuelve a preguntar. No es «falla la primera vez»: no anda nunca. Se siembra `~/.config/qalculate/qalc.cfg` con `calculate_as_you_type=0` desde `cosmic-start` — política de la imagen, como el color de fondo; una distro de verdad lo pondría en `/etc/skel`. **Gotchas de empaquetado de la cadena:** - **`gmp` va con `--disable-assembly`.** Elige rutinas en ensamblador mirando la CPU de quien compila, o sea hornearía la ISA del laptop en el artefacto y el hash dejaría de significar lo mismo en la granja. Es una calculadora de escritorio: la implementación en C portable sobra. - **El configure de libqalculate no encuentra readline** porque prueba `-lreadline` junto a `-lncurses`, `-lcurses`, `-ltermcap` y `-ltinfo`, y el corpus **sólo empaqueta `libncursesw.a`**. Que el paquete se llame `ncurses` no significa que provea `-lncurses`. Los alias van en un directorio LOCAL (`.curses-compat/`) y no en `/usr/lib`: esa vista es la evidencia de qué se declaró, y ensuciarla para que pase un test deja mintiendo justo al mecanismo que existe para no mentir. Ojo: hay que repetir el `-L` en el `make`, porque el `LDFLAGS` del `make` PISA al del `configure`. ### 🖥 `cosmic-term`: la primera APLICACIÓN, y la biblioteca deja de estar vacía `docs/evidencia/cosmic-terminal-qemu-2026-08-04.png`: ``` bash-5.3# uname -srm; qalc -t '6*7' Linux 6.16.12 x86_64 42 ``` Una ventana de verdad —con decoración, *File/Edit/View*, minimizar/maximizar/cerrar—, `bash` corriendo adentro, el **kernel propio de hammer** contestando, y el `qalc` que empaquetamos una hora antes devolviendo 42. Se abre haciendo click en su icono en la biblioteca de aplicaciones (`cosmic-biblioteca-con-app-qemu-2026-08-04.png`), que hasta hoy salía vacía **con razón**: los 20 `.desktop` de la imagen eran `NoDisplay=true`. Éste no lo es, y es el primero. **Selló a la primera, y eso es noticia por lo que arrastra**: su `Cargo.toml` depende de **`cosmic-files`** —el gestor de ficheros entra como LIBRERÍA, por el selector y el arrastrar-y-soltar—, así que compilar la terminal compila medio gestor de ficheros. Mismo patrón que `cosmic-settings-config` en los applets: **en esta suite las «aplicaciones» se usan unas a otras como crates**, y el grafo de deps de Cargo no se parece al mapa de componentes. `wgpu` viene en `default`, al revés que el resto de los clientes que pintan con tiny-skia. Se dejó como está: `wgpu` y `naga` ya se habían compilado enteros para `cosmic-workspaces`, así que el costo estaba pago, y apartarse de las features por defecto es apartarse de la única combinación que upstream prueba. Cierra con **dos `NEEDED`**: `libxkbcommon.so.0` y `libc.so`. **Terminfo, para la próxima:** la ncurses del corpus es *libs-only* y no instala la base de terminfo, pero trae `--with-fallbacks=…,xterm-256color,…` compilados. A la terminal no le hace falta —el que necesita terminfo es lo que corre ADENTRO— y el hijo acá es el `sh` de busybox, que no lo usa. El día que entren `vim` o `htop` en la imagen, hará falta un paquete de datos de terminfo. ### 📁 `cosmic-files`: «ya está compilado» era falso por una línea del vecino `docs/evidencia/cosmic-files-qemu-2026-08-04.png`: el gestor abre desde la biblioteca y **lista el sistema de ficheros real de hammer** — `bin` (72 ítems), `dev` (129), `ente`, `etc`, `lib`, `lost+found`—, con su barra lateral de Recientes/Carpeta personal/Papelera. Entró creyendo que era casi gratis, porque `cosmic-term` **ya compila cosmic-files como crate**. No lo era, y el motivo cabe en una línea del `Cargo.toml` del vecino: `cosmic-term` lo declara con `default-features = false`. **Lo que un paquete cuesta depende de con qué features lo pide quien lo usa**, así que «ya se compiló» puede ser falso. **Se apagaron dos features por defecto, y las dos por una razón medida:** - **`gvfs`** trae los crates `gio`/`glib`, y el enlace final pide las glib **compartidas** (`unable to find dynamic system library 'gio-2.0'`, y detrás gobject, glib, ffi, pcre2, z). El corpus las empaqueta **sólo estáticas**: haría falta una cadena `-shared` entera, que es una campaña aparte y mete una segunda glib en la imagen. Se pierden los **montajes remotos** (SFTP, MTP), no la navegación local. ⚠ Y por eso tampoco se construye `cosmic-files-applet`: su `Cargo.toml` fija `gvfs` a mano, así que apagarla en el paquete raíz no lo alcanza. - **`wgpu`** desbordó el filesystem: `No space left on device` creando un temp dir en `/src/target`, **dos veces**, con wgpu+naga+ash+glow+spirv a `codegen-units=1`. Sin él el árbol de build queda en ~2 GB. No se pierde nada: todos los demás clientes de la suite pintan con tiny-skia y la imagen no tiene GPU. **El intento con `glib` declarado dejó una regla que vale para toda la cola:** el error fue `Package 'libpcre2-8', required by 'glib-2.0', not found` — o sea que **el que falla no es la dep sino lo que su `.pc` declara en `Requires`**. Declarar una dep trae su artefacto, no su clausura de pkg-config. Se lee en dos segundos con `grep ^Requires` sobre los `.pc` del artefacto, ANTES de gastar el build: `glib-2.0.pc → libpcre2-8`, `gobject-2.0.pc → libffi`, `gio-2.0.pc → zlib`. ### ⚙ `cosmic-settings`: un panel de control que NO arrastra medio sistema `docs/evidencia/cosmic-settings-qemu-2026-08-04.png`: abre desde el lanzador (`Super`, teclear «settings») con la barra lateral entera —Red, Bluetooth, Accesibilidad, Escritorio, Pantallas, Sonido, Energía, Dispositivos de entrada, Aplicaciones, Fecha y hora, Sistema y cuentas— y la página de Escritorio poblada. **Tarda ~20 s en aparecer** con render por software; no está colgado. **La sorpresa buena: no arrastra NetworkManager, ni libpulse, ni udisks, ni accountsservice.** Habla con los daemons por D-Bus vía `zbus`, que es Rust puro. Se verificó ANTES de escribir la receta listando los crates `-sys` del `Cargo.lock` —que es donde vive la verdad sobre qué C hace falta—, y salieron sólo: `drm-sys`, `input-sys`, `libudev-sys`, `dav1d-sys`, `wayland-sys` (dlopen) y `gettext-sys` (trae su propio C). O sea el juego fijo de todo cliente + `libdrm` + `dav1d`. **Regla barata que ahorra builds: `grep '^name = ".*-sys"' Cargo.lock | sort -u` antes de adivinar deps por lo que el programa *hace*.** **Volvió a morder la doble barra de `cosmic-protocols//`**, igual que en `cosmic-applets`: el manifiesto usa esa forma para que cargo trate la fuente como distinta y el `[patch]` no se apunte a sí mismo, GitHub le contesta 404, y el build muere en el *fetch* con tres reintentos de red y `failed to load source for dependency 'cosmic-client-toolkit'`. Mismo fix: mover la barra al medio (`pop-os//cosmic-protocols`) en `Cargo.toml` **y** `Cargo.lock`. **Segunda víctima ⇒ es un patrón de la suite, no una rareza de un repo**: conviene mirarlo apenas un `cargo vendor` da 404. Instala **32 `.desktop`**, uno por página, así que cada sección del panel aparece como entrada propia en la biblioteca. Y `resources/default_schema/` va con `find`, no con `cp` plano: es el árbol `/v1/` de cosmic-config, donde **cada clave es un fichero cuyo nombre es la clave** — aplanarlo deja el escritorio sin esa configuración, callado. **Deuda que deja anotada su propio log:** `org.freedesktop.locale1 was not provided by any .service files` — nadie sirve `localed` en la imagen, así que la página de idioma no va a poder cambiar nada. Es del mismo tipo que `login1` (que sí resuelve arje-logind-compat), y se arregla del mismo modo. ### ⏱ El panel tiene un DEADLINE de arranque: con 4 vCPU sale mutilado Medido: con `-smp 4` el panel arranca **sin ala izquierda, sin reloj y sin dock** —sólo la bandeja derecha— y el único rastro es `Failed to setup panel dbus server deadline has elapsed`. Con `-smp 8 -m 8192`: **4 arranques de 4 con el escritorio completo y cero deadlines vencidos**. La causa es la estampida del arranque: el panel registra su servidor D-Bus mientras catorce clientes de iced escanean fuentes al mismo tiempo. **CORRECCIÓN medida el 2026-08-04 validando `cosmic-edit`: la carga del anfitrión TAMBIÉN cuenta, y `-smp 8` no la compensa.** Acá decía «no es carga del anfitrión (falla con el anfitrión ocioso)» — cierto para el caso de 4 vCPU, pero se leía como que con 8 el anfitrión daba igual, y no. Dos arranques de la MISMA imagen con la MISMA línea de QEMU (`-smp 8 -m 8192`): | anfitrión | colores | veredicto | |---|---|---| | `load average` ≈ 6 (otro agente compilando) | **130** | mutilado, y `deadline has elapsed` en el log | | ocioso (`load` ≈ 1,8) | **1671** | completo | Tiene sentido y conviene decirlo entero: **este anfitrión tiene 8 núcleos** (`nproc`), así que `-smp 8` le entrega la máquina entera a la VM y cualquier otra cosa que compile compite por los mismos hilos. La receta práctica: antes de creerle a un arranque mutilado, mirá `/proc/loadavg` y repetí con el anfitrión quieto — es un minuto y evita perseguir una regresión que no existe, que es exactamente el error que ya está documentado dos párrafos más abajo. **Y una lección de medición que costó horas: el conteo de procesos NO sirve como veredicto.** Perseguí una regresión inexistente comparando «48 procesos» contra «31», revirtiendo uno por uno tres cambios míos que no tenían nada que ver — cuando un escritorio completo y sano muestra **32**. El número de procesos no distingue «arrancó» de «dibujó». El framebuffer sí, y sin ambigüedad: **894 colores = completo, 130 = mutilado.** (Con las cuatro aplicaciones en el dock el escritorio completo da ~1670: el umbral es «cientos vs. miles», no un número exacto.) ## ✎ LA CUARTA APLICACIÓN: cosmic-edit (2026-08-04) `docs/evidencia/cosmic-edit-qemu-2026-08-04.png` y `…-escribiendo-…png` — 1280×800, **2105 colores**: ventana con menús *File/Edit/View*, pestaña *New document*, numeración de líneas, y el icono del editor en el dock con el punto de «en ejecución». La segunda captura tiene `hammer edita` escrito por teclado sobre la línea 1 y el punto de modificado en la pestaña: **se escribe, no sólo se ve**. Se llegó por el lanzador, sin tocar el ratón para elegir: `Super` → teclear `text` → el primer resultado es *«Text editor for the COSMIC desktop»* (`Ctrl+1`) → `Enter`. Que el editor sea lo que responde a «text» es efecto del `.desktop` que genera `xdgen`, con sus `Keywords` y su `MimeType=text/plain` — el mismo fichero que lo hace manejador por defecto de los `.txt` en el gestor. **Lo que la receta enseñó, y que corrige un borde de la regla del `Cargo.lock`:** leer los crates `-sys` del lock sigue siendo lo primero que hay que hacer, pero **el lock lista lo POSIBLE, no lo ENCENDIDO**. En cosmic-edit aparecen `gio-sys`, `glib-sys` y `gobject-sys` —que en cualquier otro paquete mandarían a declarar glib, y de ahí a la campaña de la cadena compartida— y entran sólo por la feature `gvfs`, que se apaga. El lock dice el universo; las features dicen el recorte. La receta no declara ninguna glib y su binario enlaza **sólo `libxkbcommon.so.0` y `libc.so`**: dos NEEDED, el mínimo de la suite. ## Lo que falta, en orden 1. **Más aplicaciones**: `cosmic-store`, `cosmic-screenshot`. 2. **La cadena glib COMPARTIDA** (`glib` + `pcre2` + `libffi` + `zlib` con `.so`), que destrabaría `gvfs` en cosmic-files y su applet del panel. Es una campaña propia y hay que decidir antes si la imagen puede tener una sola glib. 2. `xdg-desktop-portal-cosmic` — sin él no hay captura de pantalla, ni file-chooser, ni compartir. 3. `cosmic-settings` (el panel de control) y `cosmic-files`: la suite tiene aplicaciones y el catálogo todavía no. 4. `cosmic-greeter` (hoy `cosmic-session` lo lanza y falla con ENOENT, sin consecuencias) y `cosmic-osd` con teclas de brillo/volumen reales. 5. Metal: la imagen de QEMU y la de metal comparten todo menos el driver; el viaje físico es el mismo que el de `metal-dual-desktop-en-curso`.