# Korin Timer (Linux) Eigenständige Neu-Implementierung eines 2-Step-Title-Switching-Timers als Overlay (kein Reverse Engineering, keine Codeübernahme aus proprietärer Software - nur nach Funktionsbeschreibung neu gebaut). ## Funktionsprinzip - **Ohne Vor-Aktivierung** (Normalfall): Hotkey drücken startet den Cooldown-Timer direkt, ein einziger Druck reicht. - **Mit Vor-Aktivierung** (optional, pro Titel im Settings-Fenster einstellbar): eine zusätzliche Taste/Kombination - z.B. eine Taste, die im Spiel ohnehin gedrückt wird - die den Titel in Standby versetzt (gepulster Rahmen zeigt das an). Die Haupttaste startet dann, *ein einziges Mal gedrückt*, den Cooldown; sie allein (ohne vorherige Vor-Aktivierung) macht in diesem Modus nichts. - **Standby-Timeout** (optional, pro Titel, 0 = aus, wirkt nur im Vor-Aktivierungs-Modus): springt automatisch zurück auf Idle, wenn die Haupttaste nicht innerhalb der eingestellten Sekundenzahl nach der Vor-Aktivierung gedrückt wird - inklusive Countdown-Anzeige im Overlay, solange er läuft. - **Neu triggerbar** (Checkbox pro Titel, aus per Default): normalerweise wird die Haupttaste während laufendem Cooldown ignoriert. Mit dieser Option gedrückt startet sie den Cooldown stattdessen neu (volle Dauer ab dem Zeitpunkt des Drucks) - für Buffs, die man aktiv auffrischen will. - **Dogma-Modus** (`dogma_mode: true` in der Config, Schalter im Settings-Fenster): solange irgendein Cooldown läuft, werden Hotkeys für *andere* Titel ignoriert. Aus, wenn alle Titel unabhängig voneinander gleichzeitig laufen sollen. - **Verschieben**: `Strg+Alt` gedrückt halten + mit der linken Maustaste aufs Overlay klicken und ziehen. Die neue Position wird beim Loslassen in der Config-Datei des aktiven Charakter-Profils (`overlay.x`/`overlay.y`, siehe unten) gespeichert und bleibt über Neustarts erhalten. Funktioniert wie die Hotkeys über rohes `evdev` (siehe unten) - das Overlay ist click-through, bekommt also nie normale Mausereignisse vom Fenstersystem. (Nicht nur `Alt`, weil das mit GNOMEs eigenem "Alt+Drag verschiebt jedes Fenster"-Standardverhalten kollidiert.) ## Warum XWayland/EWMH statt Layer-Shell Erster Ansatz war `gtk4-layer-shell` (`wlr-layer-shell`-Protokoll) - das funktioniert aber nur auf wlroots-Compositors (Sway, Hyprland). **GNOME/Mutter implementiert dieses Protokoll nicht.** Getestet gegen eine echte GNOME- Wayland-Session: die Layer-Shell-Initialisierung schlug fehl (`compositor does not support the Layer Shell protocol`). Stattdessen läuft das Overlay über XWayland als normales X11-Fenster mit EWMH-Hints (`_NET_WM_STATE_ABOVE`, `_NET_WM_STATE_SKIP_TASKBAR`, Klick-durch via XShape-Extension). Das funktioniert auf GNOME, KDE *und* wlroots- Compositors, weil XWayland praktisch überall mitläuft. Einzige Falle dabei: Mutter respektiert `_NET_WM_STATE_ABOVE` nur, wenn es per ClientMessage **nach** dem Mapping des Fensters gesendet wird (nicht als Property davor, im Gegensatz zu SKIP_TASKBAR/SKIP_PAGER) - siehe `x11_overlay.raise_above()`. Das umgeht auch das ursprüngliche Wine-Problem: statt eines transparenten DirectX-Fensters unter Wine (das dort schwarz statt durchsichtig gerendert wurde) ist das hier ein natives GTK4-Fenster mit echter RGBA-Transparenz. ## Charakter-Profile Jeder Charakter hat eine eigene, vollständige Config unter `~/.config/titletimer/profiles/.json` (Titel, Hotkeys, Cooldowns, Dogma-Modus, Overlay-Position - alles). Im Einstellungsfenster oben eine Zeile "Charakter:" mit Dropdown + `Neu`/`Duplizieren`/`Umbenennen`/`Löschen`: - **Neu**: legt ein Profil mit den Beispiel-Titeln aus `config.example.json` an, das direkt umbenannt/umgebaut werden kann. - **Duplizieren**: kopiert das aktuell aktive Profil unter neuem Namen - praktisch für einen zweiten Charakter mit ähnlichem Titel-Setup. - **Umbenennen** / **Löschen**: benennt die Profil-Datei um bzw. entfernt sie (der letzte verbleibende Charakter lässt sich nicht löschen). Profil-Wechsel im Dropdown lädt sofort das gewählte Profil - laufendes Overlay übernimmt es ohne Neustart, wie beim normalen "Speichern". Nicht gespeicherte Änderungen am vorherigen Profil gehen beim Wechsel verloren (genau wie beim Schließen des Fensters ohne zu speichern). Das zuletzt aktive Profil wird gemerkt (`~/.config/titletimer/last_profile.txt`) und beim nächsten Start automatisch wieder geladen. Bereits vorhandene Configs aus einer Version vor diesem Feature (`~/.config/titletimer/config.json`) werden beim ersten Start automatisch zu einem Profil namens "Default" migriert (per Umbenennen in `profiles/Default.json`) - kein manueller Schritt nötig. `--config ` umgeht das Profil-System komplett und lädt direkt eine beliebige Datei (z.B. zum Testen) - dann ist die Profil-Zeile im Einstellungsfenster ausgeblendet, da es kein Profil-Konzept dafür gibt. ## Setup (aus dem Quellcode, ohne AppImage) Falls du nicht das AppImage nutzt, sondern direkt aus `src/` starten willst, brauchst du GTK4/PyGObject/evdev/xlib systemweit oder per `nix-shell` (siehe `shell.nix` im Repo - relevant auf NixOS, wo es kein systemweites `python3`/GTK4 auf dem `PATH` gibt): ```bash # Arch/CachyOS sudo pacman -S python-gobject gtk4 gtk4-layer-shell python-evdev python-xlib # NixOS nix-shell # nutzt die shell.nix im Repo-Root # Nutzer muss in der input-Gruppe sein, damit evdev /dev/input lesen darf sudo usermod -aG input "$USER" # danach ab- und wieder anmelden (Gruppenmitgliedschaft wird erst beim # nächsten Login aktiv) ``` Config: Falls beim ersten Start noch kein Charakter-Profil existiert (`~/.config/titletimer/profiles/`, siehe oben), wird automatisch eines namens "Default" aus der eingebauten `config.example.json` angelegt (auch im AppImage - keine separate Datei nötig). Danach anpassen - entweder per Hand: ```bash $EDITOR ~/.config/titletimer/profiles/Default.json # hotkey ist entweder ein einzelner evdev-Keycode-String ("KEY_F1") oder # eine Liste für eine Tastenkombination (["KEY_LEFTCTRL", "KEY_F1"]) - alle # gelisteten Tasten muessen gleichzeitig gehalten werden. Keycode-Namen: # python3 -c "from evdev import ecodes; print([k for k in dir(ecodes) if k.startswith('KEY_')])" ``` oder über das eingebaute Einstellungsfenster (Titel hinzufügen/entfernen, Hotkeys per Klick-und-Tastendruck erfassen - auch Mehrfachkombinationen wie Strg+F1 -, Cooldowns, Farben, optionales Icon pro Titel, Dogma-Modus, Anker/Rand/Skalierung, sowie die per Alt+Drag gesetzte Position zurücksetzen) - das öffnet sich automatisch mit dem Overlay zusammen. "Speichern" wirkt sofort auf das laufende Overlay, kein Neustart nötig (ein aktuell laufender Cooldown wird dabei zurückgesetzt). Starten (aus dem Projektverzeichnis): ```bash PYTHONPATH=src python3 -m titletimer ``` Startet Overlay **und** Einstellungsfenster zusammen. Das Einstellungs- fenster zu schließen beendet die ganze App (auch das Overlay) - das ist aktuell der einzige Weg, das rahmenlose, click-through Overlay wieder zu stoppen. Nur das Einstellungsfenster ohne Overlay: ```bash PYTHONPATH=src python3 -m titletimer --settings # oder: ./Korin-Timer.AppImage --settings ``` ## AppImage bauen (voll gebündelt, keine Systemabhängigkeiten) ```bash ./packaging/build-appimage.sh ``` Das AppImage ist **vollständig in sich geschlossen**: eigener Python-Interpreter + Standardbibliothek, GTK4, `gtk4-layer-shell`, PyGObject, pycairo, `evdev`, `python-xlib` - und deren *gesamter* transitiver Shared-Library-Baum (~78 `.so`-Dateien), inklusive `ld.so` und `libc` selbst. Nichts davon kommt vom Zielsystem. Warum so radikal gebündelt: Auf NixOS gibt es kein FHS-`/usr/bin/python3` und kein System-GTK4 auf dem `PATH` - der ursprüngliche Ansatz ("GTK4 ist auf so gut wie jedem Desktop schon da") war für NixOS schlicht falsch. `packaging/bundle-runtime.sh` sammelt den kompletten Abhängigkeitsbaum per rekursivem `ldd` über Python3-Binary, PyGObject-Extensions (`_gi`, `_gi_cairo`), pycairo, `libgtk-4.so.1` und `libgtk4-layer-shell.so.0` ein. `AppRun` ruft danach nicht mehr `python3` über `PATH` auf, sondern das gebündelte `ld-linux-x86-64.so.2` direkt mit `--library-path` auf das gebündelte `lib/`-Verzeichnis, plus `PYTHONHOME`/`PYTHONPATH`/`GI_TYPELIB_PATH` auf die gebündelten Pfade. Damit entfällt auch die vorherige Sorge um `evdev`s Python-ABI-Tag (CPython-Minor-Version-Mismatch) komplett - es ist ja unser eigener Interpreter, keiner vom Zielsystem. Größe: ca. 28 MB (statt vorher 1,4 MB) - der Preis für echte Systemunabhängigkeit. Der Build erzeugt `build/Korin-Timer.AppImage`. Das Artefakt gehört nicht zum Repository und wird nicht eingecheckt. ```bash chmod +x build/Korin-Timer.AppImage # nur falls Ausführbarkeit fehlt ./build/Korin-Timer.AppImage ``` Einzige verbleibende Voraussetzung auf dem Zielsystem: die `input`-Gruppen- Mitgliedschaft für `evdev` (s.o.) - das kann eine AppImage-Datei nicht selbst mitbringen, das ist eine Kernel-Berechtigung. **Wie das `.AppImage` selbst gebaut wurde**: `appimagetool`s eigene AppImage-Datei ist selbst eine statisch-PIE-gelinkte ELF-Datei ohne `PT_INTERP` - in meiner Sandbox (ein eingeschränkter Podman-Container) ließ sich dieses Binärformat gar nicht ausführen (`execve` schlug mit ENOENT fehl, unabhängig von FUSE). Deshalb baut das Skript jetzt automatisch alternativ von Hand: die separate `runtime-x86_64`-Datei (der reine ELF-Loader-Stub) direkt mit `mksquashfs AppDir` zusammen-`cat`en - exakt das, was `appimagetool` intern auch tut. Auf deinem echten System sollte `appimagetool` normal durchlaufen; falls nicht, greift derselbe Fallback automatisch. ## Was tatsächlich getestet wurde - **Config-Loader + State-Machine** (2-Step-Umschaltung, Dogma-Mutex, Cooldown-Expiry): per Skript ausgeführt und alle Assertions verifiziert. - **GTK4/GdkX11/evdev-Imports**: installiert und erfolgreich importiert. - **Overlay live gegen deine echte laufende GNOME-Wayland-Session getestet** (nicht nur eine isolierte Testumgebung): Fenster wird erzeugt, `evdev` findet deine echten Tastatur-Devices, und per `xprop` verifiziert: `_NET_WM_STATE_ABOVE`, `_NET_WM_STATE_SKIP_TASKBAR`, `_NET_WM_STATE_SKIP_PAGER` und `_NET_WM_WINDOW_TYPE_UTILITY` sind tatsächlich gesetzt, Fenster läuft ohne Exceptions. - **AppImage-Datei selbst**: ELF-Header und eingebettetes SquashFS per `unsquashfs` an der korrekten Byte-Offset-Grenze extrahiert und geprüft (`AppRun`, `.desktop`-Datei lesbar) - der Aufbau der Datei ist also korrekt. **Nicht** verifiziert: das tatsächliche Doppelklick-Starten der `.AppImage`, weil meine Sandbox dieses Binärformat generell nicht ausführen kann (s.o.) - das musst du einmal bei dir bestätigen. - **Nicht** verifiziert: wie es *visuell* aussieht (Puls-Rahmen-Optik, Transparenz-Feinschliff) und ob es zuverlässig über einem Vollbild-Spiel liegt - das kannst nur du im echten Gameplay beurteilen. ## Aktueller Funktionsumfang Nur der 2-Step Title-Switching-Timer + Dogma-Mode ist implementiert (siehe Absprache). Flow-System (Pet-Tracking), Horary Loop Timer, Custom/Foto-Timer und Post-it-Labels aus dem Original sind bewusst noch nicht gebaut - können bei Bedarf nachgezogen werden.