Files
dotfiles/hosts/mainframe/sway/headless.conf
T
2026-10-22 09:21:28 -04:00

148 lines
7.1 KiB
Plaintext

# A compositor that exists only so Sunshine has something to capture.
# mainframe has no desktop: the only connected output (HDMI-A-2) hangs off the
# Intel iGPU, while the RTX 3060 that does the encoding drives no monitor at
# all. Capturing the real output would mean grabbing on Intel and encoding on
# NVIDIA — a cross-GPU copy every frame. A virtual output on the 3060 keeps
# capture and NVENC on one card.
output HEADLESS-1 mode 1920x1080@60Hz position 0,0
output HEADLESS-1 background #101920 solid_color
# Start Xwayland eagerly rather than on first X client. Steam is an X app, and
# with the default lazy start DISPLAY does not exist yet when the line below
# imports the environment — so Steam launched by Sunshine got
# "Unable to open a connection to X" and showed a blank stream.
xwayland force
# Sunshine is a systemd user unit and inherits nothing from this session, so
# hand it the display variables before starting it.
#
# ONE exec with &&, not two. sway runs exec lines asynchronously and does not
# order them, so as two separate lines Sunshine won every time: it started in
# the same second as the compositor, before DISPLAY was imported, and passed no
# DISPLAY to the apps it launched. Steam then failed with "Unable to open a
# connection to X" even though Xwayland was up and the manager environment was
# correct.
exec systemctl --user import-environment WAYLAND_DISPLAY DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP && systemctl --user start sunshine.service
# Input arrives from the Moonlight client, so keybinds are worth having even
# though nobody is sitting at this machine. Without them the Desktop app
# streams an empty screen with no way to open anything.
#
# Mod1 (Alt), not Mod4 (Super). The client machines run Hyprland with SUPER as
# their own modifier, and a compositor grabs its own modifier before Moonlight
# ever sees the key, so every Super bind here was unreachable from the stream —
# it acted on the client instead. Alt is not grabbed there, so it arrives. The
# cost is that an Alt bind here shadows the focused app's menu mnemonics
# (Alt+F, Alt+D), which is acceptable on a session that shows one fullscreen
# app at a time.
set $mod Mod1
# Tiling, sway's default. This used to float every window, which left a
# single-app stream at whatever size the app opened at instead of filling the
# output. Each Sunshine app switches to its own workspace first (apps.json), so
# its window is alone there and fills the screen. Dialogs sway recognizes as
# dialogs still float.
# Title bars are the handle you grab with the mouse, so floating windows keep
# a real border rather than the thin default.
default_floating_border normal 2
# Alt+drag moves a window, Alt+right-drag resizes it. Without this the
# only way to move a window is the title bar, which a fullscreen app has none of.
floating_modifier $mod normal
# Hyprland takes $browser from hosts/<host>/hypr/local-env.conf; mainframe links
# no hypr config, so it is named here. qutebrowser, to match the laptops.
set $browser qutebrowser
# These mirror common/hypr/hyprland.conf, so the muscle memory from the laptops
# carries over the stream. Only binds whose target exists on this host are
# ported: mainframe has no grim/grimblast, no udiskie, no
# networkmanager_dmenu, no ydotool, no julia, no ncmpcpp and no swayimg, so
# Hyprland's $mainMod+P, Shift+P, M, W, A, S and V are deliberately absent
# rather than bound to a missing binary — sway reports nothing when an exec
# target does not exist, so a ported bind would look correct and silently do
# nothing. The brightness keys are out for the same reason twice over:
# brightnessctl is absent and /sys/class/backlight is empty.
#
# $mainMod+N and Shift+N are out too, less obviously. The daemon here is mako,
# not swaync, and mako's do-not-disturb is not built in — it needs a
# [mode=do-not-disturb] invisible=1 section, and this host has no mako config at
# all. `makoctl mode -t do-not-disturb` would therefore succeed and suppress
# nothing. Write that config first if these binds are wanted.
#
# $mainMod+Ctrl+L is not ported: there is no lock screen here on purpose, and
# locking the session Sunshine captures would blank the stream.
bindsym $mod+Return exec kitty
bindsym $mod+Shift+Return exec wofi --show drun
bindsym $mod+b exec $browser
bindsym $mod+q kill
bindsym $mod+Shift+q exit
bindsym $mod+f fullscreen
bindsym $mod+t floating toggle
# Hyprland moves focus with both vim keys and arrows; so does this.
bindsym $mod+h focus left
bindsym $mod+j focus down
bindsym $mod+k focus up
bindsym $mod+l focus right
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
# Not a Hyprland bind, kept because a Moonlight client arrives with a pointer
# and little else: this is the only way to reach a window the pointer cannot
# see. Spelled literally rather than as $mod so it stays Alt+Tab if $mod moves.
bindsym Alt+Tab focus next
bindsym Alt+Shift+Tab focus prev
# Workspaces, and $mod+scroll to cycle them, as on the laptops. Sway has no
# master layout, so Hyprland's $mainMod+Space, Y, U, O and I have no
# counterpart; nor do its
# movecurrentworkspacetomonitor binds, with one virtual output.
bindsym $mod+1 workspace number 1
bindsym $mod+2 workspace number 2
bindsym $mod+3 workspace number 3
bindsym $mod+4 workspace number 4
bindsym $mod+5 workspace number 5
bindsym $mod+6 workspace number 6
bindsym $mod+7 workspace number 7
bindsym $mod+8 workspace number 8
bindsym $mod+9 workspace number 9
bindsym $mod+0 workspace number 10
bindsym $mod+Shift+1 move container to workspace number 1
bindsym $mod+Shift+2 move container to workspace number 2
bindsym $mod+Shift+3 move container to workspace number 3
bindsym $mod+Shift+4 move container to workspace number 4
bindsym $mod+Shift+5 move container to workspace number 5
bindsym $mod+Shift+6 move container to workspace number 6
bindsym $mod+Shift+7 move container to workspace number 7
bindsym $mod+Shift+8 move container to workspace number 8
bindsym $mod+Shift+9 move container to workspace number 9
bindsym $mod+Shift+0 move container to workspace number 10
bindsym --whole-window $mod+button4 workspace prev_on_output
bindsym --whole-window $mod+button5 workspace next_on_output
# Volume reaches the client: the default sink is sink-sunshine-stereo, which is
# the one Sunshine captures. --locked because there is no lock screen to respect.
# XF86AudioMicMute is not ported — this host has no audio source at all.
bindsym --locked XF86AudioRaiseVolume exec wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
bindsym --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindsym --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindsym --locked XF86AudioNext exec playerctl next
bindsym --locked XF86AudioPause exec playerctl play-pause
bindsym --locked XF86AudioPlay exec playerctl play-pause
bindsym --locked XF86AudioPrev exec playerctl previous
# Something visible on login, so a working capture is obviously working rather
# than indistinguishable from a broken one.
exec kitty
# No bar, no idle daemon, no lock screen — there is no one sitting here.