#!/bin/bash set -Euo pipefail Super_Shift_S_VERSION="12.27-KDE" 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 : "${PERFORMANCE_MODE:=enabled}" NEEDS_RELOGIN=0 NEEDS_REBOOT=0 info(){ echo "[*] $*"; } warn(){ echo "[!] $*"; } err(){ echo "[!] $*" >&2; } die() { local msg="$1"; local code="${2:-1}" echo "FATAL: $msg" >&2 logger -t gaming-mode "Installation failed: $msg" exit "$code" } check_aur_helper_functional() { local helper="$1" if $helper --version &>/dev/null; then return 0 else return 1 fi } rebuild_yay() { info "Attempting to rebuild yay..." local tmp_dir tmp_dir=$(mktemp -d) pushd "$tmp_dir" >/dev/null || return 1 if git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si --noconfirm; then popd >/dev/null || true rm -rf "$tmp_dir" info "yay rebuilt successfully" return 0 else popd >/dev/null || true rm -rf "$tmp_dir" err "Failed to rebuild yay" return 1 fi } validate_environment() { command -v pacman >/dev/null || die "pacman required" if ! command -v plasmashell >/dev/null 2>&1 && ! command -v kwin_wayland >/dev/null 2>&1; then die "KDE Plasma not found (plasmashell/kwin_wayland required)" fi [ -d "$HOME/.config" ] || mkdir -p "$HOME/.config" } check_package() { pacman -Qi "$1" &>/dev/null; } is_amd_igpu_card() { local card_path="$1" local device_path="$card_path/device" local pci_slot="" [[ -L "$device_path" ]] && pci_slot=$(basename "$(readlink -f "$device_path")") [[ -z "$pci_slot" ]] && return 1 local device_info device_info=$(/usr/bin/lspci -s "$pci_slot" 2>/dev/null) if echo "$device_info" | grep -iqE 'renoir|cezanne|barcelo|rembrandt|phoenix|raphael|lucienne|picasso|raven|vega.*mobile|vega.*integrated|radeon.*graphics|yellow.*carp|green.*sardine|cyan.*skillfish|vangogh|van gogh|mendocino|hawk.*point|strix.*point|strix.*halo|krackan|sarlak'; then return 0 fi if echo "$device_info" | grep -iqE 'radeon rx|navi [0-9]|navi[0-9]|vega 56|vega 64|radeon vii|radeon pro|firepro|polaris|ellesmere|baffin|lexa|radeon [0-9]{3,4}[^0-9]'; then return 1 fi return 1 } check_intel_only() { local card_name driver driver_link local has_intel=false local has_amd_nvidia=false for card_path in /sys/class/drm/card[0-9]*; do card_name=$(basename "$card_path") [[ "$card_name" == render* ]] && continue driver_link="$card_path/device/driver" [[ -L "$driver_link" ]] || continue driver=$(basename "$(readlink "$driver_link")") case "$driver" in i915|xe) has_intel=true ;; nvidia|amdgpu) has_amd_nvidia=true ;; esac done if $has_intel && ! $has_amd_nvidia; then return 0 fi return 1 } detect_dgpu_monitors() { local -n _monitors=$1 local -n _dgpu_card=$2 local -n _dgpu_type=$3 _monitors=() _dgpu_card="" _dgpu_type="" local lspci_output lspci_output=$(/usr/bin/lspci 2>/dev/null) if echo "$lspci_output" | grep -qi nvidia; then _dgpu_type="NVIDIA" elif echo "$lspci_output" | grep -iqE 'radeon rx|navi|vega 56|vega 64|radeon vii|radeon pro'; then _dgpu_type="AMD dGPU" fi for card_path in /sys/class/drm/card[0-9]*; do local card_name card_name=$(basename "$card_path") [[ "$card_name" == render* ]] && continue local driver_link="$card_path/device/driver" [[ -L "$driver_link" ]] || continue local driver driver=$(basename "$(readlink "$driver_link")") local is_dgpu=false case "$driver" in nvidia) is_dgpu=true [[ -z "$_dgpu_type" ]] && _dgpu_type="NVIDIA" ;; amdgpu) if ! is_amd_igpu_card "$card_path"; then is_dgpu=true [[ -z "$_dgpu_type" ]] && _dgpu_type="AMD dGPU" fi ;; esac if $is_dgpu; then _dgpu_card="$card_name" for connector in "$card_path"/"$card_name"-*/status; do [[ -f "$connector" ]] || continue local conn_dir conn_dir=$(dirname "$connector") local conn_name conn_name=$(basename "$conn_dir") conn_name=${conn_name#card*-} [[ "$conn_name" == Writeback* ]] && continue local status status=$(cat "$connector" 2>/dev/null) if [[ "$status" == "connected" ]]; then local resolution="" local mode_file="$conn_dir/modes" [[ -f "$mode_file" ]] && [[ -s "$mode_file" ]] && resolution=$(head -1 "$mode_file" 2>/dev/null) _monitors+=("$conn_name|$resolution") fi done break fi done } check_nvidia_kernel_params() { local lspci_output lspci_output=$(/usr/bin/lspci 2>/dev/null) if ! echo "$lspci_output" | grep -qi nvidia; then return 0 fi echo "" echo "================================================================" echo " NVIDIA KERNEL PARAMETER CHECK" echo "================================================================" echo "" if grep -qE "nvidia[-_]drm\.modeset=1" /proc/cmdline 2>/dev/null; then info "nvidia-drm.modeset=1 is already configured" return 0 fi warn "nvidia-drm.modeset=1 is NOT SET - required for Gaming Mode!" echo "" local bootloader="" local config_file="" if [ -f /boot/limine.conf ]; then bootloader="limine"; config_file="/boot/limine.conf" elif [ -f /boot/limine/limine.conf ]; then bootloader="limine"; config_file="/boot/limine/limine.conf" elif sudo test -d /boot/loader/entries 2>/dev/null; then bootloader="systemd-boot" elif [ -f /etc/default/grub ]; then bootloader="grub" fi info "Detected bootloader: $bootloader" case "$bootloader" in limine) info "Limine config: $config_file" read -p "Add nvidia-drm.modeset=1 to Limine config? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then configure_limine_nvidia "$config_file" else warn "Skipping - you'll need to add nvidia-drm.modeset=1 manually" show_manual_nvidia_instructions fi ;; systemd-boot) echo "" read -p "Add nvidia-drm.modeset=1 to systemd-boot entries? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then configure_systemd_boot_nvidia else warn "Skipping - you'll need to add nvidia-drm.modeset=1 manually" show_manual_nvidia_instructions fi ;; grub) echo "" read -p "Add nvidia-drm.modeset=1 to GRUB config? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then configure_grub_nvidia else warn "Skipping - you'll need to add nvidia-drm.modeset=1 manually" show_manual_nvidia_instructions fi ;; *) echo "" warn "Could not detect bootloader type" show_manual_nvidia_instructions ;; esac } configure_limine_nvidia() { local config_file="$1" info "Backing up Limine config..." sudo cp "$config_file" "${config_file}.backup.$(date +%Y%m%d%H%M%S)" || { err "Failed to backup Limine config" return 1 } info "Adding nvidia-drm.modeset=1 to Limine cmdline..." if sudo sed -i '/^[[:space:]]*cmdline:/ s/$/ nvidia-drm.modeset=1/' "$config_file"; then if grep -q "nvidia-drm.modeset=1" "$config_file"; then info "Successfully added nvidia-drm.modeset=1 to Limine config" echo "" echo " Limine config updated" echo " Changes will take effect after reboot" echo "" NEEDS_REBOOT=1 else err "Failed to add parameter - please add manually" show_manual_nvidia_instructions fi else err "Failed to modify Limine config" show_manual_nvidia_instructions fi } configure_grub_nvidia() { local grub_default="/etc/default/grub" info "Backing up GRUB config..." sudo cp "$grub_default" "${grub_default}.backup.$(date +%Y%m%d%H%M%S)" || { err "Failed to backup GRUB config" return 1 } info "Adding nvidia-drm.modeset=1 to GRUB..." if ! grep -q "nvidia-drm.modeset=1" "$grub_default"; then # Handle both single-quoted and double-quoted GRUB_CMDLINE_LINUX_DEFAULT sudo sed -i -E "s/(GRUB_CMDLINE_LINUX_DEFAULT=['\"][^'\"]*)/\1 nvidia-drm.modeset=1/" "$grub_default" if grep -q "nvidia-drm.modeset=1" "$grub_default"; then info "Regenerating GRUB config..." sudo grub-mkconfig -o /boot/grub/grub.cfg || { err "Failed to regenerate GRUB config" return 1 } info "Successfully configured GRUB for NVIDIA" NEEDS_REBOOT=1 else err "Failed to add parameter to GRUB" show_manual_nvidia_instructions fi fi } configure_systemd_boot_nvidia() { local entries_dir="/boot/loader/entries" if ! sudo test -d "$entries_dir" 2>/dev/null; then err "systemd-boot entries directory not found at $entries_dir" show_manual_nvidia_instructions return 1 fi local -a entry_files=() while IFS= read -r -d '' f; do entry_files+=("$f") done < <(sudo find "$entries_dir" -name "*.conf" -print0 2>/dev/null) if [[ ${#entry_files[@]} -eq 0 ]]; then err "No boot entries found in $entries_dir" show_manual_nvidia_instructions return 1 fi info "Found ${#entry_files[@]} boot entry/entries:" for entry in "${entry_files[@]}"; do echo " - $(basename "$entry")" done echo "" local modified=0 for entry in "${entry_files[@]}"; do if sudo grep -q "nvidia[-_]drm\.modeset=1" "$entry" 2>/dev/null; then info "$(basename "$entry"): nvidia-drm.modeset=1 already present" continue fi if ! sudo grep -q "^options" "$entry" 2>/dev/null; then warn "$(basename "$entry"): no 'options' line found, skipping" continue fi info "Backing up $(basename "$entry")..." sudo cp "$entry" "${entry}.backup.$(date +%Y%m%d%H%M%S)" || { err "Failed to backup $entry" continue } info "Adding nvidia-drm.modeset=1 to $(basename "$entry")..." if sudo sed -i '/^options/ s/$/ nvidia-drm.modeset=1/' "$entry"; then if sudo grep -q "nvidia-drm.modeset=1" "$entry"; then info "$(basename "$entry"): successfully updated" ((modified++)) else err "$(basename "$entry"): failed to add parameter" fi else err "Failed to modify $entry" fi done if [[ $modified -gt 0 ]]; then info "Successfully updated $modified boot entry/entries" echo "" echo " systemd-boot entries updated" echo " Changes will take effect after reboot" echo "" NEEDS_REBOOT=1 else warn "No entries were modified" show_manual_nvidia_instructions fi } show_manual_nvidia_instructions() { cat <<'MSG' Manual configuration required: Limine: Add nvidia-drm.modeset=1 to cmdline in /boot/limine.conf systemd-boot: Add to options in /boot/loader/entries/*.conf GRUB: Add to GRUB_CMDLINE_LINUX_DEFAULT, then run grub-mkconfig -o /boot/grub/grub.cfg MSG warn "Gaming Mode may not work correctly without nvidia-drm.modeset=1" } install_nvidia_deckmode_env() { local lspci_output lspci_output=$(/usr/bin/lspci 2>/dev/null) if ! echo "$lspci_output" | grep -qi nvidia; then info "No NVIDIA detected; skipping NVIDIA Deck-mode env." return 0 fi local env_file="/etc/environment.d/90-nvidia-gamescope.conf" if [ -f "$env_file" ]; then info "NVIDIA gamescope env already present: $env_file" return 0 fi info "Installing NVIDIA gamescope env (Deck-mode style)..." sudo mkdir -p /etc/environment.d sudo tee "$env_file" >/dev/null <<'EOF' GBM_BACKEND=nvidia-drm __GLX_VENDOR_LIBRARY_NAME=nvidia __VK_LAYER_NV_optimus=NVIDIA_only EOF info "Installed $env_file" NEEDS_RELOGIN=1 } check_steam_dependencies() { info "Checking Steam dependencies for Arch Linux..." info "Force refreshing package database from all mirrors..." sudo pacman -Syy || die "Failed to refresh package database" echo "" echo "================================================================" echo " SYSTEM UPDATE RECOMMENDED" echo "================================================================" echo "" echo " It's recommended to upgrade your system before installing" echo " gaming dependencies to avoid package version conflicts." echo "" read -p "Upgrade system now? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then info "Upgrading system..." sudo pacman -Syu || die "Failed to upgrade system" fi echo "" local -a missing_deps=() local -a optional_deps=() local multilib_enabled=false 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 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 local -a core_deps=( "steam" "lib32-vulkan-icd-loader" "vulkan-icd-loader" "lib32-mesa" "mesa" "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" ) local gpu_vendor gpu_vendor=$(/usr/bin/lspci 2>/dev/null | grep -iE 'vga|3d|display' || echo "") local has_nvidia=false has_amd=false if echo "$gpu_vendor" | grep -qi nvidia; then has_nvidia=true info "Detected NVIDIA GPU" fi if echo "$gpu_vendor" | grep -iqE 'amd|radeon|advanced micro'; then has_amd=true info "Detected AMD GPU" fi if echo "$gpu_vendor" | grep -iq intel; then info "Detected Intel GPU; no Intel-specific drivers will be installed" fi local primary_gpu="unknown" if $has_nvidia; then primary_gpu="nvidia" elif $has_amd; then primary_gpu="amd" fi PRIMARY_GPU="$primary_gpu" info "Primary GPU selection: $PRIMARY_GPU" local -a gpu_deps=() if $has_nvidia; then gpu_deps+=( "nvidia-utils" "lib32-nvidia-utils" "nvidia-settings" "libva-nvidia-driver" ) # Check for any NVIDIA kernel module: packaged, DKMS, or CachyOS pre-built local has_nvidia_module=false if check_package "nvidia" || check_package "nvidia-dkms" || check_package "nvidia-open-dkms"; then has_nvidia_module=true else # CachyOS ships pre-built nvidia-open modules per kernel (e.g. linux-cachyos-nvidia-open) while IFS= read -r pkg; do if [[ "$pkg" =~ ^linux-.*nvidia ]]; then has_nvidia_module=true break fi done < <(pacman -Qq 2>/dev/null) fi if ! $has_nvidia_module; then info "Note: You may need to install 'nvidia', 'nvidia-dkms', or 'nvidia-open-dkms' kernel module" optional_deps+=("nvidia-dkms") fi fi if $has_amd; then gpu_deps+=( "vulkan-radeon" "lib32-vulkan-radeon" "libvdpau" "lib32-libvdpau" ) ! check_package "xf86-video-amdgpu" && optional_deps+=("xf86-video-amdgpu") fi if ! $has_nvidia && ! $has_amd; then info "No NVIDIA/AMD GPU detected; installing AMD Vulkan drivers as fallback..." gpu_deps+=("vulkan-radeon" "lib32-vulkan-radeon") fi gpu_deps+=( "vulkan-tools" "vulkan-mesa-layers" ) local -a recommended_deps=( "gamescope" "mangohud" "lib32-mangohud" "proton-ge-custom-bin" "udisks2" ) info "Checking core Steam dependencies..." for dep in "${core_deps[@]}"; do if ! check_package "$dep"; then missing_deps+=("$dep") fi done info "Checking GPU-specific dependencies..." for dep in "${gpu_deps[@]}"; do if ! check_package "$dep"; then missing_deps+=("$dep") fi done info "Checking recommended dependencies..." for dep in "${recommended_deps[@]}"; do if ! check_package "$dep"; then optional_deps+=("$dep") fi done echo "" echo "================================================================" echo " STEAM DEPENDENCY CHECK RESULTS" echo "================================================================" echo "" 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 "" 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 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[@]}"}") 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 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 "Syncing package database before installing..." sudo pacman -Sy || warn "Failed to sync package database" info "Installing recommended packages..." local -a failed_deps=() local -a pacman_deps=() for dep in "${optional_deps[@]}"; do if pacman -Si "$dep" &>/dev/null; then pacman_deps+=("$dep") else failed_deps+=("$dep") fi done if ((${#pacman_deps[@]})); then sudo pacman -S --needed --noconfirm "${pacman_deps[@]}" || warn "Some packages failed to install" fi if ((${#failed_deps[@]})); then local mirrorlist="/etc/pacman.d/mirrorlist" local fallback_mirror="Server = https://geo.mirror.pkgbuild.com/\$repo/os/\$arch" local added_fallback=false if ! grep -q "geo.mirror.pkgbuild.com" "$mirrorlist" 2>/dev/null; then info "Some packages not found in current repos. Adding official Arch mirror..." sudo sed -i "1i $fallback_mirror" "$mirrorlist" sudo pacman -Syy || warn "Failed to sync with official mirror" added_fallback=true fi local -a aur_optional=() local -a retry_pacman=() for dep in "${failed_deps[@]}"; do if pacman -Si "$dep" &>/dev/null; then retry_pacman+=("$dep") else aur_optional+=("$dep") fi done if ((${#retry_pacman[@]})); then info "Found packages in official repos: ${retry_pacman[*]}" sudo pacman -S --needed --noconfirm "${retry_pacman[@]}" || warn "Some packages failed to install from official repos" fi if $added_fallback; then sudo sed -i '/geo\.mirror\.pkgbuild\.com/d' "$mirrorlist" sudo pacman -Syy 2>/dev/null || true info "Removed fallback mirror, restored original mirrorlist" fi else local -a aur_optional=() 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 "" local aur_helper_available="" if command -v yay >/dev/null 2>&1; then if check_aur_helper_functional yay; then aur_helper_available="yay" else warn "yay is installed but broken (needs rebuild after system update)" read -p "Rebuild yay now? [Y/n]: " -n 1 -r echo REPLY=${REPLY:-Y} if [[ $REPLY =~ ^[Yy]$ ]] && rebuild_yay && check_aur_helper_functional yay; then aur_helper_available="yay" fi fi elif command -v paru >/dev/null 2>&1; then if check_aur_helper_functional paru; then aur_helper_available="paru" fi fi if [[ -n "$aur_helper_available" ]]; then read -p "Install AUR packages with $aur_helper_available? [y/N]: " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then for dep in "${aur_optional[@]}"; do info "Installing $dep..." # Use --skipreview (paru) to avoid blocking on out-of-date prompts with --noconfirm local aur_flags=(--needed --noconfirm) if [[ "$aur_helper_available" == "paru" ]]; then aur_flags+=(--skipreview) fi $aur_helper_available -S "${aur_flags[@]}" "$dep" || warn "Failed to install $dep from AUR" done fi else info "No functional AUR helper found (yay/paru). Install manually if desired." fi fi fi else info "All recommended packages are already installed!" fi echo "" echo "================================================================" check_steam_config } 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" sudo sed -i '/^#\[multilib\]/,/^#Include/ 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" 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" fi else die "Failed to enable multilib repository" fi } check_steam_config() { info "Checking Steam configuration..." local missing_groups=() if ! groups | grep -qw 'video'; then missing_groups+=("video") fi if ! groups | grep -qw 'input'; then missing_groups+=("input") fi if ! groups | grep -qw 'wheel'; then missing_groups+=("wheel") fi if ((${#missing_groups[@]})); then echo "" echo "================================================================" echo " USER GROUP PERMISSIONS" echo "================================================================" echo "" echo " Your user needs to be added to the following groups:" echo "" for group in "${missing_groups[@]}"; do case "$group" in video) echo " - video - Required for GPU hardware access" ;; input) echo " - input - Required for controller/gamepad support and gaming mode keybinds" ;; wheel) echo " - wheel - Required for NetworkManager control in gaming mode" ;; esac done echo "" echo " NOTE: After adding groups, you MUST log out and log back in" echo "" read -p "Add user to ${missing_groups[*]} group(s)? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then local groups_to_add groups_to_add=$(IFS=,; echo "${missing_groups[*]}") info "Adding user to groups: $groups_to_add" if sudo usermod -aG "$groups_to_add" "$USER"; then info "Successfully added user to group(s): $groups_to_add" NEEDS_RELOGIN=1 else err "Failed to add user to groups" fi fi else info "User is in video, input, and wheel groups - permissions OK" fi if [ -d "$HOME/.steam" ]; then info "Steam directory found at ~/.steam" fi if [ -d "$HOME/.local/share/Steam" ]; then info "Steam data directory found at ~/.local/share/Steam" fi 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" fi fi 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" fi } setup_performance_permissions() { local udev_rules_file="/etc/udev/rules.d/99-gaming-performance.rules" local sudoers_file="/etc/sudoers.d/gaming-mode-sysctl" local needs_setup=false if [ ! -f "$udev_rules_file" ] || [ ! -f "$sudoers_file" ]; then needs_setup=true fi if [ "$needs_setup" = false ]; then info "Performance permissions already configured" return 0 fi echo "" echo "================================================================" echo " PERFORMANCE PERMISSIONS SETUP" echo "================================================================" echo "" echo " To avoid sudo password prompts during gaming, we need to set" echo " up permissions for CPU and GPU performance control." echo "" read -p "Set up passwordless performance controls? [Y/n]: " -n 1 -r echo if [[ $REPLY =~ ^[Nn]$ ]]; then info "Skipping permissions setup" return 0 fi if [ ! -f "$udev_rules_file" ]; then info "Creating udev rules for CPU/GPU performance control..." if sudo tee "$udev_rules_file" > /dev/null <<'UDEV_RULES' KERNEL=="cpu[0-9]*", SUBSYSTEM=="cpu", ACTION=="add", RUN+="/bin/chmod 666 /sys/devices/system/cpu/%k/cpufreq/scaling_governor" KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="amdgpu", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/device/power_dpm_force_performance_level" KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/gt_boost_freq_mhz" KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/gt_min_freq_mhz" KERNEL=="card[0-9]", SUBSYSTEM=="drm", DRIVERS=="i915", ACTION=="add", RUN+="/bin/chmod 666 /sys/class/drm/%k/gt_max_freq_mhz" UDEV_RULES then info "Udev rules created successfully" sudo udevadm control --reload-rules || true sudo udevadm trigger --subsystem-match=cpu --subsystem-match=drm || true fi fi if [[ -f "$sudoers_file" ]]; then info "Performance sudoers already exist at $sudoers_file" else info "Creating sudoers rule for Performance Mode sysctl tuning..." sudo mkdir -p /etc/sudoers.d if sudo tee "$sudoers_file" > /dev/null << 'SUDOERS_PERF' %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_autogroup_enabled=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_migration_cost_ns=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_min_granularity_ns=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w kernel.sched_latency_ns=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.swappiness=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_ratio=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_background_ratio=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_writeback_centisecs=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w vm.dirty_expire_centisecs=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_watches=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.inotify.max_user_instances=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w fs.file-max=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.rmem_max=* %video ALL=(ALL) NOPASSWD: /usr/bin/sysctl -w net.core.wmem_max=* %video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pm * %video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pl * SUDOERS_PERF then sudo chmod 0440 "$sudoers_file" info "Performance sudoers created successfully" else err "Failed to create performance sudoers file" fi fi local memlock_file="/etc/security/limits.d/99-gaming-memlock.conf" if [ ! -f "$memlock_file" ]; then info "Creating memlock limits for gaming performance..." if sudo tee "$memlock_file" > /dev/null << 'MEMLOCKCONF' * soft memlock 2147484 * hard memlock 2147484 MEMLOCKCONF then info "Memlock limits configured (2GB)" fi fi local pipewire_conf_dir="/etc/pipewire/pipewire.conf.d" local pipewire_conf="$pipewire_conf_dir/10-gaming-latency.conf" if [ ! -f "$pipewire_conf" ]; then info "Creating PipeWire low-latency audio configuration..." sudo mkdir -p "$pipewire_conf_dir" if sudo tee "$pipewire_conf" > /dev/null << 'PIPEWIRECONF' context.properties = { default.clock.min-quantum = 256 } PIPEWIRECONF then info "PipeWire gaming latency configured" fi fi info "Performance permissions configured" return 0 } setup_shader_cache() { local env_file="/etc/environment.d/99-shader-cache.conf" if [ -f "$env_file" ]; then info "Shader cache configuration already exists" return 0 fi echo "" echo "================================================================" echo " SHADER CACHE OPTIMIZATION" echo "================================================================" echo "" echo " Configuring shader cache sizes for better gaming performance." echo " This reduces stuttering in games by caching compiled shaders." echo "" read -p "Configure shader cache optimization? [Y/n]: " -n 1 -r echo if [[ $REPLY =~ ^[Nn]$ ]]; then info "Skipping shader cache configuration" return 0 fi info "Creating shader cache configuration..." sudo mkdir -p /etc/environment.d || { warn "Failed to create /etc/environment.d"; return 0; } local tmp_shader tmp_shader=$(mktemp) || { warn "Failed to create temp file"; return 0; } cat > "$tmp_shader" << 'SHADERCACHE' MESA_SHADER_CACHE_MAX_SIZE=12G MESA_SHADER_CACHE_DISABLE_CLEANUP=1 RADV_PERFTEST=gpl __GL_SHADER_DISK_CACHE=1 __GL_SHADER_DISK_CACHE_SIZE=12884901888 __GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1 DXVK_STATE_CACHE=1 FCITX_NO_WAYLAND_DIAGNOSE=1 SHADERCACHE if sudo cp "$tmp_shader" "$env_file"; then rm -f "$tmp_shader" sudo chmod 644 "$env_file" info "Shader cache configured for all GPUs (AMD/NVIDIA + Proton)" else rm -f "$tmp_shader" warn "Failed to create shader cache configuration" fi } setup_fcitx_silence() { local env_dir="$HOME/.config/environment.d" local env_file="$env_dir/90-fcitx-wayland.conf" if [[ ! -f "$env_file" ]] || ! grep -q "FCITX_NO_WAYLAND_DIAGNOSE=1" "$env_file" 2>/dev/null; then mkdir -p "$env_dir" || return 0 cat > "$env_file" <<'EOF' FCITX_NO_WAYLAND_DIAGNOSE=1 EOF info "Created fcitx Wayland silence config" NEEDS_RELOGIN=1 fi } 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 "${packages_to_install[@]}" || die "package install failed" else die "Required packages missing - cannot continue" fi else info "All required packages present." fi setup_performance_permissions setup_fcitx_silence setup_shader_cache 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 "" echo "================================================================" echo " GAMESCOPE CAPABILITY REQUEST" echo "================================================================" echo "" echo " Performance mode requires granting cap_sys_nice to gamescope." echo "" read -p "Grant cap_sys_nice to gamescope? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then sudo setcap 'cap_sys_nice=eip' "$(command -v gamescope)" || warn "Failed to set capability" info "Capability granted to gamescope" fi fi fi } # Detect the KDE Plasma session name from available .desktop files detect_kde_session_name() { local session_name="plasma" # Check wayland sessions first (preferred) if [[ -f /usr/share/wayland-sessions/plasma.desktop ]]; then session_name="plasma" elif [[ -f /usr/share/wayland-sessions/plasmawayland.desktop ]]; then session_name="plasmawayland" # Fallback to X11 elif [[ -f /usr/share/xsessions/plasma.desktop ]]; then session_name="plasma" elif [[ -f /usr/share/xsessions/plasmax11.desktop ]]; then session_name="plasmax11" fi echo "$session_name" } setup_kde_shortcut() { local user_home="$1" info "Setting up KDE Plasma global shortcut for Gaming Mode..." # Create a .desktop file for the switch-to-gaming action local desktop_dir="/usr/share/applications" local desktop_file="$desktop_dir/switch-to-gaming.desktop" sudo tee "$desktop_file" > /dev/null << 'DESKTOP_ENTRY' [Desktop Entry] Type=Application Name=Switch to Gaming Mode Comment=Switch from KDE Plasma to Gaming Mode (Gamescope) Exec=/usr/local/bin/switch-to-gaming Icon=input-gaming NoDisplay=true Terminal=false Categories=Game; DESKTOP_ENTRY sudo chmod 644 "$desktop_file" info "Created $desktop_file" # Register the global shortcut in KDE's kglobalshortcutsrc local shortcuts_file="${user_home}/.config/kglobalshortcutsrc" if [[ -f "$shortcuts_file" ]]; then if grep -q "\[switch-to-gaming.desktop\]" "$shortcuts_file" 2>/dev/null; then info "Gaming Mode shortcut already exists in kglobalshortcutsrc" return 0 fi # Clear the Screen Reader shortcut (Meta+Alt+S) to avoid conflict if grep -q "Toggle Screen Reader On and Off=Meta+Alt+S" "$shortcuts_file" 2>/dev/null; then info "Clearing conflicting Screen Reader shortcut (Meta+Alt+S)..." sed -i 's|Toggle Screen Reader On and Off=Meta+Alt+S,Meta+Alt+S,|Toggle Screen Reader On and Off=none,none,|' "$shortcuts_file" info "Screen Reader shortcut cleared" fi fi # Append the shortcut configuration cat >> "$shortcuts_file" << 'KDE_SHORTCUT' [switch-to-gaming.desktop] _k_friendly_name=Switch to Gaming Mode _launch=Meta+Alt+S,none,Switch to Gaming Mode KDE_SHORTCUT info "Added Super+Alt+S shortcut to KDE global shortcuts" # Also create a khotkeys entry as fallback for older Plasma versions local khotkeys_file="${user_home}/.config/khotkeysrc" if [[ -f "$khotkeys_file" ]]; then if grep -q "switch-to-gaming" "$khotkeys_file" 2>/dev/null; then info "Gaming Mode khotkey already exists" return 0 fi fi # Notify KDE to reload shortcuts if running if command -v dbus-send >/dev/null 2>&1; then dbus-send --type=signal --dest=org.kde.kglobalaccel /kglobalaccel org.kde.KGlobalAccel.yourShortcutsChanged 2>/dev/null || true fi # Also try qdbus approach for Plasma 6 if command -v qdbus6 >/dev/null 2>&1; then qdbus6 org.kde.kglobalaccel /kglobalaccel org.kde.KGlobalAccel.blockGlobalShortcuts false 2>/dev/null || true fi info "KDE shortcut configuration complete" echo " NOTE: You may need to log out and back in for the shortcut to activate." echo " You can also manually set it in System Settings > Shortcuts > Custom Shortcuts." } setup_session_switching() { echo "" echo "================================================================" echo " SESSION SWITCHING SETUP (KDE Plasma <-> Gamescope)" echo " Using ChimeraOS gamescope-session packages" echo "================================================================" echo "" # Intel-only check if check_intel_only; then echo "" echo " NO DICE - INTEL ONLY DETECTED" echo "" err "This setup does not support Intel GPUs (iGPU or Arc)." echo "" echo " Gaming Mode requires AMD or NVIDIA graphics." echo "" exit 1 fi echo " This will:" echo " - Install gamescope-session-git and gamescope-session-steam-git from AUR" echo " - Configure Super+Alt+S to switch to Gaming Mode" echo " - Configure Steam's 'Exit to Desktop' to return to KDE Plasma" echo "" read -p "Set up session switching? [Y/n]: " -n 1 -r echo if [[ $REPLY =~ ^[Nn]$ ]]; then info "Skipping session switching setup" return 0 fi local current_user="${SUDO_USER:-$USER}" local user_home user_home=$(getent passwd "$current_user" | cut -d: -f6) local monitor_width=1920 local monitor_height=1080 local monitor_refresh=60 local monitor_output="" local -a dgpu_monitors=() local dgpu_card="" local dgpu_type="" detect_dgpu_monitors dgpu_monitors dgpu_card dgpu_type if [[ -z "$dgpu_card" ]]; then if [[ "$dgpu_type" == "NVIDIA" ]]; then warn "NVIDIA GPU detected but no DRM card found!" echo "" echo " This usually means nvidia-drm.modeset=1 is not set." echo " Continuing setup with defaults (2560x1440@60 on NVIDIA)." echo " A REBOOT will be required for full functionality." echo "" NEEDS_REBOOT=1 # Use safe NVIDIA defaults so session switching config is still created dgpu_card="card0" monitor_width=2560 monitor_height=1440 monitor_refresh=60 fi if [[ "$dgpu_type" != "NVIDIA" ]]; then # No dGPU and not NVIDIA - check for APU local apu_card="" local apu_monitors=() local card_name driver_link driver conn_dir conn_name status resolution mode_file for card_path in /sys/class/drm/card[0-9]*; do card_name=$(basename "$card_path") [[ "$card_name" == render* ]] && continue driver_link="$card_path/device/driver" [[ -L "$driver_link" ]] || continue driver=$(basename "$(readlink "$driver_link")") if [[ "$driver" == "amdgpu" ]] && is_amd_igpu_card "$card_path"; then apu_card="$card_name" for connector in "$card_path"/"$card_name"-*/status; do [[ -f "$connector" ]] || continue conn_dir=$(dirname "$connector") conn_name=$(basename "$conn_dir") conn_name=${conn_name#card*-} [[ "$conn_name" == Writeback* ]] && continue status=$(cat "$connector" 2>/dev/null) if [[ "$status" == "connected" ]]; then resolution="" mode_file="$conn_dir/modes" [[ -f "$mode_file" ]] && [[ -s "$mode_file" ]] && resolution=$(head -1 "$mode_file" 2>/dev/null) apu_monitors+=("$conn_name|$resolution") fi done break fi done if [[ -n "$apu_card" && ${#apu_monitors[@]} -gt 0 ]]; then echo "" info "No discrete GPU found, but detected AMD APU ($apu_card)" echo "" echo " This system has an AMD APU which can run Gaming Mode." echo " Detected monitors: ${#apu_monitors[@]}" echo "" read -p " Set up Gaming Mode for APU? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then dgpu_card="$apu_card" dgpu_type="AMD APU" dgpu_monitors=("${apu_monitors[@]}") info "Configuring Gaming Mode for AMD APU" else info "Skipping APU Gaming Mode setup" return 0 fi else err "No discrete GPU (dGPU) or AMD APU found!" echo " Gaming mode requires a supported GPU with a connected display." return 1 fi fi fi info "Found $dgpu_type on $dgpu_card" if [[ ${#dgpu_monitors[@]} -eq 0 ]]; then if [[ "$dgpu_type" == "NVIDIA" && "$NEEDS_REBOOT" -eq 1 ]]; then info "No monitors detected on NVIDIA (nvidia-drm.modeset=1 not yet active)" info "Using defaults: 2560x1440@60 — will auto-detect after reboot" else err "No monitors connected to dGPU!" echo "" echo " Gaming mode requires a monitor connected to the discrete GPU." echo " Please connect an external monitor to your dGPU port (HDMI/DP/USB-C)" echo " and re-run this installer." echo "" return 1 fi fi if [[ ${#dgpu_monitors[@]} -eq 1 ]]; then local entry="${dgpu_monitors[0]}" monitor_output="${entry%%|*}" local res="${entry##*|}" if [[ -n "$res" ]]; then monitor_width="${res%%x*}" monitor_height="${res##*x}" monitor_height="${monitor_height%%@*}" [[ "$res" == *@* ]] && monitor_refresh="${res##*@}" && monitor_refresh="${monitor_refresh%%.*}" fi else echo "" echo " Multiple monitors connected to $dgpu_type:" local i=1 for entry in "${dgpu_monitors[@]}"; do local name="${entry%%|*}" local res="${entry##*|}" echo " $i) $name ${res:+($res)}" ((i++)) done echo "" read -r -p "Select monitor for Gaming Mode [1-${#dgpu_monitors[@]}]: " selection if [[ ! "$selection" =~ ^[0-9]+$ ]] || ((selection < 1 || selection > ${#dgpu_monitors[@]})); then selection=1 fi local entry="${dgpu_monitors[$((selection-1))]}" monitor_output="${entry%%|*}" local res="${entry##*|}" if [[ -n "$res" ]]; then monitor_width="${res%%x*}" monitor_height="${res##*x}" monitor_height="${monitor_height%%@*}" [[ "$res" == *@* ]] && monitor_refresh="${res##*@}" && monitor_refresh="${monitor_refresh%%.*}" fi fi info "Selected dGPU display: ${monitor_output} (${monitor_width}x${monitor_height}@${monitor_refresh}Hz)" info "Checking for old custom session files to clean up..." local -a old_files=( "/usr/bin/gamescope-session" "/usr/share/wayland-sessions/gamescope-session.desktop" "/usr/bin/jupiter-biosupdate" "/usr/bin/steamos-update" "/usr/bin/steamos-select-branch" "/usr/bin/steamos-session-select" ) local cleaned=false for old_file in "${old_files[@]}"; do if [[ -f "$old_file" ]]; then info "Removing old file: $old_file" sudo rm -f "$old_file" && cleaned=true fi done if $cleaned; then info "Old custom session files removed" else info "No old files to clean up" fi info "Checking for ChimeraOS gamescope-session packages..." local -a aur_packages=() local -a packages_to_remove=() if ! check_package "gamescope-session-git" && ! check_package "gamescope-session"; then aur_packages+=("gamescope-session-git") # Remove conflicting providers (e.g. gamescope-session-cachyos) for conflicting in gamescope-session-cachyos; do if check_package "$conflicting"; then packages_to_remove+=("$conflicting") fi done fi local steam_scripts_missing=false local -a required_steam_scripts=( "/usr/bin/steamos-session-select" "/usr/bin/steamos-update" "/usr/bin/jupiter-biosupdate" "/usr/bin/steamos-select-branch" ) for script in "${required_steam_scripts[@]}"; do if [[ ! -f "$script" ]]; then steam_scripts_missing=true break fi done if ! check_package "gamescope-session-steam-git"; then if check_package "gamescope-session-steam"; then warn "gamescope-session-steam (non-git) is installed but missing Steam compatibility scripts" info "The -git version from ChimeraOS includes required scripts:" info " - steamos-session-select, steamos-update, jupiter-biosupdate, steamos-select-branch" packages_to_remove+=("gamescope-session-steam") fi aur_packages+=("gamescope-session-steam-git") elif $steam_scripts_missing; then warn "gamescope-session-steam-git is installed but Steam compatibility scripts are missing!" info "Will reinstall package to restore missing files:" for script in "${required_steam_scripts[@]}"; do if [[ ! -f "$script" ]]; then info " - Missing: $script" fi done packages_to_remove+=("gamescope-session-steam-git") aur_packages+=("gamescope-session-steam-git") fi # Clean stale AUR git caches local aur_cache_helper="" if command -v yay >/dev/null 2>&1; then aur_cache_helper="yay" elif command -v paru >/dev/null 2>&1; then aur_cache_helper="paru" fi if [[ -n "$aur_cache_helper" ]]; then local pkg_cache for pkg in gamescope-session-git gamescope-session-steam-git; do pkg_cache="$HOME/.cache/$aur_cache_helper/$pkg" if [[ -d "$pkg_cache" ]]; then info "Clearing AUR build cache for $pkg..." rm -rf "$pkg_cache" fi done fi if ((${#aur_packages[@]})); then echo "" echo " The following AUR packages are required for ChimeraOS session:" for pkg in "${aur_packages[@]}"; do echo " - $pkg" done if ((${#packages_to_remove[@]})); then echo "" echo " The following packages need to be replaced:" for pkg in "${packages_to_remove[@]}"; do echo " - $pkg (will be removed)" done fi echo "" 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 if [[ -n "$aur_helper" ]]; then read -p "Install ChimeraOS session packages with $aur_helper? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then if ((${#packages_to_remove[@]})); then info "Removing conflicting packages: ${packages_to_remove[*]}" sudo pacman -Rdd --noconfirm "${packages_to_remove[@]}" || { warn "Failed to remove old packages, trying to continue anyway..." } fi info "Installing ChimeraOS gamescope-session packages..." local overwrite_flags="--overwrite '/usr/bin/steamos-polkit-helpers/*'" if [[ "$aur_helper" == "yay" ]]; then $aur_helper -S --needed --noconfirm --answeredit None --answerclean None --answerdiff None $overwrite_flags "${aur_packages[@]}" || { err "Failed to install gamescope-session packages" warn "You may need to install them manually: $aur_helper -S ${aur_packages[*]}" } else $aur_helper -S --needed --noconfirm --skipreview $overwrite_flags "${aur_packages[@]}" || { err "Failed to install gamescope-session packages" warn "You may need to install them manually: $aur_helper -S ${aur_packages[*]}" } fi fi else warn "No AUR helper found (yay/paru). Please install manually:" if ((${#packages_to_remove[@]})); then echo " sudo pacman -Rns ${packages_to_remove[*]}" fi echo " yay -S ${aur_packages[*]}" echo "" read -r -p "Press Enter to continue after installing, or Ctrl+C to abort..." fi else info "ChimeraOS gamescope-session packages already installed (correct -git versions)" fi # Prevent distro repos (e.g. CachyOS) from replacing -git packages on system upgrades info "Ensuring gamescope-session -git packages are protected from replacement..." local ignore_pkgs=("gamescope-session-git" "gamescope-session-steam-git") local pacman_conf="/etc/pacman.conf" for pkg in "${ignore_pkgs[@]}"; do if grep -q "^IgnorePkg" "$pacman_conf"; then if ! grep -q "$pkg" "$pacman_conf"; then sudo sed -i "s/^IgnorePkg\s*=\(.*\)/IgnorePkg =\1 $pkg/" "$pacman_conf" info "Added $pkg to IgnorePkg in pacman.conf" fi else if grep -q "^#IgnorePkg" "$pacman_conf"; then sudo sed -i "s/^#IgnorePkg\s*=.*/IgnorePkg = $pkg/" "$pacman_conf" info "Enabled IgnorePkg with $pkg in pacman.conf" else sudo sed -i "/^\[options\]/a IgnorePkg = $pkg" "$pacman_conf" info "Added IgnorePkg with $pkg to pacman.conf" fi fi done info "Setting up NetworkManager integration..." if systemctl is-active --quiet iwd; then info "Detected iwd is active - configuring NetworkManager to use iwd backend..." 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 info "Created NetworkManager iwd backend configuration" fi if systemctl is-active --quiet systemd-networkd; then info "Detected systemd-networkd - configuring NetworkManager to avoid conflicts..." sudo mkdir -p /etc/NetworkManager/conf.d sudo tee /etc/NetworkManager/conf.d/20-unmanaged-systemd.conf > /dev/null << 'NM_UNMANAGED' [keyfile] unmanaged-devices=interface-name:en*;interface-name:eth* NM_UNMANAGED info "Configured NetworkManager to not manage ethernet interfaces" fi 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" LOG_TAG="gamescope-nm" log() { logger -t "$LOG_TAG" "$*"; echo "$*"; } if ! systemctl is-active --quiet NetworkManager.service; then log "Starting NetworkManager service..." systemctl start NetworkManager.service if [ $? -eq 0 ]; then touch "$NM_MARKER" log "NetworkManager started successfully" else log "ERROR: Failed to start NetworkManager" exit 1 fi log "Waiting for NetworkManager to initialize..." for i in {1..20}; do if nmcli general status &>/dev/null; then log "NetworkManager ready after ${i} attempts" break fi sleep 0.5 done if nmcli general status 2>/dev/null | grep -q "connected"; then log "Network connected and ready" else log "WARNING: NetworkManager running but not connected" fi else log "NetworkManager already running" fi nmcli general status 2>/dev/null || log "WARNING: nmcli status check failed" NM_START sudo chmod +x "$nm_start_script" local nm_stop_script="/usr/local/bin/gamescope-nm-stop" sudo tee "$nm_stop_script" > /dev/null << 'NM_STOP' #!/bin/bash NM_MARKER="/tmp/.gamescope-started-nm" LOG_TAG="gamescope-nm" log() { logger -t "$LOG_TAG" "$*"; echo "$*"; } if [ -f "$NM_MARKER" ]; then rm -f "$NM_MARKER" if systemctl is-active --quiet NetworkManager.service; then log "Stopping NetworkManager service..." systemctl stop NetworkManager.service 2>/dev/null || true sleep 1 fi log "Restarting iwd to restore WiFi..." systemctl restart iwd.service 2>/dev/null || true sleep 3 WIFI_IFACE=$(iw dev 2>/dev/null | awk '/Interface/{print $2; exit}') if [ -z "$WIFI_IFACE" ]; then WIFI_IFACE=$(ls /sys/class/net/ 2>/dev/null | grep -E '^wl' | head -1) fi if [ -n "$WIFI_IFACE" ]; then if iwctl station "$WIFI_IFACE" show 2>/dev/null | grep -qi "connected"; then log "WiFi restored on $WIFI_IFACE" else log "Triggering WiFi scan on $WIFI_IFACE..." iwctl station "$WIFI_IFACE" scan 2>/dev/null || true sleep 3 if iwctl station "$WIFI_IFACE" show 2>/dev/null | grep -qi "connected"; then log "WiFi connected on $WIFI_IFACE after scan" else log "WiFi on $WIFI_IFACE may need manual reconnection via iwctl" fi fi else log "No wireless interface found - skipping WiFi verification" fi else log "No marker file found - NetworkManager was not started by gaming session" fi NM_STOP sudo chmod +x "$nm_stop_script" info "Created NetworkManager start/stop scripts" local steam_mount_script="/usr/local/bin/steam-library-mount" info "Creating Steam library drive mount script..." sudo tee "$steam_mount_script" > /dev/null << 'STEAM_MOUNT' #!/bin/bash LOG_TAG="steam-library-mount" MOUNT_BASE="/run/media/$USER" log() { logger -t "$LOG_TAG" "$*"; } check_steam_library() { local mount_point="$1" if [[ -d "$mount_point/steamapps" ]] || \ [[ -d "$mount_point/SteamLibrary/steamapps" ]] || \ [[ -d "$mount_point/SteamLibrary" ]] || \ [[ -f "$mount_point/libraryfolder.vdf" ]] || \ [[ -f "$mount_point/steamapps/libraryfolder.vdf" ]] || \ [[ -f "$mount_point/SteamLibrary/libraryfolder.vdf" ]]; then return 0 fi return 1 } handle_device() { local device="$1" local part_name part_name=$(basename "$device") log "Checking device: $device" if findmnt -n "$device" &>/dev/null; then local existing_mount existing_mount=$(findmnt -n -o TARGET "$device" 2>/dev/null) if [[ -n "$existing_mount" ]] && check_steam_library "$existing_mount"; then log "Steam library already mounted at $existing_mount" else log "Device $device mounted at $existing_mount (no Steam library)" fi return fi [[ "$device" =~ [0-9]$ ]] || { log "Skipping whole disk: $device"; return; } local fstype fstype=$(lsblk -n -o FSTYPE --nodeps "$device" 2>/dev/null) case "$fstype" in ext4|ext3|ext2|btrfs|xfs|ntfs|vfat|exfat|f2fs) ;; crypto_LUKS) log "Skipping encrypted: $device"; return ;; swap) log "Skipping swap: $device"; return ;; "") log "Skipping $device - no filesystem"; return ;; *) log "Skipping $device - unsupported filesystem: $fstype"; return ;; esac if ! command -v udisksctl &>/dev/null; then log "udisksctl not found - cannot mount $device" return fi log "Attempting to mount $device..." local mount_output mount_output=$(udisksctl mount -b "$device" --no-user-interaction 2>&1) local mount_rc=$? if [[ $mount_rc -ne 0 ]]; then log "Could not mount $device: $mount_output" return fi local mount_point mount_point=$(findmnt -n -o TARGET "$device" 2>/dev/null) if [[ -z "$mount_point" ]]; then log "Could not determine mount point for $device" return fi if check_steam_library "$mount_point"; then log "Steam library found on $device at $mount_point - keeping mounted" else log "No Steam library on $device - unmounting" udisksctl unmount -b "$device" --no-user-interaction 2>/dev/null fi } log "Starting Steam library drive monitor..." shopt -s nullglob for dev in /dev/sd*[0-9]* /dev/nvme*p[0-9]*; do [[ -b "$dev" ]] && handle_device "$dev" done shopt -u nullglob log "Initial device scan complete, watching for new devices..." udevadm monitor --kernel --subsystem-match=block 2>/dev/null | while read -r line; do if [[ "$line" =~ ^KERNEL.*[[:space:]]add[[:space:]]+.*/([^/[:space:]]+)[[:space:]]+\(block\)$ ]]; then dev_name="${BASH_REMATCH[1]}" dev_path="/dev/$dev_name" if [[ "$dev_name" =~ [0-9]$ ]] && [[ -b "$dev_path" ]]; then sleep 1 handle_device "$dev_path" fi fi done STEAM_MOUNT sudo chmod +x "$steam_mount_script" info "Created $steam_mount_script" local polkit_rules="/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules" if sudo test -f "$polkit_rules"; then info "Polkit rules already exist at $polkit_rules" else info "Creating Polkit rules for NetworkManager D-Bus access..." sudo mkdir -p /etc/polkit-1/rules.d if sudo tee "$polkit_rules" > /dev/null << 'POLKIT_RULES' polkit.addRule(function(action, subject) { if ((action.id == "org.freedesktop.NetworkManager.enable-disable-network" || action.id == "org.freedesktop.NetworkManager.enable-disable-wifi" || action.id == "org.freedesktop.NetworkManager.network-control" || action.id == "org.freedesktop.NetworkManager.wifi.scan" || action.id == "org.freedesktop.NetworkManager.settings.modify.system" || action.id == "org.freedesktop.NetworkManager.settings.modify.own" || action.id == "org.freedesktop.NetworkManager.settings.modify.hostname") && subject.isInGroup("wheel")) { return polkit.Result.YES; } }); POLKIT_RULES then sudo chmod 644 "$polkit_rules" info "Polkit rules created successfully" sudo systemctl restart polkit.service 2>/dev/null || true else err "Failed to create polkit rules file" fi fi local udisks_polkit="/etc/polkit-1/rules.d/50-udisks-gaming.rules" if sudo test -f "$udisks_polkit"; then info "Udisks2 polkit rules already exist at $udisks_polkit" else info "Creating Polkit rules for external drive auto-mount..." sudo mkdir -p /etc/polkit-1/rules.d if 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 then sudo chmod 644 "$udisks_polkit" info "Udisks2 polkit rules created successfully" sudo systemctl restart polkit.service 2>/dev/null || true else err "Failed to create udisks2 polkit rules" fi fi info "Creating gamescope-session-plus configuration..." local env_dir="${user_home}/.config/environment.d" local gamescope_conf="${env_dir}/gamescope-session-plus.conf" mkdir -p "$env_dir" local output_connector="" [[ -n "$monitor_output" ]] && output_connector="OUTPUT_CONNECTOR=$monitor_output" local is_nvidia=false local nvidia_device_id="" if [[ "$dgpu_type" == "NVIDIA" ]]; then is_nvidia=true nvidia_device_id=$(/usr/bin/lspci -nn | grep -i nvidia | grep -oP '\[10de:\K[0-9a-fA-F]+' | head -1) if [ "$monitor_width" -gt 2560 ]; then monitor_width=2560 fi if [ "$monitor_height" -gt 1440 ]; then monitor_height=1440 fi fi if $is_nvidia; then local vulkan_adapter="" [[ -n "$nvidia_device_id" ]] && vulkan_adapter="VULKAN_ADAPTER=10de:${nvidia_device_id}" cat > "$gamescope_conf" << GAMESCOPE_CONF SCREEN_WIDTH=${monitor_width} SCREEN_HEIGHT=${monitor_height} CUSTOM_REFRESH_RATES=${monitor_refresh} ${output_connector} ${vulkan_adapter} GBM_BACKEND=nvidia-drm STEAM_ALLOW_DRIVE_UNMOUNT=1 FCITX_NO_WAYLAND_DIAGNOSE=1 SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0 GAMESCOPE_CONF else cat > "$gamescope_conf" << GAMESCOPE_CONF SCREEN_WIDTH=${monitor_width} SCREEN_HEIGHT=${monitor_height} CUSTOM_REFRESH_RATES=${monitor_refresh} ${output_connector} ADAPTIVE_SYNC=1 ENABLE_GAMESCOPE_HDR=1 STEAM_ALLOW_DRIVE_UNMOUNT=1 FCITX_NO_WAYLAND_DIAGNOSE=1 SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0 GAMESCOPE_CONF fi info "Created $gamescope_conf" info "Creating NVIDIA gamescope wrapper..." local nvidia_wrapper_dir="/usr/local/lib/gamescope-nvidia" local nvidia_wrapper="${nvidia_wrapper_dir}/gamescope" sudo mkdir -p "$nvidia_wrapper_dir" sudo tee "$nvidia_wrapper" > /dev/null << 'NVIDIA_WRAPPER' #!/bin/bash EXTRA_ARGS="" if /usr/bin/gamescope --help 2>&1 | grep -q "force-composition"; then EXTRA_ARGS="--force-composition" fi exec /usr/bin/gamescope $EXTRA_ARGS "$@" NVIDIA_WRAPPER sudo chmod +x "$nvidia_wrapper" info "Created $nvidia_wrapper" info "Creating NetworkManager session wrapper..." local nm_wrapper="/usr/local/bin/gamescope-session-nm-wrapper" sudo tee "$nm_wrapper" > /dev/null << 'NM_WRAPPER' #!/bin/bash log() { logger -t gamescope-wrapper "$*"; echo "$*"; } enable_performance_mode() { log "Enabling performance mode..." for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo performance > "$gov" 2>/dev/null && log "CPU governor set to performance" break done for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo performance > "$gov" 2>/dev/null done if command -v nvidia-smi &>/dev/null; then sudo -n nvidia-smi -pm 1 2>/dev/null && log "NVIDIA persistence mode enabled" local max_power max_power=$(nvidia-smi --query-gpu=power.max_limit --format=csv,noheader,nounits 2>/dev/null | head -1 | cut -d'.' -f1) if [[ -n "$max_power" && "$max_power" -gt 0 ]]; then sudo -n nvidia-smi -pl "$max_power" 2>/dev/null && log "NVIDIA power limit set to ${max_power}W" fi for nvidia_pci in /sys/bus/pci/devices/*/power/control; do if [[ -f "${nvidia_pci%/power/control}/driver" ]]; then local drv=$(basename "$(readlink -f "${nvidia_pci%/power/control}/driver")" 2>/dev/null) if [[ "$drv" == "nvidia" ]]; then echo on > "$nvidia_pci" 2>/dev/null && log "NVIDIA runtime suspend disabled" fi fi done fi if command -v powerprofilesctl &>/dev/null; then powerprofilesctl set performance 2>/dev/null && log "Power profile set to performance" fi } restore_balanced_mode() { log "Restoring balanced mode..." for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo powersave > "$gov" 2>/dev/null done if command -v nvidia-smi &>/dev/null; then local default_power default_power=$(nvidia-smi --query-gpu=power.default_limit --format=csv,noheader,nounits 2>/dev/null | head -1 | cut -d'.' -f1) if [[ -n "$default_power" && "$default_power" -gt 0 ]]; then sudo -n nvidia-smi -pl "$default_power" 2>/dev/null fi for nvidia_pci in /sys/bus/pci/devices/*/power/control; do if [[ -f "${nvidia_pci%/power/control}/driver" ]]; then local drv=$(basename "$(readlink -f "${nvidia_pci%/power/control}/driver")" 2>/dev/null) if [[ "$drv" == "nvidia" ]]; then echo auto > "$nvidia_pci" 2>/dev/null fi fi done sudo -n nvidia-smi -pm 0 2>/dev/null fi if command -v powerprofilesctl &>/dev/null; then powerprofilesctl set balanced 2>/dev/null fi log "Balanced mode restored" } cleanup() { pkill -f steam-library-mount 2>/dev/null || true pkill -f gaming-keybind-monitor 2>/dev/null || true sudo -n /usr/local/bin/gamescope-nm-stop 2>/dev/null || true restore_balanced_mode rm -f /tmp/.gaming-session-active } trap cleanup EXIT INT TERM enable_performance_mode if /usr/bin/lspci 2>/dev/null | grep -qi nvidia; then export PATH="/usr/local/lib/gamescope-nvidia:$PATH" fi sudo -n /usr/local/bin/gamescope-nm-start 2>/dev/null || { log "Warning: Could not start NetworkManager" } if [[ -x /usr/local/bin/steam-library-mount ]]; then /usr/local/bin/steam-library-mount & log "Steam library drive monitor started" else log "Warning: steam-library-mount not found" fi echo "gamescope" > /tmp/.gaming-session-active keybind_ok=true if ! python3 -c "import evdev" 2>/dev/null; then log "WARNING: python-evdev not installed" keybind_ok=false fi if ! groups | grep -qw input; then log "WARNING: User not in 'input' group" keybind_ok=false fi if $keybind_ok && ! ls /dev/input/event* >/dev/null 2>&1; then log "WARNING: No input devices accessible" keybind_ok=false fi if $keybind_ok; then /usr/local/bin/gaming-keybind-monitor & log "Keybind monitor started (Super+Alt+R to exit)" else log "Keybind monitor NOT started" fi export QT_IM_MODULE=steam export GTK_IM_MODULE=Steam export STEAM_DISABLE_AUDIO_DEVICE_SWITCHING=1 export STEAM_ENABLE_VOLUME_HANDLER=1 /usr/share/gamescope-session-plus/gamescope-session-plus steam rc=$? exit $rc NM_WRAPPER sudo chmod +x "$nm_wrapper" info "Created $nm_wrapper" info "Creating 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 (ChimeraOS) Comment=Steam Big Picture with ChimeraOS gamescope-session Exec=/usr/local/bin/gamescope-session-nm-wrapper Type=Application DesktopNames=gamescope SESSION_DESKTOP info "Created $session_desktop" info "Creating session-select script..." local os_session_select="/usr/lib/os-session-select" sudo tee "$os_session_select" > /dev/null << 'OS_SESSION_SELECT' #!/bin/bash rm -f /tmp/.gaming-session-active sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || { echo "Warning: Failed to update session config" } timeout 5 steam -shutdown 2>/dev/null || true sleep 1 nohup sudo -n systemctl restart sddm &>/dev/null & disown exit 0 OS_SESSION_SELECT sudo chmod +x "$os_session_select" info "Created $os_session_select" info "Creating switch-to-gaming script..." local switch_script="/usr/local/bin/switch-to-gaming" sudo tee "$switch_script" > /dev/null << 'SWITCH_SCRIPT' #!/bin/bash # Inhibit suspend FIRST sudo -n systemctl mask --runtime sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null sudo -n /usr/local/bin/gaming-session-switch gaming 2>/dev/null || { notify-send -u critical -t 3000 "Gaming Mode" "Failed to update session config" 2>/dev/null || true } notify-send -u normal -t 2000 "Gaming Mode" "Switching to Gaming Mode..." 2>/dev/null || true pkill -9 gamescope 2>/dev/null || true pkill -9 -f gamescope-session 2>/dev/null || true sleep 1 sudo -n chvt 2 2>/dev/null || true sleep 0.3 sudo -n systemctl restart sddm SWITCH_SCRIPT sudo chmod +x "$switch_script" info "Created $switch_script" info "Creating switch-to-desktop script..." local switch_desktop_script="/usr/local/bin/switch-to-desktop" sudo tee "$switch_desktop_script" > /dev/null << 'SWITCH_DESKTOP' #!/bin/bash if [[ ! -f /tmp/.gaming-session-active ]]; then exit 0 fi rm -f /tmp/.gaming-session-active sudo -n systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || true # Re-enable Bluetooth sudo -n /usr/bin/rfkill unblock bluetooth 2>/dev/null || true sudo -n /usr/bin/systemctl start bluetooth.service 2>/dev/null || true timeout 5 steam -shutdown 2>/dev/null || true sleep 1 pkill -TERM gamescope 2>/dev/null || true pkill -TERM -f gamescope-session 2>/dev/null || true for _ in {1..6}; do pgrep -x gamescope >/dev/null 2>&1 || break sleep 0.5 done if pgrep -x gamescope >/dev/null 2>&1; then pkill -9 gamescope 2>/dev/null || true pkill -9 -f gamescope-session 2>/dev/null || true fi sleep 2 sudo -n chvt 2 2>/dev/null || true sleep 0.5 sudo -n systemctl stop sddm 2>/dev/null || true sleep 1 sudo -n systemctl start sddm & disown exit 0 SWITCH_DESKTOP sudo chmod +x "$switch_desktop_script" info "Created $switch_desktop_script" info "Creating gaming mode keybind monitor..." local keybind_monitor="/usr/local/bin/gaming-keybind-monitor" sudo tee "$keybind_monitor" > /dev/null << 'KEYBIND_MONITOR' #!/usr/bin/env python3 import sys import subprocess import time import syslog def log(msg, error=False): print(msg, file=sys.stderr if error else sys.stdout) syslog.syslog(syslog.LOG_ERR if error else syslog.LOG_INFO, msg) syslog.openlog("gaming-keybind-monitor", syslog.LOG_PID) try: import evdev from evdev import ecodes except ImportError: log("FATAL: python-evdev not installed", error=True) sys.exit(1) def find_keyboards(): keyboards = [] devices_checked = 0 permission_errors = 0 for path in evdev.list_devices(): devices_checked += 1 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 PermissionError: permission_errors += 1 except Exception: continue if permission_errors > 0 and not keyboards: log(f"FATAL: Permission denied on {permission_errors}/{devices_checked} input devices.", error=True) return keyboards def monitor_keyboards(keyboards): meta_pressed = False alt_pressed = False from selectors import DefaultSelector, EVENT_READ selector = DefaultSelector() for kbd in keyboards: selector.register(kbd, EVENT_READ) log(f"Monitoring {len(keyboards)} keyboard(s) for Super+Alt+R...") 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_LEFTALT, ecodes.KEY_RIGHTALT): alt_pressed = event.value > 0 elif event.code == ecodes.KEY_R and event.value == 1: if meta_pressed and alt_pressed: log("Super+Alt+R detected! Switching to desktop...") subprocess.run(['/usr/local/bin/switch-to-desktop']) return except Exception as e: log(f"Read error: {e}", error=True) continue except KeyboardInterrupt: pass finally: selector.close() def main(): time.sleep(2) keyboards = find_keyboards() if not keyboards: log("FATAL: No accessible keyboards found!", error=True) sys.exit(1) monitor_keyboards(keyboards) if __name__ == '__main__': main() KEYBIND_MONITOR sudo chmod +x "$keybind_monitor" info "Created $keybind_monitor" # Detect the correct KDE session name local kde_session_name kde_session_name=$(detect_kde_session_name) info "Detected KDE session: $kde_session_name" info "Creating SDDM session switching config..." local sddm_gaming_conf="/etc/sddm.conf.d/zz-gaming-session.conf" local autologin_user="$current_user" if [[ -f /etc/sddm.conf.d/autologin.conf ]]; then 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" fi sudo mkdir -p /etc/sddm.conf.d sudo tee "$sddm_gaming_conf" > /dev/null << SDDM_GAMING [Autologin] User=${autologin_user} Session=${kde_session_name} Relogin=true SDDM_GAMING info "Created $sddm_gaming_conf (default session: $kde_session_name)" # Remove conflicting Session=/Autologin from /etc/sddm.conf so .conf.d takes effect if [[ -f /etc/sddm.conf ]] && grep -q '^Session=' /etc/sddm.conf 2>/dev/null; then info "Removing conflicting Session= from /etc/sddm.conf (would override .conf.d)..." sudo sed -i '/^Session=.*/d' /etc/sddm.conf # Clean up empty [Autologin] section if nothing left in it if ! grep -qE '^(User|Session|Relogin)=' /etc/sddm.conf 2>/dev/null; then sudo sed -i '/^\[Autologin\]/d' /etc/sddm.conf fi sudo sed -i '/^[[:space:]]*$/d' /etc/sddm.conf fi # Ensure user is in the autologin group (required by SDDM for passwordless login) if ! groups "$autologin_user" 2>/dev/null | grep -q '\bautologin\b'; then info "Adding $autologin_user to autologin group for passwordless session switching..." sudo groupadd -f autologin sudo usermod -aG autologin "$autologin_user" info "Added $autologin_user to autologin group" else info "User $autologin_user already in autologin group" fi info "Creating session switching helper script..." local session_helper="/usr/local/bin/gaming-session-switch" sudo tee "$session_helper" > /dev/null << SESSION_HELPER #!/bin/bash CONF="/etc/sddm.conf.d/zz-gaming-session.conf" MAIN_CONF="/etc/sddm.conf" if [[ ! -f "\$CONF" ]]; then echo "Error: Config file not found: \$CONF" >&2 exit 1 fi case "\$1" in gaming) sed -i 's/^Session=.*/Session=gamescope-session-steam-nm/' "\$CONF" echo "Session set to: gaming mode" ;; desktop) sed -i 's/^Session=.*/Session=${kde_session_name}/' "\$CONF" echo "Session set to: desktop mode (KDE Plasma)" ;; *) echo "Usage: \$0 {gaming|desktop}" >&2 exit 1 ;; esac # Remove conflicting Session= from /etc/sddm.conf so .conf.d takes effect if [[ -f "\$MAIN_CONF" ]] && grep -q '^Session=' "\$MAIN_CONF" 2>/dev/null; then sed -i '/^Session=.*/d' "\$MAIN_CONF" # Clean up empty [Autologin] section if nothing left in it if ! grep -qE '^(User|Session|Relogin)=' "\$MAIN_CONF" 2>/dev/null; then sed -i '/^\[Autologin\]/d' "\$MAIN_CONF" fi sed -i '/^[[:space:]]*$/d' "\$MAIN_CONF" fi SESSION_HELPER sudo chmod +x "$session_helper" info "Created $session_helper" local sudoers_session="/etc/sudoers.d/gaming-session-switch" if [[ -f "$sudoers_session" ]]; then info "Removing old sudoers rules to update..." sudo rm -f "$sudoers_session" fi info "Creating sudoers rules for session switching..." sudo mkdir -p /etc/sudoers.d if sudo tee "$sudoers_session" > /dev/null << 'SUDOERS_SWITCH' %video ALL=(ALL) NOPASSWD: /usr/local/bin/gaming-session-switch %video ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart sddm %video ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop sddm %video ALL=(ALL) NOPASSWD: /usr/bin/systemctl start sddm %video ALL=(ALL) NOPASSWD: /usr/bin/chvt %video ALL=(ALL) NOPASSWD: /usr/bin/systemctl mask --runtime sleep.target suspend.target hibernate.target hybrid-sleep.target %video ALL=(ALL) NOPASSWD: /usr/bin/systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target %wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl start NetworkManager.service %wheel ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop NetworkManager.service %video ALL=(ALL) NOPASSWD: /usr/bin/systemctl start bluetooth.service %video ALL=(ALL) NOPASSWD: /usr/bin/rfkill unblock bluetooth %wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-start %wheel ALL=(ALL) NOPASSWD: /usr/local/bin/gamescope-nm-stop %video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pm * %video ALL=(ALL) NOPASSWD: /usr/bin/nvidia-smi -pl * SUDOERS_SWITCH then sudo chmod 0440 "$sudoers_session" info "Sudoers rules created successfully" else err "Failed to create sudoers file" fi # Set up KDE Plasma keybinding instead of Hyprland binding setup_kde_shortcut "$user_home" info "Steam compatibility scripts provided by gamescope-session-steam-git" info "Verifying NetworkManager integration..." echo "" local nm_test_ok=true local iwd_was_active=false systemctl is-active --quiet iwd.service && iwd_was_active=true if ! systemctl is-active --quiet NetworkManager.service; then info "Testing NetworkManager startup..." if sudo systemctl start NetworkManager.service 2>/dev/null; then sleep 2 if nmcli general status &>/dev/null; then info "NetworkManager started successfully" if nmcli general status 2>/dev/null | grep -qE "connected|connecting"; then info "NetworkManager can see network - Steam network access should work" else warn "NetworkManager running but shows disconnected" warn "This is expected if iwd/systemd-networkd manages your connection" info "Steam should still be able to use the network via D-Bus" fi sudo systemctl stop NetworkManager.service 2>/dev/null || true if $iwd_was_active; then info "Restoring iwd WiFi connection..." sudo systemctl restart iwd.service 2>/dev/null || true sleep 2 fi else nm_test_ok=false err "NetworkManager started but nmcli not responding" sudo systemctl stop NetworkManager.service 2>/dev/null || true if $iwd_was_active; then sudo systemctl restart iwd.service 2>/dev/null || true fi fi else nm_test_ok=false err "Failed to start NetworkManager for testing" fi else info "NetworkManager already running - integration should work" fi echo "" echo "================================================================" echo " SESSION SWITCHING CONFIGURED (ChimeraOS + KDE Plasma)" echo "================================================================" echo "" echo " Usage:" echo " - Press Super+Alt+S in KDE Plasma to switch to Gaming Mode" echo " - Press Super+Alt+R in Gaming Mode to return to KDE Plasma" echo " - (Steam's Power > Exit to Desktop also works as fallback)" echo "" echo " ChimeraOS packages installed:" echo " - gamescope-session-git (base session framework)" echo " - gamescope-session-steam-git (Steam session)" echo "" echo " Files created/modified:" echo " - ~/.config/environment.d/gamescope-session-plus.conf" echo " - /usr/local/bin/gamescope-session-nm-wrapper" echo " - /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop" echo " - /usr/lib/os-session-select" echo " - /usr/local/bin/switch-to-gaming" echo " - /usr/local/bin/switch-to-desktop" echo " - /usr/local/bin/gaming-keybind-monitor (Super+Alt+R)" echo " - /usr/share/applications/switch-to-gaming.desktop" echo " - ~/.config/kglobalshortcutsrc (KDE shortcut added)" echo "" echo " NetworkManager integration (Steam network access):" echo " - /usr/local/bin/gamescope-nm-start" echo " - /usr/local/bin/gamescope-nm-stop" echo " - /etc/polkit-1/rules.d/50-gamescope-networkmanager.rules" echo " - /etc/sudoers.d/gaming-session-switch (NM rules added)" if [[ -f /etc/NetworkManager/conf.d/10-iwd-backend.conf ]]; then echo " - /etc/NetworkManager/conf.d/10-iwd-backend.conf (iwd backend)" fi if [[ -f /etc/NetworkManager/conf.d/20-unmanaged-systemd.conf ]]; then echo " - /etc/NetworkManager/conf.d/20-unmanaged-systemd.conf (systemd-networkd coexistence)" fi echo "" if [[ "$nm_test_ok" != "true" ]]; then echo " WARNING: NetworkManager test failed!" echo " Steam may not have network access in Gaming Mode." echo "" echo " Troubleshooting:" echo " 1. Ensure NetworkManager is installed: pacman -S networkmanager" echo " 2. Check if iwd is running: systemctl status iwd" echo " 3. Try manually: sudo systemctl start NetworkManager && nmcli general" echo " 4. Check logs: journalctl -u NetworkManager -n 50" echo "" fi return 0 } verify_installation() { echo "" echo "================================================================" echo " GAMING MODE INSTALLATION VERIFICATION (KDE Plasma)" echo "================================================================" echo "" local all_ok=true local missing_files=() local permission_issues=() declare -A expected_files=( ["/usr/local/bin/gamescope-session-nm-wrapper"]="755:ChimeraOS session with NM wrapper" ["/usr/local/lib/gamescope-nvidia/gamescope"]="755:NVIDIA gamescope wrapper (--force-composition)" ["/usr/local/bin/gaming-session-switch"]="755:Session switching helper (gaming/desktop)" ["/usr/lib/os-session-select"]="755:Steam Exit to Desktop handler" ["/usr/local/bin/switch-to-gaming"]="755:KDE Plasma to Gaming Mode switcher" ["/usr/local/bin/switch-to-desktop"]="755:Gaming Mode to Desktop switcher (Super+Alt+R)" ["/usr/local/bin/gaming-keybind-monitor"]="755:Keybind monitor for Super+Alt+R" ["/usr/local/bin/gamescope-nm-start"]="755:NetworkManager start script" ["/usr/local/bin/gamescope-nm-stop"]="755:NetworkManager stop script" ["/usr/local/bin/steam-library-mount"]="755:Steam library drive auto-mount script" ["/usr/bin/steamos-session-select"]="755:Steam compatibility (from AUR package)" ["/usr/bin/steamos-update"]="755:Steam compatibility (from AUR package)" ["/usr/bin/jupiter-biosupdate"]="755:Steam compatibility (from AUR package)" ["/usr/bin/steamos-select-branch"]="755:Steam compatibility (from AUR package)" ["/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"]="644:SDDM session entry" ["/usr/share/gamescope-session-plus/gamescope-session-plus"]="755:ChimeraOS session launcher (from AUR)" ["/usr/share/applications/switch-to-gaming.desktop"]="644:KDE shortcut desktop entry" ["/etc/sddm.conf.d/zz-gaming-session.conf"]="644:SDDM session switching config" ["/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules"]="644:Polkit NM rules" ["/etc/polkit-1/rules.d/50-udisks-gaming.rules"]="644:Polkit udisks2 rules (external drive mount)" ["/etc/sudoers.d/gaming-session-switch"]="440:Sudoers rules" ["/etc/NetworkManager/conf.d/10-iwd-backend.conf"]="644:NM iwd backend config (optional)" ["/etc/NetworkManager/conf.d/20-unmanaged-systemd.conf"]="644:NM systemd coexistence (optional)" ["/etc/udev/rules.d/99-gaming-performance.rules"]="644:Udev performance rules" ["/etc/sudoers.d/gaming-mode-sysctl"]="440:Performance sudoers" ["/etc/security/limits.d/99-gaming-memlock.conf"]="644:Memlock limits" ["/etc/pipewire/pipewire.conf.d/10-gaming-latency.conf"]="644:PipeWire low-latency" ["/etc/environment.d/99-shader-cache.conf"]="644:Shader cache config" ) echo " FILE STATUS:" echo " ------------" echo "" for file in "${!expected_files[@]}"; do local expected_perm="${expected_files[$file]%%:*}" local description="${expected_files[$file]#*:}" local is_optional=false [[ "$description" == *"(optional)"* ]] && is_optional=true if sudo test -f "$file" 2>/dev/null; then local actual_perm actual_perm=$(sudo stat -c "%a" "$file" 2>/dev/null) if [[ "$actual_perm" == "$expected_perm" ]]; then printf " OK %-55s [%s]\n" "$file" "$actual_perm" else printf " !! %-55s [%s] (expected %s)\n" "$file" "$actual_perm" "$expected_perm" permission_issues+=("$file: has $actual_perm, expected $expected_perm") all_ok=false fi else if $is_optional; then printf " -- %-55s [SKIPPED] %s\n" "$file" "(optional)" else printf " XX %-55s [MISSING]\n" "$file" missing_files+=("$file: $description") all_ok=false fi fi done echo "" echo " KDE PLASMA SHORTCUT:" echo " ---------------------" local kde_shortcuts="$HOME/.config/kglobalshortcutsrc" if [[ -f "$kde_shortcuts" ]]; then if grep -q "switch-to-gaming" "$kde_shortcuts" 2>/dev/null; then echo " OK Gaming Mode shortcut (Super+Alt+S) configured in kglobalshortcutsrc" else echo " XX Gaming Mode shortcut NOT found in kglobalshortcutsrc" echo " You can add it manually: System Settings > Shortcuts > Custom Shortcuts" all_ok=false fi else echo " !! kglobalshortcutsrc not found - shortcut needs manual setup" echo " Go to System Settings > Shortcuts to add Super+Alt+S" fi echo "" echo " CHIMERAOS PACKAGES:" echo " -------------------" if check_package "gamescope-session-git" || check_package "gamescope-session"; then echo " OK gamescope-session installed" else echo " XX gamescope-session NOT installed" all_ok=false fi if check_package "gamescope-session-steam-git" || check_package "gamescope-session-steam"; then echo " OK gamescope-session-steam installed" else echo " XX gamescope-session-steam NOT installed" all_ok=false fi echo "" echo " STEAM LIBRARY DRIVE SUPPORT:" echo " -----------------------------" if [[ -x "/usr/local/bin/steam-library-mount" ]]; then echo " OK steam-library-mount script installed" else echo " XX steam-library-mount NOT found - external Steam libraries will not auto-mount" all_ok=false fi if check_package "udisks2"; then echo " OK udisks2 installed (mount backend)" else echo " XX udisks2 NOT installed" all_ok=false fi if sudo test -f "/etc/polkit-1/rules.d/50-udisks-gaming.rules" 2>/dev/null; then echo " OK udisks2 polkit rules configured" else echo " XX udisks2 polkit rules NOT found" all_ok=false fi echo "" echo " KEYBIND MONITOR (Super+Alt+R):" echo " ---------------------------------" local keybind_ok=true if check_package "python-evdev"; then echo " OK python-evdev installed" else echo " XX python-evdev NOT installed" keybind_ok=false all_ok=false fi if python3 -c "import evdev" 2>/dev/null; then echo " OK python-evdev importable" else echo " XX python-evdev cannot be imported" keybind_ok=false all_ok=false fi if groups 2>/dev/null | grep -qw input; then echo " OK User in 'input' group" else echo " XX User NOT in 'input' group (required for keybind)" keybind_ok=false all_ok=false fi if ls /dev/input/event* >/dev/null 2>&1; then local test_device test_device=$(find /dev/input -name 'event*' -print -quit 2>/dev/null) if [[ -r "$test_device" ]]; then echo " OK Can read input devices" else echo " XX Cannot read $test_device (permission denied)" echo " (May need to log out/in after adding to input group)" keybind_ok=false all_ok=false fi else echo " !! No /dev/input/event* devices found" fi if $keybind_ok; then echo " -> Super+Alt+R keybind should work" else echo " -> Super+Alt+R keybind will NOT work (use Steam > Power > Exit to Desktop)" fi echo "" echo " USER CONFIG:" echo " ------------" local user_conf="$HOME/.config/environment.d/gamescope-session-plus.conf" if [[ -f "$user_conf" ]]; then echo " OK gamescope-session-plus.conf exists" else echo " XX gamescope-session-plus.conf NOT found" all_ok=false fi echo "" echo " USER GROUPS:" echo " ------------" local user_groups user_groups=$(groups 2>/dev/null) for grp in video input wheel; do if echo "$user_groups" | grep -qw "$grp"; then printf " OK User is in '%s' group\n" "$grp" else printf " XX User is NOT in '%s' group\n" "$grp" all_ok=false fi done echo "" echo " SERVICE STATUS:" echo " ---------------" echo " NetworkManager: $(systemctl is-active NetworkManager.service 2>/dev/null || echo 'inactive') (should be inactive until gaming mode)" echo " iwd: $(systemctl is-active iwd.service 2>/dev/null || echo 'inactive')" echo " systemd-networkd: $(systemctl is-active systemd-networkd.service 2>/dev/null || echo 'inactive')" echo " polkit: $(systemctl is-active polkit.service 2>/dev/null || echo 'inactive')" echo "" echo " SUDO PERMISSIONS TEST:" echo " ----------------------" if sudo -n true 2>/dev/null; then echo " OK sudo -n works (passwordless sudo available)" if sudo -n -l /usr/local/bin/gamescope-nm-start &>/dev/null; then echo " OK Can run gamescope-nm-start without password" else echo " XX Cannot run gamescope-nm-start without password" all_ok=false fi else echo " !! sudo -n test skipped (requires recent sudo auth)" echo " Run: sudo -v && sudo -n -l /usr/local/bin/gamescope-nm-start" fi echo "" echo "================================================================" if $all_ok; then echo " ALL CHECKS PASSED - Gaming Mode should work correctly" else echo " SOME ISSUES DETECTED" echo "" if ((${#missing_files[@]})); then echo " Missing files (${#missing_files[@]}):" for f in "${missing_files[@]}"; do echo " - $f" done fi if ((${#permission_issues[@]})); then echo "" echo " Permission issues (${#permission_issues[@]}):" for p in "${permission_issues[@]}"; do echo " - $p" done fi echo "" echo " Re-run the installer to fix these issues." fi echo "================================================================" echo "" $all_ok && return 0 || return 1 } execute_setup() { sudo -k sudo -v || die "sudo authentication required" validate_environment echo "" echo "================================================================" echo " SUPER SHIFT S GAMING MODE INSTALLER v${Super_Shift_S_VERSION}" echo " KDE Plasma Edition - Dependencies & GPU Configuration" echo "================================================================" echo "" check_steam_dependencies check_nvidia_kernel_params install_nvidia_deckmode_env setup_requirements setup_session_switching if [ "$NEEDS_REBOOT" -eq 1 ]; then echo "" echo "================================================================" echo " IMPORTANT: REBOOT REQUIRED" echo "================================================================" echo "" echo " Bootloader configuration has been updated (nvidia-drm.modeset=1)." echo " You MUST reboot for the kernel parameter to take effect." echo "" if [ "$NEEDS_RELOGIN" -eq 1 ]; then echo " Additionally, user groups were updated (video/input/wheel)." fi echo "" read -p "Reboot now? [y/N]: " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then info "Rebooting..." sleep 2 systemctl reboot else echo "" echo " Remember to reboot before continuing!" echo "" fi elif [ "$NEEDS_RELOGIN" -eq 1 ]; then echo "" echo "================================================================" echo " IMPORTANT: LOG OUT REQUIRED" echo "================================================================" echo "" echo " User groups have been updated. You MUST log out and log back in" echo " for the changes to take effect." echo "" read -r -p "Press Enter to exit (remember to log out)..." else echo "" echo "================================================================" echo " SETUP COMPLETE" echo "================================================================" echo "" echo " Dependencies, GPU configuration, and session switching are ready." echo "" echo " To switch to Gaming Mode: Press Super+Alt+S" echo " To return to Desktop: Press Super+Alt+R" echo "" fi echo "" read -p "Run installation verification? [Y/n]: " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then verify_installation fi } show_help() { echo "Super Shift S Gaming Mode Installer v${Super_Shift_S_VERSION}" echo "KDE Plasma Edition (CachyOS)" echo "" echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --help, -h Show this help message" echo " --verify, -v Run verification only (check all files and permissions)" echo " --version Show version number" echo "" echo "Without options, runs the full installation/setup process." echo "" } case "${1:-}" in --help|-h) show_help exit 0 ;; --verify|-v) echo "Running verification only..." verify_installation exit $? ;; --version) echo "Super Shift S Gaming Mode Installer v${Super_Shift_S_VERSION}" exit 0 ;; "") execute_setup ;; *) echo "Unknown option: $1" echo "Use --help for usage information." exit 1 ;; esac