From 295c9df7624b7909bb98d0bc2ae5ad0c4ee18c67 Mon Sep 17 00:00:00 2001 From: 28allday Date: Sat, 28 Mar 2026 18:23:48 +0000 Subject: [PATCH] Initial commit: Intel Arc gaming mode installer for Omarchy Co-Authored-By: Claude Opus 4.6 (1M context) --- ARCGames_installv2.sh | 1829 +++++++++++++++++++++++++++++++++++++++++ ARCGames_uninstall.sh | 322 ++++++++ 2 files changed, 2151 insertions(+) create mode 100755 ARCGames_installv2.sh create mode 100755 ARCGames_uninstall.sh diff --git a/ARCGames_installv2.sh b/ARCGames_installv2.sh new file mode 100755 index 0000000..287553c --- /dev/null +++ b/ARCGames_installv2.sh @@ -0,0 +1,1829 @@ +#!/bin/bash +# +# ARCGames - Gaming Mode Installer for Intel Arc dGPUs +# Version: 1.5.0 +# +# Description: +# Sets up a SteamOS-like gaming experience on Arch Linux with Hyprland, +# specifically optimized for Intel Arc discrete GPUs (Alchemist, Battlemage). +# +# Features: +# - Steam and gaming dependencies installation +# - Mesa-git or stable Mesa driver selection +# - Gamescope session switching (Hyprland <-> Gaming Mode) +# - Performance tuning (GPU, audio, memory) +# - External Steam library auto-mounting +# +# Usage: +# ./ARCGames_install.sh [--help|--version] +# +# Keybinds (after installation): +# Super+Shift+S - Switch to Gaming Mode (from Hyprland) +# Super+Shift+R - Return to Desktop (from Gaming Mode) +# +############################################################################### + +set -uo pipefail + +ARCGAMES_VERSION="1.5.0" + +# Track mesa driver state for safe cleanup on interrupt +_MESA_REMOVAL_IN_PROGRESS=0 +cleanup_on_exit() { + if [[ "$_MESA_REMOVAL_IN_PROGRESS" -eq 1 ]]; then + echo "" >&2 + echo "================================================================" >&2 + echo " WARNING: Installation interrupted during mesa driver swap!" >&2 + echo " Your system may have no graphics driver installed." >&2 + echo " Recovery from TTY:" >&2 + echo " sudo pacman -S mesa lib32-mesa vulkan-intel lib32-vulkan-intel" >&2 + echo "================================================================" >&2 + echo "" >&2 + fi +} +trap cleanup_on_exit EXIT + +############################################################################### +# CONFIGURATION +############################################################################### + +CONFIG_FILE="/etc/gaming-mode.conf" +# Note: REAL_HOME not yet defined here, check both locations +[[ -f "${HOME}/.gaming-mode.conf" ]] && CONFIG_FILE="${HOME}/.gaming-mode.conf" +[[ -n "${SUDO_USER:-}" ]] && { + _sudo_home=$(getent passwd "$SUDO_USER" | cut -d: -f6) + [[ -n "$_sudo_home" && -f "$_sudo_home/.gaming-mode.conf" ]] && CONFIG_FILE="$_sudo_home/.gaming-mode.conf" +} + +# Parse config file safely (no arbitrary code execution) +if [[ -f "$CONFIG_FILE" ]]; then + while IFS='=' read -r _key _value; do + _key="${_key#"${_key%%[![:space:]]*}"}" # trim leading whitespace + _key="${_key%"${_key##*[![:space:]]}"}" # trim trailing whitespace + _value="${_value#"${_value%%[![:space:]]*}"}" + _value="${_value%"${_value##*[![:space:]]}"}" + case "$_key" in + PERFORMANCE_MODE) PERFORMANCE_MODE="$_value" ;; + USE_MESA_GIT) USE_MESA_GIT="$_value" ;; + esac + done < "$CONFIG_FILE" 2>/dev/null || true +fi + +: "${PERFORMANCE_MODE:=enabled}" +: "${USE_MESA_GIT:=1}" # 1 = mesa-git from AUR (recommended), 0 = stable mesa + +# Global state +NEEDS_RELOGIN=0 +INTEL_ARC_VK_DEVICE="" +INTEL_ARC_DRM_CARD="" + +# Resolve actual user (handles sudo case) +REAL_USER="${SUDO_USER:-$USER}" +REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6) + +############################################################################### +# UTILITY FUNCTIONS +############################################################################### + +info() { echo "[*] $*"; } +warn() { echo "[!] $*" >&2; } +err() { echo "[!] $*" >&2; } + +die() { + local msg="$1" + local code="${2:-1}" + echo "FATAL: $msg" >&2 + logger -t arcgames "Installation failed: $msg" + exit "$code" +} + +check_package() { + pacman -Qi "$1" &>/dev/null +} + +check_aur_helper_functional() { + local helper="$1" + "$helper" --version &>/dev/null +} + +# Validate REAL_HOME was resolved (must be after die() is defined) +[[ -z "$REAL_HOME" ]] && die "Could not resolve home directory for user: $REAL_USER" + +# Run command as the original user (handles case where script is run with sudo) +run_as_user() { + if [[ -n "${SUDO_USER:-}" ]] && [[ "$EUID" -eq 0 ]]; then + sudo -u "$SUDO_USER" "$@" + else + "$@" + fi +} + +############################################################################### +# ENVIRONMENT VALIDATION +############################################################################### + +validate_environment() { + command -v pacman >/dev/null || die "pacman required" + command -v hyprctl >/dev/null || die "hyprctl required" + [[ -d "$REAL_HOME/.config/hypr" ]] || die "Hyprland config directory not found (~/.config/hypr)" + + # Ensure lspci is available 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 +} + +############################################################################### +# GPU DETECTION +############################################################################### + +# Check if a DRM card is an Intel iGPU (integrated) vs dGPU (discrete Arc) +is_intel_igpu() { + local card_path="$1" + local device_path="$card_path/device" + local pci_slot="" + + [[ -L "$device_path" ]] && pci_slot=$(basename "$(readlink -f "$device_path")") + [[ -z "$pci_slot" ]] && return 1 + + local device_info + device_info=$(lspci -s "$pci_slot" 2>/dev/null) + + # Arc dGPU patterns (NOT iGPUs) + if echo "$device_info" | grep -iqE 'arc|alchemist|battlemage|celestial'; then + return 1 + fi + + # iGPU patterns + if echo "$device_info" | grep -iqE 'uhd|iris|hd graphics|integrated'; then + return 0 + fi + + # xe driver = Arc dGPU, i915 can be either + local driver_link="$card_path/device/driver" + if [[ -L "$driver_link" ]]; then + local driver + driver=$(basename "$(readlink "$driver_link")") + [[ "$driver" == "xe" ]] && return 1 + fi + + # Fallback: PCI bus 00 is typically iGPU + [[ "$pci_slot" =~ ^0000:00: ]] && return 0 + + return 1 # Assume dGPU +} + +# Get the Vulkan device ID (vendor:device) for a PCI slot +get_vk_device_id() { + local pci_slot="$1" + local vendor device + + vendor=$(cat "/sys/bus/pci/devices/$pci_slot/vendor" 2>/dev/null | sed 's/0x//') + device=$(cat "/sys/bus/pci/devices/$pci_slot/device" 2>/dev/null | sed 's/0x//') + + if [[ -n "$vendor" && -n "$device" ]]; then + echo "${vendor}:${device}" + fi +} + +# Find Intel Arc dGPU with connected display +find_intel_arc_display_gpu() { + local found_arc=false + local arc_card="" + local arc_pci="" + local arc_has_display=false + + for card_path in /sys/class/drm/card[0-9]*; do + local card_name + card_name=$(basename "$card_path") + [[ "$card_name" == render* ]] && continue + + # Check for Intel GPU driver + local driver_link="$card_path/device/driver" + [[ -L "$driver_link" ]] || continue + + local driver + driver=$(basename "$(readlink "$driver_link")") + [[ "$driver" == "i915" || "$driver" == "xe" ]] || continue + + # Skip iGPUs - we only want discrete Arc + if is_intel_igpu "$card_path"; then + info "Skipping Intel iGPU: $card_name" + continue + fi + + # This is an Intel Arc dGPU + found_arc=true + local pci_slot + pci_slot=$(basename "$(readlink -f "$card_path/device")") + + # Check for connected display + for connector in "$card_path"/"$card_name"-*/status; do + if [[ -f "$connector" ]] && grep -q "^connected$" "$connector" 2>/dev/null; then + arc_card="$card_name" + arc_pci="$pci_slot" + arc_has_display=true + info "Intel Arc dGPU with display: $card_name (PCI: $pci_slot)" + break 2 + fi + done + + # Remember Arc GPU even if no display connected + if [[ -z "$arc_card" ]]; then + arc_card="$card_name" + arc_pci="$pci_slot" + fi + done + + $found_arc || return 1 + + # Set global variables + INTEL_ARC_DRM_CARD="$arc_card" + INTEL_ARC_VK_DEVICE=$(get_vk_device_id "$arc_pci") + + if $arc_has_display; then + info "Monitor connected to Intel Arc: $INTEL_ARC_DRM_CARD" + else + warn "No monitor detected on Intel Arc, but will use: $INTEL_ARC_DRM_CARD" + fi + + [[ -n "$INTEL_ARC_VK_DEVICE" ]] && info "Vulkan device ID: $INTEL_ARC_VK_DEVICE" + + return 0 +} + +# Verify Intel Arc GPU is present and detect display GPU +check_intel_arc() { + local gpu_info + gpu_info=$(lspci 2>/dev/null | grep -iE 'vga|3d|display' || echo "") + + # Verify Intel GPU presence + if ! echo "$gpu_info" | grep -iq intel; then + die "No Intel GPU detected. This script is for Intel Arc dGPUs only." + fi + + # Check for Arc-specific patterns + if ! echo "$gpu_info" | grep -iqE 'arc|alchemist|battlemage|celestial'; then + local has_xe=false + for card in /sys/class/drm/card[0-9]*/device/driver; do + if [[ -L "$card" ]]; then + local driver + driver=$(basename "$(readlink "$card")") + if [[ "$driver" == "xe" ]]; then + has_xe=true + break + fi + fi + done + $has_xe || warn "No Intel Arc pattern found in lspci. Checking for discrete Intel GPU..." + fi + + # Find Arc dGPU with display + if ! find_intel_arc_display_gpu; then + die "No Intel Arc discrete GPU found. This script is for Intel Arc dGPUs only." + fi + + info "Intel Arc dGPU detected and selected: $INTEL_ARC_DRM_CARD" + return 0 +} + +############################################################################### +# MULTILIB REPOSITORY +############################################################################### + +enable_multilib_repo() { + info "Enabling multilib repository..." + sudo cp /etc/pacman.conf "/etc/pacman.conf.backup.$(date +%Y%m%d%H%M%S)" || die "Failed to backup pacman.conf" + # Uncomment only the [multilib] header and its Include line + sudo sed -i '/^#\[multilib\]$/{s/^#//;n;s/^#//}' /etc/pacman.conf || die "Failed to enable multilib" + + if grep -q "^\[multilib\]" /etc/pacman.conf 2>/dev/null; then + info "Multilib repository enabled successfully" + sudo pacman -Syu --noconfirm || die "Failed to update system" + else + die "Failed to enable multilib repository" + fi +} + +############################################################################### +# MESA MANAGEMENT +############################################################################### + +rollback_to_stable_mesa() { + info "Rolling back from mesa-git to stable mesa..." + + # Identify installed mesa-git packages + local -a git_pkgs_to_remove=() + check_package "lib32-mesa-git" && git_pkgs_to_remove+=("lib32-mesa-git") + check_package "mesa-git" && git_pkgs_to_remove+=("mesa-git") + + # Remove mesa-git packages (lib32 first due to dependency) + if ((${#git_pkgs_to_remove[@]})); then + info "Removing mesa-git packages: ${git_pkgs_to_remove[*]}" + + if check_package "lib32-mesa-git"; then + sudo pacman -Rdd --noconfirm lib32-mesa-git 2>/dev/null || warn "Failed to remove lib32-mesa-git" + fi + + if check_package "mesa-git"; then + sudo pacman -Rdd --noconfirm mesa-git 2>/dev/null || die "Failed to remove mesa-git" + fi + fi + + # Install stable mesa packages + info "Installing stable mesa packages..." + local -a stable_pkgs=("mesa" "vulkan-intel" "vulkan-mesa-layers") + + if grep -q "^\[multilib\]" /etc/pacman.conf 2>/dev/null; then + stable_pkgs+=("lib32-mesa" "lib32-vulkan-intel" "lib32-vulkan-mesa-layers") + fi + + sudo pacman -S --needed --noconfirm "${stable_pkgs[@]}" || \ + die "Failed to install stable mesa packages. System may be in broken state! + Try manually: sudo pacman -S ${stable_pkgs[*]}" + + # Verify installation + if check_package "mesa"; then + info "Rollback complete - stable mesa installed" + else + die "Rollback verification failed - mesa not installed" + fi +} + +install_mesa_git() { + local multilib_enabled="$1" + + info "Installing mesa-git from AUR (recommended for Intel Arc)..." + + # Install build tools + info "Ensuring build tools are installed..." + sudo pacman -S --needed --noconfirm base-devel git || die "Failed to install build tools" + + # Find AUR helper + local aur_helper="" + if command -v yay >/dev/null 2>&1 && check_aur_helper_functional yay; then + aur_helper="yay" + elif command -v paru >/dev/null 2>&1 && check_aur_helper_functional paru; then + aur_helper="paru" + fi + + [[ -z "$aur_helper" ]] && die "No AUR helper found (yay or paru required for mesa-git). Install one first: + git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si" + + info "Using AUR helper: $aur_helper" + + # Check for and remove conflicting packages + local -a potential_conflicts=( + "lib32-vulkan-mesa-implicit-layers" "lib32-vulkan-mesa-layers" "lib32-vulkan-intel" "lib32-mesa" + "vulkan-mesa-implicit-layers" "vulkan-mesa-layers" "vulkan-intel" "mesa" + ) + local -a found_conflicts=() + for pkg in "${potential_conflicts[@]}"; do + pacman -Qi "$pkg" &>/dev/null && found_conflicts+=("$pkg") + done + + if ((${#found_conflicts[@]})); then + info "Removing conflicting packages: ${found_conflicts[*]}" + _MESA_REMOVAL_IN_PROGRESS=1 + # Use -Rdd to remove without dependency checks (mesa-git will satisfy deps) + sudo pacman -Rdd --noconfirm "${found_conflicts[@]}" || \ + die "Failed to remove conflicting packages: ${found_conflicts[*]}. Cannot install mesa-git with conflicting packages still present." + # Verify removal + sleep 1 + for pkg in "${found_conflicts[@]}"; do + if pacman -Qi "$pkg" &>/dev/null; then + warn "Retrying removal of $pkg..." + sudo pacman -Rdd --noconfirm "$pkg" || die "Failed to remove $pkg - cannot continue with mesa-git install" + fi + done + fi + + # Clear AUR helper cache for mesa-git + run_as_user rm -rf "${REAL_HOME}/.cache/yay/mesa-git" 2>/dev/null || true + run_as_user rm -rf "${REAL_HOME}/.cache/paru/clone/mesa-git" 2>/dev/null || true + + # Install mesa-git (lib32-mesa-git depends on it) + info "Building and installing mesa-git (this may take a while)..." + if ! run_as_user "$aur_helper" -S --noconfirm --removemake --cleanafter --overwrite '/usr/lib/*' \ + --answeredit None --answerclean None --answerdiff None mesa-git; then + # Check for working mesa driver + if ! pacman -Qi mesa-git &>/dev/null && ! pacman -Qi mesa &>/dev/null; then + die "Failed to install mesa-git and no mesa driver is installed! + Run: sudo pacman -S mesa lib32-mesa vulkan-intel lib32-vulkan-intel" + fi + die "Failed to install mesa-git" + fi + + # Verify installation + pacman -Qi mesa-git &>/dev/null || die "mesa-git installation verification failed" + info "mesa-git installed successfully" + + # Install lib32-mesa-git if multilib enabled (REQUIRED for 32-bit games/Steam) + # NOTE: lib32-mesa-git MUST be used with mesa-git - stable lib32-vulkan-intel is incompatible! + if [[ "$multilib_enabled" == "true" ]]; then + info "Building and installing lib32-mesa-git (required for Steam)..." + info "This may take 10-30 minutes to compile..." + + # Remove conflicting lib32 packages BEFORE attempting install + # This prevents the interactive "Remove lib32-mesa? [y/N]" prompt + # which --noconfirm defaults to N (abort) + local -a _lib32_conflicts=() + for _pkg in lib32-vulkan-mesa-implicit-layers lib32-vulkan-mesa-layers \ + lib32-vulkan-intel lib32-mesa; do + pacman -Qi "$_pkg" &>/dev/null && _lib32_conflicts+=("$_pkg") + done + if ((${#_lib32_conflicts[@]})); then + info "Removing conflicting lib32 packages: ${_lib32_conflicts[*]}" + sudo pacman -Rdd --noconfirm "${_lib32_conflicts[@]}" || \ + die "Failed to remove conflicting lib32 packages" + sleep 1 + fi + + local max_attempts=2 + local attempt=1 + local lib32_success=false + + while [[ $attempt -le $max_attempts ]] && [[ "$lib32_success" == "false" ]]; do + # On retries, re-check for conflicts (may have been reinstalled by failed build) + if [[ $attempt -gt 1 ]]; then + local -a _retry_conflicts=() + for _pkg in lib32-vulkan-mesa-implicit-layers lib32-vulkan-mesa-layers \ + lib32-vulkan-intel lib32-mesa; do + pacman -Qi "$_pkg" &>/dev/null && _retry_conflicts+=("$_pkg") + done + if ((${#_retry_conflicts[@]})); then + info "Removing conflicting lib32 packages: ${_retry_conflicts[*]}" + sudo pacman -Rdd --noconfirm "${_retry_conflicts[@]}" 2>/dev/null || true + sleep 1 + fi + fi + + # Clear AUR helper cache for lib32-mesa-git to force fresh install + run_as_user rm -rf "${REAL_HOME}/.cache/yay/lib32-mesa-git" 2>/dev/null || true + run_as_user rm -rf "${REAL_HOME}/.cache/paru/clone/lib32-mesa-git" 2>/dev/null || true + + if run_as_user "$aur_helper" -S --noconfirm --removemake --cleanafter --overwrite '/usr/lib32/*' \ + --answeredit None --answerclean None --answerdiff None lib32-mesa-git; then + lib32_success=true + info "lib32-mesa-git installed successfully" + else + warn "lib32-mesa-git build attempt $attempt failed" + ((attempt++)) + [[ $attempt -le $max_attempts ]] && info "Retrying..." + fi + done + + if [[ "$lib32_success" == "false" ]]; then + die "Failed to install lib32-mesa-git after $max_attempts attempts. +This is REQUIRED when using mesa-git (stable lib32-vulkan-intel is incompatible). + +To fix manually: + 1. $aur_helper -S lib32-mesa-git + +Or rollback to stable mesa: + 1. sudo pacman -Rdd mesa-git + 2. sudo pacman -S mesa lib32-mesa vulkan-intel lib32-vulkan-intel" + fi + fi + + _MESA_REMOVAL_IN_PROGRESS=0 + info "mesa-git installation complete" +} + +############################################################################### +# STEAM DEPENDENCIES +############################################################################### + +check_steam_dependencies() { + info "Checking Steam dependencies for Intel Arc..." + + #--------------------------------------------------------------------------- + # System Update + #--------------------------------------------------------------------------- + echo "" + echo "================================================================" + echo " SYSTEM UPDATE RECOMMENDED" + echo "================================================================" + echo "" + read -p "Upgrade system now? [Y/n]: " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + info "Upgrading system..." + sudo pacman -Syu --noconfirm || die "Failed to upgrade system" + fi + echo "" + + #--------------------------------------------------------------------------- + # Mesa Driver Selection + #--------------------------------------------------------------------------- + local current_mesa="none" + local has_mesa_git=false + + if check_package "mesa-git"; then + current_mesa="mesa-git" + has_mesa_git=true + elif check_package "mesa"; then + current_mesa="stable" + fi + + echo "================================================================" + echo " MESA DRIVER SELECTION" + echo "================================================================" + echo "" + [[ "$current_mesa" != "none" ]] && echo " Currently installed: $current_mesa" && echo "" + echo " Choose your Mesa driver:" + echo "" + echo " [1] mesa-git (Recommended for Intel Arc)" + echo " - Latest drivers from Mesa development branch" + echo " - Best performance and newest fixes for Arc GPUs" + echo " - Built from AUR (takes longer to install/update)" + echo "" + echo " [2] Stable mesa" + echo " - Official Arch Linux packages" + echo " - Faster to install, standard updates" + echo " - May lack latest Intel Arc optimizations" + echo "" + read -p "Select driver [1/2] (default: 1): " -n 1 -r mesa_choice + echo + + if [[ "$mesa_choice" == "2" ]]; then + USE_MESA_GIT=0 + info "Using stable mesa packages" + + # Handle rollback if switching from mesa-git + if $has_mesa_git; then + echo "" + echo "================================================================" + echo " ROLLBACK: mesa-git -> stable mesa" + echo "================================================================" + echo "" + echo " This will:" + echo " - Remove mesa-git and lib32-mesa-git" + echo " - Install stable mesa, lib32-mesa, vulkan-intel, lib32-vulkan-intel" + echo "" + read -p "Proceed with rollback? [Y/n]: " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + rollback_to_stable_mesa + else + warn "Rollback cancelled - keeping mesa-git" + USE_MESA_GIT=1 + fi + fi + else + USE_MESA_GIT=1 + info "Using mesa-git from AUR" + fi + echo "" + + #--------------------------------------------------------------------------- + # Multilib Repository Check + #--------------------------------------------------------------------------- + local -a missing_deps=() + local -a optional_deps=() + local multilib_enabled=false + + 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)" + echo "" + echo "================================================================" + echo " MULTILIB REPOSITORY REQUIRED" + echo "================================================================" + echo "" + echo " Steam requires 32-bit libraries from the multilib repository." + echo "" + read -p "Enable multilib repository now? [Y/n]: " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + enable_multilib_repo + multilib_enabled=true + else + die "Multilib repository is required for Steam" + fi + fi + + #--------------------------------------------------------------------------- + # Define Package Lists + #--------------------------------------------------------------------------- + local -a core_deps=( + "steam" + "lib32-vulkan-icd-loader" + "vulkan-icd-loader" + "mesa-utils" + "lib32-glibc" + "lib32-gcc-libs" + "lib32-libx11" + "lib32-libxss" + "lib32-alsa-plugins" + "lib32-libpulse" + "lib32-openal" + "lib32-nss" + "lib32-libcups" + "lib32-sdl2-compat" + "lib32-freetype2" + "lib32-fontconfig" + "lib32-libnm" + "networkmanager" + "gamemode" + "lib32-gamemode" + "ttf-liberation" + "xdg-user-dirs" + "kbd" + ) + + # Add stable mesa if not using mesa-git + if [[ "${USE_MESA_GIT:-1}" -eq 0 ]]; then + core_deps+=("mesa" "lib32-mesa") + fi + + local -a gpu_deps=( + "intel-media-driver" + "vulkan-tools" + ) + + # Add stable vulkan-intel if not using mesa-git + if [[ "${USE_MESA_GIT:-1}" -eq 0 ]]; then + gpu_deps+=("vulkan-intel" "lib32-vulkan-intel" "vulkan-mesa-layers") + fi + + local -a recommended_deps=( + "gamescope" + "mangohud" + "lib32-mangohud" + "proton-ge-custom-bin" + "proton-cachyos-slr" + "udisks2" + ) + + #--------------------------------------------------------------------------- + # Check Dependencies + #--------------------------------------------------------------------------- + info "Checking core Steam dependencies..." + for dep in "${core_deps[@]}"; do + check_package "$dep" || missing_deps+=("$dep") + done + + info "Checking Intel GPU dependencies..." + for dep in "${gpu_deps[@]}"; do + check_package "$dep" || missing_deps+=("$dep") + done + + # Check mesa-git packages + local mesa_git_needed=false + if [[ "${USE_MESA_GIT:-1}" -eq 1 ]]; then + info "Checking mesa-git (AUR)..." + if ! check_package "mesa-git"; then + mesa_git_needed=true + info "mesa-git: not installed (will install from AUR)" + else + local current_mesa_ver + current_mesa_ver=$(pacman -Q mesa-git 2>/dev/null | awk '{print $2}') + info "mesa-git: already installed ($current_mesa_ver)" + echo "" + read -p "Rebuild mesa-git from latest source? [y/N]: " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + mesa_git_needed=true + info "mesa-git will be rebuilt from latest source" + fi + fi + + if $multilib_enabled && ! check_package "lib32-mesa-git"; then + mesa_git_needed=true + info "lib32-mesa-git: not installed (will install from AUR)" + elif $multilib_enabled; then + info "lib32-mesa-git: already installed" + fi + fi + + info "Checking recommended dependencies..." + for dep in "${recommended_deps[@]}"; do + check_package "$dep" || optional_deps+=("$dep") + done + + #--------------------------------------------------------------------------- + # Display Results + #--------------------------------------------------------------------------- + echo "" + echo "================================================================" + echo " STEAM DEPENDENCY CHECK RESULTS" + echo "================================================================" + echo "" + + # Clean missing deps array + local -a clean_missing=() + for item in "${missing_deps[@]}"; do + [[ -n "$item" && "$item" != "multilib-repository" ]] && clean_missing+=("$item") + done + missing_deps=("${clean_missing[@]+"${clean_missing[@]}"}") + + # Show mesa driver status + if [[ "${USE_MESA_GIT:-1}" -eq 1 ]]; then + echo " MESA DRIVER: mesa-git (Intel Arc optimized)" + if $mesa_git_needed; then + echo " - Will be built and installed from AUR" + else + echo " - Already installed" + fi + else + echo " MESA DRIVER: Stable mesa (official packages)" + fi + echo "" + + #--------------------------------------------------------------------------- + # Install mesa-git (FIRST, before other packages) + #--------------------------------------------------------------------------- + if [[ "${USE_MESA_GIT:-1}" -eq 1 ]] && $mesa_git_needed; then + echo "================================================================" + echo " MESA-GIT INSTALLATION (AUR)" + echo "================================================================" + echo "" + echo " Building mesa-git from AUR..." + echo " This may take 10-30 minutes depending on your system." + echo "" + install_mesa_git "$multilib_enabled" + echo "" + fi + + #--------------------------------------------------------------------------- + # Verify mesa-git provides required vulkan drivers before continuing + #--------------------------------------------------------------------------- + if [[ "${USE_MESA_GIT:-1}" -eq 1 ]]; then + if ! pacman -Qi mesa-git &>/dev/null; then + die "mesa-git is not installed. Cannot continue." + fi + if [[ "$multilib_enabled" == "true" ]] && ! pacman -Qi lib32-mesa-git &>/dev/null; then + die "lib32-mesa-git is not installed but is required for Steam. +Run: yay -S lib32-mesa-git" + fi + info "Verified: mesa-git and lib32-mesa-git are installed" + fi + + #--------------------------------------------------------------------------- + # Install Missing Packages + #--------------------------------------------------------------------------- + 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 --noconfirm "${missing_deps[@]}" || die "Failed to install dependencies" + info "Required dependencies installed successfully" + else + die "Missing required Steam dependencies" + fi + else + info "All required pacman dependencies are installed!" + fi + + #--------------------------------------------------------------------------- + # Install Optional Packages + #--------------------------------------------------------------------------- + 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..." + + 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" + fi + + if ((${#aur_optional[@]})); then + local aur_helper="" + command -v yay >/dev/null 2>&1 && check_aur_helper_functional yay && aur_helper="yay" + [[ -z "$aur_helper" ]] && command -v paru >/dev/null 2>&1 && check_aur_helper_functional paru && aur_helper="paru" + + if [[ -n "$aur_helper" ]]; then + read -p "Install AUR packages with $aur_helper? [y/N]: " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + run_as_user "$aur_helper" -S --needed --noconfirm "${aur_optional[@]}" || info "Some AUR packages failed" + fi + fi + fi + fi + fi + + check_steam_config +} + +############################################################################### +# STEAM CONFIGURATION +############################################################################### + +check_steam_config() { + info "Checking Steam configuration..." + + # Check the real user's groups (not root's when run with sudo) + local user_groups + user_groups=$(id -Gn "$REAL_USER" 2>/dev/null || groups "$REAL_USER" 2>/dev/null || echo "") + + local missing_groups=() + echo "$user_groups" | grep -qw 'video' || missing_groups+=("video") + echo "$user_groups" | grep -qw 'input' || missing_groups+=("input") + echo "$user_groups" | grep -qw 'wheel' || missing_groups+=("wheel") + + if ((${#missing_groups[@]})); then + echo "" + echo "================================================================" + echo " USER GROUP PERMISSIONS" + echo "================================================================" + echo "" + read -p "Add $REAL_USER to ${missing_groups[*]} group(s)? [Y/n]: " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + local groups_to_add + groups_to_add=$(IFS=,; echo "${missing_groups[*]}") + if sudo usermod -aG "$groups_to_add" "$REAL_USER"; then + info "Successfully added $REAL_USER to group(s): $groups_to_add" + NEEDS_RELOGIN=1 + fi + fi + else + info "User $REAL_USER is in video, input, and wheel groups - permissions OK" + fi +} + +############################################################################### +# PERFORMANCE CONFIGURATION +############################################################################### + +setup_performance_permissions() { + local udev_rules_file="/etc/udev/rules.d/99-gaming-performance.rules" + local sudoers_file="/etc/sudoers.d/gaming-mode-sysctl" + + if [[ -f "$udev_rules_file" ]] && [[ -f "$sudoers_file" ]]; then + info "Performance permissions already configured" + return 0 + fi + + echo "" + echo "================================================================" + echo " PERFORMANCE PERMISSIONS SETUP" + echo "================================================================" + echo "" + read -p "Set up passwordless performance controls? [Y/n]: " -n 1 -r + echo + + [[ $REPLY =~ ^[Nn]$ ]] && { info "Skipping permissions setup"; return 0; } + + # Udev rules for GPU frequency control + if [[ ! -f "$udev_rules_file" ]]; then + info "Creating udev rules for Intel Arc performance control..." + sudo tee "$udev_rules_file" > /dev/null <<'UDEV_RULES' +# Gaming Mode Performance Control Rules - Intel Arc +# Group-writable (video group) instead of world-writable for security +KERNEL=="cpu[0-9]*", SUBSYSTEM=="cpu", ACTION=="add", RUN+="/bin/sh -c 'chgrp video /sys/devices/system/cpu/%k/cpufreq/scaling_governor && chmod 664 /sys/devices/system/cpu/%k/cpufreq/scaling_governor'" +# Intel Xe driver (Arc GPUs) +KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="xe", ACTION=="add", RUN+="/bin/sh -c 'chgrp video /sys/class/drm/%k/gt_boost_freq_mhz && chmod 664 /sys/class/drm/%k/gt_boost_freq_mhz'" +KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="xe", ACTION=="add", RUN+="/bin/sh -c 'chgrp video /sys/class/drm/%k/gt_min_freq_mhz && chmod 664 /sys/class/drm/%k/gt_min_freq_mhz'" +KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="xe", ACTION=="add", RUN+="/bin/sh -c 'chgrp video /sys/class/drm/%k/gt_max_freq_mhz && chmod 664 /sys/class/drm/%k/gt_max_freq_mhz'" +# Fallback for i915 driver +KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/sh -c 'chgrp video /sys/class/drm/%k/gt_boost_freq_mhz && chmod 664 /sys/class/drm/%k/gt_boost_freq_mhz'" +KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/sh -c 'chgrp video /sys/class/drm/%k/gt_min_freq_mhz && chmod 664 /sys/class/drm/%k/gt_min_freq_mhz'" +KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/sh -c 'chgrp video /sys/class/drm/%k/gt_max_freq_mhz && chmod 664 /sys/class/drm/%k/gt_max_freq_mhz'" +UDEV_RULES + sudo udevadm control --reload-rules || true + sudo udevadm trigger --subsystem-match=cpu --subsystem-match=drm || true + fi + + # Sudoers rules for sysctl + if [[ ! -f "$sudoers_file" ]]; then + info "Creating sudoers rule for Performance Mode..." + sudo tee "$sudoers_file" > /dev/null << 'SUDOERS_PERF' +# Gaming Mode - Allow passwordless sysctl for performance tuning +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_autogroup_enabled=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_migration_cost_ns=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_min_granularity_ns=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_latency_ns=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.swappiness=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_ratio=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_background_ratio=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_writeback_centisecs=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_expire_centisecs=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_watches=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_instances=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.file-max=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.rmem_max=* +%video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.wmem_max=* +SUDOERS_PERF + sudo chmod 0440 "$sudoers_file" + fi + + # Memory lock limits + local memlock_file="/etc/security/limits.d/99-gaming-memlock.conf" + if [[ ! -f "$memlock_file" ]]; then + info "Creating memlock limits..." + # Set memlock to ~25% of total RAM (in KB) + local total_ram_kb + total_ram_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo) + local memlock_kb=$(( total_ram_kb / 4 )) + # Clamp: minimum 2GB, maximum 16GB + (( memlock_kb < 2097152 )) && memlock_kb=2097152 + (( memlock_kb > 16777216 )) && memlock_kb=16777216 + info "Setting memlock to $(( memlock_kb / 1024 ))MB (based on $(( total_ram_kb / 1024 ))MB total RAM)" + sudo tee "$memlock_file" > /dev/null << MEMLOCKCONF +# Gaming memlock limits (auto-calculated: ~25% of total RAM) +* soft memlock ${memlock_kb} +* hard memlock ${memlock_kb} +MEMLOCKCONF + fi + + # PipeWire low-latency config + local pipewire_conf_dir="/etc/pipewire/pipewire.conf.d" + local pipewire_conf="$pipewire_conf_dir/10-gaming-latency.conf" + if [[ ! -f "$pipewire_conf" ]]; then + info "Creating PipeWire low-latency configuration..." + sudo mkdir -p "$pipewire_conf_dir" + sudo tee "$pipewire_conf" > /dev/null << 'PIPEWIRECONF' +# Low-latency PipeWire tuning +context.properties = { + default.clock.min-quantum = 256 +} +PIPEWIRECONF + fi + + info "Performance permissions configured" +} + +setup_shader_cache() { + local env_file="/etc/environment.d/99-shader-cache.conf" + + if [[ -f "$env_file" ]]; then + info "Shader cache configuration already exists" + return 0 + fi + + echo "" + echo "================================================================" + echo " SHADER CACHE OPTIMIZATION" + echo "================================================================" + echo "" + read -p "Configure shader cache optimization? [Y/n]: " -n 1 -r + echo + + [[ $REPLY =~ ^[Nn]$ ]] && return 0 + + info "Creating shader cache configuration..." + sudo mkdir -p /etc/environment.d + sudo tee "$env_file" > /dev/null << 'SHADERCACHE' +# Shader cache tuning for Intel Arc +MESA_SHADER_CACHE_MAX_SIZE=12G +MESA_SHADER_CACHE_DISABLE_CLEANUP=1 +__GL_SHADER_DISK_CACHE=1 +__GL_SHADER_DISK_CACHE_SIZE=12884901888 +__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1 +DXVK_STATE_CACHE=1 +SHADERCACHE + sudo chmod 644 "$env_file" + info "Shader cache configured for Intel Arc" +} + +setup_requirements() { + local -a required_packages=( + "steam" "gamescope" "mangohud" "python" "python-evdev" + "libcap" "gamemode" "curl" "pciutils" "ntfs-3g" "xcb-util-cursor" + ) + 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 --noconfirm "${packages_to_install[@]}" || die "package install failed" + else + die "Required packages missing" + fi + else + info "All required packages present." + fi + + setup_performance_permissions + setup_shader_cache + + # Grant cap_sys_nice to gamescope + 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 + echo "" + read -p "Grant cap_sys_nice to gamescope? [Y/n]: " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + sudo setcap 'cap_sys_nice=eip' "$(command -v gamescope)" || warn "Failed to set capability" + info "Capability granted to gamescope" + fi + fi + fi +} + +############################################################################### +# SESSION SWITCHING +############################################################################### + +setup_session_switching() { + echo "" + echo "================================================================" + echo " SESSION SWITCHING SETUP (Hyprland <-> Gamescope)" + echo " Intel Arc dGPU Configuration" + echo "================================================================" + echo "" + read -p "Set up session switching? [Y/n]: " -n 1 -r + echo + [[ $REPLY =~ ^[Nn]$ ]] && return 0 + + # Use global REAL_USER and REAL_HOME for consistency + local current_user="$REAL_USER" + local user_home="$REAL_HOME" + + #--------------------------------------------------------------------------- + # Detect Monitor Resolution + #--------------------------------------------------------------------------- + local monitor_width=1920 + local monitor_height=1080 + local monitor_refresh=60 + local monitor_output="" + + if command -v hyprctl >/dev/null 2>&1; then + local monitor_json + monitor_json=$(hyprctl monitors -j 2>/dev/null) + if [[ -n "$monitor_json" ]]; then + if command -v jq >/dev/null 2>&1; then + monitor_width=$(echo "$monitor_json" | jq -r '.[0].width // 1920') || monitor_width=1920 + monitor_height=$(echo "$monitor_json" | jq -r '.[0].height // 1080') || monitor_height=1080 + monitor_refresh=$(echo "$monitor_json" | jq -r '.[0].refreshRate // 60 | floor') || monitor_refresh=60 + monitor_output=$(echo "$monitor_json" | jq -r '.[0].name // ""') || monitor_output="" + else + # Fallback: parse JSON without jq + monitor_width=$(echo "$monitor_json" | grep -o '"width":[[:space:]]*[0-9]*' | head -1 | grep -o '[0-9]*$') || monitor_width=1920 + monitor_height=$(echo "$monitor_json" | grep -o '"height":[[:space:]]*[0-9]*' | head -1 | grep -o '[0-9]*$') || monitor_height=1080 + # refreshRate can be decimal (e.g., 143.998), extract integer part + monitor_refresh=$(echo "$monitor_json" | grep -o '"refreshRate":[[:space:]]*[0-9.]*' | head -1 | grep -o '[0-9.]*$' | cut -d. -f1) || monitor_refresh=60 + monitor_output=$(echo "$monitor_json" | grep -o '"name":[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/') || monitor_output="" + fi + fi + fi + + info "Detected display: ${monitor_width}x${monitor_height}@${monitor_refresh}Hz${monitor_output:+ on $monitor_output}" + + #--------------------------------------------------------------------------- + # Install ChimeraOS Packages + #--------------------------------------------------------------------------- + info "Checking for ChimeraOS gamescope-session packages..." + + local -a aur_packages=() + local -a packages_to_remove=() + local -a steam_compat_scripts=( + "/usr/bin/steamos-session-select" + "/usr/bin/steamos-update" + "/usr/bin/jupiter-biosupdate" + "/usr/bin/steamos-select-branch" + ) + + # Check gamescope-session base package + if ! check_package "gamescope-session-git" && ! check_package "gamescope-session"; then + aur_packages+=("gamescope-session-git") + fi + + # Check gamescope-session-steam package + if ! check_package "gamescope-session-steam-git"; then + if check_package "gamescope-session-steam"; then + warn "gamescope-session-steam (non-git) is installed but may be missing Steam compatibility scripts" + local scripts_missing=false + for script in "${steam_compat_scripts[@]}"; do + [[ ! -f "$script" ]] && { scripts_missing=true; break; } + done + $scripts_missing && packages_to_remove+=("gamescope-session-steam") + fi + aur_packages+=("gamescope-session-steam-git") + else + local scripts_missing=false + for script in "${steam_compat_scripts[@]}"; do + if [[ ! -f "$script" ]]; then + warn "gamescope-session-steam-git is installed but $script is missing!" + scripts_missing=true + break + fi + done + if $scripts_missing; then + warn "Reinstalling gamescope-session-steam-git to fix missing scripts..." + packages_to_remove+=("gamescope-session-steam-git") + aur_packages+=("gamescope-session-steam-git") + fi + fi + + # Find AUR helper + local aur_helper="" + command -v yay >/dev/null 2>&1 && check_aur_helper_functional yay && aur_helper="yay" + [[ -z "$aur_helper" ]] && command -v paru >/dev/null 2>&1 && check_aur_helper_functional paru && aur_helper="paru" + + # Remove problematic packages + if ((${#packages_to_remove[@]})) && [[ -n "$aur_helper" ]]; then + info "Removing incomplete packages: ${packages_to_remove[*]}" + sudo pacman -Rns --noconfirm "${packages_to_remove[@]}" 2>/dev/null || true + fi + + # Install missing packages + if ((${#aur_packages[@]})); then + if [[ -n "$aur_helper" ]]; then + read -p "Install ChimeraOS session packages with $aur_helper? [Y/n]: " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + info "Installing ChimeraOS gamescope-session packages..." + run_as_user "$aur_helper" -S --noconfirm --overwrite '/usr/share/gamescope-session*' --overwrite '/usr/bin/steamos-*' --answeredit None --answerclean None --answerdiff None "${aur_packages[@]}" || \ + err "Failed to install gamescope-session packages" + fi + else + warn "No AUR helper found (yay/paru). Please install manually: ${aur_packages[*]}" + fi + else + info "ChimeraOS gamescope-session packages already installed (correct -git versions)" + fi + + #--------------------------------------------------------------------------- + # NetworkManager Integration + #--------------------------------------------------------------------------- + info "Setting up NetworkManager integration..." + + if systemctl is-active --quiet iwd; then + sudo mkdir -p /etc/NetworkManager/conf.d + sudo tee /etc/NetworkManager/conf.d/10-iwd-backend.conf > /dev/null << 'NM_IWD_CONF' +[device] +wifi.backend=iwd +wifi.scan-rand-mac-address=no +[main] +plugins=ifupdown,keyfile +[ifupdown] +managed=false +[connection] +connection.autoconnect-slaves=0 +NM_IWD_CONF + fi + + # NM start script + local nm_start_script="/usr/local/bin/gamescope-nm-start" + sudo tee "$nm_start_script" > /dev/null << 'NM_START' +#!/bin/bash +NM_MARKER="/tmp/.gamescope-started-nm" +if ! systemctl is-active --quiet NetworkManager.service; then + if systemctl start NetworkManager.service; then + touch "$NM_MARKER" + for _ in {1..20}; do + nmcli general status &>/dev/null && break + sleep 0.5 + done + fi +fi +NM_START + sudo chmod +x "$nm_start_script" + + # NM stop script - also restores iwd and bluetooth after gaming session + local nm_stop_script="/usr/local/bin/gamescope-nm-stop" + sudo tee "$nm_stop_script" > /dev/null << 'NM_STOP' +#!/bin/bash +NM_MARKER="/tmp/.gamescope-started-nm" +if [ -f "$NM_MARKER" ]; then + rm -f "$NM_MARKER" + systemctl stop NetworkManager.service 2>/dev/null || true +fi + +# Restore iwd (WiFi) and bluetooth if they are enabled but got disrupted +if systemctl is-enabled --quiet iwd.service 2>/dev/null; then + systemctl restart iwd.service 2>/dev/null || true +fi +if systemctl is-enabled --quiet bluetooth.service 2>/dev/null; then + systemctl restart bluetooth.service 2>/dev/null || true +fi +NM_STOP + sudo chmod +x "$nm_stop_script" + + #--------------------------------------------------------------------------- + # Polkit Rules + #--------------------------------------------------------------------------- + local polkit_created=false + + local polkit_rules="/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules" + if ! sudo test -f "$polkit_rules"; then + sudo mkdir -p /etc/polkit-1/rules.d + sudo tee "$polkit_rules" > /dev/null << 'POLKIT_RULES' +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.NetworkManager.enable-disable-network" || + action.id == "org.freedesktop.NetworkManager.enable-disable-wifi" || + action.id == "org.freedesktop.NetworkManager.network-control" || + action.id == "org.freedesktop.NetworkManager.wifi.scan" || + action.id == "org.freedesktop.NetworkManager.settings.modify.system" || + action.id == "org.freedesktop.NetworkManager.settings.modify.own" || + action.id == "org.freedesktop.NetworkManager.settings.modify.hostname") && + subject.isInGroup("wheel")) { + return polkit.Result.YES; + } +}); +POLKIT_RULES + sudo chmod 644 "$polkit_rules" + polkit_created=true + fi + + local udisks_polkit="/etc/polkit-1/rules.d/50-udisks-gaming.rules" + if ! sudo test -f "$udisks_polkit"; then + sudo tee "$udisks_polkit" > /dev/null << 'UDISKS_POLKIT' +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.udisks2.filesystem-mount" || + action.id == "org.freedesktop.udisks2.filesystem-mount-system" || + action.id == "org.freedesktop.udisks2.filesystem-unmount-others" || + action.id == "org.freedesktop.udisks2.encrypted-unlock" || + action.id == "org.freedesktop.udisks2.power-off-drive") && + subject.isInGroup("wheel")) { + return polkit.Result.YES; + } +}); +UDISKS_POLKIT + sudo chmod 644 "$udisks_polkit" + polkit_created=true + fi + + $polkit_created && { sudo systemctl restart polkit.service 2>/dev/null || true; info "Polkit rules created"; } + + #--------------------------------------------------------------------------- + # Gamescope Session Configuration + #--------------------------------------------------------------------------- + info "Creating gamescope-session-plus configuration for Intel Arc..." + + local env_dir="${user_home}/.config/environment.d" + local gamescope_conf="${env_dir}/gamescope-session-plus.conf" + run_as_user mkdir -p "$env_dir" + + local output_connector_line="" + [[ -n "$monitor_output" ]] && output_connector_line="OUTPUT_CONNECTOR=$monitor_output" + + run_as_user tee "$gamescope_conf" > /dev/null << GAMESCOPE_CONF +# Gamescope Session Plus Configuration +# Generated by ARCGames Installer v${ARCGAMES_VERSION} +# Intel Arc dGPU Configuration +# NOTE: environment.d format does NOT use 'export' keyword + +# Display configuration (managed by Steam - no hardcoded resolution) +${output_connector_line} + +# Adaptive sync / VRR disabled +ADAPTIVE_SYNC=0 + +# Intel Arc Workarounds & Optimizations +# norbc = disable render buffer compression to avoid visual artifacts on Arc +INTEL_DEBUG=norbc +DISABLE_LAYER_MESA_ANTI_LAG=1 +VKD3D_CONFIG=dxr11,dxr +mesa_glthread=true +ANV_QUEUE_THREAD_DISABLE=1 + +# Storage and drive management +STEAM_ALLOW_DRIVE_UNMOUNT=1 + +# Misc +FCITX_NO_WAYLAND_DIAGNOSE=1 +SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0 +GAMESCOPE_CONF + info "Created $gamescope_conf" + + #--------------------------------------------------------------------------- + # Session Wrapper Script + #--------------------------------------------------------------------------- + local nm_wrapper="/usr/local/bin/gamescope-session-nm-wrapper" + sudo tee "$nm_wrapper" > /dev/null << 'NM_WRAPPER' +#!/bin/bash +# Gamescope session wrapper (NM + keybind monitor) +# Intel Arc configuration is handled via environment.d config file + +log() { logger -t gamescope-wrapper "$*"; echo "$*"; } + +cleanup() { + pkill -f steam-library-mount 2>/dev/null || true + pkill -f gaming-keybind-monitor 2>/dev/null || true + sudo -n /usr/local/bin/gamescope-nm-stop 2>/dev/null || true + rm -f /tmp/.gaming-session-active +} +trap cleanup EXIT INT TERM + +# Start NetworkManager +sudo -n /usr/local/bin/gamescope-nm-start 2>/dev/null || { + log "Warning: Could not start NetworkManager - Steam network features may not work" +} + +# Start Steam library drive auto-mounter +if [[ -x /usr/local/bin/steam-library-mount ]]; then + /usr/local/bin/steam-library-mount & + log "Steam library drive monitor started" +else + log "Warning: steam-library-mount not found - external Steam libraries will not auto-mount" +fi + +# Mark gaming session active +echo "gamescope" > /tmp/.gaming-session-active + +# Pre-flight check for keybind monitor +keybind_ok=true + +if ! python3 -c "import evdev" 2>/dev/null; then + log "WARNING: python-evdev not installed - Super+Shift+R keybind disabled" + log "Fix: sudo pacman -S python-evdev" + keybind_ok=false +fi + +if ! groups | grep -qw input; then + log "WARNING: User not in 'input' group - Super+Shift+R keybind disabled" + log "Fix: sudo usermod -aG input $USER && log out/in" + keybind_ok=false +fi + +if $keybind_ok && ! ls /dev/input/event* >/dev/null 2>&1; then + log "WARNING: No input devices accessible - Super+Shift+R keybind disabled" + keybind_ok=false +fi + +if $keybind_ok; then + /usr/local/bin/gaming-keybind-monitor & + log "Keybind monitor started (Super+Shift+R to exit)" +else + log "Keybind monitor NOT started - use Steam > Power > Exit to Desktop instead" +fi + +# Steam-specific environment variables +export QT_IM_MODULE=steam +export GTK_IM_MODULE=Steam +export STEAM_DISABLE_AUDIO_DEVICE_SWITCHING=1 +export STEAM_ENABLE_VOLUME_HANDLER=1 + +log "Starting gamescope-session-plus (Intel Arc config via environment.d)" + +/usr/share/gamescope-session-plus/gamescope-session-plus steam +rc=$? + +exit "$rc" +NM_WRAPPER + sudo chmod +x "$nm_wrapper" + + #--------------------------------------------------------------------------- + # SDDM Session Entry + #--------------------------------------------------------------------------- + local session_desktop="/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop" + sudo tee "$session_desktop" > /dev/null << 'SESSION_DESKTOP' +[Desktop Entry] +Name=Gaming Mode (Intel Arc) +Comment=Steam Big Picture with gamescope-session +Exec=/usr/local/bin/gamescope-session-nm-wrapper +Type=Application +DesktopNames=gamescope +SESSION_DESKTOP + + #--------------------------------------------------------------------------- + # Session Switch Scripts + #--------------------------------------------------------------------------- + local os_session_select="/usr/lib/os-session-select" + sudo tee "$os_session_select" > /dev/null << 'OS_SESSION_SELECT' +#!/bin/bash +rm -f /tmp/.gaming-session-active +sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || true +timeout 5 steam -shutdown 2>/dev/null || true +sleep 1 +nohup sudo -n systemctl restart sddm &>/dev/null & +disown +exit 0 +OS_SESSION_SELECT + sudo chmod +x "$os_session_select" + + local switch_script="/usr/local/bin/switch-to-gaming" + sudo tee "$switch_script" > /dev/null << 'SWITCH_SCRIPT' +#!/bin/bash +sudo -n /usr/local/bin/gaming-session-switch gaming 2>/dev/null || { + notify-send -u critical -t 3000 "Gaming Mode" "Failed to update session config" 2>/dev/null || true +} +notify-send -u normal -t 2000 "Gaming Mode" "Switching to Gaming Mode..." 2>/dev/null || true +pkill gamescope 2>/dev/null || true +pkill -f gamescope-session 2>/dev/null || true +sleep 2 +pkill -9 gamescope 2>/dev/null || true +pkill -9 -f gamescope-session 2>/dev/null || true +sudo -n chvt 2 2>/dev/null || true +sleep 0.3 +sudo -n systemctl restart sddm +SWITCH_SCRIPT + sudo chmod +x "$switch_script" + + local switch_desktop_script="/usr/local/bin/switch-to-desktop" + sudo tee "$switch_desktop_script" > /dev/null << 'SWITCH_DESKTOP' +#!/bin/bash +[[ ! -f /tmp/.gaming-session-active ]] && exit 0 +rm -f /tmp/.gaming-session-active +sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || true +timeout 5 steam -shutdown 2>/dev/null || true +sleep 1 +pkill gamescope 2>/dev/null || true +pkill -f gamescope-session 2>/dev/null || true +sleep 2 +pkill -9 gamescope 2>/dev/null || true +pkill -9 -f gamescope-session 2>/dev/null || true +sudo -n chvt 2 2>/dev/null || true +sleep 0.3 +nohup sudo -n systemctl restart sddm &>/dev/null & +disown +exit 0 +SWITCH_DESKTOP + sudo chmod +x "$switch_desktop_script" + + #--------------------------------------------------------------------------- + # Keybind Monitor (Python) + #--------------------------------------------------------------------------- + local keybind_monitor="/usr/local/bin/gaming-keybind-monitor" + sudo tee "$keybind_monitor" > /dev/null << 'KEYBIND_MONITOR' +#!/usr/bin/env python3 +"""Gaming Mode Keybind Monitor - Super+Shift+R to exit""" +import sys, subprocess, time +try: + import evdev + from evdev import ecodes +except ImportError: + sys.exit(1) + +def find_keyboards(): + keyboards = [] + for path in evdev.list_devices(): + try: + device = evdev.InputDevice(path) + caps = device.capabilities() + if ecodes.EV_KEY in caps: + keys = caps[ecodes.EV_KEY] + if ecodes.KEY_A in keys and ecodes.KEY_R in keys: + keyboards.append(device) + except Exception: + continue + return keyboards + +def monitor_keyboards(keyboards): + meta_pressed = shift_pressed = False + from selectors import DefaultSelector, EVENT_READ + selector = DefaultSelector() + for kbd in keyboards: + selector.register(kbd, EVENT_READ) + try: + while True: + for key, mask in selector.select(): + device = key.fileobj + try: + for event in device.read(): + if event.type != ecodes.EV_KEY: + continue + if event.code in (ecodes.KEY_LEFTMETA, ecodes.KEY_RIGHTMETA): + meta_pressed = event.value > 0 + elif event.code in (ecodes.KEY_LEFTSHIFT, ecodes.KEY_RIGHTSHIFT): + shift_pressed = event.value > 0 + elif event.code == ecodes.KEY_R and event.value == 1: + if meta_pressed and shift_pressed: + subprocess.run(['/usr/local/bin/switch-to-desktop']) + return + except Exception: + continue + except KeyboardInterrupt: + pass + finally: + selector.close() + +def main(): + time.sleep(2) + keyboards = find_keyboards() + if keyboards: + monitor_keyboards(keyboards) + +if __name__ == '__main__': + main() +KEYBIND_MONITOR + sudo chmod +x "$keybind_monitor" + + #--------------------------------------------------------------------------- + # Steam Library Auto-Mounter + #--------------------------------------------------------------------------- + local steam_mount_script="/usr/local/bin/steam-library-mount" + sudo tee "$steam_mount_script" > /dev/null << 'STEAM_MOUNT' +#!/bin/bash +# Steam Library Drive Auto-Mounter + +check_steam_library() { + local mount_point="$1" + [[ -d "$mount_point/steamapps" ]] || [[ -d "$mount_point/SteamLibrary/steamapps" ]] || \ + [[ -f "$mount_point/libraryfolder.vdf" ]] || [[ -f "$mount_point/steamapps/libraryfolder.vdf" ]] +} + +handle_device() { + local device="$1" + findmnt -n "$device" &>/dev/null && return + [[ "$device" =~ [0-9]$ ]] || return + local fstype + fstype="$(lsblk -n -o FSTYPE --nodeps "$device" 2>/dev/null)" + case "$fstype" in + ext4|ext3|ext2|btrfs|xfs|ntfs|vfat|exfat|f2fs) ;; + *) return ;; + esac + command -v udisksctl &>/dev/null || return + udisksctl mount -b "$device" --no-user-interaction 2>/dev/null || return + local mount_point + mount_point="$(findmnt -n -o TARGET "$device" 2>/dev/null)" + [[ -z "$mount_point" ]] && return + check_steam_library "$mount_point" || udisksctl unmount -b "$device" --no-user-interaction 2>/dev/null +} + +shopt -s nullglob +for dev in /dev/sd*[0-9]* /dev/nvme*p[0-9]*; do + [[ -b "$dev" ]] && handle_device "$dev" +done +shopt -u nullglob + +action="" dev_path="" +udevadm monitor --kernel --property --subsystem-match=block 2>/dev/null | while read -r line; do + case "$line" in + ACTION=*) action="${line#ACTION=}" ;; + DEVNAME=*) dev_path="${line#DEVNAME=}" ;; + "") + if [[ "$action" == "add" && -n "$dev_path" && "$dev_path" =~ [0-9]$ && -b "$dev_path" ]]; then + sleep 1 + handle_device "$dev_path" + fi + action="" dev_path="" + ;; + esac +done +STEAM_MOUNT + sudo chmod +x "$steam_mount_script" + + #--------------------------------------------------------------------------- + # SDDM Configuration + #--------------------------------------------------------------------------- + sudo mkdir -p /etc/sddm.conf.d + + local sddm_gaming_conf="/etc/sddm.conf.d/zz-gaming-session.conf" + local autologin_user="$current_user" + [[ -f /etc/sddm.conf.d/autologin.conf ]] && \ + autologin_user=$(sed -n 's/^User=//p' /etc/sddm.conf.d/autologin.conf 2>/dev/null | head -1) + [[ -z "$autologin_user" ]] && autologin_user="$current_user" + + sudo tee "$sddm_gaming_conf" > /dev/null << SDDM_GAMING +[Autologin] +User=${autologin_user} +Session=hyprland-uwsm +Relogin=true +SDDM_GAMING + + local session_helper="/usr/local/bin/gaming-session-switch" + sudo tee "$session_helper" > /dev/null << 'SESSION_HELPER' +#!/bin/bash +CONF="/etc/sddm.conf.d/zz-gaming-session.conf" +[[ ! -f "$CONF" ]] && exit 1 +case "$1" in + gaming) sed -i 's/^Session=.*/Session=gamescope-session-steam-nm/' "$CONF" ;; + desktop) sed -i 's/^Session=.*/Session=hyprland-uwsm/' "$CONF" ;; + *) exit 1 ;; +esac +SESSION_HELPER + sudo chmod +x "$session_helper" + + #--------------------------------------------------------------------------- + # Sudoers Rules + #--------------------------------------------------------------------------- + local sudoers_session="/etc/sudoers.d/gaming-session-switch" + sudo tee "$sudoers_session" > /dev/null << 'SUDOERS_SWITCH' +%video ALL=(ALL) NOPASSWD: /usr/local/bin/gaming-session-switch +%video ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart sddm +%video ALL=(ALL) NOPASSWD: /usr/bin/chvt +%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl start NetworkManager.service +%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop NetworkManager.service +%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart iwd.service +%wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart bluetooth.service +%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-start +%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-stop +SUDOERS_SWITCH + sudo chmod 0440 "$sudoers_session" + + #--------------------------------------------------------------------------- + # Hyprland Keybind + #--------------------------------------------------------------------------- + local hypr_bindings_conf="${user_home}/.config/hypr/bindings.conf" + local hypr_main_conf="${user_home}/.config/hypr/hyprland.conf" + local keybind_target="" + + # Determine where to add the keybind + if [[ -f "$hypr_bindings_conf" ]]; then + keybind_target="$hypr_bindings_conf" + elif [[ -f "$hypr_main_conf" ]]; then + keybind_target="$hypr_main_conf" + fi + + if [[ -n "$keybind_target" ]] && ! grep -q "switch-to-gaming" "$keybind_target" 2>/dev/null; then + run_as_user tee -a "$keybind_target" > /dev/null << 'HYPR_GAMING' + +# Gaming Mode - Switch to Gamescope session (Intel Arc) +bindd = SUPER SHIFT, S, Gaming Mode, exec, /usr/local/bin/switch-to-gaming +HYPR_GAMING + info "Added Gaming Mode keybind to $(basename "$keybind_target")" + elif [[ -z "$keybind_target" ]]; then + warn "No Hyprland config found - please add keybind manually:" + warn " bindd = SUPER SHIFT, S, Gaming Mode, exec, /usr/local/bin/switch-to-gaming" + fi + + # Reload Hyprland + command -v hyprctl >/dev/null 2>&1 && hyprctl monitors >/dev/null 2>&1 && hyprctl reload >/dev/null 2>&1 + + #--------------------------------------------------------------------------- + # Done + #--------------------------------------------------------------------------- + echo "" + echo "================================================================" + echo " SESSION SWITCHING CONFIGURED (Intel Arc)" + echo "================================================================" + echo "" + echo " Usage:" + echo " - Press Super+Shift+S in Hyprland to switch to Gaming Mode" + echo " - Press Super+Shift+R in Gaming Mode to return to Hyprland" + echo "" +} + +############################################################################### +# MAIN ENTRY POINT +############################################################################### + +execute_setup() { + sudo -k + sudo -v || die "sudo authentication required" + + validate_environment + check_intel_arc + + echo "" + echo "================================================================" + echo " ARCGames INSTALLER v${ARCGAMES_VERSION}" + echo " Intel Arc dGPU Gaming Mode Setup" + echo "================================================================" + echo "" + + check_steam_dependencies + setup_requirements + setup_session_switching + + if [[ "$NEEDS_RELOGIN" -eq 1 ]]; then + echo "" + echo "================================================================" + echo " IMPORTANT: LOG OUT REQUIRED" + echo "================================================================" + echo "" + echo " User groups have been updated. Please log out and log back in." + echo "" + else + echo "" + echo "================================================================" + echo " SETUP COMPLETE" + echo "================================================================" + echo "" + echo " To switch to Gaming Mode: Press Super+Shift+S" + echo " To return to Desktop: Press Super+Shift+R" + echo "" + fi +} + +show_help() { + cat << EOF +ARCGames Installer v${ARCGAMES_VERSION} + +Gaming Mode installer for Intel Arc discrete GPUs. + +Usage: $0 [OPTIONS] + +Options: + --help, -h Show this help message + --version Show version number + --rebuild-mesa Rebuild mesa-git from latest upstream source + +EOF +} + +############################################################################### +# MESA-GIT REBUILD +############################################################################### + +rebuild_mesa_git() { + info "Mesa-git rebuild requested" + + # Check if mesa-git is actually installed + if ! check_package "mesa-git"; then + die "mesa-git is not installed. Run the full installer first, or use: yay -S mesa-git" + fi + + # Find AUR helper + local aur_helper="" + command -v yay >/dev/null 2>&1 && check_aur_helper_functional yay && aur_helper="yay" + [[ -z "$aur_helper" ]] && command -v paru >/dev/null 2>&1 && check_aur_helper_functional paru && aur_helper="paru" + [[ -z "$aur_helper" ]] && die "No AUR helper found (yay or paru required)" + + info "Using AUR helper: $aur_helper" + + # Show current mesa-git version + local current_ver + current_ver=$(pacman -Qi mesa-git 2>/dev/null | grep "^Version" | awk '{print $3}') + info "Current mesa-git version: $current_ver" + + echo "" + echo "================================================================" + echo " MESA-GIT REBUILD" + echo "================================================================" + echo "" + echo " This will rebuild mesa-git from the latest upstream source." + echo " Build time: typically 10-30 minutes per package." + echo "" + + local -a packages=("mesa-git") + if check_package "lib32-mesa-git"; then + packages+=("lib32-mesa-git") + echo " Packages to rebuild: mesa-git, lib32-mesa-git" + else + echo " Package to rebuild: mesa-git" + fi + echo "" + + read -p "Proceed with rebuild? [Y/n]: " -n 1 -r + echo + [[ $REPLY =~ ^[Nn]$ ]] && { info "Rebuild cancelled"; return 0; } + + # Clear AUR helper cache to force fresh build + for pkg in "${packages[@]}"; do + run_as_user rm -rf "${REAL_HOME}/.cache/yay/${pkg}" 2>/dev/null || true + run_as_user rm -rf "${REAL_HOME}/.cache/paru/clone/${pkg}" 2>/dev/null || true + done + + # Rebuild mesa-git + info "Rebuilding mesa-git from latest source..." + if ! run_as_user "$aur_helper" -S --noconfirm --rebuild --removemake --cleanafter \ + --overwrite '/usr/lib/*' --answeredit None --answerclean None --answerdiff None mesa-git; then + die "Failed to rebuild mesa-git" + fi + info "mesa-git rebuilt successfully" + + # Rebuild lib32-mesa-git if installed + if check_package "lib32-mesa-git"; then + info "Rebuilding lib32-mesa-git from latest source..." + if ! run_as_user "$aur_helper" -S --noconfirm --rebuild --removemake --cleanafter \ + --overwrite '/usr/lib32/*' --answeredit None --answerclean None --answerdiff None lib32-mesa-git; then + die "Failed to rebuild lib32-mesa-git" + fi + info "lib32-mesa-git rebuilt successfully" + fi + + # Show new version + local new_ver + new_ver=$(pacman -Qi mesa-git 2>/dev/null | grep "^Version" | awk '{print $3}') + echo "" + echo "================================================================" + echo " MESA-GIT REBUILD COMPLETE" + echo "================================================================" + echo "" + echo " Previous version: $current_ver" + echo " New version: $new_ver" + echo "" +} + +############################################################################### +# COMMAND LINE HANDLING +############################################################################### + +case "${1:-}" in + --help|-h) show_help; exit 0 ;; + --version) echo "ARCGames Installer v${ARCGAMES_VERSION}"; exit 0 ;; + --rebuild-mesa) rebuild_mesa_git ;; + "") execute_setup ;; + *) echo "Unknown option: $1"; exit 1 ;; +esac diff --git a/ARCGames_uninstall.sh b/ARCGames_uninstall.sh new file mode 100755 index 0000000..d7e237c --- /dev/null +++ b/ARCGames_uninstall.sh @@ -0,0 +1,322 @@ +#!/bin/bash +# +# ARCGames - Uninstaller +# Removes all files and configuration created by ARCGames_install.sh +# +# Usage: +# ./ARCGames_uninstall.sh [--help|--dry-run] +# +############################################################################### + +set -uo pipefail + +REAL_USER="${SUDO_USER:-$USER}" +REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6) + +if [[ -z "$REAL_HOME" ]]; then + echo "FATAL: Could not resolve home directory for user: $REAL_USER" >&2 + exit 1 +fi + +DRY_RUN=false +REMOVED=0 +FAILED=0 + +############################################################################### +# UTILITY FUNCTIONS +############################################################################### + +info() { echo "[*] $*"; } +warn() { echo "[!] $*"; } +err() { echo "[!] $*" >&2; } + +remove_file() { + local file="$1" + local description="${2:-}" + + if [[ ! -e "$file" ]]; then + return 0 + fi + + if $DRY_RUN; then + info "[dry-run] Would remove: $file${description:+ ($description)}" + ((REMOVED++)) + return 0 + fi + + if sudo rm -f "$file"; then + info "Removed: $file${description:+ ($description)}" + ((REMOVED++)) + else + err "Failed to remove: $file" + ((FAILED++)) + fi +} + +remove_dir_if_empty() { + local dir="$1" + if [[ -d "$dir" ]] && [[ -z "$(ls -A "$dir" 2>/dev/null)" ]]; then + if $DRY_RUN; then + info "[dry-run] Would remove empty dir: $dir" + else + sudo rmdir "$dir" 2>/dev/null && info "Removed empty dir: $dir" + fi + fi +} + +remove_hyprland_keybind() { + local file="$1" + [[ -f "$file" ]] || return 0 + + if grep -q "switch-to-gaming" "$file" 2>/dev/null; then + if $DRY_RUN; then + info "[dry-run] Would remove gaming keybind from: $file" + ((REMOVED++)) + return 0 + fi + + # Remove the comment line and the keybind line + sudo sed -i '/^# Gaming Mode - Switch to Gamescope session/d' "$file" + sudo sed -i '/switch-to-gaming/d' "$file" + info "Removed gaming keybind from: $file" + ((REMOVED++)) + fi +} + +############################################################################### +# MAIN +############################################################################### + +show_help() { + cat << 'EOF' +ARCGames Uninstaller + +Removes all files and configuration created by the ARCGames installer. + +Usage: + ./ARCGames_uninstall.sh [OPTIONS] + +Options: + --help, -h Show this help message + --dry-run Show what would be removed without deleting anything + +What gets removed: + - Gaming mode scripts (/usr/local/bin/switch-to-*, gaming-*, gamescope-*, steam-library-mount) + - Udev rules (/etc/udev/rules.d/99-gaming-performance.rules) + - Sudoers files (/etc/sudoers.d/gaming-mode-*, gaming-session-switch) + - Polkit rules (/etc/polkit-1/rules.d/50-gamescope-*, 50-udisks-gaming.rules) + - SDDM gaming session config + - PipeWire low-latency config + - Shader cache config + - Memlock limits config + - Gamescope session environment config + - Hyprland gaming mode keybind + - NetworkManager gaming config + - Session desktop entry + +What is NOT removed: + - Installed packages (Steam, mesa-git, gamescope, etc.) + - User game data or Steam libraries + - pacman.conf backup files + - User group memberships (video, input, wheel) +EOF +} + +uninstall() { + echo "" + echo "================================================================" + echo " ARCGames UNINSTALLER" + echo "================================================================" + echo "" + + if $DRY_RUN; then + info "DRY RUN MODE - nothing will be deleted" + echo "" + fi + + # Confirm unless dry run + if ! $DRY_RUN; then + echo " This will remove all ARCGames gaming mode files and configs." + echo " Installed packages (Steam, mesa-git, etc.) will NOT be removed." + echo "" + read -p " Proceed with uninstall? [y/N]: " -n 1 -r + echo + [[ ! $REPLY =~ ^[Yy]$ ]] && { info "Uninstall cancelled."; exit 0; } + echo "" + + sudo -v || { err "sudo authentication required"; exit 1; } + fi + + #--------------------------------------------------------------------------- + # Kill running gaming mode processes + #--------------------------------------------------------------------------- + if ! $DRY_RUN; then + info "Stopping gaming mode processes..." + sudo pkill -f gaming-keybind-monitor 2>/dev/null || true + sudo pkill -f steam-library-mount 2>/dev/null || true + else + info "[dry-run] Would stop gaming-keybind-monitor and steam-library-mount" + fi + + #--------------------------------------------------------------------------- + # Scripts in /usr/local/bin/ + #--------------------------------------------------------------------------- + info "Removing gaming mode scripts..." + remove_file "/usr/local/bin/switch-to-gaming" "session switch script" + remove_file "/usr/local/bin/switch-to-desktop" "session switch script" + remove_file "/usr/local/bin/gaming-session-switch" "SDDM session helper" + remove_file "/usr/local/bin/gaming-keybind-monitor" "keybind monitor (Python)" + remove_file "/usr/local/bin/steam-library-mount" "drive auto-mounter" + remove_file "/usr/local/bin/gamescope-session-nm-wrapper" "gamescope wrapper" + remove_file "/usr/local/bin/gamescope-nm-start" "NetworkManager start" + remove_file "/usr/local/bin/gamescope-nm-stop" "NetworkManager stop" + + #--------------------------------------------------------------------------- + # System override file + #--------------------------------------------------------------------------- + remove_file "/usr/lib/os-session-select" "session select override" + + #--------------------------------------------------------------------------- + # Session desktop entry + #--------------------------------------------------------------------------- + remove_file "/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop" "SDDM session entry" + + #--------------------------------------------------------------------------- + # Udev rules + #--------------------------------------------------------------------------- + info "Removing udev rules..." + remove_file "/etc/udev/rules.d/99-gaming-performance.rules" "GPU/CPU performance udev rules" + if ! $DRY_RUN; then + sudo udevadm control --reload-rules 2>/dev/null || true + fi + + #--------------------------------------------------------------------------- + # Sudoers files + #--------------------------------------------------------------------------- + info "Removing sudoers rules..." + remove_file "/etc/sudoers.d/gaming-mode-sysctl" "performance sysctl sudoers" + remove_file "/etc/sudoers.d/gaming-session-switch" "session switch sudoers" + + #--------------------------------------------------------------------------- + # Polkit rules + #--------------------------------------------------------------------------- + info "Removing polkit rules..." + remove_file "/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules" "NM polkit rule" + remove_file "/etc/polkit-1/rules.d/50-udisks-gaming.rules" "udisks polkit rule" + if ! $DRY_RUN; then + sudo systemctl restart polkit.service 2>/dev/null || true + fi + + #--------------------------------------------------------------------------- + # SDDM config + #--------------------------------------------------------------------------- + info "Removing SDDM gaming session config..." + remove_file "/etc/sddm.conf.d/zz-gaming-session.conf" "SDDM gaming session" + remove_dir_if_empty "/etc/sddm.conf.d" + + #--------------------------------------------------------------------------- + # PipeWire config + #--------------------------------------------------------------------------- + remove_file "/etc/pipewire/pipewire.conf.d/10-gaming-latency.conf" "PipeWire low-latency" + remove_dir_if_empty "/etc/pipewire/pipewire.conf.d" + + #--------------------------------------------------------------------------- + # Shader cache config + #--------------------------------------------------------------------------- + remove_file "/etc/environment.d/99-shader-cache.conf" "shader cache env vars" + + #--------------------------------------------------------------------------- + # Memlock limits + #--------------------------------------------------------------------------- + remove_file "/etc/security/limits.d/99-gaming-memlock.conf" "memlock limits" + + #--------------------------------------------------------------------------- + # NetworkManager gaming config + #--------------------------------------------------------------------------- + remove_file "/etc/NetworkManager/conf.d/10-iwd-backend.conf" "NM iwd backend config" + + #--------------------------------------------------------------------------- + # Gaming mode config file + #--------------------------------------------------------------------------- + remove_file "/etc/gaming-mode.conf" "global gaming-mode config" + remove_file "${REAL_HOME}/.gaming-mode.conf" "user gaming-mode config" + + #--------------------------------------------------------------------------- + # User config: gamescope session environment + #--------------------------------------------------------------------------- + info "Removing user config files..." + remove_file "${REAL_HOME}/.config/environment.d/gamescope-session-plus.conf" "gamescope env config" + remove_dir_if_empty "${REAL_HOME}/.config/environment.d" + + #--------------------------------------------------------------------------- + # Hyprland keybind + #--------------------------------------------------------------------------- + info "Removing Hyprland gaming mode keybind..." + remove_hyprland_keybind "${REAL_HOME}/.config/hypr/bindings.conf" + remove_hyprland_keybind "${REAL_HOME}/.config/hypr/hyprland.conf" + + # Reload Hyprland if running + if ! $DRY_RUN; then + if command -v hyprctl >/dev/null 2>&1 && hyprctl monitors >/dev/null 2>&1; then + hyprctl reload >/dev/null 2>&1 && info "Hyprland config reloaded" + fi + fi + + #--------------------------------------------------------------------------- + # Gamescope capability + #--------------------------------------------------------------------------- + if ! $DRY_RUN && command -v gamescope >/dev/null 2>&1; then + if getcap "$(command -v gamescope)" 2>/dev/null | grep -q 'cap_sys_nice'; then + sudo setcap -r "$(command -v gamescope)" 2>/dev/null && \ + info "Removed cap_sys_nice from gamescope" + fi + elif $DRY_RUN && command -v gamescope >/dev/null 2>&1; then + if getcap "$(command -v gamescope)" 2>/dev/null | grep -q 'cap_sys_nice'; then + info "[dry-run] Would remove cap_sys_nice from gamescope" + fi + fi + + #--------------------------------------------------------------------------- + # Temp files + #--------------------------------------------------------------------------- + remove_file "/tmp/.gaming-session-active" "session marker" + remove_file "/tmp/.gamescope-started-nm" "NM start marker" + + #--------------------------------------------------------------------------- + # Summary + #--------------------------------------------------------------------------- + echo "" + echo "================================================================" + if $DRY_RUN; then + echo " DRY RUN COMPLETE" + else + echo " UNINSTALL COMPLETE" + fi + echo "================================================================" + echo "" + echo " Removed: $REMOVED files" + [[ $FAILED -gt 0 ]] && echo " Failed: $FAILED files" + echo "" + + if ! $DRY_RUN && [[ $REMOVED -gt 0 ]]; then + echo " Note: Installed packages were NOT removed." + echo " To also remove gaming packages:" + echo " sudo pacman -Rns gamescope mangohud lib32-mangohud gamemode lib32-gamemode" + echo "" + echo " To remove mesa-git and restore stable mesa:" + echo " sudo pacman -Rdd mesa-git lib32-mesa-git" + echo " sudo pacman -S mesa lib32-mesa vulkan-intel lib32-vulkan-intel" + echo "" + fi +} + +############################################################################### +# COMMAND LINE HANDLING +############################################################################### + +case "${1:-}" in + --help|-h) show_help; exit 0 ;; + --dry-run) DRY_RUN=true; uninstall ;; + "") uninstall ;; + *) echo "Unknown option: $1"; exit 1 ;; +esac