#!/bin/bash # Runs on the Frame host (not in the container). Starts a Plasma desktop with several # screens, each its own SteamVR panel. Modeled on /usr/bin/steamos-nested-desktop, which # does the same with one screen in gamescope. # # Backends (BACKEND in ~/.config/frametop.conf): # screens (default) ft-screens (screens), our own compositor: KWin opens a # window per screen, ft-screens gives each its size (from the layout, see # layout) and shows it as its own panel. Any resolution and shape. # gamescope gamescope in PerWindow mode: every screen one size, at most 1920x1080 # worth of pixels, panels owned by the SteamVR dashboard. # # Settings come from ~/.config/frametop.conf (see frametop.conf.example) and, for the # screens backend, ~/.config/frametop-layout.json (Frametop Display Settings writes both). # FT_BACKEND, FT_SCREENS, FT_WIDTH, FT_HEIGHT, and FT_PHYS_WIDTH override them. set -eu here=$(dirname "$(readlink -f "$0")") # Started from the VR launcher, the desktop inherits the Steam client's environment. Apps in # it should see the system as a normal login does, so drop the client's runtime: its # LD_LIBRARY_PATH put Steam's own libraries ahead of the system's (Steam's libavcodec has no # H.264 decoder, so VLC couldn't play most videos), and its overlay and launch settings are # meant for games. SteamOS's own defaults (/usr/share/deckard/mesavars.sh) stay. for var in $(compgen -e); do case $var in LD_LIBRARY_PATH | LD_PRELOAD | STEAM_* | Steam* | SRT_* | PRESSURE_VESSEL_* | MANGOHUD_* | \ ENABLE_VK_LAYER_VALVE_steam_overlay_* | STEAMVIDEOTOKEN) unset "$var" ;; esac done conf=$HOME/.config/frametop.conf BACKEND=screens SCREENS=2 WIDTH=1920 HEIGHT=1080 PHYS_WIDTH=1.6 REMOTE=0 # shellcheck disable=SC1090 [ -f "$conf" ] && . "$conf" backend=${FT_BACKEND:-$BACKEND} screens=${FT_SCREENS:-$SCREENS} width=${FT_WIDTH:-$WIDTH} height=${FT_HEIGHT:-$HEIGHT} phys_width=${FT_PHYS_WIDTH:-$PHYS_WIDTH} remote=${FT_REMOTE:-$REMOTE} if [ "$backend" = gamescope ] && [ $((width * height)) -gt $((1920 * 1080)) ]; then # gamescope's VR backend aborts above 1920x1080 worth of pixels (its upload buffer; # see docs/design.md). Shrink a bigger size to fit, keeping its shape. read -r width height < <(awk -v w="$width" -v h="$height" 'BEGIN { k = sqrt(1920 * 1080 / (w * h)); printf "%d %d\n", int(w * k / 8) * 8, int(h * k / 8) * 8 }') echo "frametop: resolution too big for gamescope's VR mode; using ${width}x${height}" >&2 fi if [ "${1:-}" != --inner ]; then # One instance at a time. The launcher can be clicked twice. if pgrep -f '[v]r-overlay-key frametop ' >/dev/null || pgrep -x ft-screens >/dev/null; then echo "Frametop is already running" >&2 exit 0 fi set -a; . /usr/share/deckard/mesavars.sh; set +a # Flatpak apps (Chromium) publish their launcher entries under the Flatpak # exports dirs. SSH and launcher environments may lack XDG_DATA_DIRS, and then # Plasma can't find them and opens Discover instead. export XDG_DATA_DIRS=${XDG_DATA_DIRS:-/usr/local/share:/usr/share} set +u; . /etc/profile.d/flatpak.sh; set -u # Arrange the screens in the saved layout once they're up (layout; skipped # when auto-arrange is off). setsid "$here/../layout/ft-layout" apply --wait 90 > /tmp/frametop-layout.log 2>&1 < /dev/null & if [ "$backend" = gamescope ]; then export ENABLE_GAMESCOPE_WSI=1 GAMESCOPE_MANGOAPP_SOCKET_DISABLE=1 exec gamescope --backend openvr --virtual-connector-strategy PerWindow \ -W "$width" -H "$height" -w "$width" -h "$height" \ --vr-overlay-key frametop --vr-overlay-default-name frametop \ --vr-overlay-physical-width "$phys_width" \ --vr-overlay-show-immediately --vr-overlay-enable-click-stabilization \ --vr-overlay-enable-control-bar --vr-overlay-enable-control-bar-keyboard \ --expose-wayland \ --cursor-hotspot 5,3 --cursor /usr/share/steamos/steamos-cursor.png \ -- "$0" --inner fi # ft-screens runs in the dev container (it's built against Fedora's wlroots); KWin and # Plasma stay on the host and connect to its socket. socket=ft-screens-0 read -ra screen_args <<< "$("$here/../layout/ft-layout" screen-args)" export FT_SCREEN_COUNT=$(( ${#screen_args[@]} / 2 )) "$here/../scripts/container-up.sh" # not owned by this desktop, or stopping it would stop the container "$HOME/.local/bin/distrobox" enter dev -- "$here/../screens/build/ft-screens" --socket "$socket" \ "${screen_args[@]}" > /tmp/frametop-screens.log 2>&1 < /dev/null & stop_screens() { pkill -x ft-screens 2>/dev/null || true; } trap stop_screens EXIT for _ in $(seq 100); do [ -S "$XDG_RUNTIME_DIR/$socket" ] && break; sleep 0.2; done [ -S "$XDG_RUNTIME_DIR/$socket" ] || { echo "ft-screens didn't start (see /tmp/frametop-screens.log)" >&2; exit 1; } WAYLAND_DISPLAY=$XDG_RUNTIME_DIR/$socket "$0" --inner exit fi # Inside the host compositor (ft-screens or gamescope) from here on. The desktop isn't a # gamescope client with ft-screens, so the gamescope session's Vulkan layer stays off, and # the gamescope session's portal config isn't Plasma's. [ "$backend" = gamescope ] || unset ENABLE_GAMESCOPE_WSI unset XDG_DESKTOP_PORTAL_DIR [ "$backend" = gamescope ] || screens=${FT_SCREEN_COUNT:-$screens} host_runtime=$XDG_RUNTIME_DIR runtime=$host_runtime/frametop cleanup() { pkill -f '[k]rdpserver --plasma' 2>/dev/null || true pkill -f '[X]vnc :20 ' 2>/dev/null || true pkill -f '[x]freerdp /v:.*:3390' 2>/dev/null || true fusermount3 -u -z "$runtime/doc" 2>/dev/null || true umount --recursive "$runtime" 2>/dev/null || true rm -rf "$runtime" } trap cleanup EXIT cleanup mkdir -m 0700 "$runtime" "$runtime/pulse" "$runtime/bin" ln -s "$host_runtime/pulse/native" "$runtime/pulse/native" ln -s "$host_runtime"/pipewire* "$runtime/" # The file picker gives Flatpak apps paths in our document portal, $runtime/doc/ID/NAME. # Sandboxes only see the portal at /run/flatpak/doc, and their /run/user/UID is a # private folder ($runtime/.flatpak/APP/xdg-run). So a saved download or an upload # lands in an empty folder the app creates in there and never leaves the sandbox. # Link that path to the portal in each app's folder. xdg-desktop-portal after 1.22.1 # returns /run/flatpak/doc paths itself (upstream commit 69ba5e1). Apps installed # while the desktop runs get the link at the next start. sandbox_runtime=/run/user/$(id -u) if [[ $runtime == "$sandbox_runtime"/* ]]; then while read -r app; do app_doc=$runtime/.flatpak/$app/xdg-run/${runtime#"$sandbox_runtime"/}/doc (umask 077; mkdir -p "$(dirname "$app_doc")" && ln -sfn /run/flatpak/doc "$app_doc") || true done < <(flatpak list --app --columns=application 2>/dev/null) fi # plasma-session starts KWin through kwin_wayland_wrapper. Shadow it to add our outputs. # With ft-screens the size is only the starting one: ft-screens sets each screen's own. cat > "$runtime/bin/kwin_wayland_wrapper" < /tmp/frametop-remote.log 2>&1 & "$here/vnc-bridge.sh" "$width" "$height" > /tmp/frametop-vnc.log 2>&1 & fi dbus-run-session startplasma-wayland