Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions bin/omarchy-hw-nvidia-display
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash

# omarchy:summary=Detect whether an NVIDIA GPU drives the display.

# The boot VGA device owns the firmware display, which on a working system is
# the GPU driving the active display. Forcing NVIDIA-only video-decode
# settings (e.g. LIBVA_DRIVER_NAME) while another GPU drives the display
# breaks hardware video playback, so only claim NVIDIA when it owns boot VGA.
# When an NVIDIA GPU exists but no display device exposes the boot_vga
# attribute at all, claim NVIDIA to preserve the previous behavior on setups
# without VGA arbitration info.
#
# Reads 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

saw_nvidia=false
saw_boot_attr=false

for device in "$pci_devices_path"/*; do
[[ $(< "$device/class") == 0x03* ]] || continue
[[ $(< "$device/vendor") == "0x10de" ]] && saw_nvidia=true
# NB: `$(< file)` reads nothing when combined with other redirections,
# so guard readability separately instead of `2>/dev/null` inline.
[[ -r $device/boot_vga ]] || continue
# Any display device exposing boot_vga makes the setup determinable,
# including a boot_vga=0 that rules NVIDIA out.
saw_boot_attr=true
[[ $(< "$device/boot_vga") == "1" ]] || continue
[[ $(< "$device/vendor") == "0x10de" ]] && exit 0
done

# No NVIDIA GPU at all: nothing to claim.
[[ $saw_nvidia == "false" ]] && exit 1

# NVIDIA exists but no display device exposes boot_vga at all:
# undeterminable, keep the previous behavior.
[[ $saw_boot_attr == "false" ]] && exit 0

exit 1
8 changes: 7 additions & 1 deletion default/hypr/nvidia.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ local paths = require("default.hypr.paths")
local nvidia = paths.omarchy_path .. "/bin/omarchy-hw-nvidia"
local nvidia_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-gsp"
local nvidia_without_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-without-gsp"
local nvidia_display = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-display"

-- These detectors read cached sysfs IDs rather than shelling out to lspci.
-- 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
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")
-- Only force the NVIDIA VA-API driver when NVIDIA drives the display.
-- On hybrid GPUs whose display runs on another GPU this breaks hardware
-- video playback in Chromium and Electron (grey or black video).
if o.shell_succeeds(o.shell_quote(nvidia_display)) then
hl.env("LIBVA_DRIVER_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")
Expand Down
79 changes: 53 additions & 26 deletions test/shell.d/hw-nvidia-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ 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. The boot_vga part is omitted when the device exposes no
# such attribute.
write_pci_devices() {
rm -rf "$tmp_dir/devices"
mkdir -p "$tmp_dir/devices"
Expand All @@ -18,9 +20,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
}
Expand All @@ -30,15 +35,16 @@ hw_nvidia() {
}

assert_detects() {
local description="$1" nvidia="$2" gsp="$3" without_gsp="$4"
local description="$1" nvidia="$2" gsp="$3" without_gsp="$4" display="$5"

local command
for command in nvidia gsp without-gsp; do
for command in nvidia gsp without-gsp display; do
local expected
case $command in
nvidia) expected=$nvidia ;;
gsp) expected=$gsp ;;
without-gsp) expected=$without_gsp ;;
display) expected=$display ;;
esac

local detector=nvidia
Expand All @@ -54,46 +60,67 @@ assert_detects() {
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
# AMD Cezanne integrated graphics, driving the display.
write_pci_devices 0x1002:0x15e7:0x030000:1
assert_detects "a machine without an NVIDIA GPU detects nothing" no 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
write_pci_devices 0x1002:0x15e7:0x030000:1 0x10de:0x2560:0x030200:0
assert_detects "a hybrid Ampere laptop detects a GSP GPU without an NVIDIA display" yes yes no no

# NVIDIA AD107M [RTX 4060 Mobile] alongside AMD HawkPoint, display on AMD.
write_pci_devices 0x1002:0x1900:0x030000:1 0x10de:0x28e0:0x030000:0
assert_detects "a hybrid Ada laptop with the display on AMD detects no NVIDIA display" yes yes no no

# ROG Zephyrus G14 GA403WW (Ryzen AI 9 HX 370 + RTX 5080 Max-Q), display on
# AMD: the AMD Display controller (class 0x038000) exposes no boot_vga,
# while NVIDIA reports boot_vga=0, so the back-compat fallback must not
# fire (see #10589 discussion). The AMD device ID is illustrative; only
# vendor, class, and boot_vga presence matter here.
write_pci_devices 0x1002:0x1111:0x038000 0x10de:0x2c02:0x030000:0
assert_detects "a hybrid Blackwell laptop whose AMD display exposes no boot_vga detects no NVIDIA display" yes yes no 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
write_pci_devices 0x10de:0x1f91:0x030000:1
assert_detects "Turing is the oldest generation with GSP firmware" yes yes no yes

# NVIDIA TU117M driving the display of a hybrid Intel+NVIDIA laptop.
write_pci_devices 0x8086:0x9bc4:0x030000:0 0x10de:0x1f91:0x030000:1
assert_detects "a muxed hybrid with the display on NVIDIA detects an NVIDIA display" yes yes no yes

# 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
write_pci_devices 0x10de:0x1d81:0x030000:1
assert_detects "Volta is the newest generation without GSP firmware" yes no yes yes

# NVIDIA GP104 [GTX 1080].
write_pci_devices 0x10de:0x1b80:0x030000
assert_detects "Pascal detects a GPU without GSP firmware" yes no yes
write_pci_devices 0x10de:0x1b80:0x030000:1
assert_detects "Pascal detects a GPU without GSP firmware" yes no yes 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
write_pci_devices 0x10de:0x1340:0x030000:1
assert_detects "Maxwell detects a GPU without GSP firmware" yes no yes 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
write_pci_devices 0x10de:0x1004:0x030000:1
assert_detects "Kepler is too old for either driver" yes no no yes

# NVIDIA GF100 [GTX 470], older still.
write_pci_devices 0x10de:0x06cd:0x030000
assert_detects "Fermi is too old for either driver" yes no no
write_pci_devices 0x10de:0x06cd:0x030000:1
assert_detects "Fermi is too old for either driver" yes no no yes

# 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
write_pci_devices 0x10de:0x2c02:0x030000:1
assert_detects "Blackwell detects a GSP GPU" yes yes no yes

# NVIDIA GA106M without any boot VGA info: undeterminable, so keep the
# previous behavior and claim the display.
write_pci_devices 0x10de:0x2560:0x030000
assert_detects "an NVIDIA GPU without boot VGA info keeps the display claim" yes yes no yes

# 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
assert_detects "a non-display NVIDIA function is not a GPU" no no no no

write_pci_devices
assert_detects "a machine with no PCI devices detects nothing" no no no
assert_detects "a machine with no PCI devices detects nothing" no no no no