This commit is contained in:
timka123 committed 2026-10-18 18:02:05 +03:00
commit 17a0a3c4c9
121 files changed
+17641

No files matched your search

@@ -0,0 +1,119 @@
"""Knowing when you walked away.
Two signals, both from the session itself rather than from guesswork:
* **logind** emits `Lock`/`Unlock` on your session object, and `PrepareForSleep`
when the machine suspends. Locking the screen is the clearest possible "I am
not at this desk" — the timer pauses, and resumes when you come back.
* **Idle time** comes from KDE's `org.kde.screensaver.GetSessionIdleTime` or
GNOME's `org.gnome.Mutter.IdleMonitor`, so an interval you forgot to stop does
not quietly bank half an hour of "focus" you spent in the kitchen.
"""
from __future__ import annotations
import logging
import os
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
from PyQt6.QtDBus import QDBusConnection, QDBusInterface, QDBusMessage
log = logging.getLogger(__name__)
_LOGIND = "org.freedesktop.login1"
_MANAGER_PATH = "/org/freedesktop/login1"
_MANAGER_IFACE = "org.freedesktop.login1.Manager"
_SESSION_IFACE = "org.freedesktop.login1.Session"
class SessionWatcher(QObject):
"""Emits lock/unlock/sleep, and answers "how long have they been idle?"."""
locked = pyqtSignal()
unlocked = pyqtSignal()
sleeping = pyqtSignal()
woke = pyqtSignal()
def __init__(self, parent: QObject | None = None) -> None:
super().__init__(parent)
self._system_bus = QDBusConnection.systemBus()
self._session_bus = QDBusConnection.sessionBus()
self._session_path = self._find_session_path()
if self._session_path:
for signal_name, slot in (("Lock", self._on_lock), ("Unlock", self._on_unlock)):
self._system_bus.connect(_LOGIND, self._session_path, _SESSION_IFACE, signal_name, slot)
log.debug("watching logind session %s", self._session_path)
self._system_bus.connect(_LOGIND, _MANAGER_PATH, _MANAGER_IFACE, "PrepareForSleep", self._on_sleep)
# Plasma's own lock signal, for setups where logind's does not arrive.
self._session_bus.connect(
"org.freedesktop.ScreenSaver",
"/org/freedesktop/ScreenSaver",
"org.freedesktop.ScreenSaver",
"ActiveChanged",
self._on_screensaver,
)
def _find_session_path(self) -> str:
"""Ask logind for *this* session's object path."""
manager = QDBusInterface(_LOGIND, _MANAGER_PATH, _MANAGER_IFACE, self._system_bus)
if not manager.isValid():
return ""
session_id = os.environ.get("XDG_SESSION_ID", "")
if session_id:
reply = manager.call("GetSession", session_id)
arguments = reply.arguments()
if arguments and not reply.errorName():
return str(arguments[0].path() if hasattr(arguments[0], "path") else arguments[0])
reply = manager.call("GetSessionByPID", os.getpid())
arguments = reply.arguments()
if arguments and not reply.errorName():
value = arguments[0]
return str(value.path() if hasattr(value, "path") else value)
return ""
def idle_seconds(self) -> int:
"""Seconds since the last input, or -1 when nothing can tell us."""
kde = QDBusInterface(
"org.freedesktop.ScreenSaver", "/ScreenSaver", "org.kde.screensaver", self._session_bus
)
if kde.isValid():
reply = kde.call("GetSessionIdleTime")
arguments = reply.arguments()
if arguments and not reply.errorName():
return int(arguments[0]) // 1000
mutter = QDBusInterface(
"org.gnome.Mutter.IdleMonitor",
"/org/gnome/Mutter/IdleMonitor/Core",
"org.gnome.Mutter.IdleMonitor",
self._session_bus,
)
if mutter.isValid():
reply = mutter.call("GetIdletime")
arguments = reply.arguments()
if arguments and not reply.errorName():
return int(arguments[0]) // 1000
return -1
@pyqtSlot(QDBusMessage)
def _on_lock(self, _message: QDBusMessage) -> None:
self.locked.emit()
@pyqtSlot(QDBusMessage)
def _on_unlock(self, _message: QDBusMessage) -> None:
self.unlocked.emit()
@pyqtSlot(QDBusMessage)
def _on_sleep(self, message: QDBusMessage) -> None:
arguments = message.arguments()
going_to_sleep = bool(arguments[0]) if arguments else False
(self.sleeping if going_to_sleep else self.woke).emit()
@pyqtSlot(QDBusMessage)
def _on_screensaver(self, message: QDBusMessage) -> None:
arguments = message.arguments()
if not arguments:
return
(self.locked if bool(arguments[0]) else self.unlocked).emit()