Der normale Weg zu einem programmierbaren Android-Homescreen: Du behältst den Launcher, der mit dem Handy kam, und schraubst seitlich eine Automatisierungs-App wie Tasker dran. Die Kombi ist ausgereift, sie liegt im Play Store, sie hat ein Jahrzehnt Community-Rezepte im Rücken, und für die meisten Leute erledigt sie den Job, ohne dass irgendwer ein Build-System anfassen muss.
Ich bin den anderen Weg gegangen und habe Dikciz gebaut, einen Launcher, der selbst die Automatisierungsplattform ist, statt etwas, an dem Automatisierung von außen rumstochert. HTML-Widgets mit JavaScript-Bridge, sandboxed Lua-Skripte, die auf Handy-Events reagieren, ein WebSocket und ein MCP-Server auf dem Loopback des Geräts, und das alles konfiguriert über einfache Dateien in /sdcard/Dikciz/. So schlagen sich die beiden Ansätze wirklich, ein Punkt nach dem anderen.
Worauf Es Aufbaut
Ein Stock-Launcher ist das geschlossene Produkt von irgendwem, und die Automatisierungs-App obendrauf ist das von irgendwem anders. Du lebst mit zwei Release-Zyklen, und keiner weiß, dass der andere existiert.
Dikciz basiert auf Fossify Launcher, GPLv3, weil ein Launcher von null bedeutet, in langweiligen Randfällen rund um App-Icons, Drawer und Androids Home-Rolle zu ersaufen. Der komplette Fossify-Quellcode bleibt als Upstream-Basis unangetastet. Der ganze Dikciz-Code lebt isoliert in einem Gradle-Product-Flavour unter app/src/dikciz/ (plus kombinierte Source-Sets wie app/src/dikcizDebug/), damit Fossifys eigene Quellen, Ressourcen und Übersetzungen austauschbar bleiben. Ein neueres Fossify-Release kommt über einen geprüften Weg rein:
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-sourceDer Ablauf staget den Quellcode, prüft ein Backup, bewahrt die Pfade, die Dikciz gehören, und bleibt bei einer Kollision oder einem gescheiterten Patch sofort stehen, und er weigert sich, irgendwas zu ersetzen, solange der Release-Marker des gewählten Fossify-Releases nicht neuer ist als der eingecheckte. Kein “mergen und hoffen”-Scheiß.
Der Build, den du tatsächlich laufen lässt, ist dikcizDebug, Package eu.psyb0t.dikciz.launcher.debug. Der hält die Control Planes, die öffentliche Config, die Logs, die HTML-Bridge und die ganze Automatisierungsfläche eingeschaltet.
Wo die Config Wohnt
Bei einem Stock-Launcher ist dein Homescreen eine private Datenbank, die du nie zu Gesicht bekommst. Du kriegst einen Einstellungsbildschirm, und die Automatisierungs-App hält ihre eigenen Profile in ihrem eigenen Format. Eins von beiden zu diffen, als Text zu sichern oder aus einem Skript zu generieren, liegt irgendwo zwischen umständlich und unmöglich.
In Dikciz liegt alles, was der Nutzer bearbeiten kann, unter /sdcard/Dikciz/, mit config.json als Wurzel. Seiten, Widgets, Skripte, Automatisierungs-Policies, Themes, Schriften, Wallpaper, Pakete aus der Widget-Bibliothek, Auth-Config und Logs kriegen jeweils ihren eigenen Pfad. Dikciz beobachtet den Baum und übernimmt gültige Änderungen ohne App-Neustart. Schieb eine Datei per ADB rüber, und der Homescreen ändert sich. Pack den Baum in git, wenn du Bock hast.
Die mitgelieferte Config hat harte Limits, damit eine verkackte Änderung nicht das Handy frisst: maximal 24 Seiten, 128 Widgets pro Seite, 8 HTML-Widgets pro Seite, 1 MiB Config, 192 KiB pro HTML-Widget-Dokument. Wenn trotzdem was in die Hose geht, öffnet der Safe Mode die mitgelieferte erste Seite, ohne deine gespeicherte Config zu überschreiben, hält Diagnose und Logs erreichbar und blockiert Homescreen-Änderungen und Eingriffe am Gerät, bis du das geregelt hast.
Den Homescreen Anordnen
Stock-Launcher geben dir ein Raster und eine Ziehgeste. Okay für Menschen, nutzlos für alles, was eine Stelle auf dem Bildschirm beim Namen ansprechen will.
Dikciz-Seiten liegen auf einem Raster mit zwei Achsen und werden per Koordinate adressiert: 1H1V ist Home, 2H1V eine nach rechts, 1H2V eine nach unten. Jede Seite ist ein natives Raster mit festem Viewport, der nie wächst. Fügst du ein Widget hinzu, nimmt es die erste freie Zelle, von links nach rechts und dann nach unten. Seite voll? Dann wird das Hinzufügen abgelehnt. Keine Überlappung, keine Icons, die heimlich vom Bildschirm geschubst werden.
Was auf eine Seite kann:
- HTML-Widgets mit lokalem HTML, CSS, JavaScript und persistentem State, in der Größe änderbar, mit nativen Long-Press-Aktionen und an den Inhalt angepasster Höhe.
- Native App-Shortcuts und App-Gruppen. Zieh einen Shortcut auf einen anderen, und du hast eine Gruppe.
- Echte Android-AppWidgets von Drittanbietern. Die behalten ihre eigene
AppWidgetHostViewund werden nie in eine WebView gestopft. - Ein natives Skript-Dashboard, das deine Lua-Skripte mit Status, letztem Lauf, Logs und Steuerung zum Aktivieren, Deaktivieren und Bearbeiten auflistet.
Themes, Schriften (Inter, Space Grotesk, JetBrains Mono und Comic Neue sind dabei, na klar), Wallpaper, Kartenstile und Sperren runden das ab. Das Dikciz-Logo neben der Seitenleiste öffnet den App-Drawer, Zur-Seite-hinzufügen, Seite verwalten und die Einstellungen. Die Lupe daneben macht die Seitensuche und listet jede Seite als Name (address), etwa Notes (2H1V). Der App-Drawer ist nativ, keine WebView, und seine Aktionen decken Shortcut hinzufügen, App-Info, Deinstallieren und Force Stop ab, wobei Letzteres nur auftaucht, wenn das Gerät Root gewährt.
Dein Eigenes Widget Schreiben
Auf der Stock-Seite heißt ein eigenes Widget entweder, eine komplette Android-App mit AppWidgetProvider und RemoteViews zu schreiben, oder noch eine Widget-Bastel-App mit eigenem Editor zu installieren. Beides geht. Beides ist eine Scheißarbeit für eine Karte, die drei Zahlen anzeigt.
Ein Dikciz-Widget ist eine Webseite. Jedes HTML-Widget bekommt window.dikciz, window.selfWidget und ein dikciz-ready-Event. Von da aus kann es:
- jeden Launcher-Befehl mit
dikciz.command(type, fields)aufrufen, dieselben Befehle, die auch WebSocket- und MCP-Clients nutzen - typisierte Aktionen über
dikciz.dispatch({ actions })ausführen - die Seitenkarte mit
dikciz.pages()unddikciz.home()lesen - den State eines anderen Widgets mit
dikciz.patchWidgetState(address, values)patchen - gerendertes DOM ändern, ohne es zu speichern, über
dikciz.patchDom(address, selector, values) - Events veröffentlichen und abonnieren mit
dikciz.emitEventunddikciz.onEvent
const result = await dikciz.patchDom(
"1H1V-device-card",
"#temperature",
{
text: "18 C",
className: "fresh",
attributes: { "data-updated": "now" }
},
);State-Patches aktualisieren das bestehende Dokument. Sie laden die WebView nicht neu, also flackert das Widget nicht wie die Leuchtreklame einer Absteige, jedes Mal wenn sich ein Wert ändert.
Auf Handy-Events Reagieren
Hier verdient sich die drangeschraubte Automatisierungs-App ihren Ruf. Profile, Kontexte, Tasks, eine riesige Bibliothek an Triggern. Ihre Schwachstelle: Der Launcher hat keinen Schimmer, dass irgendwas davon existiert, also stochert die Automatisierung von außen am Homescreen rum und hofft, dass das Ergebnis richtig aussieht.
In Dikciz ist die Event-Verarbeitung Teil des Launchers. Lua ist kein Widget-Typ, es läuft als Skripte aus dem öffentlichen Skript-Baum auf LuaJ. Ein Skript bekommt ein Event und gibt einen Status plus eine Liste typisierter Aktionen zurück:
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 }
}
}
}
endDie Event-Liste ist verdammt lang: Uhrzeit, Wecker, Akku, Laden, Thermik-Status, Bildschirm an und aus, Konnektivität, Bluetooth, Sensoren, Standort, Health-Connect-Schritte, Kalender, Kontakte, Telefonstatus, eingehende SMS, Zwischenablage, gepostete und entfernte Benachrichtigungen, Media-Sessions, Accessibility-Fenster, Device-Admin-Status, Widget-Änderungen, manuelle Trigger und jedes eigene Event, das du dir ausdenkst.
Auch die Aktionen sind typisiert: selectPage, patchWidget, patchState, patchDom, launchApp, appAction, postNotification, sendSms, explicitIntent, emitEvent, mediaControl, mediaVolume, notificationControl, lockDevice und mehr. Weil das Skript direkt mit dem Launcher redet, landet patchWidget auf einem benannten Widget an einer benannten Adresse statt auf irgendeiner Annäherung an “das Ding oben links”.
Wie Viel ein Skript Kaputtmachen Kann
Automatisierungs-Apps drücken einem Task gern einen Haufen Macht in die Hand: Shell ausführen, Dateien schreiben, beliebige Intents abfeuern. Praktisch, und genau so macht ein aus irgendeinem Forum kopiertes Rezept am Ende etwas, das du nie gelesen hast.
Dikciz ist absichtlich geiziger. Jede Aktion und jede Event-Fähigkeit, die ein Skript nutzt, muss in der öffentlichen Automatisierungs-Policy genau dieses Skripts stehen, und die Lua-Runtime ist eingesperrt. require, package, load, loadfile, dofile, debug und print sind weg. Keine Shell, kein Dateisystem, keine rohen Android-Objekte. Typisierte Aktionen oder verpiss dich.
Logs landen als begrenzte JSON Lines unter /sdcard/Dikciz/logs/, lesbar im Dashboard, im Viewer in den Settings, über WebSocket scriptLogs und über MCP dikciz_script_logs. Werte, die nach Zugangsdaten aussehen, werden geschwärzt, bevor sie in der Datei landen. Ein gescheiterter Lua-Lauf kann eine Android-Benachrichtigung posten, mit einer Ruhephase pro Skript, damit er dich nicht zu Tode spammt.
Einen KI-Agenten ans Steuer Lassen
Das ist die Disziplin, für die ich das Ding eigentlich gebaut habe. Gib einem LLM-Agenten einen Stock-Launcher, und die übliche Antwort lautet “Screenshot machen, Koordinaten raten, tippen, beten”. Das ist keine Automatisierung, das ist ein Besoffener mit einem Stylus.
Dikciz betreibt zwei versionierte Listener auf dem Loopback des Geräts:
- WebSocket unter
ws://127.0.0.1:19001/v1/automation - Streamable HTTP MCP unter
http://127.0.0.1:19002/mcp
Beide treffen dasselbe Befehlsziel: Config, semantische UI-Snapshots und Interaktion, Seiten, Widgets, Screenshots, UI-Dumps, Diagnose, Skript-Logs, den App-Katalog, App-Aktionen, Android-Intents und Shell-Befehle. Die MCP-Seite stellt Tools wie dikciz_snapshot, dikciz_tap, dikciz_find, dikciz_wait_for, dikciz_screenshot, dikciz_app_catalogue und dikciz_shell bereit. Häng einen Agenten dran, und der Agent untersucht den Launcher semantisch, statt Pixel zu raten.
Shell-Befehle fragen standardmäßig nach su. Gib root: false mit, und sie laufen als Dikciz’ eigener Android-App-User. Ob Root überhaupt existiert und genehmigt wird, entscheidet das Handy, nicht ich.
Schalte Dikciz’ Accessibility-Service ein, und Dikciz greift auch über den eigenen Homescreen hinaus: Lokale Clients kriegen einen begrenzten Baum des aktiven Fensters aus jeder App, plus accessibilityAction, accessibilityGesture und accessibilityGlobalAction. Lua bekommt dasselbe Fenster als context.accessibility.
Weil “alles auf dem Loopback darf mein Handy fernsteuern” ein bescheuerter Default für ein Gerät ist, das du überall mit rumschleppst, kannst du in den Dikciz Settings ein Bearer-Passwort für beide Control Planes verlangen. Die öffentliche Config speichert einen bcrypt-Verifier, nie den Klartext.
Das Repo bringt außerdem einen Agent-Skill dikciz-launcher mit, der einem Agenten beibringt, die APK per ADB zu installieren und den Launcher zu steuern, mit Setup- und Fähigkeits-Referenzen, und einen separaten Skill dikciz-android-lab, der Builds aus dem Quellcode und Emulator-Arbeit an android-lab weiterleitet. Lass deinen Agenten in so was wie aicodebox laufen, und das ist der ganze Kreislauf: Der Agent liest den Skill, verbindet sich mit dem MCP-Endpoint, steuert dein Handy.
Wie Es aufs Handy Kommt
Hier gewinnt die Stock-Kombi haushoch: Im Play Store auf Installieren tippen, fertig. Dikciz geht nicht über Google Play und wird es auch nie. Du baust es selbst.
Ich installiere kein Android Studio. Niemals. Build und Emulator laufen auf android-lab, meinem Docker-Setup für das Android SDK und den Emulator. Das Makefile will lokal gebaute Images android-lab:0.13.0 und android-lab:0.13.0-emulator-api36, und wenn eins fehlt, hält es an und schickt dich zu den Build-Anleitungen von android-lab, statt in deinem Namen irgendeinen Mist runterzuladen.
git clone https://github.com/psyb0t/dikciz-launcher.git
cd dikciz-launcher
make build
make dikciz-runmake build baut dikcizDebug. Tool-Container nehmen die CPU-Anzahl des Docker-Hosts, gedeckelt bei 10, mit genauso vielen Gradle-Workern. Auf einer schwächeren Kiste:
make build ANDROID_LAB_TOOL_CPU_LIMIT=2 ANDROID_LAB_GRADLE_MAX_WORKERS=2make dikciz-run installiert es auf dem gemeinsamen Emulator, gewährt Dateizugriff, setzt es als Android Home und leitet die Control Planes auf 19101 (WebSocket) und 19102 (MCP) an den Host weiter. Den Emulator-Bildschirm hast du im Browser unter:
http://127.0.0.1:61326/vnc.html?autoconnect=true&resize=scaleDann sprichst du ihn über den mitgelieferten Client an:
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'Wenn du Auth eingeschaltet hast, exportiere DIKCIZ_REMOTE_BEARER_TOKEN aus einer ignorierten Env-Datei. Pack ihn nicht wie ein Höhlenmensch in ein Befehlsargument. make dikciz-reset plättet nur Dikciz und /sdcard/Dikciz und bootet den Start-Homescreen. make stop stoppt den Stack, ohne das gespeicherte Handy zu löschen. Der State (Gradle-Cache, Signier-Identität, Emulator-Disk, Artefakte) liegt in .android-lab/.
Die Tests sind UI-Tests gegen denselben Emulator, mit engen Targets wie make dikciz-test-accessibility oder make dikciz-test-app-drawer-entry, und make dikciz-test für die breite Suite, die Screenshots und UI-Dumps nach .android-lab/shared/artifacts/ kippt und das Handy danach auf den Start-Homescreen zurücksetzt, egal ob grün oder rot.
Wann Du beim Stock-Launcher und Tasker Bleiben Solltest
Wenn du ein Handy willst, das ab dem Kauftag funktioniert, aus dem Play Store installiert wird und Automatisierung über eine GUI mit Jahren geteilter Rezepte im Rücken kriegt, behalte den Stock-Launcher und die drangeschraubte App. Genauso, wenn du nie vorhast, eine Config-Datei zu bearbeiten, eine Zeile Lua zu schreiben oder einen Agenten auf dein Handy anzusetzen. Dikciz verlangt, dass du es selbst in Docker baust, und das zahlt sich nur aus, wenn du willst, dass dein Homescreen aus Dateien, Code, einem Socket und einem MCP-Server besteht.
Wenn das Letzte genau nach dem klingt, was dir gefehlt hat: Es liegt auf github.com/psyb0t/dikciz-launcher.