lock: adopt the session lock across a scene reload
sessiond admits shell_ready from the lease holder's own pid; the shell carries its lock request through the reload so WlSessionLock adopts the existing lock instead of taking the unlock branch on it.
This commit is contained in:
parent
0745b48dc5
commit
efe2d0d1e0
2 changed files with 211 additions and 1 deletions
|
|
@ -66,6 +66,10 @@ Scope {
|
|||
target: GlobalStates
|
||||
function onScreenLockedChanged() {
|
||||
Persistent.states.lock.locked = GlobalStates.screenLocked;
|
||||
// Persistent is a file and answers asynchronously, so it can
|
||||
// never be the thing a reload reads at construction. This is
|
||||
// the same fact held in-process, where the reload can see it.
|
||||
lockContinuity.held = GlobalStates.screenLocked;
|
||||
if (GlobalStates.screenLocked) {
|
||||
lockContext.reset();
|
||||
lockContext.tryFingerUnlock();
|
||||
|
|
@ -101,8 +105,54 @@ Scope {
|
|||
}
|
||||
}
|
||||
|
||||
// TASK-48. The lock REQUEST, carried across a quickshell scene reload by
|
||||
// quickshell's own reload machinery — in-process and synchronous, which is
|
||||
// what this has to be. Declared before the WlSessionLock on purpose:
|
||||
// `Scope` is a ReloadPropagator and reloads its children in declaration
|
||||
// order, so `held` is restored and applied before the lock below reloads.
|
||||
//
|
||||
// Why any of this is needed, from reading quickshell's session_lock.cpp:
|
||||
// on reload the new WlSessionLock adopts the outgoing one's
|
||||
// SessionLockManager and then calls realizeLockTarget() with whatever
|
||||
// `locked` evaluated to at construction. GlobalStates is a fresh singleton
|
||||
// by then, so screenLocked is false, so lockTarget is false — and the
|
||||
// false branch is `unlock()`, which on an adopted manager that IS locked
|
||||
// sends ext_session_lock_v1.unlock_and_destroy. A scene reload therefore
|
||||
// UNLOCKS the session. What is left is a compositor with no lock surface,
|
||||
// a phone that reads black, and `{"locked":false,"lockRequested":true}`.
|
||||
// If instead the adoption does not match, the new manager's lock() is
|
||||
// refused (the outgoing lock is still the process-global holder) and
|
||||
// updateSurfaces(true) is called anyway — that is the FATAL this task was
|
||||
// raised for. Same root cause, two faces.
|
||||
//
|
||||
// With the request true at construction, realizeLockTarget takes the adopt
|
||||
// branch: surfaces are re-created against the SAME compositor lock,
|
||||
// manager->lock() declines harmlessly because we already hold it, and
|
||||
// updateSurfaces sees an active lock. No re-request, no denial, no
|
||||
// unlock_and_destroy. This is the task's "adopting beats re-requesting",
|
||||
// and it is the only branch that never opens the panel.
|
||||
PersistentProperties {
|
||||
id: lockContinuity
|
||||
reloadableId: "souveraineLockContinuity"
|
||||
|
||||
property bool held: false
|
||||
|
||||
onLoaded: {
|
||||
if (lockContinuity.held && !GlobalStates.screenLocked) {
|
||||
console.log("[lock] scene reload owed a lock — adopting, not re-requesting");
|
||||
GlobalStates.screenLocked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WlSessionLock {
|
||||
id: lock
|
||||
// Explicit so the adoption survives being reparented out of a Scope.
|
||||
// Under a Scope children match by index and this is unused; anywhere
|
||||
// else an empty reloadableId means oldInstance is ALWAYS null, the
|
||||
// manager is never adopted, and the lock cannot be re-acquired for the
|
||||
// life of the process.
|
||||
reloadableId: "souveraineSessionLock"
|
||||
locked: GlobalStates.screenLocked
|
||||
surface: root.sessionLockSurface
|
||||
|
||||
|
|
@ -155,6 +205,25 @@ Scope {
|
|||
}
|
||||
}
|
||||
|
||||
// `secure` is an EDGE, and after a reload that adopts the lock that edge
|
||||
// is in the past: the compositor acked before this tree existed, so
|
||||
// onSecureChanged never fires and GlobalStates.screenLockSecure would sit
|
||||
// false on a session that is genuinely secure. Everything gating on it —
|
||||
// redaction, capability tiers, and the locked_ack this handoff owes
|
||||
// sessiond — would then be wrong in the dangerous direction.
|
||||
//
|
||||
// Publish the LEVEL once the reload has actually run. Component.onCompleted
|
||||
// is too early: Reloadable defers onReload to after component completion,
|
||||
// so `lock.secure` is still false there. Setting screenLockSecure is enough
|
||||
// to send the ack — SessiondBridge is already listening for it.
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onReloadCompleted() {
|
||||
GlobalStates.screenLockSecure = lock.secure;
|
||||
root.dismissBloomIfSecure();
|
||||
}
|
||||
}
|
||||
|
||||
// Idempotent, and deliberately NOT a timeout. A bloom that outlives its
|
||||
// reason is a bug to locate, not something to paper over with a timer — if
|
||||
// this is still up while the lock is secure, the caller is missing and the
|
||||
|
|
|
|||
Loading…
Reference in a new issue