From 6ecf4bf2008ce79fefa7b80a6ac7871cc7d638e0 Mon Sep 17 00:00:00 2001 From: vatsal nadkarni Date: Sun, 13 Sep 2026 19:34:17 +0530 Subject: [PATCH 1/2] Only force NVIDIA VA-API/GLX env when NVIDIA drives the display --- bin/omarchy-hw-nvidia-display | 45 ++++++++++++++++++++++++++++++++++ default/hypr/nvidia.lua | 15 +++++++++--- test/shell.d/hw-nvidia-test.sh | 38 +++++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 6 deletions(-) create mode 100755 bin/omarchy-hw-nvidia-display 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 From 28405af27f6339cc7ac899a7b892b783712f73f2 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 24 Sep 2026 22:49:41 -0400 Subject: [PATCH 2/2] Request reboot for hybrid NVIDIA fix and streamline tests --- migrations/1790282866.sh | 8 ++ test/shell.d/hw-nvidia-test.sh | 161 +++++++++------------------------ 2 files changed, 53 insertions(+), 116 deletions(-) create mode 100644 migrations/1790282866.sh diff --git a/migrations/1790282866.sh b/migrations/1790282866.sh new file mode 100644 index 00000000..92c9734a --- /dev/null +++ b/migrations/1790282866.sh @@ -0,0 +1,8 @@ +echo "Apply the NVIDIA video driver fix on hybrid laptops" + +if omarchy-hw-nvidia && ! omarchy-hw-nvidia-display; then + # Hyprland applies env at startup. Reloading the updated defaults cannot + # clear the old overrides inherited by the compositor and its applications. + omarchy-state set reboot-required + echo "Reboot to stop forcing NVIDIA VA-API/GLX drivers on the integrated GPU." +fi diff --git a/test/shell.d/hw-nvidia-test.sh b/test/shell.d/hw-nvidia-test.sh index 72dc5a7b..eba3f7ab 100755 --- a/test/shell.d/hw-nvidia-test.sh +++ b/test/shell.d/hw-nvidia-test.sh @@ -3,129 +3,58 @@ set -euo pipefail source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" +require_command lua tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT +export OMARCHY_PCI_DEVICES_PATH="$tmp_dir/devices" OMARCHY_PATH="$ROOT" PATH="$ROOT/bin:$PATH" -# 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() { +# Exit statuses, expected NVD_BACKEND/LIBVA/GLX values, then vendor:device:class[:boot_vga]. +while IFS='|' read -r description nvidia gsp without_gsp display expected_env devices; do rm -rf "$tmp_dir/devices" mkdir -p "$tmp_dir/devices" - - local index=0 - local spec - for spec in "$@"; do - local slot - slot=$(printf '0000:%02x:00.0' "$index") - mkdir -p "$tmp_dir/devices/$slot" - 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' "$(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" + slot=0 + for spec in $devices; do + IFS=: read -r vendor device class boot_vga <<<"$spec" + device_dir="$tmp_dir/devices/$slot" + mkdir -p "$device_dir" + printf '%s\n' "$vendor" >"$device_dir/vendor" + printf '%s\n' "$device" >"$device_dir/device" + printf '%s\n' "$class" >"$device_dir/class" + if [[ -n $boot_vga ]]; then + printf '%s\n' "$boot_vga" >"$device_dir/boot_vga" fi - index=$((index + 1)) - done -} - -hw_nvidia() { - OMARCHY_PCI_DEVICES_PATH="$tmp_dir/devices" "$ROOT/bin/omarchy-hw-$1" -} - -assert_detects() { - local description="$1" nvidia="$2" gsp="$3" without_gsp="$4" - - local command - for command in nvidia gsp without-gsp; do - local expected - case $command in - nvidia) expected=$nvidia ;; - gsp) expected=$gsp ;; - without-gsp) expected=$without_gsp ;; - esac - - local detector=nvidia - [[ $command == "nvidia" ]] || detector="nvidia-$command" - - local actual=no - hw_nvidia "$detector" && actual=yes - - [[ $actual == "$expected" ]] || - fail "$description" "omarchy-hw-$detector: expected $expected, got $actual" + slot=$((slot + 1)) done + for check in "nvidia:$nvidia" "nvidia-gsp:$gsp" "nvidia-without-gsp:$without_gsp" "nvidia-display:$display"; do + status=0 + "omarchy-hw-${check%:*}" || status=$? + [[ $status == "${check#*:}" ]] || fail "$description: ${check%:*}" "expected ${check#*:}, got $status" + done + + # Run the real Lua config and detectors; capture only Hyprland's env calls. + actual_env=$(lua <<'LUA' +package.path = os.getenv("ROOT") .. "/?.lua;" .. package.path +require("default.hypr.helpers") +local env = {} +hl = { env = function(key, value) env[key] = value end } +require("default.hypr.nvidia") +print(table.concat({ env.NVD_BACKEND or "-", env.LIBVA_DRIVER_NAME or "-", env.__GLX_VENDOR_LIBRARY_NAME or "-" }, " ")) +LUA + ) + [[ $actual_env == "$expected_env" ]] || fail "$description: driver environment" "expected $expected_env, got $actual_env" 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 - -# NVIDIA GA106M [RTX 3060 Mobile] alongside AMD Cezanne, the pair from issue #6660. -write_pci_devices 0x1002:0x15e7:0x030000 0x10de:0x2560:0x030200 -assert_detects "a hybrid Ampere laptop detects a GSP GPU" yes yes no - -# NVIDIA TU117M [GTX 1650 Mobile], the first generation with GSP firmware. -write_pci_devices 0x10de:0x1f91:0x030000 -assert_detects "Turing is the oldest generation with GSP firmware" yes yes no - -# NVIDIA GV100 [TITAN V], the newest generation without GSP firmware. -write_pci_devices 0x10de:0x1d81:0x030000 -assert_detects "Volta is the newest generation without GSP firmware" yes no yes - -# NVIDIA GP104 [GTX 1080]. -write_pci_devices 0x10de:0x1b80:0x030000 -assert_detects "Pascal detects a GPU without GSP firmware" yes no yes - -# NVIDIA GM108M [GeForce 830M], the oldest part the 580xx driver supports. -write_pci_devices 0x10de:0x1340:0x030000 -assert_detects "Maxwell detects a GPU without GSP firmware" yes no yes - -# NVIDIA GK110 [GTX 780]. Kepler predates GSP but also predates 580xx, so -# claiming it here would install a driver that cannot drive it. -write_pci_devices 0x10de:0x1004:0x030000 -assert_detects "Kepler is too old for either driver" yes no no - -# NVIDIA GF100 [GTX 470], older still. -write_pci_devices 0x10de:0x06cd:0x030000 -assert_detects "Fermi is too old for either driver" yes no no - -# NVIDIA GB203 [RTX 5080], newer than every other device ID here. -write_pci_devices 0x10de:0x2c02:0x030000 -assert_detects "Blackwell detects a GSP GPU" yes yes no - -# The GA106 audio function carries the NVIDIA vendor ID but is not a GPU. -write_pci_devices 0x10de:0x228e:0x040300 -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 +done <<'CASES' +AMD only|1|1|1|1|- - -|0x1002:0x15e7:0x030000:1 +NVIDIA audio only|1|1|1|0|- - -|0x10de:0x228e:0x040300 +No PCI devices|1|1|1|0|- - -| +Turing (first GSP)|0|0|1|0|direct nvidia nvidia|0x10de:0x1f91:0x030000 +Volta (last without GSP)|0|1|0|0|egl - nvidia|0x10de:0x1d81:0x030000 +Maxwell (first 580xx)|0|1|0|0|egl - nvidia|0x10de:0x1340:0x030000 +Kepler (unsupported)|0|1|1|0|- - -|0x10de:0x1004:0x030000 +AMD display with Ampere offload|0|0|1|1|direct - -|0x1002:0x15bf:0x030000:1 0x10de:0x25ac:0x030200:0 +Intel display with Maxwell offload|0|1|0|1|egl - -|0x8086:0x46a6:0x030000:1 0x10de:0x1340:0x030000:0 +NVIDIA display with inactive iGPU|0|0|1|0|direct nvidia nvidia|0x1002:0x15bf:0x030000:0 0x10de:0x2c02:0x030000:1 +Hybrid without boot_vga|0|0|1|0|direct nvidia nvidia|0x1002:0x15e7:0x030000 0x10de:0x2560:0x030200 +CASES