La forma normal de tener una pantalla de inicio de Android programable es quedarte con el launcher que trajo el móvil y atornillarle al lado una app de automatización tipo Tasker. El combo está maduro, está en la Play Store, tiene detrás una década de recetas de la comunidad y, para la mayoría de la gente, cumple sin que nadie tenga que tocar un build system.
Yo fui por el otro lado y construí Dikciz, un launcher que es la plataforma de automatización en sí, no algo que la automatización toquetea desde fuera. Widgets HTML con un bridge de JavaScript, scripts Lua en sandbox que reaccionan a los eventos del móvil, un WebSocket y un servidor MCP en el loopback del dispositivo, y todo configurado con ficheros planos en /sdcard/Dikciz/. Así se comparan de verdad los dos enfoques, punto por punto.
Sobre Qué Está Construido
Un launcher de serie es el producto cerrado de alguien, y la app de automatización de encima es el de otro. Te comes dos calendarios de releases y ninguno sabe que el otro existe.
Dikciz parte de Fossify Launcher, GPLv3, porque escribir un launcher desde cero es ahogarte en casos límite aburridísimos con iconos, drawers y el rol de inicio de Android. Todo el código de Fossify se queda intacto como base upstream. El código de Dikciz vive aislado en un product flavour de Gradle bajo app/src/dikciz/ (más source sets combinados como app/src/dikcizDebug/), así que las fuentes, los recursos y las traducciones de Fossify siguen siendo reemplazables. Una release de Fossify más nueva entra por una ruta controlada:
make dikciz-upstream-compare DIKCIZ_UPSTREAM_DIR=path/to/fossify-source
make dikciz-upstream-refresh-dry-run DIKCIZ_UPSTREAM_DIR=path/to/fossify-source
DIKCIZ_UPSTREAM_REFRESH=1 make dikciz-upstream-refresh \
DIKCIZ_UPSTREAM_DIR=path/to/fossify-sourcePrepara la fuente aparte, verifica una copia de seguridad, conserva las rutas que son de Dikciz y se para en seco ante una colisión o un patch fallido, y se niega a reemplazar nada si el marcador de la release de Fossify elegida no es más nuevo que el que hay en el repo. Nada de esa mierda de “mergeamos y a rezar”.
El build que ejecutas de verdad es dikcizDebug, paquete eu.psyb0t.dikciz.launcher.debug. Es el que mantiene encendidos los control planes, la config pública, los logs, el bridge HTML y toda la superficie de automatización.
Dónde Vive la Config
Con un launcher de serie, tu pantalla de inicio es una base de datos privada que no ves nunca. Te dan una pantalla de ajustes, y la app de automatización guarda sus propios perfiles en su propio formato. Hacer diff de cualquiera de los dos, guardarlo como texto o generarlo desde un script va de incómodo a imposible.
En Dikciz todo lo que el usuario puede editar vive bajo /sdcard/Dikciz/, con config.json como raíz. Páginas, widgets, scripts, políticas de automatización, temas, fuentes, fondo de pantalla, paquetes de la biblioteca de widgets, config de auth y logs tienen cada uno su ruta. Dikciz vigila el árbol y aplica los cambios válidos sin reiniciar la app. Empujas un fichero por ADB y cambia el inicio. Mételo en git si te da la gana.
La config incluida trae límites duros para que una edición hecha con el culo no se coma el móvil: 24 páginas como máximo, 128 widgets por página, 8 widgets HTML por página, 1 MiB de config, 192 KiB por documento de widget HTML. Si aun así algo se va a la mierda, el safe mode abre la primera página incluida sin sobrescribir tu config guardada, deja los diagnósticos y los logs a mano, y bloquea las ediciones del inicio y los cambios en el dispositivo hasta que lo arregles.
Cómo Montar la Pantalla de Inicio
Los launchers de serie te dan una cuadrícula y un gesto de arrastrar. Bien para humanos, inútil para cualquier cosa que quiera referirse a un sitio de la pantalla por su nombre.
Las páginas de Dikciz están en una cuadrícula de dos ejes y se direccionan por coordenadas: 1H1V es el inicio, 2H1V es una a la derecha, 1H2V es una abajo. Cada página es una cuadrícula nativa con un viewport fijo que no crece nunca. Añades un widget y ocupa la primera celda libre, de izquierda a derecha y luego hacia abajo. ¿Página llena? Se rechaza. Nada de solapes, nada de iconos empujados fuera de la pantalla a escondidas.
Qué se puede poner en una página:
- Widgets HTML con HTML, CSS y JavaScript locales y estado persistente, redimensionables, con acciones nativas de pulsación larga y altura ajustada al contenido.
- Accesos directos nativos de apps y grupos de apps. Sueltas un acceso directo encima de otro y tienes un grupo.
- AppWidgets de Android de terceros de verdad. Conservan su propio
AppWidgetHostViewy nunca se meten a presión dentro de un WebView. - Un dashboard nativo de scripts que lista tus scripts Lua con estado, última ejecución, logs y controles para activar, desactivar y editar.
Temas, fuentes (Inter, Space Grotesk, JetBrains Mono y Comic Neue vienen incluidas, cómo no), fondo de pantalla, estilos de tarjeta y bloqueos completan el conjunto. La marca de Dikciz junto al raíl de páginas abre el drawer de apps, añadir a la página, gestionar la página y los ajustes. La lupa de al lado hace la búsqueda de páginas, listando cada página como Name (address), por ejemplo Notes (2H1V). El drawer de apps es nativo, no un WebView, y sus acciones cubren añadir acceso directo, info de la app, desinstalar y forzar detención, que solo aparece cuando el dispositivo concede root.
Cómo Escribir Tu Propio Widget
En el lado de serie, un widget a medida significa o escribir una app Android entera con AppWidgetProvider y RemoteViews, o instalar otra app más de hacer widgets con su propio editor. Las dos funcionan. Las dos son un puto coñazo para una tarjeta que enseña tres números.
Un widget de Dikciz es una página web. Cada widget HTML recibe window.dikciz, window.selfWidget y un evento dikciz-ready. A partir de ahí puede:
- llamar a cualquier comando del launcher con
dikciz.command(type, fields), los mismos comandos que usan los clientes WebSocket y MCP - ejecutar acciones tipadas con
dikciz.dispatch({ actions }) - leer el mapa de páginas con
dikciz.pages()ydikciz.home() - parchear el estado de otro widget con
dikciz.patchWidgetState(address, values) - cambiar el DOM renderizado sin guardarlo, con
dikciz.patchDom(address, selector, values) - publicar eventos y suscribirse a ellos con
dikciz.emitEventydikciz.onEvent
const result = await dikciz.patchDom(
"1H1V-device-card",
"#temperature",
{
text: "18 C",
className: "fresh",
attributes: { "data-updated": "now" }
},
);Los parches de estado actualizan el documento existente. No recargan el WebView, así que el widget no parpadea como el letrero de un motel de mala muerte cada vez que cambia un valor.
Cómo Reaccionar a los Eventos del Móvil
Aquí es donde la app de automatización atornillada se gana su fama. Perfiles, contextos, tareas, una biblioteca enorme de disparadores. Su punto débil es que el launcher no tiene ni idea de que nada de eso existe, así que la automatización toquetea la pantalla de inicio desde fuera y reza para que el resultado quede bien.
En Dikciz el manejo de eventos es parte del launcher. Lua no es un tipo de widget, se ejecuta como scripts desde el árbol público de scripts, sobre LuaJ. Un script recibe un evento y devuelve un estado más una lista de acciones tipadas:
function on_event(event)
if event.type ~= "example.pipeline.request" then
return { status = "Waiting for a pipeline request" }
end
local message = event.payload.message or "Pipeline request received"
return {
status = "Updated the widget",
actions = {
{
type = "patchWidget",
widgetAddress = "1H1V-example-widget",
values = { state = { message = message } }
},
{
type = "emitEvent",
event = "example.pipeline.done",
payload = { message = "Lua finished: " .. message }
}
}
}
endLa lista de eventos es larga de cojones: hora, alarmas, batería, carga, estado térmico, pantalla encendida y apagada, conectividad, Bluetooth, sensores, ubicación, pasos de Health Connect, calendario, contactos, estado del teléfono, SMS entrantes, portapapeles, notificaciones publicadas y eliminadas, sesiones multimedia, ventanas de accesibilidad, estado de administrador del dispositivo, cambios de widgets, disparadores manuales y cualquier evento propio que te inventes.
Las acciones también son tipadas: selectPage, patchWidget, patchState, patchDom, launchApp, appAction, postNotification, sendSms, explicitIntent, emitEvent, mediaControl, mediaVolume, notificationControl, lockDevice y más. Como el script habla directamente con el launcher, patchWidget cae en un widget con nombre en una dirección con nombre, no en una aproximación de “eso de arriba a la izquierda”.
Cuánto Puede Romper un Script
Las apps de automatización suelen darle a una tarea un montón de poder: ejecutar shell, escribir ficheros, lanzar intents arbitrarios. Útil, y también justo la manera en que una receta copiada de algún foro acaba haciendo algo que nunca leíste.
Dikciz es más rácano a propósito. Cada acción y cada capacidad de eventos que usa un script tiene que estar listada en la política de automatización pública de ese script, y el runtime de Lua va enjaulado. require, package, load, loadfile, dofile, debug y print han desaparecido. Sin shell, sin sistema de ficheros, sin objetos Android en crudo. Acciones tipadas o a tomar por culo.
Los logs caen como JSON Lines acotados bajo /sdcard/Dikciz/logs/, legibles desde el dashboard, el visor de Settings, el WebSocket scriptLogs y el MCP dikciz_script_logs. Los valores con pinta de credenciales se censuran antes de llegar al fichero. Una ejecución de Lua fallida puede publicar una notificación de Android, con un periodo de silencio por script para que no te fría a spam hasta matarte.
Dejar que un Agente de IA Conduzca
Esta es la dimensión para la que construí el invento de verdad. Dale un launcher de serie a un agente LLM y la respuesta habitual es “haz una captura, adivina coordenadas, toca, reza”. Eso no es automatización, eso es un borracho con un stylus.
Dikciz ejecuta dos listeners versionados en el loopback del dispositivo:
- WebSocket en
ws://127.0.0.1:19001/v1/automation - Streamable HTTP MCP en
http://127.0.0.1:19002/mcp
Los dos llegan al mismo destino de comandos: config, snapshots semánticos de la UI e interacción, páginas, widgets, capturas, volcados de UI, diagnósticos, logs de scripts, el catálogo de apps, acciones de apps, intents de Android y comandos shell. El lado MCP expone herramientas como dikciz_snapshot, dikciz_tap, dikciz_find, dikciz_wait_for, dikciz_screenshot, dikciz_app_catalogue y dikciz_shell. Apunta un agente ahí y el agente inspecciona el launcher semánticamente en vez de adivinar píxeles.
Los comandos shell piden su por defecto. Pasa root: false y se ejecutan como el usuario Android propio de la app Dikciz. Si el root existe y se aprueba lo decide el móvil, no yo.
Activa el servicio de Accesibilidad de Dikciz y Dikciz llega también más allá de su propia pantalla de inicio: los clientes locales reciben un árbol acotado de la ventana activa de cualquier app, más accessibilityAction, accessibilityGesture y accessibilityGlobalAction. Lua recibe la misma ventana como context.accessibility.
Como “cualquier cosa en el loopback puede manejar mi móvil” es un valor por defecto estúpido para un aparato que llevas encima a todas partes, puedes exigir una contraseña bearer para los dos control planes desde los Dikciz Settings. La config pública guarda un verificador bcrypt, nunca la contraseña en claro.
El repo trae además un skill de agente dikciz-launcher que enseña a un agente a instalar el APK por ADB y a manejar el launcher, con referencias de setup y de capacidades, y un skill aparte dikciz-android-lab que manda los builds desde el código fuente y el trabajo con el emulador a android-lab. Ejecuta tu agente en algo como aicodebox y ese es todo el ciclo: el agente lee el skill, se conecta al endpoint MCP, maneja tu móvil.
Cómo Meterlo en un Móvil
Aquí el combo de serie gana de calle: tocas instalar en la Play Store y listo. Dikciz no pasa por Google Play ni va a pasar nunca. Lo compilas tú.
Yo no instalo Android Studio. Jamás. El build y el emulador corren sobre android-lab, mi montaje Docker para el SDK de Android y el emulador. El Makefile quiere las imágenes android-lab:0.13.0 y android-lab:0.13.0-emulator-api36 compiladas en local, y si falta alguna se para y te manda a las instrucciones de build de android-lab en vez de bajarte mierda por su cuenta.
git clone https://github.com/psyb0t/dikciz-launcher.git
cd dikciz-launcher
make build
make dikciz-runmake build ensambla dikcizDebug. Los contenedores de herramientas toman el número de CPUs del host Docker con un tope de 10, y otros tantos workers de Gradle. En una máquina más justa:
make build ANDROID_LAB_TOOL_CPU_LIMIT=2 ANDROID_LAB_GRADLE_MAX_WORKERS=2make dikciz-run lo instala en el emulador compartido, concede acceso a ficheros, lo pone como Android Home y reenvía los control planes al host en 19101 (WebSocket) y 19102 (MCP). La pantalla del emulador la tienes en el navegador en:
http://127.0.0.1:61326/vnc.html?autoconnect=true&resize=scaleLuego le hablas con el cliente incluido:
make control CONTROL_ARGS='websocket controlStatus --port 19101 --pretty'
make control CONTROL_ARGS='mcp-tools --port 19102 --pretty'
make control CONTROL_ARGS='websocket scriptLogs --port 19101 --pretty'Si activaste la auth, exporta DIKCIZ_REMOTE_BEARER_TOKEN desde un fichero env ignorado. No lo metas como argumento de un comando como un animal. make dikciz-reset se carga solo Dikciz y /sdcard/Dikciz y arranca el inicio de partida. make stop para el stack sin borrar el móvil guardado. El estado (caché de Gradle, identidad de firma, disco del emulador, artefactos) vive en .android-lab/.
Los tests son tests de UI contra ese mismo emulador, con objetivos acotados como make dikciz-test-accessibility o make dikciz-test-app-drawer-entry, y make dikciz-test para la suite amplia, que vuelca capturas y volcados de UI en .android-lab/shared/artifacts/ y deja el móvil otra vez en el inicio de partida al terminar, pase o falle.
Cuándo Quedarte con el Launcher de Serie y Tasker
Si quieres un móvil que funcione el día que lo compras, que se instale desde la Play Store y que tenga automatización con una GUI y años de recetas compartidas detrás, quédate con el launcher de serie y la app atornillada. Lo mismo si nunca piensas editar un fichero de config, escribir una línea de Lua o apuntar un agente a tu móvil. Dikciz te pide que lo compiles tú mismo en Docker, y solo te lo devuelve si quieres que la pantalla de inicio sea ficheros, código, un socket y un servidor MCP.
Si eso último suena a lo que te faltaba, está en github.com/psyb0t/dikciz-launcher.