> 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…
cursores: cosmic y sway corrían con el puntero INVISIBLE — receta propia, y el vigía medía mal
`vigia-imagen.py` daba ✗ en cursores en DOS de los cuatro escritorios. No es cosmético: con el
cursor por software —obligatorio en virtio-gpu y en todo render por CPU— el compositor dibuja la
imagen que le da el TEMA, y sin tema el ratón se mueve invisible. cosmic llegó a 43/43 y sway a
173/173 así, porque un tema de cursor no es dep de build de nadie: sólo entra si se DECLARA.
`adwaita-cursors` (corpus, 48.1, data-only): del mismo tarball que `adwaita-icon-theme` pero SÓLO
`Adwaita/cursors/` — 39 ficheros y 14 MB, sin un icono. Promover el tema entero habría regalado a
sway y a cosmic los iconos de GNOME, que está anotado como decisión pendiente y no como olvido.
Las dos cosas que el tarball no trae y la receta fabrica:
· los nombres X11 heredados (`left_ptr`, `xterm`, `watch`, `hand2`…) son enlaces que genera el
`meson.build` de upstream. El mapa se PARSEA de ahí, no se copia: copiado envejece en silencio.
Si el origen de un enlace no existe, la fase falla — upstream pone un `files()` como aserción.
· `/usr/share/icons/default/index.theme` con `Inherits=Adwaita`. Sin `XCURSOR_THEME` en el
entorno, libXcursor y wlroots buscan el tema llamado literalmente `default`; sin él no hay
puntero AUNQUE Adwaita esté instalado. Es el eslabón que hace que ande sin configuración.
⚠ no declarar esta receta junto a `adwaita-icon-theme`: chocan en `Adwaita/cursors/*`.
Y el vigía estaba midiendo el invariante de al lado: exigía `index.theme` en el directorio para
contar un tema, que es correcto para ICONOS —la búsqueda XDG recorre `Directories=`— y falso para
CURSORES, porque libXcursor abre `<tema>/cursors/<nombre>` directo y el índice sólo hace falta para
seguir un `Inherits=`. Con la receta instalada seguía diciendo «NINGÚN tema de cursor».
De paso queda anotado en `targets.toml` que el comentario de cosmic decía «sin ellos arranca sin
puntero» sobre `cosmic-icons`, que no trae cursores: describía una protección que no existía.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGyBj4QyeCZiwLMkP9GJp6
diff --git a/docs/state/build-state-cosmic.json b/docs/state/build-state-cosmic.json
index 170e572c..40ac9e37 100644
--- a/docs/state/build-state-cosmic.json
+++ b/docs/state/build-state-cosmic.json
@@ -1,23 +1,23 @@
{
"schema": "hammer-build-state/1",
"totals": {
- "recipes": 856,
- "nodes": 858,
- "sealed": 856,
+ "recipes": 857,
+ "nodes": 859,
+ "sealed": 857,
"ajeno": 2
},
"by_class": {
"ajeno": 2,
"c": 203,
"go": 362,
- "gui": 35,
+ "gui": 36,
"kernel": 6,
"rust": 250
},
"debt_by_class": {},
"by_queue": {
"corpus": {
- "sealed": 819,
+ "sealed": 820,
"ajeno": 2
},
"incoming-cosmic": {
@@ -36,9 +36,9 @@
"sealed": 75
},
"escritorio-cosmic": {
- "raices": 39,
- "clausura": 133,
- "sealed": 133
+ "raices": 40,
+ "clausura": 134,
+ "sealed": 134
},
"escritorio-mirada": {
"raices": 14,
@@ -104,6 +104,25 @@
"perfiles": [],
"dependientes_total": 0
},
+ "adwaita-cursors": {
+ "iname": "adwaita-cursors",
+ "version": "48.1",
+ "link": "dynamic",
+ "compiler": "zig-cc",
+ "deps": [],
+ "cls": "gui",
+ "queue": "corpus",
+ "foreign": false,
+ "hash": "b3:22ae0ff6369e6f15cb6810ddd979d95ebe38307e8da668c1210d9434ef9b5b05",
+ "state": "sealed",
+ "blocked_by": [],
+ "unblocks": 0,
+ "perfiles": [
+ "escritorio-cosmic",
+ "escritorio-sway"
+ ],
+ "dependientes_total": 0
+ },
"adwaita-hello": {
"iname": "adwaita-hello",
"version": "1.7.6",
diff --git a/docs/state/build-state-wlr.json b/docs/state/build-state-wlr.json
index 90151c43..51eebbd1 100644
--- a/docs/state/build-state-wlr.json
+++ b/docs/state/build-state-wlr.json
@@ -1,23 +1,23 @@
{
"schema": "hammer-build-state/1",
"totals": {
- "recipes": 834,
- "nodes": 836,
- "sealed": 834,
+ "recipes": 835,
+ "nodes": 837,
+ "sealed": 835,
"ajeno": 2
},
"by_class": {
"ajeno": 2,
"c": 202,
"go": 362,
- "gui": 35,
+ "gui": 36,
"kernel": 6,
"rust": 229
},
"debt_by_class": {},
"by_queue": {
"corpus": {
- "sealed": 820,
+ "sealed": 821,
"ajeno": 2
},
"incoming-wlr": {
@@ -41,9 +41,9 @@
"sealed": 33
},
"escritorio-sway": {
- "raices": 70,
- "clausura": 173,
- "sealed": 173
+ "raices": 71,
+ "clausura": 174,
+ "sealed": 174
}
},
"sin_perfil": 645,
@@ -104,6 +104,25 @@
"perfiles": [],
"dependientes_total": 0
},
+ "adwaita-cursors": {
+ "iname": "adwaita-cursors",
+ "version": "48.1",
+ "link": "dynamic",
+ "compiler": "zig-cc",
+ "deps": [],
+ "cls": "gui",
+ "queue": "corpus",
+ "foreign": false,
+ "hash": "b3:22ae0ff6369e6f15cb6810ddd979d95ebe38307e8da668c1210d9434ef9b5b05",
+ "state": "sealed",
+ "blocked_by": [],
+ "unblocks": 0,
+ "perfiles": [
+ "escritorio-cosmic",
+ "escritorio-sway"
+ ],
+ "dependientes_total": 0
+ },
"adwaita-hello": {
"iname": "adwaita-hello",
"version": "1.7.6",
diff --git a/docs/state/build-state.json b/docs/state/build-state.json
index d53783e8..3998381f 100644
--- a/docs/state/build-state.json
+++ b/docs/state/build-state.json
@@ -1,23 +1,23 @@
{
"schema": "hammer-build-state/1",
"totals": {
- "recipes": 821,
- "nodes": 823,
- "sealed": 821,
+ "recipes": 822,
+ "nodes": 824,
+ "sealed": 822,
"ajeno": 2
},
"by_class": {
"ajeno": 2,
"c": 189,
"go": 362,
- "gui": 35,
+ "gui": 36,
"kernel": 6,
"rust": 229
},
"debt_by_class": {},
"by_queue": {
"corpus": {
- "sealed": 821,
+ "sealed": 822,
"ajeno": 2
}
},
@@ -96,6 +96,25 @@
"perfiles": [],
"dependientes_total": 0
},
+ "adwaita-cursors": {
+ "iname": "adwaita-cursors",
+ "version": "48.1",
+ "link": "dynamic",
+ "compiler": "zig-cc",
+ "deps": [],
+ "cls": "gui",
+ "queue": "corpus",
+ "foreign": false,
+ "hash": "b3:22ae0ff6369e6f15cb6810ddd979d95ebe38307e8da668c1210d9434ef9b5b05",
+ "state": "sealed",
+ "blocked_by": [],
+ "unblocks": 0,
+ "perfiles": [
+ "escritorio-cosmic",
+ "escritorio-sway"
+ ],
+ "dependientes_total": 0
+ },
"adwaita-hello": {
"iname": "adwaita-hello",
"version": "1.7.6",
diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index c7e7939f..76a7e4de 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -407,6 +407,10 @@ paquetes = [
# tipografía. Ninguno es dep de build de nadie y sin ellos el escritorio arranca roto — sin puntero,
# sin teclado o con tofu.
"cosmic-icons", "hicolor-icon-theme", "xkeyboard-config", "dejavu-fonts",
+ # ⚠ «sin puntero» lo decía este comentario desde que se escribió y NO era cierto: `cosmic-icons`
+ # instala iconos de aplicación, no cursores, así que este perfil llegó a 43/43 con el puntero
+ # INVISIBLE. Lo cazó `vigia-imagen.py` el 2026-09-04. Los cursores son su propia raíz.
+ "adwaita-cursors",
# `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
# iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
# statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
@@ -520,6 +524,11 @@ paquetes = [
"dejavu-fonts-nerd",
"xkeyboard-config", # sin datos XKB: «xkbcommon: ERROR: failed to add default include path
# /usr/share/X11/xkb», y sway no completa el seat
+ # Cursores. Mismo caso que las fuentes y el XKB, y con el mismo modo de fallo silencioso: con el
+ # cursor por software el compositor dibuja lo que le da el TEMA, y sin tema el puntero se mueve
+ # invisible. Este perfil llegó a 173/173 sin uno. Trae además `/usr/share/icons/default`, que es
+ # lo que buscan libXcursor y wlroots cuando nadie fijó `XCURSOR_THEME` — o sea, siempre acá.
+ "adwaita-cursors",
# ── LAS TRES `.so` QUE sway PIDE Y EL CORPUS SÓLO PRODUCE COMO `.a` ─────────────────────────
# `sway` es `link = "dynamic"` a propósito (dlopea los drivers DRI de mesa) y sale con
# `NEEDED libz.so.1 / libexpat.so.1 / libffi.so.8`. Las canónicas son `--disable-shared`, así que
diff --git a/recipes/adwaita-cursors.toml b/recipes/adwaita-cursors.toml
new file mode 100644
index 00000000..4548e42a
--- /dev/null
+++ b/recipes/adwaita-cursors.toml
@@ -0,0 +1,94 @@
+# adwaita-cursors 48.1 — SÓLO los cursores de Adwaita, en el CORPUS.
+#
+# ══ EL HUECO QUE TAPA ══════════════════════════════════════════════════════════════════════════
+# `vigia-imagen.py` mide un invariante que ninguna métrica de clausura ve: **≥1 tema de cursor con
+# `cursors/` poblado**. El 2026-09-04 daba ✗ en DOS de los cuatro escritorios —`escritorio-cosmic`
+# y `escritorio-sway`—, y eso no es cosmético: con el cursor por software (obligatorio en
+# virtio-gpu y en cualquier render por CPU) el compositor dibuja la imagen que le da el tema, así
+# que sin tema **el puntero se mueve invisible**. Es la misma figura que las fuentes de GNOME y que
+# los iconos de KDE: sellado ≠ usable, y el agujero no es dep de build de nadie.
+#
+# ══ POR QUÉ UNA RECETA NUEVA Y NO PROMOVER `adwaita-icon-theme` ════════════════════════════════
+# Los cursores viven DENTRO de `adwaita-icon-theme` (recipes/incoming-gnome/), que son ~5000
+# ficheros de tema de ICONOS. Promoverla al corpus para conseguir un puntero le regalaría a sway y
+# a cosmic el tema de iconos de GNOME — y eso está declarado como una DECISIÓN pendiente, no como
+# un olvido (`vigia-imagen.py` lo marca ⊘ en sway a propósito). Esta receta corta por lo sano:
+# mismo tarball, misma versión, **sólo `Adwaita/cursors/`** (~1 MB), sin un solo icono.
+#
+# ⚠ **NO declarar esta receta y `adwaita-icon-theme` en el MISMO perfil**: las dos instalan
+# `/usr/share/icons/Adwaita/cursors/*` y chocarían fichero a fichero — la misma colisión por la que
+# zathura no entra en `escritorio-kde` (dos poppler). `escritorio-gnome` ya tiene sus cursores por
+# el tema completo y NO lleva ésta.
+#
+# ══ LAS DOS COSAS QUE EL TARBALL NO TRAE Y HAY QUE FABRICAR ════════════════════════════════════
+# 1. **Los nombres X11 heredados.** El tarball trae los nombres CSS (`default`, `pointer`, `text`…);
+# `left_ptr`, `hand2`, `xterm`, `watch` y compañía son ENLACES que genera el `meson.build`
+# (`cursor_symlinks`). Una app que pida `left_ptr` —y las viejas piden eso— no encontraría nada.
+# El mapa se PARSEA de `meson.build`, no se copia a mano: la lista autoritativa es upstream, y
+# copiada envejece en silencio. Si el origen del enlace no existe, la fase FALLA: upstream pone
+# ahí un `files()` justamente como aserción, y perderla convertiría un cambio de upstream en un
+# enlace roto que nadie ve.
+# 2. **El tema `default`.** Sin `XCURSOR_THEME` en el entorno, libXcursor y wlroots buscan el tema
+# literalmente llamado `default`; si no existe, no hay puntero AUNQUE `Adwaita` esté instalado.
+# Por eso se escribe `/usr/share/icons/default/index.theme` con `Inherits=Adwaita`. Es el
+# eslabón que hace que esto funcione en una imagen SIN configuración, que es el caso real.
+#
+# Los `.cur` son los cursores de Windows: upstream los excluye en la instalación de Linux
+# (`exclude_files: win32_cursors`) y acá también, por el mismo motivo.
+#
+# Data-only: no hay un compilador involucrado.
+name = "adwaita-cursors"
+version = "48.1"
+license = "LGPL-3.0-or-later OR CC-BY-SA-3.0"
+
+[source]
+tarball = "https://download.gnome.org/sources/adwaita-icon-theme/48/adwaita-icon-theme-48.1.tar.xz"
+sha256 = "cbfe9b86ebcd14b03ba838c49829f7e86a7b132873803b90ac10be7d318a6e12"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "dynamic"
+
+[build.phases]
+configure = "true"
+compile = "true"
+install = '''
+set -e
+CUR=/out/usr/share/icons/Adwaita/cursors
+mkdir -p "$CUR"
+
+# los cursores propiamente dichos, sin los .cur de Windows
+find Adwaita/cursors -maxdepth 1 -type f ! -name '*.cur' -exec install -Dm0644 {} "$CUR"/ \;
+# MEDIDO, no supuesto: 48.1 trae 37 cursores reales (nombres CSS) + 31 `.cur` de Windows. El
+# primer intento puso el piso en 50 «por si acaso» y la fase falló contra el tarball CORRECTO —
+# un guardián calibrado por encima de la escala real no protege: bloquea.
+N=$(ls -1 "$CUR" | wc -l)
+[ "$N" -ge 30 ] || { echo "sólo $N cursores: el tarball cambió de forma" >&2; exit 1; }
+
+# los nombres X11 heredados, leídos del meson.build de upstream (no copiados a mano)
+sed -n '/cursor_symlinks = {/,/^ }/p' meson.build \
+ | sed -n "s/^ *'\([^']*\)': *\[\([^]]*\)\].*/\1 \2/p" \
+ | while read -r SRC RESTO; do
+ [ -e "$CUR/$SRC" ] || { echo "el mapa nombra un cursor ausente: $SRC" >&2; exit 1; }
+ echo "$RESTO" | tr ',' '\n' | tr -d " '" | while read -r DST; do
+ [ -n "$DST" ] && ln -sf "$SRC" "$CUR/$DST"
+ done
+ done
+
+# el tema `default`: sin esto, una imagen sin XCURSOR_THEME no encuentra NINGÚN cursor
+mkdir -p /out/usr/share/icons/default
+cat > /out/usr/share/icons/default/index.theme <<'EOF'
+[Icon Theme]
+Name=Default
+Comment=Alias al tema de cursores real
+Inherits=Adwaita
+EOF
+
+# la comprobación que no se puede cumplir por accidente: el nombre que pide TODA app vieja
+[ -e "$CUR/left_ptr" ] || { echo "sin left_ptr: el mapa de enlaces no se aplicó" >&2; exit 1; }
+[ -e "$CUR/default" ] || { echo "sin el cursor 'default': el tema no sirve" >&2; exit 1; }
+'''
+
+[deps]
+build = []
diff --git a/scripts/vigia-imagen.py b/scripts/vigia-imagen.py
index be860723..544054ae 100644
--- a/scripts/vigia-imagen.py
+++ b/scripts/vigia-imagen.py
@@ -30,7 +30,8 @@
# FALLBACK obligatorio del estándar y no trae iconos propios: es el contrato, no el
# contenido.
# cursores ≥1 tema con `cursors/` poblado. Con el cursor por software —obligatorio en virtio-gpu—
-# sin tema el puntero se mueve INVISIBLE.
+# sin tema el puntero se mueve INVISIBLE. A diferencia de los iconos, acá NO se exige
+# `index.theme`: libXcursor abre `<tema>/cursors/<nombre>` directo.
# fuentes ≥1 `.ttf/.otf/.ttc/.pfb`. fontconfig es el motor que BUSCA fuentes, no una fuente.
# terminal ≥1 `.desktop` con `TerminalEmulator` en Categories. Un escritorio sin terminal no es
# uno incompleto: es uno del que no se puede salir cuando algo falla.
@@ -118,8 +119,14 @@ def escanear(nodos):
if os.path.isdir(base):
for t in sorted(os.listdir(base)):
td = os.path.join(base, t)
- if not os.path.exists(os.path.join(td, "index.theme")):
- continue # un directorio sin index.theme NO es un tema
+ # ⚠ ICONOS y CURSORES no se buscan igual, y medirlos con la misma regla dio un ✗
+ # falso el 2026-09-04: `adwaita-cursors` instalaba `Adwaita/cursors/` poblado y este
+ # vigía seguía diciendo «NINGÚN tema de cursor» porque el directorio no traía
+ # `index.theme`. Para un tema de ICONOS ese requisito es correcto —la búsqueda XDG
+ # recorre `Directories=` y sin el índice el tema no existe—, pero libXcursor y
+ # wlroots abren `<tema>/cursors/<nombre>` DIRECTO; `index.theme` sólo hace falta
+ # para seguir un `Inherits=`. Exigirlo también acá medía el invariante de al lado.
+ tiene_indice = os.path.exists(os.path.join(td, "index.theme"))
n_ico = n_cur = 0
for dp, _, fn in os.walk(td):
for f in fn:
@@ -127,11 +134,11 @@ def escanear(nodos):
n_ico += 1
elif os.path.basename(dp) == "cursors":
n_cur += 1
- if n_ico:
+ if n_ico and tiene_indice:
temas.setdefault(t, (nombre, n_ico))
if n_cur:
cursores.setdefault(t, (nombre, n_cur))
- if t == "hicolor":
+ if t == "hicolor" and tiene_indice:
temas.setdefault("hicolor", (nombre, n_ico))
# ── el resto, en UNA pasada por el árbol ────────────────────────────────────────────────
for dp, _, fn in os.walk(d):