Make manual pin permanent instead of a 5-minute timeout

"Show X now" now persists indefinitely (survives restarts too, it's in
config.json) instead of auto-expiring back to rotation after 5 minutes.
Implemented by reusing the existing rotation.enabled flag rather than a
separate scheduler-side timer: pinning a screen sets rotation.enabled=False
and records which screen in a new pinned_screen config field. Re-enabling
rotation -- either the "Resume automatic" button or just checking
"Enabled" again on the Rotation settings page -- clears the pin and
resumes normal cycling either way.

Navidrome priority is unchanged: still checked first, still preempts a
pin the instant something's playing, same as it already preempted
rotation.

Verified end-to-end: pin persists across many scheduler ticks (previously
would have reverted after 5 min), both resume paths (button and settings
checkbox) clear it correctly.
This commit is contained in:
Honey committed 2026-07-22 00:07:25 +02:00
1 parent c0192fca7a
commit 15c7c38d86
4 files changed
+49 -25

No files matched your search

+16 -18
View File
@@ -22,7 +22,6 @@ from PIL import Image
logger = logging.getLogger(__name__)
TICK_SECONDS = 5
MANUAL_OVERRIDE_SECONDS = 300 # how long "show this now" sticks before falling back to automatic
PREVIEW_PATH = Path(__file__).resolve().parent / "web" / "static" / "preview.png"
@@ -49,8 +48,6 @@ class Scheduler:
self._rotation_switched_at = time.time()
self._last_image_hash: str | None = None
self._manual_screen: str | None = None
self._manual_until: float = 0.0
self._force_once = False
self._wake = threading.Event()
@@ -80,23 +77,23 @@ class Scheduler:
self._wake.set()
def request_switch(self, screen_id: str):
"""Pin a screen for MANUAL_OVERRIDE_SECONDS. Navidrome priority can
still preempt it if something starts playing -- that's the one thing
this app promises to always show reliably -- but rotation won't."""
self._manual_screen = screen_id
self._manual_until = time.time() + MANUAL_OVERRIDE_SECONDS
"""Pin a screen indefinitely (persisted in config, turns rotation
off). Navidrome priority can still preempt it if something starts
playing -- that's the one thing this app promises to always show
reliably -- but rotation won't, until it's explicitly re-enabled."""
self.config.pin_screen(screen_id)
self._wake.set()
def request_resume_rotation(self):
self._manual_screen = None
self.config.resume_rotation()
self._wake.set()
def get_status(self) -> dict:
with self._status_lock:
status = dict(self._status)
manual_active = bool(self._manual_screen) and time.time() < self._manual_until
status["manual_screen"] = self._manual_screen if manual_active else None
status["manual_until"] = time.strftime("%H:%M:%S", time.localtime(self._manual_until)) if manual_active else None
rotation = self.config.get_rotation()
pinned = self.config.get_pinned_screen()
status["pinned_screen"] = pinned if (pinned and not rotation.get("enabled", True)) else None
return status
# ------------------------------------------------------------- internal
@@ -144,18 +141,19 @@ class Scheduler:
Priority order: Navidrome-when-playing (if enabled) always wins --
that's the one thing this app promises to show reliably. Then a
manual "show this now" pin, while it's still within its window. Then
normal rotation.
manual "show this now" pin, if rotation is off because of one (set
together in Config.pin_screen()). Then normal rotation.
"""
if self.config.get_navidrome_priority() and "navidrome" in self.screens:
screen_id, state, checked = self._poll_screen("navidrome")
if state is not None:
return screen_id, state, checked
if self._manual_screen:
if time.time() < self._manual_until:
return self._poll_screen(self._manual_screen)
self._manual_screen = None # window expired
rotation = self.config.get_rotation()
if not rotation.get("enabled", True):
pinned = self.config.get_pinned_screen()
if pinned and pinned in self.screens:
return self._poll_screen(pinned)
return self._rotation_screen()