diff --git a/bin/omarchy-hw-nvidia-display b/bin/omarchy-hw-nvidia-display new file mode 100755 index 00000000..1cdf5b70 --- /dev/null +++ b/bin/omarchy-hw-nvidia-display @@ -0,0 +1,45 @@ +#!/bin/bash + +# omarchy:summary=Detect whether NVIDIA drives the display (rather than a hybrid iGPU). + +# On hybrid laptops the display is usually wired to the integrated GPU while +# the NVIDIA card only handles offloaded rendering. Forcing NVIDIA's VA-API and +# GLX drivers globally then breaks Chromium-based browsers (black video with +# audio still playing), so the Hyprland NVIDIA env only applies when NVIDIA is +# the display GPU. +# +# sysfs exposes which VGA device drives the display via boot_vga. When no +# device claims it (attribute missing or unreadable), assume NVIDIA is the +# display GPU to preserve the current behavior on machines we cannot tell. +# +# Read the cached sysfs IDs rather than lspci, which reads PCI config space and +# resumes runtime-suspended GPUs. +pci_devices_path="${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}" + +shopt -s nullglob + +nvidia_boot=0 +other_boot=0 + +for device in "$pci_devices_path"/*; do + [[ $(< "$device/class") == 0x03* ]] || continue + boot_vga=0 + if [[ -f $device/boot_vga ]]; then + boot_vga=$(< "$device/boot_vga") + fi + if [[ $(< "$device/vendor") == "0x10de" ]]; then + if (( boot_vga == 1 )); then + nvidia_boot=1 + fi + else + if (( boot_vga == 1 )); then + other_boot=1 + fi + fi +done + +if (( other_boot == 1 && nvidia_boot == 0 )); then + exit 1 +else + exit 0 +fi diff --git a/default/hypr/nvidia.lua b/default/hypr/nvidia.lua index 663fe79e..a3a15546 100644 --- a/default/hypr/nvidia.lua +++ b/default/hypr/nvidia.lua @@ -1,6 +1,7 @@ local paths = require("default.hypr.paths") local nvidia = paths.omarchy_path .. "/bin/omarchy-hw-nvidia" +local nvidia_display = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-display" local nvidia_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-gsp" local nvidia_without_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-without-gsp" @@ -8,12 +9,20 @@ local nvidia_without_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-without -- lspci reads PCI config space, which resumes a runtime-suspended GPU, and on a -- hybrid laptop that wake alone outlasts Hyprland's 1.5s config reload budget. if o.shell_succeeds(o.shell_quote(nvidia)) then + -- On hybrid laptops the display is wired to the iGPU, so forcing NVIDIA's + -- VA-API/GLX drivers globally breaks Chromium-based browsers (black video). + -- Only apply those when NVIDIA actually drives the display. + local nvidia_is_display = o.shell_succeeds(o.shell_quote(nvidia_display)) if o.shell_succeeds(o.shell_quote(nvidia_gsp)) then hl.env("NVD_BACKEND", "direct") - hl.env("LIBVA_DRIVER_NAME", "nvidia") - hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia") + if nvidia_is_display then + hl.env("LIBVA_DRIVER_NAME", "nvidia") + hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia") + end elseif o.shell_succeeds(o.shell_quote(nvidia_without_gsp)) then hl.env("NVD_BACKEND", "egl") - hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia") + if nvidia_is_display then + hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia") + end end end diff --git a/test/shell.d/hw-nvidia-test.sh b/test/shell.d/hw-nvidia-test.sh index da461ce4..72dc5a7b 100755 --- a/test/shell.d/hw-nvidia-test.sh +++ b/test/shell.d/hw-nvidia-test.sh @@ -7,7 +7,8 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT -# Each argument is a PCI device as "vendor:device:class", in sysfs's own format. +# Each argument is a PCI device as "vendor:device:class[:boot_vga]", in sysfs's +# own format. boot_vga is omitted to simulate firmware that lacks the flag. write_pci_devices() { rm -rf "$tmp_dir/devices" mkdir -p "$tmp_dir/devices" @@ -18,9 +19,12 @@ write_pci_devices() { local slot slot=$(printf '0000:%02x:00.0' "$index") mkdir -p "$tmp_dir/devices/$slot" - printf '%s\n' "${spec%%:*}" >"$tmp_dir/devices/$slot/vendor" + printf '%s\n' "$(cut -d: -f1 <<<"$spec")" >"$tmp_dir/devices/$slot/vendor" printf '%s\n' "$(cut -d: -f2 <<<"$spec")" >"$tmp_dir/devices/$slot/device" - printf '%s\n' "${spec##*:}" >"$tmp_dir/devices/$slot/class" + printf '%s\n' "$(cut -d: -f3 <<<"$spec")" >"$tmp_dir/devices/$slot/class" + if [[ $spec == *:*:*:* ]]; then + printf '%s\n' "$(cut -d: -f4 <<<"$spec")" >"$tmp_dir/devices/$slot/boot_vga" + fi index=$((index + 1)) done } @@ -54,6 +58,18 @@ assert_detects() { pass "$description" } +assert_display() { + local description="$1" expected="$2" + + local actual=no + OMARCHY_PCI_DEVICES_PATH="$tmp_dir/devices" "$ROOT/bin/omarchy-hw-nvidia-display" && actual=yes + + [[ $actual == "$expected" ]] || + fail "$description" "omarchy-hw-nvidia-display: expected $expected, got $actual" + + pass "$description" +} + # AMD Cezanne integrated graphics. write_pci_devices 0x1002:0x15e7:0x030000 assert_detects "a machine without an NVIDIA GPU detects nothing" no no no @@ -97,3 +113,19 @@ assert_detects "a non-display NVIDIA function is not a GPU" no no no write_pci_devices assert_detects "a machine with no PCI devices detects nothing" no no no + +# AMD Phoenix iGPU drives the display, NVIDIA RTX 3050 is discrete. +write_pci_devices 0x1002:0x15bf:0x030000:1 0x10de:0x25ac:0x030000:0 +assert_display "a hybrid laptop with an AMD display GPU is not NVIDIA-driven" no + +# Intel Alder Lake iGPU drives the display, NVIDIA Turing is discrete. +write_pci_devices 0x8086:0x46a6:0x030000:1 0x10de:0x1f91:0x030000:0 +assert_display "a hybrid Intel+NVIDIA laptop is not NVIDIA-driven" no + +# NVIDIA-only desktop keeps the current behavior. +write_pci_devices 0x10de:0x2c02:0x030000:1 +assert_display "an NVIDIA-only machine is NVIDIA-driven" yes + +# Fixtures without boot_vga preserve the current behavior. +write_pci_devices 0x10de:0x25ac:0x030000 +assert_display "missing boot_vga info assumes NVIDIA drives the display" yes