SmartestHome/hosts/thin-client/configs/eww/eww.yuck

103 lines
5.3 KiB
Plaintext

;; Two on-screen widgets for the thin client: now-playing and the idle-gallery
;; weather/clock overlay. Installed to
;; /home/<KIOSK_USERNAME>/.config/eww/eww.yuck by build-thin-client-iso.sh.
;;
;; Data sources are separate `deflisten`/`defpoll` processes, NOT thinclient_agent —
;; now-playing uses /usr/local/bin/now-playing-json (`playerctl --follow`), the
;; weather overlay uses /usr/local/bin/weather-json (MQTT). The agent is a *system*
;; service that deliberately outlives the compositor, whereas both widgets are
;; session-scoped and die with sway; wiring either to the agent would mean either
;; restarting the agent whenever a widget restarts, or opening a second inbound
;; control channel into it, which the security boundary in mqtt_discovery.py rules
;; out. The cost is one extra subscription each, which is negligible.
;;
;; The :onclick strings below are fixed constants in this file. Nothing from MQTT, HA,
;; or player metadata is ever interpolated into them.
(defvar players "mpv,spotifyd,%any")
(deflisten nowplaying
:initial '{"visible":false,"title":"","artist":"","art":"","controls":false}'
"/usr/local/bin/now-playing-json")
(defwidget np-meta []
(box :orientation "vertical" :space-evenly false :halign "start" :valign "center" :hexpand true
(label :class "np-title" :text nowplaying.title :halign "start" :limit-width 34 :truncate true)
(label :class "np-artist" :text nowplaying.artist :halign "start" :limit-width 34 :truncate true)))
;; Hidden rather than absent when the player exposes no transport control (some
;; sources only report metadata) — the widget then degrades to art + text.
(defwidget np-controls []
(box :class "np-controls" :orientation "horizontal" :space-evenly true :spacing 4
:visible {nowplaying.controls} :valign "center"
(button :class "np-btn" :onclick "playerctl -p ${players} previous" "⏮")
(button :class "np-btn" :onclick "playerctl -p ${players} play-pause" "⏯")
(button :class "np-btn" :onclick "playerctl -p ${players} next" "⏭")))
(defwidget np-root []
(box :class "np-root" :orientation "horizontal" :space-evenly false :spacing 12
:visible {nowplaying.visible}
(image :class "np-art" :path nowplaying.art :image-width 72 :image-height 72)
(np-meta)
(np-controls)))
;; :exclusive false — the widget floats over whatever is on screen and must never
;; reserve layer-shell space, or it would shrink the fullscreen media surfaces this
;; whole image exists to show. Visibility is owned by fullscreen-watcher.sh, which
;; opens and closes this window; the :visible binding above only handles "nothing is
;; playing at all".
(defwindow now-playing
:monitor 0
:geometry (geometry :x "24px" :y "24px" :anchor "bottom left"
:width "420px" :height "96px")
:stacking "overlay"
:exclusive false
:focusable false
(np-root))
;; --- Idle-gallery weather/clock overlay --------------------------------------
;;
;; Time/date/weekday are computed locally (no Home Assistant round-trip needed for
;; those — the box already has a clock) via `date`(1); only temperature/condition/
;; location come from weather-json's MQTT subscription, see that script's own
;; docstring for the smarthome/weather/current data contract. A missing/never-
;; published weather reading degrades to just hiding the weather line — the clock
;; and date keep showing regardless, same "never blank the whole thing over one
;; missing part" rule this repo applies everywhere (digest render.js, admin-canvas
;; render.js, idle-gallery.sh's own fallback chain).
(defpoll clock_time :interval "20s" :initial "--:--" "date +'%H:%M'")
(defpoll clock_weekday :interval "60s" :initial "" "date +'%A'")
(defpoll clock_date :interval "60s" :initial "" "date +'%-d %B'")
(deflisten weatherinfo
:initial '{"available":false,"temperature":"","condition":"","location":""}'
"/usr/local/bin/weather-json")
(defwidget weather-overlay-root []
(box :class "wo-root" :orientation "vertical" :space-evenly false :halign "end"
(label :class "wo-time" :text clock_time)
(label :class "wo-date" :text "${clock_weekday}, ${clock_date}")
(box :class "wo-weather" :orientation "horizontal" :space-evenly false :halign "end"
:visible {weatherinfo.available}
(label :class "wo-location" :text weatherinfo.location)
(label :class "wo-sep" :text " · " :visible {weatherinfo.location != ""})
(label :class "wo-temp" :text weatherinfo.temperature)
(label :class "wo-sep" :text " · ")
(label :class "wo-condition" :text weatherinfo.condition))))
;; Same :exclusive false/:stacking "overlay" shape as now-playing, and the same
;; ownership split: visibility is entirely owned by idle-gallery.sh, which is the
;; one thing that actually knows when its own slideshow is the thing on screen —
;; opened right before the slideshow's mpv is launched, closed in stop_slideshow().
;; No :visible gate at the window level the way now-playing has one, because there
;; is no "open but should still hide" sub-case here: once idle-gallery owns the
;; screen, showing at least the clock is always correct.
(defwindow weather-overlay
:monitor 0
:geometry (geometry :x "32px" :y "32px" :anchor "top right"
:width "440px" :height "140px")
:stacking "overlay"
:exclusive false
:focusable false
(weather-overlay-root))