Watch
1
0
Fork
You've already forked souveraine
0

lockhint: say so when the session can never hold a lock hint

logind refuses SetLockedHint for any session that is not Class=user. When
that happens LockedHint is pinned at no, is_locked() is permanently false,
every request_blank() times out its ack budget, and the panel goes dark on
a session nobody could confirm was locked — the machine keeps working and
only the security half is gone.

That state ran for weeks on blueline undetected: greetd's default_session
is the greeter slot, so any restart produced it, and the only trace was
one blank-without-lock per blank, which reads like a timing problem rather
than a structural one. Now it is one line naming the cause at startup.

A warning, not a refusal to start: a phone that will not boot because its
session class is wrong is worse than one that boots and says so.
This commit is contained in:
Fimeg 2026-08-02 15:44:43 -04:00
commit 106c3bb680

View file

@ -206,6 +206,57 @@ pub(crate) fn parse_locked_hint(line: &str) -> Option<bool> {
} }
} }
/// Say so, loudly and once, when this session can never hold a lock hint.
///
/// logind refuses `SetLockedHint` for any session that is not `Class=user` —
/// "Session does not support lock screen". When that happens `LockedHint` is
/// pinned at `no` forever, so `is_locked()` is permanently false, every
/// `request_blank()` times out its ack budget, and the panel goes dark on a
/// session nobody could confirm was locked. The machine keeps working and keeps
/// blanking; only the security half is gone.
///
/// That state lasted weeks undetected on blueline (greetd's `default_session`
/// runs as the greeter, so any `systemctl restart greetd` produced it), and the
/// only trace was one `blank-without-lock` per blank — an entry that reads like
/// a timing problem rather than a structural one. This turns it into a single
/// line naming the cause.
///
/// A warning and not a refusal to start: a phone that will not boot because its
/// session class is wrong is worse than one that boots and says the lock hint is
/// unavailable.
fn warn_if_session_cannot_lock(path: &str) {
let Ok(out) = Command::new("busctl")
.args([
"--system",
"get-property",
"org.freedesktop.login1",
path,
"org.freedesktop.login1.Session",
"Class",
])
.output()
else {
return;
};
if !out.status.success() {
return;
}
// `s "user"`
let text = String::from_utf8_lossy(&out.stdout);
let Some(class) = text.split('"').nth(1) else {
return;
};
if class == "user" {
return;
}
warn!(
"[lock-hint] session {path} is Class={class}, not user — logind will refuse SetLockedHint \
for it, so LockedHint can never become true, `locked` stays false, and EVERY blank will \
go out on a session that could not be confirmed locked (blank-without-lock). On greetd \
this means the desktop is running in the greeter slot; see LOCK-DPMS-LESSONS §1."
);
}
/// Watch `LockedHint` forever, reporting every change. /// Watch `LockedHint` forever, reporting every change.
/// ///
/// The callback fires once at startup with the current value, then on each /// The callback fires once at startup with the current value, then on each
@ -225,6 +276,7 @@ pub fn spawn(on_change: Arc<dyn Fn(bool) + Send + Sync>) {
} }
}; };
info!("[lock-hint] watching {path}"); info!("[lock-hint] watching {path}");
warn_if_session_cannot_lock(&path);
let mut last = match read_locked_hint(&path) { let mut last = match read_locked_hint(&path) {
Ok(v) => { Ok(v) => {