Skip to content

Show the splash on macOS screen lock, not on Stream Deck key inactivity #4

Description

@louisalexander

Problem

The deck should show the Flightdeck splash when the operator walks away —
when the macOS screen is locked — and show live fleet state at every
other moment.

What ships today is the Stream Deck app's own sleep wallpaper
(assets/brand/flightdeck-wallpaper-768x384.png, set via the app's
per-device settings). That removes the Elgato logo and is the right
picture, but it fires on the wrong signal.

Why the built-in timer is the wrong signal

The app's sleep feature is driven by three per-device preferences:

key meaning
map_dev_sleep_time whether the device sleeps at all
map_dev_sleep_time_min idle minutes before it sleeps
map_dev_sleep_wallpaper_path the image to show while asleep

Its idle clock counts Stream Deck key inactivity, not macOS lock state.
So sitting at the desk working, without pressing a deck key for N minutes,
puts the whole panel to sleep behind brand chrome — including a slot that
has gone amber and is asking for a decision.

That directly contradicts a rule the codebase already enforces. From
plugin/src/splash.ts:

Amber means operator attention; do not use it decoratively, and chrome
must never obscure an agent that needs the operator. A boot tile is
chrome. So a Fleet Slot showing blocked [...] is never eligible for
the splash, boot window or not.

shouldShowSplash() enforces exactly that for the boot window. The sleep
wallpaper has no such exemption: it is painted by the Stream Deck app at
the device level and overrides whatever the plugin has set.

Proposed work

Drive the idle splash from macOS lock state instead, and reuse the boot
splash machinery rather than inventing a second painting path.

  • Signal. macOS exposes lock state via
    CGSessionCopyCurrentDictionary()'s CGSSessionScreenIsLocked key, and
    posts com.apple.screenIsLocked / com.apple.screenIsUnlocked on the
    distributed notification centre. Decide between polling the session
    dictionary and subscribing to the notifications; subscribing is prompt
    and cheap but needs a resident helper, whereas the repo already has a
    launchd job and a slots.json the plugin watches.
  • Transport. The natural seam is the one that already exists: have the
    python side publish lock state (a locked field in slots.json, or a
    marker file in the manner of ~/.fleet/blocked/<id>) and let the plugin
    react to it. Note the reaper's 15s cadence is too slow to piggyback on
    directly — locking the screen and waiting 15s for the splash would feel
    broken, so this likely wants its own watcher.
  • Reuse. splashTileSvg(row, col) already renders the per-key artwork,
    and renderBootTile already handles the splash/Night decision. An idle
    splash is the same paint with a different predicate; the boot window and
    the lock state should collapse into one "is chrome showing?" question
    rather than two independent code paths racing to set the same key.
  • Keep the amber exemption. Whatever predicate replaces
    isBooting(), a blocked slot must still refuse the splash. Walking
    away is exactly when an agent needing a decision matters most, and a
    locked screen is precisely when someone might glance at the deck from
    across the room.
  • Turn the built-in timer off as part of this, or it will fire first
    and cover the lock-aware behaviour. Whether flightdeck should manage
    that preference itself or simply document it is an open question — see
    below.

Open question: writing the preference

map_dev_sleep_wallpaper_path is set through the app's UI today. Whether
it can be written programmatically is unverified: the binary carries a
wallpaper://image/ URI scheme, so the stored value may not be a plain
filesystem path, and the app caches preferences in memory and rewrites
them on quit — a blind write risks being silently discarded. If the
installer is ever to configure this, read back what the UI produced first
and match that format exactly.

Coverage required

  • lock → splash across the panel; unlock → live state restored;
  • a blocked slot is never covered, locked or not;
  • boot window and lock state do not fight over the same key;
  • lock state that changes while the plugin is starting is not missed.

Follow-up to the sleep wallpaper added alongside this issue. The wallpaper
is the interim answer: right picture, wrong trigger.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions