Super-Shift-G-Nobara-Deck-Mode/super_shift_g_nobara.sh
28allday 4b100dee18 Initial release: Super Shift G Gaming Mode for Nobara (Fedora KDE)
Fedora/KDE Plasma/plasmalogin edition of the gaming mode installer.
Builds Gamescope from source, uses dnf, Super+Alt+G keybind.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 21:34:15 +01:00

3274 lines
111 KiB
Bash
Executable file

#!/bin/bash
set -Euo pipefail
# Ensure critical files are created even if the script aborts via set -e
trap 'if [[ "$(id -u)" == "0" ]] && type ensure_critical_permissions &>/dev/null; then ensure_critical_permissions 2>/dev/null; fi' EXIT
Super_Shift_S_VERSION="13.00-fedora-kde"
CONFIG_FILE="/etc/gaming-mode.conf"
[[ -f "$HOME/.gaming-mode.conf" ]] && CONFIG_FILE="$HOME/.gaming-mode.conf"
source "$CONFIG_FILE" 2>/dev/null || true
: "${PERFORMANCE_MODE:=enabled}"
NEEDS_RELOGIN=0
NEEDS_REBOOT=0
info(){ echo "[*] $*"; }
warn(){ echo "[!] $*"; }
err(){ echo "[!] $*" >&2; }
die() {
local msg="$1"; local code="${2:-1}"
echo "FATAL: $msg" >&2
logger -t gaming-mode "Installation failed: $msg"
exit "$code"
}
dnf_fix_and_retry() {
local -a packages=("$@")
local attempt=0
local max_attempts=3
info "Cleaning dnf cache and metadata..."
sudo dnf clean all 2>/dev/null || true
info "Retrying package install..."
sudo dnf makecache 2>/dev/null || true
if sudo dnf install -y "${packages[@]}" 2>/dev/null; then
info "Packages installed successfully on retry"
return 0
fi
for ((attempt=1; attempt<=max_attempts; attempt++)); do
warn "Retry attempt $attempt/$max_attempts..."
sudo dnf clean all 2>/dev/null || true
sudo dnf makecache 2>/dev/null || true
if sudo dnf install -y --best --allowerasing "${packages[@]}" 2>/dev/null; then
info "Packages installed successfully on attempt $attempt"
return 0
fi
if sudo dnf install -y --skip-broken "${packages[@]}" 2>/dev/null; then
info "Packages installed (some may have been skipped) on attempt $attempt"
return 0
fi
done
warn "Bulk install failed - trying packages individually..."
local -a still_missing=()
for pkg in "${packages[@]}"; do
if ! check_package "$pkg"; then
if ! sudo dnf install -y "$pkg" 2>/dev/null; then
still_missing+=("$pkg")
fi
fi
done
if ((${#still_missing[@]})); then
err "Failed to install ${#still_missing[@]} package(s) after all retry attempts:"
for pkg in "${still_missing[@]}"; do
echo " - $pkg"
done
return 1
fi
info "All packages installed (individually)"
return 0
}
validate_environment() {
command -v dnf >/dev/null || die "dnf required (not a Fedora-based system)"
# Check for KDE Plasma / Wayland session
local de_ok=false
if [[ "${XDG_CURRENT_DESKTOP:-}" == *"KDE"* ]] || \
command -v plasmashell >/dev/null 2>&1 || \
command -v kwin_wayland >/dev/null 2>&1 || \
pgrep -x plasmashell >/dev/null 2>&1; then
de_ok=true
fi
if [[ "$de_ok" != "true" ]]; then
warn "KDE Plasma session not detected (XDG_CURRENT_DESKTOP=${XDG_CURRENT_DESKTOP:-unset})"
warn "This script is designed for Fedora with KDE Plasma desktop"
read -p "Continue anyway? [y/N]: " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || die "Aborting - KDE Plasma desktop not detected"
fi
# Check for plasmalogin display manager (Nobara)
if ! systemctl list-unit-files plasmalogin.service &>/dev/null || \
! systemctl list-unit-files plasmalogin.service 2>/dev/null | grep -q plasmalogin; then
warn "plasmalogin not detected - session switching requires plasmalogin"
read -p "Continue anyway? [y/N]: " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || die "Aborting - plasmalogin not detected"
fi
[ -d "$HOME/.config" ] || mkdir -p "$HOME/.config"
}
check_package() { rpm -q "$1" &>/dev/null; }
is_amd_igpu_card() {
local card_path="$1"
local device_path="$card_path/device"
local pci_slot=""
[[ -L "$device_path" ]] && pci_slot=$(basename "$(readlink -f "$device_path")")
[[ -z "$pci_slot" ]] && return 1
local device_info=$(/usr/bin/lspci -s "$pci_slot" 2>/dev/null)
if echo "$device_info" | grep -iqE 'renoir|cezanne|barcelo|rembrandt|phoenix|raphael|lucienne|picasso|raven|vega.*mobile|vega.*integrated|radeon.*graphics|yellow.*carp|green.*sardine|cyan.*skillfish|vangogh|van gogh|mendocino|hawk.*point|strix.*point|strix.*halo|krackan|sarlak'; then
return 0
fi
if echo "$device_info" | grep -iqE 'radeon rx|navi [0-9]|navi[0-9]|vega 56|vega 64|radeon vii|radeon pro|firepro|polaris|ellesmere|baffin|lexa|radeon [0-9]{3,4}[^0-9]'; then
return 1
fi
return 1
}
check_intel_only() {
local card_name driver driver_link
local has_intel=false
local has_amd_nvidia=false
for card_path in /sys/class/drm/card[0-9]*; do
card_name=$(basename "$card_path")
[[ "$card_name" == render* ]] && continue
driver_link="$card_path/device/driver"
[[ -L "$driver_link" ]] || continue
driver=$(basename "$(readlink "$driver_link")")
case "$driver" in
i915|xe)
has_intel=true
;;
nvidia|amdgpu)
has_amd_nvidia=true
;;
esac
done
if $has_intel && ! $has_amd_nvidia; then
return 0
fi
return 1
}
detect_dgpu_monitors() {
local -n _monitors=$1
local -n _dgpu_card=$2
local -n _dgpu_type=$3
_monitors=()
_dgpu_card=""
_dgpu_type=""
# GPU selection priority: NVIDIA dGPU > AMD dGPU > AMD iGPU (APU)
# Intel GPUs (iGPU and Arc) are NEVER selected — i915/xe drivers are skipped.
#
# Pass 1: collect all candidate cards by priority tier
local -a nvidia_cards=()
local -a amd_dgpu_cards=()
local -a amd_igpu_cards=()
for card_path in /sys/class/drm/card[0-9]*; do
local card_name=$(basename "$card_path")
[[ "$card_name" == render* ]] && continue
local driver_link="$card_path/device/driver"
[[ -L "$driver_link" ]] || continue
local driver=$(basename "$(readlink "$driver_link")")
case "$driver" in
nvidia)
nvidia_cards+=("$card_path")
;;
amdgpu)
if is_amd_igpu_card "$card_path"; then
amd_igpu_cards+=("$card_path")
else
amd_dgpu_cards+=("$card_path")
fi
;;
i915|xe)
# Intel iGPU and Arc dGPU — explicitly skip, never select
;;
esac
done
# Pass 2: select best GPU by priority
local selected_path=""
if [[ ${#nvidia_cards[@]} -gt 0 ]]; then
selected_path="${nvidia_cards[0]}"
_dgpu_type="NVIDIA"
elif [[ ${#amd_dgpu_cards[@]} -gt 0 ]]; then
selected_path="${amd_dgpu_cards[0]}"
_dgpu_type="AMD dGPU"
elif [[ ${#amd_igpu_cards[@]} -gt 0 ]]; then
selected_path="${amd_igpu_cards[0]}"
_dgpu_type="AMD APU"
fi
if [[ -z "$selected_path" ]]; then
return
fi
# Pass 3: enumerate connected monitors on the selected GPU
local card_name=$(basename "$selected_path")
_dgpu_card="$card_name"
for connector in "$selected_path"/"$card_name"-*/status; do
[[ -f "$connector" ]] || continue
local conn_dir=$(dirname "$connector")
local conn_name=$(basename "$conn_dir")
conn_name=${conn_name#card*-}
[[ "$conn_name" == Writeback* ]] && continue
local status=$(cat "$connector" 2>/dev/null)
if [[ "$status" == "connected" ]]; then
local resolution=""
local mode_file="$conn_dir/modes"
[[ -f "$mode_file" ]] && [[ -s "$mode_file" ]] && resolution=$(head -1 "$mode_file" 2>/dev/null)
_monitors+=("$conn_name|$resolution")
fi
done
}
check_nvidia_kernel_params() {
local lspci_output
lspci_output=$(/usr/bin/lspci 2>/dev/null)
if ! echo "$lspci_output" | grep -qi nvidia; then
return 0
fi
echo ""
echo "================================================================"
echo " NVIDIA KERNEL PARAMETER CHECK"
echo "================================================================"
echo ""
if grep -qE "nvidia[-_]drm\.modeset=1" /proc/cmdline 2>/dev/null; then
info "nvidia-drm.modeset=1 is already configured"
return 0
fi
warn "nvidia-drm.modeset=1 is NOT SET - required for Gaming Mode!"
echo ""
if [ -f /etc/default/grub ]; then
echo ""
read -p "Add nvidia-drm.modeset=1 to GRUB config? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
configure_grub_nvidia
else
warn "Skipping - you'll need to add nvidia-drm.modeset=1 manually"
show_manual_nvidia_instructions
fi
else
warn "Could not find /etc/default/grub"
show_manual_nvidia_instructions
fi
}
configure_grub_nvidia() {
local grub_default="/etc/default/grub"
info "Backing up GRUB config..."
sudo cp "$grub_default" "${grub_default}.backup.$(date +%Y%m%d%H%M%S)" || {
err "Failed to backup GRUB config"
return 1
}
info "Adding nvidia-drm.modeset=1 to GRUB..."
if ! grep -q "nvidia-drm.modeset=1" "$grub_default"; then
sudo sed -i 's/\(GRUB_CMDLINE_LINUX="[^"]*\)/\1 nvidia-drm.modeset=1/' "$grub_default"
if grep -q "nvidia-drm.modeset=1" "$grub_default"; then
info "Regenerating GRUB config..."
sudo grub2-mkconfig -o /boot/grub2/grub.cfg || {
err "Failed to regenerate GRUB config"
return 1
}
info "Successfully configured GRUB for NVIDIA"
NEEDS_REBOOT=1
else
err "Failed to add parameter to GRUB"
show_manual_nvidia_instructions
fi
fi
}
show_manual_nvidia_instructions() {
cat <<'MSG'
Manual configuration required:
Fedora (GRUB2): Add nvidia-drm.modeset=1 to GRUB_CMDLINE_LINUX in /etc/default/grub
Then run: sudo grub2-mkconfig -o /boot/grub2/grub.cfg
MSG
warn "Gaming Mode may not work correctly without nvidia-drm.modeset=1"
}
install_nvidia_deckmode_env() {
local lspci_output
lspci_output=$(/usr/bin/lspci 2>/dev/null)
if ! echo "$lspci_output" | grep -qi nvidia; then
info "No NVIDIA detected; skipping NVIDIA Deck-mode env."
return 0
fi
local env_file="/etc/environment.d/90-nvidia-gamescope.conf"
if [ -f "$env_file" ]; then
info "NVIDIA gamescope env already present: $env_file"
return 0
fi
info "Installing NVIDIA gamescope env (Deck-mode style)..."
sudo mkdir -p /etc/environment.d
sudo tee "$env_file" >/dev/null <<'EOF'
GBM_BACKEND=nvidia-drm
__GLX_VENDOR_LIBRARY_NAME=nvidia
__VK_LAYER_NV_optimus=NVIDIA_only
EOF
info "Installed $env_file"
NEEDS_RELOGIN=1
}
check_steam_dependencies() {
info "Checking Steam dependencies for Fedora..."
info "Updating package database..."
if ! sudo dnf makecache; then
warn "dnf makecache had errors - cleaning cache and retrying..."
sudo dnf clean all
sudo dnf makecache || warn "dnf makecache still has errors - continuing with available data"
fi
echo ""
echo "================================================================"
echo " SYSTEM UPDATE RECOMMENDED"
echo "================================================================"
echo ""
echo " It's recommended to upgrade your system before installing"
echo " gaming dependencies to avoid package version conflicts."
echo ""
read -p "Upgrade system now? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
info "Upgrading system..."
if ! sudo dnf upgrade -y; then
warn "Upgrade failed - attempting to fix..."
sudo dnf clean all
sudo dnf makecache 2>/dev/null || true
if ! sudo dnf upgrade -y --best --allowerasing 2>/dev/null; then
warn "Upgrade still failing - continuing with package installation"
warn "Package conflicts will be resolved during dependency install"
fi
fi
fi
echo ""
local -a missing_deps=()
local -a optional_deps=()
if ! command -v lspci >/dev/null 2>&1; then
info "Installing pciutils for GPU detection..."
sudo dnf install -y pciutils || { dnf_fix_and_retry pciutils || die "Failed to install pciutils"; }
fi
# No dpkg --add-architecture needed on Fedora; multilib is handled
# by installing .i686 packages directly. Ensure the repo is available.
info "Multilib support: Fedora handles 32-bit via .i686 packages natively"
local -a core_deps=(
"steam"
"vulkan-loader"
"vulkan-loader.i686"
"mesa-dri-drivers"
"mesa-dri-drivers.i686"
"glx-utils"
"glibc.i686"
"libgcc.i686"
"libX11.i686"
"libXScrnSaver.i686"
"alsa-plugins-pulseaudio.i686"
"pulseaudio-libs.i686"
"openal-soft.i686"
"nss.i686"
"cups-libs.i686"
"sdl2-compat.i686"
"freetype.i686"
"fontconfig.i686"
"NetworkManager-libnm.i686"
"NetworkManager"
"gamemode"
"gamemode.i686"
"liberation-fonts-all"
"xdg-user-dirs"
)
local gpu_vendor
gpu_vendor=$(/usr/bin/lspci 2>/dev/null | grep -iE 'vga|3d|display' || echo "")
local has_nvidia=false has_amd=false
if echo "$gpu_vendor" | grep -qi nvidia; then
has_nvidia=true
info "Detected NVIDIA GPU"
fi
if echo "$gpu_vendor" | grep -iqE 'amd|radeon|advanced micro'; then
has_amd=true
info "Detected AMD GPU"
fi
if echo "$gpu_vendor" | grep -iq intel; then
info "Detected Intel GPU (iGPU/Arc); Intel is not supported for Gaming Mode — skipping"
fi
local primary_gpu="unknown"
if $has_nvidia; then
primary_gpu="nvidia"
elif $has_amd; then
primary_gpu="amd"
fi
PRIMARY_GPU="$primary_gpu"
info "Primary GPU selection: $PRIMARY_GPU"
local -a gpu_deps=()
if $has_nvidia; then
# Detect installed NVIDIA driver on Fedora (RPM Fusion akmod-nvidia or nvidia-driver)
local nvidia_installed=false
if rpm -qa 'nvidia-driver*' 2>/dev/null | grep -q nvidia-driver; then
nvidia_installed=true
elif rpm -qa 'akmod-nvidia*' 2>/dev/null | grep -q akmod-nvidia; then
nvidia_installed=true
fi
local nvidia_ver=""
nvidia_ver=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1 | cut -d. -f1)
[[ -n "$nvidia_ver" ]] && info "NVIDIA driver version detected: $nvidia_ver"
# Nobara uses nvidia-driver instead of xorg-x11-drv-nvidia
if ! check_package "nvidia-driver" && ! check_package "xorg-x11-drv-nvidia"; then
gpu_deps+=("nvidia-driver")
fi
if ! check_package "nvidia-driver-libs.i686" && ! check_package "xorg-x11-drv-nvidia-libs.i686"; then
gpu_deps+=("nvidia-driver-libs.i686")
fi
if ! check_package "nvidia-settings"; then
gpu_deps+=("nvidia-settings")
fi
fi
if $has_amd; then
gpu_deps+=("vulkan-loader" "libvdpau" "libvdpau.i686")
# Nobara uses mesa-vulkan-drivers-freeworld instead of mesa-vulkan-drivers
if ! check_package "mesa-vulkan-drivers-freeworld"; then
if ! check_package "mesa-vulkan-drivers"; then
gpu_deps+=("mesa-vulkan-drivers-freeworld")
fi
fi
if ! check_package "mesa-vulkan-drivers-freeworld.i686"; then
if ! check_package "mesa-vulkan-drivers.i686"; then
gpu_deps+=("mesa-vulkan-drivers-freeworld.i686")
fi
fi
fi
if ! $has_nvidia && ! $has_amd; then
info "No NVIDIA/AMD GPU detected; installing Vulkan drivers as fallback..."
if ! check_package "mesa-vulkan-drivers-freeworld" && ! check_package "mesa-vulkan-drivers"; then
gpu_deps+=("mesa-vulkan-drivers-freeworld")
fi
if ! check_package "mesa-vulkan-drivers-freeworld.i686" && ! check_package "mesa-vulkan-drivers.i686"; then
gpu_deps+=("mesa-vulkan-drivers-freeworld.i686")
fi
fi
gpu_deps+=("vulkan-tools")
# Add mesa vulkan drivers if neither variant is installed
if ! check_package "mesa-vulkan-drivers-freeworld" && ! check_package "mesa-vulkan-drivers"; then
gpu_deps+=("mesa-vulkan-drivers-freeworld")
fi
local -a recommended_deps=(
"udisks2"
"libnotify"
)
info "Checking core Steam dependencies..."
for dep in "${core_deps[@]}"; do
if ! check_package "$dep"; then
missing_deps+=("$dep")
fi
done
info "Checking GPU-specific dependencies..."
for dep in "${gpu_deps[@]}"; do
if ! check_package "$dep"; then
missing_deps+=("$dep")
fi
done
info "Checking recommended dependencies..."
for dep in "${recommended_deps[@]}"; do
if ! check_package "$dep"; then
optional_deps+=("$dep")
fi
done
echo ""
echo "================================================================"
echo " STEAM DEPENDENCY CHECK RESULTS"
echo "================================================================"
echo ""
# Remove duplicates from missing_deps
local -a clean_missing=()
local -A seen_deps=()
for item in "${missing_deps[@]}"; do
if [[ -n "$item" && -z "${seen_deps[$item]:-}" ]]; then
clean_missing+=("$item")
seen_deps[$item]=1
fi
done
missing_deps=("${clean_missing[@]+"${clean_missing[@]}"}")
if ((${#missing_deps[@]})); then
echo " MISSING REQUIRED PACKAGES (${#missing_deps[@]}):"
for dep in "${missing_deps[@]}"; do
echo " - $dep"
done
echo ""
read -p "Install missing required packages? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
info "Installing missing dependencies..."
if ! sudo dnf install -y "${missing_deps[@]}"; then
warn "Initial install failed - attempting recovery..."
if ! dnf_fix_and_retry "${missing_deps[@]}"; then
local -a final_missing=()
for dep in "${missing_deps[@]}"; do
check_package "$dep" || final_missing+=("$dep")
done
if ((${#final_missing[@]})); then
err "Could not install ${#final_missing[@]} required package(s):"
for dep in "${final_missing[@]}"; do
echo " - $dep"
done
echo ""
echo " Possible fixes:"
echo " 1. Check your internet connection"
echo " 2. Try: sudo dnf clean all && sudo dnf makecache"
echo " 3. Ensure RPM Fusion repos are enabled (for Steam/NVIDIA)"
echo " 4. Run: sudo dnf distro-sync --best --allowerasing"
echo ""
read -p "Continue anyway with partial install? [y/N]: " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || die "Missing required Steam dependencies"
warn "Continuing with partial install - some features may not work"
else
info "All required packages eventually installed (after recovery)"
fi
fi
else
info "Required dependencies installed successfully"
fi
else
die "Missing required Steam dependencies"
fi
else
info "All required Steam dependencies are installed!"
fi
echo ""
if ((${#optional_deps[@]})); then
echo " RECOMMENDED PACKAGES (${#optional_deps[@]}):"
for dep in "${optional_deps[@]}"; do
echo " - $dep"
done
echo ""
read -p "Install recommended packages? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
info "Installing recommended packages..."
if ! sudo dnf install -y "${optional_deps[@]}" 2>/dev/null; then
warn "Some recommended packages failed - attempting recovery..."
dnf_fix_and_retry "${optional_deps[@]}" || warn "Some recommended packages could not be installed"
fi
fi
else
info "All recommended packages are already installed!"
fi
echo ""
echo "================================================================"
check_steam_config
}
check_steam_config() {
info "Checking Steam configuration..."
local missing_groups=()
if ! groups | grep -qw 'video'; then
missing_groups+=("video")
fi
if ! groups | grep -qw 'input'; then
missing_groups+=("input")
fi
if ! groups | grep -qw 'wheel'; then
missing_groups+=("wheel")
fi
if ((${#missing_groups[@]})); then
echo ""
echo "================================================================"
echo " USER GROUP PERMISSIONS"
echo "================================================================"
echo ""
echo " Your user needs to be added to the following groups:"
echo ""
for group in "${missing_groups[@]}"; do
case "$group" in
video) echo " - video - Required for GPU hardware access" ;;
input) echo " - input - Required for controller/gamepad support" ;;
wheel) echo " - wheel - Required for NetworkManager control in gaming mode" ;;
esac
done
echo ""
echo " NOTE: After adding groups, you MUST log out and log back in"
echo ""
read -p "Add user to ${missing_groups[*]} group(s)? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
local groups_to_add=$(IFS=,; echo "${missing_groups[*]}")
info "Adding user to groups: $groups_to_add"
if sudo usermod -aG "$groups_to_add" "$USER"; then
info "Successfully added user to group(s): $groups_to_add"
NEEDS_RELOGIN=1
else
err "Failed to add user to groups"
fi
fi
else
info "User is in video, input, and wheel groups - permissions OK"
fi
if [ -d "$HOME/.steam" ]; then
info "Steam directory found at ~/.steam"
fi
if [ -d "$HOME/.local/share/Steam" ]; then
info "Steam data directory found at ~/.local/share/Steam"
fi
# Check if Steam client is actually bootstrapped
if check_package "steam"; then
local steam_bin=""
if [[ -x /usr/bin/steam ]]; then
steam_bin="/usr/bin/steam"
elif command -v steam >/dev/null 2>&1; then
steam_bin="$(command -v steam)"
fi
if [[ -n "$steam_bin" ]] && [[ ! -f "$HOME/.local/share/Steam/ubuntu12_32/steam" ]]; then
echo ""
echo "================================================================"
echo " STEAM CLIENT BOOTSTRAP REQUIRED"
echo "================================================================"
echo ""
echo " The Steam package is installed, but the Steam client has not"
echo " completed its first-run download (~400MB)."
echo ""
echo " Steam will now launch to download and install the client."
echo " Once the login screen appears, you can close it or log in."
echo ""
read -p "Bootstrap Steam client now? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
info "Launching Steam to bootstrap client (this may take a few minutes)..."
$steam_bin &
local steam_pid=$!
info "Steam launched (PID: $steam_pid) - waiting for bootstrap to complete..."
local wait_count=0
while [[ ! -f "$HOME/.local/share/Steam/ubuntu12_32/steam" ]] && ((wait_count < 120)); do
sleep 5
((wait_count++))
if ! kill -0 "$steam_pid" 2>/dev/null; then
sleep 3
if [[ -f "$HOME/.local/share/Steam/ubuntu12_32/steam" ]]; then
break
fi
local new_pid
new_pid=$(pgrep -f "steam" 2>/dev/null | head -1)
if [[ -n "$new_pid" ]]; then
steam_pid="$new_pid"
else
break
fi
fi
done
if [[ -f "$HOME/.local/share/Steam/ubuntu12_32/steam" ]]; then
info "Steam client bootstrapped successfully!"
else
warn "Steam bootstrap may not have completed - you may need to run 'steam' manually"
fi
echo ""
echo " You can now log into Steam or close it."
echo " The installer will continue once you press Enter."
echo ""
read -r -p "Press Enter to continue..."
pkill -f steam 2>/dev/null || true
sleep 2
else
warn "Skipping Steam bootstrap - run 'steam' manually before using Gaming Mode"
fi
elif [[ -f "$HOME/.local/share/Steam/ubuntu12_32/steam" ]]; then
info "Steam client is fully bootstrapped"
fi
fi
}
setup_performance_permissions() {
local udev_rules_file="/etc/udev/rules.d/99-gaming-performance.rules"
local sudoers_file="/etc/sudoers.d/gaming-mode-sysctl"
local needs_setup=false
if [ ! -f "$udev_rules_file" ] || [ ! -f "$sudoers_file" ]; then
needs_setup=true
fi
if [ "$needs_setup" = false ]; then
info "Performance permissions already configured"
return 0
fi
echo ""
echo "================================================================"
echo " PERFORMANCE PERMISSIONS SETUP"
echo "================================================================"
echo ""
echo " To avoid sudo password prompts during gaming, we need to set"
echo " up permissions for CPU and GPU performance control."
echo ""
read -p "Set up passwordless performance controls? [Y/n]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
info "Skipping permissions setup"
return 0
fi
if [ ! -f "$udev_rules_file" ]; then
info "Creating udev rules for CPU/GPU performance control..."
if sudo tee "$udev_rules_file" > /dev/null <<'UDEV_RULES'
KERNEL=="cpu[0-9]*", SUBSYSTEM=="cpu", ACTION=="add", RUN+="/bin/chmod 666 /sys/devices/system/cpu/%k/cpufreq/scaling_governor"
KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="amdgpu", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/device/power_dpm_force_performance_level"
KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/gt_boost_freq_mhz"
KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/gt_min_freq_mhz"
KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/gt_max_freq_mhz"
UDEV_RULES
then
info "Udev rules created successfully"
sudo udevadm control --reload-rules || true
sudo udevadm trigger --subsystem-match=cpu --subsystem-match=drm || true
fi
fi
if [[ -f "$sudoers_file" ]]; then
info "Performance sudoers already exist at $sudoers_file"
else
info "Creating sudoers rule for Performance Mode sysctl tuning..."
if sudo tee "$sudoers_file" > /dev/null << 'SUDOERS_PERF'
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_autogroup_enabled=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_migration_cost_ns=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_min_granularity_ns=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_latency_ns=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.swappiness=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_ratio=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_background_ratio=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_writeback_centisecs=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_expire_centisecs=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_watches=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_instances=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.file-max=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.rmem_max=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.wmem_max=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.split_lock_mitigate=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.max_map_count=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.compaction_proactiveness=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.nmi_watchdog=*
%video ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/kernel/mm/transparent_hugepage/enabled
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pm *
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pl *
SUDOERS_PERF
then
sudo chmod 0440 "$sudoers_file"
if sudo visudo -c -f "$sudoers_file" >/dev/null 2>&1; then
info "Performance sudoers created and validated successfully"
else
err "Sudoers syntax validation FAILED -- removing broken file"
sudo rm -f "$sudoers_file"
return 1
fi
else
err "Failed to create performance sudoers file"
fi
fi
local memlock_file="/etc/security/limits.d/99-gaming-memlock.conf"
if [ ! -f "$memlock_file" ]; then
info "Creating memlock limits for gaming performance..."
if sudo tee "$memlock_file" > /dev/null << 'MEMLOCKCONF'
* soft memlock 2147484
* hard memlock 2147484
MEMLOCKCONF
then
info "Memlock limits configured (2GB)"
fi
fi
local pipewire_conf_dir="/etc/pipewire/pipewire.conf.d"
local pipewire_conf="$pipewire_conf_dir/10-gaming-latency.conf"
if [ ! -f "$pipewire_conf" ]; then
info "Creating PipeWire low-latency audio configuration..."
sudo mkdir -p "$pipewire_conf_dir"
if sudo tee "$pipewire_conf" > /dev/null << 'PIPEWIRECONF'
context.properties = {
default.clock.min-quantum = 256
}
PIPEWIRECONF
then
info "PipeWire gaming latency configured"
fi
fi
info "Performance permissions configured"
return 0
}
setup_shader_cache() {
local env_file="/etc/environment.d/99-shader-cache.conf"
if [ -f "$env_file" ]; then
info "Shader cache configuration already exists"
return 0
fi
echo ""
echo "================================================================"
echo " SHADER CACHE OPTIMIZATION"
echo "================================================================"
echo ""
echo " Configuring shader cache sizes for better gaming performance."
echo " This reduces stuttering in games by caching compiled shaders."
echo ""
read -p "Configure shader cache optimization? [Y/n]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
info "Skipping shader cache configuration"
return 0
fi
info "Creating shader cache configuration..."
sudo mkdir -p /etc/environment.d || { warn "Failed to create /etc/environment.d"; return 0; }
local tmp_shader
tmp_shader=$(mktemp) || { warn "Failed to create temp file"; return 0; }
cat > "$tmp_shader" << 'SHADERCACHE'
MESA_SHADER_CACHE_MAX_SIZE=12G
MESA_SHADER_CACHE_DISABLE_CLEANUP=1
RADV_PERFTEST=gpl
__GL_SHADER_DISK_CACHE=1
__GL_SHADER_DISK_CACHE_SIZE=12884901888
__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1
DXVK_STATE_CACHE=1
SHADERCACHE
if sudo cp "$tmp_shader" "$env_file"; then
rm -f "$tmp_shader"
sudo chmod 644 "$env_file"
info "Shader cache configured for all GPUs (AMD/NVIDIA + Proton)"
else
rm -f "$tmp_shader"
warn "Failed to create shader cache configuration"
fi
}
install_proton_ge() {
echo ""
echo "================================================================"
echo " PROTON GE (GloriousEggroll) INSTALLATION"
echo "================================================================"
echo ""
local steam_root=""
if [[ -d "$HOME/.local/share/Steam" ]]; then
steam_root="$HOME/.local/share/Steam"
elif [[ -L "$HOME/.steam/root" ]]; then
steam_root="$(readlink -f "$HOME/.steam/root")"
elif [[ -d "$HOME/.steam/debian-installation" ]]; then
steam_root="$HOME/.steam/debian-installation"
elif [[ -d "$HOME/.steam/steam" ]]; then
steam_root="$HOME/.steam/steam"
fi
if [[ -z "$steam_root" || ! -d "$steam_root" ]]; then
warn "Steam directory not found"
warn "Run Steam at least once before installing Proton GE"
return 0
fi
info "Steam installation found at: $steam_root"
local compat_dir="$steam_root/compatibilitytools.d"
local existing_ge=()
if [[ -d "$compat_dir" ]]; then
while IFS= read -r -d '' dir; do
existing_ge+=("$(basename "$dir")")
done < <(find "$compat_dir" -maxdepth 1 -type d -name "GE-Proton*" -print0 2>/dev/null | sort -zV)
fi
if ((${#existing_ge[@]})); then
echo " Existing Proton GE installations:"
for ge in "${existing_ge[@]}"; do
echo " - $ge"
done
echo ""
else
echo " No Proton GE versions currently installed."
echo ""
fi
info "Checking latest Proton GE release..."
local api_response
api_response=$(curl -sL --connect-timeout 10 --max-time 30 \
"https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases/latest" 2>/dev/null)
if [[ -z "$api_response" ]]; then
err "Failed to fetch Proton GE release info from GitHub"
echo " Check your internet connection and try again."
return 1
fi
local latest_tag latest_url latest_sha_url
latest_tag=$(echo "$api_response" | grep -oP '"tag_name"\s*:\s*"\K[^"]+' | head -1)
latest_url=$(echo "$api_response" | grep -oP '"browser_download_url"\s*:\s*"\K[^"]+\.tar\.gz(?=")' | head -1)
latest_sha_url=$(echo "$api_response" | grep -oP '"browser_download_url"\s*:\s*"\K[^"]+\.sha512sum(?=")' | head -1)
if [[ -z "$latest_tag" || -z "$latest_url" ]]; then
err "Could not parse Proton GE release info"
return 1
fi
echo " Latest release: $latest_tag"
echo " Download URL: $latest_url"
echo ""
if [[ -d "$compat_dir/$latest_tag" ]]; then
info "Proton GE $latest_tag is already installed!"
read -p "Reinstall $latest_tag? [y/N]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return 0
fi
info "Removing existing $latest_tag installation..."
rm -rf "${compat_dir:?}/$latest_tag"
else
read -p "Install Proton GE $latest_tag? [Y/n]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
info "Skipping Proton GE installation"
return 0
fi
fi
mkdir -p "$compat_dir"
local tmp_dir
tmp_dir=$(mktemp -d)
local tarball="$tmp_dir/${latest_tag}.tar.gz"
info "Downloading Proton GE $latest_tag (this may take a few minutes)..."
if ! curl -L --progress-bar --connect-timeout 15 --max-time 600 \
-o "$tarball" "$latest_url"; then
err "Failed to download Proton GE"
rm -rf "$tmp_dir"
return 1
fi
if [[ -n "$latest_sha_url" ]]; then
info "Verifying download checksum..."
local sha_file="$tmp_dir/checksum.sha512sum"
if curl -sL --connect-timeout 10 --max-time 30 -o "$sha_file" "$latest_sha_url" 2>/dev/null; then
pushd "$tmp_dir" >/dev/null || true
local expected_name
expected_name=$(awk '{print $2}' "$sha_file" | tr -d '*')
if [[ -n "$expected_name" && "$expected_name" != "${latest_tag}.tar.gz" ]]; then
mv "$tarball" "$tmp_dir/$expected_name"
tarball="$tmp_dir/$expected_name"
fi
if sha512sum -c "$sha_file" >/dev/null 2>&1; then
info "Checksum verified OK"
else
warn "Checksum verification failed - file may be corrupt"
read -p "Continue anyway? [y/N]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
rm -rf "$tmp_dir"
return 1
fi
fi
popd >/dev/null || true
else
warn "Could not download checksum file - skipping verification"
fi
fi
info "Extracting Proton GE $latest_tag..."
if ! tar -xf "$tarball" -C "$compat_dir"; then
err "Failed to extract Proton GE archive"
rm -rf "$tmp_dir"
return 1
fi
rm -rf "$tmp_dir"
if [[ -d "$compat_dir/$latest_tag" ]]; then
info "Proton GE $latest_tag installed successfully to:"
info " $compat_dir/$latest_tag"
else
local extracted
extracted=$(ls -td "$compat_dir"/GE-Proton* 2>/dev/null | head -1)
if [[ -n "$extracted" ]]; then
info "Proton GE installed successfully to:"
info " $extracted"
else
err "Extraction completed but Proton GE directory not found"
return 1
fi
fi
echo ""
echo " To use Proton GE in Steam:"
echo " 1. Restart Steam (if running)"
echo " 2. Right-click a game > Properties > Compatibility"
echo " 3. Check 'Force the use of a specific Steam Play compatibility tool'"
echo " 4. Select '$latest_tag' from the dropdown"
echo ""
if ((${#existing_ge[@]})); then
echo " You have ${#existing_ge[@]} other Proton GE version(s) installed."
read -p "Remove old Proton GE versions? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
for old_ge in "${existing_ge[@]}"; do
if [[ "$old_ge" != "$latest_tag" && -d "$compat_dir/$old_ge" ]]; then
info "Removing $old_ge..."
rm -rf "${compat_dir:?}/$old_ge"
fi
done
info "Old Proton GE versions removed"
fi
fi
return 0
}
install_mangoapp() {
echo ""
echo "================================================================"
echo " MANGOHUD + MANGOAPP INSTALLATION (from source)"
echo "================================================================"
echo ""
# The repo version (0.6.9.1) is too old for gamescope 3.16.x+
# It fails with "Couldn't create socket pipe at 'mangohud'"
# Always build from source for compatibility
# Check if we already have a source-built version at /usr/local
if [[ -x /usr/local/bin/mangoapp ]]; then
local mango_ver
mango_ver=$(/usr/local/bin/mangohud --version 2>&1 | head -1 || echo "unknown")
info "mangoapp already installed from source (MangoHud version: $mango_ver)"
return 0
fi
# Remove repo versions if installed — they're incompatible with our gamescope
if rpm -q mangoapp &>/dev/null; then
info "Removing incompatible repo version of mangohud/mangoapp..."
sudo dnf remove -y mangohud mangoapp 2>/dev/null || true
fi
install_mangoapp_from_source
}
install_mangoapp_from_source() {
info "Building MangoHud from source to get mangoapp..."
local -a mangohud_build_deps=(
"meson"
"ninja-build"
"cmake"
"pkgconf-pkg-config"
"gcc-c++"
"libstdc++-static"
"git"
"python3-mako"
"glslang"
"glew-devel"
"glfw-devel"
"libXNVCtrl-devel"
"dbus-devel"
"libX11-devel"
"libxkbcommon-devel"
"wayland-devel"
"wayland-protocols-devel"
"vulkan-loader-devel"
"spdlog-devel"
"fmt-devel"
"appstream"
)
info "Installing MangoHud build dependencies..."
if ! sudo dnf install -y "${mangohud_build_deps[@]}" 2>/dev/null; then
warn "Some build dependencies failed to install - attempting recovery..."
dnf_fix_and_retry "${mangohud_build_deps[@]}" || {
warn "Some build dependencies could not be installed"
warn "MangoHud build may fail - attempting anyway..."
}
fi
local build_dir
build_dir=$(mktemp -d)
local mangohud_src="$build_dir/MangoHud"
info "Cloning MangoHud from GitHub..."
if ! git clone --recurse-submodules --depth 1 https://github.com/flightlessmango/MangoHud.git "$mangohud_src"; then
err "Failed to clone MangoHud repository"
rm -rf "$build_dir"
return 1
fi
pushd "$mangohud_src" >/dev/null || { rm -rf "$build_dir"; return 1; }
info "Configuring MangoHud build (with mangoapp)..."
if ! meson setup build --prefix=/usr/local --buildtype=release \
-Dwith_wayland=enabled \
-Dwith_xnvctrl=enabled \
-Dmangoapp=true \
-Dmangohudctl=true; then
err "Meson configure failed"
popd >/dev/null || true
rm -rf "$build_dir"
return 1
fi
info "Building MangoHud (this may take a few minutes)..."
if ! ninja -C build; then
err "MangoHud build failed"
popd >/dev/null || true
rm -rf "$build_dir"
return 1
fi
info "Installing MangoHud..."
if ! sudo ninja -C build install; then
err "MangoHud install failed"
popd >/dev/null || true
rm -rf "$build_dir"
return 1
fi
sudo ldconfig
popd >/dev/null || true
rm -rf "$build_dir"
if command -v mangoapp >/dev/null 2>&1 || [[ -x /usr/local/bin/mangoapp ]]; then
info "mangoapp installed successfully at $(command -v mangoapp 2>/dev/null || echo /usr/local/bin/mangoapp)"
else
err "mangoapp binary not found after installation"
return 1
fi
}
install_gamescope_from_source() {
echo ""
echo "================================================================"
echo " GAMESCOPE INSTALLATION (from source)"
echo "================================================================"
echo ""
if command -v gamescope >/dev/null 2>&1; then
local gs_version
gs_version=$(gamescope --version 2>&1 | head -1 || echo "unknown")
info "gamescope already installed: $gs_version — skipping source build"
return 0
fi
info "gamescope is not available in Fedora repos - building from source..."
echo ""
local -a build_deps=(
"meson"
"ninja-build"
"cmake"
"pkgconf-pkg-config"
"gcc-c++"
"glslang"
"git"
"libcap-devel"
"libdrm-devel"
"libinput-devel"
"sdl2-compat-devel"
"vulkan-loader-devel"
"wayland-devel"
"libX11-devel"
"libX11-xcb"
"libxcb-devel"
"libXcomposite-devel"
"libXcursor-devel"
"libXdamage-devel"
"libXext-devel"
"libXfixes-devel"
"libXi-devel"
"libxkbcommon-devel"
"libXmu-devel"
"libXrender-devel"
"libXres-devel"
"libXtst-devel"
"libXxf86vm-devel"
"wayland-protocols-devel"
"hwdata"
"pipewire-devel"
"libavif-devel"
"libdecor-devel"
"libdisplay-info-devel"
"libei-devel"
"libliftoff-devel"
"luajit-devel"
"pixman-devel"
"stb_image-devel"
"systemd-devel"
"libseat-devel"
"xcb-util-wm-devel"
"xcb-util-image-devel"
"xcb-util-renderutil-devel"
"xcb-util-devel"
"xorg-x11-server-Xwayland-devel"
"lcms2-devel"
)
info "Installing build dependencies..."
if ! sudo dnf install -y "${build_deps[@]}" 2>/dev/null; then
warn "Some build dependencies failed to install - attempting recovery..."
dnf_fix_and_retry "${build_deps[@]}" || {
warn "Some build dependencies could not be installed"
warn "Gamescope build may fail - attempting anyway..."
}
fi
local build_dir
build_dir=$(mktemp -d)
local gamescope_src="$build_dir/gamescope"
local gamescope_tag="3.14.24"
local wayland_ver
wayland_ver=$(pkg-config --modversion wayland-server 2>/dev/null || echo "0")
if [[ "$(printf '%s\n' "1.23" "$wayland_ver" | sort -V | head -1)" == "1.23" ]]; then
gamescope_tag=""
info "wayland-server $wayland_ver >= 1.23 - building latest gamescope"
else
info "wayland-server $wayland_ver < 1.23 - pinning to gamescope $gamescope_tag"
fi
info "Cloning gamescope from GitHub..."
if [[ -n "$gamescope_tag" ]]; then
if ! git clone --recursive --branch "$gamescope_tag" --depth 1 https://github.com/ValveSoftware/gamescope.git "$gamescope_src"; then
err "Failed to clone gamescope $gamescope_tag"
rm -rf "$build_dir"
return 1
fi
else
if ! git clone --recursive https://github.com/ValveSoftware/gamescope.git "$gamescope_src"; then
err "Failed to clone gamescope repository"
rm -rf "$build_dir"
return 1
fi
fi
pushd "$gamescope_src" >/dev/null || { rm -rf "$build_dir"; return 1; }
info "Configuring gamescope build..."
if ! meson setup build --prefix=/usr/local --buildtype=release -Dpipewire=enabled; then
err "Meson configure failed"
popd >/dev/null || true
rm -rf "$build_dir"
return 1
fi
info "Building gamescope (this may take a few minutes)..."
if ! ninja -C build; then
err "Gamescope build failed"
popd >/dev/null || true
rm -rf "$build_dir"
return 1
fi
info "Installing gamescope..."
if ! sudo ninja -C build install; then
err "Gamescope install failed"
popd >/dev/null || true
rm -rf "$build_dir"
return 1
fi
popd >/dev/null || true
rm -rf "$build_dir"
if command -v gamescope >/dev/null 2>&1 || [[ -x /usr/local/bin/gamescope ]]; then
info "gamescope installed successfully at $(command -v gamescope 2>/dev/null || echo /usr/local/bin/gamescope)"
else
err "gamescope binary not found after installation"
return 1
fi
}
install_chimera_session_scripts() {
echo ""
echo "================================================================"
echo " CHIMERAOS SESSION SCRIPTS (from source)"
echo "================================================================"
echo ""
local install_dir="/usr/share/gamescope-session-plus"
local needs_base=false
local needs_steam=false
if [[ ! -d "$install_dir" ]] || [[ ! -x "$install_dir/gamescope-session-plus" ]]; then
needs_base=true
fi
local -a required_steam_scripts=(
"/usr/bin/steamos-session-select"
"/usr/bin/steamos-update"
"/usr/bin/jupiter-biosupdate"
"/usr/bin/steamos-select-branch"
"$install_dir/sessions.d/steam"
)
for script in "${required_steam_scripts[@]}"; do
if [[ ! -f "$script" ]]; then
needs_steam=true
break
fi
done
if [[ "$needs_base" == "false" && "$needs_steam" == "false" ]]; then
info "ChimeraOS session scripts already installed"
return 0
fi
local build_dir
build_dir=$(mktemp -d)
if $needs_base; then
info "Installing gamescope-session-plus from ChimeraOS..."
if git clone https://github.com/ChimeraOS/gamescope-session.git "$build_dir/gamescope-session"; then
pushd "$build_dir/gamescope-session" >/dev/null || true
sudo mkdir -p "$install_dir"
if [[ -f "gamescope-session-plus" ]]; then
sudo cp gamescope-session-plus "$install_dir/gamescope-session-plus"
sudo chmod 755 "$install_dir/gamescope-session-plus"
elif [[ -f "usr/share/gamescope-session-plus/gamescope-session-plus" ]]; then
sudo cp -r usr/share/gamescope-session-plus/* "$install_dir/"
sudo chmod 755 "$install_dir/gamescope-session-plus"
else
find . -name "gamescope-session-plus" -type f | while read -r f; do
sudo cp "$f" "$install_dir/gamescope-session-plus"
sudo chmod 755 "$install_dir/gamescope-session-plus"
done
fi
for f in gamescope-session-plus-*.sh; do
[[ -f "$f" ]] && sudo cp "$f" "$install_dir/" && sudo chmod 755 "$install_dir/$f"
done
popd >/dev/null || true
info "gamescope-session-plus installed"
else
err "Failed to clone gamescope-session repository"
fi
fi
if $needs_steam; then
info "Installing gamescope-session-steam scripts from ChimeraOS..."
if git clone https://github.com/ChimeraOS/gamescope-session-steam.git "$build_dir/gamescope-session-steam"; then
pushd "$build_dir/gamescope-session-steam" >/dev/null || true
sudo mkdir -p "$install_dir/sessions.d"
if [[ -f "usr/share/gamescope-session-plus/sessions.d/steam" ]]; then
sudo cp "usr/share/gamescope-session-plus/sessions.d/steam" "$install_dir/sessions.d/steam"
info "Installed sessions.d/steam config from repo"
else
warn "sessions.d/steam not found in repo - creating manually"
fi
# Always write our own sessions.d/steam with MangoHud fix
# This ensures post_gamescope_start enables the overlay on non-SteamOS
sudo tee "$install_dir/sessions.d/steam" > /dev/null << 'STEAM_SESSION_CONF'
#! /bin/bash
function short_session_recover {
mkdir -p ~/.local/share/Steam
rm -rf --one-file-system ~/.local/share/Steam/config/widevine
steamos-session-select desktop
}
function post_gamescope_start {
# Run steam-tweaks if exists
if command -v steam-tweaks > /dev/null; then
steam-tweaks
fi
# On non-SteamOS, Steam may not update the MangoHud config file.
# Wait for Steam to initialize, then check if the config still has
# 'no_display'. If so, replace it with a default overlay config.
if [[ -n "$MANGOHUD_CONFIGFILE" ]]; then
(sleep 15
if grep -q "no_display" "$MANGOHUD_CONFIGFILE" 2>/dev/null; then
cat > "$MANGOHUD_CONFIGFILE" << 'MHCFG'
fps
cpu_temp
gpu_temp
ram
vram
frametime
MHCFG
fi
) &
fi
}
export STEAM_GAMESCOPE_VRR_SUPPORTED=1
export STEAM_MANGOAPP_PRESETS_SUPPORTED=1
export STEAM_USE_MANGOAPP=1
export STEAM_USE_DYNAMIC_VRS=1
export STEAM_GAMESCOPE_HAS_TEARING_SUPPORT=1
export STEAM_GAMESCOPE_TEARING_SUPPORTED=1
export STEAM_GAMESCOPE_HDR_SUPPORTED=1
export STEAMOS_STEAM_REBOOT_SENTINEL="/tmp/steamos-reboot-sentinel"
export REBOOT_SENTINEL=$STEAMOS_STEAM_REBOOT_SENTINEL
export STEAMOS_STEAM_SHUTDOWN_SENTINEL="/tmp/steamos-shutdown-sentinel"
export SHUTDOWN_SENTINEL=$STEAMOS_STEAM_SHUTDOWN_SENTINEL
export STEAM_ENABLE_VOLUME_HANDLER=1
export SRT_URLOPEN_PREFER_STEAM=1
export STEAM_DISABLE_AUDIO_DEVICE_SWITCHING=1
export STEAM_MULTIPLE_XWAYLANDS=1
export STEAM_GAMESCOPE_DYNAMIC_FPSLIMITER=1
export STEAM_GAMESCOPE_NIS_SUPPORTED=1
export STEAM_ALLOW_DRIVE_UNMOUNT=1
export STEAM_DISABLE_MANGOAPP_ATOM_WORKAROUND=1
export STEAM_MANGOAPP_HORIZONTAL_SUPPORTED=1
export STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1
export STEAM_GAMESCOPE_COLOR_MANAGED=1
export STEAM_GAMESCOPE_VIRTUAL_WHITE=1
export QT_IM_MODULE=steam
export GTK_IM_MODULE=Steam
if [ -f "/usr/share/steamos/steamos.png" ] ; then
export STEAM_UPDATEUI_PNG_BACKGROUND=/usr/share/steamos/steamos.png
fi
export CURSOR_FILE="${HOME}/.local/share/Steam/tenfoot/resource/images/cursors/arrow.png"
export CLIENTCMD="steam -gamepadui -steamos3 -steampal -steamdeck"
touch "${HOME}"/.steam/root/config/SteamAppData.vdf || true
if [[ -f "/etc/first-boot/bootstraplinux_ubuntu12_32.tar.xz" ]] && ! grep -q "set_bootstrap=1" "$STEAM_BOOTSTRAP_CONFIG"; then
mkdir -p ~/.local/share/Steam
tar xf /etc/first-boot/bootstraplinux_ubuntu12_32.tar.xz -C ~/.local/share/Steam
echo "set_bootstrap=1" >> "$STEAM_BOOTSTRAP_CONFIG"
fi
# If we have steam_notif_daemon binary start it
if command -v steam_notif_daemon > /dev/null; then
steam_notif_daemon &
fi
STEAM_SESSION_CONF
sudo chmod 755 "$install_dir/sessions.d/steam"
info "Installed sessions.d/steam config with MangoHud overlay fix"
# Install Steam compatibility stubs
for script_name in steamos-session-select steamos-update jupiter-biosupdate steamos-select-branch; do
local src_file=""
for candidate in "usr/bin/$script_name" "$script_name" "bin/$script_name"; do
if [[ -f "$candidate" ]]; then
src_file="$candidate"
break
fi
done
if [[ -n "$src_file" ]]; then
sudo cp "$src_file" "/usr/bin/$script_name"
sudo chmod 755 "/usr/bin/$script_name"
info "Installed /usr/bin/$script_name"
else
info "Creating stub for $script_name..."
case "$script_name" in
steamos-session-select)
sudo tee "/usr/bin/$script_name" > /dev/null << 'STUB_SELECT'
#!/bin/bash
# ChimeraOS compatibility stub for Steam (Fedora KDE)
case "$1" in
desktop)
exec /usr/lib/os-session-select
;;
gamescope)
echo "Already in gamescope session"
;;
*)
echo "Usage: $0 {desktop|gamescope}"
;;
esac
STUB_SELECT
;;
steamos-update)
sudo tee "/usr/bin/$script_name" > /dev/null << 'STUB_UPDATE'
#!/bin/bash
# ChimeraOS compatibility stub - no SteamOS updates on Fedora
echo "System updates are managed through Discover or dnf"
exit 0
STUB_UPDATE
;;
jupiter-biosupdate)
sudo tee "/usr/bin/$script_name" > /dev/null << 'STUB_BIOS'
#!/bin/bash
# ChimeraOS compatibility stub - not applicable on desktop
exit 0
STUB_BIOS
;;
steamos-select-branch)
sudo tee "/usr/bin/$script_name" > /dev/null << 'STUB_BRANCH'
#!/bin/bash
# ChimeraOS compatibility stub
echo "Branch selection not available on Fedora"
exit 0
STUB_BRANCH
;;
esac
sudo chmod 755 "/usr/bin/$script_name"
fi
done
for f in gamescope-session-steam*.sh; do
[[ -f "$f" ]] && sudo cp "$f" "$install_dir/" && sudo chmod 755 "$install_dir/$f"
done
popd >/dev/null || true
info "gamescope-session-steam scripts installed"
else
err "Failed to clone gamescope-session-steam repository"
fi
fi
rm -rf "$build_dir"
}
setup_requirements() {
local -a required_packages=("steam" "libcap" "gamemode" "curl" "pciutils" "ntfs-3g")
local -a packages_to_install=()
for pkg in "${required_packages[@]}"; do
check_package "$pkg" || packages_to_install+=("$pkg")
done
if ((${#packages_to_install[@]})); then
info "The following packages are required: ${packages_to_install[*]}"
read -p "Install missing packages? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
if ! sudo dnf install -y "${packages_to_install[@]}"; then
warn "Initial install failed - attempting recovery..."
if ! dnf_fix_and_retry "${packages_to_install[@]}"; then
local -a still_needed=()
for pkg in "${packages_to_install[@]}"; do
check_package "$pkg" || still_needed+=("$pkg")
done
if ((${#still_needed[@]})); then
err "Could not install: ${still_needed[*]}"
read -p "Continue anyway? [y/N]: " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || die "Required packages missing - cannot continue"
fi
fi
fi
else
die "Required packages missing - cannot continue"
fi
else
info "All required packages present."
fi
setup_performance_permissions
setup_shader_cache
# Build/install gamescope from source
install_gamescope_from_source
# Install mangoapp (try repos first, fallback to source build)
if ! command -v mangoapp >/dev/null 2>&1 && ! [[ -x /usr/local/bin/mangoapp ]]; then
install_mangoapp
fi
# Install ChimeraOS session scripts from source
install_chimera_session_scripts
# Install Proton GE for better game compatibility
install_proton_ge
local gamescope_bin=""
if command -v gamescope >/dev/null 2>&1; then
gamescope_bin="$(command -v gamescope)"
elif [[ -x /usr/local/bin/gamescope ]]; then
gamescope_bin="/usr/local/bin/gamescope"
fi
if [[ "${PERFORMANCE_MODE,,}" == "enabled" ]] && [[ -n "$gamescope_bin" ]]; then
if ! getcap "$gamescope_bin" 2>/dev/null | grep -q 'cap_sys_nice'; then
echo ""
echo "================================================================"
echo " GAMESCOPE CAPABILITY REQUEST"
echo "================================================================"
echo ""
echo " Performance mode requires granting cap_sys_nice to gamescope."
echo ""
read -p "Grant cap_sys_nice to gamescope? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
sudo setcap 'cap_sys_nice=eip' "$gamescope_bin" || warn "Failed to set capability"
info "Capability granted to gamescope"
fi
fi
fi
}
# Detect the KDE Plasma session name from available .desktop files
detect_kde_session_name() {
local session_name="plasma"
# Check wayland sessions first (preferred)
if [[ -f /usr/share/wayland-sessions/plasma.desktop ]]; then
session_name="plasma"
elif [[ -f /usr/share/wayland-sessions/plasmawayland.desktop ]]; then
session_name="plasmawayland"
# Fallback to X11
elif [[ -f /usr/share/xsessions/plasma.desktop ]]; then
session_name="plasma"
elif [[ -f /usr/share/xsessions/plasmax11.desktop ]]; then
session_name="plasmax11"
fi
echo "$session_name"
}
setup_kde_shortcut() {
local user_home="$1"
local current_user="${SUDO_USER:-$USER}"
info "Setting up KDE Plasma global shortcut for Gaming Mode..."
# Create a .desktop file for the switch-to-gaming action
local desktop_dir="/usr/share/applications"
local desktop_file="$desktop_dir/switch-to-gaming.desktop"
sudo tee "$desktop_file" > /dev/null << 'DESKTOP_ENTRY'
[Desktop Entry]
Type=Application
Name=Switch to Gaming Mode
Comment=Switch from KDE Plasma to Gaming Mode (Gamescope)
Exec=/usr/local/bin/switch-to-gaming
Icon=input-gaming
NoDisplay=true
Terminal=false
Categories=Game;
X-KDE-Shortcuts=Meta+Alt+G
DESKTOP_ENTRY
sudo chmod 644 "$desktop_file"
info "Created $desktop_file"
# Update the desktop database so KDE discovers the new .desktop file
if command -v update-desktop-database >/dev/null 2>&1; then
sudo update-desktop-database "$desktop_dir" 2>/dev/null || true
fi
# Register the global shortcut in KDE's kglobalshortcutsrc
# This matches the working CachyOS approach exactly:
# 1. kwriteconfig to write the shortcut to the config file
# 2. kbuildsycoca to discover the .desktop file
# 3. Tell KWin to reconfigure (picks up the new shortcut)
local kwriteconfig=""
if command -v kwriteconfig6 >/dev/null 2>&1; then
kwriteconfig="kwriteconfig6"
elif command -v kwriteconfig5 >/dev/null 2>&1; then
kwriteconfig="kwriteconfig5"
fi
if [[ -n "$kwriteconfig" ]]; then
sudo -u "$current_user" "$kwriteconfig" --file kglobalshortcutsrc \
--group "switch-to-gaming.desktop" \
--key "_k_friendly_name" "Switch to Gaming Mode"
sudo -u "$current_user" "$kwriteconfig" --file kglobalshortcutsrc \
--group "switch-to-gaming.desktop" \
--key "_launch" "Meta+Alt+G,none,Switch to Gaming Mode"
info "Registered shortcut via $kwriteconfig"
else
# Fallback: manually append to kglobalshortcutsrc
local shortcuts_file="${user_home}/.config/kglobalshortcutsrc"
if [[ -f "$shortcuts_file" ]]; then
if grep -q "\[switch-to-gaming.desktop\]" "$shortcuts_file" 2>/dev/null; then
info "Gaming Mode shortcut already exists in kglobalshortcutsrc"
fi
fi
# Ensure file ends with a newline before appending
if [[ -f "$shortcuts_file" ]] && [[ -s "$shortcuts_file" ]]; then
[[ "$(tail -c1 "$shortcuts_file")" != "" ]] && echo "" >> "$shortcuts_file"
fi
cat >> "$shortcuts_file" << 'KDE_SHORTCUT'
[switch-to-gaming.desktop]
_k_friendly_name=Switch to Gaming Mode
_launch=Meta+Alt+G,none,Switch to Gaming Mode
KDE_SHORTCUT
info "Added Super+Alt+G shortcut to KDE global shortcuts (manual fallback)"
fi
# Rebuild KDE's sycoca cache so it picks up the new .desktop file and shortcut
if command -v kbuildsycoca6 >/dev/null 2>&1; then
sudo -u "$current_user" kbuildsycoca6 2>/dev/null || true
elif command -v kbuildsycoca5 >/dev/null 2>&1; then
sudo -u "$current_user" kbuildsycoca5 2>/dev/null || true
fi
# Tell KWin to reconfigure so it picks up the new shortcut immediately
# On Fedora Plasma 6, kglobalaccel is embedded in KWin, so we reconfigure KWin
if command -v dbus-send >/dev/null 2>&1; then
sudo -u "$current_user" dbus-send --session --type=method_call \
--dest=org.kde.KWin /KWin org.kde.KWin.reconfigure 2>/dev/null || true
info "Sent reconfigure signal to KWin"
fi
info "KDE shortcut configuration complete"
echo " NOTE: You MUST log out and back in for the shortcut to activate."
echo " You can also set it manually in System Settings > Shortcuts."
NEEDS_RELOGIN=1
}
setup_session_switching() {
echo ""
echo "================================================================"
echo " SESSION SWITCHING SETUP (KDE Plasma <-> Gamescope)"
echo " Using ChimeraOS gamescope-session + plasmalogin"
echo "================================================================"
echo ""
# Intel-only check
if check_intel_only; then
echo ""
echo " NO DICE - INTEL ONLY DETECTED"
echo ""
err "This setup does not support Intel GPUs (iGPU or Arc)."
echo ""
echo " Gaming Mode requires AMD or NVIDIA graphics."
echo ""
exit 1
fi
echo " This will:"
echo " - Configure session switching between KDE Plasma and Gaming Mode"
echo " - Configure Super+Alt+G to switch to Gaming Mode"
echo " - Configure Steam's 'Exit to Desktop' to return to KDE Plasma"
echo ""
read -p "Set up session switching? [Y/n]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
info "Skipping session switching setup"
return 0
fi
local current_user="${SUDO_USER:-$USER}"
local user_home
user_home=$(getent passwd "$current_user" | cut -d: -f6)
[[ -d "$user_home" ]] || user_home="$HOME"
local monitor_width=1920
local monitor_height=1080
local monitor_refresh=60
local monitor_output=""
local -a dgpu_monitors=()
local dgpu_card=""
local dgpu_type=""
detect_dgpu_monitors dgpu_monitors dgpu_card dgpu_type
if [[ -z "$dgpu_card" ]]; then
if [[ "$dgpu_type" == "NVIDIA" ]]; then
warn "NVIDIA GPU detected but no DRM card found!"
echo ""
echo " This usually means nvidia-drm.modeset=1 is not set."
echo " Continuing setup with defaults (2560x1440@60 on NVIDIA)."
echo " A REBOOT will be required for full functionality."
echo ""
NEEDS_REBOOT=1
# Use safe NVIDIA defaults so session switching config is still created
dgpu_card="card0"
monitor_width=2560
monitor_height=1440
monitor_refresh=60
fi
if [[ "$dgpu_type" != "NVIDIA" ]]; then
# No dGPU and not NVIDIA - check for APU
local apu_card=""
local apu_monitors=()
local card_name driver_link driver conn_dir conn_name status resolution mode_file
for card_path in /sys/class/drm/card[0-9]*; do
card_name=$(basename "$card_path")
[[ "$card_name" == render* ]] && continue
driver_link="$card_path/device/driver"
[[ -L "$driver_link" ]] || continue
driver=$(basename "$(readlink "$driver_link")")
if [[ "$driver" == "amdgpu" ]] && is_amd_igpu_card "$card_path"; then
apu_card="$card_name"
for connector in "$card_path"/"$card_name"-*/status; do
[[ -f "$connector" ]] || continue
conn_dir=$(dirname "$connector")
conn_name=$(basename "$conn_dir")
conn_name=${conn_name#card*-}
[[ "$conn_name" == Writeback* ]] && continue
status=$(cat "$connector" 2>/dev/null)
if [[ "$status" == "connected" ]]; then
resolution=""
mode_file="$conn_dir/modes"
[[ -f "$mode_file" ]] && [[ -s "$mode_file" ]] && resolution=$(head -1 "$mode_file" 2>/dev/null)
apu_monitors+=("$conn_name|$resolution")
fi
done
break
fi
done
if [[ -n "$apu_card" && ${#apu_monitors[@]} -gt 0 ]]; then
echo ""
info "No discrete GPU found, but detected AMD APU ($apu_card)"
echo ""
echo " This system has an AMD APU which can run Gaming Mode."
echo " Detected monitors: ${#apu_monitors[@]}"
echo ""
read -p " Set up Gaming Mode for APU? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
dgpu_card="$apu_card"
dgpu_type="AMD APU"
dgpu_monitors=("${apu_monitors[@]}")
info "Configuring Gaming Mode for AMD APU"
else
info "Skipping APU Gaming Mode setup"
return 0
fi
else
err "No discrete GPU (dGPU) or AMD APU found!"
echo " Gaming mode requires a supported GPU with a connected display."
return 1
fi
fi
fi
info "Found $dgpu_type on $dgpu_card"
if [[ ${#dgpu_monitors[@]} -eq 0 ]]; then
if [[ "$dgpu_type" == "NVIDIA" && "$NEEDS_REBOOT" -eq 1 ]]; then
info "No monitors detected on NVIDIA (nvidia-drm.modeset=1 not yet active)"
info "Using defaults: 2560x1440@60 — will auto-detect after reboot"
else
err "No monitors connected to dGPU!"
echo ""
echo " Gaming mode requires a monitor connected to the discrete GPU."
echo " Please connect an external monitor to your dGPU port (HDMI/DP/USB-C)"
echo " and re-run this installer."
echo ""
return 1
fi
fi
if [[ ${#dgpu_monitors[@]} -eq 1 ]]; then
local entry="${dgpu_monitors[0]}"
monitor_output="${entry%%|*}"
local res="${entry##*|}"
if [[ -n "$res" ]]; then
monitor_width="${res%%x*}"
monitor_height="${res##*x}"
monitor_height="${monitor_height%%@*}"
[[ "$res" == *@* ]] && monitor_refresh="${res##*@}" && monitor_refresh="${monitor_refresh%%.*}"
fi
else
echo ""
echo " Multiple monitors connected to $dgpu_type:"
local i=1
for entry in "${dgpu_monitors[@]}"; do
local name="${entry%%|*}"
local res="${entry##*|}"
echo " $i) $name ${res:+($res)}"
((i++))
done
echo ""
read -r -p "Select monitor for Gaming Mode [1-${#dgpu_monitors[@]}]: " selection
if [[ ! "$selection" =~ ^[0-9]+$ ]] || ((selection < 1 || selection > ${#dgpu_monitors[@]})); then
selection=1
fi
local entry="${dgpu_monitors[$((selection-1))]}"
monitor_output="${entry%%|*}"
local res="${entry##*|}"
if [[ -n "$res" ]]; then
monitor_width="${res%%x*}"
monitor_height="${res##*x}"
monitor_height="${monitor_height%%@*}"
[[ "$res" == *@* ]] && monitor_refresh="${res##*@}" && monitor_refresh="${monitor_refresh%%.*}"
fi
fi
info "Selected dGPU display: ${monitor_output} (${monitor_width}x${monitor_height}@${monitor_refresh}Hz)"
info "Checking for old custom session files to clean up..."
local -a old_files=(
"/usr/bin/gamescope-session"
"/usr/share/wayland-sessions/gamescope-session.desktop"
"/usr/bin/jupiter-biosupdate"
"/usr/bin/steamos-update"
"/usr/bin/steamos-select-branch"
"/usr/bin/steamos-session-select"
)
local cleaned=false
for old_file in "${old_files[@]}"; do
if [[ -f "$old_file" ]]; then
info "Removing old file: $old_file"
sudo rm -f "$old_file" && cleaned=true
fi
done
if $cleaned; then
info "Old custom session files removed"
else
info "No old files to clean up"
fi
# Re-install ChimeraOS session scripts (they were cleaned above)
install_chimera_session_scripts
# NetworkManager - ensure it's enabled (Fedora uses NM by default)
info "Verifying NetworkManager is enabled..."
if systemctl is-active --quiet NetworkManager.service; then
info "NetworkManager is running"
else
warn "NetworkManager is not running - enabling..."
sudo systemctl enable --now NetworkManager.service 2>/dev/null || warn "Failed to start NetworkManager"
fi
# NM start/stop scripts (Fedora: NM is always running)
local nm_start_script="/usr/local/bin/gamescope-nm-start"
sudo tee "$nm_start_script" > /dev/null << 'NM_START'
#!/bin/bash
# On Fedora, NetworkManager is always running.
LOG_TAG="gamescope-nm"
log() { logger -t "$LOG_TAG" "$*"; echo "$*"; }
if systemctl is-active --quiet NetworkManager.service; then
log "NetworkManager is running"
else
log "Starting NetworkManager service..."
systemctl start NetworkManager.service
if [ $? -eq 0 ]; then
log "NetworkManager started successfully"
else
log "ERROR: Failed to start NetworkManager"
exit 1
fi
for i in {1..20}; do
if nmcli general status &>/dev/null; then
log "NetworkManager ready after ${i} attempts"
break
fi
sleep 0.5
done
fi
nmcli general status 2>/dev/null || log "WARNING: nmcli status check failed"
NM_START
sudo chmod +x "$nm_start_script"
local nm_stop_script="/usr/local/bin/gamescope-nm-stop"
sudo tee "$nm_stop_script" > /dev/null << 'NM_STOP'
#!/bin/bash
# On Fedora, we do NOT stop NetworkManager - it manages all networking.
LOG_TAG="gamescope-nm"
log() { logger -t "$LOG_TAG" "$*"; echo "$*"; }
log "Gaming session ended - NetworkManager remains active (Fedora default)"
NM_STOP
sudo chmod +x "$nm_stop_script"
info "Created NetworkManager start/stop scripts"
# Steam library mount script
local steam_mount_script="/usr/local/bin/steam-library-mount"
info "Creating Steam library drive mount script..."
sudo tee "$steam_mount_script" > /dev/null << 'STEAM_MOUNT'
#!/bin/bash
LOG_TAG="steam-library-mount"
MOUNT_BASE="/run/media/$USER"
log() { logger -t "$LOG_TAG" "$*"; }
check_steam_library() {
local mount_point="$1"
if [[ -d "$mount_point/steamapps" ]] || \
[[ -d "$mount_point/SteamLibrary/steamapps" ]] || \
[[ -d "$mount_point/SteamLibrary" ]] || \
[[ -f "$mount_point/libraryfolder.vdf" ]] || \
[[ -f "$mount_point/steamapps/libraryfolder.vdf" ]] || \
[[ -f "$mount_point/SteamLibrary/libraryfolder.vdf" ]]; then
return 0
fi
return 1
}
handle_device() {
local device="$1"
local part_name
part_name=$(basename "$device")
log "Checking device: $device"
if findmnt -n "$device" &>/dev/null; then
local existing_mount
existing_mount=$(findmnt -n -o TARGET "$device" 2>/dev/null)
if [[ -n "$existing_mount" ]] && check_steam_library "$existing_mount"; then
log "Steam library already mounted at $existing_mount"
else
log "Device $device mounted at $existing_mount (no Steam library)"
fi
return
fi
[[ "$device" =~ [0-9]$ ]] || { log "Skipping whole disk: $device"; return; }
local fstype
fstype=$(lsblk -n -o FSTYPE --nodeps "$device" 2>/dev/null)
case "$fstype" in
ext4|ext3|ext2|btrfs|xfs|ntfs|vfat|exfat|f2fs) ;;
crypto_LUKS) log "Skipping encrypted: $device"; return ;;
swap) log "Skipping swap: $device"; return ;;
"") log "Skipping $device - no filesystem"; return ;;
*) log "Skipping $device - unsupported filesystem: $fstype"; return ;;
esac
if ! command -v udisksctl &>/dev/null; then
log "udisksctl not found - cannot mount $device"
return
fi
log "Attempting to mount $device..."
local mount_output
mount_output=$(udisksctl mount -b "$device" --no-user-interaction 2>&1)
local mount_rc=$?
if [[ $mount_rc -ne 0 ]]; then
log "Could not mount $device: $mount_output"
return
fi
local mount_point
mount_point=$(findmnt -n -o TARGET "$device" 2>/dev/null)
if [[ -z "$mount_point" ]]; then
log "Could not determine mount point for $device"
return
fi
if check_steam_library "$mount_point"; then
log "Steam library found on $device at $mount_point - keeping mounted"
else
log "No Steam library on $device - unmounting"
udisksctl unmount -b "$device" --no-user-interaction 2>/dev/null
fi
}
log "Starting Steam library drive monitor..."
shopt -s nullglob
for dev in /dev/sd*[0-9]* /dev/nvme*p[0-9]*; do
[[ -b "$dev" ]] && handle_device "$dev"
done
shopt -u nullglob
log "Initial device scan complete, watching for new devices..."
udevadm monitor --kernel --subsystem-match=block 2>/dev/null | while read -r line; do
if [[ "$line" =~ ^KERNEL.*[[:space:]]add[[:space:]]+.*/([^/[:space:]]+)[[:space:]]+\(block\)$ ]]; then
dev_name="${BASH_REMATCH[1]}"
dev_path="/dev/$dev_name"
if [[ "$dev_name" =~ [0-9]$ ]] && [[ -b "$dev_path" ]]; then
sleep 1
handle_device "$dev_path"
fi
fi
done
STEAM_MOUNT
sudo chmod +x "$steam_mount_script"
info "Created $steam_mount_script"
# Polkit rules - use 'wheel' group for Fedora
local polkit_rules="/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules"
if [[ -f "$polkit_rules" ]]; then
info "Polkit rules already exist at $polkit_rules"
else
info "Creating Polkit rules for NetworkManager D-Bus access..."
sudo mkdir -p /etc/polkit-1/rules.d
if sudo tee "$polkit_rules" > /dev/null << 'POLKIT_RULES'
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.NetworkManager.enable-disable-network" ||
action.id == "org.freedesktop.NetworkManager.enable-disable-wifi" ||
action.id == "org.freedesktop.NetworkManager.network-control" ||
action.id == "org.freedesktop.NetworkManager.wifi.scan" ||
action.id == "org.freedesktop.NetworkManager.settings.modify.system" ||
action.id == "org.freedesktop.NetworkManager.settings.modify.own" ||
action.id == "org.freedesktop.NetworkManager.settings.modify.hostname") &&
subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
});
POLKIT_RULES
then
sudo chmod 644 "$polkit_rules"
info "Polkit rules created successfully"
sudo systemctl restart polkit.service 2>/dev/null || true
else
err "Failed to create polkit rules file"
fi
fi
local udisks_polkit="/etc/polkit-1/rules.d/50-udisks-gaming.rules"
if [[ -f "$udisks_polkit" ]]; then
info "Udisks2 polkit rules already exist at $udisks_polkit"
else
info "Creating Polkit rules for external drive auto-mount..."
sudo mkdir -p /etc/polkit-1/rules.d
local udisks_exit=0
sudo tee "$udisks_polkit" > /dev/null << 'UDISKS_POLKIT' || udisks_exit=$?
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.udisks2.filesystem-mount" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-system" ||
action.id == "org.freedesktop.udisks2.filesystem-unmount-others" ||
action.id == "org.freedesktop.udisks2.encrypted-unlock" ||
action.id == "org.freedesktop.udisks2.power-off-drive") &&
subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
});
UDISKS_POLKIT
if [[ $udisks_exit -eq 0 ]]; then
sudo chmod 644 "$udisks_polkit"
info "Udisks2 polkit rules created successfully"
sudo systemctl restart polkit.service 2>/dev/null || true
else
err "Failed to create udisks2 polkit rules"
fi
fi
info "Creating gamescope-session-plus configuration..."
local env_dir="${user_home}/.config/environment.d"
local gamescope_conf="${env_dir}/gamescope-session-plus.conf"
sudo -u "$current_user" mkdir -p "$env_dir"
local output_connector=""
[[ -n "$monitor_output" ]] && output_connector="OUTPUT_CONNECTOR=$monitor_output"
local is_nvidia=false
local nvidia_device_id=""
if [[ "$dgpu_type" == "NVIDIA" ]]; then
is_nvidia=true
nvidia_device_id=$(/usr/bin/lspci -nn | grep -i nvidia | grep -oP '\[10de:\K[0-9a-fA-F]+' | head -1)
if [ "$monitor_width" -gt 2560 ]; then
monitor_width=2560
fi
if [ "$monitor_height" -gt 1440 ]; then
monitor_height=1440
fi
fi
if $is_nvidia; then
local vulkan_adapter=""
[[ -n "$nvidia_device_id" ]] && vulkan_adapter="VULKAN_ADAPTER=10de:${nvidia_device_id}"
cat > "$gamescope_conf" << GAMESCOPE_CONF
SCREEN_WIDTH=${monitor_width}
SCREEN_HEIGHT=${monitor_height}
CUSTOM_REFRESH_RATES=${monitor_refresh}
${output_connector}
${vulkan_adapter}
GBM_BACKEND=nvidia-drm
STEAM_ALLOW_DRIVE_UNMOUNT=1
FCITX_NO_WAYLAND_DIAGNOSE=1
SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0
GAMESCOPE_CONF
else
cat > "$gamescope_conf" << GAMESCOPE_CONF
SCREEN_WIDTH=${monitor_width}
SCREEN_HEIGHT=${monitor_height}
CUSTOM_REFRESH_RATES=${monitor_refresh}
${output_connector}
ADAPTIVE_SYNC=1
ENABLE_GAMESCOPE_HDR=1
STEAM_ALLOW_DRIVE_UNMOUNT=1
FCITX_NO_WAYLAND_DIAGNOSE=1
SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0
GAMESCOPE_CONF
fi
chown "$current_user":"$current_user" "$gamescope_conf" 2>/dev/null || true
info "Created $gamescope_conf"
info "Creating NVIDIA gamescope wrapper..."
local nvidia_wrapper_dir="/usr/local/lib/gamescope-nvidia"
local nvidia_wrapper="${nvidia_wrapper_dir}/gamescope"
sudo mkdir -p "$nvidia_wrapper_dir"
local gamescope_real="/usr/local/bin/gamescope"
[[ -x "$gamescope_real" ]] || gamescope_real="/usr/bin/gamescope"
sudo tee "$nvidia_wrapper" > /dev/null << NVIDIA_WRAPPER
#!/bin/bash
EXTRA_ARGS=""
if ${gamescope_real} --help 2>&1 | grep -q "force-composition"; then
EXTRA_ARGS="--force-composition"
fi
exec ${gamescope_real} \$EXTRA_ARGS "\$@"
NVIDIA_WRAPPER
sudo chmod +x "$nvidia_wrapper"
info "Created $nvidia_wrapper"
info "Creating NetworkManager session wrapper..."
local nm_wrapper="/usr/local/bin/gamescope-session-nm-wrapper"
sudo tee "$nm_wrapper" > /dev/null << 'NM_WRAPPER'
#!/bin/bash
log() { logger -t gamescope-wrapper "$*"; echo "$*"; }
# Save original values for restore on exit
ORIG_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null || echo "powersave")
ORIG_EPP=$(cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference 2>/dev/null || echo "balance_performance")
ORIG_SPLIT_LOCK=$(sysctl -n kernel.split_lock_mitigate 2>/dev/null || echo "")
ORIG_MAX_MAP=$(sysctl -n vm.max_map_count 2>/dev/null || echo "1048576")
enable_performance_mode() {
log "Enabling performance mode..."
# Set CPU governor to performance
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance > "$gov" 2>/dev/null && log "CPU governor set to performance"
break
done
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance > "$gov" 2>/dev/null
done
# AMD pstate: set energy performance preference to performance
for epp in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
if [[ -w "$epp" ]]; then
echo performance > "$epp" 2>/dev/null
fi
done
if [[ -f /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference ]]; then
log "AMD pstate EPP set to performance"
fi
# Kernel tuning: disable split lock mitigation (prevents stalls in some Proton games)
[[ -n "$ORIG_SPLIT_LOCK" ]] && sudo -n /usr/bin/sysctl -w kernel.split_lock_mitigate=0 2>/dev/null && log "Split lock mitigation disabled"
# Increase max memory map areas (prevents crashes in heavy Proton titles)
sudo -n /usr/bin/sysctl -w vm.max_map_count=2147483642 2>/dev/null && log "vm.max_map_count increased"
# Disable proactive memory compaction (reduces latency spikes)
sudo -n /usr/bin/sysctl -w vm.compaction_proactiveness=0 2>/dev/null && log "Proactive compaction disabled"
# Disable NMI watchdog (frees a hardware perf counter)
sudo -n /usr/bin/sysctl -w kernel.nmi_watchdog=0 2>/dev/null && log "NMI watchdog disabled"
# Set transparent hugepages to madvise (avoids background compaction stalls)
echo madvise | sudo -n /usr/bin/tee /sys/kernel/mm/transparent_hugepage/enabled >/dev/null 2>&1 && log "THP set to madvise"
# NVIDIA dGPU performance mode
if command -v nvidia-smi &>/dev/null; then
sudo -n /usr/bin/nvidia-smi -pm 1 2>/dev/null && log "NVIDIA persistence mode enabled"
local max_power
max_power=$(nvidia-smi --query-gpu=power.max_limit --format=csv,noheader,nounits 2>/dev/null | head -1 | cut -d'.' -f1)
if [[ -n "$max_power" && "$max_power" -gt 0 ]]; then
sudo -n /usr/bin/nvidia-smi -pl "$max_power" 2>/dev/null && log "NVIDIA power limit set to ${max_power}W"
fi
for nvidia_pci in /sys/bus/pci/devices/*/power/control; do
if [[ -f "${nvidia_pci%/power/control}/driver" ]]; then
local drv=$(basename "$(readlink -f "${nvidia_pci%/power/control}/driver")" 2>/dev/null)
if [[ "$drv" == "nvidia" ]]; then
echo on > "$nvidia_pci" 2>/dev/null && log "NVIDIA runtime suspend disabled"
fi
fi
done
fi
if command -v powerprofilesctl &>/dev/null; then
powerprofilesctl set performance 2>/dev/null && log "Power profile set to performance"
fi
}
restore_balanced_mode() {
log "Restoring balanced mode..."
# Restore CPU governor to original
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo "${ORIG_GOV:-powersave}" > "$gov" 2>/dev/null
done
# AMD pstate: restore energy performance preference
for epp in /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference; do
if [[ -w "$epp" ]]; then
echo "${ORIG_EPP:-balance_performance}" > "$epp" 2>/dev/null
fi
done
# Restore kernel tuning to original values
[[ -n "${ORIG_SPLIT_LOCK:-}" ]] && sudo -n /usr/bin/sysctl -w kernel.split_lock_mitigate="${ORIG_SPLIT_LOCK}" 2>/dev/null
sudo -n /usr/bin/sysctl -w vm.max_map_count="${ORIG_MAX_MAP:-1048576}" 2>/dev/null
sudo -n /usr/bin/sysctl -w vm.compaction_proactiveness=20 2>/dev/null
sudo -n /usr/bin/sysctl -w kernel.nmi_watchdog=1 2>/dev/null
echo always | sudo -n /usr/bin/tee /sys/kernel/mm/transparent_hugepage/enabled >/dev/null 2>&1
if command -v nvidia-smi &>/dev/null; then
local default_power
default_power=$(nvidia-smi --query-gpu=power.default_limit --format=csv,noheader,nounits 2>/dev/null | head -1 | cut -d'.' -f1)
if [[ -n "$default_power" && "$default_power" -gt 0 ]]; then
sudo -n /usr/bin/nvidia-smi -pl "$default_power" 2>/dev/null
fi
for nvidia_pci in /sys/bus/pci/devices/*/power/control; do
if [[ -f "${nvidia_pci%/power/control}/driver" ]]; then
local drv=$(basename "$(readlink -f "${nvidia_pci%/power/control}/driver")" 2>/dev/null)
if [[ "$drv" == "nvidia" ]]; then
echo auto > "$nvidia_pci" 2>/dev/null
fi
fi
done
sudo -n /usr/bin/nvidia-smi -pm 0 2>/dev/null
fi
if command -v powerprofilesctl &>/dev/null; then
powerprofilesctl set balanced 2>/dev/null
fi
log "Balanced mode restored"
}
cleanup() {
pkill -f steam-library-mount 2>/dev/null || true
sudo -n /usr/local/bin/gamescope-nm-stop 2>/dev/null || true
restore_balanced_mode
# Unmask sleep targets that were masked during switch-to-gaming
sudo -n /usr/bin/systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null || true
# Re-enable Bluetooth
sudo -n /usr/bin/rfkill unblock bluetooth 2>/dev/null || true
sudo -n /usr/bin/systemctl start bluetooth.service 2>/dev/null || true
# Unmask drkonqi crash reporter
systemctl --user unmask drkonqi-coredump-launcher@.service 2>/dev/null || true
rm -f /tmp/.gaming-session-active
}
trap cleanup EXIT INT TERM
# Mask drkonqi crash reporter during gaming session
# Gaming processes crash harmlessly on shutdown — no need for crash dialogs
systemctl --user mask drkonqi-coredump-launcher@.service 2>/dev/null || true
log "Masked drkonqi crash reporter for gaming session"
enable_performance_mode
if /usr/bin/lspci 2>/dev/null | grep -qi nvidia; then
export PATH="/usr/local/lib/gamescope-nvidia:$PATH"
# Use the NVIDIA wrapper which adds --force-composition
if [[ -x /usr/local/lib/gamescope-nvidia/gamescope ]]; then
export GAMESCOPE_BIN="/usr/local/lib/gamescope-nvidia/gamescope"
log "Using NVIDIA gamescope wrapper at $GAMESCOPE_BIN"
elif [[ -x /usr/local/bin/gamescope ]]; then
export GAMESCOPE_BIN="/usr/local/bin/gamescope"
log "Warning: NVIDIA wrapper not found, using gamescope directly at $GAMESCOPE_BIN"
fi
elif [[ -x /usr/local/bin/gamescope ]]; then
export GAMESCOPE_BIN="/usr/local/bin/gamescope"
log "Using gamescope at $GAMESCOPE_BIN"
fi
sudo -n /usr/local/bin/gamescope-nm-start 2>/dev/null || {
log "Warning: Could not run NM start script"
}
if [[ -x /usr/local/bin/steam-library-mount ]]; then
/usr/local/bin/steam-library-mount &
log "Steam library drive monitor started"
else
log "Warning: steam-library-mount not found"
fi
echo "gamescope" > /tmp/.gaming-session-active
export QT_IM_MODULE=steam
export GTK_IM_MODULE=Steam
export STEAM_DISABLE_AUDIO_DEVICE_SWITCHING=1
export STEAM_ENABLE_VOLUME_HANDLER=1
/usr/share/gamescope-session-plus/gamescope-session-plus steam
rc=$?
exit $rc
NM_WRAPPER
sudo chmod +x "$nm_wrapper"
info "Created $nm_wrapper"
info "Creating wayland session entry..."
local session_desktop="/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"
sudo mkdir -p /usr/share/wayland-sessions
sudo tee "$session_desktop" > /dev/null << 'SESSION_DESKTOP'
[Desktop Entry]
Name=Gaming Mode (ChimeraOS)
Comment=Steam Big Picture with ChimeraOS gamescope-session
Exec=/usr/local/bin/gamescope-session-nm-wrapper
Type=Application
DesktopNames=gamescope
SESSION_DESKTOP
info "Created $session_desktop"
info "Creating session-select script..."
local os_session_select="/usr/lib/os-session-select"
sudo tee "$os_session_select" > /dev/null << 'OS_SESSION_SELECT'
#!/bin/bash
rm -f /tmp/.gaming-session-active
sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || {
echo "Warning: Failed to update session config"
}
timeout 5 steam -shutdown 2>/dev/null || true
sleep 1
nohup sudo -n /usr/bin/systemctl restart plasmalogin &>/dev/null &
disown
exit 0
OS_SESSION_SELECT
sudo chmod +x "$os_session_select"
info "Created $os_session_select"
info "Creating switch-to-gaming script..."
local switch_script="/usr/local/bin/switch-to-gaming"
sudo tee "$switch_script" > /dev/null << 'SWITCH_SCRIPT'
#!/bin/bash
# Inhibit suspend FIRST
sudo -n /usr/bin/systemctl mask --runtime sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null
sudo -n /usr/local/bin/gaming-session-switch gaming 2>/dev/null || {
notify-send -u critical -t 3000 "Gaming Mode" "Failed to update session config" 2>/dev/null || true
}
notify-send -u normal -t 2000 "Gaming Mode" "Switching to Gaming Mode..." 2>/dev/null || true
pkill -TERM gamescope 2>/dev/null || true
pkill -TERM -f gamescope-session 2>/dev/null || true
sleep 1
# Force kill if still running
pgrep -x gamescope >/dev/null 2>&1 && pkill -9 gamescope 2>/dev/null || true
pgrep -f gamescope-session >/dev/null 2>&1 && pkill -9 -f gamescope-session 2>/dev/null || true
sleep 0.5
# Wait for DRM master to be released (critical on NVIDIA)
for i in $(seq 1 20); do
fuser /dev/dri/card* &>/dev/null || break
sleep 0.5
done
sudo -n /usr/bin/chvt 2 2>/dev/null || true
sleep 0.3
sudo -n /usr/bin/systemctl restart plasmalogin
SWITCH_SCRIPT
sudo chmod +x "$switch_script"
info "Created $switch_script"
info "Creating switch-to-desktop script..."
local switch_desktop_script="/usr/local/bin/switch-to-desktop"
sudo tee "$switch_desktop_script" > /dev/null << 'SWITCH_DESKTOP'
#!/bin/bash
if [[ ! -f /tmp/.gaming-session-active ]]; then
exit 0
fi
rm -f /tmp/.gaming-session-active
sudo -n /usr/bin/systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null
sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || true
# Re-enable Bluetooth
sudo -n /usr/bin/rfkill unblock bluetooth 2>/dev/null || true
sudo -n /usr/bin/systemctl start bluetooth.service 2>/dev/null || true
timeout 5 steam -shutdown 2>/dev/null || true
sleep 1
pkill -TERM gamescope 2>/dev/null || true
pkill -TERM -f gamescope-session 2>/dev/null || true
for _ in {1..6}; do
pgrep -x gamescope >/dev/null 2>&1 || break
sleep 0.5
done
if pgrep -x gamescope >/dev/null 2>&1; then
pkill -9 gamescope 2>/dev/null || true
pkill -9 -f gamescope-session 2>/dev/null || true
fi
sleep 2
# Wait for DRM master to be released (critical on NVIDIA)
for i in $(seq 1 20); do
fuser /dev/dri/card* &>/dev/null || break
sleep 0.5
done
sudo -n /usr/bin/chvt 2 2>/dev/null || true
sleep 0.5
sudo -n /usr/bin/systemctl stop plasmalogin 2>/dev/null || true
sleep 1
sudo -n /usr/bin/systemctl start plasmalogin &
disown
exit 0
SWITCH_DESKTOP
sudo chmod +x "$switch_desktop_script"
info "Created $switch_desktop_script"
# Detect the correct KDE session name
local kde_session_name
kde_session_name=$(detect_kde_session_name)
info "Detected KDE session: $kde_session_name"
info "Creating plasmalogin session switching config..."
local plasmalogin_conf="/etc/plasmalogin.conf"
local autologin_user="$current_user"
if [[ -f "$plasmalogin_conf" ]]; then
local existing_user
existing_user=$(sed -n 's/^User=//p' "$plasmalogin_conf" 2>/dev/null | head -1)
[[ -n "$existing_user" ]] && autologin_user="$existing_user"
fi
# Backup existing plasmalogin.conf
if [[ -f "$plasmalogin_conf" ]]; then
sudo cp "$plasmalogin_conf" "${plasmalogin_conf}.bak.gaming-mode"
info "Backed up $plasmalogin_conf to ${plasmalogin_conf}.bak.gaming-mode"
fi
# Update or create the [Autologin] section in plasmalogin.conf
if [[ -f "$plasmalogin_conf" ]] && grep -q '^\[Autologin\]' "$plasmalogin_conf"; then
sudo sed -i "s/^Session=.*/Session=${kde_session_name}/" "$plasmalogin_conf"
if ! grep -q '^User=' "$plasmalogin_conf"; then
sudo sed -i "/^\[Autologin\]/a User=${autologin_user}" "$plasmalogin_conf"
fi
if ! grep -q '^Relogin=' "$plasmalogin_conf"; then
sudo sed -i "/^\[Autologin\]/a Relogin=true" "$plasmalogin_conf"
fi
else
sudo tee "$plasmalogin_conf" > /dev/null << PLASMA_GAMING
[Autologin]
User=${autologin_user}
Session=${kde_session_name}
Relogin=true
PLASMA_GAMING
fi
info "Configured $plasmalogin_conf (default session: $kde_session_name)"
# Ensure user is in the autologin group (required for passwordless login)
if ! groups "$autologin_user" 2>/dev/null | grep -q '\bautologin\b'; then
info "Adding $autologin_user to autologin group for passwordless session switching..."
sudo groupadd -f autologin
sudo usermod -aG autologin "$autologin_user"
info "Added $autologin_user to autologin group"
else
info "User $autologin_user already in autologin group"
fi
info "Creating session switching helper script..."
local session_helper="/usr/local/bin/gaming-session-switch"
sudo tee "$session_helper" > /dev/null << SESSION_HELPER
#!/bin/bash
CONF="/etc/plasmalogin.conf"
if [[ ! -f "\$CONF" ]]; then
echo "Error: Config file not found: \$CONF" >&2
exit 1
fi
case "\$1" in
gaming)
sed -i 's/^Session=.*/Session=gamescope-session-steam-nm/' "\$CONF"
echo "Session set to: gaming mode"
;;
desktop)
sed -i 's/^Session=.*/Session=${kde_session_name}/' "\$CONF"
echo "Session set to: desktop mode (KDE Plasma)"
;;
*)
echo "Usage: \$0 {gaming|desktop}" >&2
exit 1
;;
esac
SESSION_HELPER
sudo chmod +x "$session_helper"
info "Created $session_helper"
local sudoers_session="/etc/sudoers.d/gaming-session-switch"
if [[ -f "$sudoers_session" ]]; then
info "Removing old sudoers rules to update..."
sudo rm -f "$sudoers_session"
fi
info "Creating sudoers rules for session switching..."
sudo mkdir -p /etc/sudoers.d
if sudo tee "$sudoers_session" > /dev/null << 'SUDOERS_SWITCH'
%video ALL=(ALL) NOPASSWD: /usr/local/bin/gaming-session-switch
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart plasmalogin
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop plasmalogin
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl start plasmalogin
%video ALL=(ALL) NOPASSWD: /usr/bin/chvt
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl mask --runtime sleep.target suspend.target hibernate.target hybrid-sleep.target
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target
%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl start NetworkManager.service
%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop NetworkManager.service
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl start bluetooth.service
%video ALL=(ALL) NOPASSWD: /usr/bin/rfkill unblock bluetooth
%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-start
%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-stop
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pm *
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pl *
SUDOERS_SWITCH
then
sudo chmod 0440 "$sudoers_session"
if sudo visudo -c -f "$sudoers_session" >/dev/null 2>&1; then
info "Sudoers rules created and validated successfully"
else
err "Sudoers syntax validation FAILED -- removing broken file"
sudo rm -f "$sudoers_session"
fi
else
err "Failed to create sudoers file"
fi
# SELinux: allow domain mmap for Steam's sandbox (pressure-vessel/bubblewrap)
# Fedora uses SELinux instead of AppArmor.
info "Configuring SELinux permissions for gaming session..."
sudo setsebool -P domain_can_mmap_files 1 2>/dev/null || \
warn "Could not set SELinux boolean domain_can_mmap_files"
# Verify user namespaces are available (they should be on Fedora)
local max_userns
max_userns=$(cat /proc/sys/user/max_user_namespaces 2>/dev/null || echo "0")
if [[ "$max_userns" -lt 1 ]]; then
warn "Unprivileged user namespaces appear disabled - Steam's sandbox may not work"
warn "Try: sudo sysctl -w user.max_user_namespaces=65536"
else
info "Unprivileged user namespaces enabled (max=$max_userns)"
fi
info "SELinux permissions configured for gaming session"
# Suppress drkonqi crash dialogs during gaming session
# Gamescope, bwrap, and wine crash on shutdown (SIGTERM/SIGKILL) which
# triggers KDE's crash reporter with noisy but harmless dialogs.
# We mask drkonqi in the gaming session wrapper and unmask on exit.
info "drkonqi crash reporter will be masked during gaming sessions"
# Set up KDE Plasma keybinding
setup_kde_shortcut "$user_home"
echo ""
echo "================================================================"
echo " SESSION SWITCHING CONFIGURED (ChimeraOS on Fedora KDE)"
echo "================================================================"
echo ""
echo " Usage:"
echo " - Press Super+Alt+G in KDE Plasma to switch to Gaming Mode"
echo " - In Gaming Mode, use Steam > Power > Exit to Desktop to return to KDE Plasma"
echo ""
echo " ChimeraOS scripts installed from source:"
echo " - gamescope-session-plus (base session framework)"
echo " - gamescope-session-steam scripts (Steam session)"
echo ""
echo " Files created/modified:"
echo " - ~/.config/environment.d/gamescope-session-plus.conf"
echo " - /usr/local/bin/gamescope-session-nm-wrapper"
echo " - /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"
echo " - /usr/lib/os-session-select"
echo " - /usr/local/bin/switch-to-gaming"
echo " - /usr/local/bin/switch-to-desktop"
echo " - /usr/share/applications/switch-to-gaming.desktop"
echo " - ~/.config/kglobalshortcutsrc (KDE shortcut added)"
echo " - /etc/plasmalogin.conf (session switching via [Autologin] section)"
echo ""
echo " NetworkManager integration:"
echo " - /usr/local/bin/gamescope-nm-start"
echo " - /usr/local/bin/gamescope-nm-stop"
echo " - /etc/polkit-1/rules.d/50-gamescope-networkmanager.rules"
echo " - /etc/sudoers.d/gaming-session-switch"
echo ""
return 0
}
verify_installation() {
echo ""
echo "================================================================"
echo " GAMING MODE INSTALLATION VERIFICATION (Fedora KDE)"
echo "================================================================"
echo ""
local all_ok=true
local missing_files=()
local permission_issues=()
declare -A expected_files=(
["/usr/local/bin/gamescope-session-nm-wrapper"]="755:ChimeraOS session with NM wrapper"
["/usr/local/lib/gamescope-nvidia/gamescope"]="755:NVIDIA gamescope wrapper (--force-composition)"
["/usr/local/bin/gaming-session-switch"]="755:Session switching helper (gaming/desktop)"
["/usr/lib/os-session-select"]="755:Steam Exit to Desktop handler"
["/usr/local/bin/switch-to-gaming"]="755:KDE Plasma to Gaming Mode switcher"
["/usr/local/bin/switch-to-desktop"]="755:Gaming Mode to Desktop switcher"
["/usr/local/bin/gamescope-nm-start"]="755:NetworkManager start script"
["/usr/local/bin/gamescope-nm-stop"]="755:NetworkManager stop script"
["/usr/local/bin/steam-library-mount"]="755:Steam library drive auto-mount script"
["/usr/bin/steamos-session-select"]="755:Steam compatibility script"
["/usr/bin/steamos-update"]="755:Steam compatibility script"
["/usr/bin/jupiter-biosupdate"]="755:Steam compatibility script"
["/usr/bin/steamos-select-branch"]="755:Steam compatibility script"
["/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"]="644:plasmalogin session entry"
["/usr/share/gamescope-session-plus/gamescope-session-plus"]="755:ChimeraOS session launcher"
["/usr/share/gamescope-session-plus/sessions.d/steam"]="755:Steam session config (CLIENTCMD + env vars)"
["/usr/share/applications/switch-to-gaming.desktop"]="644:KDE shortcut desktop entry"
["/etc/plasmalogin.conf"]="644:plasmalogin session switching config"
["/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules"]="644:Polkit NM rules"
["/etc/polkit-1/rules.d/50-udisks-gaming.rules"]="644:Polkit udisks2 rules (external drive mount)"
["/etc/sudoers.d/gaming-session-switch"]="440:Sudoers rules"
["/etc/udev/rules.d/99-gaming-performance.rules"]="644:Udev performance rules"
["/etc/sudoers.d/gaming-mode-sysctl"]="440:Performance sudoers"
["/etc/security/limits.d/99-gaming-memlock.conf"]="644:Memlock limits"
["/etc/pipewire/pipewire.conf.d/10-gaming-latency.conf"]="644:PipeWire low-latency"
["/etc/environment.d/99-shader-cache.conf"]="644:Shader cache config"
)
echo " FILE STATUS:"
echo " ------------"
echo ""
for file in "${!expected_files[@]}"; do
local expected_perm="${expected_files[$file]%%:*}"
local description="${expected_files[$file]#*:}"
local is_optional=false
[[ "$description" == *"(optional)"* ]] && is_optional=true
if sudo test -f "$file" 2>/dev/null; then
local actual_perm
actual_perm=$(sudo stat -c "%a" "$file" 2>/dev/null)
if [[ "$actual_perm" == "$expected_perm" ]]; then
printf " OK %-55s [%s]\n" "$file" "$actual_perm"
else
printf " !! %-55s [%s] (expected %s)\n" "$file" "$actual_perm" "$expected_perm"
permission_issues+=("$file: has $actual_perm, expected $expected_perm")
all_ok=false
fi
else
if $is_optional; then
printf " -- %-55s [SKIPPED] %s\n" "$file" "(optional)"
else
printf " XX %-55s [MISSING]\n" "$file"
missing_files+=("$file: $description")
all_ok=false
fi
fi
done
echo ""
echo " GAMESCOPE BINARY:"
echo " -----------------"
if command -v gamescope >/dev/null 2>&1; then
echo " OK gamescope found at $(command -v gamescope)"
elif [[ -x /usr/local/bin/gamescope ]]; then
echo " OK gamescope found at /usr/local/bin/gamescope"
else
echo " XX gamescope NOT found (needs to be built from source)"
all_ok=false
fi
echo ""
echo " MANGOAPP (Performance Overlay):"
echo " --------------------------------"
if command -v mangoapp >/dev/null 2>&1; then
echo " OK mangoapp found at $(command -v mangoapp)"
elif [[ -x /usr/local/bin/mangoapp ]]; then
echo " OK mangoapp found at /usr/local/bin/mangoapp"
else
echo " XX mangoapp NOT found (Steam performance overlay will NOT work)"
echo " Fix: Re-run setup to install mangoapp"
all_ok=false
fi
echo ""
echo " PROTON GE:"
echo " ----------"
local verify_steam_root=""
if [[ -d "$HOME/.local/share/Steam" ]]; then
verify_steam_root="$HOME/.local/share/Steam"
elif [[ -L "$HOME/.steam/root" ]]; then
verify_steam_root="$(readlink -f "$HOME/.steam/root")"
elif [[ -d "$HOME/.steam/debian-installation" ]]; then
verify_steam_root="$HOME/.steam/debian-installation"
elif [[ -d "$HOME/.steam/steam" ]]; then
verify_steam_root="$HOME/.steam/steam"
fi
local compat_dir="${verify_steam_root:+$verify_steam_root/compatibilitytools.d}"
local ge_versions=()
if [[ -d "$compat_dir" ]]; then
while IFS= read -r -d '' dir; do
ge_versions+=("$(basename "$dir")")
done < <(find "$compat_dir" -maxdepth 1 -type d -name "GE-Proton*" -print0 2>/dev/null | sort -zV)
fi
if ((${#ge_versions[@]})); then
echo " OK Proton GE installed (${#ge_versions[@]} version(s)):"
for gev in "${ge_versions[@]}"; do
echo " - $gev"
done
else
echo " XX Proton GE NOT installed"
echo " Re-run setup or manually download from:"
echo " https://github.com/GloriousEggroll/proton-ge-custom/releases"
all_ok=false
fi
echo ""
echo " KDE PLASMA SHORTCUT:"
echo " ---------------------"
local kde_shortcuts="$HOME/.config/kglobalshortcutsrc"
if [[ -f "$kde_shortcuts" ]]; then
if grep -q "switch-to-gaming" "$kde_shortcuts" 2>/dev/null; then
echo " OK Gaming Mode shortcut (Super+Alt+G) configured in kglobalshortcutsrc"
else
echo " XX Gaming Mode shortcut NOT found in kglobalshortcutsrc"
echo " You can add it manually: System Settings > Shortcuts > Custom Shortcuts"
all_ok=false
fi
else
echo " !! kglobalshortcutsrc not found - shortcut needs manual setup"
echo " Go to System Settings > Shortcuts to add Super+Alt+G"
fi
echo ""
echo " CHIMERAOS SCRIPTS:"
echo " -------------------"
if [[ -x "/usr/share/gamescope-session-plus/gamescope-session-plus" ]]; then
echo " OK gamescope-session-plus installed"
else
echo " XX gamescope-session-plus NOT installed"
all_ok=false
fi
if [[ -x "/usr/bin/steamos-session-select" ]]; then
echo " OK steamos-session-select installed"
else
echo " XX steamos-session-select NOT installed"
all_ok=false
fi
echo ""
echo " STEAM LIBRARY DRIVE SUPPORT:"
echo " -----------------------------"
if [[ -x "/usr/local/bin/steam-library-mount" ]]; then
echo " OK steam-library-mount script installed"
else
echo " XX steam-library-mount NOT found - external Steam libraries will not auto-mount"
all_ok=false
fi
if check_package "udisks2"; then
echo " OK udisks2 installed (mount backend)"
else
echo " XX udisks2 NOT installed"
all_ok=false
fi
if sudo test -f "/etc/polkit-1/rules.d/50-udisks-gaming.rules" 2>/dev/null; then
echo " OK udisks2 polkit rules configured"
else
echo " XX udisks2 polkit rules NOT found"
all_ok=false
fi
echo ""
echo " EXIT GAMING MODE:"
echo " ------------------"
echo " Use Steam > Power > Exit to Desktop to return to KDE Plasma"
echo ""
echo " USER CONFIG:"
echo " ------------"
local user_conf="$HOME/.config/environment.d/gamescope-session-plus.conf"
if [[ -f "$user_conf" ]]; then
echo " OK gamescope-session-plus.conf exists"
else
echo " XX gamescope-session-plus.conf NOT found"
all_ok=false
fi
echo ""
echo " USER GROUPS:"
echo " ------------"
local user_groups
user_groups=$(groups 2>/dev/null)
for grp in video input wheel; do
if echo "$user_groups" | grep -qw "$grp"; then
printf " OK User is in '%s' group\n" "$grp"
else
printf " XX User is NOT in '%s' group\n" "$grp"
all_ok=false
fi
done
echo ""
echo " SERVICE STATUS:"
echo " ---------------"
echo " NetworkManager: $(systemctl is-active NetworkManager.service 2>/dev/null || echo 'inactive')"
echo " plasmalogin: $(systemctl is-active plasmalogin.service 2>/dev/null || echo 'inactive')"
echo " polkit: $(systemctl is-active polkit.service 2>/dev/null || echo 'inactive')"
echo ""
echo " SUDO PERMISSIONS TEST:"
echo " ----------------------"
if sudo -n true 2>/dev/null; then
echo " OK sudo -n works (passwordless sudo available)"
if sudo -n -l /usr/local/bin/gamescope-nm-start &>/dev/null; then
echo " OK Can run gamescope-nm-start without password"
else
echo " XX Cannot run gamescope-nm-start without password"
all_ok=false
fi
else
echo " ?? sudo -n test skipped (requires recent sudo auth)"
echo " Run: sudo -v && sudo -n -l /usr/local/bin/gamescope-nm-start"
fi
echo ""
echo "================================================================"
if $all_ok; then
echo " ALL CHECKS PASSED - Gaming Mode should work correctly"
else
echo " SOME ISSUES DETECTED"
echo ""
if ((${#missing_files[@]})); then
echo " Missing files (${#missing_files[@]}):"
for f in "${missing_files[@]}"; do
echo " - $f"
done
fi
if ((${#permission_issues[@]})); then
echo ""
echo " Permission issues (${#permission_issues[@]}):"
for p in "${permission_issues[@]}"; do
echo " - $p"
done
fi
echo ""
echo " Re-run the installer to fix these issues."
fi
echo "================================================================"
echo ""
$all_ok && return 0 || return 1
}
ensure_critical_permissions() {
# This function guarantees sudoers and polkit files exist.
if [[ ! -f /etc/sudoers.d/gaming-mode-sysctl ]]; then
info "Creating performance sudoers (safety net)..."
sudo tee /etc/sudoers.d/gaming-mode-sysctl > /dev/null << 'SUDOERS1'
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_autogroup_enabled=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_migration_cost_ns=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_min_granularity_ns=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_latency_ns=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.swappiness=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_ratio=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_background_ratio=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_writeback_centisecs=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_expire_centisecs=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_watches=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_instances=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.file-max=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.rmem_max=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.wmem_max=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.split_lock_mitigate=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.max_map_count=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.compaction_proactiveness=*
%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.nmi_watchdog=*
%video ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/kernel/mm/transparent_hugepage/enabled
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pm *
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pl *
SUDOERS1
sudo chmod 0440 /etc/sudoers.d/gaming-mode-sysctl
if sudo visudo -c -f /etc/sudoers.d/gaming-mode-sysctl >/dev/null 2>&1; then
info "Performance sudoers created and validated"
else
err "Performance sudoers validation failed — removing"
sudo rm -f /etc/sudoers.d/gaming-mode-sysctl
fi
fi
if [[ ! -f /etc/sudoers.d/gaming-session-switch ]]; then
info "Creating session switch sudoers (safety net)..."
sudo tee /etc/sudoers.d/gaming-session-switch > /dev/null << 'SUDOERS2'
%video ALL=(ALL) NOPASSWD: /usr/local/bin/gaming-session-switch
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart plasmalogin
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop plasmalogin
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl start plasmalogin
%video ALL=(ALL) NOPASSWD: /usr/bin/chvt
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl mask --runtime sleep.target suspend.target hibernate.target hybrid-sleep.target
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target
%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl start NetworkManager.service
%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop NetworkManager.service
%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl start bluetooth.service
%video ALL=(ALL) NOPASSWD: /usr/bin/rfkill unblock bluetooth
%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-start
%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-stop
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pm *
%video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pl *
SUDOERS2
sudo chmod 0440 /etc/sudoers.d/gaming-session-switch
if sudo visudo -c -f /etc/sudoers.d/gaming-session-switch >/dev/null 2>&1; then
info "Session switch sudoers created and validated"
else
err "Session switch sudoers validation failed — removing"
sudo rm -f /etc/sudoers.d/gaming-session-switch
fi
fi
if [[ ! -f /etc/polkit-1/rules.d/50-gamescope-networkmanager.rules ]]; then
info "Creating NetworkManager polkit rules (safety net)..."
sudo mkdir -p /etc/polkit-1/rules.d
sudo tee /etc/polkit-1/rules.d/50-gamescope-networkmanager.rules > /dev/null << 'POLKIT1'
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.NetworkManager.enable-disable-network" ||
action.id == "org.freedesktop.NetworkManager.enable-disable-wifi" ||
action.id == "org.freedesktop.NetworkManager.network-control" ||
action.id == "org.freedesktop.NetworkManager.wifi.scan" ||
action.id == "org.freedesktop.NetworkManager.settings.modify.system" ||
action.id == "org.freedesktop.NetworkManager.settings.modify.own" ||
action.id == "org.freedesktop.NetworkManager.settings.modify.hostname") &&
subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
});
POLKIT1
sudo chmod 644 /etc/polkit-1/rules.d/50-gamescope-networkmanager.rules
info "NetworkManager polkit rules created"
fi
if [[ ! -f /etc/polkit-1/rules.d/50-udisks-gaming.rules ]]; then
info "Creating udisks2 polkit rules (safety net)..."
sudo mkdir -p /etc/polkit-1/rules.d
sudo tee /etc/polkit-1/rules.d/50-udisks-gaming.rules > /dev/null << 'POLKIT2'
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.udisks2.filesystem-mount" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-system" ||
action.id == "org.freedesktop.udisks2.filesystem-unmount-others" ||
action.id == "org.freedesktop.udisks2.encrypted-unlock" ||
action.id == "org.freedesktop.udisks2.power-off-drive") &&
subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
});
POLKIT2
sudo chmod 644 /etc/polkit-1/rules.d/50-udisks-gaming.rules
info "Udisks2 polkit rules created"
fi
# Restart polkit if any rules were just created
if [[ -f /etc/polkit-1/rules.d/50-gamescope-networkmanager.rules ]] && \
[[ -f /etc/polkit-1/rules.d/50-udisks-gaming.rules ]]; then
sudo systemctl restart polkit.service 2>/dev/null || true
fi
}
execute_setup() {
sudo -k
sudo -v || die "sudo authentication required"
validate_environment
echo ""
echo "================================================================"
echo " SUPER SHIFT S GAMING MODE INSTALLER v${Super_Shift_S_VERSION}"
echo " Fedora / KDE Plasma / plasmalogin Edition (Nobara)"
echo " Dependencies & GPU Configuration"
echo "================================================================"
echo ""
check_steam_dependencies
check_nvidia_kernel_params
install_nvidia_deckmode_env
setup_requirements
setup_session_switching
# Safety net: ensure critical sudoers and polkit files always exist.
sudo -v 2>/dev/null || {
echo ""
echo " Sudo credentials expired. Please re-enter your password to complete setup."
sudo -v || die "sudo authentication required for final setup"
}
ensure_critical_permissions
if [ "$NEEDS_REBOOT" -eq 1 ]; then
echo ""
echo "================================================================"
echo " IMPORTANT: REBOOT REQUIRED"
echo "================================================================"
echo ""
echo " Boot configuration has been updated (nvidia-drm.modeset=1)."
echo " You MUST reboot for the kernel parameter to take effect."
echo ""
if [ "$NEEDS_RELOGIN" -eq 1 ]; then
echo " Additionally, user groups were updated (video/input/wheel)."
fi
echo ""
read -p "Reboot now? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
info "Rebooting..."
sleep 2
systemctl reboot
else
echo ""
echo " Remember to reboot before continuing!"
echo ""
fi
elif [ "$NEEDS_RELOGIN" -eq 1 ]; then
echo ""
echo "================================================================"
echo " IMPORTANT: LOG OUT REQUIRED"
echo "================================================================"
echo ""
echo " User groups have been updated. You MUST log out and log back in"
echo " for the changes to take effect."
echo ""
read -r -p "Press Enter to exit (remember to log out)..."
else
echo ""
echo "================================================================"
echo " SETUP COMPLETE"
echo "================================================================"
echo ""
echo " Dependencies, GPU configuration, and session switching are ready."
echo ""
echo " To switch to Gaming Mode: Press Super+Alt+G"
echo " To return to Desktop: Steam > Power > Exit to Desktop"
echo ""
fi
echo ""
read -p "Run installation verification? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
verify_installation
fi
}
show_help() {
echo "Super Shift S Gaming Mode Installer v${Super_Shift_S_VERSION}"
echo "Fedora / KDE Plasma / plasmalogin Edition (Nobara)"
echo ""
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --help, -h Show this help message"
echo " --verify, -v Run verification only (check all files and permissions)"
echo " --version Show version number"
echo ""
echo "Without options, runs the full installation/setup process."
echo ""
echo "Adapted for Fedora/KDE Plasma/plasmalogin (Nobara)."
echo "Gamescope is built from source (not available in Fedora repos)."
echo "ChimeraOS session scripts are cloned from GitHub."
echo ""
}
case "${1:-}" in
--help|-h)
show_help
exit 0
;;
--verify|-v)
echo "Running verification only..."
verify_installation
exit $?
;;
--version)
echo "Super Shift S Gaming Mode Installer v${Super_Shift_S_VERSION}"
exit 0
;;
"")
# Run setup; if it fails partway through, the fallback below still creates critical files
execute_setup || true
# Always ensure critical permission files exist, even if setup failed/aborted
sudo -v 2>/dev/null || sudo -v 2>/dev/null || true
ensure_critical_permissions
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information."
exit 1
;;
esac