Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

11 changed files with 226 additions and 486 deletions

View file

@ -1,20 +0,0 @@
pkgbase = deckless-git
pkgdesc = Keep official Steam intact while fixing proxy split, Big Picture rendering, and i3 couch-mode handoff on Linux
pkgver = 0.1.0.r0.g089aa68
pkgrel = 1
url = https://github.com/m1ngsama/deckless
arch = any
license = GPL-3.0-or-later
makedepends = git
depends = bash
depends = jq
depends = steam
optdepends = gamescope: run Big Picture inside gamescope
optdepends = gamemode: enable gamemode for Big Picture sessions
optdepends = i3-wm: enable fullscreen handoff between Big Picture and launched games
provides = deckless
conflicts = deckless
source = git+https://github.com/m1ngsama/deckless.git
sha256sums = SKIP
pkgname = deckless-git

View file

@ -4,18 +4,6 @@ All notable changes to this project will be documented in this file.
The format is inspired by Keep a Changelog, and the project follows semantic versioning once tagged releases begin. The format is inspired by Keep a Changelog, and the project follows semantic versioning once tagged releases begin.
## [0.2.0] - 2026-03-28
### Added
- A `deckless-git` `PKGBUILD` and `.SRCINFO` entry point for Arch users who want to package Deckless from a local clone.
- A real-machine Big Picture screenshot and validation notes in the README.
### Fixed
- Wrapper timing around Steam's updater verification so Deckless no longer needs to present a modified wrapper during the verification phase to recover the proxied, GPU-enabled webhelper path.
- Wrapper cleanup now survives Steam's shutdown path by detaching the cleanup helper from the launcher session before Steam exits.
## [0.1.0] - 2026-03-28 ## [0.1.0] - 2026-03-28
### Added ### Added
@ -30,5 +18,3 @@ The format is inspired by Keep a Changelog, and the project follows semantic ver
- GitHub Actions validation for shell syntax and ShellCheck. - GitHub Actions validation for shell syntax and ShellCheck.
[0.1.0]: https://github.com/m1ngsama/deckless/releases/tag/v0.1.0 [0.1.0]: https://github.com/m1ngsama/deckless/releases/tag/v0.1.0
[0.2.0]: https://github.com/m1ngsama/deckless/compare/v0.1.0...v0.2.0
[Unreleased]: https://github.com/m1ngsama/deckless/compare/v0.2.0...HEAD

View file

@ -1,56 +0,0 @@
# Maintainer: m1ngsama <contact@m1ng.space>
pkgname=deckless-git
pkgver=0.1.0.r0.g089aa68
pkgrel=1
pkgdesc='Keep official Steam intact while fixing proxy split, Big Picture rendering, and i3 couch-mode handoff on Linux'
arch=('any')
url='https://github.com/m1ngsama/deckless'
license=('GPL-3.0-or-later')
depends=('bash' 'jq' 'steam')
makedepends=('git')
optdepends=(
'gamescope: run Big Picture inside gamescope'
'gamemode: enable gamemode for Big Picture sessions'
'i3-wm: enable fullscreen handoff between Big Picture and launched games'
)
provides=('deckless')
conflicts=('deckless')
source=('git+https://github.com/m1ngsama/deckless.git')
sha256sums=('SKIP')
pkgver() {
cd deckless
git describe --long --tags --abbrev=7 | sed 's/^v//; s/-/.r/; s/-/./'
}
package() {
cd deckless
install -d "${pkgdir}/usr/share/deckless"
cp -a \
LICENSE \
README.md \
CHANGELOG.md \
CONTRIBUTING.md \
install.sh \
uninstall.sh \
bin \
config \
docs \
"${pkgdir}/usr/share/deckless/"
install -Dm755 /dev/stdin "${pkgdir}/usr/bin/deckless-install" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
exec /usr/share/deckless/install.sh "$@"
EOF
install -Dm755 /dev/stdin "${pkgdir}/usr/bin/deckless-uninstall" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
exec /usr/share/deckless/uninstall.sh "$@"
EOF
}

View file

@ -14,26 +14,13 @@ It started from a real Arch Linux desktop that needed direct game traffic, proxi
- Keeps `/usr/bin/steam` as the official upstream client. - Keeps `/usr/bin/steam` as the official upstream client.
- Proxies only `steamwebhelper` traffic, so store, community pages, avatars, and embedded web content can use a proxy while games and downloads stay direct. - Proxies only `steamwebhelper` traffic, so store, community pages, avatars, and embedded web content can use a proxy while games and downloads stay direct.
- Restores the original `steamwebhelper_sniper_wrap.sh` shortly after Steam exits through a detached cleanup helper. - Restores the original `steamwebhelper_sniper_wrap.sh` when Steam exits.
- Removes Steam's `--disable-gpu` and `--disable-gpu-compositing` flags for Big Picture webhelper sessions. - Removes Steam's `--disable-gpu` and `--disable-gpu-compositing` flags for Big Picture webhelper sessions.
- Exports sane `GBM`, `EGL`, and PulseAudio defaults to help Steam stay on hardware acceleration instead of dropping to software rendering. - Exports sane `GBM`, `EGL`, and PulseAudio defaults to help Steam stay on hardware acceleration instead of dropping to software rendering.
- Launches Big Picture through `gamescope` and `gamemode` when they are available. - Launches Big Picture through `gamescope` and `gamemode` when they are available.
- Ships an optional i3 bridge that lets a newly launched game take fullscreen from Big Picture and hands fullscreen back when the game exits. - Ships an optional i3 bridge that lets a newly launched game take fullscreen from Big Picture and hands fullscreen back when the game exits.
- Installs cleanly into XDG paths and can be rolled back with `./uninstall.sh`. - Installs cleanly into XDG paths and can be rolled back with `./uninstall.sh`.
## Real-machine verification
Validated on March 28, 2026 on an Arch Linux desktop with X11, i3, NVIDIA 595.58.3, and a 240 Hz monitor.
![Deckless Big Picture running fullscreen on Arch Linux with i3](assets/screenshots/big-picture-arch-i3.png)
The live validation for this repository confirmed:
- Steam updater verification completed without a wrapper size mismatch loop.
- The top-level `steamwebhelper` launched with `--proxy-server=...` and without Steam's forced `--disable-gpu*` flags.
- `webhelper_gpu.txt` reported `gpu_compositing: enabled`, `opengl: enabled_on`, and `video_decode: enabled`.
- Big Picture opened as a focused fullscreen `steamwebhelper` window under i3.
## Design goals ## Design goals
- Official Steam first, customization second. - Official Steam first, customization second.
@ -79,30 +66,11 @@ For a controller-first session, launch:
steam-bigpicture steam-bigpicture
``` ```
## Arch packaging
This repository also ships a `PKGBUILD` for a rolling `deckless-git` package.
From a local clone:
```bash
makepkg -si
deckless-install
```
That installs Deckless under `/usr/share/deckless` and exposes:
- `deckless-install`
- `deckless-uninstall`
## What gets installed ## What gets installed
- `~/.local/share/deckless/bin/deckless-steam` - `~/.local/share/deckless/bin/deckless-steam`
- `~/.local/share/deckless/bin/deckless-bigpicture` - `~/.local/share/deckless/bin/deckless-bigpicture`
- `~/.local/share/deckless/bin/deckless-i3-bigpicture-bridge` - `~/.local/share/deckless/bin/deckless-i3-bigpicture-bridge`
- `~/.local/share/deckless/bin/deckless-sync-webhelper-wrapper`
- `~/.local/share/deckless/bin/deckless-webhelper-heal`
- `~/.local/share/deckless/bin/deckless-webhelper-cleanup`
- `~/.local/bin/steam` - `~/.local/bin/steam`
- `~/.local/bin/steam-bigpicture` - `~/.local/bin/steam-bigpicture`
- `~/.local/share/applications/steam.desktop` - `~/.local/share/applications/steam.desktop`
@ -151,8 +119,6 @@ See [docs/i3.md](docs/i3.md) for the handoff behavior.
- official `steam` package - official `steam` package
- X11 - X11
- i3 - i3
- NVIDIA proprietary driver 595.58.3
- 240 Hz display
- `gamescope` - `gamescope`
- `gamemode` - `gamemode`
- `jq` - `jq`
@ -163,7 +129,7 @@ Other X11 desktop environments may still benefit from the proxy split and Big Pi
- Deckless never replaces `/usr/bin/steam`. - Deckless never replaces `/usr/bin/steam`.
- The Steam runtime wrapper is only rewritten while Steam is active. - The Steam runtime wrapper is only rewritten while Steam is active.
- The original wrapper is restored shortly after Steam exits, once Steam activity is gone. - The original wrapper is restored after Steam exits.
- `./uninstall.sh` restores the local launchers and desktop entries that existed before the first install. - `./uninstall.sh` restores the local launchers and desktop entries that existed before the first install.
## Documentation ## Documentation

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

View file

@ -1,11 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" script_path="$(readlink -f "${BASH_SOURCE[0]}")"
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/deckless" config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/deckless"
settings_env="${DECKLESS_SETTINGS_ENV:-${config_dir}/deckless.env}" settings_env="${DECKLESS_SETTINGS_ENV:-${config_dir}/deckless.env}"
legacy_proxy_env="${HOME}/.config/network/proxy-env.sh" legacy_proxy_env="${HOME}/.config/network/proxy-env.sh"
proxy_env="${DECKLESS_PROXY_ENV:-${config_dir}/proxy-env.sh}"
steam_bin="${DECKLESS_STEAM_BIN:-/usr/bin/steam}" steam_bin="${DECKLESS_STEAM_BIN:-/usr/bin/steam}"
if [[ -r "$settings_env" ]]; then if [[ -r "$settings_env" ]]; then
@ -23,11 +22,7 @@ target_dir="${target%/*}"
state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/deckless/steam" state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/deckless/steam"
session_state="${state_dir}/session.env" session_state="${state_dir}/session.env"
original_wrapper="${state_dir}/steamwebhelper_sniper_wrap.sh.original" original_wrapper="${state_dir}/steamwebhelper_sniper_wrap.sh.original"
proxy_env="${DECKLESS_PROXY_ENV:-${config_dir}/proxy-env.sh}"
sync_helper="${script_dir}/deckless-sync-webhelper-wrapper"
heal_helper="${script_dir}/deckless-webhelper-heal"
cleanup_helper="${script_dir}/deckless-webhelper-cleanup"
cleanup_done=0
if [[ ! -d "$target_dir" ]]; then if [[ ! -d "$target_dir" ]]; then
printf 'Steam runtime directory not found: %s\n' "$target_dir" >&2 printf 'Steam runtime directory not found: %s\n' "$target_dir" >&2
@ -39,83 +34,30 @@ if [[ ! -x "$steam_bin" ]]; then
exit 1 exit 1
fi fi
for helper in "$sync_helper" "$heal_helper" "$cleanup_helper"; do
if [[ ! -x "$helper" ]]; then
printf 'Deckless helper not found: %s\n' "$helper" >&2
exit 1
fi
done
mkdir -p "$state_dir" mkdir -p "$state_dir"
steam_activity_running() { terminate_stale_monitors() {
local args
while IFS= read -r args; do
if [[ "$args" == *"${steam_root}/ubuntu12_32/steam"* ]] || \
[[ "$args" == *"${steam_root}/ubuntu12_64/steamwebhelper"* ]] || \
[[ "$args" == *"${target}"* ]] || \
[[ "$args" == *"${steam_bin}"* ]]; then
return 0
fi
done < <(ps -ww -eo args=)
return 1
}
terminate_stale_helpers() {
local pid local pid
local args local args
while read -r pid args; do while IFS= read -r line; do
pid="${line%% *}"
args="${line#* }"
[[ "$pid" == "$$" ]] && continue
case "$args" in case "$args" in
"$sync_helper"|\ "$script_path"|\
"$sync_helper "*|\ "$script_path "*|\
"$heal_helper"|\ "bash $script_path"|\
"$heal_helper "*|\ "bash $script_path "*|\
"$cleanup_helper"|\ "/usr/bin/bash $script_path"|\
"$cleanup_helper "*|\ "/usr/bin/bash $script_path "*)
"bash $sync_helper"|\
"bash $sync_helper "*|\
"bash $heal_helper"|\
"bash $heal_helper "*|\
"bash $cleanup_helper"|\
"bash $cleanup_helper "*|\
"/usr/bin/bash $sync_helper"|\
"/usr/bin/bash $sync_helper "*|\
"/usr/bin/bash $heal_helper"|\
"/usr/bin/bash $heal_helper "*|\
"/usr/bin/bash $cleanup_helper"|\
"/usr/bin/bash $cleanup_helper "*)
kill -TERM "$pid" 2>/dev/null || true kill -TERM "$pid" 2>/dev/null || true
;; ;;
esac esac
done < <(ps -ww -eo pid=,args=) done < <(ps -ww -eo pid=,args=)
} }
start_detached_helper() {
local helper="$1"
if command -v setsid >/dev/null 2>&1; then
setsid -f "$helper" >/dev/null 2>&1 </dev/null || true
else
nohup "$helper" >/dev/null 2>&1 </dev/null &
fi
}
restore_official_wrapper() {
if (( cleanup_done == 1 )); then
return 0
fi
if grep -Fq 'deckless-managed steam webhelper wrapper' "$target" 2>/dev/null && [[ -f "$original_wrapper" ]]; then
cp -a "$original_wrapper" "$target"
fi
rm -f "$session_state" "$original_wrapper"
cleanup_done=1
}
apply_optional_locale() { apply_optional_locale() {
if [[ -n "${DECKLESS_LANG:-}" ]]; then if [[ -n "${DECKLESS_LANG:-}" ]]; then
while IFS='=' read -r name _; do while IFS='=' read -r name _; do
@ -185,11 +127,206 @@ build_chromium_bypass_list() {
printf '%s\n' "${entries[*]}" printf '%s\n' "${entries[*]}"
} }
if ! steam_activity_running; then restore_tracked_wrapper() {
terminate_stale_helpers if [[ -f "$original_wrapper" ]]; then
restore_official_wrapper cp -a "$original_wrapper" "$target"
fi
}
restore_official_wrapper() {
restore_tracked_wrapper
rm -f "$session_state" "$original_wrapper"
}
write_managed_wrapper() {
local tmp_wrapper
local session_state_q
tmp_wrapper="$(mktemp "${target_dir}/.steamwebhelper_sniper_wrap.sh.XXXXXX")"
printf -v session_state_q '%q' "$session_state"
cat >"$tmp_wrapper" <<EOF
#!/bin/bash
# deckless-managed steam webhelper wrapper
session_state=${session_state_q}
export LD_LIBRARY_PATH=.\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}
if [[ -z "\${XDG_RUNTIME_DIR:-}" ]]; then
export XDG_RUNTIME_DIR="/run/user/\$(id -u)"
fi fi
if [[ -z "\${PULSE_SERVER:-}" && -S "\${XDG_RUNTIME_DIR}/pulse/native" ]]; then
export PULSE_SERVER="unix:\${XDG_RUNTIME_DIR}/pulse/native"
fi
if [[ -z "\${GBM_BACKENDS_PATH:-}" ]]; then
if [[ -r /run/host/usr/lib/gbm/dri_gbm.so ]]; then
export GBM_BACKENDS_PATH='/run/host/usr/lib/gbm'
elif [[ -r /usr/lib/gbm/dri_gbm.so ]]; then
export GBM_BACKENDS_PATH='/usr/lib/gbm'
fi
fi
if [[ -z "\${__EGL_VENDOR_LIBRARY_DIRS:-}" ]]; then
if [[ -d /run/host/usr/share/glvnd/egl_vendor.d ]]; then
export __EGL_VENDOR_LIBRARY_DIRS='/run/host/usr/share/glvnd/egl_vendor.d'
elif [[ -d /usr/share/glvnd/egl_vendor.d ]]; then
export __EGL_VENDOR_LIBRARY_DIRS='/usr/share/glvnd/egl_vendor.d'
fi
fi
proxy_server="\${DECKLESS_WEBHELPER_PROXY_SERVER:-}"
proxy_bypass="\${DECKLESS_WEBHELPER_PROXY_BYPASS_LIST:-}"
if [[ -z "\$proxy_server" && -r "\$session_state" ]]; then
# shellcheck disable=SC1090
. "\$session_state"
proxy_server="\${DECKLESS_WEBHELPER_PROXY_SERVER:-}"
proxy_bypass="\${DECKLESS_WEBHELPER_PROXY_BYPASS_LIST:-}"
fi
extra_args=()
forwarded_args=()
for arg in "\$@"; do
case "\$arg" in
--disable-gpu|--disable-gpu-compositing)
continue
;;
*)
forwarded_args+=( "\$arg" )
;;
esac
done
if [[ -n "\$proxy_server" ]]; then
extra_args+=( "--proxy-server=\${proxy_server}" )
fi
if [[ -n "\$proxy_bypass" ]]; then
extra_args+=( "--proxy-bypass-list=\${proxy_bypass}" )
fi
extra_args+=( "--ignore-gpu-blocklist" "--enable-gpu-rasterization" )
echo "<6>exec ./steamwebhelper \${extra_args[*]} \${forwarded_args[*]}"
echo "<remaining-lines-assume-level=7>"
exec ./steamwebhelper "\${extra_args[@]}" "\${forwarded_args[@]}"
EOF
chmod 0755 "$tmp_wrapper"
mv -f "$tmp_wrapper" "$target"
}
ensure_managed_wrapper() {
if grep -Fq 'deckless-managed steam webhelper wrapper' "$target" 2>/dev/null; then
return 0
fi
cp -a "$target" "$original_wrapper"
write_managed_wrapper
}
restart_unproxied_webhelper() {
local pid
local args
local stale=0
local -a top_level_pids=()
while IFS= read -r line; do
pid="${line%% *}"
args="${line#* }"
if [[ "$args" == *'steamwebhelper -nocrashdialog'* ]]; then
stale=0
if [[ -n "${proxy_server:-}" && "$args" != *'--proxy-server='* ]]; then
stale=1
fi
if [[ "$args" == *'--disable-gpu '* || "$args" == *'--disable-gpu-compositing'* ]]; then
stale=1
fi
(( stale == 1 )) && top_level_pids+=("$pid")
elif [[ "$args" == *"${steam_root}/ubuntu12_64/steamwebhelper"* && "$args" != *'--type='* ]]; then
stale=0
if [[ -n "${proxy_server:-}" && "$args" != *'--proxy-server='* ]]; then
stale=1
fi
if [[ "$args" == *'--disable-gpu '* || "$args" == *'--disable-gpu-compositing'* ]]; then
stale=1
fi
(( stale == 1 )) && top_level_pids+=("$pid")
fi
done < <(ps -ww -eo pid=,args=)
if (( ${#top_level_pids[@]} > 0 )); then
kill -TERM "${top_level_pids[@]}" 2>/dev/null || true
fi
}
steam_activity_running() {
local args
while IFS= read -r args; do
if [[ "$args" == *"${steam_root}/ubuntu12_32/steam"* ]] || \
[[ "$args" == *"${steam_root}/ubuntu12_64/steamwebhelper"* ]] || \
[[ "$args" == *"${target}"* ]] || \
[[ "$args" == *"${steam_bin}"* ]]; then
return 0
fi
done < <(ps -ww -eo args=)
return 1
}
steam_updater_active() {
local args
while IFS= read -r args; do
if [[ "$args" == *"${steam_root}/ubuntu12_32/steam"* && "$args" == *'-child-update-ui'* ]]; then
return 0
fi
done < <(ps -ww -eo args=)
return 1
}
start_wrapper_monitor() {
(
local seen_steam=0
local idle_checks=0
local startup_checks=0
while (( idle_checks < 60 )); do
if steam_activity_running; then
seen_steam=1
idle_checks=0
startup_checks=0
if steam_updater_active; then
if grep -Fq 'deckless-managed steam webhelper wrapper' "$target" 2>/dev/null; then
restore_tracked_wrapper
elif [[ ! -f "$original_wrapper" ]]; then
cp -a "$target" "$original_wrapper"
fi
else
ensure_managed_wrapper
restart_unproxied_webhelper
fi
else
if (( seen_steam == 1 )); then
(( idle_checks += 1 ))
else
(( startup_checks += 1 ))
if (( startup_checks >= 60 )); then
break
fi
fi
fi
sleep 1
done
restore_official_wrapper
) >/dev/null 2>&1 &
}
terminate_stale_monitors
apply_optional_locale apply_optional_locale
if [[ -z "${SDL_VIDEODRIVER:-}" ]]; then if [[ -z "${SDL_VIDEODRIVER:-}" ]]; then
@ -227,6 +364,12 @@ if [[ -n "$bypass_candidate" ]]; then
proxy_bypass="$(build_chromium_bypass_list "$bypass_candidate")" proxy_bypass="$(build_chromium_bypass_list "$bypass_candidate")"
fi fi
if grep -Fq 'deckless-managed steam webhelper wrapper' "$target" 2>/dev/null && [[ -f "$original_wrapper" ]]; then
cp -a "$original_wrapper" "$target"
fi
cp -a "$target" "$original_wrapper"
tmp_state="$(mktemp "${state_dir}/.session.env.XXXXXX")" tmp_state="$(mktemp "${state_dir}/.session.env.XXXXXX")"
trap 'rm -f "${tmp_state:-}"' EXIT trap 'rm -f "${tmp_state:-}"' EXIT
{ {
@ -238,22 +381,11 @@ chmod 0600 "$tmp_state"
mv -f "$tmp_state" "$session_state" mv -f "$tmp_state" "$session_state"
trap - EXIT trap - EXIT
start_wrapper_monitor
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
unset ALL_PROXY all_proxy unset ALL_PROXY all_proxy
unset no_proxy NO_PROXY unset no_proxy NO_PROXY
unset DECKLESS_WEBHELPER_PROXY_SERVER DECKLESS_WEBHELPER_PROXY_BYPASS_LIST unset DECKLESS_WEBHELPER_PROXY_SERVER DECKLESS_WEBHELPER_PROXY_BYPASS_LIST
trap 'restore_official_wrapper' EXIT HUP INT TERM exec "$steam_bin" "$@"
start_detached_helper "$heal_helper"
start_detached_helper "$cleanup_helper"
set +e
"$steam_bin" "$@"
steam_status=$?
set -e
restore_official_wrapper
trap - EXIT HUP INT TERM
exit "$steam_status"

View file

@ -1,104 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
steam_root="$(readlink -e "$HOME/.steam/steam" || true)"
if [[ -z "$steam_root" ]]; then
steam_root="${XDG_DATA_HOME:-$HOME/.local/share}/Steam"
fi
target="${steam_root}/ubuntu12_64/steamwebhelper_sniper_wrap.sh"
target_dir="${target%/*}"
state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/deckless/steam"
session_state="${state_dir}/session.env"
original_wrapper="${state_dir}/steamwebhelper_sniper_wrap.sh.original"
[[ -d "$target_dir" ]] || exit 0
[[ -f "$target" ]] || exit 0
[[ -f "$session_state" ]] || exit 0
mkdir -p "$state_dir"
if ! grep -Fq 'deckless-managed steam webhelper wrapper' "$target" 2>/dev/null && [[ ! -f "$original_wrapper" ]]; then
cp -a "$target" "$original_wrapper"
fi
tmp_wrapper="$(mktemp "${target_dir}/.steamwebhelper_sniper_wrap.sh.XXXXXX")"
trap 'rm -f "$tmp_wrapper"' EXIT
printf -v session_state_q '%q' "$session_state"
cat >"$tmp_wrapper" <<EOF
#!/bin/bash
# deckless-managed steam webhelper wrapper
session_state=${session_state_q}
export LD_LIBRARY_PATH=.\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}
if [[ -z "\${XDG_RUNTIME_DIR:-}" ]]; then
export XDG_RUNTIME_DIR="/run/user/\$(id -u)"
fi
if [[ -z "\${PULSE_SERVER:-}" && -S "\${XDG_RUNTIME_DIR}/pulse/native" ]]; then
export PULSE_SERVER="unix:\${XDG_RUNTIME_DIR}/pulse/native"
fi
if [[ -z "\${GBM_BACKENDS_PATH:-}" ]]; then
if [[ -r /run/host/usr/lib/gbm/dri_gbm.so ]]; then
export GBM_BACKENDS_PATH='/run/host/usr/lib/gbm'
elif [[ -r /usr/lib/gbm/dri_gbm.so ]]; then
export GBM_BACKENDS_PATH='/usr/lib/gbm'
fi
fi
if [[ -z "\${__EGL_VENDOR_LIBRARY_DIRS:-}" ]]; then
if [[ -d /run/host/usr/share/glvnd/egl_vendor.d ]]; then
export __EGL_VENDOR_LIBRARY_DIRS='/run/host/usr/share/glvnd/egl_vendor.d'
elif [[ -d /usr/share/glvnd/egl_vendor.d ]]; then
export __EGL_VENDOR_LIBRARY_DIRS='/usr/share/glvnd/egl_vendor.d'
fi
fi
proxy_server="\${DECKLESS_WEBHELPER_PROXY_SERVER:-}"
proxy_bypass="\${DECKLESS_WEBHELPER_PROXY_BYPASS_LIST:-}"
if [[ -z "\$proxy_server" && -r "\$session_state" ]]; then
# shellcheck disable=SC1090
. "\$session_state"
proxy_server="\${DECKLESS_WEBHELPER_PROXY_SERVER:-}"
proxy_bypass="\${DECKLESS_WEBHELPER_PROXY_BYPASS_LIST:-}"
fi
extra_args=()
forwarded_args=()
for arg in "\$@"; do
case "\$arg" in
--disable-gpu|--disable-gpu-compositing)
continue
;;
*)
forwarded_args+=( "\$arg" )
;;
esac
done
if [[ -n "\$proxy_server" ]]; then
extra_args+=( "--proxy-server=\${proxy_server}" )
fi
if [[ -n "\$proxy_bypass" ]]; then
extra_args+=( "--proxy-bypass-list=\${proxy_bypass}" )
fi
extra_args+=( "--ignore-gpu-blocklist" "--enable-gpu-rasterization" )
echo "<6>exec ./steamwebhelper \${extra_args[*]} \${forwarded_args[*]}"
echo "<remaining-lines-assume-level=7>"
exec ./steamwebhelper "\${extra_args[@]}" "\${forwarded_args[@]}"
EOF
if [[ ! -f "$target" ]] || ! cmp -s "$tmp_wrapper" "$target"; then
chmod 0755 "$tmp_wrapper"
mv -f "$tmp_wrapper" "$target"
fi

View file

@ -1,63 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
steam_root="$(readlink -e "$HOME/.steam/steam" || true)"
if [[ -z "$steam_root" ]]; then
steam_root="${XDG_DATA_HOME:-$HOME/.local/share}/Steam"
fi
wrapper_target="${steam_root}/ubuntu12_64/steamwebhelper_sniper_wrap.sh"
webhelper_binary="${steam_root}/ubuntu12_64/steamwebhelper"
state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/deckless/steam"
session_state="${state_dir}/session.env"
original_wrapper="${state_dir}/steamwebhelper_sniper_wrap.sh.original"
[[ -f "$session_state" ]] || exit 0
steam_activity_running() {
local args
while IFS= read -r args; do
if [[ "$args" == *"${steam_root}/ubuntu12_32/steam"* ]] || \
[[ "$args" == *"${webhelper_binary}"* ]] || \
[[ "$args" == *"${wrapper_target}"* ]] || \
[[ "$args" == *'/usr/bin/steam'* ]]; then
return 0
fi
done < <(ps -ww -eo args=)
return 1
}
restore_wrapper() {
if grep -Fq 'deckless-managed steam webhelper wrapper' "$wrapper_target" 2>/dev/null && [[ -f "$original_wrapper" ]]; then
cp -a "$original_wrapper" "$wrapper_target"
fi
rm -f "$session_state" "$original_wrapper"
}
seen_steam=0
idle_checks=0
startup_checks=0
while (( idle_checks < 30 )); do
if steam_activity_running; then
seen_steam=1
idle_checks=0
startup_checks=0
else
if (( seen_steam == 1 )); then
(( idle_checks += 1 ))
else
(( startup_checks += 1 ))
if (( startup_checks >= 30 )); then
break
fi
fi
fi
sleep 2
done
restore_wrapper

View file

@ -1,85 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
steam_root="$(readlink -e "$HOME/.steam/steam" || true)"
if [[ -z "$steam_root" ]]; then
steam_root="${XDG_DATA_HOME:-$HOME/.local/share}/Steam"
fi
wrapper_target="${steam_root}/ubuntu12_64/steamwebhelper_sniper_wrap.sh"
webhelper_binary="${steam_root}/ubuntu12_64/steamwebhelper"
state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/deckless/steam"
session_state="${state_dir}/session.env"
sync_helper="${script_dir}/deckless-sync-webhelper-wrapper"
[[ -f "$session_state" ]] || exit 0
[[ -x "$sync_helper" ]] || exit 0
proxy_server=''
if [[ -r "$session_state" ]]; then
# shellcheck disable=SC1090
. "$session_state"
proxy_server="${DECKLESS_WEBHELPER_PROXY_SERVER:-}"
fi
webhelper_args_healthy() {
local args="$1"
if [[ -n "$proxy_server" && "$args" != *'--proxy-server='* ]]; then
return 1
fi
if [[ "$args" == *'--disable-gpu '* || "$args" == *'--disable-gpu-compositing'* ]]; then
return 1
fi
return 0
}
attempt=0
while (( attempt < 180 )); do
top_level_pids=()
wrap_pids=()
healthy=0
while read -r pid args; do
if [[ "$args" == *"${wrapper_target}"* ]]; then
wrap_pids+=("$pid")
fi
if [[ "$args" == *'steamwebhelper -nocrashdialog'* ]]; then
if webhelper_args_healthy "$args"; then
healthy=1
break
fi
top_level_pids+=("$pid")
elif [[ "$args" == *"${webhelper_binary}"* && "$args" != *'--type='* ]]; then
if webhelper_args_healthy "$args"; then
healthy=1
break
fi
top_level_pids+=("$pid")
fi
done < <(ps -ww -eo pid=,args=)
(( healthy == 1 )) && exit 0
if (( ${#wrap_pids[@]} > 0 || ${#top_level_pids[@]} > 0 )); then
"$sync_helper" || exit 0
if (( ${#wrap_pids[@]} > 0 )); then
kill -TERM "${wrap_pids[@]}" 2>/dev/null || true
fi
if (( ${#top_level_pids[@]} > 0 )); then
kill -TERM "${top_level_pids[@]}" 2>/dev/null || true
fi
exit 0
fi
sleep 1
(( attempt += 1 ))
done

View file

@ -15,22 +15,9 @@ This is the main launcher.
- resolves the active Steam runtime path - resolves the active Steam runtime path
- sources optional Deckless config - sources optional Deckless config
- loads proxy settings - loads proxy settings
- writes session state for helper processes - writes a session-only replacement for `steamwebhelper_sniper_wrap.sh`
- starts a healer that waits for the first Steam webhelper hop and then patches `steamwebhelper_sniper_wrap.sh`
- clears proxy environment variables before starting the official Steam client - clears proxy environment variables before starting the official Steam client
- starts a detached cleanup worker that restores the original wrapper after Steam exits - restores the original wrapper after Steam exits
### `deckless-sync-webhelper-wrapper`
This helper writes the managed `steamwebhelper_sniper_wrap.sh` replacement for the current session and captures the official upstream wrapper before replacing it.
### `deckless-webhelper-heal`
This helper waits for the first top-level `steamwebhelper` launch, checks whether proxy and GPU policy landed, and re-launches it once through the managed wrapper when needed.
### `deckless-webhelper-cleanup`
This helper runs detached from the launcher session, waits until Steam activity is gone, restores the official wrapper, and removes the session backup.
### `deckless-bigpicture` ### `deckless-bigpicture`
@ -42,7 +29,7 @@ This bridge listens to i3 window events and hands fullscreen from Big Picture to
## Why patch the Steam webhelper wrapper at runtime ## Why patch the Steam webhelper wrapper at runtime
Steam itself already uses `steamwebhelper_sniper_wrap.sh` as the last hop before `steamwebhelper`. Replacing that wrapper only after Steam reaches the webhelper launch phase gives Deckless a narrow control point for: Steam itself already uses `steamwebhelper_sniper_wrap.sh` as the last hop before `steamwebhelper`. Replacing that wrapper only while Steam is running gives Deckless a narrow control point for:
- webhelper-only proxy flags - webhelper-only proxy flags
- removing forced `--disable-gpu` arguments - removing forced `--disable-gpu` arguments

View file

@ -124,9 +124,6 @@ backup_once "${autostart_dir}/deckless-i3-bigpicture-bridge.desktop" "autostart/
install -Dm755 "${repo_root}/bin/deckless-steam" "${install_root}/bin/deckless-steam" install -Dm755 "${repo_root}/bin/deckless-steam" "${install_root}/bin/deckless-steam"
install -Dm755 "${repo_root}/bin/deckless-bigpicture" "${install_root}/bin/deckless-bigpicture" install -Dm755 "${repo_root}/bin/deckless-bigpicture" "${install_root}/bin/deckless-bigpicture"
install -Dm755 "${repo_root}/bin/deckless-i3-bigpicture-bridge" "${install_root}/bin/deckless-i3-bigpicture-bridge" install -Dm755 "${repo_root}/bin/deckless-i3-bigpicture-bridge" "${install_root}/bin/deckless-i3-bigpicture-bridge"
install -Dm755 "${repo_root}/bin/deckless-sync-webhelper-wrapper" "${install_root}/bin/deckless-sync-webhelper-wrapper"
install -Dm755 "${repo_root}/bin/deckless-webhelper-heal" "${install_root}/bin/deckless-webhelper-heal"
install -Dm755 "${repo_root}/bin/deckless-webhelper-cleanup" "${install_root}/bin/deckless-webhelper-cleanup"
install -Dm644 "${repo_root}/config/proxy-env.example.sh" "${config_dir}/proxy-env.example.sh" install -Dm644 "${repo_root}/config/proxy-env.example.sh" "${config_dir}/proxy-env.example.sh"
install -Dm644 "${repo_root}/config/deckless.env.example" "${config_dir}/deckless.env.example" install -Dm644 "${repo_root}/config/deckless.env.example" "${config_dir}/deckless.env.example"