gioser

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 1389Shell 137Markdown 80Rust 802602 Sutichaykuna

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

8c4e1a6b24489d5c9ac3d1c32a6aafe20ed16404 · ruwaq Sergio · (1 killa ñawpaq) · tayta a62ec9061 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 1

scripts/vigia-imagen.py

6 qillqakuna · +193 −26

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):