W.O.P.R/WOPR.sh
2025-11-23 19:43:54 +00:00

1661 lines
59 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -Euo pipefail
TARGET_DIR="$HOME/.local/share/steam-launcher"
SWITCH_BIN="$TARGET_DIR/enter-gamesmode"
RETURN_BIN="$TARGET_DIR/leave-gamesmode"
# Try to find the user's bindings config file
BINDINGS_CONFIG=""
for location in \
"$HOME/.config/hypr/bindings.conf" \
"$HOME/.config/hypr/keybinds.conf" \
"$HOME/.config/hypr/hyprland.conf"; do
if [ -f "$location" ]; then
BINDINGS_CONFIG="$location"
break
fi
done
CONFIG_FILE="/etc/gaming-mode.conf"
[[ -f "$HOME/.gaming-mode.conf" ]] && CONFIG_FILE="$HOME/.gaming-mode.conf"
# shellcheck source=/dev/null
source "$CONFIG_FILE" 2>/dev/null || true
# Set defaults for any unset variables (works even if config partially defines them)
: "${STEAM_LAUNCH_MODE:=bigpicture}"
: "${PERFORMANCE_MODE:=enabled}"
ADDED_BINDINGS=0
CREATED_TARGET_DIR=0
NEEDS_RELOGIN=0
info(){ echo "[*] $*"; }
err(){ echo "[!] $*" >&2; }
die() {
local msg="$1"; local code="${2:-1}"
echo "FATAL: $msg" >&2
logger -t gaming-mode "Installation failed: $msg"
rollback_changes
exit "$code"
}
rollback_changes() {
[ -f "$SWITCH_BIN" ] && rm -f "$SWITCH_BIN"
[ -f "$RETURN_BIN" ] && rm -f "$RETURN_BIN"
if [ "$CREATED_TARGET_DIR" -eq 1 ] && [ -d "$TARGET_DIR" ]; then
rmdir "$TARGET_DIR" 2>/dev/null || true
fi
# Remove added bindings
if [ "$ADDED_BINDINGS" -eq 1 ] && [ -n "$BINDINGS_CONFIG" ] && [ -f "$BINDINGS_CONFIG" ]; then
sed -i '/# Gaming Mode bindings - added by installation script/,/# End Gaming Mode bindings/d' "$BINDINGS_CONFIG"
fi
}
validate_environment() {
command -v pacman >/dev/null || die "pacman required"
command -v hyprctl >/dev/null || die "hyprctl required"
[ -n "$BINDINGS_CONFIG" ] || die "Could not find bindings config file (checked bindings.conf, keybinds.conf, hyprland.conf)"
[ -f "$BINDINGS_CONFIG" ] || die "Bindings config file not found: $BINDINGS_CONFIG"
# Verify Hyprland is actually configured (check if any hypr config exists)
[ -d "$HOME/.config/hypr" ] || die "Hyprland config directory not found (~/.config/hypr)"
}
check_package() { pacman -Qi "$1" &>/dev/null; }
# Helper function: Detect Intel GPU type (xe=Arc, i915=iGPU)
# Returns: "arc", "igpu", "both", or "none"
# Sets global: has_arc, has_intel_igpu
detect_intel_gpu_type() {
has_arc=false
has_intel_igpu=false
for card_device in /sys/class/drm/card*/device/driver; do
[[ -e "$card_device" ]] || continue
if [[ -L "$card_device" ]]; then
local driver_name=$(basename "$(readlink -f "$card_device")" 2>/dev/null)
case "$driver_name" in
xe) has_arc=true ;;
i915) has_intel_igpu=true ;;
esac
fi
done
if $has_arc && $has_intel_igpu; then
echo "both"
elif $has_arc; then
echo "arc"
elif $has_intel_igpu; then
echo "igpu"
else
echo "none"
fi
}
check_steam_dependencies() {
info "Checking Steam dependencies for Arch Linux..."
# Force refresh package database to avoid 404 errors from stale mirrors
info "Force refreshing package database from all mirrors..."
sudo pacman -Syy || die "Failed to refresh package database"
# Prompt for full system upgrade before installing dependencies
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 ""
echo " This will run: sudo pacman -Syu"
echo ""
echo "════════════════════════════════════════════════════════════════"
echo ""
read -p "Upgrade system now? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
info "Upgrading system..."
sudo pacman -Syu || die "Failed to upgrade system"
else
info "Skipping system upgrade - this may cause package conflicts"
fi
echo ""
local -a missing_deps=()
local -a optional_deps=()
local multilib_enabled=false
# Ensure pciutils is installed for GPU detection
if ! command -v lspci >/dev/null 2>&1; then
info "Installing pciutils for GPU detection..."
sudo pacman -S --needed --noconfirm pciutils || die "Failed to install pciutils"
fi
# Check if multilib repository is enabled (required for 32-bit Steam libraries)
if grep -q "^\[multilib\]" /etc/pacman.conf 2>/dev/null; then
multilib_enabled=true
info "Multilib repository: enabled"
else
err "Multilib repository: NOT enabled (required for Steam)"
missing_deps+=("multilib-repository")
fi
# Core Steam dependencies
local -a core_deps=(
"steam" # Steam client
"lib32-vulkan-icd-loader" # 32-bit Vulkan loader
"vulkan-icd-loader" # 64-bit Vulkan loader
"lib32-mesa" # 32-bit Mesa (OpenGL)
"mesa" # 64-bit Mesa
"mesa-utils" # Mesa utilities (glxinfo, etc.)
"lib32-systemd" # 32-bit systemd libs
"lib32-glibc" # 32-bit glibc
"lib32-gcc-libs" # 32-bit GCC libs
"lib32-libx11" # 32-bit X11
"lib32-libxss" # 32-bit X screensaver
"lib32-alsa-plugins" # 32-bit ALSA
"lib32-libpulse" # 32-bit PulseAudio
"lib32-openal" # 32-bit OpenAL
"lib32-nss" # 32-bit NSS
"lib32-libcups" # 32-bit CUPS (printing)
"lib32-sdl2" # 32-bit SDL2
"lib32-freetype2" # 32-bit fonts
"lib32-fontconfig" # 32-bit font config
"ttf-liberation" # Font package
"xdg-user-dirs" # User directories
)
# GPU-specific Vulkan drivers
local gpu_vendor
gpu_vendor=$(lspci 2>/dev/null | grep -iE 'vga|3d|display' || echo "")
# Detect GPU type(s) - system may have multiple GPUs
local has_nvidia=false has_amd=false has_intel=false has_arc=false has_intel_igpu=false
if echo "$gpu_vendor" | grep -iq 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
has_intel=true
local intel_type=$(detect_intel_gpu_type)
case "$intel_type" in
arc) info "Detected Intel Arc discrete GPU (xe driver)" ;;
igpu) info "Detected Intel integrated GPU (i915 driver)" ;;
both) info "Detected Intel Arc + integrated GPU" ;;
*) info "Detected Intel GPU" ;;
esac
fi
# Intel Arc-specific version checks
if $has_intel; then
echo ""
info "Performing Intel Arc compatibility checks..."
check_mesa_version # Will die if Mesa too old
echo ""
fi
local -a gpu_deps=()
# NVIDIA drivers
if $has_nvidia; then
gpu_deps+=(
"nvidia-utils" # NVIDIA OpenGL/Vulkan driver
"lib32-nvidia-utils" # 32-bit NVIDIA driver
"nvidia-settings" # NVIDIA control panel
"libva-nvidia-driver" # VA-API support for NVIDIA
)
# Check if user has nvidia, nvidia-dkms, or nvidia-open-dkms installed
if ! check_package "nvidia" && ! check_package "nvidia-dkms" && ! check_package "nvidia-open-dkms"; then
info "Note: You may need to install 'nvidia', 'nvidia-dkms', or 'nvidia-open-dkms' kernel module"
optional_deps+=("nvidia-dkms")
fi
fi
# AMD drivers
if $has_amd; then
gpu_deps+=(
"vulkan-radeon" # AMD Vulkan driver (RADV)
"lib32-vulkan-radeon" # 32-bit AMD Vulkan driver
"libva-mesa-driver" # VA-API support for AMD
"lib32-libva-mesa-driver" # 32-bit VA-API
"mesa-vdpau" # VDPAU support
"lib32-mesa-vdpau" # 32-bit VDPAU
)
# Optional AMD-specific packages (only if not installed)
! check_package "xf86-video-amdgpu" && optional_deps+=("xf86-video-amdgpu")
fi
# Intel drivers
if $has_intel; then
if $has_arc; then
# Intel Arc discrete GPUs:
# Use a lean Vulkan + compute stack to avoid unnecessary VA-API complexity.
gpu_deps+=(
"vulkan-intel" # Intel Vulkan driver (ANV)
"lib32-vulkan-intel" # 32-bit Intel Vulkan driver
"vulkan-icd-loader" # Vulkan ICD loader
"lib32-vulkan-icd-loader" # 32-bit Vulkan ICD loader
"intel-compute-runtime" # OpenCL/compute support (Arc / XE)
"level-zero-loader" # Intel oneAPI Level Zero (if available)
"intel-gpu-tools" # Intel GPU debugging and monitoring tools
)
else
# Integrated Intel GPUs (UHD / Xe iGPU) keep the full media stack.
gpu_deps+=(
"vulkan-intel" # Intel Vulkan driver (ANV)
"lib32-vulkan-intel" # 32-bit Intel Vulkan driver
"intel-media-driver" # VA-API for Intel (Broadwell+)
"libva-intel-driver" # VA-API for older Intel
"lib32-libva-intel-driver" # 32-bit VA-API
"intel-compute-runtime" # OpenCL/compute support
"level-zero-loader" # Intel oneAPI Level Zero (if available)
"intel-gpu-tools" # Intel GPU debugging and monitoring tools
)
fi
fi
# Fallback if no GPU detected
if ! $has_nvidia && ! $has_amd && ! $has_intel; then
info "GPU vendor not auto-detected, installing generic Vulkan drivers..."
gpu_deps+=("vulkan-radeon" "lib32-vulkan-radeon" "vulkan-intel" "lib32-vulkan-intel")
fi
# Common Vulkan tools for all GPUs
gpu_deps+=(
"vulkan-tools" # vkcube and vulkaninfo
"vulkan-mesa-layers" # Mesa Vulkan layers
)
# Optional but recommended packages
local -a recommended_deps=(
"gamemode" # System optimization
"lib32-gamemode" # 32-bit gamemode
"gamescope" # Gaming compositor
"mangohud" # Performance overlay
"lib32-mangohud" # 32-bit MangoHud
"proton-ge-custom-bin" # Custom Proton (AUR)
"protontricks" # Proton helper
)
# Check core dependencies
info "Checking core Steam dependencies..."
for dep in "${core_deps[@]}"; do
if ! check_package "$dep"; then
missing_deps+=("$dep")
fi
done
# Check GPU-specific dependencies
info "Checking GPU-specific dependencies..."
for dep in "${gpu_deps[@]}"; do
if ! check_package "$dep"; then
missing_deps+=("$dep")
fi
done
# Check recommended dependencies
info "Checking recommended dependencies..."
for dep in "${recommended_deps[@]}"; do
if ! check_package "$dep"; then
optional_deps+=("$dep")
fi
done
# Report findings
echo ""
echo "════════════════════════════════════════════════════════════════"
echo " STEAM DEPENDENCY CHECK RESULTS"
echo "════════════════════════════════════════════════════════════════"
echo ""
if [ "$multilib_enabled" = false ]; then
echo " CRITICAL: Multilib repository must be enabled!"
echo ""
echo " To enable multilib, edit /etc/pacman.conf and uncomment:"
echo " [multilib]"
echo " Include = /etc/pacman.d/mirrorlist"
echo ""
echo " Then run: sudo pacman -Syu"
echo ""
read -p "Enable multilib repository now? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
enable_multilib_repo
else
die "Multilib repository is required for Steam"
fi
fi
# Remove "multilib-repository" from missing_deps if it was added
local -a clean_missing=()
for item in "${missing_deps[@]}"; do
[[ -n "$item" && "$item" != "multilib-repository" ]] && clean_missing+=("$item")
done
missing_deps=("${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..."
sudo pacman -S --needed "${missing_deps[@]}" || die "Failed to install Steam dependencies"
info "Required dependencies installed successfully"
else
err "Cannot proceed without required dependencies"
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..."
# Filter out AUR packages (proton-ge-custom-bin needs AUR helper)
local -a pacman_optional=()
local -a aur_optional=()
for dep in "${optional_deps[@]}"; do
if pacman -Si "$dep" &>/dev/null; then
pacman_optional+=("$dep")
else
aur_optional+=("$dep")
fi
done
if ((${#pacman_optional[@]})); then
sudo pacman -S --needed --noconfirm "${pacman_optional[@]}" || info "Some optional packages failed to install"
fi
if ((${#aur_optional[@]})); then
echo ""
info "The following packages are from AUR and need an AUR helper:"
for dep in "${aur_optional[@]}"; do
echo "$dep"
done
echo ""
# Check for AUR helpers
if command -v yay >/dev/null 2>&1; then
read -p "Install AUR packages with yay? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
yay -S --needed --noconfirm "${aur_optional[@]}" || info "Some AUR packages failed to install"
fi
elif command -v paru >/dev/null 2>&1; then
read -p "Install AUR packages with paru? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
paru -S --needed --noconfirm "${aur_optional[@]}" || info "Some AUR packages failed to install"
fi
else
info "No AUR helper found (yay/paru). Install manually if desired."
fi
fi
fi
else
info "All recommended packages are already installed!"
fi
echo ""
echo "════════════════════════════════════════════════════════════════"
# Additional Steam configuration checks
check_steam_config
}
enable_multilib_repo() {
info "Enabling multilib repository..."
# Backup pacman.conf
sudo cp /etc/pacman.conf "/etc/pacman.conf.backup.$(date +%Y%m%d%H%M%S)" || die "Failed to backup pacman.conf"
# Enable multilib by uncommenting the lines
sudo sed -i '/^#\[multilib\]/,/^#Include/ s/^#//' /etc/pacman.conf || die "Failed to enable multilib"
# Verify it was enabled
if grep -q "^\[multilib\]" /etc/pacman.conf 2>/dev/null; then
info "Multilib repository enabled successfully"
echo ""
info "Updating system to enable multilib packages..."
read -p "Proceed with system upgrade? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
sudo pacman -Syu || die "Failed to update and upgrade system"
else
die "System upgrade required after enabling multilib - run 'sudo pacman -Syu' and try again"
fi
else
die "Failed to enable multilib repository"
fi
}
check_steam_config() {
info "Checking Steam configuration..."
# Check for proper permissions and offer to add user to groups
local missing_groups=()
if ! groups | grep -q '\bvideo\b'; then
missing_groups+=("video")
fi
if ! groups | grep -q '\binput\b'; then
missing_groups+=("input")
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"
;;
esac
done
echo ""
echo " Without these groups, you may experience:"
echo " - GPU access issues"
echo " - Controller/gamepad not working in games"
echo ""
echo " NOTE: After adding groups, you MUST log out and log back in"
echo " for the changes to take effect."
echo ""
echo "════════════════════════════════════════════════════════════════"
echo ""
read -p "Add user to ${missing_groups[*]} group(s)? [Y/n]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
# Join groups with comma for usermod
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"
info "You can manually add yourself with: sudo usermod -aG $groups_to_add $USER"
fi
else
info "Skipping group assignment - you may encounter GPU/controller issues"
fi
else
info "User is in video and input groups - permissions OK"
fi
# Check for Steam directory
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 for Proton/Wine dependencies
if check_package "wine"; then
info "Wine is installed (helps with some Windows games)"
else
info "Tip: Install 'wine' for better Windows game compatibility"
fi
# Check for controller support
if check_package "steam-native-runtime"; then
info "Steam native runtime installed (better compatibility)"
fi
# Check kernel parameters for gaming
if [ -f /proc/sys/vm/swappiness ]; then
local swappiness
swappiness=$(cat /proc/sys/vm/swappiness)
if [ "$swappiness" -gt 10 ]; then
info "Tip: Consider lowering vm.swappiness to 10 for better gaming performance"
info " Current value: $swappiness"
fi
fi
# Check for esync/fsync support
local max_files
max_files=$(ulimit -n 2>/dev/null || echo "0")
if [ "$max_files" -lt 524288 ]; then
info "Tip: Increase open file limit for esync support"
info " Add to /etc/security/limits.conf:"
info " * hard nofile 524288"
info " * soft nofile 524288"
fi
}
check_mesa_version() {
local mesa_version=""
# Try to get Mesa version from multiple sources
if command -v glxinfo >/dev/null 2>&1; then
# Extract Mesa version without Perl regex
mesa_version=$(glxinfo 2>/dev/null | grep "OpenGL version string" | sed -n 's/.*Mesa \([0-9]\+\.[0-9]\+\).*/\1/p' | head -n1)
fi
# Fallback: check package version
if [ -z "$mesa_version" ]; then
mesa_version=$(pacman -Q mesa 2>/dev/null | awk '{print $2}' | cut -d'-' -f1)
fi
if [ -z "$mesa_version" ]; then
info "Could not detect Mesa version - will install latest from repositories"
return 0
fi
# Strip epoch prefix if present (e.g., "1:25.0.0" -> "25.0.0")
mesa_version=$(echo "$mesa_version" | sed 's/^[0-9]*://')
local major minor
major=$(echo "$mesa_version" | cut -d'.' -f1)
minor=$(echo "$mesa_version" | cut -d'.' -f2)
local version_number=$((major * 100 + minor))
local required_version=2202 # 22.2
local recommended_version=2300 # 23.0
if [ "$version_number" -lt "$required_version" ]; then
echo ""
echo "════════════════════════════════════════════════════════════════"
echo " ⚠️ MESA VERSION WARNING"
echo "════════════════════════════════════════════════════════════════"
echo ""
echo " Current Mesa: $mesa_version"
echo " Required for Intel Arc: 22.2 or higher"
echo " Recommended: 23.0 or higher"
echo ""
echo " Intel Arc GPUs require Mesa 22.2+ for proper Vulkan support."
echo " Your current version is too old and will cause issues."
echo ""
echo "════════════════════════════════════════════════════════════════"
echo ""
die "Mesa version too old for Intel Arc. Update your system: sudo pacman -Syu"
elif [ "$version_number" -lt "$recommended_version" ]; then
info "Mesa $mesa_version detected (meets minimum 22.2, but 23.0+ recommended)"
else
info "Mesa version $mesa_version meets Intel Arc recommendations (23.0+)"
fi
return 0
}
setup_performance_permissions() {
local udev_rules_file="/etc/udev/rules.d/99-gaming-performance.rules"
# Check if udev rules already exist
if [ -f "$udev_rules_file" ]; then
info "Performance udev rules already installed"
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 ""
echo " This will create udev rules to allow users to modify:"
echo " • CPU governor (performance mode)"
echo " • GPU performance settings (AMD/Intel/NVIDIA)"
echo ""
echo " This is a one-time setup that requires sudo."
echo " After this, gaming mode will launch without password prompts."
echo ""
echo "════════════════════════════════════════════════════════════════"
echo ""
read -p "Set up passwordless performance controls? [Y/n]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
info "Skipping udev rules setup"
info "Note: You'll need to enter your password each time you launch gaming mode"
return 0
fi
info "Creating udev rules for performance control..."
# Create the udev rules file
sudo tee "$udev_rules_file" > /dev/null <<'UDEV_RULES'
# Gaming Mode Performance Control Rules
# Allow users to modify CPU governor and GPU performance settings without sudo
# CPU governor control (all CPUs)
KERNEL=="cpu[0-9]*", SUBSYSTEM=="cpu", ACTION=="add", RUN+="/bin/chmod 666 /sys/devices/system/cpu/%k/cpufreq/scaling_governor"
# AMD GPU performance control
KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="amdgpu", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/device/power_dpm_force_performance_level"
# Intel GPU frequency control (i915 driver - NOT used for xe driver GPUs)
# Note: This script ignores i915 GPUs at runtime to prevent conflicts with xe driver
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"
# Intel Arc (Xe) GPU control - NOT CURRENTLY FUNCTIONAL
# The xe driver does not expose user-controllable frequency knobs in sysfs yet
# Leaving these here for future kernel support
KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="xe", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/device/power/control"
UDEV_RULES
if [ $? -ne 0 ]; then
err "Failed to create udev rules"
info "You can manually create $udev_rules_file later"
return 1
fi
info "Udev rules created successfully"
# Reload udev rules
info "Reloading udev rules..."
sudo udevadm control --reload-rules || info "Failed to reload udev rules"
sudo udevadm trigger --subsystem-match=cpu --subsystem-match=drm || info "Failed to trigger udev"
# Apply permissions immediately (don't wait for reboot)
info "Applying permissions immediately..."
# CPU governor permissions
if [ -d /sys/devices/system/cpu/cpu0/cpufreq ]; then
sudo chmod 666 /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor 2>/dev/null || \
info "Could not set CPU governor permissions (will work after reboot)"
fi
# Detect GPU and apply permissions
local gpu_vendor
gpu_vendor=$(lspci 2>/dev/null | grep -iE 'vga|3d|display' || echo "")
# AMD GPU permissions
if echo "$gpu_vendor" | grep -iqE 'amd|radeon|advanced micro'; then
sudo chmod 666 /sys/class/drm/card*/device/power_dpm_force_performance_level 2>/dev/null || \
info "Could not set AMD GPU permissions (will work after reboot)"
fi
# Intel i915 GPU permissions (will be ignored at runtime)
if echo "$gpu_vendor" | grep -iq intel; then
sudo chmod 666 /sys/class/drm/card*/gt_boost_freq_mhz 2>/dev/null || true
sudo chmod 666 /sys/class/drm/card*/gt_min_freq_mhz 2>/dev/null || true
sudo chmod 666 /sys/class/drm/card*/gt_max_freq_mhz 2>/dev/null || true
fi
info "✓ Performance permissions configured"
info "Gaming mode will now launch without password prompts"
return 0
}
setup_intel_arc_workarounds() {
# Fix GTK4 rendering issues on Intel Arc GPUs (Nautilus, GNOME apps)
# Force OpenGL renderer instead of Vulkan to prevent visual glitches
local intel_type=$(detect_intel_gpu_type)
[[ "$intel_type" == "arc" || "$intel_type" == "both" ]] || return 0
local env_dir="$HOME/.config/environment.d"
local env_file="$env_dir/10-intel-arc-gtk.conf"
# Check if workaround already applied
if [[ -f "$env_file" ]] && grep -q "GSK_RENDERER=" "$env_file" 2>/dev/null; then
local current_renderer=$(grep "GSK_RENDERER=" "$env_file" | cut -d'=' -f2)
info "Intel Arc GTK4 workaround already configured (GSK_RENDERER=$current_renderer)"
return 0
fi
echo ""
info "Intel Arc GTK4 Rendering Fix (REQUIRED)"
info "Applying GSK_RENDERER=gl to fix Nautilus/GTK4 visual glitches"
info "Config: $env_file"
info "Note: Log out and log back in after installation"
echo ""
local renderer="gl"
# Create environment.d directory if it doesn't exist
mkdir -p "$env_dir" || die "Failed to create $env_dir"
# Create the environment file
info "Creating GTK4 renderer configuration..."
cat > "$env_file" <<ENV_CONFIG
# Intel Arc GTK4 Rendering Fix
# Added by gaming mode installer
# Fixes visual glitches in GTK4 applications on Wayland with Intel Arc GPUs
GSK_RENDERER=$renderer
ENV_CONFIG
if [ $? -ne 0 ]; then
err "Failed to create environment file"
info "You can manually create $env_file with: GSK_RENDERER=$renderer"
return 1
fi
info "✓ Intel Arc GTK4 fix configured (GSK_RENDERER=$renderer)"
info "Changes will take effect after logging out and back in"
# Set a flag to remind user to log out
NEEDS_RELOGIN=1
return 0
}
setup_requirements() {
local -a required_packages=("steam" "gamescope" "mangohud" "gum" "python" "libcap" "gamemode" "curl" "pciutils")
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
sudo pacman -S --needed "${packages_to_install[@]}" || die "package install failed"
else
die "Required packages missing - cannot continue"
fi
else
info "All required packages present."
fi
# Set up passwordless performance controls
setup_performance_permissions
# Fix Intel Arc GTK4 rendering issues (Nautilus glitches, etc.)
setup_intel_arc_workarounds
if [[ "${PERFORMANCE_MODE,,}" == "enabled" ]] && command -v gamescope >/dev/null 2>&1; then
# Check if capability is already set before trying to set it
if ! getcap "$(command -v gamescope)" 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 ""
echo " This allows gamescope to:"
echo " • Run with real-time priority (--rt flag)"
echo " • Reduce latency and improve frame pacing"
echo ""
echo " Security note: This capability will be granted to ALL users"
echo " who can execute $(command -v gamescope)"
echo ""
echo " You can remove it later with:"
echo " sudo setcap -r $(command -v gamescope)"
echo ""
echo "════════════════════════════════════════════════════════════════"
echo ""
read -p "Grant cap_sys_nice to gamescope? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
sudo setcap 'cap_sys_nice+ep' "$(command -v gamescope)" || info "setcap failed; --rt may be ignored."
info "cap_sys_nice granted to gamescope"
else
info "Skipping cap_sys_nice - gamescope will run without real-time priority"
info "Performance mode will still work but with slightly higher latency"
fi
else
info "Gamescope already has cap_sys_nice capability"
fi
fi
}
deploy_launchers() {
if [ ! -d "$TARGET_DIR" ]; then
mkdir -p "$TARGET_DIR" || die "cannot create $TARGET_DIR"
CREATED_TARGET_DIR=1
fi
cat > "$SWITCH_BIN" <<'EOF'
#!/bin/bash
set -Euo pipefail
STATE_DIR="$HOME/.cache/gaming-session"
LOCK_FILE="$STATE_DIR/idle-prevention.lock"
SESSION_FILE="$STATE_DIR/session.pid"
CONFIG_FILE="/etc/gaming-mode.conf"
[[ -f "$HOME/.gaming-mode.conf" ]] && CONFIG_FILE="$HOME/.gaming-mode.conf"
source "$CONFIG_FILE" 2>/dev/null || true
# Set defaults for any unset variables (works even if config partially defines them)
: "${STEAM_LAUNCH_MODE:=bigpicture}"
: "${PERFORMANCE_MODE:=enabled}"
# Cleanup function to restore system state on failure
cleanup_on_error() {
echo "[!] Error occurred, restoring system state..." >&2
# Restore CPU governor
if [[ -f "$STATE_DIR/original_governors" ]]; then
while IFS=: read -r cpu_path original_gov; do
[[ -z "$cpu_path" || -z "$original_gov" || "$original_gov" == "unknown" ]] && continue
if [[ -f "$cpu_path" ]]; then
echo "$original_gov" > "$cpu_path" 2>/dev/null || \
echo "$original_gov" | sudo tee "$cpu_path" >/dev/null 2>&1 || true
fi
done < "$STATE_DIR/original_governors"
rm -f "$STATE_DIR/original_governors"
fi
# Restore GPU performance mode
if [[ -f "$STATE_DIR/gpu_perf_mode" ]]; then
while IFS=: read -r gpu_path original_mode; do
[[ -z "$gpu_path" || -z "$original_mode" ]] && continue
[[ "$original_mode" == "nvidia_optimized" || "$original_mode" == "amd_optimized" || "$original_mode" == "xe_optimized" ]] && continue
if [[ -f "$gpu_path" ]]; then
echo "$original_mode" > "$gpu_path" 2>/dev/null || \
echo "$original_mode" | sudo tee "$gpu_path" >/dev/null 2>&1 || true
fi
done < "$STATE_DIR/gpu_perf_mode"
# Restore NVIDIA settings
if grep -q "nvidia_optimized" "$STATE_DIR/gpu_perf_mode" 2>/dev/null; then
command -v nvidia-settings >/dev/null 2>&1 && nvidia-settings -a "[gpu:0]/GpuPowerMizerMode=0" >/dev/null 2>&1 || true
command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -pm 0 >/dev/null 2>&1 || true
fi
rm -f "$STATE_DIR/gpu_perf_mode"
fi
# Restart hypridle only if it was running before gaming mode
if [[ -f "$STATE_DIR/hypridle_state" ]]; then
if [[ "$(cat "$STATE_DIR/hypridle_state" 2>/dev/null)" == "running" ]]; then
if command -v hypridle >/dev/null 2>&1 && ! pgrep -x hypridle >/dev/null 2>&1; then
nohup hypridle >/dev/null 2>&1 &
disown
fi
fi
rm -f "$STATE_DIR/hypridle_state"
fi
# Clean up state files
rm -f "$LOCK_FILE" "$SESSION_FILE" 2>/dev/null || true
}
# Set trap to cleanup on any error
trap cleanup_on_error ERR
steam_icon() { echo "steam"; }
# Helper function to center output in terminal
center_output() {
local width=${1:-70}
local term_width=$(tput cols)
local padding=$(( (term_width - width) / 2 ))
[[ $padding -lt 0 ]] && padding=0
while IFS= read -r line; do
printf "%${padding}s%s\n" "" "$line"
done
}
center_notify() {
local msg="$*"
# TUI notification using gum - centered (uses terminal theme colors)
clear
local term_width=$(tput cols)
local box_width=50
local padding=$(( (term_width - box_width) / 2 ))
[[ $padding -lt 0 ]] && padding=0
echo ""
gum style --align center --width 50 --border normal --padding "1 2" "$msg" | while IFS= read -r line; do printf "%${padding}s%s\\n" "" "$line"; done
sleep 2
}
get_display() {
local j; j="$(hyprctl monitors -j 2>/dev/null || true)"
if [[ -z "$j" ]]; then echo "1920 1080 60"; return; fi
echo "$j" | python3 -c "
import json,sys
try:
d=json.load(sys.stdin); m=d[0] if d else {}
print(int(m.get('width',1920)), int(m.get('height',1080)), int(round(m.get('refreshRate',60))))
except Exception:
print('1920 1080 60')
"
}
read -r horizontal_res vertical_res monitor_hz <<<"$(get_display)"
DISPLAY_WIDTH="$horizontal_res"; DISPLAY_HEIGHT="$vertical_res"; REFRESH_RATE="$monitor_hz"
# Detect system theme and set gum colors to match Omarchy theme
detect_system_theme() {
local omarchy_theme_dir="$HOME/.config/omarchy/current/theme"
local ghostty_conf="$omarchy_theme_dir/ghostty.conf"
# Default colors (fallback)
local accent_color="6" # Cyan (ANSI color 6)
local border_color="7" # White/light gray
local cursor_color="6" # Cyan
# Read colors from Omarchy ghostty theme if available
if [[ -f "$ghostty_conf" ]]; then
# Extract accent color (use palette 6 or 2 as accent - typically cyan/green)
local palette_6=$(grep "^palette = 6=" "$ghostty_conf" 2>/dev/null | cut -d'=' -f3 | tr -d ' #')
local palette_2=$(grep "^palette = 2=" "$ghostty_conf" 2>/dev/null | cut -d'=' -f3 | tr -d ' #')
if [[ -n "$palette_6" ]]; then
# Convert hex to gum color (use the hex directly)
accent_color="#$palette_6"
cursor_color="#$palette_6"
elif [[ -n "$palette_2" ]]; then
accent_color="#$palette_2"
cursor_color="#$palette_2"
fi
# Get foreground color for borders
local fg_color=$(grep "^foreground = " "$ghostty_conf" 2>/dev/null | cut -d'=' -f2 | tr -d ' ')
if [[ -n "$fg_color" ]]; then
border_color="$fg_color"
fi
fi
# Set gum environment variables to match theme
export GUM_CHOOSE_CURSOR_FOREGROUND="$cursor_color"
export GUM_CHOOSE_SELECTED_FOREGROUND="$accent_color"
export GUM_STYLE_BORDER_FOREGROUND="$border_color"
export GUM_SPIN_SPINNER_FOREGROUND="$accent_color"
}
detect_system_theme
# Check if previous settings exist
SETTINGS_FILE="$STATE_DIR/last-settings"
USE_PREVIOUS=false
# Initialize padding for centering (used in all paths)
term_width=$(tput cols)
menu_width=75
menu_pad=$(( (term_width - menu_width) / 2 ))
[[ $menu_pad -lt 0 ]] && menu_pad=0
pad_str=$(printf "%${menu_pad}s" "")
clear
echo ""
gum style --align center --width 70 --border double --padding "1 2" $'W.O.P.R\n\nShall we play a game?' | center_output 74
if [[ -f "$SETTINGS_FILE" ]]; then
# Ask user if they want to use previous settings
echo ""
gum style --align center --width 70 --bold "Previous settings found." | center_output 70
echo ""
# Calculate padding for confirm dialog
term_width=$(tput cols)
confirm_width=40
confirm_pad=$(( (term_width - confirm_width) / 2 ))
[[ $confirm_pad -lt 0 ]] && confirm_pad=0
confirm_pad_str=$(printf "%${confirm_pad}s" "")
gum style --align center --width 70 "Use previous settings?" | center_output 70
echo ""
CONFIRM_CHOICE=$(gum choose --height 6 \
"${confirm_pad_str}Yes - Use previous settings" \
"${confirm_pad_str}No - Configure new settings") || CONFIRM_CHOICE=""
if [[ "$CONFIRM_CHOICE" == *"Yes"* ]]; then
USE_PREVIOUS=true
source "$SETTINGS_FILE"
else
USE_PREVIOUS=false
fi
fi
if [[ "$USE_PREVIOUS" == false ]]; then
# Show resolution selection menu
clear
echo ""
gum style --align center --width 70 --border double --padding "1 2" "🎮 Gaming Mode - Resolution Settings" | center_output 74
echo ""
gum style --align center --width 70 --faint "Current Display: ${DISPLAY_WIDTH}x${DISPLAY_HEIGHT} @ ${REFRESH_RATE}Hz" | center_output 70
echo ""
gum style --align center --width 70 "Select your preferred gaming resolution:" | center_output 70
echo ""
# Format native resolution option to align with others (30 chars before │)
native_res="${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}"
native_option=$(printf "%-30s" "$native_res")
RESOLUTION_CHOICE=$(gum choose --height 12 \
"${pad_str}${native_option}│ Render at native resolution (best quality)" \
"${pad_str}1440p Upscaled │ Render at 1080p, upscale to 1440p (better FPS)" \
"${pad_str}1440p (2560x1440) │ Render at 1440p resolution" \
"${pad_str}UHD Upscaled │ Render at 1440p, upscale to 4K (balanced)" \
"${pad_str}UHD (3840x2160) │ Render at 4K/UHD resolution") || {
center_notify "Gaming Mode cancelled"
exit 0
}
# If user cancelled, exit
if [[ -z "$RESOLUTION_CHOICE" ]]; then
center_notify "Gaming Mode cancelled"
exit 0
fi
# Extract just the resolution name (before the │), removing padding
RESOLUTION_CHOICE=$(echo "$RESOLUTION_CHOICE" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*│.*//' | xargs)
# Convert native resolution to standard format for matching
if [[ "$RESOLUTION_CHOICE" == "${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" ]]; then
RESOLUTION_CHOICE="Native (${DISPLAY_WIDTH}x${DISPLAY_HEIGHT})"
fi
# Show MangoHud preset selection
clear
echo ""
gum style --align center --width 70 --border double --padding "1 2" "🎮 Gaming Mode - MangoHud Settings" | center_output 74
echo ""
gum style --align center --width 70 "Select your preferred performance overlay:" | center_output 70
echo ""
MANGOHUD_CHOICE=$(gum choose --height 10 \
"${pad_str}Off │ No performance overlay (cleanest view)" \
"${pad_str}Minimal │ FPS counter only (recommended)" \
"${pad_str}Full Stats │ Detailed metrics (CPU, GPU, temps, frametime)") || {
center_notify "Gaming Mode cancelled"
exit 0
}
# If user cancelled, exit
if [[ -z "$MANGOHUD_CHOICE" ]]; then
center_notify "Gaming Mode cancelled"
exit 0
fi
# Extract just the preset name (before the │), removing padding
MANGOHUD_CHOICE=$(echo "$MANGOHUD_CHOICE" | sed 's/^[[:space:]]*//' | sed 's/ │.*//' | xargs)
# Save settings for next time
mkdir -p "$STATE_DIR"
cat > "$SETTINGS_FILE" <<SETTINGS
RESOLUTION_CHOICE="$RESOLUTION_CHOICE"
MANGOHUD_CHOICE="$MANGOHUD_CHOICE"
SETTINGS
fi
# Set game rendering and output resolution based on choice
game_width="$DISPLAY_WIDTH"
game_height="$DISPLAY_HEIGHT"
output_width="$DISPLAY_WIDTH"
output_height="$DISPLAY_HEIGHT"
case "$RESOLUTION_CHOICE" in
"Native (${DISPLAY_WIDTH}x${DISPLAY_HEIGHT})")
game_width="$DISPLAY_WIDTH"
game_height="$DISPLAY_HEIGHT"
output_width="$DISPLAY_WIDTH"
output_height="$DISPLAY_HEIGHT"
;;
"UHD (3840x2160)")
game_width=3840
game_height=2160
output_width=3840
output_height=2160
;;
"UHD Upscaled")
game_width=2560
game_height=1440
output_width=3840
output_height=2160
;;
"1440p (2560x1440)")
game_width=2560
game_height=1440
output_width=2560
output_height=1440
;;
"1440p Upscaled")
game_width=1920
game_height=1080
output_width=2560
output_height=1440
;;
esac
mkdir -p "$STATE_DIR"
: > "$LOCK_FILE"
# Save hypridle state and terminate if running
if pgrep -x hypridle >/dev/null 2>&1; then
# Mark that hypridle was running so we can restore it later
echo "running" > "$STATE_DIR/hypridle_state"
pkill hypridle 2>/dev/null
# Wait up to 2 seconds for graceful shutdown
for i in {1..4}; do
sleep 0.5
pgrep -x hypridle >/dev/null 2>&1 || break
done
# Force kill if still running
pkill -9 hypridle 2>/dev/null || true
else
# Mark that hypridle was not running
echo "stopped" > "$STATE_DIR/hypridle_state"
fi
# ===== CPU GOVERNOR OPTIMIZATION =====
# Set CPU to performance mode for lower latency and consistent frame times
if [[ -d /sys/devices/system/cpu/cpu0/cpufreq ]]; then
# Save current governor for each CPU (handles hybrid CPUs with different governors)
: > "$STATE_DIR/original_governors"
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
if [[ -f "$cpu" ]]; then
current_gov=$(cat "$cpu" 2>/dev/null || echo "unknown")
echo "$cpu:$current_gov" >> "$STATE_DIR/original_governors"
fi
done
# Set all CPUs to performance mode
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
if [[ -f "$cpu" ]]; then
if [[ -w "$cpu" ]]; then
echo performance > "$cpu" 2>/dev/null || \
echo performance | sudo tee "$cpu" >/dev/null 2>&1
else
echo performance | sudo tee "$cpu" >/dev/null 2>&1 || true
fi
fi
done
fi
# ===== GPU PERFORMANCE MODE =====
# Force GPU to maximum clocks for stable performance
# Initialize GPU state file (clear any old data)
: > "$STATE_DIR/gpu_perf_mode"
GPU_VENDOR=$(lspci 2>/dev/null | grep -iE 'vga|3d|display' || echo "")
# Detect all GPU types
HAS_AMD=false
HAS_NVIDIA=false
HAS_INTEL=false
echo "$GPU_VENDOR" | grep -iqE 'amd|radeon|advanced micro' && HAS_AMD=true
echo "$GPU_VENDOR" | grep -iq nvidia && HAS_NVIDIA=true
echo "$GPU_VENDOR" | grep -iq intel && HAS_INTEL=true
# AMD GPU optimization
if $HAS_AMD; then
for gpu in /sys/class/drm/card*/device/power_dpm_force_performance_level; do
if [[ -f "$gpu" ]]; then
# Save current mode
current_mode=$(cat "$gpu" 2>/dev/null || echo "auto")
echo "$gpu:$current_mode" >> "$STATE_DIR/gpu_perf_mode"
# Set to high performance
if [[ -w "$gpu" ]]; then
echo high > "$gpu" 2>/dev/null || \
echo high | sudo tee "$gpu" >/dev/null 2>&1
fi
fi
done
echo "amd_optimized" >> "$STATE_DIR/gpu_perf_mode"
fi
# NVIDIA GPU optimization
if $HAS_NVIDIA; then
# nvidia-settings requires X display, may not work on pure Wayland
if command -v nvidia-settings >/dev/null 2>&1 && [[ -n "${DISPLAY:-}" || -n "${WAYLAND_DISPLAY:-}" ]]; then
# Set PowerMizer to max performance (mode 1)
if nvidia-settings -a "[gpu:0]/GpuPowerMizerMode=1" >/dev/null 2>&1; then
echo "nvidia_optimized" >> "$STATE_DIR/gpu_perf_mode"
fi
fi
# Also try nvidia-smi for persistence mode (doesn't require display)
if command -v nvidia-smi >/dev/null 2>&1; then
nvidia-smi -pm 1 >/dev/null 2>&1 || true
fi
fi
# ===== INTEL GPU OPTIMIZATION (XE DRIVER ONLY) =====
# IMPORTANT: This section ONLY optimizes Intel GPUs using the xe driver (Arc GPUs)
# It IGNORES i915 GPUs (integrated graphics) to prevent conflicts
# DISABLED: xe driver optimization causes visual glitches on Arc B580 with kernel 6.17
# The xe driver power management is not mature enough for forced always-on mode
if $HAS_INTEL; then
# Check each Intel card to see which driver it uses
for card_device in /sys/class/drm/card*/device/driver; do
if [[ -L "$card_device" ]]; then
# Get the driver name
driver_name=$(basename "$(readlink -f "$card_device")")
# Only process xe driver cards
if [[ "$driver_name" == "xe" ]]; then
# Extract card name (e.g., card0)
card_path=$(dirname "$(dirname "$card_device")")
card_name=$(basename "$card_path")
echo "# xe driver detected on $card_name - SKIPPED (optimization disabled)" >> "$STATE_DIR/gpu_perf_mode"
# NOTE: Power management optimization for xe driver is DISABLED
# Forcing the GPU to always-on mode causes visual glitches on Arc B580
# with kernel 6.17.x. Let the driver manage power automatically.
#
# This may be revisited when xe driver matures in future kernels.
elif [[ "$driver_name" == "i915" ]]; then
# Explicitly skip i915 cards
card_path=$(dirname "$(dirname "$card_device")")
card_name=$(basename "$card_path")
echo "# i915 driver detected on $card_name - SKIPPED (not optimized)" >> "$STATE_DIR/gpu_perf_mode"
fi
fi
done
fi
# center_notify "Starting Gamesmode at ${game_width}x${game_height}…"
gamescope_perf=""
[[ "${PERFORMANCE_MODE,,}" == "enabled" ]] && gamescope_perf="--rt --immediate-flips"
steam_args=""
case "${STEAM_LAUNCH_MODE,,}" in
gamepadui) steam_args="-gamepadui" ;;
bigpicture|"") steam_args="-tenfoot" ;;
*) steam_args="-tenfoot" ;;
esac
# Configure MangoHud based on user choice
mangohud_flag=""
mangohud_config=""
case "$MANGOHUD_CHOICE" in
"Off")
mangohud_flag=""
;;
"Minimal")
mangohud_flag="--mangoapp"
mangohud_config="fps,fps_only,position=top-left,font_size=24"
;;
"Full Stats")
mangohud_flag="--mangoapp"
mangohud_config="cpu_temp,gpu_temp,cpu_power,gpu_power,ram,vram,fps,frametime,frame_timing=1,gpu_stats,cpu_stats,position=top-left"
;;
esac
# Show launch status
clear
echo ""
gum style --align center --width 70 --border double --padding "1 2" "🎮 Launching Gaming Mode" | center_output 74
echo ""
gum style --align center --width 70 --faint "Resolution: ${game_width}x${game_height}" | center_output 70
gum style --align center --width 70 --faint "Output: ${output_width}x${output_height}" | center_output 70
gum style --align center --width 70 --faint "Refresh: ${REFRESH_RATE}Hz" | center_output 70
gum style --align center --width 70 --faint "MangoHud: ${MANGOHUD_CHOICE}" | center_output 70
echo ""
echo "${pad_str}" | tr -d '\\n'
gum spin --spinner dot --title "Starting gamescope..." -- sleep 2
# Launch gamescope with MangoHud configuration and Gamemode optimizations
# Gamemode adds: process priority, I/O scheduling, and additional system tweaks
if [[ -n "$mangohud_config" ]]; then
export MANGOHUD_CONFIG="$mangohud_config"
fi
setsid gamemoderun /usr/bin/gamescope $gamescope_perf $mangohud_flag -f -w "$game_width" -h "$game_height" -W "$output_width" -H "$output_height" -r "$REFRESH_RATE" --force-grab-cursor -e -- /usr/bin/steam $steam_args &
# Capture the actual PID of our gamescope instance (not all gamescope processes)
gamescope_pid=$!
# Give gamescope a moment to initialize
sleep 2
# Verify gamescope started successfully
if ! kill -0 "$gamescope_pid" 2>/dev/null; then
echo "[!] ERROR: Gamescope failed to start!" >&2
echo "[!] Restoring system state..." >&2
cleanup_on_error
exit 1
fi
# Save our specific gamescope PID for cleanup
echo "$gamescope_pid" > "$SESSION_FILE"
exit 0
EOF
chmod +x "$SWITCH_BIN" || die "cannot chmod $SWITCH_BIN"
cat > "$RETURN_BIN" <<'EOF'
#!/bin/bash
set -Euo pipefail
STATE_DIR="$HOME/.cache/gaming-session"
LOCK_FILE="$STATE_DIR/idle-prevention.lock"
SESSION_FILE="$STATE_DIR/session.pid"
steam_icon() { echo "steam"; }
center_notify() {
# TUI notification - brief message on exit (uses terminal theme colors)
if command -v gum >/dev/null 2>&1; then
clear
local term_width=$(tput cols)
local box_width=50
local padding=$(( (term_width - box_width) / 2 ))
[[ $padding -lt 0 ]] && padding=0
echo ""
gum style --align center --width 50 --border normal --padding "1 2" "BACK TO WORK" | while IFS= read -r line; do printf "%${padding}s%s\\n" "" "$line"; done
sleep 1
else
echo "BACK TO WORK"
sleep 1
fi
}
# First, gracefully terminate Steam to allow it to save state
if pgrep -x steam >/dev/null 2>&1; then
pkill -TERM steam 2>/dev/null
# Wait up to 3 seconds for graceful shutdown
for i in {1..6}; do
sleep 0.5
pgrep -x steam >/dev/null 2>&1 || break
done
# Force kill if still running
pkill -9 steam 2>/dev/null || true
fi
# Kill any remaining Steam processes (reaper, fossilize, etc.)
pkill -9 -f "steam" 2>/dev/null || true
# Terminate gamescope (this should also kill Steam if it's still inside)
if [[ -f "$SESSION_FILE" ]]; then
PID="$(cat "$SESSION_FILE" 2>/dev/null || true)"
if [[ -n "$PID" ]] && kill -0 "$PID" 2>/dev/null; then
kill "$PID" 2>/dev/null || true
# Wait up to 2 seconds for graceful shutdown
for i in {1..4}; do
sleep 0.5
kill -0 "$PID" 2>/dev/null || break
done
# Force kill if still running
kill -9 "$PID" 2>/dev/null || true
fi
fi
# Gracefully terminate gamescope with proper verification
if pgrep -x gamescope >/dev/null 2>&1; then
pkill gamescope 2>/dev/null
# Wait up to 2 seconds for graceful shutdown
for i in {1..4}; do
sleep 0.5
pgrep -x gamescope >/dev/null 2>&1 || break
done
# Force kill if still running
pkill -9 gamescope 2>/dev/null || true
fi
# ===== RESTORE CPU GOVERNOR =====
if [[ -f "$STATE_DIR/original_governors" ]]; then
while IFS=: read -r cpu_path original_gov; do
[[ -z "$cpu_path" || -z "$original_gov" || "$original_gov" == "unknown" ]] && continue
if [[ -f "$cpu_path" ]]; then
if [[ -w "$cpu_path" ]]; then
echo "$original_gov" > "$cpu_path" 2>/dev/null || \
echo "$original_gov" | sudo tee "$cpu_path" >/dev/null 2>&1
else
echo "$original_gov" | sudo tee "$cpu_path" >/dev/null 2>&1 || true
fi
fi
done < "$STATE_DIR/original_governors"
rm -f "$STATE_DIR/original_governors"
fi
# ===== RESTORE GPU PERFORMANCE MODE =====
if [[ -f "$STATE_DIR/gpu_perf_mode" ]]; then
while IFS=: read -r gpu_path original_mode; do
# Skip empty or malformed lines
[[ -z "$gpu_path" || -z "$original_mode" ]] && continue
# Skip marker lines and comments
[[ "$original_mode" == "nvidia_optimized" || "$original_mode" == "amd_optimized" || "$original_mode" == "xe_optimized" ]] && continue
[[ "$gpu_path" == "#"* ]] && continue
if [[ -f "$gpu_path" ]]; then
if [[ -w "$gpu_path" ]]; then
echo "$original_mode" > "$gpu_path" 2>/dev/null || \
echo "$original_mode" | sudo tee "$gpu_path" >/dev/null 2>&1
fi
fi
done < "$STATE_DIR/gpu_perf_mode"
# Restore NVIDIA settings if they were changed
if grep -q "nvidia_optimized" "$STATE_DIR/gpu_perf_mode" 2>/dev/null; then
if command -v nvidia-settings >/dev/null 2>&1; then
# Set back to Adaptive mode (0)
nvidia-settings -a "[gpu:0]/GpuPowerMizerMode=0" >/dev/null 2>&1
fi
# Disable persistence mode
if command -v nvidia-smi >/dev/null 2>&1; then
nvidia-smi -pm 0 >/dev/null 2>&1 || true
fi
fi
rm -f "$STATE_DIR/gpu_perf_mode"
fi
# ===== RESTART HYPRIDLE =====
# Restart hypridle only if it was running before gaming mode
if [[ -f "$STATE_DIR/hypridle_state" ]]; then
if [[ "$(cat "$STATE_DIR/hypridle_state" 2>/dev/null)" == "running" ]]; then
if command -v hypridle >/dev/null 2>&1; then
# Check if hypridle is not already running
if ! pgrep -x hypridle >/dev/null 2>&1; then
nohup hypridle >/dev/null 2>&1 &
disown
fi
fi
fi
rm -f "$STATE_DIR/hypridle_state"
fi
rm -f "$LOCK_FILE" "$SESSION_FILE" 2>/dev/null || true
EOF
chmod +x "$RETURN_BIN" || die "cannot chmod $RETURN_BIN"
}
configure_shortcuts() {
info "Adding keybindings to: $BINDINGS_CONFIG"
# Check if bindings already exist
if grep -q "# Gaming Mode bindings - added by installation script" "$BINDINGS_CONFIG" 2>/dev/null; then
info "Gaming mode bindings already exist in config, skipping..."
return 0
fi
# Detect the binding style used in the config
local bind_style="bindd"
if ! grep -q "^bindd[[:space:]]*=" "$BINDINGS_CONFIG" 2>/dev/null; then
if grep -q "^bind[[:space:]]*=" "$BINDINGS_CONFIG" 2>/dev/null; then
bind_style="bind"
fi
fi
# Detect preferred terminal (ghostty preferred, fallback to others)
local terminal_cmd="ghostty -e"
local terminal_class="com.mitchellh.ghostty"
if ! command -v ghostty >/dev/null 2>&1; then
if command -v kitty >/dev/null 2>&1; then
terminal_cmd="kitty -e"
terminal_class="kitty"
elif command -v alacritty >/dev/null 2>&1; then
terminal_cmd="alacritty -e"
terminal_class="Alacritty"
elif command -v foot >/dev/null 2>&1; then
terminal_cmd="foot -e"
terminal_class="foot"
else
terminal_cmd="xterm -e"
terminal_class="XTerm"
fi
fi
info "Using terminal: ${terminal_cmd%% -*} (class: $terminal_class)"
# Add bindings to the config file (TUI needs terminal with window rules)
{
echo ""
echo "# Gaming Mode bindings - added by installation script"
echo "windowrulev2 = float, class:($terminal_class)"
echo "windowrulev2 = size 800 600, class:($terminal_class)"
echo "windowrulev2 = center, class:($terminal_class)"
echo "windowrulev2 = pin, class:($terminal_class)"
if [ "$bind_style" = "bindd" ]; then
echo "bindd = SUPER SHIFT, S, Steam Gaming Mode, exec, $terminal_cmd $SWITCH_BIN"
echo "bindd = SUPER SHIFT, R, Exit Gaming Mode, exec, $RETURN_BIN"
else
echo "bind = SUPER SHIFT, S, exec, $terminal_cmd $SWITCH_BIN"
echo "bind = SUPER SHIFT, R, exec, $RETURN_BIN"
fi
echo "# End Gaming Mode bindings"
} >> "$BINDINGS_CONFIG" || die "failed to add bindings to $BINDINGS_CONFIG"
ADDED_BINDINGS=1
# Reload Hyprland config
hyprctl reload >/dev/null 2>&1 || info "Hyprland reload may have failed; relog if binds inactive."
}
check_component() {
case "$1" in
launcher)
[ -x "$SWITCH_BIN" ] && [ -x "$RETURN_BIN" ] || {
err "launcher check failed"
return 1
}
;;
shortcuts)
if [ ! -f "$BINDINGS_CONFIG" ] || ! grep -q "# Gaming Mode bindings" "$BINDINGS_CONFIG"; then
err "shortcuts check failed"
return 1
fi
;;
capabilities)
if [[ "${PERFORMANCE_MODE,,}" == "enabled" ]] && command -v gamescope >/dev/null 2>&1; then
if ! getcap "$(command -v gamescope)" 2>/dev/null | grep -q 'cap_sys_nice'; then
info "gamescope lacks cap_sys_nice; --rt may be ignored."
fi
fi
;;
*)
err "unknown component: $1"
return 1
;;
esac
}
validate_deployment() {
local errors=0
for component in launcher shortcuts capabilities; do
check_component "$component" || ((errors++))
done
return $errors
}
execute_setup() {
validate_environment
info "Found bindings config at: $BINDINGS_CONFIG"
# Check if this is a first install or re-run
local is_reinstall=false
if [ -x "$SWITCH_BIN" ] && [ -x "$RETURN_BIN" ]; then
is_reinstall=true
fi
# Only check dependencies on fresh install or if user requests it
if [ "$is_reinstall" = false ]; then
check_steam_dependencies
else
info "Gaming mode already installed - skipping dependency check"
echo ""
read -p "Run dependency check anyway? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
check_steam_dependencies
fi
fi
# Handle reinstall option
if [ -x "$SWITCH_BIN" ] && [ -x "$RETURN_BIN" ]; then
# Launchers exist - offer fresh install option
info "Gaming mode launchers found"
echo ""
read -p "Gaming mode is already installed. Reinstall? [y/N]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
info "Installation cancelled"
exit 0
fi
fi
setup_requirements
deploy_launchers
configure_shortcuts
validate_deployment || die "deployment validation failed"
echo ""
info "✓ Install complete!"
info " Launch: Super+Shift+S"
info " Exit: Super+Shift+R"
info ""
info "Binaries: $TARGET_DIR"
info "Bindings: $BINDINGS_CONFIG"
info "Config: $CONFIG_FILE"
# Check if user needs to log out/reboot for group changes
if [ "$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 " (or reboot) for the changes to take effect."
echo ""
echo " The gaming mode has been installed, but the group permissions"
echo " will not be active until you log out and log back in."
echo ""
echo "════════════════════════════════════════════════════════════════"
echo ""
read -r -p "Press Enter to exit (remember to log out/log back in)..."
fi
}
gaming_mode::initialize() { validate_environment; setup_requirements; }
gaming_mode::install() { deploy_launchers; }
gaming_mode::configure() { configure_shortcuts; validate_deployment || die "validation failed"; }
execute_setup
# Intel Arc (xe driver) Gaming Mode Installer
# - Detects and differentiates Arc (xe) vs integrated (i915) GPUs
# - Applies GSK_RENDERER=gl fix for GTK4 apps on Arc GPUs
# - Installs appropriate drivers for NVIDIA, AMD, Intel Arc, Intel iGPU