Add detailed comments explaining each section of the installer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c72eaf577d
commit
263bc67683
1 changed files with 359 additions and 0 deletions
|
|
@ -1,20 +1,54 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# ==============================================================================
|
||||||
|
# Super Shift S - Omarchy Deck Mode Installer
|
||||||
|
#
|
||||||
|
# This script transforms an Omarchy (Arch Linux + Hyprland) desktop into a
|
||||||
|
# dual-mode system: Desktop Mode (Hyprland) and Gaming Mode (Steam Big Picture
|
||||||
|
# inside Gamescope — the same compositor the Steam Deck uses).
|
||||||
|
#
|
||||||
|
# It handles everything needed for this transformation:
|
||||||
|
# - Installing all Steam/gaming dependencies and GPU drivers
|
||||||
|
# - Configuring NVIDIA kernel parameters (nvidia-drm.modeset=1)
|
||||||
|
# - Setting up session switching between Hyprland and Gamescope via SDDM
|
||||||
|
# - Creating keybinds (Super+Shift+S to enter, Super+Shift+R to exit)
|
||||||
|
# - Configuring NetworkManager handoff (iwd <-> NM) for Steam network access
|
||||||
|
# - Setting up performance tuning (CPU governor, GPU power, kernel sysctl)
|
||||||
|
# - Auto-mounting external drives with Steam libraries
|
||||||
|
# - Configuring permissions (polkit, sudoers, udev) so it all works
|
||||||
|
# without password prompts during gameplay
|
||||||
|
#
|
||||||
|
# The script is idempotent — running it again skips steps that are already done.
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
set -Euo pipefail
|
set -Euo pipefail
|
||||||
|
# -E: ERR traps are inherited by functions, command substitutions, and subshells
|
||||||
|
# -u: Treat unset variables as errors (catches typos in variable names)
|
||||||
|
# -o pipefail: A pipeline fails if ANY command in it fails, not just the last one
|
||||||
|
|
||||||
Super_Shift_S_VERSION="12.27"
|
Super_Shift_S_VERSION="12.27"
|
||||||
|
|
||||||
|
# Load configuration from /etc/gaming-mode.conf (system-wide) or
|
||||||
|
# ~/.gaming-mode.conf (user override). This lets users disable performance
|
||||||
|
# tuning by setting PERFORMANCE_MODE=disabled without editing the script.
|
||||||
CONFIG_FILE="/etc/gaming-mode.conf"
|
CONFIG_FILE="/etc/gaming-mode.conf"
|
||||||
[[ -f "$HOME/.gaming-mode.conf" ]] && CONFIG_FILE="$HOME/.gaming-mode.conf"
|
[[ -f "$HOME/.gaming-mode.conf" ]] && CONFIG_FILE="$HOME/.gaming-mode.conf"
|
||||||
source "$CONFIG_FILE" 2>/dev/null || true
|
source "$CONFIG_FILE" 2>/dev/null || true
|
||||||
: "${PERFORMANCE_MODE:=enabled}"
|
: "${PERFORMANCE_MODE:=enabled}"
|
||||||
|
|
||||||
|
# Flags that track whether the user needs to reboot or re-login after setup.
|
||||||
|
# Various steps set these to 1 when they make changes that only take effect
|
||||||
|
# after a session restart (e.g. adding user groups, changing kernel params).
|
||||||
NEEDS_RELOGIN=0
|
NEEDS_RELOGIN=0
|
||||||
NEEDS_REBOOT=0
|
NEEDS_REBOOT=0
|
||||||
|
|
||||||
|
# Logging helpers — consistent prefix makes it easy to spot installer output
|
||||||
|
# in a busy terminal. err() goes to stderr so it can be captured separately.
|
||||||
info(){ echo "[*] $*"; }
|
info(){ echo "[*] $*"; }
|
||||||
warn(){ echo "[!] $*"; }
|
warn(){ echo "[!] $*"; }
|
||||||
err(){ echo "[!] $*" >&2; }
|
err(){ echo "[!] $*" >&2; }
|
||||||
|
|
||||||
|
# Fatal error handler — logs to the system journal (journalctl -t gaming-mode)
|
||||||
|
# so failures can be diagnosed even after the terminal is closed.
|
||||||
die() {
|
die() {
|
||||||
local msg="$1"; local code="${2:-1}"
|
local msg="$1"; local code="${2:-1}"
|
||||||
echo "FATAL: $msg" >&2
|
echo "FATAL: $msg" >&2
|
||||||
|
|
@ -22,6 +56,9 @@ die() {
|
||||||
exit "$code"
|
exit "$code"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# AUR helpers (yay, paru) can break after a major system update if their
|
||||||
|
# own dependencies change. This checks whether the helper actually runs,
|
||||||
|
# not just whether the binary exists on disk.
|
||||||
check_aur_helper_functional() {
|
check_aur_helper_functional() {
|
||||||
local helper="$1"
|
local helper="$1"
|
||||||
if $helper --version &>/dev/null; then
|
if $helper --version &>/dev/null; then
|
||||||
|
|
@ -31,6 +68,10 @@ check_aur_helper_functional() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# If yay is broken (common after Go or glibc updates), this rebuilds it
|
||||||
|
# from scratch by cloning the AUR package and running makepkg. This avoids
|
||||||
|
# a chicken-and-egg problem where you need an AUR helper to install an
|
||||||
|
# AUR helper.
|
||||||
rebuild_yay() {
|
rebuild_yay() {
|
||||||
info "Attempting to rebuild yay..."
|
info "Attempting to rebuild yay..."
|
||||||
local tmp_dir
|
local tmp_dir
|
||||||
|
|
@ -49,14 +90,28 @@ rebuild_yay() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Sanity check — make sure we're actually running on an Omarchy system.
|
||||||
|
# pacman = Arch Linux, hyprctl = Hyprland compositor, ~/.config/hypr = config dir.
|
||||||
|
# If any of these are missing, the script can't do its job.
|
||||||
validate_environment() {
|
validate_environment() {
|
||||||
command -v pacman >/dev/null || die "pacman required"
|
command -v pacman >/dev/null || die "pacman required"
|
||||||
command -v hyprctl >/dev/null || die "hyprctl required"
|
command -v hyprctl >/dev/null || die "hyprctl required"
|
||||||
[ -d "$HOME/.config/hypr" ] || die "Hyprland config directory not found (~/.config/hypr)"
|
[ -d "$HOME/.config/hypr" ] || die "Hyprland config directory not found (~/.config/hypr)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Quick check if a pacman package is installed. Used throughout the script
|
||||||
|
# to avoid reinstalling things that are already present.
|
||||||
check_package() { pacman -Qi "$1" &>/dev/null; }
|
check_package() { pacman -Qi "$1" &>/dev/null; }
|
||||||
|
|
||||||
|
# Distinguishes AMD integrated GPUs (iGPUs/APUs) from discrete AMD GPUs (dGPUs).
|
||||||
|
# This matters because Gaming Mode needs to target the RIGHT GPU — if you have
|
||||||
|
# both an AMD APU and an NVIDIA dGPU, we want to use the NVIDIA card for gaming,
|
||||||
|
# not the low-power APU that's driving your laptop display.
|
||||||
|
#
|
||||||
|
# It works by reading the PCI device info and matching against known AMD APU
|
||||||
|
# codenames (Phoenix, Rembrandt, Van Gogh, etc.). If the name matches an APU
|
||||||
|
# pattern, it returns 0 (true = this IS an iGPU). If it matches a discrete
|
||||||
|
# GPU pattern (Navi, RX, Vega 56/64), it returns 1 (false = this is a dGPU).
|
||||||
is_amd_igpu_card() {
|
is_amd_igpu_card() {
|
||||||
local card_path="$1"
|
local card_path="$1"
|
||||||
local device_path="$card_path/device"
|
local device_path="$card_path/device"
|
||||||
|
|
@ -73,6 +128,11 @@ is_amd_igpu_card() {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Intel-only GPU detection — Gaming Mode doesn't support Intel GPUs because
|
||||||
|
# Gamescope (the compositor) doesn't work well with Intel graphics.
|
||||||
|
# However, many laptops have Intel iGPU + NVIDIA/AMD dGPU — those are fine
|
||||||
|
# because we use the discrete GPU for gaming. This function only blocks
|
||||||
|
# systems where Intel is the ONLY GPU available.
|
||||||
check_intel_only() {
|
check_intel_only() {
|
||||||
# Returns 0 (true) if system has Intel GPU but NO AMD/NVIDIA GPU
|
# Returns 0 (true) if system has Intel GPU but NO AMD/NVIDIA GPU
|
||||||
# Returns 1 (false) if system has AMD/NVIDIA (Intel iGPU + dGPU is OK)
|
# Returns 1 (false) if system has AMD/NVIDIA (Intel iGPU + dGPU is OK)
|
||||||
|
|
@ -104,6 +164,17 @@ check_intel_only() {
|
||||||
return 1 # Has AMD/NVIDIA (or no Intel), allow it
|
return 1 # Has AMD/NVIDIA (or no Intel), allow it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Finds which monitors are physically connected to the discrete GPU.
|
||||||
|
# Gaming Mode needs to know which display output to use (e.g. HDMI-1, DP-2)
|
||||||
|
# and what resolution/refresh rate it supports.
|
||||||
|
#
|
||||||
|
# It scans /sys/class/drm/ for GPU cards, identifies the discrete GPU by its
|
||||||
|
# driver (nvidia or amdgpu), then checks each connector (HDMI, DP, etc.) to
|
||||||
|
# see if a monitor is plugged in. Resolution and refresh rate are read from
|
||||||
|
# the EDID data that the monitor reports to the system.
|
||||||
|
#
|
||||||
|
# Uses bash nameref variables (_monitors, _dgpu_card, _dgpu_type) so the
|
||||||
|
# caller gets the results without needing subshells or global variables.
|
||||||
detect_dgpu_monitors() {
|
detect_dgpu_monitors() {
|
||||||
local -n _monitors=$1
|
local -n _monitors=$1
|
||||||
local -n _dgpu_card=$2
|
local -n _dgpu_card=$2
|
||||||
|
|
@ -162,6 +233,15 @@ detect_dgpu_monitors() {
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# NVIDIA GPUs require a kernel parameter (nvidia-drm.modeset=1) to work
|
||||||
|
# properly with Wayland compositors like Gamescope. Without it, the GPU
|
||||||
|
# won't expose DRM (Direct Rendering Manager) devices, and Gamescope
|
||||||
|
# won't be able to take over the display.
|
||||||
|
#
|
||||||
|
# This function checks if the parameter is already set in the running
|
||||||
|
# kernel's command line (/proc/cmdline). If not, it detects the bootloader
|
||||||
|
# (Limine, GRUB, or systemd-boot) and offers to add it automatically.
|
||||||
|
# A reboot is required after adding the parameter.
|
||||||
check_nvidia_kernel_params() {
|
check_nvidia_kernel_params() {
|
||||||
local lspci_output
|
local lspci_output
|
||||||
lspci_output=$(/usr/bin/lspci 2>/dev/null)
|
lspci_output=$(/usr/bin/lspci 2>/dev/null)
|
||||||
|
|
@ -236,6 +316,10 @@ check_nvidia_kernel_params() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Adds nvidia-drm.modeset=1 to the Limine bootloader config.
|
||||||
|
# Limine stores kernel command line parameters on lines starting with "cmdline:".
|
||||||
|
# This appends the parameter to the end of that line. A backup is created first
|
||||||
|
# in case something goes wrong.
|
||||||
configure_limine_nvidia() {
|
configure_limine_nvidia() {
|
||||||
local config_file="$1"
|
local config_file="$1"
|
||||||
|
|
||||||
|
|
@ -265,6 +349,9 @@ configure_limine_nvidia() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Adds nvidia-drm.modeset=1 to GRUB's kernel parameters.
|
||||||
|
# GRUB stores defaults in /etc/default/grub — after modifying it, grub-mkconfig
|
||||||
|
# must be run to regenerate the actual boot config at /boot/grub/grub.cfg.
|
||||||
configure_grub_nvidia() {
|
configure_grub_nvidia() {
|
||||||
local grub_default="/etc/default/grub"
|
local grub_default="/etc/default/grub"
|
||||||
|
|
||||||
|
|
@ -304,6 +391,14 @@ MSG
|
||||||
warn "Gaming Mode may not work correctly without nvidia-drm.modeset=1"
|
warn "Gaming Mode may not work correctly without nvidia-drm.modeset=1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Sets NVIDIA-specific environment variables needed for Gamescope to work
|
||||||
|
# properly with NVIDIA GPUs. These tell the system to use the NVIDIA DRM
|
||||||
|
# backend for GBM (Generic Buffer Management), which is how Wayland
|
||||||
|
# compositors allocate GPU memory for rendering.
|
||||||
|
#
|
||||||
|
# GBM_BACKEND=nvidia-drm — Use NVIDIA's DRM backend for buffer allocation
|
||||||
|
# __GLX_VENDOR_LIBRARY_NAME=nvidia — Use NVIDIA's GLX implementation
|
||||||
|
# __VK_LAYER_NV_optimus=NVIDIA_only — On Optimus laptops, force Vulkan to use NVIDIA
|
||||||
install_nvidia_deckmode_env() {
|
install_nvidia_deckmode_env() {
|
||||||
local lspci_output
|
local lspci_output
|
||||||
lspci_output=$(/usr/bin/lspci 2>/dev/null)
|
lspci_output=$(/usr/bin/lspci 2>/dev/null)
|
||||||
|
|
@ -332,6 +427,17 @@ EOF
|
||||||
NEEDS_RELOGIN=1
|
NEEDS_RELOGIN=1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Steam on Linux requires a LOT of dependencies — 32-bit libraries (lib32-*),
|
||||||
|
# Vulkan drivers, audio libraries, fonts, and GPU-specific drivers. This
|
||||||
|
# function checks for everything Steam needs and offers to install what's missing.
|
||||||
|
#
|
||||||
|
# It handles three categories:
|
||||||
|
# 1. Core deps — required for Steam to run at all (lib32 libs, Vulkan, audio)
|
||||||
|
# 2. GPU deps — driver packages specific to NVIDIA or AMD
|
||||||
|
# 3. Recommended — nice-to-haves like MangoHud (FPS overlay), Proton-GE, etc.
|
||||||
|
#
|
||||||
|
# The multilib repository must be enabled in pacman.conf for 32-bit packages
|
||||||
|
# to be available — Steam is a 32-bit application that needs 32-bit libraries.
|
||||||
check_steam_dependencies() {
|
check_steam_dependencies() {
|
||||||
info "Checking Steam dependencies for Arch Linux..."
|
info "Checking Steam dependencies for Arch Linux..."
|
||||||
|
|
||||||
|
|
@ -657,6 +763,11 @@ check_steam_dependencies() {
|
||||||
check_steam_config
|
check_steam_config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Enables the multilib repository in /etc/pacman.conf. Multilib provides
|
||||||
|
# 32-bit versions of libraries (lib32-*) which Steam requires because it's
|
||||||
|
# a 32-bit application. By default on Arch, these lines are commented out.
|
||||||
|
# This function uncomments them and runs a full system upgrade so the new
|
||||||
|
# packages become available.
|
||||||
enable_multilib_repo() {
|
enable_multilib_repo() {
|
||||||
info "Enabling multilib repository..."
|
info "Enabling multilib repository..."
|
||||||
|
|
||||||
|
|
@ -679,6 +790,14 @@ enable_multilib_repo() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Checks that the user is in the right Linux groups for gaming:
|
||||||
|
# - video: Access to GPU hardware (required for rendering)
|
||||||
|
# - input: Access to controllers, gamepads, and keyboard input devices
|
||||||
|
# - wheel: Sudo/admin group, needed for NetworkManager control in gaming mode
|
||||||
|
#
|
||||||
|
# Also checks for some common performance tips like lowering vm.swappiness
|
||||||
|
# (reduces swap usage during gaming) and increasing the open file limit
|
||||||
|
# (needed for esync, a Steam/Proton feature that reduces CPU overhead).
|
||||||
check_steam_config() {
|
check_steam_config() {
|
||||||
info "Checking Steam configuration..."
|
info "Checking Steam configuration..."
|
||||||
|
|
||||||
|
|
@ -753,6 +872,23 @@ check_steam_config() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Sets up the permissions needed for Gaming Mode to tune system performance
|
||||||
|
# WITHOUT requiring a password prompt. This creates three things:
|
||||||
|
#
|
||||||
|
# 1. UDEV RULES — Make CPU governor and GPU performance files writable by
|
||||||
|
# regular users. Normally these sysfs files are root-only, but udev rules
|
||||||
|
# can relax permissions when the devices are detected at boot.
|
||||||
|
#
|
||||||
|
# 2. SUDOERS RULES — Allow members of the "video" group to run specific
|
||||||
|
# sysctl commands (kernel scheduler tuning, VM settings, network buffers)
|
||||||
|
# and nvidia-smi commands without entering a password. These are narrowly
|
||||||
|
# scoped — only the exact commands needed, not blanket sudo access.
|
||||||
|
#
|
||||||
|
# 3. MEMLOCK LIMITS — Increase the memory lock limit to 2GB. Games using
|
||||||
|
# esync/fsync need to lock memory pages to avoid latency spikes.
|
||||||
|
#
|
||||||
|
# 4. PIPEWIRE CONFIG — Sets a lower audio quantum (buffer size) for reduced
|
||||||
|
# audio latency during gaming. Lower = less delay but more CPU usage.
|
||||||
setup_performance_permissions() {
|
setup_performance_permissions() {
|
||||||
local udev_rules_file="/etc/udev/rules.d/99-gaming-performance.rules"
|
local udev_rules_file="/etc/udev/rules.d/99-gaming-performance.rules"
|
||||||
local sudoers_file="/etc/sudoers.d/gaming-mode-sysctl"
|
local sudoers_file="/etc/sudoers.d/gaming-mode-sysctl"
|
||||||
|
|
@ -866,6 +1002,18 @@ PIPEWIRECONF
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Configures shader cache settings for better gaming performance.
|
||||||
|
# When a game runs for the first time, the GPU driver compiles shaders
|
||||||
|
# (small programs that run on the GPU). This causes stuttering because
|
||||||
|
# compilation takes time. By caching these compiled shaders to disk
|
||||||
|
# (up to 12GB), the stuttering only happens once — next time the shader
|
||||||
|
# loads instantly from cache.
|
||||||
|
#
|
||||||
|
# MESA_SHADER_CACHE — AMD/Intel open-source driver cache
|
||||||
|
# __GL_SHADER_DISK_CACHE — NVIDIA proprietary driver cache
|
||||||
|
# DXVK_STATE_CACHE — Proton/Wine DirectX-to-Vulkan translation cache
|
||||||
|
# RADV_PERFTEST=gpl — AMD Vulkan: enables graphics pipeline library for
|
||||||
|
# faster shader compilation
|
||||||
setup_shader_cache() {
|
setup_shader_cache() {
|
||||||
local env_file="/etc/environment.d/99-shader-cache.conf"
|
local env_file="/etc/environment.d/99-shader-cache.conf"
|
||||||
|
|
||||||
|
|
@ -916,6 +1064,10 @@ SHADERCACHE
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Silences a harmless but annoying warning from fcitx5 (input method framework).
|
||||||
|
# fcitx5 complains about Wayland support on every login, even if you don't use
|
||||||
|
# it for input. This sets FCITX_NO_WAYLAND_DIAGNOSE=1 to suppress the warning
|
||||||
|
# in both Hyprland config and the user's environment.
|
||||||
setup_fcitx_silence() {
|
setup_fcitx_silence() {
|
||||||
local env_dir="$HOME/.config/environment.d"
|
local env_dir="$HOME/.config/environment.d"
|
||||||
local env_file="$env_dir/90-fcitx-wayland.conf"
|
local env_file="$env_dir/90-fcitx-wayland.conf"
|
||||||
|
|
@ -941,6 +1093,10 @@ EOF
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Configures the Elephant app launcher (used in Omarchy) to launch desktop
|
||||||
|
# applications through uwsm-app. UWSM (Universal Wayland Session Manager)
|
||||||
|
# ensures apps are properly associated with the Wayland session, which
|
||||||
|
# prevents issues with apps losing track of their display server.
|
||||||
configure_elephant_launcher() {
|
configure_elephant_launcher() {
|
||||||
local cfg="$HOME/.config/elephant/desktopapplications.toml"
|
local cfg="$HOME/.config/elephant/desktopapplications.toml"
|
||||||
if [[ ! -f "$cfg" ]]; then
|
if [[ ! -f "$cfg" ]]; then
|
||||||
|
|
@ -963,6 +1119,8 @@ configure_elephant_launcher() {
|
||||||
restart_elephant_walker
|
restart_elephant_walker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Restarts the Elephant/Walker launcher service so config changes take
|
||||||
|
# effect immediately without requiring a logout.
|
||||||
restart_elephant_walker() {
|
restart_elephant_walker() {
|
||||||
if ! systemctl --user show-environment >/dev/null 2>&1; then
|
if ! systemctl --user show-environment >/dev/null 2>&1; then
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -975,6 +1133,16 @@ restart_elephant_walker() {
|
||||||
systemctl --user restart app-walker@autostart.service >/dev/null 2>&1 || true
|
systemctl --user restart app-walker@autostart.service >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Installs the core packages that the Gaming Mode scripts themselves need
|
||||||
|
# (as opposed to Steam's dependencies which are handled separately).
|
||||||
|
# These include:
|
||||||
|
# - python-evdev: Reads raw keyboard input for the Super+Shift+R hotkey
|
||||||
|
# - libcap: Sets Linux capabilities on gamescope (cap_sys_nice for priority)
|
||||||
|
# - ntfs-3g: Mounts NTFS-formatted game drives (common for Windows dual-boot)
|
||||||
|
# - xcb-util-cursor: X11 cursor support needed by some Proton games
|
||||||
|
#
|
||||||
|
# After installing packages, it also runs the sub-setup functions for
|
||||||
|
# performance permissions, shader cache, and gamescope capabilities.
|
||||||
setup_requirements() {
|
setup_requirements() {
|
||||||
local -a required_packages=("steam" "gamescope" "mangohud" "python" "python-evdev" "libcap" "gamemode" "curl" "pciutils" "ntfs-3g" "xcb-util-cursor")
|
local -a required_packages=("steam" "gamescope" "mangohud" "python" "python-evdev" "libcap" "gamemode" "curl" "pciutils" "ntfs-3g" "xcb-util-cursor")
|
||||||
local -a packages_to_install=()
|
local -a packages_to_install=()
|
||||||
|
|
@ -1019,6 +1187,34 @@ setup_requirements() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# SESSION SWITCHING — The heart of the installer
|
||||||
|
#
|
||||||
|
# This is the biggest and most important function. It sets up everything needed
|
||||||
|
# to seamlessly switch between Desktop Mode (Hyprland) and Gaming Mode
|
||||||
|
# (Gamescope + Steam Big Picture).
|
||||||
|
#
|
||||||
|
# The switching mechanism works through SDDM (the display/login manager):
|
||||||
|
# 1. User presses Super+Shift+S in Hyprland
|
||||||
|
# 2. switch-to-gaming script updates SDDM config to point to Gaming session
|
||||||
|
# 3. SDDM restarts and auto-logs into the Gaming Mode session
|
||||||
|
# 4. gamescope-session-nm-wrapper starts performance tuning, NetworkManager,
|
||||||
|
# drive mounting, keybind monitor, then launches Gamescope + Steam
|
||||||
|
# 5. When done (Super+Shift+R or Steam > Exit to Desktop), the reverse happens
|
||||||
|
#
|
||||||
|
# This function creates ALL the scripts and config files needed for this flow:
|
||||||
|
# - Session wrapper (gamescope-session-nm-wrapper)
|
||||||
|
# - Switch scripts (switch-to-gaming, switch-to-desktop)
|
||||||
|
# - Keybind monitor (Python daemon using evdev for Super+Shift+R)
|
||||||
|
# - NetworkManager start/stop scripts (iwd <-> NM handoff)
|
||||||
|
# - Steam library auto-mount daemon
|
||||||
|
# - SDDM session entry and config
|
||||||
|
# - Polkit and sudoers rules for passwordless operation
|
||||||
|
# - Hyprland keybind for Super+Shift+S
|
||||||
|
#
|
||||||
|
# It also installs ChimeraOS's gamescope-session packages from AUR, which
|
||||||
|
# provide the base session framework that the Steam Deck uses.
|
||||||
|
# ==============================================================================
|
||||||
setup_session_switching() {
|
setup_session_switching() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "================================================================"
|
echo "================================================================"
|
||||||
|
|
@ -1312,6 +1508,17 @@ setup_session_switching() {
|
||||||
info "ChimeraOS gamescope-session packages already installed (correct -git versions)"
|
info "ChimeraOS gamescope-session packages already installed (correct -git versions)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# NetworkManager Integration
|
||||||
|
#
|
||||||
|
# Omarchy uses iwd (Intel Wireless Daemon) for WiFi, but Steam requires
|
||||||
|
# NetworkManager for its network settings UI. These can't run simultaneously
|
||||||
|
# without conflicts, so we create a managed handoff:
|
||||||
|
# - On gaming session start: NM starts, takes over networking from iwd
|
||||||
|
# - On gaming session exit: NM stops, iwd restarts and reconnects to WiFi
|
||||||
|
#
|
||||||
|
# If iwd is active, we configure NM to use iwd as its WiFi backend so they
|
||||||
|
# cooperate instead of fighting. If systemd-networkd is also running, we
|
||||||
|
# tell NM to leave ethernet interfaces alone to avoid conflicts.
|
||||||
info "Setting up NetworkManager integration..."
|
info "Setting up NetworkManager integration..."
|
||||||
if systemctl is-active --quiet iwd; then
|
if systemctl is-active --quiet iwd; then
|
||||||
info "Detected iwd is active - configuring NetworkManager to use iwd backend..."
|
info "Detected iwd is active - configuring NetworkManager to use iwd backend..."
|
||||||
|
|
@ -1431,6 +1638,16 @@ NM_STOP
|
||||||
sudo chmod +x "$nm_stop_script"
|
sudo chmod +x "$nm_stop_script"
|
||||||
info "Created NetworkManager start/stop scripts"
|
info "Created NetworkManager start/stop scripts"
|
||||||
|
|
||||||
|
# Steam Library Auto-Mount Daemon
|
||||||
|
#
|
||||||
|
# Many gamers have games spread across multiple drives (external SSDs,
|
||||||
|
# NTFS partitions from a Windows dual-boot, etc.). This daemon:
|
||||||
|
# 1. Scans all connected drives for Steam library folders (steamapps/)
|
||||||
|
# 2. Mounts drives that contain Steam libraries via udisks2
|
||||||
|
# 3. Unmounts drives that DON'T have Steam libraries (keeps it clean)
|
||||||
|
# 4. Watches for hot-plugged drives (USB drives plugged in during gaming)
|
||||||
|
#
|
||||||
|
# It runs in the background during Gaming Mode and stops when you exit.
|
||||||
local steam_mount_script="/usr/local/bin/steam-library-mount"
|
local steam_mount_script="/usr/local/bin/steam-library-mount"
|
||||||
info "Creating Steam library drive mount script..."
|
info "Creating Steam library drive mount script..."
|
||||||
sudo tee "$steam_mount_script" > /dev/null << 'STEAM_MOUNT'
|
sudo tee "$steam_mount_script" > /dev/null << 'STEAM_MOUNT'
|
||||||
|
|
@ -1538,6 +1755,13 @@ STEAM_MOUNT
|
||||||
sudo chmod +x "$steam_mount_script"
|
sudo chmod +x "$steam_mount_script"
|
||||||
info "Created $steam_mount_script"
|
info "Created $steam_mount_script"
|
||||||
|
|
||||||
|
# Polkit Rules
|
||||||
|
#
|
||||||
|
# Polkit is Linux's permission system for D-Bus actions. Steam communicates
|
||||||
|
# with NetworkManager over D-Bus, but by default only root can control NM.
|
||||||
|
# These rules allow members of the "wheel" group (admin users) to control
|
||||||
|
# NetworkManager without a password prompt — otherwise Steam would show
|
||||||
|
# a "permission denied" error when trying to access network settings.
|
||||||
local polkit_rules="/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules"
|
local polkit_rules="/etc/polkit-1/rules.d/50-gamescope-networkmanager.rules"
|
||||||
|
|
||||||
if sudo test -f "$polkit_rules"; then
|
if sudo test -f "$polkit_rules"; then
|
||||||
|
|
@ -1572,6 +1796,10 @@ POLKIT_RULES
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Udisks2 Polkit Rules — same concept as above but for drive mounting.
|
||||||
|
# Allows the steam-library-mount daemon to mount/unmount drives without
|
||||||
|
# a password prompt. Without this, plugging in a USB drive with games
|
||||||
|
# would show a password dialog — not ideal in the middle of a gaming session.
|
||||||
local udisks_polkit="/etc/polkit-1/rules.d/50-udisks-gaming.rules"
|
local udisks_polkit="/etc/polkit-1/rules.d/50-udisks-gaming.rules"
|
||||||
|
|
||||||
if sudo test -f "$udisks_polkit"; then
|
if sudo test -f "$udisks_polkit"; then
|
||||||
|
|
@ -1601,6 +1829,18 @@ UDISKS_POLKIT
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Gamescope Session Configuration
|
||||||
|
#
|
||||||
|
# This config file tells gamescope-session-plus (from ChimeraOS) how to
|
||||||
|
# set up the gaming display. It includes:
|
||||||
|
# - Resolution and refresh rate (auto-detected from your monitor)
|
||||||
|
# - Which display output to use (e.g. HDMI-1, DP-2)
|
||||||
|
# - GPU-specific settings (NVIDIA vs AMD have different requirements)
|
||||||
|
#
|
||||||
|
# NVIDIA gets: GBM_BACKEND=nvidia-drm, VULKAN_ADAPTER pointing to the GPU
|
||||||
|
# AMD gets: ADAPTIVE_SYNC=1 (FreeSync), ENABLE_GAMESCOPE_HDR=1 (HDR support)
|
||||||
|
#
|
||||||
|
# NVIDIA is capped at 2560x1440 due to Gamescope limitations with NVIDIA GPUs.
|
||||||
info "Creating gamescope-session-plus configuration..."
|
info "Creating gamescope-session-plus configuration..."
|
||||||
local env_dir="${user_home}/.config/environment.d"
|
local env_dir="${user_home}/.config/environment.d"
|
||||||
local gamescope_conf="${env_dir}/gamescope-session-plus.conf"
|
local gamescope_conf="${env_dir}/gamescope-session-plus.conf"
|
||||||
|
|
@ -1653,6 +1893,13 @@ GAMESCOPE_CONF
|
||||||
|
|
||||||
info "Created $gamescope_conf"
|
info "Created $gamescope_conf"
|
||||||
|
|
||||||
|
# NVIDIA Gamescope Wrapper
|
||||||
|
#
|
||||||
|
# NVIDIA GPUs need the --force-composition flag in Gamescope to avoid
|
||||||
|
# rendering glitches. This wrapper script sits in front of the real
|
||||||
|
# gamescope binary — when Gaming Mode starts, it calls this wrapper
|
||||||
|
# instead, which adds the flag and then exec's the real gamescope.
|
||||||
|
# It checks if the flag is actually supported first (older versions don't have it).
|
||||||
info "Creating NVIDIA gamescope wrapper..."
|
info "Creating NVIDIA gamescope wrapper..."
|
||||||
local nvidia_wrapper_dir="/usr/local/lib/gamescope-nvidia"
|
local nvidia_wrapper_dir="/usr/local/lib/gamescope-nvidia"
|
||||||
local nvidia_wrapper="${nvidia_wrapper_dir}/gamescope"
|
local nvidia_wrapper="${nvidia_wrapper_dir}/gamescope"
|
||||||
|
|
@ -1670,6 +1917,24 @@ NVIDIA_WRAPPER
|
||||||
sudo chmod +x "$nvidia_wrapper"
|
sudo chmod +x "$nvidia_wrapper"
|
||||||
info "Created $nvidia_wrapper"
|
info "Created $nvidia_wrapper"
|
||||||
|
|
||||||
|
# Main Session Wrapper — gamescope-session-nm-wrapper
|
||||||
|
#
|
||||||
|
# This is the master script that runs when Gaming Mode starts. It's the
|
||||||
|
# entry point for the entire gaming session and orchestrates everything:
|
||||||
|
#
|
||||||
|
# 1. Enables performance mode (CPU governor → performance, GPU max power)
|
||||||
|
# 2. Adds NVIDIA wrapper to PATH if needed
|
||||||
|
# 3. Starts NetworkManager for Steam's network access
|
||||||
|
# 4. Launches steam-library-mount daemon for external drive detection
|
||||||
|
# 5. Starts the keybind monitor (listens for Super+Shift+R to exit)
|
||||||
|
# 6. Sets Steam-specific environment variables
|
||||||
|
# 7. Launches gamescope-session-plus (the actual Gamescope + Steam session)
|
||||||
|
#
|
||||||
|
# When the session ends (for any reason), the cleanup trap:
|
||||||
|
# - Kills the mount daemon and keybind monitor
|
||||||
|
# - Stops NetworkManager and restores iwd WiFi
|
||||||
|
# - Restores balanced power mode (CPU powersave, GPU defaults)
|
||||||
|
# - Removes the session marker file
|
||||||
info "Creating NetworkManager session wrapper..."
|
info "Creating NetworkManager session wrapper..."
|
||||||
local nm_wrapper="/usr/local/bin/gamescope-session-nm-wrapper"
|
local nm_wrapper="/usr/local/bin/gamescope-session-nm-wrapper"
|
||||||
|
|
||||||
|
|
@ -1824,6 +2089,12 @@ NM_WRAPPER
|
||||||
sudo chmod +x "$nm_wrapper"
|
sudo chmod +x "$nm_wrapper"
|
||||||
info "Created $nm_wrapper"
|
info "Created $nm_wrapper"
|
||||||
|
|
||||||
|
# SDDM Session Entry
|
||||||
|
#
|
||||||
|
# SDDM (the login/display manager) needs a .desktop file to know about
|
||||||
|
# Gaming Mode as a session option. This is what tells SDDM "when you
|
||||||
|
# auto-login to the gaming session, run this script." It's placed in
|
||||||
|
# /usr/share/wayland-sessions/ alongside the normal Hyprland session.
|
||||||
info "Creating SDDM session entry..."
|
info "Creating SDDM session entry..."
|
||||||
local session_desktop="/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"
|
local session_desktop="/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"
|
||||||
|
|
||||||
|
|
@ -1838,6 +2109,12 @@ SESSION_DESKTOP
|
||||||
|
|
||||||
info "Created $session_desktop"
|
info "Created $session_desktop"
|
||||||
|
|
||||||
|
# os-session-select — Steam's "Exit to Desktop" handler
|
||||||
|
#
|
||||||
|
# When you click Steam > Power > "Exit to Desktop" inside Gaming Mode,
|
||||||
|
# Steam calls /usr/lib/os-session-select. On a real Steam Deck this
|
||||||
|
# switches to Desktop Mode. Our version does the same thing — it updates
|
||||||
|
# SDDM to boot back into Hyprland and restarts the display manager.
|
||||||
info "Creating session-select script..."
|
info "Creating session-select script..."
|
||||||
local os_session_select="/usr/lib/os-session-select"
|
local os_session_select="/usr/lib/os-session-select"
|
||||||
|
|
||||||
|
|
@ -1857,6 +2134,15 @@ OS_SESSION_SELECT
|
||||||
sudo chmod +x "$os_session_select"
|
sudo chmod +x "$os_session_select"
|
||||||
info "Created $os_session_select"
|
info "Created $os_session_select"
|
||||||
|
|
||||||
|
# switch-to-gaming — Called when Super+Shift+S is pressed in Hyprland
|
||||||
|
#
|
||||||
|
# This script handles the transition from Desktop to Gaming Mode:
|
||||||
|
# 1. Masks suspend targets — prevents the system from sleeping when the
|
||||||
|
# monitor briefly disconnects during the display manager restart
|
||||||
|
# 2. Updates SDDM config to auto-login to the gaming session
|
||||||
|
# 3. Kills any leftover gamescope processes from a previous session
|
||||||
|
# 4. Switches to VT2 (virtual terminal) to avoid display conflicts
|
||||||
|
# 5. Restarts SDDM, which auto-logs into Gaming Mode
|
||||||
info "Creating switch-to-gaming script..."
|
info "Creating switch-to-gaming script..."
|
||||||
local switch_script="/usr/local/bin/switch-to-gaming"
|
local switch_script="/usr/local/bin/switch-to-gaming"
|
||||||
|
|
||||||
|
|
@ -1879,6 +2165,15 @@ SWITCH_SCRIPT
|
||||||
sudo chmod +x "$switch_script"
|
sudo chmod +x "$switch_script"
|
||||||
info "Created $switch_script"
|
info "Created $switch_script"
|
||||||
|
|
||||||
|
# switch-to-desktop — Called when Super+Shift+R is pressed in Gaming Mode
|
||||||
|
#
|
||||||
|
# This script handles the transition back from Gaming to Desktop Mode:
|
||||||
|
# 1. Unmasks suspend targets (re-enables sleep/hibernate)
|
||||||
|
# 2. Restores Bluetooth (disabled during gaming to reduce interference)
|
||||||
|
# 3. Gracefully shuts down Steam (timeout 5s, then force kill)
|
||||||
|
# 4. Kills gamescope with SIGTERM first, then SIGKILL if it won't die
|
||||||
|
# 5. Updates SDDM config back to Hyprland session
|
||||||
|
# 6. Restarts SDDM, which auto-logs into Desktop Mode
|
||||||
info "Creating switch-to-desktop script..."
|
info "Creating switch-to-desktop script..."
|
||||||
local switch_desktop_script="/usr/local/bin/switch-to-desktop"
|
local switch_desktop_script="/usr/local/bin/switch-to-desktop"
|
||||||
|
|
||||||
|
|
@ -1926,6 +2221,17 @@ SWITCH_DESKTOP
|
||||||
sudo chmod +x "$switch_desktop_script"
|
sudo chmod +x "$switch_desktop_script"
|
||||||
info "Created $switch_desktop_script"
|
info "Created $switch_desktop_script"
|
||||||
|
|
||||||
|
# Keybind Monitor — Python daemon for Super+Shift+R in Gaming Mode
|
||||||
|
#
|
||||||
|
# Inside Gamescope, Hyprland isn't running so its keybinds don't work.
|
||||||
|
# This Python script uses python-evdev to read raw keyboard input directly
|
||||||
|
# from /dev/input/event* devices, bypassing the compositor entirely.
|
||||||
|
#
|
||||||
|
# It uses Linux's selector (epoll) interface to efficiently monitor multiple
|
||||||
|
# keyboard devices simultaneously without busy-waiting. When it detects
|
||||||
|
# Super+Shift+R, it calls switch-to-desktop to return to Hyprland.
|
||||||
|
#
|
||||||
|
# The user must be in the "input" group to read /dev/input/ devices.
|
||||||
info "Creating gaming mode keybind monitor..."
|
info "Creating gaming mode keybind monitor..."
|
||||||
local keybind_monitor="/usr/local/bin/gaming-keybind-monitor"
|
local keybind_monitor="/usr/local/bin/gaming-keybind-monitor"
|
||||||
|
|
||||||
|
|
@ -2018,6 +2324,19 @@ KEYBIND_MONITOR
|
||||||
sudo chmod +x "$keybind_monitor"
|
sudo chmod +x "$keybind_monitor"
|
||||||
info "Created $keybind_monitor"
|
info "Created $keybind_monitor"
|
||||||
|
|
||||||
|
# SDDM Session Switching Config
|
||||||
|
#
|
||||||
|
# SDDM supports auto-login — it can automatically log in a user to a
|
||||||
|
# specific session without showing the login screen. This config file
|
||||||
|
# controls WHICH session SDDM auto-logs into.
|
||||||
|
#
|
||||||
|
# The switching mechanism works by editing this file:
|
||||||
|
# - "Session=hyprland-uwsm" → boots into Desktop Mode
|
||||||
|
# - "Session=gamescope-session-steam-nm" → boots into Gaming Mode
|
||||||
|
#
|
||||||
|
# The gaming-session-switch helper script toggles this value, then SDDM
|
||||||
|
# is restarted to pick up the change. The "zz-" prefix ensures this
|
||||||
|
# config loads LAST and overrides any other SDDM autologin settings.
|
||||||
info "Creating SDDM session switching config..."
|
info "Creating SDDM session switching config..."
|
||||||
local sddm_gaming_conf="/etc/sddm.conf.d/zz-gaming-session.conf"
|
local sddm_gaming_conf="/etc/sddm.conf.d/zz-gaming-session.conf"
|
||||||
|
|
||||||
|
|
@ -2066,6 +2385,16 @@ SESSION_HELPER
|
||||||
sudo chmod +x "$session_helper"
|
sudo chmod +x "$session_helper"
|
||||||
info "Created $session_helper"
|
info "Created $session_helper"
|
||||||
|
|
||||||
|
# Sudoers Rules for Session Switching
|
||||||
|
#
|
||||||
|
# The session switching scripts need to run several commands as root
|
||||||
|
# (restart SDDM, start/stop NetworkManager, control Bluetooth, etc.).
|
||||||
|
# These sudoers rules allow members of the "video" and "wheel" groups
|
||||||
|
# to run ONLY these specific commands without a password.
|
||||||
|
#
|
||||||
|
# This is much safer than giving blanket NOPASSWD sudo — each rule
|
||||||
|
# is scoped to a specific binary path. An attacker can't leverage
|
||||||
|
# these rules to run arbitrary commands as root.
|
||||||
local sudoers_session="/etc/sudoers.d/gaming-session-switch"
|
local sudoers_session="/etc/sudoers.d/gaming-session-switch"
|
||||||
|
|
||||||
if [[ -f "$sudoers_session" ]]; then
|
if [[ -f "$sudoers_session" ]]; then
|
||||||
|
|
@ -2218,6 +2547,20 @@ HYPR_GAMING
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Comprehensive verification — checks every file, permission, package, group,
|
||||||
|
# and service that Gaming Mode depends on. This is the --verify mode that
|
||||||
|
# users can run to diagnose problems without re-running the full installer.
|
||||||
|
#
|
||||||
|
# It checks:
|
||||||
|
# - All installed files exist and have correct permissions
|
||||||
|
# - Hyprland keybind is configured
|
||||||
|
# - ChimeraOS AUR packages are installed
|
||||||
|
# - Steam library mount script and udisks2 are ready
|
||||||
|
# - python-evdev works and user is in the input group
|
||||||
|
# - Gamescope session config exists
|
||||||
|
# - User is in required groups (video, input, wheel)
|
||||||
|
# - Service states (NM should be inactive, iwd should be active)
|
||||||
|
# - Sudo permissions work without password
|
||||||
verify_installation() {
|
verify_installation() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "================================================================"
|
echo "================================================================"
|
||||||
|
|
@ -2471,6 +2814,17 @@ verify_installation() {
|
||||||
$all_ok && return 0 || return 1
|
$all_ok && return 0 || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Main orchestrator — runs the full installation process in order.
|
||||||
|
# Each step builds on the previous one:
|
||||||
|
# 1. Authenticate sudo (and clear cached credentials first for a fresh prompt)
|
||||||
|
# 2. Validate we're on an Omarchy system
|
||||||
|
# 3. Install Steam dependencies and GPU drivers
|
||||||
|
# 4. Configure NVIDIA kernel parameters (if applicable)
|
||||||
|
# 5. Set NVIDIA environment variables (if applicable)
|
||||||
|
# 6. Install script requirements and performance permissions
|
||||||
|
# 7. Set up session switching (the big one — all the scripts and configs)
|
||||||
|
# 8. Prompt for reboot/relogin if needed
|
||||||
|
# 9. Optionally run verification to confirm everything worked
|
||||||
execute_setup() {
|
execute_setup() {
|
||||||
sudo -k
|
sudo -k
|
||||||
sudo -v || die "sudo authentication required"
|
sudo -v || die "sudo authentication required"
|
||||||
|
|
@ -2559,6 +2913,11 @@ show_help() {
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Command-line argument parsing — determines what mode to run in.
|
||||||
|
# With no arguments, runs the full installation. Otherwise:
|
||||||
|
# --help/-h: Show usage information
|
||||||
|
# --verify/-v: Only check if everything is installed correctly
|
||||||
|
# --version: Print version number
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
--help|-h)
|
--help|-h)
|
||||||
show_help
|
show_help
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue