#!/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" </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" < "$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