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>
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""Entry point.
|
|
|
|
Run with no arguments to open the app. The verbs (`--toggle`, `--status`, …)
|
|
talk to a *running* instance over D-Bus instead of starting a second one, which
|
|
is what makes them usable as global shortcuts:
|
|
|
|
kadence-desktop --toggle # bind this to a key in System Settings
|
|
kadence-desktop --mini # show/hide the floating mini timer
|
|
kadence-desktop --status # prints "Focus 18:42 · Write the docs · 3/8 today"
|
|
kadence-desktop --settings # open the one window with fields in it
|
|
|
|
There is no main window: Kadence on the desktop is a tray agent that enforces
|
|
the blocking policy and shows a floating countdown. Started with no arguments it
|
|
comes up with the mini timer; `--minimized` starts it with nothing on screen,
|
|
which is what the autostart entry and the systemd user unit do.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
REMOTE_VERBS = {
|
|
"toggle": "Toggle",
|
|
"start": "StartWork",
|
|
"break": "StartBreak",
|
|
"pause": "Pause",
|
|
"resume": "Resume",
|
|
"complete": "Complete",
|
|
"stop": "Stop",
|
|
"extend": "Extend",
|
|
"interrupt": "Interrupt",
|
|
"status": "Status",
|
|
"report": "SendReport",
|
|
"mini": "ToggleMini",
|
|
"show": "Raise",
|
|
"settings": "Settings",
|
|
}
|
|
|
|
|
|
def _remote(verb: str) -> int:
|
|
"""Call the running instance. Returns a process exit code."""
|
|
from PyQt6.QtCore import QCoreApplication
|
|
from PyQt6.QtDBus import QDBusConnection, QDBusInterface
|
|
|
|
_app = QCoreApplication(sys.argv)
|
|
interface = QDBusInterface(
|
|
"org.kadence.Desktop", "/Timer", "org.kadence.Timer", QDBusConnection.sessionBus()
|
|
)
|
|
if not interface.isValid():
|
|
print("Kadence is not running.", file=sys.stderr)
|
|
return 1
|
|
reply = interface.call(REMOTE_VERBS[verb])
|
|
arguments = reply.arguments()
|
|
if arguments:
|
|
print(arguments[0])
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(prog="kadence-desktop", description=__doc__)
|
|
parser.add_argument("--minimized", action="store_true", help="start in the tray only")
|
|
parser.add_argument(
|
|
"--view-only", action="store_true", help="start in view-only mode with timer turned off"
|
|
)
|
|
parser.add_argument("--verbose", action="store_true")
|
|
for verb in REMOTE_VERBS:
|
|
parser.add_argument(
|
|
f"--{verb}", action="store_true", help=f"ask the running instance to {verb}"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
logging.basicConfig(
|
|
level=logging.DEBUG if args.verbose else os.environ.get("KADENCE_LOG", "INFO").upper(),
|
|
format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
|
|
)
|
|
|
|
for verb in REMOTE_VERBS:
|
|
if getattr(args, verb):
|
|
return _remote(verb)
|
|
|
|
from kadence_desktop.app import KadenceApp
|
|
from kadence_desktop.integrations.dbus_service import call_existing
|
|
|
|
# Single instance: if somebody already owns the name, show their countdown.
|
|
if call_existing("Raise"):
|
|
print("Kadence is already running — brought its timer up.")
|
|
return 0
|
|
|
|
return KadenceApp(sys.argv, minimized=args.minimized, view_only=args.view_only).run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|