# Sway kiosk session for the thin client. # Installed to /home//.config/sway/config by build-thin-client-iso.sh. # # Every `exec` below inherits the environment set by /usr/local/bin/kiosk-session, # which sources /etc/thinclient-agent/config.env — that is how $DIGEST_WEB_URL gets # here without this file being templated. set $mod Mod4 set $ws_web 1:web set $ws_digest 2:digest set $ws_media 3:media set $ws_admin 4:admin set $ws_capture 5:capture # Workspace names are a contract with thinclient_agent/digest_canvas.py, # thinclient_agent/admin_canvas.py, thinclient_agent/capture_control.py, and # thinclient_agent/sway_control.py — changing one side means changing the other. # --------------------------------------------------------------------------- # Output / input # --------------------------------------------------------------------------- output * bg #101014 solid_color input type:keyboard { xkb_layout @KEYBOARD_LAYOUT@ } input type:touchpad { tap enabled natural_scroll enabled } # --------------------------------------------------------------------------- # Look — no bars, no borders. Primary control is HA/MQTT and wayvnc; the local # display is a media surface, not a desktop. # --------------------------------------------------------------------------- default_border none default_floating_border none hide_edge_borders both gaps inner 0 gaps outer 0 # --------------------------------------------------------------------------- # Remote control # --------------------------------------------------------------------------- # exec_always so a `swaymsg reload` re-establishes it. start-wayvnc refuses to run # until /etc/wayvnc/wayvnc-password has been set on this machine. exec_always /usr/local/bin/start-wayvnc # Now-playing widget. Session-scoped (dies with sway, unlike thinclient-agent) — see # fullscreen-watcher.sh's own comment for why it isn't part of the system service. # Its own PID-file guard makes exec_always safe across `swaymsg reload`. exec_always /usr/local/bin/fullscreen-watcher # Camera gesture control. Session-scoped for the same blast-radius reason as the widget # above, plus a privacy one: the camera must not be open while there is no session whose # pointer it could move. It no-ops both when the image was built without it and when # gesture-config.json still has the default "enabled": false — see the wrapper. exec_always /usr/local/bin/gesture-control # thinclient-agent is NOT started here. systemd owns it (thinclient-agent.service, # enabled by 0700-thinclient-agent.hook.chroot) so that it is up and connected to # Mosquitto whether or not a graphical session ever comes up, and so it survives a # sway restart. Starting it from sway too would give two competing MQTT clients. # --------------------------------------------------------------------------- # Applications # --------------------------------------------------------------------------- # No `assign [app_id=...]` rules here on purpose: both digest-browser and web-browser # (added for the minimal-chrome general-browsing app) launch the same firefox-esr # app_id on different profiles, and `assign` cannot tell those two windows apart — # it would fight thinclient_agent.sway_control.launch_app()'s switch-workspace-then- # launch approach for whichever one it matched. Every launch path, including the one # below, is therefore explicit about its own workspace instead. # Steam Link runs under Xwayland (native Wayland black-screens on wlroots), so it # appears as an X11 class, not a Wayland app_id. assign [class="steamlink"] $ws_media for_window [class="steamlink"] fullscreen enable for_window [app_id="mpv"] fullscreen enable # Guarded so a powered-off container host (or an unset DIGEST_WEB_URL) leaves an # empty workspace instead of hanging the session — Phase 11.10. The explicit # `swaymsg workspace` mirrors launch_app()'s own switch-then-launch order, since this # runs before thinclient-agent's MQTT-driven launches ever fire. exec sh -c '[ -n "$DIGEST_WEB_URL" ] && { swaymsg workspace $ws_digest; /usr/local/bin/digest-browser "$DIGEST_WEB_URL/full.html?detail_level=full"; }' # --------------------------------------------------------------------------- # Idle # --------------------------------------------------------------------------- # Never lock: this is an always-on shared media station, and a lock screen would make # the room's display unusable to anyone who isn't holding a keyboard. Any wayvnc or # local input resumes it. # # After 15 minutes, idle-gallery replaces the old unconditional "blank the panel" # behaviour with a photo slideshow from the gallery SMB share (Phase 11 follow-up) — # but it degrades all the way back to that exact blank-the-panel behaviour itself if # no share is configured, unreachable, or empty, so a freshly-built or offline thin # client behaves exactly as before. See configs/idle-gallery/idle-gallery.sh. exec swayidle -w \ timeout 900 '/usr/local/bin/idle-gallery' \ resume 'swaymsg "output * power on"; /usr/local/bin/idle-gallery stop' # Media playback keeps the screen alive. for_window [class="steamlink"] inhibit_idle focus for_window [app_id="mpv"] inhibit_idle focus for_window [app_id="firefox-esr"] inhibit_idle fullscreen # --------------------------------------------------------------------------- # Local override keys — a fallback for standing in front of the machine, not the # primary control surface. # --------------------------------------------------------------------------- # Maintenance shell. A deliberately obscure chord (not $mod+Return, which is the # ordinary local-terminal key above it) so it is not something a visitor bumps into, # floating so it overlays the kiosk content instead of tiling against it. It only opens # a shell — it does not pause thinclient-agent or anything else, since standing at the # machine already means treating it as a maintenance session; if that turns out to be # too little, tightening it is a config change here, not a new subsystem. bindsym $mod+Shift+Ctrl+m exec foot --title maintenance-shell for_window [title="maintenance-shell"] floating enable, resize set width 800 height 500, move position center bindsym $mod+Return exec foot bindsym $mod+q kill bindsym $mod+f fullscreen toggle bindsym $mod+Shift+c reload bindsym $mod+1 workspace $ws_web bindsym $mod+2 workspace $ws_digest bindsym $mod+3 workspace $ws_media bindsym $mod+4 workspace $ws_admin bindsym $mod+5 workspace $ws_capture bindsym $mod+Left focus left bindsym $mod+Right focus right bindsym $mod+Up focus up bindsym $mod+Down focus down # --------------------------------------------------------------------------- # Remote control — the full standardised media-key set. # # THE DEVICE THIS IS FOR: a wireless USB remote with TV controls on the front and a # small keyboard on the back. To Linux that is just two HID keyboards on one dongle # (a normal keyboard plus a "consumer control" device), so there is nothing to # configure per-device — the front buttons arrive as the XF86* keysyms below and the # back keyboard arrives as ordinary keys. Any remote in that class works; the bindings # are what make it useful. # # WHAT IS DELIBERATELY *NOT* BOUND: plain Up/Down/Left/Right and Return. A remote's # D-pad and OK button send exactly those, unmodified — and Chromium, mpv and every # kiosk page need them. Stealing them at the compositor would break scrolling a web # page with the remote, which is most of what the remote is for. Window focus stays on # $mod+arrows above; the bare arrows belong to whatever is on screen. # # CHECKING WHAT YOUR REMOTE ACTUALLY SENDS: remotes vary more than their marketing # does. `wev` (or `sudo libinput debug-events`) prints the keysym for each button — # run it once from the maintenance shell and add any button that comes back unbound. # A button that reports no keysym at all is one the kernel has no mapping for, which # is a udev hwdb entry, not a Sway binding. # --------------------------------------------------------------------------- # Transport. `playerctl -p mpv,spotifyd` matches the two players this image runs; # the order is the priority when both are alive. bindsym XF86AudioPlay exec playerctl -p mpv,spotifyd play-pause bindsym XF86AudioPause exec playerctl -p mpv,spotifyd pause bindsym XF86AudioStop exec playerctl -p mpv,spotifyd stop bindsym XF86AudioNext exec playerctl -p mpv,spotifyd next bindsym XF86AudioPrev exec playerctl -p mpv,spotifyd previous # Skip rather than scan: a remote's ⏪/⏩ are pressed repeatedly, not held, and # playerctl has no scan mode. 30s forward / 10s back is the podcast convention and is # the one people already have in their fingers. bindsym XF86AudioForward exec playerctl -p mpv,spotifyd position 30+ bindsym XF86AudioRewind exec playerctl -p mpv,spotifyd position 10- # Volume. Sinks, not players — the remote's volume rocker should move the room's # volume regardless of what is making the noise. bindsym XF86AudioRaiseVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ bindsym XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- bindsym XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle bindsym XF86AudioMicMute exec wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle # Navigation. A media station's "channels" are its workspaces, which is the closest # honest analogy and means the channel rocker does something useful instead of nothing. bindsym XF86ChannelUp workspace next bindsym XF86ChannelDown workspace prev bindsym XF86HomePage workspace $ws_media bindsym XF86Back workspace back_and_forth bindsym XF86Forward workspace back_and_forth # Power. **The display, not the machine.** A remote's power button on a TV turns the # picture off, and that is what people expect it to do here — while `poweroff` on a # thin client that autologins is a button that takes the room's screen away until # somebody walks over to it. See the display-toggle script for the wake side. bindsym XF86PowerOff exec display-toggle bindsym XF86Sleep exec display-toggle bindsym XF86ScreenSaver exec display-toggle # Deliberately no exit binding: `swaymsg exit` would drop to a black VT, and greetd # would just autologin straight back in. Use SSH or the local terminal to administer. workspace $ws_digest