From 2d6ddc431b49c7cf15117c18004a2487796b403f Mon Sep 17 00:00:00 2001 From: maziggy Date: Sun, 12 Apr 2026 10:56:44 +0200 Subject: [PATCH] feat(spoolbuddy): auto-wake display on NFC tag scan or scale activity (#945) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Display now powers on via wlopm when the daemon detects an NFC tag or a significant weight change (>=50g, i.e. spool placed/removed) while the screen is blanked. Minor scale fluctuations no longer wake the display or prevent blanking. The daemon only re-blanks screens it woke itself — touch-based wake/blank stays with swayidle so the two don't conflict. Daemon discovers the Wayland session from the shared runtime dir since it runs as a systemd service outside the compositor. --- spoolbuddy/daemon/display_control.py | 50 ++++++++++------------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/spoolbuddy/daemon/display_control.py b/spoolbuddy/daemon/display_control.py index 9e13811d8..e991d247b 100644 --- a/spoolbuddy/daemon/display_control.py +++ b/spoolbuddy/daemon/display_control.py @@ -2,10 +2,11 @@ Brightness: DSI backlights are controlled via sysfs /sys/class/backlight/*/brightness. HDMI brightness is handled by the frontend via CSS filter. -Blanking: The daemon tracks idle state and controls HDMI power via wlopm when - available. NFC tag scans and scale weight changes wake the display - automatically, and the idle timeout re-blanks it. swayidle handles - touch-based wake/blank independently — both are idempotent via wlopm. +Blanking: swayidle is the sole authority on screen blanking (idle timeout → + wlopm --off, touch → wlopm --on). The daemon only *wakes* the + display via wlopm --on when NFC/scale activity is detected — it + never blanks. wlopm --on is idempotent so both paths coexist + safely. """ import logging @@ -27,7 +28,6 @@ class DisplayControl: self._blank_timeout = 0 # seconds, 0 = disabled self._last_activity = time.monotonic() self._blanked = False - self._daemon_woke = False # True when the daemon woke the display (NFC/scale) self._wlopm_path = shutil.which("wlopm") self._wayland_env: dict[str, str] | None = None self._output = os.environ.get("SPOOLBUDDY_DISPLAY_OUTPUT", "HDMI-A-1") @@ -85,20 +85,26 @@ class DisplayControl: self._blank_timeout = max(0, seconds) def wake(self): - """Wake screen on activity (NFC tag, scale weight change).""" + """Wake screen on activity (NFC tag, scale weight change). + + Always calls wlopm --on regardless of the daemon's internal blanked + state, because swayidle may have blanked the screen independently and + the daemon has no way to know. wlopm --on is idempotent so calling it + while the screen is already on is harmless. + """ self._last_activity = time.monotonic() - if self._blanked: - self._unblank() + self._blanked = False + self._wlopm(on=True) def tick(self): - """Called periodically from heartbeat loop. Blanks screen if idle.""" + """Called periodically from heartbeat loop. Tracks idle state internally.""" if self._blank_timeout <= 0: - if self._blanked: - self._unblank() + self._blanked = False return idle = time.monotonic() - self._last_activity if not self._blanked and idle >= self._blank_timeout: - self._blank() + self._blanked = True + logger.debug("Screen idle timeout reached (swayidle manages blanking)") def _discover_wayland_env(self) -> dict[str, str] | None: """Discover WAYLAND_DISPLAY and XDG_RUNTIME_DIR for the kiosk session. @@ -139,23 +145,3 @@ class DisplayControl: ) except Exception as e: logger.debug("wlopm %s %s failed: %s", flag, self._output, e) - - def _blank(self): - self._blanked = True - # Only power off HDMI if the daemon was responsible for the last wake. - # Touch-based wake/blank is managed entirely by swayidle — if we called - # wlopm --off here unconditionally, we'd fight swayidle because the - # daemon never sees touch events and its idle timer would expire while - # the user is still interacting via the touchscreen. - if self._daemon_woke: - self._daemon_woke = False - self._wlopm(on=False) - logger.debug("Daemon wake idle timeout reached, HDMI off") - else: - logger.debug("Screen idle timeout reached (swayidle manages HDMI)") - - def _unblank(self): - self._blanked = False - self._daemon_woke = True - self._wlopm(on=True) - logger.debug("Activity detected (NFC/scale), HDMI on")