desktop: an agent and a mini timer, not an application window
The design of the full desktop app was still bad, and the reason it stayed bad is that it had no business existing. Five pages — Timer, Tasks, Blocker, Report, Settings — each a second view of the *server's* data: a second place to be wrong, a second place to style, and a second place to keep in step with a web app that already does lists, rules and reports properly in a browser, on any device. So it is gone, along with 2,100 lines of QML and the controller surface that fed it. What is left is what only a program on your machine can do, plus the two surfaces worth having in front of you: * **the tray menu — unchanged**, as asked. All fourteen entries, plus "Settings…". "Open Kadence" now brings the countdown up, because that is what it meant all along. * **the mini timer**, which is now the whole of this app's own interface and so finally carries the house style on its own: warm surface, hairline instead of shadow, a letterspaced eyebrow, the progress lane as a rule, and exactly one filled button — whichever action is the obvious next one. The countdown is set in the display serif on a **fixed advance**, because Fraunces has no tabular figures and ships no `tnum`: each glyph now sits in a box as wide as the widest digit, instead of the clock shifting sideways on every tick. The phone client documents the same trap; the desktop had it too and nobody had looked. It also says OFFLINE when the backend is away — the count and the clock are both the server's, and this is the only window left to say it in. * **a settings dialog**, because a fresh install has to be pointed at a backend somewhere. One column, hairline sections: the backend and its token, where the mini timer sits, what this machine enforces, and what it does to the session. It edits `desktop.json` and nothing else — with one honest exception, view-only mode, which is a server-side flag the other clients have to honour too. Structurally, `app.py` no longer loads a QML root at all: a plain `QQmlEngine` with the mini timer as a component, and the dialog built on demand. Everything that used to ask "is the main window visible?" is simpler for not having one — the mini timer's auto-show rule is now just "an interval is running", which is what the setting always claimed. The window-only controller surface goes with the pages: tasks and task writes, stats, the daily report, the settings document, the presets, the cycle and interruption counters, and their worker jobs and API calls. Two things were kept deliberately: the task list is still read once a minute for one fact — which task is pinned, so "Start focus" from the tray attaches what you pinned this morning — and `timerEnabled` now comes from the timer state, which already carries `timer_enabled`, rather than from a second fetch of a document this app can no longer edit. Also: `--settings` and a `Settings` D-Bus verb; toasts go to the tray's balloon now that the snackbar is gone; the launcher entry starts the agent and grew a Settings action; a systemd user unit is shipped rather than only written at runtime; and both READMEs say what this piece is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
26 files changed
+752
-2690
No files matched your search
+45
-23
@@ -1,15 +1,27 @@
|
||||
# Kadence — the native desktop app
|
||||
# Kadence — the desktop agent
|
||||
|
||||
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.
|
||||
Not Electron, and **not an application window either**. This piece is an agent:
|
||||
it lives in the tray, enforces the server's blocking policy on this machine,
|
||||
pauses your music, holds the screen awake, answers D-Bus, posts notifications
|
||||
with buttons — and shows a floating countdown. **PyQt6 + QML with Qt Quick
|
||||
Controls' Material style**, so what it does draw is a native Wayland/X11 surface
|
||||
with 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, DNS — is the Python standard library.
|
||||
|
||||
It is also the piece that *enforces* things, because it is the only part running
|
||||
on your machine. The interface is the same warm, hairline-ruled design as the
|
||||
web app — the `Theme` singleton in `qml/` holds the same tokens as the web
|
||||
app's `tokens.ts`.
|
||||
There used to be a five-page window here (Timer, Tasks, Blocker, Report,
|
||||
Settings) and it is gone. Every one of those pages was a second view of the
|
||||
*server's* data, which made it a second place to be wrong and a second place to
|
||||
style — and the styling is what it never earned. The web app does lists, rules
|
||||
and reports properly, in a browser, on any device. What is left on the desktop is
|
||||
the part only a program on your machine can do, plus the two surfaces worth
|
||||
having in front of you:
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **the tray menu** | the whole timer: start, pause, finish, +5, abandon, report, view-only, mini timer, settings |
|
||||
| **the mini timer** | a floating pill with the countdown, the task, and the controls |
|
||||
| **the settings dialog** | the handful of settings that belong to *this machine* |
|
||||
|
||||
```bash
|
||||
sudo pacman -S --needed python-pyqt6 qt6-declarative # Arch
|
||||
@@ -18,7 +30,12 @@ sudo ./helper/install.sh # privileged helper (w
|
||||
kadence-desktop
|
||||
```
|
||||
|
||||
Run it straight from the checkout instead: `python3 -m kadence_desktop`.
|
||||
Run it straight from the checkout instead: `python3 -m kadence_desktop`. Point it
|
||||
at the backend from the tray's **Settings…**, or `kadence-desktop --settings`.
|
||||
|
||||
The usual way to run it is with no window at all — `--minimized`, which is what
|
||||
the autostart entry and the systemd user unit use. The mini timer then appears by
|
||||
itself when an interval starts.
|
||||
|
||||
---
|
||||
|
||||
@@ -26,20 +43,20 @@ Run it straight from the checkout instead: `python3 -m kadence_desktop`.
|
||||
|
||||
| Feature | How |
|
||||
| --- | --- |
|
||||
| **A floating mini timer** | A small pill with the countdown, the task you are on, and the controls — *Pause / Resume / Finish / +5*, or *Start focus / Break* when idle — all on their own rows and all visible without hovering. Open it with **Ctrl+M**, the tray menu, or `kadence-desktop --mini`; it also appears by itself while an interval runs and the main window is away. Dragging moves it (through the compositor, so it works on Wayland) and double-clicking opens the main window. It is **not** a `Qt.Tool` window — Qt hides those whenever the application is deactivated, which made it vanish on every click in another app — and it refuses keyboard focus instead. |
|
||||
| **A floating mini timer** | A small pill with the countdown, the task you are on, and the controls — *Pause / Resume / Finish / +5*, or *Start focus / Break* when idle — all on their own rows and all visible without hovering. Open it from the tray menu or with `kadence-desktop --mini`; it also appears by itself while an interval runs. Dragging moves it (through the compositor, so it works on Wayland) and double-clicking opens the web app. It is **not** a `Qt.Tool` window — Qt hides those whenever the application is deactivated, which made it vanish on every click in another app — and it refuses keyboard focus instead. The countdown is set in the display serif on a **fixed advance**: Fraunces has no tabular figures and ships no `tnum`, so each glyph is laid in a box as wide as the widest digit, or the clock jitters sideways on every tick. |
|
||||
| **Where it appears** | Settings → *Mini timer appears*: any of the four corners, the top/bottom edge centres, the centre of the screen, or *where I last dragged it*, with a configurable gap from the edges. It is placed on the screen the app is on, inside the usable area (panels excluded), and it returns to that spot every time it comes up. On X11 the app positions the window; on Wayland — where a client may not — the point is written into the same KWin rule. |
|
||||
| **Above everything, on Wayland** | Wayland gives an application no way to ask for "always on top", so on KDE the app installs a **KWin window rule** (keep above, skip task bar, skip switcher, skip pager) and asks KWin to re-read its config over D-Bus. The rule matches the window title as a **substring**: Qt appends the application's display name, so KWin sees `Kadence mini — Kadence`, and an exact match silently never fires. The app backs the file up first, leaves your other rules alone, re-writes the rule when an upgrade changes it, and *Settings → Keep the mini timer above everything* removes it. Verify with `busctl --user call org.kde.KWin /Scripting org.kde.kwin.Scripting loadScript …` or simply: the mini timer should stay on top of a maximised window. |
|
||||
| **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. |
|
||||
| **Scriptable over D-Bus** | `org.kadence.Desktop` exports `StartWork`, `Toggle`, `Pause`, `Complete`, `Extend`, `Interrupt`, `Status`, `SendReport`, `ToggleMini`, `Settings`, `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*. |
|
||||
| **Single instance** | Owning the D-Bus name is the lock; a second launch brings the first one's countdown up and exits. |
|
||||
| **Launcher actions** | Right-click the icon: *Start a pomodoro*, *Start a break*, *Start / pause*, *Send today's report*, *Settings*. |
|
||||
|
||||
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.
|
||||
@@ -188,9 +205,9 @@ export a plain `QObject` instead and Qt names the interface after the class
|
||||
(`local.kadence.TimerDBusService`), so every documented command fails with
|
||||
*"No such interface"*.
|
||||
|
||||
In-window shortcuts: **Space** start/pause · **Ctrl+Enter** finish ·
|
||||
**Ctrl+E** +5 min · **Ctrl+R** refresh sources · **Ctrl+M** mini timer ·
|
||||
**Ctrl+1…5** pages · **Ctrl+W** hide to tray.
|
||||
There are no in-window shortcuts any more — there is no window to be focused in.
|
||||
Bind the D-Bus verbs (or `kadence-desktop --toggle`) to real global shortcuts
|
||||
instead, which is what they were always better as.
|
||||
|
||||
> **Wayland note.** The mini timer's placement and "always on top" are a KWin rule written to
|
||||
> `~/.config/kwinrulesrc` (backed up once as `kwinrulesrc.kadence-backup`); you
|
||||
@@ -204,9 +221,14 @@ In-window shortcuts: **Space** start/pause · **Ctrl+Enter** finish ·
|
||||
## 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.
|
||||
backend URL and token, where the mini timer sits, 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 and are edited in the web app.
|
||||
|
||||
The **Settings…** dialog (tray menu, or `kadence-desktop --settings`) edits that
|
||||
file and nothing else, with one exception that has to be server-side: *view-only
|
||||
mode* writes `pomodoro.timer_enabled`, because the other clients have to honour
|
||||
it too.
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -220,7 +242,7 @@ kadence_desktop/
|
||||
tray.py # the painted countdown icon and its menu
|
||||
integrations/ # notifications, inhibit/DND, MPRIS, launcher, logind, D-Bus, autostart
|
||||
blocking/ # hosts region, DNS stub + resolved route, /proc scanning, the manager that syncs policy
|
||||
qml/ # Material UI: Timer, Tasks, Blocker, Report, Settings
|
||||
qml/ # MiniTimer, SettingsDialog, the Theme singleton, five components
|
||||
helper/ # the privileged helper + polkit action + installer
|
||||
packaging/ # .desktop entry, icon, user installer
|
||||
packaging/ # .desktop entry, icon, systemd user unit, user installer
|
||||
```
|
||||
Reference in new issue
Block a user