Skip to content

feat(session): lock/blank awareness (issue #160) - #164

Merged
jonocodes merged 1 commit into
mainfrom
implement/160
Sep 23, 2026
Merged

jonocodes merged 1 commit into
mainfrom
implement/160

Conversation

@jonocodes

Copy link
Copy Markdown
Owner

Summary

Implements two-state session screen awareness (issue #160). When the desktop session locks, the phone swaps to a lock takeover and the daemon refuses focus-targeting input (presses, key/type injection, trackpad, window raising). When the screen blanks without locking, a soft "wake me" banner appears but input stays enabled—the first tap wakes the machine.

Changes

Protocol & Core

  • StateMessage now carries two independent booleans: locked (credentials required) and blanked (screen off), not a single conflated state. This split is critical—blanking alone can't gate input, else a plain screen blank forfeits "wake from phone" entirely.
  • State snapshots are pushed on every transition AND replayed in the connect snapshot, so a phone joining mid-lock isn't stranded on a stale layout.
  • Added --allow-while-locked opt-out flag for setups that want ungated remote control behind the shield (e.g., scripted kiosks).

Client (React)

  • Lock takeover: Full-screen replacement of grid/trackpad/windows views when locked=true. Shows lock icon, "Screen locked" title, and host name. Keeps the connection dot green (socket is fine).
  • Blank banner: Soft "Screen asleep—press anything to wake" strip when blanked=true and locked=false. Input deliberately stays enabled.
  • Chrome button gating: Trackpad and running-windows buttons disabled while locked; their keyboard shortcuts are early-returned.
  • App badge: Reads "Screen locked" in place of the focused app name during lock.
  • Announcements: Aria-live updates for lock/unlock/wake transitions so screen readers learn why the grid disappeared.
  • Updated terminology: the deckd password gate is now "sign-in needed", freeing "locked" for the desktop session state.

Daemon (Python)

  • Backend capability: GNOME backend advertises session_lock and session_blank capabilities. Probes both halves independently via login1 session LockedHint and org.gnome.ScreenSaver.GetActive.
  • Watch loop: watch_session_state() polls every 2 seconds (slow cadence by design—lock transitions matter at conversation speed).
  • Input gate: While locked (and not opted-out), the daemon refuses presses, key/type, pad/jog, and window raising. All refusals return an error frame with reason screen_locked so a client that missed the transition still surfaces something real.
  • Metric tracking: Lock-refused attempts recorded as lock_dropped outcome in recent-actions and diagnostics.
  • Jog/jog_end silently dropped rather than flooded with error frames (high-frequency scroll).

Testing

  • New tests/test_session_lock.py: 20+ tests covering backend detection, transition polling, connect snapshot, input gating, and opt-out behavior.
  • Updated platform parity tests: GNOME advertises both flags; KDE deliberately does not (follow-up).
  • Client and daemon test suites expanded for lock/blank state transitions.

Documentation

  • Updated CONTEXT.md, PLATFORM-PARITY.md, REFERENCE.md, and README.md with session-state terminology and capability matrix.

Design Decisions

  1. Two booleans, not one: Blank-without-lock is essential for "wake from phone" on machines with Automatic Screen Lock off.
  2. Daemon-side gate: Input refused before it lands on the lock screen; client mirrors the policy in its takeover.
  3. Opt-out flag: --allow-while-locked for kiosks that need ungated remote control.
  4. Graceful degradation: Backends without lock detection advertise no capabilities and never send a state frame; the client's default (all-false) is honest.
  5. GNOME only for now: KDE's approach is documented as a follow-up; advertising a capability without a working observer could strand the client.

… (GNOME)

Two-state session screen awareness (issue #160): `locked` (login1
LockedHint) drives a daemon-side refusal of focus-targeting input and
a full lock takeover on the client; `blanked` (org.gnome.ScreenSaver)
drives only a soft wake banner and keeps presses enabled so a press
wakes the machine.

- protocol: StateMessage gains `blanked`; pushed on transitions and
  replayed in the connect snapshot (capability-gated — a backend that
  can't observe never produces a frame, so no client strands).
- platform: GnomeShellFocusBackend.watch_session_state polls both
  halves; base backend refuses and does not advertise session_lock /
  session_blank; KDE deliberately not inherited (follow-up).
- server: gate in _dispatch_press and key/type/pad/jog paths beside
  the _injection_blocked precedent (lock_dropped outcome +
  ErrorMessage reason screen_locked); --allow-while-locked opt-out.
- client: rename auth label to "sign-in needed" (naming collision);
  lock takeover with host-named copy, connection dot stays green,
  app badge reads "Screen locked"; running-programs button disabled
  with the lock-specific windows-empty slot; now playing / settings /
  editor stay gated-off the takeover (still usable).
- docs/tests: capability matrix rows for both flags, _AXIS-guarded;
  CONTEXT.md language; tests/test_session_lock.py.

Closes #160
@jonocodes
jonocodes merged commit dbb8295 into main Sep 23, 2026
2 of 4 checks passed
@jonocodes
jonocodes deleted the implement/160 branch September 23, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant