# Sway session for the Steam TV box.
# Installed to /home/<KIOSK_USERNAME>/.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
