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.
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'sper-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:
map_dev_sleep_timemap_dev_sleep_time_minmap_dev_sleep_wallpaper_pathIts 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:shouldShowSplash()enforces exactly that for the boot window. The sleepwallpaper 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.
CGSessionCopyCurrentDictionary()'sCGSSessionScreenIsLockedkey, andposts
com.apple.screenIsLocked/com.apple.screenIsUnlockedon thedistributed 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.jsonthe plugin watches.python side publish lock state (a
lockedfield inslots.json, or amarker file in the manner of
~/.fleet/blocked/<id>) and let the pluginreact 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.
splashTileSvg(row, col)already renders the per-key artwork,and
renderBootTilealready handles the splash/Night decision. An idlesplash 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.
isBooting(), ablockedslot must still refuse the splash. Walkingaway 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.
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_pathis set through the app's UI today. Whetherit can be written programmatically is unverified: the binary carries a
wallpaper://image/URI scheme, so the stored value may not be a plainfilesystem 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
blockedslot is never covered, locked or not;Follow-up to the sleep wallpaper added alongside this issue. The wallpaper
is the interim answer: right picture, wrong trigger.