Files
desktop-planer/README.md
2026-10-05 22:09:06 +02:00

8.2 KiB
Raw Permalink Blame History

Desktop

GNOME-Shell-Extension (GNOME 46, Ubuntu 24.04): Kalender, Todos und Notizen direkt auf dem Desktop, unter allen Fenstern, über dem Wallpaper.

Bedienung

  • Woche: startet mit der aktuellen Uhrzeit in der Mitte, rote Linie markiert jetzt.
  • Termin anlegen: in der Wochenansicht auf eine Stunde drücken und bis zur Endstunde ziehen, auch über mehrere Tage. Klick ohne Ziehen = eine Stunde. Ziehen in der Zeile „ganz“ = ganztägig. Oder Rechtsklick auf einen Tag (Monat), eine Stunde (Woche) oder die Tagesfläche: „Termin erstellen“. Popover: Titel, Von und Bis je mit Datum und Uhrzeit. Ohne Ende gilt eine Stunde, ohne Uhrzeit ganztägig.
  • Todo anlegen: Rechtsklick ins Todo-Panel oder in den Kalender, „Todo erstellen“. Text plus Datum und Uhrzeit.
  • Notiz: Rechtsklick irgendwo im Notiz-Panel, „Notiz erstellen“ fragt den Namen und öffnet den Editor. Klick in die Notizfläche bearbeitet. Mit Fokus steht der rohe Markdown-Text, ohne Fokus die formatierte Ansicht. Gespeichert wird alle 5 Sekunden und beim Klick woanders hin. Enter in einer Liste setzt den nächsten Punkt, Enter auf einem leeren Punkt beendet die Liste.
  • Maustasten: links bearbeitet, Mitte hakt ein Todo ab oder benennt Notiz/Ordner um, rechts öffnet das Menü. Alle Popover öffnen am Mauszeiger.
  • Rechtsklick auf freie Fläche: Erstellen (Todos, Tagesansicht, Notizbaum im Wurzelordner).
  • Ordner: Klick klappt auf und zu, der Pfeil zeigt den Zustand wie in Obsidian.
  • Rechtsklick auf Ordner: Notiz oder Unterordner darin erstellen, Umbenennen, Löschen.
  • Rechtsklick auf Eintrag: Umbenennen, Löschen.
  • Popover: Enter speichert, Esc oder Klick daneben bricht ab. Keine Buttons.

Aufbau

  • desktop@marha.de/ – die Extension
    • extension.js – Layout auf dem Arbeitsbereich, Stapelung über Desktop-Icons
    • calendar.js – Monat, Woche (Drag), Tag, Termin-Popover
    • todos.js – Todo-Liste, Todo-Popover
    • notes.js – Ordnerbaum, Markdown-Anzeige, Inline-Editor, Kontextmenü
    • widgets.js – Popover (Grab, Esc, Tab, Klick daneben), Kontextmenü, Datums- und Zeitauswahl
    • markdown.js, dates.js – reine Logik, ohne Shell testbar
    • store.js – Ablage, Tombstones, Übernahme vom Server
    • sync.js – Abgleich mit dem Server (Soup, Timer)
  • backend/ – Sync-Server (FastAPI, SQLite), Dockerfile, docker-compose.yml
  • android/ – native Android-App mit denselben drei Bereichen, siehe android/README.md
  • dev/ – Test-Werkzeuge

Daten

~/.local/share/desktop-app/

  • events.json – Termine
  • todos.json – Todos
  • notes/ – Notizen als .md, Ordner beliebig verschachtelt (Obsidian kann den Ordner öffnen)
  • deleted-notes.json – gelöschte Notizen mit Zeitpunkt, damit der Sync sie auch woanders löscht
  • folders.json – Ordner mit Zeitpunkt und deleted-Flag, damit auch leere Ordner mitlaufen
  • sync.json – Server-Zugang, siehe Sync
  • sync-state.json – letzter Server-Stand

Gelöschte Termine und Todos bleiben mit deleted: true in der Datei und werden nur ausgeblendet.

Sync

Der Server (backend/, FastAPI + SQLite) hält den Kern. Jedes Gerät gleicht regelmäßig ab und sofort nach jeder eigenen Änderung: eigene Änderungen hoch, fremde Änderungen runter. Der Takt richtet sich nach der Aktivität: nach einer Änderung (eigene oder vom Server) eine Minute lang jede Sekunde, dann alle 5 Sekunden, nach 5 Minuten Ruhe alle 10 Sekunden. Bei Konflikten gewinnt der Eintrag mit dem neueren updated (Notizen: Änderungszeit der Datei, ganze Datei). Offline geht weiter, beim Wiederverbinden wird nachgeliefert. Eine gerade offene Notiz wird nicht von außen überschrieben.

Claude-Code-Sitzungen (nur PCs)

Die Extension gleicht auch ~/.claude/projects/ ab, also Sitzungen und Memory von Claude Code, im selben Takt wie die Einträge. Jede Änderung geht beim nächsten Lauf hoch, also nach 1 bis 10 Sekunden. Sitzungen wachsen nur am Ende, deshalb geht meist nur der neue Rest: der Client nennt Größe und Hash der letzten 64 KB des Standes, den der Server haben müsste, und der Server hängt an. Passt das nicht, geht die ganze Datei. Vom Server kommt eine Datei, wenn sie dort neuer ist, ebenfalls nur ab dem eigenen Stand, mit ihrer Änderungszeit. Gelöschte Dateien verschwinden auch auf dem anderen PC. Die App fragt diese Dateien nie an. Auf dem Server liegen die Inhalte unter data/files/, in SQLite nur Pfad, Zeitpunkt und Größe. Eine Sitzung darf nur auf einem PC gleichzeitig laufen, sonst gewinnt die neuere Datei als Ganzes. Bedingung: gleicher Pfad auf beiden PCs, der Ordnername enthält ihn. "files": false in sync.json schaltet es ab, ein Pfad statt false ersetzt den Ordner. make sync-files stößt den Abgleich sofort an und wartet, bis er durch ist.

Zugang auf jedem Gerät in ~/.local/share/desktop-app/sync.json:

{"url": "https://planer.marha.de", "token": "<SYNC_TOKEN aus .env>"}

Ohne diese Datei läuft die Extension rein lokal. Optional "interval": Ruhetakt in Sekunden statt 10.

Server-API (alle Aufrufe mit Authorization: Bearer <SYNC_TOKEN>):

  • POST /api/sync – {"since": <cursor>, "events": [...], "todos": [...], "notes": [...], "folders": [...], "files": [...]}; Antwort: Änderungen seit since in den angefragten Sammlungen plus neuer cursor. Ohne Sammlungen kommen die vier ersten, files nur auf Anfrage. Ein files-Eintrag ist {"id", "updated", "content"} (ganz), {"id", "updated", "base": {"size", "hash"}, "append"} (nur der Rest) oder ein Tombstone; zurück kommt nur der Steckbrief {"id", "updated", "size"}, bei Ablehnung mit hash.
  • GET /api/files/<pfad>?from=<größe>&hash=<hash> – Inhalt einer Datei. Passt der eigene Stand, kommt nur der Rest ab from, sonst alles; X-From sagt, ab wo.
  • GET /api/events, /api/todos, /api/notes – aktueller Stand ohne Gelöschte, für andere Anwendungen

Server starten

cp .env.example .env    # SYNC_TOKEN setzen
make dev                # lokal, http://localhost:8040 (anderer Port: make dev PORT=8050)
make prod               # auf dem Server: Docker Compose hinter Traefik, https://planer.marha.de
make stop | make logs | make down
make test               # Server-Tests
make apk                # Android-App bauen, make apk-install schickt sie aufs Handy (android/README.md)

Installation

cp .env.example .env    # SYNC_TOKEN eintragen (derselbe wie auf dem Server)
make install            # kopiert die Extension nach ~/.local/share/gnome-shell/extensions und aktiviert sie
make update             # überschreibt eine installierte Extension mit dem Stand aus diesem Ordner

Beide legen ~/.local/share/desktop-app/sync.json aus der .env an, falls sie fehlt. Ohne .env läuft die Extension rein lokal. Danach ab- und wieder anmelden. Unter Wayland lädt die Shell neuen Code erst nach einem neuen Login. make update startet die Extension neu, damit ein neuer Sync-Zugang sofort greift. Voraussetzung: GNOME 46 (Ubuntu 24.04).

Entwicklung

gjs -m dev/tests/run.js        # Unit-Tests für dates.js und markdown.js
dev/restart.sh                 # Test-Shell als Fenster starten (zum Selbst-Ausprobieren)
dev/restart.sh headless        # Test-Shell unsichtbar, für simulierte Eingaben
dev/tests/input.sh             # Ablauf-Tests mit echter Maus und Tastatur (headless)
dev/tests/sync.sh              # Sync-Test: lokaler Server plus headless Shell, beide Richtungen
dev/eval.sh dev/seed.js        # Testdaten einspielen
dev/eval.sh 'js'               # JavaScript in der Test-Shell ausführen (oder Dateipfad)
dev/shot.sh [datei.png]        # Screenshot der Test-Shell
dev/input.sh click X Y         # Maus und Tastatur simulieren (nur headless gefahrlos)
dev/press.sh "Text"            # Button mit diesem Text direkt auslösen
dev/stop.sh                    # Test-Shell beenden

Die Test-Shell läuft mit GSETTINGS_BACKEND=keyfile und eigenem XDG_DATA_HOME unter /tmp/desktop-app-nested/. Die echte Session bleibt unberührt.

Im Fenstermodus (nested, X11-Backend) würden virtuelle Eingabegeräte auf dem echten Desktop landen. Deshalb nutzt dev/input.sh nur den headless Modus (natives Backend). dev/fake-ding.py startet ein Fenster wie Desktop Icons, um die Stapelung zu prüfen.