fix(display): a WiFi notice preempts the current screen and Vegas yields to it (#712)

A WiFi notice preempts the current screen within about a second (frame loops, post-loop check, make-up dwell), and an interrupted Vegas iteration that yielded for a notice ends the pass so the notice shows next. Goldens: wifi_notice, vegas. First of three run-loop fixes (#712, #713, #714), pre-tested together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-01 15:51:00 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 4be53d048b
commit 6c6394a1a7
8 changed files with 216 additions and 34 deletions
+45 -8
View File
@@ -1144,9 +1144,10 @@ class DisplayController:
Also services pending changes (see _service_pending_changes), and
returns early when one of them changes what the panel should show --
an on-demand start or stop, or the display schedule turning the panel
on or off -- so the caller can act on it instead of finishing a dwell
that could be a minute long (sixty seconds while scheduled off).
an on-demand start or stop, the display schedule turning the panel
on or off, or a WiFi notice arriving -- so the caller can act on it
instead of finishing a dwell that could be a minute long (sixty
seconds while scheduled off).
"""
if duration <= 0:
return
@@ -1156,6 +1157,9 @@ class DisplayController:
mode = self.current_display_mode
display_active = self.is_display_active
on_demand = self.on_demand_active
# Edge-triggered: the notice's own dwell starts with it pending and
# must not cut itself short; any other dwell ends when one arrives.
wifi_pending = self._wifi_notice_pending()
while True:
remaining = end_time - time.time()
@@ -1171,7 +1175,9 @@ class DisplayController:
self._service_pending_changes()
if (self.current_display_mode != mode
or self.is_display_active != display_active
or self.on_demand_active != on_demand):
or self.on_demand_active != on_demand
or (not wifi_pending and self.is_display_active
and self._wifi_notice_pending())):
break
def _note_empty_pass(self) -> None:
@@ -2427,6 +2433,22 @@ class DisplayController:
self._sleep_with_plugin_updates(0.5)
return True
def _wifi_notice_pending(self) -> bool:
"""True when a WiFi notice is waiting that _show_wifi_notice would draw.
Polled from the frame loops, the dwell sleep and after a Vegas
iteration yields, so a notice preempts whatever is on the panel
within about a second instead of waiting for the screen to end --
by which time a short notice has usually expired unseen. Cheap at
frame rate: _check_wifi_status_message stats the file at most once
a second. On-demand outranks the notice, as in _show_wifi_notice.
"""
if self.on_demand_active:
return False
status = self._check_wifi_status_message()
# The 1 s throttle can hand back a result that has expired since.
return bool(status) and time.time() < status['expires_at']
def _resolve_active_mode(self):
"""The mode this pass shows: the on-demand session's current mode
while one is active, else the rotation's.
@@ -2990,6 +3012,11 @@ class DisplayController:
# Scheduled off mid-iteration: blank the
# panel now rather than render a screen.
continue
if self._wifi_notice_pending():
# It yielded for a WiFi notice: the next
# pass shows it, not a rotation screen
# that would outlast a short notice.
continue
except Exception:
logger.exception("Vegas mode error")
# Fall through to normal rotation on error
@@ -3160,7 +3187,8 @@ class DisplayController:
time.sleep(_remaining if _remaining > 0 else 0.001)
if (self.current_display_mode != active_mode
or not self.is_display_active):
or not self.is_display_active
or self._wifi_notice_pending()):
logger.debug("Mode changed during high-FPS loop, breaking early")
break
@@ -3223,7 +3251,8 @@ class DisplayController:
self._service_pending_changes()
if (self.current_display_mode != active_mode
or not self.is_display_active):
or not self.is_display_active
or self._wifi_notice_pending()):
logger.info("Mode changed during display loop from %s to %s, breaking early", active_mode, self.current_display_mode)
break
@@ -3246,9 +3275,12 @@ class DisplayController:
# _activate_on_demand already sets force_change=True and clears the
# display, so the next loop iteration renders the new mode immediately.
# Likewise if the schedule turned the display off
# mid-screen: the next iteration blanks it.
# mid-screen (the next iteration blanks it), or a WiFi
# notice arrived (the next iteration shows it, then this
# mode resumes rather than rotating past it).
if (self.current_display_mode != active_mode
or not self.is_display_active):
or not self.is_display_active
or (not loop_completed and self._wifi_notice_pending())):
continue
# Ensure we honour minimum duration when not dynamic and loop ended early
@@ -3261,6 +3293,11 @@ class DisplayController:
remaining_sleep = max(0.0, max_duration - elapsed)
if remaining_sleep > 0:
self._sleep_with_plugin_updates(remaining_sleep)
# Cut short by a WiFi notice: show it, then
# resume this mode rather than rotating past it.
if (self._wifi_notice_pending()
and time.time() - start_time < max_duration):
continue
if dynamic_enabled:
elapsed_total = time.time() - start_time