refactor: enhance TimerPage layout and functionality; update theme and tokens for improved design consistency
- Refactored TimerPage component to improve layout and user experience. - Updated theme configuration to utilize design tokens for better maintainability. - Introduced new tokens for colors and typography to align with design principles. - Simplified state management and improved timer functionality. - Enhanced accessibility and responsiveness of UI elements.
This commit is contained in:
84 files changed
+4501
-2682
No files matched your search
@@ -8,6 +8,12 @@ hotkey for "start a pomodoro" needs no X11 key-grabbing hack:
|
||||
|
||||
Owning the name also gives single-instance behaviour for free: a second launch
|
||||
sees the name is taken, calls `Raise()` on the first, and exits.
|
||||
|
||||
The verbs live on a **QDBusAbstractAdaptor**, not on the exported object
|
||||
itself. Qt only honours the "D-Bus Interface" class info on an adaptor; export
|
||||
a plain QObject and the interface is named after its class
|
||||
(`local.kadence.TimerDBusService`), which is exactly the "No such interface"
|
||||
every documented command then hits.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -15,8 +21,8 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from PyQt6.QtCore import QObject, pyqtSlot
|
||||
from PyQt6.QtDBus import QDBusConnection
|
||||
from PyQt6.QtCore import QObject, pyqtClassInfo, pyqtSlot
|
||||
from PyQt6.QtDBus import QDBusAbstractAdaptor, QDBusConnection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,13 +32,14 @@ INTERFACE = "org.kadence.Timer"
|
||||
|
||||
|
||||
class TimerDBusService(QObject):
|
||||
"""The exported object. Every slot is a verb you can bind to a key."""
|
||||
"""Holds the actions; the adaptor below is what the bus sees."""
|
||||
|
||||
def __init__(self, actions: dict[str, Callable[[], object]], parent: QObject | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._actions = actions
|
||||
self.adaptor = TimerAdaptor(self)
|
||||
|
||||
def _run(self, name: str) -> str:
|
||||
def run(self, name: str) -> str:
|
||||
action = self._actions.get(name)
|
||||
if action is None:
|
||||
return f"unknown action: {name}"
|
||||
@@ -43,71 +50,113 @@ class TimerDBusService(QObject):
|
||||
log.exception("D-Bus action %s failed", name)
|
||||
return f"error: {exc}"
|
||||
|
||||
|
||||
# Qt generates no introspection XML for a Python adaptor, and without it
|
||||
# `busctl introspect` and KDE's D-Bus shortcut browser have nothing to list —
|
||||
# so the interface describes itself. Every verb takes no arguments and returns
|
||||
# one string.
|
||||
VERBS = (
|
||||
"StartWork",
|
||||
"StartBreak",
|
||||
"Toggle",
|
||||
"Pause",
|
||||
"Resume",
|
||||
"Complete",
|
||||
"Stop",
|
||||
"Extend",
|
||||
"Interrupt",
|
||||
"Status",
|
||||
"SendReport",
|
||||
"ToggleMini",
|
||||
"Raise",
|
||||
)
|
||||
|
||||
INTROSPECTION = "\n".join(
|
||||
[f'<interface name="{INTERFACE}">']
|
||||
+ [
|
||||
f' <method name="{verb}">\n <arg type="s" direction="out"/>\n </method>'
|
||||
for verb in VERBS
|
||||
]
|
||||
+ ["</interface>"]
|
||||
)
|
||||
|
||||
|
||||
@pyqtClassInfo("D-Bus Interface", INTERFACE)
|
||||
@pyqtClassInfo("D-Bus Introspection", INTROSPECTION)
|
||||
class TimerAdaptor(QDBusAbstractAdaptor):
|
||||
"""The published interface. Every slot is a verb you can bind to a key."""
|
||||
|
||||
def __init__(self, parent: TimerDBusService) -> None:
|
||||
super().__init__(parent)
|
||||
self._service = parent
|
||||
|
||||
# D-Bus methods are PascalCase by convention.
|
||||
@pyqtSlot(result=str)
|
||||
def StartWork(self) -> str: # noqa: N802 - D-Bus methods are PascalCase
|
||||
def StartWork(self) -> str: # noqa: N802
|
||||
"""Start a focus interval."""
|
||||
return self._run("start_work")
|
||||
return self._service.run("start_work")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def StartBreak(self) -> str: # noqa: N802
|
||||
return self._run("start_break")
|
||||
return self._service.run("start_break")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Toggle(self) -> str: # noqa: N802
|
||||
"""Start if idle, pause if running, resume if paused — one hotkey."""
|
||||
return self._run("toggle")
|
||||
return self._service.run("toggle")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Pause(self) -> str: # noqa: N802
|
||||
return self._run("pause")
|
||||
return self._service.run("pause")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Resume(self) -> str: # noqa: N802
|
||||
return self._run("resume")
|
||||
return self._service.run("resume")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Complete(self) -> str: # noqa: N802
|
||||
return self._run("complete")
|
||||
return self._service.run("complete")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Stop(self) -> str: # noqa: N802
|
||||
return self._run("stop")
|
||||
return self._service.run("stop")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Extend(self) -> str: # noqa: N802
|
||||
return self._run("extend")
|
||||
return self._service.run("extend")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Interrupt(self) -> str: # noqa: N802
|
||||
"""Count a distraction without stopping the clock."""
|
||||
return self._run("interrupt")
|
||||
return self._service.run("interrupt")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Status(self) -> str: # noqa: N802
|
||||
"""One line: what is running and how much is left."""
|
||||
return self._run("status")
|
||||
return self._service.run("status")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def SendReport(self) -> str: # noqa: N802
|
||||
return self._run("send_report")
|
||||
return self._service.run("send_report")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def ToggleMini(self) -> str: # noqa: N802
|
||||
"""Show or hide the floating mini timer."""
|
||||
return self._service.run("toggle_mini")
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def Raise(self) -> str: # noqa: N802
|
||||
"""Show the window (used by the second instance)."""
|
||||
return self._run("raise_window")
|
||||
"""Show the main window (used by a second instance)."""
|
||||
return self._service.run("raise_window")
|
||||
|
||||
|
||||
def register(service: TimerDBusService) -> bool:
|
||||
"""Own the name and export the object. False means another instance has it."""
|
||||
"""Own the name and export the adaptor. False means another instance has it."""
|
||||
bus = QDBusConnection.sessionBus()
|
||||
if not bus.registerService(SERVICE_NAME):
|
||||
return False
|
||||
bus.registerObject(
|
||||
OBJECT_PATH,
|
||||
service,
|
||||
QDBusConnection.RegisterOption.ExportAllSlots | QDBusConnection.RegisterOption.ExportAllInvokables,
|
||||
)
|
||||
log.info("D-Bus service %s%s is up", SERVICE_NAME, OBJECT_PATH)
|
||||
bus.registerObject(OBJECT_PATH, service, QDBusConnection.RegisterOption.ExportAdaptors)
|
||||
log.info("D-Bus service %s%s is up as %s", SERVICE_NAME, OBJECT_PATH, INTERFACE)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user