This document defines the current desktop session backend model.
The goal is to unify providers semantically, not mechanically.
For the broader map of systemd, lifecycle, desktop, and command-entrypoint events that consume these semantics, see runtime-event-handler-map.md.
GNOME, native Wayland, swayidle, and future backends do not expose the same APIs or the same
event richness. LG Buddy should not force them to look identical at the
transport layer. Instead, the session module defines:
- the canonical event meanings LG Buddy cares about
- normalized observations with source identity and observation time
Source modules own their provider-specific connection or process, validation, polling, and translation into that shared contract. The runner selects sources, owns worker lifetime and inactivity timing, and dispatches policy.
sessionowns semantics.- Source modules own provider-specific runtime mechanics and mapping.
- Missing provider observations stay missing.
- LG Buddy does not invent synthetic provider behavior just to fill gaps in the interface.
- Auxiliary input sources belong to the session runtime, not desktop backend modules.
That means a source can omit WakeRequested without being treated as
incomplete. No source-facing capability object is needed when runtime behavior
does not consume it.
These are the semantic events the runtime should reason about.
| Event | Meaning |
|---|---|
Idle |
The backend reports the session/display has become idle. |
Active |
The backend reports the session/display is active again after an idle period. |
WakeRequested |
The backend explicitly requests the display be woken. |
UserActivity |
The backend can observe user activity before it emits a normal Active transition. |
BeforeSleep |
The backend reports that the system is about to suspend. |
AfterResume |
The backend reports that the system resumed from suspend. |
Lock |
The backend reports that the session should lock or has locked. |
Unlock |
The backend reports that the session should unlock or has unlocked. |
ActiveandUnlockare not the same thing.- Some backends can report an active display transition without a session unlock event.
UserActivityis earlier and weaker thanActive.- It exists for native desktop adapters that can expose fresh activity before the desktop emits its normal active/wake signal. GNOME + Mutter is the current production example.
- It can also come from auxiliary activity sources owned by the session runtime, such as gamepad input that the desktop does not classify as activity.
WakeRequestedis optional.- Some providers expose an explicit wake request.
- Others only expose idle/resume transitions.
- Lock state is an optional cross-cutting Linux source, not a prerequisite of a
selected desktop backend. The shared session runtime observes the resolved
graphical logind session's
LockedHintwhen available. Initial or changedtruemaps toLock;falsemaps toUnlockonly after a prior locked state. Unlock is informational and never requests screen restore.
Native sources publish SessionObservation values: canonical session events,
inactivity facts, or idle-blanking permission updates, with an EventSource and
observation time. A pending permission refresh suspends automatic blanking
without changing the last confirmed permission or renewing the deadline.
Source modules do not decide whether to blank or restore the screen.
GNOME and native Wayland feed activity facts to the shared runner, which owns
their configured inactivity deadline. swayidle owns its initial timeout but
publishes Idle and independent desktop-activity observations back to the same
runner. All three backends therefore share blank, restore, and post-blank
power-off policy.
screen.honor_idle_inhibitors defaults to disabled. When enabled, native
sources also publish IdleBlankingPermission. The runner initially withholds
automatic blanking until the source establishes permission, including when an
inhibitor predates startup. Restoring permission starts a fresh full timeout;
duplicate observations do not extend it. Permission changes are never activity
or restore requests. Real desktop and gamepad input still share the inactivity
deadline. Explicit lock behavior and an already pending post-blank power-off
deadline are unaffected.
This is the current mapping for the known backends, with implementation status called out explicitly.
| Backend | Idle | Active | WakeRequested | UserActivity | Lock/Unlock | Timing and execution |
|---|---|---|---|---|---|---|
| GNOME | Observed but not authoritative | Yes | Yes | Yes | Optional logind source | Shared runner owns the configured deadline over ScreenSaver and Mutter observations |
| Native Wayland | Observed but not authoritative | Resumed notification | No | Yes | Optional logind source | Shared runner owns the configured deadline using ext_idle_notifier_v1 version 2 or newer |
swayidle |
Timeout becomes Idle |
Resume becomes independent desktop activity | No | No direct equivalent | Optional logind source | Source process owns the configured initial timeout; shared runner owns policy and post-blank timing |
Current mapping:
| Provider surface | Canonical meaning | Current Rust Status |
|---|---|---|
org.gnome.ScreenSaver.ActiveChanged (true,) |
Idle observation that cannot bypass LG Buddy's timeout | Implemented |
org.gnome.ScreenSaver.ActiveChanged (false,) |
Active |
Implemented |
org.gnome.ScreenSaver.WakeUpScreen |
WakeRequested |
Implemented |
Recent activity from org.gnome.Mutter.IdleMonitor.GetIdletime (honoring disabled) |
UserActivity |
Implemented |
Mutter WatchFired for the current AddUserActiveWatch (honoring enabled) |
UserActivity |
Implemented |
org.gnome.SessionManager.IsInhibited(8) |
Idle-blanking permission when honoring is enabled | Implemented |
Notes:
- GNOME requires GNOME Shell,
org.gnome.ScreenSaver, andorg.gnome.Mutter.IdleMonitor. - Enabling inhibitor honoring additionally requires
org.gnome.SessionManager. The source reads its current aggregate idle-inhibition state at startup and after trustedInhibitorAdded,InhibitorRemoved, or owner-change signals. Automatic blanking pauses while each refresh is pending; an unchanged result preserves the existing deadline. User input comes from Mutter's one-shot user-active watches, rearmed after each signal. UnlikeGetIdletime, these do not treat the idle-counter reset on inhibitor release as activity. Other inhibition flags do not block blanking. Losing the service or failing to read its state ends the source with a diagnostic error rather than assuming blanking is allowed. With the setting disabled, this extra dependency is not queried or subscribed to. - LG Buddy owns the configured timeout value for this backend.
- LG Buddy owns one inactivity deadline. Desktop, auxiliary, active, and wake
activity reports reset it; expiry after
screen_idle_timeouttriggers blanking. - With honoring disabled, Mutter idletime is used only to detect recent desktop activity. Its absolute value does not trigger blanking.
- ScreenSaver idle cannot trigger blanking by itself. ScreenSaver active and wake signals reset the same LG Buddy deadline and remain restore observations evaluated by screen policy.
Linux gamepad input is a desktop-independent auxiliary activity source. The
shared session runtime owns its lifecycle and feeds
UserActivityObserved into the same inactivity engine as the selected desktop
provider. Resulting runtime events retain the AuxiliaryInput source.
The gamepad source owns its device set internally. It performs an initial scan, refreshes on Linux input-device add, remove, and change events, and periodically reconciles in case an event is missed. Standard controller input is read from evdev. Logitech G923 wheel and pedal activity has a narrow raw HID fallback for hosts where those reports do not appear on the evdev node.
GNOME, native Wayland, and swayidle use the shared session runtime. The Wayland
provider owns only its connection, registry, seats, notifications, and activity
facts; it does not acquire gamepad responsibility.
Every enabled monitor backend also starts an optional system-bus observer for the
current graphical logind session. Session selection accepts only an active,
local x11 or wayland session in a user class and owned by the current UID.
An explicit XDG_SESSION_ID is validated against those rules; without it, LG
Buddy requires exactly one matching session and refuses ambiguous candidates.
The observer resolves logind's current unique bus owner, subscribes to
org.freedesktop.DBus.Properties.PropertiesChanged from that owner for the
exact session, and reconciles the initial LockedHint before processing changes.
It watches ownership of org.freedesktop.login1 and repeats session resolution,
subscription, and reconciliation when logind restarts.
A lock enters the existing blanked inactivity state and dispatches
SessionLocked from LinuxLogind, so configured-input, marker, sleep-phase,
and restore policy remain centralized in screen.rs. Unlock performs no screen
action. A lock-triggered blank starts a fixed one-second activity grace period.
Independent desktop or gamepad activity observed before the lock or inside that
period is ignored without canceling the pending timed power-off. At the grace
boundary, the first fresh independent activity can restore the picture while the
lock screen is still shown. The shared policy compares each sample's monotonic
observation time with the lock time, so delayed dispatch does not change the
decision. Provider wake/deactivation signals associated with unlocking do not
restore it; normal inactivity timing resumes from accepted fresh activity.
Known environment support follows whether the desktop or locker maintains
logind's LockedHint for the graphical session:
| Environment | Lock observation |
|---|---|
| GNOME Shell 40 or newer on Wayland or X11 | Supported |
| KDE Plasma 5.20 or newer on Wayland or X11 | Supported |
niri built with D-Bus support and a valid XDG_SESSION_ID |
Supported |
| stock sway with swaylock | Absent by default; LockedHint is not maintained |
| Hyprland with hyprlock | Absent by default; LockedHint is not maintained |
This behavior is opportunistic. If logind is unavailable or no eligible session
can be resolved, LG Buddy logs a diagnostic and continues ordinary idle/activity
monitoring. If the desktop or locker never updates LockedHint, no lock event is
observed and ordinary monitoring likewise continues unchanged. It does not use
desktop-name checks, logind lock-request signals, locker hooks, or a Wayland
session-lock protocol. The logind observer is not a backend eligibility or
selection requirement.
The native wayland backend requires ext_idle_notifier_v1 version 2 or newer
and at least one advertised wl_seat. It monitors every seat, including
seats that currently advertise no input capabilities, using zero-timeout idle
notifications from get_input_idle_notification. Its resumed maps to desktop
activity; idled remains observational, so only LG Buddy's inactivity deadline
can trigger blanking.
When inhibitor honoring is enabled, a separate zero-timeout
get_idle_notification observes permission on each seat. Blanking is allowed
only once every seat reports idle. Its inhibitor-aware resumed withdraws
permission without reporting input or restoring the TV. New seats initially
withhold permission, and removing a seat recomputes the aggregate state.
Seats are added and removed dynamically. Connection or dispatch loss, removal
of the bound notifier, or removal of the last seat is fatal to the provider and
causes the user service to retry. Explicit selection reports capability errors
without falling back. auto selects native Wayland after the complete GNOME
contract and before the deprecated swayidle compatibility backend.
Current mapping:
| Provider surface | Canonical meaning | Current Rust Status |
|---|---|---|
timeout <n> <cmd> |
Publish Idle to the shared runner |
Implemented |
resume <cmd> |
Publish independent desktop activity to the shared runner | Implemented |
Notes:
swayidleis deprecated, remains accepted for existing explicit selections, and is planned for removal in 2.0.0 after the native provider remains field-validated across supported compositors and the 1.x migration window.swayidledoes not provide a clear equivalent of GNOME'sWakeRequested.- Its source-owned timeout always honors compositor inhibition, independently
of
screen.honor_idle_inhibitors, including whenautofalls back to it. The preference is hidden for explicitswayidleselections; this compatibility backend does not offer the native default-off behavior. swayidledoes not provide a Mutter-style early activity surface.- LG Buddy owns the configured timeout value for this backend.
- The shared runner owns lock observation, screen policy, and the post-blank power-off deadline. Gamepad activity can cancel that second deadline, but does not reset swayidle's source-owned initial timeout.
The code split is:
crates/lg-buddy/src/session.rs- canonical events
- normalized source observations
crates/lg-buddy/src/session/runner.rs- source selection, worker lifetime, observation multiplexing, shared inactivity state, and policy dispatch
crates/lg-buddy/src/session/actions.rs- action dependency assembly and native TV client ownership across compatible events; one-shot commands use the same assembly with a finite lifetime
crates/lg-buddy/src/session/gamepad/- desktop-independent auxiliary input discovery and activity observations
crates/lg-buddy/src/sources/desktop/gnome.rs- GNOME session-bus connection, subscriptions, owner validation, Mutter polling, event loop, and observation mapping
crates/lg-buddy/src/sources/desktop/wayland.rs- native Wayland registry, seat, idle-notification, and activity mapping
crates/lg-buddy/src/sources/linux/logind.rs- system lifecycle mapping plus the optional current-session lock observer,
including bus setup, session resolution, rebinding, and
LockedHinttranslation
- system lifecycle mapping plus the optional current-session lock observer,
including bus setup, session resolution, rebinding, and
crates/lg-buddy/src/sources/desktop/swayidle.rs- production
swayidleprocess invocation and timeout/resume fact transport
- production
This keeps backend-specific details out of runtime policy and prevents each backend from quietly defining its own semantics.