# Sway session for the Steam TV box. # Installed to /home//.config/sway/config by build-steam-tv-box-iso.sh. # # Every `exec` below inherits the environment set by /usr/local/bin/kiosk-session, # which sources /etc/steamtv-agent/config.env — that is how GPU_VENDOR and the MQTT # settings get here without this file being templated. # # THE SHAPE OF THIS SESSION, IN ONE PARAGRAPH: the box powers on, greetd autologins, # sway starts, and the only thing it launches is Steam in Big Picture. The browser, # Spotify and the player do not exist yet. The first time somebody leaves Big Picture — # by quitting Steam, by exiting Big Picture mode, by pressing a workspace key, or via # Home Assistant — the media session is brought up behind them. See steam-session and # session-watcher, which are the two halves of that. set $mod Mod4 set $ws_steam 1:steam set $ws_games 2:games set $ws_web 3:web set $ws_media 4:media set $ws_music 5:music # Workspace names are a contract with steamtv_agent/sway_control.py, # steamtv_agent/session_mode.py, /usr/local/bin/media-session and # /usr/local/bin/session-watcher — changing one side means changing all of them. # --------------------------------------------------------------------------- # Output / input # --------------------------------------------------------------------------- output * bg #0b0b10 solid_color input type:keyboard { xkb_layout @KEYBOARD_LAYOUT@ } # A gamepad is not an input device to the compositor — it goes straight to Steam and to # games via evdev, so there is nothing to configure here for it. What does need # configuring is a mouse/trackball on the coffee table, if there is one. input type:pointer { accel_profile adaptive } # --------------------------------------------------------------------------- # Look — no bars, no borders, no gaps. This is a television. # --------------------------------------------------------------------------- default_border none default_floating_border none hide_edge_borders both gaps inner 0 gaps outer 0 # Games and Big Picture must never be tiled next to something else; every workspace # here holds exactly one thing that fills the screen. for_window [class="^[Ss]team$"] fullscreen enable for_window [class="^gamescope$"] fullscreen enable for_window [app_id="^gamescope$"] fullscreen enable for_window [app_id="mpv"] fullscreen enable # --------------------------------------------------------------------------- # Remote control (administration, not play) # --------------------------------------------------------------------------- # 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. # # Worth being explicit: this is for fixing the box, not for playing over. wayvnc streams # the compositor, so a game rendering at 120fps arrives as a slideshow — that is not a # defect to work around, it is what a screen-scraping protocol does. exec_always /usr/local/bin/start-wayvnc # The workspace-focus half of the leave-Big-Picture detection. Its own exit-on- # sway-death behaviour makes exec_always safe across a reload: the old watcher's pipe # closes with the old sway, and reload starts one watcher, not a second. exec_always /usr/local/bin/session-watcher # steamtv-agent is NOT started here. systemd owns it (steamtv-agent.service, enabled by # 0700-steamtv-agent.hook.chroot) so that it is up and connected to Mosquitto whether or # not a graphical session ever came up, and so it survives a sway restart. Starting it # from sway too would give two competing MQTT clients. # --------------------------------------------------------------------------- # The default application # --------------------------------------------------------------------------- # This is the whole boot behaviour: land on 1:steam and start Big Picture. steam-session # blocks on the Steam client and starts the media session when it exits. # # No `assign [class="steam"]` rule: Steam maps several windows (splash, client, an # optional gamescope surface) and an assign rule would scatter them across workspaces # mid-launch. Switching workspace first and letting them map where focus already is # gets the same result without the race. exec swaymsg workspace $ws_steam exec /usr/local/bin/steam-session # --------------------------------------------------------------------------- # Idle # --------------------------------------------------------------------------- # Never lock: this is a shared living-room machine, and a lock screen would make the TV # unusable to anyone not holding a keyboard. # # 20 minutes, and only the display — noticeably longer than the thin client's 15, # because "nobody has touched an input device" is a much weaker signal here. Watching a # two-hour film, or a cutscene, or a turn in a slow strategy game are all legitimately # input-free, and the inhibit rules below are what keep the screen alive through them. exec swayidle -w \ timeout 1200 'swaymsg "output * power off"' \ resume 'swaymsg "output * power on"' # Anything playing keeps the screen on. Steam gets `focus` rather than `fullscreen` # because Big Picture is fullscreen essentially always, and a game that alt-tabs itself # briefly should not start the idle countdown. for_window [class="^[Ss]team$"] inhibit_idle focus for_window [class="^gamescope$"] inhibit_idle focus for_window [app_id="^gamescope$"] inhibit_idle focus for_window [app_id="mpv"] inhibit_idle visible for_window [app_id="firefox-esr"] inhibit_idle fullscreen for_window [app_id="com.spotify.Client"] inhibit_idle focus # --------------------------------------------------------------------------- # Local keys. A gamepad drives Steam and games; these are for the keyboard on the back # of the remote, and for standing in front of the machine. # --------------------------------------------------------------------------- # Maintenance shell. A deliberately obscure chord (not $mod+Return, which is the # ordinary local-terminal key below) so it is not something a visitor bumps into, # floating so it overlays whatever is running instead of tiling against it. bindsym $mod+Shift+Ctrl+m exec foot --title maintenance-shell for_window [title="maintenance-shell"] floating enable, resize set width 900 height 550, move position center bindsym $mod+Return exec foot bindsym $mod+q kill bindsym $mod+f fullscreen toggle bindsym $mod+Shift+c reload # Getting back into Steam after "Exit Steam" — the counterpart to steam-session's # deliberate refusal to relaunch it in a loop. Idempotent, so holding the key does # nothing worse than focusing the client. bindsym $mod+s exec /usr/local/bin/steam-big-picture # And bringing the media half up by hand, for the case where somebody wants Spotify # without leaving the game. bindsym $mod+m exec /usr/local/bin/media-session start # Prism/Minecraft. This goes through Steam (steam://rungameid/…) so Steam Input is # active for it — see prism-launch. Which is also why its window lands on 1:steam and # not 2:games when Steam is running: it *is* a Steam game as far as the client is # concerned, and Big Picture shows it in the library. bindsym $mod+p exec /usr/local/bin/prism-launch bindsym $mod+1 workspace $ws_steam bindsym $mod+2 workspace $ws_games bindsym $mod+3 workspace $ws_web bindsym $mod+4 workspace $ws_media bindsym $mod+5 workspace $ws_music bindsym $mod+Left focus left bindsym $mod+Right focus right bindsym $mod+Up focus up bindsym $mod+Down focus down # --------------------------------------------------------------------------- # Remote control — the standardised media-key set. # # Same device class as the thin client's: a wireless USB remote that presents as two # HID keyboards (TV controls on the front, a small keyboard on the back). Nothing to # configure per-device; the front buttons arrive as the XF86* keysyms below. # # DELIBERATELY NOT BOUND: plain arrows and Return. Steam's Big Picture, mpv and every # web page need them, and a remote's D-pad and OK button send exactly those. Stealing # them at the compositor would make Big Picture unnavigable with the remote — which is # most of what the remote is for here. Window focus stays on $mod+arrows above. # # `wev` from the maintenance shell prints the keysym for any button that is not bound. # --------------------------------------------------------------------------- # Transport. `playerctl -p mpv,spotify` matches the two players this image runs; the # order is the priority when both are alive. Steam's own media has no MPRIS bus and is # not a target — a game's audio is not something you "pause". bindsym XF86AudioPlay exec playerctl -p mpv,spotify play-pause bindsym XF86AudioPause exec playerctl -p mpv,spotify pause bindsym XF86AudioStop exec playerctl -p mpv,spotify stop bindsym XF86AudioNext exec playerctl -p mpv,spotify next bindsym XF86AudioPrev exec playerctl -p mpv,spotify previous bindsym XF86AudioForward exec playerctl -p mpv,spotify position 30+ bindsym XF86AudioRewind exec playerctl -p mpv,spotify position 10- # Volume. Sinks, not players — the volume rocker should move the room's volume whether # the noise is coming from Spotify or from a game, and a game is the common case here. 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 # Navigation. bindsym XF86ChannelUp workspace next bindsym XF86ChannelDown workspace prev # Home goes back to the games, because on this box that is home. bindsym XF86HomePage exec /usr/local/bin/steam-big-picture bindsym XF86Back workspace back_and_forth bindsym XF86Forward workspace back_and_forth # Power. **The display, not the machine** — same reasoning as the thin client, and more # so here: `poweroff` from the sofa would drop whatever game is running, unsaved. 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 # autologin straight back in. Use SSH or the maintenance shell to administer. workspace $ws_steam