mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
- The pad keeps Steam Input's virtual-gamepad identity only in Steam sessions. SDL ignores a Steam virtual gamepad unless run under Steam, so on the desktop and from the rail every SDL emulator saw no controller at all; there it is a plain Xbox 360 pad now, which SDL and evdev map by themselves. - bannerlator-pad-defaults writes Player 1 on the controller for emulators that have no profile yet (RPCS3 first: without one it takes the keyboard). Run at desktop start and before a program from the rail; never overwrites. - The desktop and everything started from it, and a program from the rail, run on the game cores when chosen, else every core but the slowest cluster (program_cores); BL_PIN_CORES=0 turns it off. Game cores reach every mode.
510 lines
28 KiB
Bash
Executable File
510 lines
28 KiB
Bash
Executable File
#!/bin/bash
|
|
# The Linux runtime's session. Bannerlator execs this under proot with the output size and mode;
|
|
# the first pass starts gamescope, which execs this script again inside it for the program:
|
|
# bannerlator-session desktop the file manager
|
|
# bannerlator-session run <path> a Linux program (AppImage, script or ELF)
|
|
# bannerlator-session steam [steam-url] the native Steam client
|
|
# bannerlator-session lxqt the LXQt/labwc desktop (DroidDeck's desktop package)
|
|
# BL_WIDTH / BL_HEIGHT size the output, BL_FPS limits it, BL_LOG collects everything said here.
|
|
set -u
|
|
export HOME=${HOME:-/root}
|
|
mode=${1:-desktop}
|
|
shift || true
|
|
# One line per milestone, greppable as "== STEP", so a first run reads as a narrative rather than
|
|
# something to be reconstructed from Steam's own chatter. The app shows these while it waits.
|
|
step() { echo "== STEP $(date -u +%H:%M:%S) $*"; }
|
|
if [ -n "${BL_LOG:-}" ] && [ -z "${BL_INSIDE:-}" ]; then
|
|
mkdir -p "$(dirname "$BL_LOG")" 2>/dev/null
|
|
# The client alone writes megabytes an hour; one earlier log is kept, not every session's.
|
|
if [ "$(stat -c %s "$BL_LOG" 2>/dev/null || echo 0)" -gt 8388608 ]; then
|
|
mv -f "$BL_LOG" "$BL_LOG.1"
|
|
fi
|
|
exec >>"$BL_LOG" 2>&1
|
|
echo "== bannerlator-session $(date) mode=$mode $*"
|
|
echo "== uid=$(id -u) size=${BL_WIDTH:-?}x${BL_HEIGHT:-?} fps=${BL_FPS:-0} icd=${VK_ICD_FILENAMES:-none}"
|
|
echo "== gamescope: $(gamescope --version 2>&1 | head -1) at $(command -v gamescope)"
|
|
echo "== rootfs: $(cat /.version 2>/dev/null || echo unversioned)"
|
|
fi
|
|
|
|
# Until the runtime is stable, Steam's own logs are copied beside the session log at exit so a
|
|
# failed launch can be handed over as one folder.
|
|
collect_steam_logs() {
|
|
[ -n "${BL_DEBUG_DIR:-}" ] || return 0
|
|
local steam_root=${XDG_DATA_HOME:-$HOME/.local/share}/Steam
|
|
mkdir -p "$BL_DEBUG_DIR/steam" 2>/dev/null || return 0
|
|
# Into steam/ beside this session's own logs. The app scrubs these line by line after the
|
|
# session ends - they carry the account's session token and id - so nothing here is the last
|
|
# word on what the user ends up sharing; this is just the copy that survives the runtime.
|
|
[ -d "$steam_root/logs" ] && cp -r "$steam_root/logs/." "$BL_DEBUG_DIR/steam/" 2>/dev/null
|
|
[ -f "$HOME/.steam/steam/logs/steamwebhelper.log" ] && cp "$HOME/.steam/steam/logs/steamwebhelper.log" "$BL_DEBUG_DIR/steam/" 2>/dev/null
|
|
# NOT copied, ever: loginusers.vdf, config.vdf and the ssfn* files, which hold the credentials
|
|
# themselves rather than a mention of them.
|
|
echo "== steam logs copied to $BL_DEBUG_DIR/steam"
|
|
}
|
|
# A session that is killed never reaches the copy below, and those are the sessions worth reading.
|
|
trap 'collect_steam_logs 2>/dev/null || true' TERM INT HUP
|
|
# The driver everything in this session draws with: gamescope's compositing, the client's UI
|
|
# (OpenGL through Zink), every game Proton launches, and in desktop mode every program on it.
|
|
# BL_VK_DRIVER names an imported driver's ICD manifest on the Android side, which is bound into the
|
|
# session at its own path; unset means the runtime keeps the Turnip it was built with.
|
|
# VK_DRIVER_FILES REPLACES the loader's search, so a manifest whose library is unreadable would
|
|
# leave the session with no Vulkan at all - check both before setting it, and say so in the log
|
|
# rather than fail silently half a minute later.
|
|
if [ -n "${BL_VK_DRIVER:-}" ]; then
|
|
bl_icd_lib=$(sed -n 's/.*"library_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$BL_VK_DRIVER" 2>/dev/null | head -1)
|
|
# JSON may escape the separator: org.json writes "\/data\/user\/0\/...". The Vulkan loader
|
|
# parses that correctly; this check has to as well, or an absolute path looks relative and gets
|
|
# glued onto the manifest's directory - which is exactly what happened the first time.
|
|
bl_icd_lib=$(printf '%s' "$bl_icd_lib" | sed 's|\\/|/|g')
|
|
case $bl_icd_lib in
|
|
/*) ;;
|
|
?*) bl_icd_lib="$(dirname "$BL_VK_DRIVER")/$bl_icd_lib" ;;
|
|
esac
|
|
if [ -r "$BL_VK_DRIVER" ] && [ -n "$bl_icd_lib" ] && [ -r "$bl_icd_lib" ]; then
|
|
export VK_DRIVER_FILES="$BL_VK_DRIVER"
|
|
# Loaders older than 1.3.207 only know the old name; both are harmless together.
|
|
export VK_ICD_FILENAMES="$BL_VK_DRIVER"
|
|
echo "== vulkan driver: imported $bl_icd_lib (via $BL_VK_DRIVER)"
|
|
else
|
|
echo "== vulkan driver: IGNORING $BL_VK_DRIVER (manifest or ${bl_icd_lib:-library} unreadable); using the runtime's own"
|
|
fi
|
|
unset bl_icd_lib
|
|
fi
|
|
# The app's gamescope build (tools/gamescope) takes realtime-priority Vulkan queues on this
|
|
# switch instead of on CAP_SYS_NICE, which proot never has; it falls back to normal priority
|
|
# where the driver refuses, and the runtime's own build ignores it.
|
|
export GAMESCOPE_FORCE_VULKAN_REALTIME=${BL_GAMESCOPE_REALTIME:-1}
|
|
export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
|
|
export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
|
|
export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
|
|
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
|
|
|
|
# The cores a program runs on outside Steam (the desktop and everything started from it, a
|
|
# program from the rail): the user's game cores when set (BL_GAME_CPUS), else every core but the
|
|
# slowest cluster - Android is free to park an emulator's threads on the little cores, and those
|
|
# are what compile shaders and recompile PPU/SPU code. BL_PIN_CORES=0 leaves the scheduler alone.
|
|
program_cores() {
|
|
[ "${BL_PIN_CORES:-1}" = 0 ] && return 0
|
|
command -v taskset >/dev/null 2>&1 || return 0
|
|
if [ -n "${BL_GAME_CPUS:-}" ]; then echo "$BL_GAME_CPUS"; return 0; fi
|
|
local f n max min= list=
|
|
for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do
|
|
max=$(cat "$f" 2>/dev/null) || continue
|
|
[ -z "$min" ] || [ "$max" -lt "$min" ] && min=$max
|
|
done
|
|
[ -n "$min" ] || return 0
|
|
for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do
|
|
max=$(cat "$f" 2>/dev/null) || continue
|
|
n=${f#/sys/devices/system/cpu/cpu}; n=${n%%/*}
|
|
[ "$max" -gt "$min" ] && list="$list${list:+,}$n"
|
|
done
|
|
echo "$list"
|
|
}
|
|
|
|
if [ -z "${BL_INSIDE:-}" ]; then
|
|
# The compositor may still be starting, and a socket left by an earlier session is not
|
|
# a compositor: probe until a connection succeeds.
|
|
for _ in $(seq 1 100); do
|
|
wayland-info >/dev/null 2>&1 && break
|
|
sleep 0.1
|
|
done
|
|
# The desktop (DroidDeck's hosted package) is its own nested compositor, labwc, drawing straight
|
|
# into the app's: no gamescope in between.
|
|
if [ "$mode" = lxqt ]; then
|
|
step "starting the desktop"
|
|
[ -x /usr/local/bin/droiddeck-desktop ] || { echo "== the desktop package is not installed"; exit 66; }
|
|
cores=$(program_cores)
|
|
if [ -n "$cores" ]; then
|
|
echo "== desktop cores: $cores (the desktop and every program started from it)"
|
|
exec taskset -c "$cores" /usr/local/bin/droiddeck-desktop
|
|
fi
|
|
exec /usr/local/bin/droiddeck-desktop
|
|
fi
|
|
width=${BL_WIDTH:-1280}
|
|
height=${BL_HEIGHT:-720}
|
|
args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$width" -h "$height")
|
|
# -r is what gamescope advertises as the refresh rate, and what a game reads as the display's.
|
|
# Left unset it defaults to 60, so a 120 Hz panel offers only 60 Hz in game settings. The FPS
|
|
# limiter wins when it is on (it is a deliberate cap); otherwise use the panel's own rate.
|
|
if [ "${BL_FPS:-0}" -gt 0 ]; then
|
|
args+=(-r "$BL_FPS")
|
|
elif [ "${BL_REFRESH:-0}" -gt 0 ]; then
|
|
args+=(-r "$BL_REFRESH")
|
|
fi
|
|
[ "$mode" = steam ] && args+=(-e)
|
|
# A game that resizes its own window when it loses focus (the Steam menu opening over it) comes
|
|
# back smaller, and gamescope draws a window that is not flagged fullscreen at its own size in
|
|
# the top-left corner. Forcing every window to the output's size keeps it filling the screen.
|
|
# BL_GAMESCOPE_FORCE_FULLSCREEN=0 turns it off.
|
|
[ "$mode" = steam ] && [ "${BL_GAMESCOPE_FORCE_FULLSCREEN:-1}" = 1 ] && args+=(--force-windows-fullscreen)
|
|
# HDR10: the app opened its compositor's colour-management gate for this session (BL_HDR=1);
|
|
# gamescope then offers HDR to its clients through the same wp_color_manager_v1 it binds on ours.
|
|
[ "${BL_HDR:-0}" = 1 ] && args+=(--hdr-enabled)
|
|
# Deck mode's performance overlay (Quick Access Menu → Performance → Overlay Level) is drawn by
|
|
# mangoapp, which gamescope starts beside the client; the client sets the level by rewriting
|
|
# MANGOHUD_CONFIGFILE. The apk stages Valve's aarch64 build (tools/mangoapp) - without it the menu
|
|
# still offers the levels and nothing appears. Starts hidden until the client writes a level.
|
|
# BL_MANGOAPP=0 turns it off (one community build found Valve's mangoapp crashing on Turnip).
|
|
if [ "$mode" = steam ] && [ "${BL_STEAMDECK:-0}" = 1 ] && [ "${BL_MANGOAPP:-1}" = 1 ] \
|
|
&& [ "${BL_STEAM_UI:-}" != desktop ] && command -v mangoapp >/dev/null 2>&1; then
|
|
MANGOHUD_CONFIGFILE=$(mktemp /tmp/mangohud.XXXXXXXX)
|
|
echo no_display > "$MANGOHUD_CONFIGFILE"
|
|
export MANGOHUD_CONFIGFILE STEAM_USE_MANGOAPP=1 STEAM_MANGOAPP_PRESETS_SUPPORTED=1 STEAM_MANGOAPP_HORIZONTAL_SUPPORTED=1
|
|
args+=(--mangoapp)
|
|
echo "== gamescope: mangoapp on ($(command -v mangoapp), config $MANGOHUD_CONFIGFILE)"
|
|
fi
|
|
export BL_INSIDE=1
|
|
exec gamescope "${args[@]}" -- "$0" "$mode" "$@"
|
|
fi
|
|
|
|
# gamescope manages X windows: toolkits go through Xwayland, not gamescope's own Wayland socket.
|
|
export GDK_BACKEND=x11 QT_QPA_PLATFORM=xcb SDL_VIDEODRIVER=x11
|
|
# gamescope's reaper waits for every process left under it before the compositor comes down. A
|
|
# process a game left behind - a Wine service blocked on a server that is already gone - is
|
|
# reparented there when the client exits, and the screen stays black on it for good. Nothing of
|
|
# the session outlives the program it was started for.
|
|
reap_orphans() {
|
|
local round p stat pid left
|
|
for round in 1 2 3 4 5; do
|
|
left=
|
|
for p in /proc/[0-9]*; do
|
|
pid=${p#/proc/}
|
|
[ "$pid" = "$$" ] && continue
|
|
stat=$(cat "$p/stat" 2>/dev/null) || continue
|
|
stat=${stat##*) }
|
|
set -- $stat
|
|
[ "${2:-}" = "$PPID" ] && left="$left $pid"
|
|
done
|
|
[ -n "$left" ] || return 0
|
|
echo "== leaving behind:$left"
|
|
kill -9 $left 2>/dev/null
|
|
sleep 0.2
|
|
done
|
|
}
|
|
|
|
case $mode in
|
|
desktop)
|
|
exec pcmanfm
|
|
;;
|
|
run)
|
|
target=${1:?program path}
|
|
shift
|
|
cores=$(program_cores)
|
|
if [ -n "$cores" ]; then
|
|
echo "== program cores: $cores"
|
|
taskset -cp "$cores" $$ >/dev/null 2>&1 || true
|
|
fi
|
|
# Player 1 on the controller in an emulator that has no profile of its own yet.
|
|
python3 /usr/local/bin/bannerlator-pad-defaults 2>&1 || true
|
|
case ${target,,} in
|
|
*.appimage)
|
|
export APPIMAGE_EXTRACT_AND_RUN=1
|
|
chmod +x "$target" 2>/dev/null
|
|
exec "$target" "$@"
|
|
;;
|
|
*.sh)
|
|
exec bash "$target" "$@"
|
|
;;
|
|
*)
|
|
chmod +x "$target" 2>/dev/null
|
|
cd "$(dirname "$target")" && exec "$target" "$@"
|
|
;;
|
|
esac
|
|
;;
|
|
steam)
|
|
step "checking the Steam client (first run downloads it, ~1-2 min with nothing on screen)"
|
|
bannerlator-steam-install || exit $?
|
|
step "Steam client ready"
|
|
steam_root=$XDG_DATA_HOME/Steam
|
|
# Register the card library (bound at /mnt/bannerlator-sd) with the client - internal storage is
|
|
# its main library already - and the ARM64 Proton that lets it launch the Windows ones.
|
|
# A request left in ~/.bl-proton-extra installs a third-party Proton before the client starts,
|
|
# so it is in the Compatibility dropdown the first time the user looks. One per line, each the
|
|
# arguments to bannerlator-proton-extra: "ge", "cachyos", "ge --tag GE-Proton11-7", or a URL.
|
|
# Hundreds of megabytes each, so it is only ever done on request, and the line is dropped once
|
|
# it has succeeded - a failed download stays and is retried next session.
|
|
if [ -s "$HOME/.bl-proton-extra" ]; then
|
|
_bl_left=$HOME/.bl-proton-extra.left
|
|
: > "$_bl_left"
|
|
while read -r _bl_extra || [ -n "$_bl_extra" ]; do
|
|
case ${_bl_extra# } in ""|"#"*) continue ;; esac
|
|
step "installing Proton '$_bl_extra' (large download, several minutes)"
|
|
if bannerlator-proton-extra "$steam_root" $_bl_extra; then
|
|
step "installed Proton '$_bl_extra'"
|
|
else
|
|
step "Proton '$_bl_extra' failed; will retry next session"
|
|
echo "$_bl_extra" >> "$_bl_left"
|
|
fi
|
|
done < "$HOME/.bl-proton-extra"
|
|
mv -f "$_bl_left" "$HOME/.bl-proton-extra"
|
|
fi
|
|
# steam:// URLs left in ~/.bl-steam-urls are handed to the client on its command line. The app
|
|
# writes one when the user asks for a Proton that is one of Valve's own depots rather than a
|
|
# tarball: those are not files anywhere, the client fetches them like any other app. One per
|
|
# line, and the file is cleared once they have been passed on - the client either acts on a URL
|
|
# or it does not, and repeating it every session would re-ask forever.
|
|
if [ -s "$HOME/.bl-steam-urls" ]; then
|
|
while read -r _bl_url || [ -n "$_bl_url" ]; do
|
|
case ${_bl_url# } in steam://*) ;; *) continue ;; esac
|
|
step "asking the client for $_bl_url"
|
|
set -- "$@" "$_bl_url"
|
|
done < "$HOME/.bl-steam-urls"
|
|
rm -f "$HOME/.bl-steam-urls"
|
|
fi
|
|
# Cores for the client itself. Steam pins its own interface renderer to a subset - on the
|
|
# device it took 5 of 8, leaving out both little cores and the fastest one - which is a
|
|
# reasonable thing to do while a game is running and the wrong thing when the client is all
|
|
# there is. BL_CLIENT_CPUS overrides it. Re-applied on a beat because steamwebhelper spawns
|
|
# children continuously and each one inherits Steam's choice, not ours.
|
|
if [ -n "${BL_CLIENT_CPUS:-}" ]; then
|
|
step "client cores: $BL_CLIENT_CPUS"
|
|
(
|
|
for _ in $(seq 1 720); do
|
|
for _p in $(pgrep -f 'steamwebhelper|steamrtarm64/steam|gamescope' 2>/dev/null); do
|
|
taskset -cp "$BL_CLIENT_CPUS" "$_p" >/dev/null 2>&1 || true
|
|
done
|
|
sleep 5
|
|
done
|
|
) &
|
|
fi
|
|
|
|
step "registering the app's game library with Steam"
|
|
bannerlator-steam-library "$steam_root" || true
|
|
step "setting up the ARM64 Proton compatibility tool"
|
|
bannerlator-steam-compat "$steam_root" || true
|
|
# The user's own games, as non-Steam shortcuts under that tool; the list is written by the app
|
|
# each session (empty = the app's earlier entries are removed, the client's own are kept).
|
|
if [ -n "${BL_ADDED_GAMES:-}" ]; then
|
|
step "adding the app's own games to the client's library"
|
|
bannerlator-steam-shortcuts "$steam_root" "$BL_ADDED_GAMES" || true
|
|
fi
|
|
# Steam runs a game's shared redistributables inside its prefix on first launch; marking
|
|
# them as already run keeps those x86 installers from starting under emulation at all.
|
|
bannerlator-seed-redists "$steam_root" || true
|
|
# The compatibility tool runs Valve's arm64 Proton, which the client fetches like any depot.
|
|
# Asked for on the command line the first time, so a Windows title has it before it is needed.
|
|
if [ ! -f "$steam_root/steamapps/appmanifest_4427310.acf" ]; then
|
|
step "asking the client to fetch the ARM64 Proton"
|
|
set -- "$@" steam://install/4427310
|
|
# Ported from Bannerlator (bannerlator-session, device-proven 2026-09-22). The client decides
|
|
# a title's compatibility tool when the title is installed and keeps that decision for the
|
|
# rest of its run, and the tool is set up before the client comes up. So once the depot has
|
|
# fully landed, restart the client once - its own -shutdown, then the loop below, which knows
|
|
# the marker - and every title installed from then on runs through the tool, with the
|
|
# refreshed tool list and defaults, and no second launch by the user. A first run only: with
|
|
# the depot present at session start nothing here is armed.
|
|
(
|
|
_bl_acf="$steam_root/steamapps/appmanifest_4427310.acf"
|
|
for _ in $(seq 1 360); do
|
|
sleep 10
|
|
# StateFlags 4 = fully installed; a moment more for the client to finish its bookkeeping.
|
|
if grep -q '"StateFlags"[[:space:]]*"4"' "$_bl_acf" 2>/dev/null; then
|
|
sleep 10
|
|
bannerlator-steam-compat "$steam_root" >/dev/null 2>&1 || true
|
|
step "compatibility layer installed: restarting the Steam client once"
|
|
: > "$HOME/.bl-restart-once"
|
|
"$steam_root/steamrtarm64/steam" -shutdown >/dev/null 2>&1 || true
|
|
exit 0
|
|
fi
|
|
done
|
|
) &
|
|
# A client that starts signed out drops that request - on a fresh install nobody has signed
|
|
# in yet - so the install dialog only appeared after the user restarted the client by hand.
|
|
# With no remembered sign-in, ask again once the sign-in has happened: the client binary
|
|
# started a second time with a URL hands it to the running instance and exits.
|
|
if ! grep -q '"RememberPassword"[[:space:]]*"1"' "$steam_root/config/loginusers.vdf" 2>/dev/null; then
|
|
(
|
|
_bl_conn="$steam_root/logs/connection_log.txt"
|
|
_bl_from=$(stat -c %s "$_bl_conn" 2>/dev/null || echo 0)
|
|
for _ in $(seq 1 180); do
|
|
sleep 10
|
|
[ -f "$steam_root/steamapps/appmanifest_4427310.acf" ] && exit 0
|
|
if tail -c +$((_bl_from + 1)) "$_bl_conn" 2>/dev/null | grep -q "RecvMsgClientLogOnResponse() : processing complete"; then
|
|
sleep 15
|
|
[ -f "$steam_root/steamapps/appmanifest_4427310.acf" ] && exit 0
|
|
step "signed in: asking the client again for the ARM64 Proton"
|
|
"$steam_root/steamrtarm64/steam" steam://install/4427310 >/dev/null 2>&1 || true
|
|
exit 0
|
|
fi
|
|
done
|
|
) &
|
|
fi
|
|
fi
|
|
# The tool is registered before the client starts and finds the depot at launch time, so
|
|
# nothing here waits for a download. What can still change under a running client is which
|
|
# titles are installed: a title installed this session gets the client's own per-app Proton
|
|
# choice, which it cannot start. Refresh the per-app entries every so often; the registrar is
|
|
# idempotent, so a repeat costs nothing.
|
|
# The seeder has the same problem and a worse consequence. It marks the shared redistributables
|
|
# as already run in each prefix that exists, and a game's prefix is not created until the
|
|
# client launches it - so a title played for the first time this session was never covered, and
|
|
# its install scripts run for real. That is exactly the hang the seeder exists to prevent: Need
|
|
# for Speed Payback sat on "Running install script" with vcredist_x86.exe blocked in pipe_read,
|
|
# holding both ends of a pipe nobody would write to, while the client waited for it to exit.
|
|
# Seeding again on the same beat covers a prefix the moment it appears.
|
|
# The seeder runs on a shorter beat than the registrar because it is racing something. The
|
|
# client creates a prefix when it launches a title and runs that title's install scripts
|
|
# moments later, so the seeder has only that gap to mark the redistributables as already run.
|
|
# It stamps each prefix it has covered and skips it thereafter, so the extra passes cost
|
|
# almost nothing.
|
|
(
|
|
beat=0
|
|
for _ in $(seq 1 720); do
|
|
sleep 5
|
|
bannerlator-seed-redists "$steam_root" >/dev/null 2>&1 || true
|
|
beat=$((beat + 1))
|
|
if [ "$((beat % 3))" -eq 0 ]; then
|
|
bannerlator-steam-compat "$steam_root" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
) &
|
|
# Debug aid while the runtime is being brought up: an app id dropped in ~/.bl-autolaunch is
|
|
# handed to the client as a rungameid URL, so a launch can be reproduced without touching the
|
|
# screen. Removed once the runtime is stable.
|
|
if [ -s "$HOME/.bl-autolaunch" ]; then
|
|
_bl_app=$(cat "$HOME/.bl-autolaunch")
|
|
# A bare app id runs the game; a full steam:// URL is passed through, which is how a
|
|
# validate or an install is driven without touching the screen.
|
|
case $_bl_app in
|
|
steam://*) _bl_url=$_bl_app ;;
|
|
*) _bl_url=steam://rungameid/$_bl_app ;;
|
|
esac
|
|
echo "== autolaunch $_bl_url"
|
|
set -- "$@" "$_bl_url"
|
|
fi
|
|
# The client builds Settings > Internet and its connection indicator on NetworkManager, which
|
|
# it looks for on the SYSTEM bus as it starts. This runtime has neither, so the page failed
|
|
# ("StartScanningForNetworks is not a function") under a header that showed no connection even
|
|
# while downloads ran. A system bus and a stand-in that reports Android's connection - and
|
|
# refuses what it cannot do, since the app has no say over Wi-Fi - answer what the client asks.
|
|
# The bus config is written here rather than shipped, so a runtime installed earlier gets this
|
|
# from the app's copy of the session scripts without a re-host. (WinNative, maxjivi05, e7a224ae.)
|
|
if command -v dbus-daemon >/dev/null 2>&1 && command -v bannerlator-netmanager >/dev/null 2>&1; then
|
|
mkdir -p /run/dbus /usr/local/share/bannerlator
|
|
cat > /usr/local/share/bannerlator/system-bus.conf <<'BUSCONF'
|
|
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
|
|
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
|
<busconfig>
|
|
<type>system</type>
|
|
<listen>unix:path=/run/dbus/system_bus_socket</listen>
|
|
<auth>EXTERNAL</auth>
|
|
<policy context="default">
|
|
<allow user="*"/>
|
|
<allow own="*"/>
|
|
<allow send_destination="*"/>
|
|
<allow receive_sender="*"/>
|
|
</policy>
|
|
</busconfig>
|
|
BUSCONF
|
|
rm -f /run/dbus/system_bus_socket
|
|
dbus-daemon --config-file=/usr/local/share/bannerlator/system-bus.conf --nofork --nopidfile &
|
|
system_bus=$!
|
|
for _ in $(seq 1 50); do
|
|
[ -S /run/dbus/system_bus_socket ] && break
|
|
sleep 0.1
|
|
done
|
|
if [ -S /run/dbus/system_bus_socket ]; then
|
|
bannerlator-netmanager &
|
|
netmanager=$!
|
|
echo "== network: system bus up, NetworkManager stand-in running"
|
|
else
|
|
echo "== network: the system bus did not come up; the client will show no connection"
|
|
fi
|
|
else
|
|
echo "== network: no dbus-daemon or netmanager in this runtime; the client will show no connection"
|
|
fi
|
|
|
|
export STEAM_RUNTIME=1
|
|
export LD_LIBRARY_PATH=$steam_root/steamrtarm64:$steam_root/steamrtarm64/panorama${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
|
|
export SDL_VIDEO_X11_DGAMOUSE=0
|
|
# Native-Linux x86 games go through Valve's FEX compat tool, which runs FEX with FEX_PORTABLE=1
|
|
# - that makes FEX look for its config next to the binary, while the tool writes one into the
|
|
# app's compatdata instead. FEX therefore starts with no rootfs, every x86_64 library lookup
|
|
# falls through to the host, and the guest shell dies on libreadline before the game is reached.
|
|
# FEX_ROOTFS names it outright and is read whatever the config situation.
|
|
[ -d /usr/share/guestos/fex-mesa ] && export FEX_ROOTFS=/usr/share/guestos/fex-mesa
|
|
# The client exits 42 when it has updated itself and wants a fresh start (steam.sh does this).
|
|
# BL_STEAM_UI=desktop = the client's desktop UI (a window, the library as a list), which the
|
|
# desktop's launchers ask for; gamescope shows one window at a time and the game takes over.
|
|
_bl_ui=(-gamepadui)
|
|
[ "${BL_STEAM_UI:-}" = desktop ] && _bl_ui=()
|
|
# Deck mode (BL_STEAMDECK=1, the Performance page): -steamdeck is the flag that puts the Quick
|
|
# Access Menu, the performance overlay and the Deck settings pages on screen. Only -steamdeck,
|
|
# never -steamos3: that one makes the client manage itself as SteamOS does - it calls
|
|
# steamos-select-branch and opts the install into the steamdeck_stable client branch, writes
|
|
# that to package/beta, and from then on the branch on disk loses to the -clientbeta below on
|
|
# every launch; the updater finds "installed version 0", marks an update pending and exits 42
|
|
# to apply it, which never happens, so the client restarts for ever - under a running game,
|
|
# taking Steam Input with it each time. (Bannerlator 38996ea1, on device.) The SteamOS helpers
|
|
# the client calls in this mode are staged as no-op stubs under /usr/bin.
|
|
if [ "${BL_STEAMDECK:-0}" = 1 ] && [ "${BL_STEAM_UI:-}" != desktop ]; then
|
|
_bl_ui+=(-steamdeck)
|
|
# The QAM's Scaling Filter lists NVIDIA Image Scaling only when told gamescope has it (it does).
|
|
export STEAM_GAMESCOPE_NIS_SUPPORTED=1
|
|
echo "== steam: Deck mode on (-steamdeck)"
|
|
fi
|
|
# What a killed session leaves behind. Android ends sessions without warning (backgrounding, the
|
|
# low-memory killer), and the client then finds .crash - opening its update UI on the spinner -
|
|
# and the web helper's Singleton* locks in htmlcache, owned by a process that is gone, which can
|
|
# leave the Gamepad UI blank. No client or helper is running at this point in a session.
|
|
if ! pgrep -f 'steamwebhelper|steamrtarm64/steam' >/dev/null 2>&1; then
|
|
_stale=""
|
|
for _f in "$steam_root/.crash" "$steam_root/steam.pid" \
|
|
"$steam_root/config/htmlcache/SingletonLock" "$steam_root/config/htmlcache/SingletonCookie" \
|
|
"$steam_root/config/htmlcache/SingletonSocket"; do
|
|
if [ -e "$_f" ] || [ -L "$_f" ]; then rm -f "$_f" && _stale="$_stale ${_f##*/}"; fi
|
|
done
|
|
[ -n "$_stale" ] && echo "== steam: cleared what an earlier session left:$_stale"
|
|
unset _stale _f
|
|
fi
|
|
# The client branch. publicbeta is what every session has run on; steamdeck_publicbeta is the
|
|
# channel Armada bootstraps its ARM64 client from. Whatever wrote package/beta, the branch on
|
|
# disk has to agree with the one forced here - a disagreement is the restart loop above, and
|
|
# it is silent apart from rc=42 in this log.
|
|
# Checked before every start, not once: the client rewrites package/beta during its own run,
|
|
# and a restart below (rc=42, or the one after the ARM64 Proton lands) would otherwise start it
|
|
# again on the branch it just wrote.
|
|
steam_channel=${BL_STEAM_CHANNEL:-publicbeta}
|
|
steam_beta=$steam_root/package/beta
|
|
echo "== steam args: ${_bl_ui[*]} -clientbeta $steam_channel -no-cef-sandbox -cef-force-gpu -cef-ozone-platform=x11 -cef-use-gl=angle -cef-use-angle=vulkan $*"
|
|
# An update that cannot install asks for a restart (rc=42) every time; the count is capped so a
|
|
# session can never again loop in silence, which reads as a menu opening and closing by itself.
|
|
steam_updates=0
|
|
while :; do
|
|
rm -f "$HOME/.bl-restart-once"
|
|
if [ -f "$steam_beta" ] && [ "$(cat "$steam_beta" 2>/dev/null)" != "$steam_channel" ]; then
|
|
echo "== steam: client branch on disk was '$(cat "$steam_beta" 2>/dev/null)', set to '$steam_channel' to match -clientbeta"
|
|
echo "$steam_channel" > "$steam_beta"
|
|
fi
|
|
step "starting the Steam client${BL_STEAM_UI:+ ($BL_STEAM_UI UI)}"
|
|
"$steam_root/steamrtarm64/steam" "${_bl_ui[@]}" -clientbeta "$steam_channel" \
|
|
-no-cef-sandbox -cef-force-gpu -cef-ozone-platform=x11 \
|
|
-cef-use-gl=angle -cef-use-angle=vulkan "$@"
|
|
rc=$?
|
|
echo "== steam exited rc=$rc"
|
|
collect_steam_logs
|
|
# The session asked for this exit (the ARM64 Proton landed): start again, without the URL
|
|
# arguments, which were acted on. Not counted against the update cap.
|
|
if [ -f "$HOME/.bl-restart-once" ]; then
|
|
echo "== steam: restarting once so the new compatibility layer is in the client's lists"
|
|
set --
|
|
continue
|
|
fi
|
|
[ "$rc" -eq 42 ] || break
|
|
steam_updates=$((steam_updates + 1))
|
|
if [ "$steam_updates" -ge 3 ]; then
|
|
echo "== steam: asked to restart for an update $steam_updates times and it is not installing; the session stops here rather than loop"
|
|
break
|
|
fi
|
|
# The URL arguments were acted on the first time; a restart must not repeat them.
|
|
set --
|
|
done
|
|
reap_orphans
|
|
exit "$rc"
|
|
;;
|
|
*)
|
|
echo "bannerlator-session: unknown mode '$mode'" >&2
|
|
exit 64
|
|
;;
|
|
esac
|