# Kadence — the native desktop app Not Electron. **PyQt6 + QML with Qt Quick Controls' Material style**: the window is a native Wayland/X11 surface drawn by Qt, using the session's own fonts, scaling and colour scheme. The only dependency is PyQt6, which on Arch is the system package `python-pyqt6`; everything else — HTTP, config, process scanning — is the Python standard library. It is also the piece that *enforces* things, because it is the only part running on your machine. ```bash sudo pacman -S --needed python-pyqt6 qt6-declarative # Arch ./packaging/install-desktop.sh # menu entry, icon, launcher sudo ./helper/install.sh # privileged helper (website blocking) kadence-desktop ``` Run it straight from the checkout instead: `python3 -m kadence_desktop`. --- ## What "native" buys you | Feature | How | | --- | --- | | **Tray icon that *is* the timer** | `QSystemTrayIcon` (a StatusNotifierItem on KDE). The icon is painted at runtime: a ring that drains as the interval runs, tinted by focus / break / idle, with a pause glyph when held. Middle-click toggles the timer. | | **Real notifications with buttons** | `org.freedesktop.Notifications` over D-Bus, with actions: *Start break* · *Another pomodoro* · *+5 min*. Replaced in place, so a day of pomodoros never stacks a column of toasts. | | **Scriptable over D-Bus** | `org.kadence.Desktop` exports `StartWork`, `Toggle`, `Pause`, `Complete`, `Extend`, `Interrupt`, `Status`, `SendReport`, `Raise`. Bind any of them to a global shortcut in System Settings — no key-grabbing hacks. | | **Screen stays awake** | `org.freedesktop.ScreenSaver.Inhibit` while you focus, released the moment the interval ends. | | **Do Not Disturb** | Other apps' banners are suppressed during focus (Plasma's `Notifications.Inhibit`; on GNOME the `show-banners` gsetting, put back afterwards). | | **Your music pauses** | Any MPRIS player (Spotify, VLC, mpv, browsers) is paused when focus starts, and *only the ones we paused* resume on the break. | | **Taskbar progress** | `com.canonical.Unity.LauncherEntry` — a progress bar under the dock icon and a badge with today's count. | | **Knows when you walked away** | logind `Lock`/`Unlock` and `PrepareForSleep` pause and resume the timer; optional idle-pause uses KDE's or Mutter's idle monitor. | | **Starts with the session** | XDG autostart entry, or a **systemd user unit** that restarts on failure and logs to the journal. | | **Single instance** | Owning the D-Bus name is the lock; a second launch raises the first window and exits. | | **Launcher actions** | Right-click the icon: *Start a pomodoro*, *Start a break*, *Start / pause*, *Send today's report*. | Every one of these degrades to a no-op where the desktop does not provide it, so the same build runs on KDE, GNOME or a bare window manager. --- ## Blocking The rules (which sites, which apps, when) live on the backend so the web app can edit them and a second machine obeys the same list. This app enforces them: * **Websites** — the managed region of `/etc/hosts` is kept in sync with the policy. Writing it needs root, which a desktop app must not have, so it calls `kadence-helper` through **pkexec**. * **Applications** — `/proc` is scanned every few seconds for your own processes matching the list; they are terminated (SIGTERM, then SIGKILL) or merely reported, and every attempt is sent to the backend for the evening report. * Everything is released when the interval ends — and on exit, including a crash, because `shutdown()` clears the block and the inhibits. ### The privileged helper `helper/kadence-helper` is the entire privileged surface, and it is deliberately dull: * every hostname must match a strict pattern, the redirect must parse as an IP; * only the region between its own markers is rewritten — your own entries above and below survive byte for byte; * the input list is opened `O_NOFOLLOW` and must be a regular file; * the write is atomic (temp file + `rename`), and the first run backs up `/etc/hosts` to `/etc/hosts.kadence-backup`; * `localhost` can never be blocked. ```bash sudo ./helper/install.sh # polkit asks once per session sudo ./helper/install.sh --passwordless # or never, for your user only sudo ./helper/install.sh --uninstall # unblocks first, then removes itself ``` --- ## Global shortcuts System Settings → Shortcuts → Custom Shortcuts → New → D-Bus command: | Field | Value | | --- | --- | | Service | `org.kadence.Desktop` | | Path | `/Timer` | | Method | `org.kadence.Timer.Toggle` | Or bind a command: `kadence-desktop --toggle`. The verbs talk to the running instance instead of starting a second one: ```bash kadence-desktop --toggle # start → pause → resume kadence-desktop --start # start a focus interval kadence-desktop --break kadence-desktop --complete kadence-desktop --status # "Focus 18:42 · Write the docs · 3/8 today" kadence-desktop --report # send today's report to Telegram ``` In-window shortcuts: **Space** start/pause · **Ctrl+Enter** finish · **Ctrl+E** +5 min · **Ctrl+R** refresh sources · **Ctrl+1…5** pages · **Ctrl+W** hide to tray. --- ## Configuration `~/.config/kadence/desktop.json` holds only what is about *this machine* — the backend URL and token, and which integrations this box may use. Timer lengths, task sources, block lists and Telegram all live on the server, so they are the same everywhere. The **Settings** page edits both, clearly separated. ## Layout ``` kadence_desktop/ app.py # QApplication, QML engine, tray, D-Bus, single instance controller.py # the object QML binds to; wires transitions to integrations worker.py # the network thread (every HTTP call happens here) api.py # stdlib HTTP client for the backend config.py # ~/.config/kadence/desktop.json tray.py # the painted countdown icon and its menu integrations/ # notifications, inhibit/DND, MPRIS, launcher, logind, D-Bus, autostart blocking/ # hosts file, /proc scanning, and the manager that syncs policy qml/ # Material UI: Timer, Tasks, Blocker, Report, Settings helper/ # the privileged helper + polkit action + installer packaging/ # .desktop entry, icon, user installer ```