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…

⎇ main recipes/pipewire.toml TOML · 207 lines · 14.3 KB History raw

bluez + pipewire: bluez5 encendido, y el bluetooth SUENA 271d22804 · Sergio ·

1# ══ PROMOVIDA AL CORPUS 2026-09-03 — hash IDÉNTICO (b3:e240653a…) ═══════════════════════════════
2# Vivía en TRES copias, una por cola de escritorio, y ninguna en el corpus. Eso convertía a
3# `libpipewire-0.3` en inalcanzable para cualquier app del catálogo: `mpv` tuvo que salir con
4# `--ao=alsa` en vez de su salida nativa, y `wf-recorder` —que graba con pipewire o pulse— no se
5# podía ni escribir. Una receta del corpus resuelve sibling-first y después el catálogo padre, nunca
6# una cola hermana.
7#
8# GANA LA VARIANTE DE COSMIC, y no por gusto: era la única cuyo cierre ya resolvía ENTERO contra el
9# corpus (`glib`, `pcre2` y `dbus-shared` del catálogo padre). Subirla no cambió una sola arista ⇒
10# mismo ArtifactHash y cache hit. Las de KDE y GNOME resolvían `glib-shared`/`pcre2-shared` y la
11# glib SOMBRA de `incoming-gnome`, que no existen en el corpus.
12#
13# ══ POR QUÉ ESTO NO REPITE EL EPISODIO DE LAS DOS GLIB, medido antes de moverla ═════════════════
14# La objeción obvia es la que la propia cabecera de la copia de cosmic dejó escrita: dos artefactos
15# del mismo nombre con bytes distintos, hidratados en la misma imagen, ponen dos registros de GType
16# en un proceso. Acá no aplica, y se comprobó leyendo los artefactos sellados, no razonando:
17#
18#     ningún pipewire de los tres enlaza glib. Los NEEDED de las tres variantes son exactamente
19#     `libdbus-1.so.3`, `libpulse.so.0` y `libsndfile.so.1`. glib entraba sólo como Requires
20#     transitivo de pkg-config, en tiempo de BUILD.
21#
22# ⇒ `libpipewire-0.3.so` es glib-free y se puede compartir entre las cuatro imágenes.
23#
24# Lo que sí queda enlazado por SONAME es `libpulse.so.0`, y eso es correcto: en runtime lo resuelve
25# la libpulse de CADA imagen, que es ABI-compatible (misma 0.24.3). Enlazar contra una variante y
26# correr contra otra es legítimo cuando lo que cruza es un SONAME; lo que NO se puede es hidratar
27# dos artefactos distintos en la misma ruta.
28#
29# ⚠ Y POR ESO `pulseaudio` NO SE PROMOVIÓ, aunque sea la dep de al lado: ahí la objeción SÍ aplica.
30# `libpulse-mainloop-glib.so.0.0.6` mide 45.712 bytes en la variante de GNOME (NEEDED
31# `libglib-2.0.so.0`) y 2.455.600 en la del corpus, que se traga la glib ESTÁTICA del catálogo. Meter
32# ésa en el proceso de gnome-shell —que ya carga la glib sombra dinámica— es literalmente el cuadro
33# de colord. Las tres pulseaudio se quedan donde están y eso está bien: son variantes deliberadas,
34# no duplicados.
35# pipewire 1.2.7 — EL SERVIDOR DE AUDIO DE LA DISTRO. Decisión tomada por el usuario (2026-07-29):
36# **PipeWire, no PulseAudio.** Hasta hoy el corpus tenía sólo el CLIENTE libpulse y el demonio quedaba
37# sin decidir; esta receta cierra esa pregunta abierta.
38#
39# CÓMO CONVIVE CON EL `pulseaudio` DE ESTA MISMA COLA, que es lo que hace que la decisión no rompa
40# nada: `pulseaudio` se construyó `-Ddaemon=false` (sólo cliente, para que gvc pueda HABLAR el
41# protocolo) y pipewire va con `-Dlibpulse=enabled`, que produce **`pipewire-pulse`**: un servidor que
42# habla el protocolo de PulseAudio. O sea que gnome-shell sigue usando libpulse sin saber que del otro
43# lado hay PipeWire. Elegir PipeWire NO obligó a tocar una línea del cliente.
44#
45# ⚠ LLEVA UN PARCHE: `pipewire-sound-initialized.patch`. SPA descartaba TODAS las tarjetas de sonido
46# porque exige la propiedad `SOUND_INITIALIZED`, que **no la pone el kernel sino udevd al correr sus
47# reglas** — y acá el udev es libudev-zero, que lee /sys directo y no ejecuta reglas por diseño. El
48# síntoma era un `wpctl status` con `Devices:` vacío, IDÉNTICO al de no tener ALSA en el kernel. Ver el
49# parche: dice por qué se toca pipewire (radio 2) y no libudev-zero (radio 51), y por qué quitar ese
50# guarda es legítimo (protege de una carrera contra udevd que sin udevd no existe).
51#
52# ⚠ GOTCHA DEL LAB, aprendido a la mala el 2026-07-30: **los BYTES del fichero `.patch` entran al
53# ArtifactHash** (`Recipe::hash_inputs` lee el patch entero, no sólo su diff), así que **editar la PROSA
54# de un parche re-sella el artefacto y todo lo que dependa de él**. Corregir un comentario obsoleto en
55# `pipewire-sound-initialized.patch` re-hasheó pipewire y arrastró wireplumber.
56# La distinción que conviene tener presente: **comentar una RECETA es gratis** —`hash_inputs` sólo toma
57# source/compiler/target/link/flags/fases/deps, no los comentarios del TOML— **y comentar un PARCHE
58# cuesta un rebuild**. Si hay que corregir el relato de un parche, hacerlo cuando se pueda pagar.
59#
60# ⚠ SIN GESTOR DE SESIÓN todavía (`-Dsession-managers=[]`). PipeWire arranca y acepta clientes —lo que
61# quita el `Gvc-WARNING: Failed to connect context: Connection refused` del shell— pero sin
62# wireplumber no enumera ni enruta dispositivos: no hay política de qué entra y qué sale. Para una VM
63# sin tarjeta de sonido alcanza; **para audio de verdad falta autorar `wireplumber`** (Lua + su propio
64# cierre). Queda dicho para no confundir «el shell conecta» con «hay sonido».
65#
66# POR QUÉ UNA COPIA EN ESTA COLA Y NO REUSAR incoming-kde/pipewire.toml: los dos cierres traen glib y
67# **NO son la misma glib** — `incoming-kde/glib-shared` y `incoming-gnome/glib` producen
68# `libglib-2.0.so.0.8800.1` con bytes DISTINTOS (verificado con `cmp`), y comparten 370 ficheros de
69# rutas. Hidratar la receta de KDE en el rootfs de GNOME pondría las dos en la misma ruta, y una
70# ganaría por orden de proyección: dos registros de GType en el mismo proceso, que es el cuadro que ya
71# costó el episodio de colord. La copia resuelve contra la glib de la isla dinámica.
72#
73# EL PRECIO DE LA COPIA, medido: cambian `glib-shared`→`glib`, `dbus`→`dbus-shared` y
74# `pcre2-shared`→`pcre2`, así que el ArtifactHash NO coincide con el de KDE y se reconstruye. Lo que
75# SÍ fue gratis es `alsa-lib`: su única dep es `pkgconf`, que resuelve al catálogo PADRE, ⇒ copiarla
76# dio **hash idéntico y cache hit** (b3:93cae411). Es la regla de [[frente-gnome]] sobre duplicar
77# recetas entre colas, aplicada con la medición delante.
78#
79# Fuente del gitlab.freedesktop.org (como networkmanager/modemmanager).
80# Recortado al core + SPA: sin jack/v4l2/
81#
82# ══ ✅ BLUEZ5 ENCENDIDO EL 2026-10-21 — y la razón de apagarlo era exagerada ═══════════════════
83# Esta nota decía «sin bluez5 (cadena de códecs aptx/ldac/aac/opus enorme)». La cadena existe y es
84# OPCIONAL: leyendo `spa/meson.build:49-53`, las deps DURAS de `bluez5=enabled` son cinco —
85# `bluez >= 4.101`, `gio-2.0`, `gio-unix-2.0`, `glib-2.0` y `sbc`— y las cuatro primeras ya
86# estaban. Faltaba UNA receta, `sbc`, que es el único códec que A2DP exige (aptX/LDAC/AAC son
87# mejoras de calidad que se pueden añadir después sin tocar esto).
88#
89# **Qué se arregla:** el bluetooth EMPAREJABA y NO SONABA. `bluez` trae `bluetoothctl` y su daemon,
90# y los cuatro escritorios habilitan el servicio — pero sin el plugin SPA bluez5, PipeWire no ve el
91# auricular como salida. `/usr/lib/spa-0.2/` traía alsa, audioconvert, aec… y CERO bluez. Ninguno
92# de los dos paquetes estaba roto y la capacidad no existía.
93#
94# ⚠ **Esto rearma 19 recetas**, entre ellas las TRES de Gecko (firefox, firefox-instrumentado,
95# waterfox) ⇒ la cadena PGO entera. Era la razón de no hacerlo «de paso», y se hizo a pedido
96# explícito del usuario.
97# libcamera/gstreamer/ffmpeg/vulkan/roc/lv2/avahi/x11/echo-cancel-webrtc/libusb/flatpak/snap.
98# systemd/logind/selinux/rtkit off (no hay systemd acá). alsa=enabled + sndfile=enabled + libpulse=enabled
99# (el emulador pulse: deja que los clientes libpulse hablen con pipewire) — las 3 ya selladas.
100#
101# SIN ncurses a propósito: la única consumidora es la tool `pw-top` (monitor de terminal), guardada por
102# `if ncurses_dep.found()`. El ncurses del catálogo es static-only y su .pc arrastra `-static` al link ⇒
103# "error: using shared libraries requires dynamic linking" al enlazar pw-top contra libpipewire-0.3.so.
104# Quitando la dep, meson saltea pw-top y el resto construye.
105name    = "pipewire"
106version = "1.2.7"
107license = "MIT"
108
109[source]
110tarball = "https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/1.2.7/pipewire-1.2.7.tar.gz"
111sha256  = "e75568ed18bcbe75e9779af57cb9cc256fd7ebfaadc12bb347a0717055d1d3a9"
112patches = ["pipewire-sound-initialized.patch"]
113
114[build]
115compiler = "zig-cc"
116target   = "x86_64-linux-musl"
117link     = "dynamic"
118flags    = []
119
120[build.phases]
121configure = '''
122PYTHONPATH=/usr/lib/python3.12/site-packages meson setup output --prefix=/usr --buildtype=release \
123  -Ddefault_library=shared \
124  -Ddocs=disabled -Dman=disabled -Dexamples=disabled -Dtests=disabled \
125  -Dgstreamer=disabled -Dgstreamer-device-provider=disabled \
126  -Dsystemd=disabled -Dlogind=disabled -Dsystemd-system-service=disabled \
127  -Dsystemd-user-service=disabled -Dselinux=disabled -Dlegacy-rtkit=false \
128  -Dpipewire-jack=disabled -Dpipewire-v4l2=disabled -Djack=disabled -Dv4l2=disabled \
129  -Dlibcamera=disabled -Dbluez5=enabled -Dffmpeg=disabled -Dvulkan=disabled \
130  -Droc=disabled -Dlv2=disabled -Davahi=disabled -Dx11=disabled -Dx11-xfixes=disabled \
131  -Decho-cancel-webrtc=disabled -Dlibusb=disabled -Dflatpak=disabled -Dsnap=disabled \
132  -Dlibmysofa=disabled -Dsdl2=disabled -Dopus=disabled -Dlibffado=disabled -Dcompress-offload=disabled \
133  -Dlibcanberra=disabled -Dgsettings=disabled -Dreadline=disabled -Draop=disabled -Davb=disabled \
134  -Dsession-managers=[] \
135  -Dalsa=enabled -Dsndfile=enabled -Dlibpulse=enabled -Ddbus=enabled -Dudev=enabled \
136  -Dpipewire-alsa=enabled \
137  -Dspa-plugins=enabled -Daudioconvert=enabled -Daudiomixer=enabled -Dcontrol=enabled \
138  -Dvideoconvert=enabled -Dsupport=enabled
139'''
140compile = "PYTHONPATH=/usr/lib/python3.12/site-packages ninja -C output"
141install = '''
142set -e
143PYTHONPATH=/usr/lib/python3.12/site-packages DESTDIR=/out meson install -C output --no-rebuild
144# ── CABLEAR EL PLUGIN ALSA, que meson instala pero NO enchufa ──────────────────────────────────
145# `-Dpipewire-alsa=enabled` deja el plugin en /usr/lib/alsa-lib (que es donde alsa-lib lo busca, OK)
146# y su configuración en /usr/share/alsa/alsa.conf.d — un directorio que **alsa-lib no lee**. Su
147# `alsa.conf` sólo carga /var/lib/alsa/conf.d, /usr/etc/alsa/conf.d y /etc/alsa/conf.d (verificado
148# en el artefacto sellado, no supuesto). Sin este enlace el plugin está instalado y no lo usa nadie:
149# el caso clásico de "sellado y aun así inerte".
150# Es lo mismo que hace el subpaquete `pipewire-alsa` de Alpine. Los DOS ficheros, no uno:
151#   50-pipewire.conf          define `pcm.pipewire` — hace que el destino EXISTA
152#   99-pipewire-default.conf  define `pcm.!default` — hace que sea el destino POR DEFECTO, que es
153#                             lo que redirige a un cliente que abre "default" (mpv --ao=alsa)
154mkdir -p /out/etc/alsa/conf.d
155ln -sf /usr/share/alsa/alsa.conf.d/50-pipewire.conf         /out/etc/alsa/conf.d/50-pipewire.conf
156ln -sf /usr/share/alsa/alsa.conf.d/99-pipewire-default.conf /out/etc/alsa/conf.d/99-pipewire-default.conf
157
158# ── ⚠ GUARDIÁN DEL PLUGIN BLUEZ5 ──────────────────────────────────────────────────────────────
159# `-Dbluez5=enabled` hace que meson FALLE si le faltan las deps, pero no protege del caso que de
160# verdad importa acá: que el plugin se construya y **no se instale** donde PipeWire lo busca. Sin
161# el `.so` en `/usr/lib/spa-0.2/` el servidor arranca perfecto y el auricular no es una salida —
162# que es exactamente el estado que esta receta viene a dejar atrás, así que se comprueba.
163bl=$(find /out/usr/lib/spa-0.2 -name 'libspa-bluez5.so' 2>/dev/null | head -1)
164[ -n "$bl" ] || { echo "pipewire: NO quedó libspa-bluez5.so en /usr/lib/spa-0.2 — el bluetooth seguiría sin sonar" >&2; ls /out/usr/lib/spa-0.2/ >&2; exit 1; }
165# Y que traiga DENTRO el códec: el plugin enlaza sbc estáticamente, así que sus símbolos tienen que
166# estar definidos (sección ≠ UND). Pedir sólo el fichero dejaría pasar un plugin sin códec, que
167# cargaría y no podría negociar A2DP.
168command -v nm >/dev/null && {
169  n=$(nm -D --defined-only "$bl" 2>/dev/null | grep -c 'sbc_' || true)
170  [ "${n:-0}" -gt 0 ] || { echo "pipewire: libspa-bluez5.so no DEFINE ningún símbolo sbc_* — el códec no entró y A2DP no se podría negociar" >&2; exit 1; }
171  echo "pipewire: libspa-bluez5.so presente con $n símbolo(s) sbc_* definidos"
172}
173'''
174
175[deps]
176build = ["meson", "samurai", "python3", "pkgconf", "dbus-shared", "expat", "alsa-lib", "libsndfile",
177  "pulseaudio", "glib", "pcre2", "libffi", "libudev-zero", "zlib-shared", "gettext-tiny",
178  # `bluez` y `sbc` son las dos piezas de `-Dbluez5=enabled`. `bluez` aporta su `bluez.pc` (5.87) y
179  # `sbc` el códec A2DP obligatorio; `gio-2.0`/`gio-unix-2.0`/`glib-2.0` los publica ya `glib`.
180  "bluez", "sbc"]
181
182# ── LOS SERVICIOS QUE ESTE PAQUETE TRAE (SDD 30) ────────────────────────────────────────────────
183# `scope = "session"`, y eso es una afirmación con consecuencia: estos NO van en el `genesis` de la
184# seed. Necesitan `XDG_RUNTIME_DIR` y un usuario logueado, así que arrancarlos al boot los pondría a
185# correr antes de que exista ninguna de las dos cosas.
186#
187# ⚠ Y hoy NO HAY QUIÉN LOS ENTREGUE a arje fuera de mirada: ahí el compositor arma las Cards de
188# sesión (`mirada-compositor/src/session.rs`, `requires = [wayland_floor()]`) y las pasa a PID 1 por
189# `RunCard`; mutter y kwin no tienen esa integración y sus demonios de sesión los sigue lanzando el
190# script. Se declaran igual para que el hueco se CUENTE —`targets.py --servicios` avisa por cada
191# uno— en vez de quedar omitido, que es como se ve un hueco que nadie escribió.
192[[service]]
193label   = "pipewire"
194id      = "01HQAR53D4M2NBV8KZTYXFQA26"
195exec    = "/usr/bin/pipewire"
196scope   = "session"
197restart = { initial_ms = 500, max_ms = 20000 }
198
199# El puente que habla protocolo PulseAudio contra el servidor. Depende de que `pipewire` ya haya
200# creado su socket: hoy el script lo espera con un bucle sobre `$XDG_RUNTIME_DIR/pipewire-0`, que es
201# justo la readiness que arje no tiene por unidad (SDD 30 §5).
202[[service]]
203label   = "pipewire-pulse"
204id      = "01HQAR53D4M2NBV8KZTYXFQA27"
205exec    = "/usr/bin/pipewire-pulse"
206scope   = "session"
207restart = { initial_ms = 500, max_ms = 20000 }