diff --git a/.gitignore b/.gitignore index 16efa80..28c9d3f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ !/get-iso.sh !/TESTPLAN.md !/TODO.md +!/tests !/nvidia/ !/AGENTS.md !/.github/ diff --git a/tests/controllers-test.sh b/tests/controllers-test.sh new file mode 100755 index 0000000..4fe952a --- /dev/null +++ b/tests/controllers-test.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Tests lib/controllers.sh (extended controller support: xone, the Xbox wireless dongle, and xpadneo, Xbox Bluetooth) with a stub pacman +# and a temp home. No root, VM or hardware needed: bash tests/controllers-test.sh +# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo) +REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"; cd "$REPO" || exit 1 +T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT +fail=0 +check() { if eval "$2"; then echo "ok $1"; else echo "FAIL $1"; fail=1; fi; } + +SCRIPT_DIR="$PWD"; HOME="$T/home"; mkdir -p "$HOME" +source lib/common.sh; source lib/state.sh; source lib/controllers.sh; source lib/menu.sh +# Runs as the user, and refuses anything under the real system folders: a test must never touch them. +sudo() { local a; for a in "$@"; do case "$a" in /etc/*|/usr/*|/boot/*|/var/*) echo "REFUSED sudo $*" >&2; return 1 ;; esac; done; "$@"; } +info() { :; }; ok() { :; }; warn() { :; }; err() { echo "ERR $*"; } +kwriteconfig6() { local f="" k="" v=""; while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --group) shift ;; *) v="$1" ;; esac; shift; done; mkdir -p "$(dirname "$f")"; sed -i "/^$k=/d" "$f" 2>/dev/null; printf '%s=%s\n' "$k" "$v" >> "$f"; } +kreadconfig6() { local f="" k=""; while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --group|--default) shift ;; esac; shift; done; sed -n "s/^$k=//p" "$f" 2>/dev/null; } +# A pretend package database. +PKGS=""; PACMAN_LOG="$T/pacman.log"; : > "$PACMAN_LOG"; PACMAN_FAIL=0 +pacman() { + case "$1" in + -Q) local p; for p in "${@:2}"; do [[ " $PKGS " == *" $p "* ]] || return 1; done ;; + -S) [[ $PACMAN_FAIL == 1 ]] && return 1; echo "S ${*:4}" >> "$PACMAN_LOG"; PKGS+=" ${*:4}" ;; + -Rns) echo "R ${*:3}" >> "$PACMAN_LOG"; local p; for p in ${*:3}; do PKGS=" ${PKGS// $p / } "; done ;; + esac +} +HEADERS=0; install_kernel_headers() { HEADERS=$((HEADERS + 1)); } + +check "off to begin with" '! extended_controller_support_status' +extended_controller_support_enable +check "installs the repo's xpadneo-dkms, xone-dkms and the dongle firmware" 'pacman -Q xpadneo-dkms xone-dkms xone-dongle-firmware' +check "kernel headers are made sure of first" '[[ $HEADERS == 1 ]]' +check "what we installed is recorded" '[[ "$(state_get extended_controller_support installed_pkgs)" == "xpadneo-dkms xone-dkms xone-dongle-firmware" ]]' +check "it is on" extended_controller_support_status +n="$(wc -l < "$PACMAN_LOG")"; extended_controller_support_enable +check "a second run installs nothing again" '[[ "$(wc -l < "$PACMAN_LOG")" == "$n" ]]' +extended_controller_support_disable +check "off: the three packages are removed again" '! pacman -Q xpadneo-dkms 2>/dev/null && ! pacman -Q xone-dkms 2>/dev/null && ! pacman -Q xone-dongle-firmware 2>/dev/null && [[ -z "$(state_get extended_controller_support installed_pkgs)" ]]' + +# The AUR's xone-dkms-git conflicts with the repo's xone-dkms: it counts, and is kept. +PKGS=" xone-dkms-git xone-dongle-firmware xpadneo-dkms"; : > "$PACMAN_LOG" +check "xone-dkms-git counts as xone: already on" extended_controller_support_status +extended_controller_support_enable; extended_controller_support_disable +check "with xone-dkms-git: nothing installed and nothing removed" '[[ ! -s "$PACMAN_LOG" ]] && pacman -Q xone-dkms-git xpadneo-dkms xone-dongle-firmware' +PKGS=" xone-dkms-git" +extended_controller_support_enable +check "xone-dkms-git present: only the missing ones are installed, never xone-dkms" 'grep -q "^S xpadneo-dkms xone-dongle-firmware$" "$PACMAN_LOG" && ! pacman -Q xone-dkms 2>/dev/null' +extended_controller_support_disable +check "and off removes only those two, xone-dkms-git stays" 'pacman -Q xone-dkms-git && ! pacman -Q xpadneo-dkms 2>/dev/null' + +# A failed install is an error and records nothing. +PKGS=""; PACMAN_FAIL=1 +check "a failed pacman is an error" '! extended_controller_support_enable >/dev/null' +check "...and records nothing" '[[ -z "$(state_get extended_controller_support installed_pkgs)" ]]' +PACMAN_FAIL=0 + +# Packages that were already there are never removed by turning it off. +PKGS=" xpadneo-dkms xone-dkms xone-dongle-firmware"; : > "$PACMAN_LOG" +extended_controller_support_enable; extended_controller_support_disable +check "packages you had before stay" 'pacman -Q xpadneo-dkms xone-dkms xone-dongle-firmware && [[ ! -s "$PACMAN_LOG" ]]' + +# The menu. +check "offered on every PC" 'component_available extended_controller_support' +check "not preselected: it builds kernel modules for every kernel" '[[ " ${NO_PRESELECT[*]} " == *" extended_controller_support "* ]]' +check "a top-level item with a label" '[[ -z "${PARENT[extended_controller_support]:-}" && -n "${LABEL[extended_controller_support]}" ]]' + +# The "new" badge: an opt-in option added after setup shows it, but is never ticked for you. +component_selectable() { return 0; } +declare -A CURRENT=([gaming]=1 [nvidia]=0 [extended_controller_support]=0 [vram]=0 [boot]=0) +rm -f "$STATE_DIR/features.state" +check "new opt-in option (conversion on, no record): the new badge" 'feature_new_optin extended_controller_support' +check "...but it is not ticked for you (not a new default option)" '! feature_new extended_controller_support' +check "...and the menu label says (new)" '[[ "$(menu_label extended_controller_support)" == *"(new)"* ]]' +check "a new default option is still ticked for you, and is not the opt-in kind" 'feature_new vram && ! feature_new_optin vram' +check "Boot into never gets the badge (it is a choice row)" '! feature_new_optin boot' +state_set_features() { kwriteconfig6 --file "$STATE_DIR/features.state" --group State --key "$1" "$2"; } +state_set_features extended_controller_support off +check "once a run recorded it as off, the badge is gone" '! feature_new_optin extended_controller_support' +rm -f "$STATE_DIR/features.state" +CURRENT[gaming]=0; CURRENT[nvidia]=1 +check "on an NVIDIA PC (Gaming on NVIDIA on, no conversion) it shows too" 'feature_new_optin extended_controller_support' +CURRENT[nvidia]=0 +check "with neither the conversion nor Gaming on NVIDIA on (a fresh PC) nothing is new" '! feature_new_optin extended_controller_support && ! feature_new vram' +CURRENT[gaming]=1; CURRENT[extended_controller_support]=1 +check "already on: not new" '! feature_new_optin extended_controller_support' +exit $fail diff --git a/tests/nvidia-hardware-test.sh b/tests/nvidia-hardware-test.sh new file mode 100755 index 0000000..fb6b56b --- /dev/null +++ b/tests/nvidia-hardware-test.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# Test of the NVIDIA fix on a real PC with an NVIDIA GPU (the unit test fakes +# one: tests/nvidia-test.sh). Run from this repo (REPO points at the Steamify checkout): +# bash tests/nvidia-hardware-test.sh check read-only: what is detected, what the live kernel uses, what apply would change +# bash tests/nvidia-hardware-test.sh apply really applies the fix (sudo), then reboot and run check again +# bash tests/nvidia-hardware-test.sh visual after trying gaming mode: was the picture clean? (asks you) +# Every run appends to ~/steamify-nvidia-report.txt: paste that file when asking for help. +# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo) +REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"; cd "$REPO" || exit 1 +# patch_file (lib/common.sh) reads the hook script from $SCRIPT_DIR/patches. +SCRIPT_DIR="$PWD" +REPORT="${NVIDIA_REPORT:-$HOME/steamify-nvidia-report.txt}" +source lib/common.sh; source lib/hdmi-refresh.sh; source lib/vram-booster.sh; source lib/nvidia.sh +mode="${1:-check}" +fail=0 +say() { echo "$*" | tee -a "$REPORT"; } +pass() { say "PASS $*"; } +bad() { say "FAIL $*"; fail=1; } +note() { say " $*"; } +root_cat() { cat "$1" 2>/dev/null || sudo cat "$1" 2>/dev/null || echo "(unreadable)"; } + +say ""; say "===== $(date '+%F %T') mode=$mode kernel=$(uname -r)" + +report_system() { + say "-- GPU and software" + lspci -nn 2>/dev/null | grep -iE 'vga|3d|display' | while read -r l; do note "$l"; done + command -v nvidia-smi >/dev/null && note "driver: $(nvidia-smi --query-gpu=name,driver_version --format=csv,noheader 2>&1 | head -n 1)" + for p in nvidia-open nvidia-utils nvidia-dkms gamescope gamescope-session-cachyos linux-cachyos-nvidia-open; do + v="$(pacman -Q "$p" 2>/dev/null)" && note "$v" + done + note "boot loader file: $(nvidia_boot_file || true)" + note "cmdline: $(cat /proc/cmdline)" +} + +check_state() { + say "-- Steamify's view" + if nvidia_present; then pass "a supported NVIDIA GPU (RTX 20 series or newer) with its driver is detected" + elif nvidia_legacy_gpu; then bad "the NVIDIA GPU is older than RTX 20 (on chwd's legacy lists in $VRAM_CHWD_IDS): the fix is not applied" + else bad "no NVIDIA GPU with its driver detected: the fix does nothing here"; fi + for c in /sys/class/drm/card[0-9]*/device; do + [[ "$(cat "$c/vendor" 2>/dev/null)" == 0x10de ]] && note "NVIDIA PCI device id $(cat "$c/device") $(vram_nvidia_legacy_id "$(cat "$c/device")" && echo '(on a chwd legacy list)' || echo '(not on a legacy list: RTX 20 or newer)')" + done + ls "$VRAM_CHWD_IDS"/nvidia-*.ids >/dev/null 2>&1 || note "no chwd legacy lists found: every NVIDIA card counts as supported" + local p + for p in $NVIDIA_PARAMS; do + if grep -qw -- "$p" /proc/cmdline; then pass "$p is on the running kernel's command line" + elif nvidia_param_set "$p"; then bad "$p is in the boot loader's file but not booted yet: reboot" + else bad "$p is missing (run: bash tests/nvidia-hardware-test.sh apply)"; fi + done + say "-- the live NVIDIA driver" + local m + for m in modeset fbdev; do + v="$(root_cat /sys/module/nvidia_drm/parameters/$m)" + if [[ "$v" == Y ]]; then pass "nvidia_drm $m = Y"; else bad "nvidia_drm $m = $v (want Y)"; fi + done + lsmod | grep -q '^nvidia_drm' && pass "nvidia_drm is loaded" || bad "nvidia_drm is not loaded" + local c found=0 + for c in /sys/class/drm/card[0-9]*/device; do + [[ "$(cat "$c/vendor" 2>/dev/null)" == 0x10de ]] || continue + found=1; note "NVIDIA card: ${c%/device} driver=$(basename "$(readlink -f "$c/driver" 2>/dev/null)")" + for s in "${c%/device}"/card*-*/status; do [[ -f "$s" ]] && note " $(basename "$(dirname "$s")"): $(cat "$s")"; done + done + [[ $found == 1 ]] || bad "no NVIDIA card in /sys/class/drm" + say "-- initramfs" + if nvidia_modules_everywhere; then note "every installed kernel has nvidia_drm: the early-load drop-in is used" + else note "not every installed kernel has nvidia_drm: the early-load drop-in is skipped (parameters still apply)"; fi + nvidia_initramfs_ok && note "NVIDIA modules are in the initramfs config" || note "NVIDIA modules are not in the initramfs config" + say "-- what apply would change" + if nvidia_params_missing; then note "adds $NVIDIA_PARAMS to $(nvidia_boot_file)"; else note "kernel parameters: nothing to change"; fi + if ! nvidia_initramfs_ok && nvidia_modules_everywhere; then note "writes $NVIDIA_INITRAMFS_CONF"; fi +} + +case "$mode" in + check) + report_system; check_state ;; + apply) + report_system + say "-- applying" + if nvidia_enable 2>&1 | tee -a "$REPORT"; [[ ${PIPESTATUS[0]} == 0 ]]; then pass "nvidia_enable finished" + else bad "nvidia_enable failed (output above)"; fi + say "Now reboot, then run: bash tests/nvidia-hardware-test.sh check" ;; + visual) + report_system + say "-- try gaming mode now (log in to it or reboot into it), look at the screen, then answer" + read -rp "Is the gaming mode picture clean (no corruption, no flicker)? [y/n/partly] " a + read -rp "What did you see (one line, e.g. 'garbled colours at start, fine after login')? " what + say "visual result: $a: $what" + [[ "$a" == y ]] && pass "picture clean" || bad "picture not clean ($a)" + say "-- gamescope and NVIDIA lines from this boot's log" + { journalctl -b --no-pager 2>/dev/null || sudo journalctl -b --no-pager 2>/dev/null; } | + grep -iE 'gamescope|nvidia|drm|nvrm|wlserver|vulkan' | tail -n 80 | tee -a "$REPORT" | tail -n 5 + note "(more in $REPORT)" ;; + *) echo "usage: $0 check|apply|visual"; exit 2 ;; +esac +say ""; say "Report: $REPORT" +exit $fail diff --git a/tests/nvidia-test.sh b/tests/nvidia-test.sh new file mode 100755 index 0000000..ca510a7 --- /dev/null +++ b/tests/nvidia-test.sh @@ -0,0 +1,207 @@ +#!/bin/bash +# Tests lib/nvidia.sh ("Gaming on NVIDIA" and its Big Picture option) and the menu rules around it, against a fake RTX 5080 +# (PCI 10de:2c02) beside an Intel iGPU, a stub modinfo, pacman and systemctl, and a temp home. No root, VM or NVIDIA +# hardware needed: bash tests/nvidia-test.sh +# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo) +REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"; cd "$REPO" || exit 1 +T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT +fail=0 +check() { if eval "$2"; then echo "ok $1"; else echo "FAIL $1"; fail=1; fi; } + +SCRIPT_DIR="$PWD"; HOME="$T/home"; mkdir -p "$HOME" +source lib/common.sh; source lib/state.sh; source lib/login-manager.sh; source lib/single-user.sh; source lib/hdmi-refresh.sh; source lib/vram-booster.sh; source lib/nvidia.sh; source lib/menu.sh +# Runs as the user, and refuses anything under the real system folders: a test must never touch them. +sudo() { local a; for a in "$@"; do case "$a" in /etc/*|/usr/*|/boot/*|/var/*) echo "REFUSED sudo $*" >&2; return 1 ;; esac; done; "$@"; } +info() { :; }; ok() { :; }; warn() { :; }; err() { echo "ERR $*"; } +# KDE's config tools on plain files under the temp home (a relative file name is relative to ~/.config, as in KDE). +kwriteconfig6() { + local f="" k="" v="" del=0 + while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --group) shift ;; --delete) del=1 ;; *) v="$1" ;; esac; shift; done + [[ "$f" == /* ]] || f="$HOME/.config/$f"; mkdir -p "$(dirname "$f")" + if [[ $del == 1 ]]; then sed -i "/^$k=/d" "$f" 2>/dev/null; else sed -i "/^$k=/d" "$f" 2>/dev/null; printf '%s=%s\n' "$k" "$v" >> "$f"; fi +} +kreadconfig6() { + local f="" k="" d="" r + while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --default) d="$2"; shift ;; --group) shift ;; esac; shift; done + [[ "$f" == /* ]] || f="$HOME/.config/$f" + r="$(sed -n "s/^$k=//p" "$f" 2>/dev/null)"; printf '%s\n' "${r:-$d}" +} +# A pretend package database and systemd: what the tests assert on. +PKGS=""; PACMAN_LOG="$T/pacman.log"; : > "$PACMAN_LOG"; ENABLED=0 +pacman() { + case "$1" in + -Q) [[ " $PKGS " == *" $2 "* ]] ;; + -S) echo "S $*" >> "$PACMAN_LOG"; PKGS+=" ${*: -1}" ;; + -Rns) echo "R $*" >> "$PACMAN_LOG"; PKGS=" ${PKGS//${*: -1}/} " ;; + esac +} +user_systemctl() { + case "$1" in + enable) ENABLED=1 ;; + disable) ENABLED=0 ;; + is-enabled) [[ $ENABLED == 1 ]] ;; + esac + return 0 +} +FAKE_DRIVER=1; modinfo() { [[ "$FAKE_DRIVER" == 1 ]]; } +GAMING_ON=0; gaming_status() { [[ "$GAMING_ON" == 1 ]]; } +UNIT="$HOME/.config/systemd/user/$NVIDIA_UNIT" + +mkdir -p "$T/drm/card0/device" "$T/drm/card1/device" +echo 0x8086 > "$T/drm/card0/device/vendor"; echo 0x030000 > "$T/drm/card0/device/class" +echo 0x10de > "$T/drm/card1/device/vendor"; echo 0x030000 > "$T/drm/card1/device/class" +echo 0x2c02 > "$T/drm/card1/device/device" +NVIDIA_DRM_DIR="$T/drm" +backup_file() { [[ -f "$1.bak" ]] || cp "$1" "$1.bak"; } +REBUILDS=0; nvidia_rebuild_boot() { REBUILDS=$((REBUILDS + 1)); } +: > "$T/cmdline"; NVIDIA_CMDLINE="$T/cmdline" # the running kernel's command line: without the parameters +NVIDIA_INITRAMFS_CONF="$T/90-steamify-nvidia.conf" +NVIDIA_SCRIPT="$T/libexec/steamify-nvidia-initramfs"; NVIDIA_HOOK="$T/hooks/85-steamify-nvidia-initramfs.hook" +mkdir -p "$T/mods/6.1.0-cachyos/kernel"; : > "$T/mods/6.1.0-cachyos/kernel/nvidia-drm.ko.zst"; : > "$T/mods/6.1.0-cachyos/pkgbase" +NVIDIA_MODULES_DIR="$T/mods" +NVIDIA_LIMINE_CONF="$T/none/limine.conf"; NVIDIA_SDBOOT_DIR="$T/none/entries"; NVIDIA_GRUB_CFG="$T/none/grub.cfg" # not generated here: not checked +mkdir -p "$T/chwd"; VRAM_CHWD_IDS="$T/chwd" +# The component's kernel step edits this boot loader file (systemd-boot's) in the tests, never a real one. +printf 'LINUX_OPTIONS="quiet"\n' > "$T/sdboot-manage.conf"; nvidia_boot_file() { echo "$T/sdboot-manage.conf"; } + +# --- detection +check "finds the NVIDIA card as card1 beside an iGPU" nvidia_present +FAKE_DRIVER=0; check "no nvidia_drm module (nouveau): not an NVIDIA PC" '! nvidia_present' +FAKE_DRIVER=1; NVIDIA_DRM_DIR="$T/none"; check "no NVIDIA GPU: not an NVIDIA PC" '! nvidia_present' +NVIDIA_DRM_DIR="$T/drm" + +# --- which items the menu shows +check "NVIDIA PC: the SteamOS conversion and its gamescope options are hidden" '! component_available gaming && ! component_available boot && ! component_available glyphs' +check "NVIDIA PC: Gaming on NVIDIA and Big Picture are shown" 'component_available nvidia && component_available bigpicture' +GAMING_ON=1 +check "NVIDIA PC with the conversion already on: it is shown, so it can be turned off" 'component_available gaming && component_available boot' +check "...and Gaming on NVIDIA is hidden (both would start Steam)" '! component_available nvidia && ! component_available bigpicture' +GAMING_ON=0; NVIDIA_DRM_DIR="$T/none" +check "no NVIDIA: the conversion is shown as before" 'component_available gaming && component_available boot' +check "...and Gaming on NVIDIA is hidden" '! component_available nvidia && ! component_available bigpicture' +NVIDIA_DRM_DIR="$T/drm" + +# --- "Add as non-Steam game" is for gaming mode's controller: not offered on an NVIDIA PC (unless already on) +steamgame_available() { return 0; }; STEAMGAME_ON=0; steamgame_status() { [[ "$STEAMGAME_ON" == 1 ]]; } +check "NVIDIA PC: Add as non-Steam game is hidden" '! component_available steamgame' +STEAMGAME_ON=1; check "...unless it is already on (so it can be turned off)" 'component_available steamgame'; STEAMGAME_ON=0 +NVIDIA_DRM_DIR="$T/none"; check "no NVIDIA: it is offered as before" 'component_available steamgame' +steamgame_available() { return 1; }; check "no NVIDIA, no Steam account yet: still not offered" '! component_available steamgame' +steamgame_available() { return 0; }; NVIDIA_DRM_DIR="$T/drm" + +# --- turning it on +check "off to begin with" '! nvidia_status && ! bigpicture_status' +nvidia_enable +check "Steam is installed when missing, and recorded as ours" 'grep -q "^S " "$PACMAN_LOG" && [[ "$(state_get nvidia installed_pkgs)" == steam ]]' +check "the autostart unit is written: Steam without arguments" 'grep -q "^ExecStart=/usr/bin/steam $" "$UNIT"' +check "it is on" 'nvidia_status && ! bigpicture_status' +n="$(wc -l < "$PACMAN_LOG")"; nvidia_enable +check "a second run installs nothing again" '[[ "$(wc -l < "$PACMAN_LOG")" == "$n" ]]' + +# --- Big Picture +bigpicture_enable +check "Big Picture: Steam starts with -gamepadui" 'grep -q "^ExecStart=/usr/bin/steam -gamepadui$" "$UNIT" && bigpicture_status' +nvidia_enable +check "turning Gaming on NVIDIA on again keeps Big Picture" 'bigpicture_status' +check "Big Picture: Plasma starts with an empty session" '[[ "$(kreadconfig6 --file ksmserverrc --group General --key loginMode)" == emptySession ]]' +bigpicture_disable +check "Big Picture off: Steam's normal window again" '! bigpicture_status && nvidia_status' +check "...and the session setting is back as it was (not set)" '[[ -z "$(kreadconfig6 --file ksmserverrc --group General --key loginMode)" ]]' + +# --- turning it off +bigpicture_enable; nvidia_disable +check "off: the unit is gone and Steam, which we installed, is removed" '! nvidia_status && [[ ! -e "$UNIT" ]] && ! pacman -Q steam && [[ -z "$(state_get nvidia installed_pkgs)" ]]' +check "Big Picture without the unit does nothing" 'bigpicture_enable && bigpicture_disable && [[ ! -e "$UNIT" ]]' + +# --- Steam that was there already is never removed +PKGS=" steam"; : > "$PACMAN_LOG" +nvidia_enable; nvidia_disable +check "Steam installed before is kept, and not installed again" '[[ ! -s "$PACMAN_LOG" ]] && pacman -Q steam' + +# --- kernel parameters and early modules (a supported card only: RTX 20 series or newer) +check "RTX 5080 (2c02) is supported: no chwd legacy lists" nvidia_supported +printf '1c03\n1b80\n' > "$T/chwd/nvidia-580.ids" # a GTX 1060 and a GTX 1080: chwd's 580xx legacy list +check "RTX 5080 is not on a legacy list" 'nvidia_supported && ! nvidia_legacy_gpu' +echo 0x1c03 > "$T/drm/card1/device/device" +check "GTX 1060 (older than RTX 20): no kernel setup, but still an NVIDIA PC" '! nvidia_supported && nvidia_legacy_gpu && nvidia_present' +echo 0x2c02 > "$T/drm/card1/device/device" + +for loader in limine limineplus sdboot grub; do + case $loader in + limine) F="$T/limine"; printf 'KERNEL_CMDLINE[default]="quiet splash"\n' > "$F" ;; + # CachyOS writes its line as "+=" and may already have nvidia_drm.modeset=1 in it. + limineplus) F="$T/limine"; printf 'KERNEL_CMDLINE[default]+="quiet splash nvidia_drm.modeset=1"\n' > "$F" ;; + sdboot) F="$T/sdboot-manage.conf"; printf 'LINUX_OPTIONS="quiet splash"\n' > "$F" ;; + grub) F="$T/grub"; printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"\n' > "$F" ;; + esac + nvidia_boot_file() { echo "$F"; } + rm -f "$NVIDIA_INITRAMFS_CONF"; REBUILDS=0; ORIG="$(cat "$F")" + nvidia_kernel_enable + check "$loader: parameters added" 'grep -q "nvidia-drm.modeset=1" "$F" && grep -q "nvidia-drm.fbdev=1" "$F"' + check "$loader: initramfs drop-in written" 'grep -q nvidia_drm "$NVIDIA_INITRAMFS_CONF"' + check "$loader: script and pacman hook installed" '[[ -x "$NVIDIA_SCRIPT" ]] && grep -q "^Exec = $NVIDIA_SCRIPT sync" "$NVIDIA_HOOK"' + check "$loader: backup made, rebuilt once" '[[ -f "$F.bak" && $REBUILDS == 1 ]]' + SUM="$(cat "$F")"; nvidia_kernel_enable + check "$loader: second run changes nothing" '[[ "$(cat "$F")" == "$SUM" && $REBUILDS == 1 ]]' + nvidia_kernel_disable + check "$loader: disable restores the file, drops the drop-in, script and hook" '[[ "$(cat "$F")" == "$ORIG" && ! -f "$NVIDIA_INITRAMFS_CONF" && ! -e "$NVIDIA_SCRIPT" && ! -e "$NVIDIA_HOOK" ]]' +done + +# A missing patches/ file must be an error, never an empty script or hook. +( SCRIPT_DIR="$T/no-patches"; NVIDIA_SCRIPT="$T/empty/script"; NVIDIA_HOOK="$T/empty/hook"; ! nvidia_hook_install 2>/dev/null && [[ ! -s "$T/empty/script" ]] ) && echo "ok a missing patch file is an error, nothing empty installed" || { echo "FAIL a missing patch file is an error, nothing empty installed"; fail=1; } +# The pacman hook's script decides again at every kernel change. +syncit() { NVIDIA_INITRAMFS_CONF="$NVIDIA_INITRAMFS_CONF" NVIDIA_MODULES_DIR="$NVIDIA_MODULES_DIR" bash "$NVIDIA_SCRIPT" sync; } +nvidia_hook_install; rm -f "$NVIDIA_INITRAMFS_CONF" +syncit; check "hook script: every kernel has the modules -> drop-in written" '[[ -f "$NVIDIA_INITRAMFS_CONF" ]]' +mkdir -p "$T/mods/6.6.0-lts/kernel"; : > "$T/mods/6.6.0-lts/pkgbase" +syncit; check "hook script: a new kernel without modules -> drop-in removed" '[[ ! -f "$NVIDIA_INITRAMFS_CONF" ]]' +: > "$T/mods/6.6.0-lts/kernel/nvidia-drm.ko.zst" +syncit; check "hook script: the modules arrive (DKMS built) -> drop-in back" '[[ -f "$NVIDIA_INITRAMFS_CONF" ]]' +rm -rf "$T/mods/6.6.0-lts"; mkdir -p "$T/mods/5.0.0-leftover"; : > "$T/mods/5.0.0-leftover/modules.dep" +syncit; check "hook script: a leftover folder of a removed kernel is ignored" '[[ -f "$NVIDIA_INITRAMFS_CONF" ]]' +rm -rf "$T/mods/5.0.0-leftover"; nvidia_kernel_disable +# A second kernel without the NVIDIA modules: no early-load drop-in, parameters still set. +mkdir -p "$T/mods/6.6.0-lts/kernel"; : > "$T/mods/6.6.0-lts/pkgbase"; F="$T/sdboot-manage.conf"; printf 'LINUX_OPTIONS="quiet"\n' > "$F" +nvidia_boot_file() { echo "$F"; }; rm -f "$NVIDIA_INITRAMFS_CONF"; REBUILDS=0; nvidia_kernel_enable +check "kernel without nvidia modules: no drop-in, parameters set" '[[ ! -f "$NVIDIA_INITRAMFS_CONF" ]] && grep -q "nvidia-drm.fbdev=1" "$F"' +rm -rf "$T/mods/6.6.0-lts" +# Generated boot entries without the parameters (the initramfs build failed): an error. +printf 'linux /vmlinuz root=/dev/sda1\n' > "$T/grub.cfg"; NVIDIA_GRUB_CFG="$T/grub.cfg" +F="$T/grub"; printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet"\n' > "$F"; nvidia_boot_file() { echo "$F"; }; rm -f "$NVIDIA_INITRAMFS_CONF" +check "boot entries without the parameters: error" '! nvidia_kernel_enable >/dev/null' +NVIDIA_GRUB_CFG="$T/none/grub.cfg" +# A config file without the setting to edit: an error, not "applied". +F="$T/sdboot-manage.conf"; printf '#LINUX_OPTIONS=""\n' > "$F"; nvidia_boot_file() { echo "$F"; }; REBUILDS=0 +check "no setting to edit: error, nothing rebuilt" '! nvidia_kernel_enable >/dev/null && [[ $REBUILDS == 0 ]]' +FAKE_DRIVER=0; NVIDIA_DRM_DIR="$T/drm"; printf 'LINUX_OPTIONS="quiet"\n' > "$T/sdboot"; F="$T/sdboot"; REBUILDS=0; nvidia_kernel_enable +check "no NVIDIA driver: nothing touched" '[[ "$(cat "$F")" == "LINUX_OPTIONS=\"quiet\"" && $REBUILDS == 0 ]]' + +# --- single user mode on NVIDIA: its own login step (SDDM, autologin into Plasma) without the conversion +FAKE_DRIVER=1; NVIDIA_DRM_DIR="$T/drm" # an earlier section ended without the driver +SW="$T/switches.log"; : > "$SW"; TARGET_USER=tester; SINGLE_AUTOLOGIN="$T/sddm.conf.d/zzz-steamify-autologin.conf" +switch_to_sddm() { echo sddm >> "$SW"; }; switch_to_plasmalogin() { echo plasmalogin >> "$SW"; } +remove_sddm_base_autologin() { :; } # would look at the real /etc/sddm.conf +GAMING_ON=0 +check "single user mode is offered on NVIDIA without the conversion..." 'component_available single' +GAMING_ON=1; check "...and with it" 'component_available single'; GAMING_ON=0 +single_login_enable +check "single user: SDDM, autologin of the user into the Plasma session" 'grep -qx "User=tester" "$SINGLE_AUTOLOGIN" && grep -qx "Session=plasma.desktop" "$SINGLE_AUTOLOGIN" && grep -qx "Relogin=true" "$SINGLE_AUTOLOGIN" && grep -qx sddm "$SW"' +c1="$(cat "$SINGLE_AUTOLOGIN")"; single_login_enable +check "single user login: a second run changes nothing" '[[ "$(cat "$SINGLE_AUTOLOGIN")" == "$c1" ]]' +single_login_disable +check "single user off: the file is gone and plasma-login-manager is the login manager again" '[[ ! -e "$SINGLE_AUTOLOGIN" ]] && grep -qx plasmalogin "$SW"' +: > "$SW"; GAMING_ON=1; single_login_enable +check "with the conversion on, single user mode leaves the login to it" '[[ ! -e "$SINGLE_AUTOLOGIN" && ! -s "$SW" ]]' +GAMING_ON=0 +# --- the menu rules +declare -A WANTED=([nvidia]=1 [bigpicture]=1 [gaming]=0 [boot]=0 [single]=0 [machine]=0 [poweroff]=0 [launcher]=0 [steamgame]=0) +component_selectable() { return 0; } +toggle_component nvidia +check "unticking Gaming on NVIDIA unticks Big Picture" '[[ "${WANTED[nvidia]}" == 0 && "${WANTED[bigpicture]}" == 0 ]]' +toggle_component bigpicture +check "ticking Big Picture ticks Gaming on NVIDIA" '[[ "${WANTED[nvidia]}" == 1 && "${WANTED[bigpicture]}" == 1 ]]' +WANTED[single]=0; WANTED[gaming]=0; toggle_component single +check "ticking single user mode does not tick the conversion where it isn't offered" '[[ "${WANTED[single]}" == 1 && "${WANTED[gaming]}" == 0 ]]' +check "Big Picture follows Gaming on NVIDIA in the menu" '[[ " ${COMPONENTS[*]} " == *" nvidia bigpicture "* && "${PARENT[bigpicture]}" == nvidia ]]' + +exit $fail