11 KiB
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: truein 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+Altgedrü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 rohesevdev(siehe unten) - das Overlay ist click-through, bekommt also nie normale Mausereignisse vom Fenstersystem. (Nicht nurAlt, 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/<Name>.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.jsonan, 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 <pfad> 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):
# 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:
$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):
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:
PYTHONPATH=src python3 -m titletimer --settings
# oder: ./Korin-Timer.AppImage --settings
AppImage bauen (voll gebündelt, keine Systemabhängigkeiten)
./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 evdevs 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.
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: appimagetools 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-caten -
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,
evdevfindet deine echten Tastatur-Devices, und perxpropverifiziert:_NET_WM_STATE_ABOVE,_NET_WM_STATE_SKIP_TASKBAR,_NET_WM_STATE_SKIP_PAGERund_NET_WM_WINDOW_TYPE_UTILITYsind tatsächlich gesetzt, Fenster läuft ohne Exceptions. - AppImage-Datei selbst: ELF-Header und eingebettetes SquashFS per
unsquashfsan 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.