# fcitx5 — el marco de métodos de entrada. Lo que falta para ESCRIBIR en chino, japonés y coreano. # # ══ POR QUÉ ENTRA ══════════════════════════════════════════════════════════════════════════════ # El 2026-10-19 entraron las fuentes Noto (`noto-fonts`, `noto-fonts-cjk`, `noto-fonts-emoji`), así # que la distro pasó de mostrar cuadraditos a LEER esos alfabetos. Escribirlos es otra cosa: un # teclado tiene cien teclas y el chino tiene decenas de miles de caracteres, así que hace falta un # motor que convierta lo que se teclea en lo que se escribe. `censo-distro.py` marcaba la categoría # «escribir en esos idiomas» 5 de 6 ausente. # # ══ LO QUE SE MIDIÓ ANTES DE ESCRIBIR ESTO ═════════════════════════════════════════════════════ # # **1. NO hay muro estructural, y era la primera duda.** El camino de un IME en Wayland son dos # protocolos que el COMPOSITOR tiene que implementar; si no están, ningún paquete sirve y esto # habría sido trabajo tirado (como `kitty`, que no entra por falta de GL de escritorio). # `mirada-compositor` los registra los dos — `setup.rs:600-602`: # smithay::wayland::text_input::TextInputManagerState ⇒ zwp_text_input_v3 # smithay::wayland::input_method::InputMethodManagerState ⇒ zwp_input_method_v2 # # **2. Las dos deps que faltaban eran de COLA, no inexistentes.** `extra-cmake-modules` vivía en # `incoming-kde` y `iso-codes` en `incoming-gnome`, y una receta del corpus resuelve sibling-first y # después el catálogo padre — **nunca una cola hermana**. Se promovieron al corpus en el commit # anterior, verificando que es gratis: los 109 dependientes de ecm en incoming-kde conservan su hash # uno por uno. # # **3. Cinco deps se caen con dos perillas.** El `CMakeLists.txt` de upstream mete XCB y # `XCBImdkit` como REQUIRED **sólo dentro de `if(ENABLE_X11)`** (líneas 138-139), y `enchant2` sólo # con `ENABLE_ENCHANT`. Con las dos en Off desaparecen `xcb-imdkit`, `xcb-util-keysyms`, # `xcb-util-wm`, `libxkbfile` y `enchant2`. Y apagar X11 no es un recorte: **esta distro es # Wayland-only**, el X11 de fcitx5 sería código muerto. # # ══ LAS PERILLAS, UNA POR UNA ══════════════════════════════════════════════════════════════════ # ENABLE_X11=Off Wayland-only por decisión de distro; se lleva cuatro deps # ENABLE_ENCHANT=Off predicción de palabras por diccionario: no la pide nadie y trae enchant2 # USE_SYSTEMD=Off acá el init es `arje-zero`. Upstream dice que cae a libuv/libdbus, que SÍ # están — dejarlo en Auto haría que el build busque algo que no existe # ENABLE_TEST=Off el test suite de fcitx5 no se corre acá. # ENABLE_TESTING_ADDONS=On ⚠ **ESTABA EN Off Y ROMPÍA A SUS CONSUMIDORES — corregido 2026-10-20.** # Su `option()` dice «Build testing frontend, im and ui addons», que se lee # como «cosas de test, fuera», y así lo puse. Pero además de los addons # instala el MÓDULO DE CMAKE que los consumidores piden: `fcitx5-hangul` # hace `find_package(Fcitx5Module REQUIRED COMPONENTS TestFrontend)` en su # CMakeLists:15, **sin condición**, y murió con # Could not find … "Fcitx5ModuleTestFrontend" … # Fcitx5ModuleTestFrontendConfig.cmake # O sea que no es opcional desde fuera: todo motor de fcitx5 lo exige, y # apagarlo deja un marco que no puede tener motores. La lección es más # general que esta receta: **una perilla que se llama «testing» puede # publicar interfaz, y la descripción del `option()` no lo dice.** Medir # qué deja de instalarse, no leer cómo se llama. # Coste: re-sellar fcitx5, radio 1 (sólo fcitx5-hangul cuelga de él). # ENABLE_DOC=Off doxygen no está en el corpus # BUILD_SPELL_DICT=Off construye `en_dict.fscd` para el corrector inglés — depende de enchant # ENABLE_LIBUUID=Off ⚠ ÉSTA ES UNA DECISIÓN DE ARQUITECTURA DEL CORPUS, no una perilla más. # `libuuid.a` (de util-linux) no es PIC, así que `libFcitx5Utils.so` no # puede enlazarlo: # ld.lld: relocation R_X86_64_32 … recompile with -fPIC # >>> libuuid_la-randutils.o … in archive /usr/lib/libuuid.a # Arreglar la librería —como se hizo con `libuv`, radio 1— acá NO se paga: # `util-linux` tiene **radio 79 y afecta a `escritorio-kde`**, y re-sellarlo # tira a deuda lo que cuelga de él. Es el precedente de `libpng` del # 2026-08-21, donde el usuario decidió no PIC-ificar el canónico porque «es # estático por diseño; eso es arquitectura del corpus». ⇒ se arregla el # CONSUMIDOR. # Y el fallback es bueno en Linux, no un parche: `uuid_p.h:103` usa # `generateUUIDFallback("/proc/sys/kernel/random/uuid")`, o sea el UUID lo # genera el KERNEL. (El aviso del comentario de upstream sobre un «dummy # self increment» es para el camino NO-Linux, que acá no se toma.) # Apagarlo también desactiva el frontend de ibus, que no usamos: el camino # de esta distro es el de Wayland. # PKG_CONFIG_ARGN=--static ⚠ **LA PERILLA QUE UN CORPUS ESTÁTICO NECESITA, y la descubrí tarde.** # Con el trío de fuentes ya PIC, el enlace de `libclassicui.so` avanzó y # murió en otra cosa: # ld.lld: error: undefined symbol: XML_ParserCreate # >>> fcxml.o:(FcConfigParseAndLoad…) in archive /usr/lib/libfontconfig.a # `XML_ParserCreate` es de expat, y expat ESTABA declarado en `[deps]`: lo # que faltaba no era el paquete sino el `-lexpat` en la línea de enlace. # Medido en el `.pc`: # fontconfig.pc → Requires.private: expat # y pkg-config entrega los `Requires.private`/`Libs.private` **sólo en modo # estático**. Sin `--static`, cmake pide las libs como si fueran compartidas # —donde la `.so` ya trae resueltas sus privadas— y para un `.a` eso es # justamente lo que falta. `PKG_CONFIG_ARGN` es la variable que el módulo # `FindPkgConfig` de cmake pasa a pkg-config, y estaba vacía en el cache. # ⇒ Es la cura de raíz para este corpus, no un `-lexpat` a mano: toda dep # estática con privadas se resuelve por el mismo camino. # # ⚠ `link = "dynamic"` **y no es opcional**: fcitx5 es un daemon cuyos métodos de entrada son # addons que carga con `dlopen` en tiempo de ejecución (`libexec/` + `lib/fcitx5/`). Un binario # `crt-static` no puede dlopen, así que estático sellaría un daemon incapaz de cargar un solo motor # — sellado, con contenido, reproducible e inerte. # # ══ ⚠ ESTO ES EL MARCO, NO UN MÉTODO DE ENTRADA ════════════════════════════════════════════════ # fcitx5 solo no escribe chino: es la plomería (el daemon, el protocolo, la UI de la barra de # candidatos). Cada idioma es un addon APARTE y son recetas que todavía no existen: # fcitx5-chinese-addons pinyin (arrastra libime y libpinyin) # fcitx5-anthy japonés (arrastra anthy) # fcitx5-hangul coreano (arrastra libhangul) # fcitx5-rime los tres (arrastra librime, que es grande) # Declarar sólo esto en un perfil daría una barra de IME que no convierte nada. **Por eso NO se # declara en ningún perfil en este commit**: primero el marco construye, después un motor, y recién # entonces el par se declara junto. La alternativa —declarar el marco ya— es exactamente la figura # de `crun` sin podman y de `cups` sin filtros que este catálogo tiene documentada tres veces. # ⚠ **SUBE A 5.1.21, Y ESA VERSIÓN EXACTA ES EL RESULTADO DE UNA MEDICIÓN — 2026-10-20.** # Upstream va por 5.1.23. El techo que bloqueaba el chino era `librsvg-2.0` (REQUIRED dentro de # `if (ENABLE_X11 OR ENABLE_WAYLAND)`, o sea inevitable acá), y la pregunta que no me había hecho # era **en qué versión entró**. Medido tag a tag: # # fcitx5 librsvg yoga nlohmann # 5.1.20 no sí sí # 5.1.21 no sí sí ← acá # 5.1.22 SÍ sí sí # 5.1.23 SÍ sí sí # # `libime` pide `Fcitx5Utils ≥5.1.20` y `librsvg` entró en **5.1.22** ⇒ **5.1.21 es la única versión # que destraba el motor de pinyin sin tocar nada de GNOME.** Y `fcitx5-chinese-addons` encaja en la # misma ventana: su 5.1.15 exige el marco ≥5.1.22, pero su **5.1.13** pide ≥5.1.20 (y libime # ≥1.1.14, que nuestro 1.1.17 cumple). Dos rangos que se solapan en un punto, y ese punto es éste. # # ⚠ **LA RUTA POR GNOME QUE ESTO EVITA, CON SU COSTE MEDIDO** — promoví y revertí de verdad, no lo # estimé: las 7 recetas de la raíz de introspección cambiaban **5 de sus 7 hashes** y los # dependientes de GNOME **los 35**. La causa no es «faltaba promover unas recetas»: la cola de GNOME # es un **mundo paralelo dinámico+introspectado**. Su `glib` es `default_library=both` contra la # `static` del corpus —misma versión 2.88.1, otro hash—, y `glib-introspected` es una **SEGUNDA # pasada** de glib que depende de la primera Y de `gobject-introspection`, con **31 recetas de la # cola colgando de ella**. Promover esa raíz la haría construir contra la glib ESTÁTICA del corpus. # Esa decisión sigue viva, pero **ya sólo para `orca`** (`at-spi2-core` + `py3-gobject`), no para el # chino. # # ══ LOS CUATRO REQUISITOS NUEVOS DEL SALTO 5.1.14 → 5.1.21 ═════════════════════════════════════ # Comparando los `find_package`/`pkg_check_modules` de los dos CMakeLists, no de los makedepends de # otra distro — que es el error que cometí inventando una dep `fmt` para esta misma receta: # · `nlohmann_json` ya en el corpus (3.12.0) — y **reemplaza a `JsonC`**, que sale # de las deps porque 5.1.21 ya no lo busca # · `WaylandProtocols ≥1.46` ya cubierto (tenemos 1.48) # · `PlasmaWaylandProtocols ≥1.20` **se apaga**. Sólo existe en `incoming-kde`, pero no hace falta # traerlo: con `USE_SYSTEM_PLASMA_WAYLAND_PROTOCOLS=Off` la rama # `else()` usa su XML **vendorizado en el árbol** (20 KB, # comprobado presente en el tarball). Leer el `else()` antes de # escribir una receta: tercera vez que esa costumbre ahorra una. # · `yoga` **receta nueva** — es un SUBMÓDULO git y el tarball lo trae # vacío (el único directorio vacío del tarball), así que # `USE_SYSTEM_YOGA=On` no es una preferencia: sin él # `add_subdirectory(third_party/yoga/yoga)` falla al CONFIGURAR. # ══ ⚠ Y UN QUINTO REQUISITO QUE NO SALE DE NINGÚN CMakeLists: `std::osyncstream` ════════════════ # Los cuatro de arriba se encuentran comparando los dos CMakeLists. **Éste no se ve ahí y mató el # build**: `src/lib/fcitx-utils/log.cpp` usa `std::osyncstream` (C++20, salida sincronizada), y el # compilador cortó con # # log.cpp:38:30: error: no type named 'osyncstream' in namespace 'std' # # Entró en **5.1.20** —o sea en TODAS las versiones de la ventana, no hay un tag que lo evite— y no # estaba en 5.1.14, que es por lo que esta receta nunca lo había pisado. # # ⚠ **Lo primero que supuse era falso: no es que falte el header.** `` está en la libc++ # de zig. Lo que pasa es que la clase está VALLADA (`__config:54`): # # #if defined(_LIBCPP_ENABLE_EXPERIMENTAL) || defined(_LIBCPP_BUILDING_LIBRARY) # # define _LIBCPP_HAS_EXPERIMENTAL_LIBRARY 1 # … # #define _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM _LIBCPP_HAS_EXPERIMENTAL_LIBRARY # # y `` sólo declara `basic_osyncstream` si ese macro está en 1. ⇒ la cura es **un # define**, no un parche a la fuente ni una biblioteca extra: comprobado que el header no trae # ninguna declaración fuera de línea (`_LIBCPP_EXPORTED_FROM_ABI`, `extern template`), así que es # header-only y **no hace falta `-lc++experimental`**. # # Es la familia de [[cxx-runtime-debe-coincidir]]: zig trae libc++, y libc++ marca `` # como experimental por estabilidad de ABI en su dylib — una preocupación que acá no aplica, porque # cada artefacto enlaza su runtime y no compartimos una libc++ de sistema. # # ✅ **Y el define va SÓLO acá, que es el dato que evita cinco recetas tocadas**: se comprobó que # `osyncstream` aparece **únicamente** en `log.cpp` y en NINGUNA cabecera pública (`log.h` incluida), # así que `fcitx5-hangul`, `fcitx5-anthy`, `libime` y los addons compilan contra las cabeceras # instaladas sin enterarse. Si algún día upstream lo subiera a un header, el fallo saldría en los # consumidores y no acá — y esta nota es lo que haría reconocerlo. # # Lo que este salto arrastra: `fcitx5-hangul` y `fcitx5-anthy` rearman, porque dependen del marco. # Son dos recetas del corpus y el coreano y el japonés ya estaban sellados — se reconstruyen y se # vuelve a comprobar que sigan dando su addon. name = "fcitx5" version = "5.1.21" license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/fcitx/fcitx5/archive/refs/tags/5.1.21/fcitx5-5.1.21.tar.gz" sha256 = "8211fe5996db22254e5df9617cbd45873ae7fab82e7e0c42bde5a197299d1276" [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "dynamic" flags = [] [build.phases] configure = ''' ZW="$PWD/.zwrap"; mkdir -p "$ZW" printf '#!/bin/sh\nexec /opt/zig/zig cc -mcpu=baseline "$@"\n' > "$ZW/cc" printf '#!/bin/sh\nexec /opt/zig/zig c++ -mcpu=baseline "$@"\n' > "$ZW/cxx" chmod +x "$ZW/cc" "$ZW/cxx" # ⚠ **BORRAR `build/` ANTES DE CONFIGURAR, Y NO ES HIGIENE: ES CORRECCIÓN.** El árbol de fuentes # (`work/sources/-`) se REUSA entre builds — es caché por diseño—, así que un `build/` # de un intento anterior sobrevive con su `CMakeCache.txt`. Y `pkg_check_modules` cachea su # resultado: en la segunda corrida no vuelve a preguntar a pkg-config y **no recrea el target # importado**, así que `PkgConfig::Cairo` sigue ausente aunque el `.pc` ya resuelva. # # Medido el 2026-10-19 y cuesta un viaje entero: tras arreglar las deps que faltaban de verdad, el # build volvió a morir con EL MISMO error en **2 segundos** —«Generating done (0.0s)»— reusando el # cache del fallo anterior. Un primer fallo envenena todos los reintentos y el mensaje no lo dice: # es la figura de [[cache-congela-regresiones]] dentro de cmake. # # ⚠ Y esto NO es particular de fcitx5: toda receta de este repo que haga `cmake -B build` sin # limpiar tiene el mismo defecto latente. No se nota mientras el primer intento salga bien. rm -rf build cmake -B build -G Ninja -Wno-dev \ -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" -DCMAKE_ASM_COMPILER="$ZW/cc" \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ -DENABLE_X11=Off -DENABLE_ENCHANT=Off -DUSE_SYSTEMD=Off \ -DENABLE_TEST=Off -DENABLE_TESTING_ADDONS=On -DENABLE_DOC=Off \ -DBUILD_SPELL_DICT=Off -DENABLE_LIBUUID=Off \ -DUSE_SYSTEM_YOGA=On -DUSE_SYSTEM_PLASMA_WAYLAND_PROTOCOLS=Off \ -DCMAKE_CXX_FLAGS=-D_LIBCPP_ENABLE_EXPERIMENTAL \ -DPKG_CONFIG_ARGN=--static ''' compile = "cmake --build build" # El guardián de presencia, por la regla 3: lo que tiene que quedar es el DAEMON y su directorio de # addons. Un `cmake --install` que deje las cabeceras y los `.cmake` y ningún binario sellaría un # artefacto con contenido y sin función — y eso llega hasta el final diciendo que todo fue bien. install = ''' set -e DESTDIR=/out cmake --install build [ -x /out/usr/bin/fcitx5 ] || { echo "fcitx5: no quedó /usr/bin/fcitx5 — NO sello un marco sin daemon" >&2; exit 1; } n=$(find /out/usr/lib/fcitx5 -name '*.so' 2>/dev/null | wc -l) echo "fcitx5: daemon + $n addon(s) de fábrica" [ "$n" -gt 0 ] || { echo "fcitx5: cero addons en /usr/lib/fcitx5 — el daemon no podría cargar ni su propia UI" >&2; exit 1; } ''' [deps] # `iso-codes` y `extra-cmake-modules` son las dos promovidas al corpus para esto. # `util-linux` por `libuuid` (`find_package(LibUUID REQUIRED)`), `gettext-tiny` por `LibIntl`. # # ⚠ **EL CIERRE DE `.pc` NO ES EL DE LAS BIBLIOTECAS, Y MI PRIMERA LISTA LO IGNORÓ.** Declaré # `cairo`, `pango` y `glib`… bueno, `glib` ni eso, y el build murió al GENERAR —no al configurar— # con `Target "classicui" links to PkgConfig::Cairo but the target was not found`. # # La causa tiene dos mitades y las dos valen la pena: # # 1. `pkg_check_modules(Cairo IMPORTED_TARGET cairo)` va **SIN `REQUIRED`** (CMakeLists:162) y lo # mismo `Gio` (:173). Sin REQUIRED, pkg-config que no encuentra el módulo **no falla**: no crea # el target, el configure dice «Configuring done» y el error sale después, en `Generating`, # nombrando un target ausente en vez del `.pc` que falta. Dos errores distintos (Cairo y Gio) # con UNA sola causa, y ninguno nombra la causa. # 2. Un `.pc` falla si sus `Requires:` TRANSITIVOS no están. Medido en el artefacto: # cairo.pc Requires: zlib, libpng, fontconfig, freetype2, pixman-1 # pangocairo.pc Requires: … fribidi, harfbuzz, fontconfig, freetype2, cairo # gio-2.0.pc Requires: glib-2.0, gobject-2.0, gmodule-no-export-2.0, zlib # Yo tenía `cairo` y `zlib`, y me faltaban los otros cuatro de cairo. Declarar la biblioteca # que uno usa no alcanza: hay que declarar el cierre de su `.pc`. # # ⚠ **Y EL CIERRE TIENE MÁS DE UN NIVEL: `glib-2.0.pc` exige `libpcre2-8`.** El build volvió a # morir con el MISMO mensaje de target ausente, y el motivo real estaba cuatro líneas más arriba en # el log del configure, sin llamar la atención: # -- Checking for module 'cairo' → Found cairo, version 1.18.4 # -- Checking for modules 'pango;pangocairo' → Package 'libpcre2-8', required by 'glib-2.0', not found # O sea: cairo SÍ resolvía, y lo que faltaba era `pcre2`, dos saltos por debajo. `pkg_check_modules` # sin REQUIRED no grita, así que el único síntoma es el target que no existe. # # ⚠⚠ **Y MI PRIMERA COMPROBACIÓN DIO UN FALSO VERDE, por dos errores de método a la vez:** # 1. Usé `pkgconf --exists`, que NO valida los `Requires:` transitivos. Lo que hace cmake es # `--cflags --libs`, que sí. Hay que preguntar lo mismo que pregunta el que falla. # 2. Exporté sólo `PKG_CONFIG_PATH` y no `PKG_CONFIG_LIBDIR`. Sin LIBDIR, pkgconf **además** # busca en el camino del SISTEMA — y esta jaula es una imagen de Arch que SÍ trae pcre2. La # comprobación estaba leyendo el anfitrión. Es exactamente el apagón que `jaula-sysroot.sh` # documenta («con el sysroot a medias eso "andaba" porque lld caía al /usr/lib de Arch»). # # ⇒ Entran `glib`, `libpng`, `fontconfig`, `freetype`, `pixman`, `fribidi`, `harfbuzz`, `pcre2`, # `libffi`. Verificado con el chequeo FUERTE —`PKG_CONFIG_LIBDIR` puesto y `--cflags --libs`— sobre # los doce módulos que este build pide: `cairo`, `gio-2.0`, `gio-unix-2.0`, `pango`, `pangocairo`, # `gdk-pixbuf-2.0`, `wayland-client`, `wayland-cursor`, `xkbcommon`, `dbus-1`, `libuv`, `json-c`. # Los doce resuelven, cero fallan. # # ⚠ Esa lista de doce es de la **5.1.14** y se deja como el registro de lo que se midió entonces. En # **5.1.21 son once**: `json-c` desaparece, reemplazado por `nlohmann_json`, que es *headers-only* y # se encuentra con `find_package` y no con pkg-config — o sea que no entra en esa comprobación. build = ["cmake", "samurai", "extra-cmake-modules", "pkgconf", "iso-codes", "xkeyboard-config", "libxkbcommon", "wayland", "wayland-protocols", "cairo", "pango", "gdk-pixbuf", "glib", "libpng", "fontconfig", "freetype", "pixman", "fribidi", "harfbuzz", "pcre2", "libffi", "dbus", "libuv", "nlohmann-json", "expat", "zlib", "gettext-tiny", "util-linux", "libxml2", "yoga", "python3"]