Repository: lutris/lutris
Pull request: Make Nvidia info not depend on /proc access
Some environments have limited access to /proc, but we can still retrieve the same info in other ways.

(Fixes #4858)

Problem statement:
Lutris 0.5.13 PermissionError: [Errno 13] Permission denied: '/proc/driver/nvidia/version'
### Bug description

Installed Lutris 0.5.13 since Starcraft 2 stopped working in Lutris 0.5.11. I uninstalled the apt install of Lutris and installed the Snap version instead.

However Lutris won't start at all. Issuing the start command in CLI gives the following error. See using sudo below for other errors.

$~$ snap run lutris
/usr/share/libdrm/amdgpu.ids: No such file or directory
ERROR:dbus.proxies:Introspect error on :1.37:/org/gnome/Mutter/DisplayConfig: dbus.exceptions.DBusException: org.freedesktop.DBus.Error.AccessDenied: An AppArmor policy prevents this sender from sending this message to this recipient; type="method_call", sender=":1.659" (uid=1000 pid=594319 comm="python3 /snap/lutris/39/usr/bin/lutris " label="snap.lutris.lutris (enforce)") interface="org.freedesktop.DBus.Introspectable" member="Introspect" error name="(unset)" requested_reply="0" destination=":1.37" (uid=1000 pid=2891 comm="/usr/bin/gnome-shell " label="unconfined")
/usr/share/libdrm/amdgpu.ids: No such file or directory
2023-05-17 15:14:02,956: Starting Lutris 0.5.13
INFO:lutris.util.log:Starting Lutris 0.5.13
Traceback (most recent call last):
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/application.py", line 436, in do_command_line
    run_all_checks()
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/startup.py", line 189, in run_all_checks
    check_driver()
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/startup.py", line 63, in check_driver
    driver_info = drivers.get_nvidia_driver_info()
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/util/graphics/drivers.py", line 18, in get_nvidia_driver_info
    with open(version_file_path, encoding='utf-8') as version_file:
PermissionError: [Errno 13] Permission denied: '/proc/driver/nvidia/version'
2023-05-17 15:14:02,958: Shutting down Lutris
INFO:lutris.util.log:Shutting down Lutris



$~$ sudo snap run lutris
mkdir: cannot create directory '/run/user/0': Permission denied
No protocol specified
No protocol specified
No protocol specified
Error: unable to open display :0
2023-05-17 15:16:05,856: ['glxinfo', '-B'] command failed: Command '['glxinfo', '-B']' returned non-zero exit status 255.
2023-05-17 15:16:05,856: No available glxinfo output
2023-05-17 15:16:05,856: Invalid glxinfo received
No protocol specified
/usr/share/libdrm/amdgpu.ids: No such file or directory

(lutris:594524): Gtk-CRITICAL **: 15:16:06.442: gtk_icon_theme_get_for_screen: assertion 'GDK_IS_SCREEN (screen)' failed
Traceback (most recent call last):
  File "/snap/lutris/39/usr/bin/lutris", line 52, in <module>
    from lutris.gui.application import Application  # pylint: disable=no-name-in-module
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/application.py", line 58, in <module>
    from .lutriswindow import LutrisWindow
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/lutriswindow.py", line 19, in <module>
    from lutris.gui.config.preferences_dialog import PreferencesDialog
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/config/preferences_dialog.py", line 10, in <module>
    from lutris.gui.config.runners_box import RunnersBox
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/config/runners_box.py", line 8, in <module>
    from lutris.gui.config.runner_box import RunnerBox
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/config/runner_box.py", line 9, in <module>
    from lutris.gui.dialogs.runner_install import RunnerInstallDialog
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/dialogs/runner_install.py", line 51, in <module>
    class RunnerInstallDialog(ModelessDialog):
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/dialogs/runner_install.py", line 61, in RunnerInstallDialog
    if has_stock_icon("software-installed-symbolic") else "wine-symbolic"
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/widgets/utils.py", line 155, in has_stock_icon
    return theme.has_icon(name)
AttributeError: 'NoneType' object has no attribute 'has_icon'


### How to Reproduce

Steps to reproduce the behavior:
snap run lutris


### Expected behavior

I expect the Lutris GUI to show up

### Log output

```shell
$~$ lutris -d
/usr/share/libdrm/amdgpu.ids: No such file or directory
ERROR:dbus.proxies:Introspect error on :1.37:/org/gnome/Mutter/DisplayConfig: dbus.exceptions.DBusException: org.freedesktop.DBus.Error.AccessDenied: An AppArmor policy prevents this sender from sending this message to this recipient; type="method_call", sender=":1.671" (uid=1000 pid=595487 comm="python3 /snap/lutris/39/usr/bin/lutris -d " label="snap.lutris.lutris (enforce)") interface="org.freedesktop.DBus.Introspectable" member="Introspect" error name="(unset)" requested_reply="0" destination=":1.37" (uid=1000 pid=2891 comm="/usr/bin/gnome-shell " label="unconfined")
/usr/share/libdrm/amdgpu.ids: No such file or directory
INFO     2023-05-17 15:26:06,543 [startup.init_lutris:209]:Starting Lutris 0.5.13
INFO:lutris.util.log:Starting Lutris 0.5.13
Traceback (most recent call last):
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/gui/application.py", line 436, in do_command_line
    run_all_checks()
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/startup.py", line 189, in run_all_checks
    check_driver()
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/startup.py", line 63, in check_driver
    driver_info = drivers.get_nvidia_driver_info()
  File "/snap/lutris/39/usr/lib/python3/dist-packages/lutris/util/graphics/drivers.py", line 18, in get_nvidia_driver_info
    with open(version_file_path, encoding='utf-8') as version_file:
PermissionError: [Errno 13] Permission denied: '/proc/driver/nvidia/version'
INFO     2023-05-17 15:26:06,549 [application.do_shutdown:960]:Shutting down Lutris
INFO:lutris.util.log:Shutting down Lutris
```


### System Information

```shell
I am using nvidia-driver-470 i havn't dared to upgrade due to bad experiences.

I am unable to get this but lshw gives:

$~$ sudo lshw
h-tech-g14                  
    description: Notebook
    product: ROG Zephyrus G14 GA401IV
    vendor: ASUSTeK COMPUTER INC.
    version: 1.0
    serial: LBNRKDCZR07Z492
    width: 64 bits
    capabilities: smbios-3.2.0 dmi-3.2.0 smp vsyscall32
    configuration: boot=normal chassis=notebook family=ROG Zephyrus G14 uuid=1ED63EDC-0C22-406F-B4F5-2CD4E1392913
  *-core
       description: Motherboard
       product: GA401IV
       vendor: ASUSTeK COMPUTER INC.
       physical id: 0
       version: 1.0
       serial: YS0YMC00AD
       slot: MIDDLE
     *-firmware
          description: BIOS
          vendor: American Megatrends Inc.
          physical id: 0
          version: GA401IV.219
          date: 12/30/2020
          size: 64KiB
          capacity: 16MiB
          capabilities: pci upgrade shadowing cdboot bootselect socketedrom edd int13floppy1200 int13floppy720 int13floppy2880 int5printscreen int9keyboard int14serial int17printer acpi usb smartbattery biosbootspecification uefi
     *-memory
          description: System Memory
          physical id: a
          slot: System board or motherboard
          size: 32GiB
        *-bank:0
             description: SODIMM DDR4 Synchronous Unbuffered (Unregistered) 3200 MHz (0,3 ns)
             product: M471A2G43AB2-CWE
             vendor: Samsung
             physical id: 0
             serial: 00000000
             slot: DIMM 0
             size: 16GiB
             width: 64 bits
             clock: 3200MHz (0.3ns)
        *-bank:1
             description: SODIMM DDR4 Synchronous Unbuffered (Unregistered) 3200 MHz (0,3 ns)
             product: M471A2K43DB1-CWE
             vendor: Samsung
             physical id: 1
             serial: 37E8B296
             slot: DIMM 0
             size: 16GiB
             width: 64 bits
             clock: 3200MHz (0.3ns)
     *-cache:0
          description: L1 cache
          physical id: c
          slot: L1 - Cache
          size: 512KiB
          capacity: 512KiB
          clock: 1GHz (1.0ns)
          capabilities: pipeline-burst internal write-back unified
          configuration: level=1
     *-cache:1
          description: L2 cache
          physical id: d
          slot: L2 - Cache
          size: 4MiB
          capacity: 4MiB
          clock: 1GHz (1.0ns)
          capabilities: pipeline-burst internal write-back unified
          configuration: level=2
     *-cache:2
          description: L3 cache
          physical id: e
          slot: L3 - Cache
          size: 8MiB
          capacity: 8MiB
          clock: 1GHz (1.0ns)
          capabilities: pipeline-burst internal write-back unified
          configuration: level=3
     *-cpu
          description: CPU
          product: AMD Ryzen 9 4900HS with Radeon Graphics
          vendor: Advanced Micro Devices [AMD]
          physical id: f
          bus info: cpu@0
          slot: FP6
          size: 1362MHz
          capacity: 3GHz
          width: 64 bits
          clock: 100MHz
          capabilities: lm fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp x86-64 constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip rdpid overflow_recov succor smca sme sev sev_es cpufreq
          configuration: cores=8 enabledcores=8 threads=16
     *-pci:0
          description: Host bridge
          product: Renoir Root Complex
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 100
          bus info: pci@0000:00:00.0
          version: 00
          width: 32 bits
          clock: 33MHz
        *-generic UNCLAIMED
             description: IOMMU
             product: Renoir IOMMU
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 0.2
             bus info: pci@0000:00:00.2
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: msi ht cap_list
             configuration: latency=0
        *-pci:0
             description: PCI bridge
             product: Renoir PCIe GPP Bridge
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 1.1
             bus info: pci@0000:00:01.1
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: pci pm pciexpress msi ht normal_decode bus_master cap_list
             configuration: driver=pcieport
             resources: irq:26 ioport:f000(size=4096) memory:fb000000-fc0fffff ioport:b0000000(size=303038464)
           *-display
                description: VGA compatible controller
                product: TU106 [GeForce RTX 2060]
                vendor: NVIDIA Corporation
                physical id: 0
                bus info: pci@0000:01:00.0
                version: a1
                width: 64 bits
                clock: 33MHz
                capabilities: pm msi pciexpress vga_controller bus_master cap_list rom
                configuration: driver=nvidia latency=0
                resources: irq:100 memory:fb000000-fbffffff memory:b0000000-bfffffff memory:c0000000-c1ffffff ioport:f000(size=128) memory:fc000000-fc07ffff
           *-multimedia
                description: Audio device
                product: TU106 High Definition Audio Controller
                vendor: NVIDIA Corporation
                physical id: 0.1
                bus info: pci@0000:01:00.1
                version: a1
                width: 32 bits
                clock: 33MHz
                capabilities: pm msi pciexpress bus_master cap_list
                configuration: driver=snd_hda_intel latency=0
                resources: irq:95 memory:fc080000-fc083fff
           *-usb
                description: USB controller
                product: TU106 USB 3.1 Host Controller
                vendor: NVIDIA Corporation
                physical id: 0.2
                bus info: pci@0000:01:00.2
                version: a1
                width: 64 bits
                clock: 33MHz
                capabilities: msi pciexpress pm xhci cap_list
                configuration: driver=xhci_hcd latency=0
                resources: irq:34 memory:c2000000-c203ffff memory:c2040000-c204ffff
              *-usbhost:0
                   product: xHCI Host Controller
                   vendor: Linux 5.15.0-71-generic xhci-hcd
                   physical id: 0
                   bus info: usb@1
                   logical name: usb1
                   version: 5.15
                   capabilities: usb-2.00
                   configuration: driver=hub slots=2 speed=480Mbit/s
              *-usbhost:1
                   product: xHCI Host Controller
                   vendor: Linux 5.15.0-71-generic xhci-hcd
                   physical id: 1
                   bus info: usb@2
                   logical name: usb2
                   version: 5.15
                   capabilities: usb-3.10
                   configuration: driver=hub slots=4 speed=10000Mbit/s
           *-serial UNCLAIMED
                description: Serial bus controller
                product: TU106 USB Type-C UCSI Controller
                vendor: NVIDIA Corporation
                physical id: 0.3
                bus info: pci@0000:01:00.3
                version: a1
                width: 32 bits
                clock: 33MHz
                capabilities: msi pciexpress pm cap_list
                configuration: latency=0
                resources: memory:fc084000-fc084fff
        *-pci:1
             description: PCI bridge
             product: Renoir PCIe GPP Bridge
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 2.2
             bus info: pci@0000:00:02.2
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: pci pm pciexpress msi ht normal_decode bus_master cap_list
             configuration: driver=pcieport
             resources: irq:27 memory:fc800000-fc8fffff
           *-network
                description: Wireless interface
                product: Wi-Fi 6 AX200
                vendor: Intel Corporation
                physical id: 0
                bus info: pci@0000:02:00.0
                logical name: wlp2s0
                version: 1a
                serial: ac:12:03:d9:7e:1b
                width: 64 bits
                clock: 33MHz
                capabilities: pm msi pciexpress msix bus_master cap_list ethernet physical wireless
                configuration: broadcast=yes driver=iwlwifi driverversion=5.15.0-71-generic firmware=66.f1c864e0.0 cc-a0-66.ucode ip=192.168.1.225 latency=0 link=yes multicast=yes wireless=IEEE 802.11
                resources: irq:78 memory:fc800000-fc803fff
        *-pci:2
             description: PCI bridge
             product: Renoir PCIe GPP Bridge
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 2.4
             bus info: pci@0000:00:02.4
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: pci pm pciexpress msi ht normal_decode bus_master cap_list
             configuration: driver=pcieport
             resources: irq:28 memory:fc700000-fc7fffff
           *-storage
                description: Non-Volatile memory controller
                product: Sandisk Corp
                vendor: Sandisk Corp
                physical id: 0
                bus info: pci@0000:03:00.0
                version: 01
                width: 64 bits
                clock: 33MHz
                capabilities: storage pm msi msix pciexpress nvm_express bus_master cap_list
                configuration: driver=nvme latency=0
                resources: irq:36 memory:fc700000-fc703fff memory:fc704000-fc7040ff
              *-nvme0
                   description: NVMe device
                   product: WDC PC SN530 SDBPNPZ-1T00-1002
                   physical id: 0
                   logical name: /dev/nvme0
                   version: 21106000
                   serial: 20231D456603
                   configuration: nqn=nqn.2018-01.com.wdc:nguid:E8238FA6BF53-0001-001B444A443ACE9C state=live
                 *-namespace
                      description: NVMe namespace
                      physical id: 1
                      logical name: /dev/nvme0n1
                      size: 953GiB (1024GB)
                      capabilities: gpt-1.00 partitioned partitioned:gpt
                      configuration: guid=5b1f081b-7d64-4349-92bd-8ee0cac2419b logicalsectorsize=512 sectorsize=512
                    *-volume:0
                         description: Windows FAT volume
                         vendor: mkfs.fat
                         physical id: 1
                         logical name: /dev/nvme0n1p1
                         logical name: /boot/efi
                         version: FAT32
                         serial: fe0a-bf19
                         size: 510MiB
                         capacity: 511MiB
                         capabilities: boot fat initialized
                         configuration: FATs=2 filesystem=fat mount.fstype=vfat mount.options=rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro name=EFI System Partition state=mounted
                    *-volume:1
                         description: EXT4 volume
                         vendor: Linux
                         physical id: 2
                         logical name: /dev/nvme0n1p2
                         logical name: /boot
                         version: 1.0
                         serial: 86246166-acf5-408f-a5db-78f7c48c3e3d
                         size: 732MiB
                         capabilities: journaled extended_attributes large_files huge_files dir_nlink recover 64bit extents ext4 ext2 initialized
                         configuration: created=2021-07-22 18:42:16 filesystem=ext4 lastmountpoint=/boot modified=2023-05-08 11:31:50 mount.fstype=ext4 mount.options=rw,relatime mounted=2023-05-08 11:31:50 state=mounted
                    *-volume:2
                         description: EFI partition
                         physical id: 3
                         logical name: /dev/nvme0n1p3
                         serial: 2d2a3d7a-bb14-43c1-b67b-232821aef836
                         size: 952GiB
                         capacity: 952GiB
                         width: 2954738264 bits
                         capabilities: encrypted luks initialized
                         configuration: bits=24429574744 filesystem=luks hash=sha256 version=2
        *-pci:3
             description: PCI bridge
             product: Renoir Internal PCIe GPP Bridge to Bus
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 8.1
             bus info: pci@0000:00:08.1
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: pci pm pciexpress msi normal_decode bus_master cap_list
             configuration: driver=pcieport
             resources: irq:29 ioport:e000(size=4096) memory:fc200000-fc5fffff ioport:d0000000(size=270532608)
           *-display
                description: VGA compatible controller
                product: Renoir
                vendor: Advanced Micro Devices, Inc. [AMD/ATI]
                physical id: 0
                bus info: pci@0000:04:00.0
                version: c5
                width: 64 bits
                clock: 33MHz
                capabilities: pm pciexpress msi msix vga_controller bus_master cap_list
                configuration: driver=amdgpu latency=0
                resources: irq:49 memory:d0000000-dfffffff memory:e0000000-e01fffff ioport:e000(size=256) memory:fc500000-fc57ffff
           *-multimedia:0
                description: Audio device
                product: Advanced Micro Devices, Inc. [AMD/ATI]
                vendor: Advanced Micro Devices, Inc. [AMD/ATI]
                physical id: 0.1
                bus info: pci@0000:04:00.1
                version: 00
                width: 32 bits
                clock: 33MHz
                capabilities: pm pciexpress msi bus_master cap_list
                configuration: driver=snd_hda_intel latency=0
                resources: irq:98 memory:fc5c8000-fc5cbfff
           *-generic
                description: Encryption controller
                product: Family 17h (Models 10h-1fh) Platform Security Processor
                vendor: Advanced Micro Devices, Inc. [AMD]
                physical id: 0.2
                bus info: pci@0000:04:00.2
                version: 00
                width: 32 bits
                clock: 33MHz
                capabilities: pm pciexpress msi msix bus_master cap_list
                configuration: driver=ccp latency=0
                resources: irq:75 memory:fc400000-fc4fffff memory:fc5cc000-fc5cdfff
           *-usb:0
                description: USB controller
                product: Renoir USB 3.1
                vendor: Advanced Micro Devices, Inc. [AMD]
                physical id: 0.3
                bus info: pci@0000:04:00.3
                version: 00
                width: 64 bits
                clock: 33MHz
                capabilities: pm pciexpress msi msix xhci bus_master cap_list
                configuration: driver=xhci_hcd latency=0
                resources: irq:40 memory:fc300000-fc3fffff
              *-usbhost:0
                   product: xHCI Host Controller
                   vendor: Linux 5.15.0-71-generic xhci-hcd
                   physical id: 0
                   bus info: usb@3
                   logical name: usb3
                   version: 5.15
                   capabilities: usb-2.00
                   configuration: driver=hub slots=4 speed=480Mbit/s
                 *-usb:0
                      description: Mouse
                      product: USB OPTICAL MOUSE
                      vendor: [Maxxter]
                      physical id: 1
                      bus info: usb@3:1
                      version: 1.00
                      capabilities: usb-1.10
                      configuration: driver=usbhid maxpower=100mA speed=1Mbit/s
                 *-usb:1
                      description: USB hub
                      product: USB Hub
                      vendor: Alcor Micro Corp.
                      physical id: 2
                      bus info: usb@3:2
                      version: 1.00
                      capabilities: usb-2.00
                      configuration: driver=hub maxpower=100mA slots=4 speed=480Mbit/s
                    *-usb
                         description: Keyboard
                         product: D4269
                         vendor: Metadot - Das Keyboard
                         physical id: 1
                         bus info: usb@3:2.1
                         version: 1.00
                         capabilities: usb-1.10
                         configuration: driver=usbhid maxpower=100mA speed=12Mbit/s
                 *-usb:2
                      description: Keyboard
                      product: N-KEY Device
                      vendor: ASUSTeK Computer Inc.
                      physical id: 3
                      bus info: usb@3:3
                      version: 0.02
                      capabilities: usb-2.00
                      configuration: driver=usbhid maxpower=100mA speed=12Mbit/s
                 *-usb:3
                      description: Keyboard
                      product: ITE Device(8910)
                      vendor: ITE Tech. Inc.
                      physical id: 4
                      bus info: usb@3:4
                      version: 0.03
                      capabilities: usb-2.00
                      configuration: driver=usbhid maxpower=100mA speed=12Mbit/s
              *-usbhost:1
                   product: xHCI Host Controller
                   vendor: Linux 5.15.0-71-generic xhci-hcd
                   physical id: 1
                   bus info: usb@4
                   logical name: usb4
                   version: 5.15
                   capabilities: usb-3.10
                   configuration: driver=hub slots=2 speed=10000Mbit/s
           *-usb:1
                description: USB controller
                product: Renoir USB 3.1
                vendor: Advanced Micro Devices, Inc. [AMD]
                physical id: 0.4
                bus info: pci@0000:04:00.4
                version: 00
                width: 64 bits
                clock: 33MHz
                capabilities: pm pciexpress msi msix xhci bus_master cap_list
                configuration: driver=xhci_hcd latency=0
                resources: irq:49 memory:fc200000-fc2fffff
              *-usbhost:0
                   product: xHCI Host Controller
                   vendor: Linux 5.15.0-71-generic xhci-hcd
                   physical id: 0
                   bus info: usb@5
                   logical name: usb5
                   version: 5.15
                   capabilities: usb-2.00
                   configuration: driver=hub slots=4 speed=480Mbit/s
                 *-usb:0 UNCLAIMED
                      description: Generic USB device
                      product: FingerPrint
                      vendor: Goodix
                      physical id: 3
                      bus info: usb@5:3
                      version: 1.00
                      capabilities: usb-2.00
                      configuration: maxpower=100mA speed=12Mbit/s
                 *-usb:1
                      description: Bluetooth wireless interface
                      vendor: Intel Corp.
                      physical id: 4
                      bus info: usb@5:4
                      version: 0.01
                      capabilities: bluetooth usb-2.01
                      configuration: driver=btusb maxpower=100mA speed=12Mbit/s
              *-usbhost:1
                   product: xHCI Host Controller
                   vendor: Linux 5.15.0-71-generic xhci-hcd
                   physical id: 1
                   bus info: usb@6
                   logical name: usb6
                   version: 5.15
                   capabilities: usb-3.10
                   configuration: driver=hub slots=2 speed=10000Mbit/s
           *-multimedia:1 UNCLAIMED
                description: Multimedia controller
                product: Raven/Raven2/FireFlight/Renoir Audio Processor
                vendor: Advanced Micro Devices, Inc. [AMD]
                physical id: 0.5
                bus info: pci@0000:04:00.5
                version: 01
                width: 32 bits
                clock: 33MHz
                capabilities: pm pciexpress msi cap_list
                configuration: latency=0
                resources: memory:fc580000-fc5bffff
           *-multimedia:2
                description: Audio device
                product: Family 17h (Models 10h-1fh) HD Audio Controller
                vendor: Advanced Micro Devices, Inc. [AMD]
                physical id: 0.6
                bus info: pci@0000:04:00.6
                version: 00
                width: 32 bits
                clock: 33MHz
                capabilities: pm pciexpress msi bus_master cap_list
                configuration: driver=snd_hda_intel latency=0
                resources: irq:37 memory:fc5c0000-fc5c7fff
        *-pci:4
             description: PCI bridge
             product: Renoir Internal PCIe GPP Bridge to Bus
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 8.2
             bus info: pci@0000:00:08.2
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: pci pm pciexpress msi normal_decode bus_master cap_list
             configuration: driver=pcieport
             resources: irq:30 memory:fc600000-fc6fffff
           *-sata:0
                description: SATA controller
                product: FCH SATA Controller [AHCI mode]
                vendor: Advanced Micro Devices, Inc. [AMD]
                physical id: 0
                bus info: pci@0000:05:00.0
                version: 81
                width: 32 bits
                clock: 33MHz
                capabilities: sata pm pciexpress msi ahci_1.0 bus_master cap_list
                configuration: driver=ahci latency=0
                resources: irq:33 memory:fc601000-fc6017ff
           *-sata:1
                description: SATA controller
                product: FCH SATA Controller [AHCI mode]
                vendor: Advanced Micro Devices, Inc. [AMD]
                physical id: 0.1
                bus info: pci@0000:05:00.1
                version: 81
                width: 32 bits
                clock: 33MHz
                capabilities: sata pm pciexpress msi ahci_1.0 bus_master cap_list
                configuration: driver=ahci latency=0
                resources: irq:39 memory:fc600000-fc6007ff
        *-serial
             description: SMBus
             product: FCH SMBus Controller
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 14
             bus info: pci@0000:00:14.0
             version: 51
             width: 32 bits
             clock: 66MHz
             configuration: driver=piix4_smbus latency=0
             resources: irq:0
        *-isa
             description: ISA bridge
             product: FCH LPC Bridge
             vendor: Advanced Micro Devices, Inc. [AMD]
             physical id: 14.3
             bus info: pci@0000:00:14.3
             version: 51
             width: 32 bits
             clock: 66MHz
             capabilities: isa bus_master
             configuration: latency=0
     *-pci:1
          description: Host bridge
          product: Renoir PCIe Dummy Host Bridge
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 101
          bus info: pci@0000:00:01.0
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:2
          description: Host bridge
          product: Renoir PCIe Dummy Host Bridge
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 102
          bus info: pci@0000:00:02.0
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:3
          description: Host bridge
          product: Renoir PCIe Dummy Host Bridge
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 103
          bus info: pci@0000:00:08.0
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:4
          description: Host bridge
          product: Renoir Device 24: Function 0
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 104
          bus info: pci@0000:00:18.0
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:5
          description: Host bridge
          product: Renoir Device 24: Function 1
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 105
          bus info: pci@0000:00:18.1
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:6
          description: Host bridge
          product: Renoir Device 24: Function 2
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 106
          bus info: pci@0000:00:18.2
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:7
          description: Host bridge
          product: Renoir Device 24: Function 3
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 107
          bus info: pci@0000:00:18.3
          version: 00
          width: 32 bits
          clock: 33MHz
          configuration: driver=k10temp
          resources: irq:0
     *-pci:8
          description: Host bridge
          product: Renoir Device 24: Function 4
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 108
          bus info: pci@0000:00:18.4
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:9
          description: Host bridge
          product: Renoir Device 24: Function 5
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 109
          bus info: pci@0000:00:18.5
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:10
          description: Host bridge
          product: Renoir Device 24: Function 6
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 10a
          bus info: pci@0000:00:18.6
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pci:11
          description: Host bridge
          product: Renoir Device 24: Function 7
          vendor: Advanced Micro Devices, Inc. [AMD]
          physical id: 10b
          bus info: pci@0000:00:18.7
          version: 00
          width: 32 bits
          clock: 33MHz
     *-pnp00:00
          product: PnP device PNP0c01
          physical id: 1
          capabilities: pnp
          configuration: driver=system
     *-pnp00:01
          product: PnP device PNP0b00
          physical id: 2
          capabilities: pnp
          configuration: driver=rtc_cmos
     *-pnp00:02
          product: PnP device PNP0c02
          physical id: 3
          capabilities: pnp
          configuration: driver=system
```


### Media (optional)

_No response_

### Checklist:

- [X] I'm not asking for support with a game or the wine runner.
- [X] I have followed the above mentioned guides and have all the graphics and wine dependencies installed.
- [X] I have checked for existing issues that describe my problem prior to opening this one.
- [X] I understand that improperly formatted bug reports may be closed without explanation.

Hints from issue discussion:
> I uninstalled the apt install of Lutris and installed the Snap version instead.

Then uninstall it and re-install the deb package, it's available on the releases page.
To my knowledge, the snap package is still being worked on and isn't ready for regular use.
Also, leave an issue here: https://github.com/CapeCrusader321/Lutris-snap

This seems to be the source of the Snap

Closing this issue since it's specific to the Snap package.
Just for other with the same issue. I can confirm that installing 0.5.13 deb package works without issue and solved my Starcraft 2 boot issue.

Commit message: Typing additions for drivers file

Patch to review:
diff --git a/lutris/util/graphics/drivers.py b/lutris/util/graphics/drivers.py
index 01bb0f9bd1..b07437f115 100644
--- a/lutris/util/graphics/drivers.py
+++ b/lutris/util/graphics/drivers.py
@@ -2,83 +2,139 @@
 
 Everything in this module should rely on /proc or /sys only, no executable calls
 """
+import contextlib
 import os
 import re
+from typing import Dict, Iterable, List
 
+from lutris.util.graphics.glxinfo import GlxInfo
 from lutris.util.log import logger
+from lutris.util.system import read_process_output
 
 MIN_RECOMMENDED_NVIDIA_DRIVER = 415
 
 
-def get_nvidia_driver_info():
+def get_nvidia_driver_info() -> Dict[str, Dict[str, str]]:
     """Return information about NVidia drivers"""
     version_file_path = "/proc/driver/nvidia/version"
     if not os.path.exists(version_file_path):
-        return
-    with open(version_file_path, encoding='utf-8') as version_file:
-        content = version_file.readlines()
-        nvrm_version = content[0].split(': ')[1].strip().split()
-        if 'Open' in nvrm_version:
+        return {}
+    with contextlib.suppress(OSError):
+        with open(version_file_path, encoding="utf-8") as version_file:
+            content = version_file.readlines()
+        nvrm_version = content[0].split(": ")[1].strip().split()
+        if "Open" in nvrm_version:
             return {
-                'nvrm': {
-                    'vendor': nvrm_version[0],
-                    'platform': nvrm_version[1],
-                    'arch': nvrm_version[6],
-                    'version': nvrm_version[7]
+                "nvrm": {
+                    "vendor": nvrm_version[0],
+                    "platform": nvrm_version[1],
+                    "arch": nvrm_version[6],
+                    "version": nvrm_version[7],
                 }
             }
         return {
-            'nvrm': {
-                'vendor': nvrm_version[0],
-                'platform': nvrm_version[1],
-                'arch': nvrm_version[2],
-                'version': nvrm_version[5],
-                'date': ' '.join(nvrm_version[6:])
+            "nvrm": {
+                "vendor": nvrm_version[0],
+                "platform": nvrm_version[1],
+                "arch": nvrm_version[2],
+                "version": nvrm_version[5],
+                "date": " ".join(nvrm_version[6:]),
             }
         }
+    # If the /proc file failed, look it up with glxinfo.
+    glx_info = GlxInfo()
+    platform = read_process_output(["uname", "-s"])
+    arch = read_process_output(["uname", "-m"])
+    return {
+        "nvrm": {
+            "vendor": glx_info.opengl_vendor,  # type: ignore[attr-defined]
+            "platform": platform,
+            "arch": arch,
+            "version": glx_info.opengl_version.rsplit(maxsplit=1)[-1],  # type: ignore[attr-defined]
+        }
+    }
 
 
-def get_nvidia_gpu_ids():
+def get_nvidia_gpu_ids() -> List[str]:
     """Return the list of Nvidia GPUs"""
-    return os.listdir("/proc/driver/nvidia/gpus")
+    with contextlib.suppress(OSError):
+        return os.listdir("/proc/driver/nvidia/gpus")
+    values = read_process_output(
+        # 10de is NVIDIA's vendor ID, 0300 gets you video controllers.
+        ["lspci", "-D", "-n", "-d", "10de::0300"],
+    ).splitlines()
+    return [line.split(maxsplit=1)[0] for line in values]
 
 
-def get_nvidia_gpu_info(gpu_id):
+def get_nvidia_gpu_info(gpu_id: str) -> Dict[str, str]:
     """Return details about a GPU"""
-    with open("/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding='utf-8') as info_file:
-        content = info_file.readlines()
-    infos = {}
-    for line in content:
+    with contextlib.suppress(OSError):
+        with open(
+            "/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding="utf-8"
+        ) as info_file:
+            content = info_file.readlines()
+        infos = {}
+        for line in content:
+            key, value = line.split(":", 1)
+            infos[key] = value.strip()
+        return infos
+    lspci_data = read_process_output(["lspci", "-v", "-s", gpu_id])
+    model_info = re.search(r"NVIDIA Corporation \w+ \[(.+?)\]", lspci_data)
+    if model_info:
+        model = model_info.group(1)
+    irq_info = re.search("IRQ ([0-9]+)", lspci_data)
+    if irq_info:
+        irq = irq_info.group(1)
+
+    info = {
+        "Model": f"NVIDIA {model}",
+        "IRQ": irq,
+        "Bus Location": gpu_id,
+    }
+    for line in lspci_data.splitlines():
+        if ":" not in line:
+            continue
         key, value = line.split(":", 1)
-        infos[key] = value.strip()
-    return infos
+        info[key.strip()] = value.strip()
+    return info
 
 
-def is_nvidia():
+def is_nvidia() -> bool:
     """Return true if the Nvidia drivers are currently in use"""
-    return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
+        return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
+        with open("/proc/modules") as f:
+            modules = f.read()
+        return bool(re.search(r"^nvidia ", modules, flags=re.MULTILINE))
+    glx_info = GlxInfo()
+    return "NVIDIA" in glx_info.opengl_vendor  # type: ignore[attr-defined]
 
 
-def get_gpus():
+def get_gpus() -> Iterable[str]:
     """Return GPUs connected to the system"""
     if not os.path.exists("/sys/class/drm"):
         logger.error("No GPU available on this system!")
-        return
+        return []
     try:
         cardlist = os.listdir("/sys/class/drm/")
     except PermissionError:
-        logger.error("Your system does not allow reading from /sys/class/drm, no GPU detected.")
+        logger.error(
+            "Your system does not allow reading from /sys/class/drm, no GPU detected."
+        )
         return
     for cardname in cardlist:
         if re.match(r"^card\d$", cardname):
             yield cardname
 
 
-def get_gpu_info(card):
+def get_gpu_info(card: str) -> Dict[str, str]:
     """Return information about a GPU"""
     infos = {"DRIVER": "", "PCI_ID": "", "PCI_SUBSYS_ID": ""}
     try:
-        with open("/sys/class/drm/%s/device/uevent" % card, encoding='utf-8') as card_uevent:
+        with open(
+            "/sys/class/drm/%s/device/uevent" % card, encoding="utf-8"
+        ) as card_uevent:
             content = card_uevent.readlines()
     except FileNotFoundError:
         logger.error("Unable to read driver information for card %s", card)
@@ -89,7 +145,7 @@ def get_gpu_info(card):
     return infos
 
 
-def is_amd():
+def is_amd() -> bool:
     """Return true if the system uses the AMD driver"""
     for card in get_gpus():
         if get_gpu_info(card)["DRIVER"] == "amdgpu":
@@ -97,22 +153,28 @@ def is_amd():
     return False
 
 
-def check_driver():
+def check_driver() -> None:
     """Report on the currently running driver"""
     if is_nvidia():
         driver_info = get_nvidia_driver_info()
         # pylint: disable=logging-format-interpolation
-        logger.info("Using {vendor} drivers {version} for {arch}".format(**driver_info["nvrm"]))
+        logger.info(
+            "Using {vendor} drivers {version} for {arch}".format(**driver_info["nvrm"])
+        )
         gpus = get_nvidia_gpu_ids()
         for gpu_id in gpus:
             gpu_info = get_nvidia_gpu_info(gpu_id)
             logger.info("GPU: %s", gpu_info.get("Model"))
     for card in get_gpus():
         # pylint: disable=logging-format-interpolation
-        logger.info("GPU: {PCI_ID} {PCI_SUBSYS_ID} using {DRIVER} driver".format(**get_gpu_info(card)))
+        logger.info(
+            "GPU: {PCI_ID} {PCI_SUBSYS_ID} using {DRIVER} driver".format(
+                **get_gpu_info(card)
+            )
+        )
 
 
-def is_outdated():
+def is_outdated() -> bool:
     if not is_nvidia():
         return False
     driver_info = get_nvidia_driver_info()
diff --git a/tests/util/graphics/test_drivers.py b/tests/util/graphics/test_drivers.py
new file mode 100644
index 0000000000..757927bac4
--- /dev/null
+++ b/tests/util/graphics/test_drivers.py
@@ -0,0 +1,272 @@
+import io
+import subprocess
+import unittest
+from unittest.mock import patch
+
+from lutris.util.graphics import drivers, glxinfo
+
+PROPRIETARY_MODULE_VERSION_FILE = """\
+NVRM version: NVIDIA UNIX x86_64 Kernel Module  525.105.17  Tue Mar 28 18:02:59 UTC 2023
+GCC version:  gcc version 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04)
+"""
+PROPRIETARY_MODULE_OUTPUT = {
+    "nvrm": {
+        "vendor": "NVIDIA",
+        "platform": "UNIX",
+        "arch": "x86_64",
+        "version": "525.105.17",
+        "date": "Tue Mar 28 18:02:59 UTC 2023",
+    }
+}
+
+OPEN_MODULE_VERSION_FILE = """\
+NVRM version: NVIDIA UNIX Open Kernel Module for x86_64  515.43.04  Release Build  (archlinux-builder@archlinux)
+GCC version:  gcc version 12.1.0 (GCC)
+"""
+OPEN_MODULE_OUTPUT = {
+    "nvrm": {
+        "vendor": "NVIDIA",
+        "platform": "UNIX",
+        "arch": "x86_64",
+        "version": "515.43.04",
+    }
+}
+DRIVER_VERSION_FILES = (
+    ("Proprietary", PROPRIETARY_MODULE_VERSION_FILE, PROPRIETARY_MODULE_OUTPUT),
+    ("Open", OPEN_MODULE_VERSION_FILE, OPEN_MODULE_OUTPUT),
+)
+SAMPLE_GLXINFO_OUTPUT = """\
+name of display: :0
+display: :0  screen: 0
+direct rendering: Yes
+Memory info (GL_NVX_gpu_memory_info):
+    Dedicated video memory: 6144 MB
+    Total available memory: 6144 MB
+    Currently available dedicated video memory: 3359 MB
+OpenGL vendor string: NVIDIA Corporation
+OpenGL renderer string: NVIDIA GeForce GTX 1660 SUPER/PCIe/SSE2
+OpenGL core profile version string: 4.6.0 NVIDIA 525.105.17
+OpenGL core profile shading language version string: 4.60 NVIDIA
+OpenGL core profile context flags: (none)
+OpenGL core profile profile mask: core profile
+
+OpenGL version string: 4.6.0 NVIDIA 525.105.17
+OpenGL shading language version string: 4.60 NVIDIA
+OpenGL context flags: (none)
+OpenGL profile mask: (none)
+
+OpenGL ES profile version string: OpenGL ES 3.2 NVIDIA 525.105.17
+OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.20
+"""
+FAKE_GLXINFO_NVIDIA = glxinfo.GlxInfo(SAMPLE_GLXINFO_OUTPUT)
+SAMPLE_GPU_INFORMATION = """\
+Model:           NVIDIA GeForce GTX 1660 SUPER
+IRQ:             35
+GPU UUID:        GPU-12345678-1234-1234-1234-1234567890ab
+Video BIOS:      90.16.48.00.aa
+Bus Type:        PCIe
+DMA Size:        47 bits
+DMA Mask:        0x7fffffffffff
+Bus Location:    0000:01:00.0
+Device Minor:    0
+GPU Excluded:    No
+"""
+SAMPLE_GPU_INFO_DICT = {
+    "Model": "NVIDIA GeForce GTX 1660 SUPER",
+    "IRQ": "35",
+    "GPU UUID": "GPU-12345678-1234-1234-1234-1234567890ab",
+    "Video BIOS": "90.16.48.00.aa",
+    "Bus Type": "PCIe",
+    "DMA Size": "47 bits",
+    "DMA Mask": "0x7fffffffffff",
+    "Bus Location": "0000:01:00.0",
+    "Device Minor": "0",
+    "GPU Excluded": "No",
+}
+SAMPLE_LSPCI_GPU = """\
+01:00.0 VGA compatible controller: NVIDIA Corporation TU116 \
+[GeForce GTX 1660 SUPER] (rev a1) (prog-if 00 [VGA controller])
+        Subsystem: eVga.com. Corp. TU116 [GeForce GTX 1660 SUPER]
+        Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
+        Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
+        Latency: 0
+        Interrupt: pin A routed to IRQ 35
+        Region 0: Memory at f6000000 (32-bit, non-prefetchable) [size=16M]
+        Region 1: Memory at e0000000 (64-bit, prefetchable) [size=256M]
+        Region 3: Memory at f0000000 (64-bit, prefetchable) [size=32M]
+        Region 5: I/O ports at e000 [size=128]
+        Expansion ROM at 000c0000 [virtual] [disabled] [size=128K]
+        Capabilities: <access denied>
+        Kernel driver in use: nvidia
+        Kernel modules: nvidiafb, nouveau, nvidia_drm, nvidia
+"""
+SAMPLE_PROC_MODULES = """\
+nvidia_uvm 1384448 2 - Live 0x0000000000000000 (POE)
+nvidia_drm 69632 26 - Live 0x0000000000000000 (POE)
+nvidia_modeset 1241088 67 nvidia_drm, Live 0x0000000000000000 (POE)
+uvcvideo 114688 0 - Live 0x0000000000000000
+videobuf2_vmalloc 20480 1 uvcvideo, Live 0x0000000000000000
+videobuf2_memops 20480 1 videobuf2_vmalloc, Live 0x0000000000000000
+videobuf2_v4l2 32768 1 uvcvideo, Live 0x0000000000000000
+nvidia 56500224 3776 nvidia_uvm,nvidia_modeset, Live 0x0000000000000000 (POE)
+videobuf2_common 81920 4 uvcvideo,videobuf2_vmalloc,videobuf2_memops,videobuf2_v4l2, Live 0x0000000000000000
+videodev 274432 3 uvcvideo,videobuf2_v4l2,videobuf2_common, Live 0x0000000000000000
+mc 65536 5 uvcvideo,videobuf2_v4l2,snd_usb_audio,videobuf2_common,videodev, Live 0x0000000000000000
+drm_kms_helper 200704 1 nvidia_drm, Live 0x0000000000000000
+drm 581632 30 nvidia_drm,nvidia,drm_kms_helper, Live 0x0000000000000000
+i2c_nvidia_gpu 16384 0 - Live 0x0000000000000000
+i2c_ccgx_ucsi 16384 1 i2c_nvidia_gpu, Live 0x0000000000000000
+video 65536 0 - Live 0x0000000000000000
+"""
+
+
+class TestGetNvidiaDriverInfo(unittest.TestCase):
+    def test_success_on_current_machine(self):
+        drivers.get_nvidia_driver_info()
+
+    @patch("os.path.exists", return_value=False)
+    def test_returns_none_if_file_doesnt_exist(self, mock_path_exists):
+        self.assertEqual(drivers.get_nvidia_driver_info(), {})
+
+    @patch("builtins.open")
+    @patch("os.path.exists", return_value=True)
+    def test_from_file(self, mock_path_exists, mock_open):
+        for test_type, version_file, expected in DRIVER_VERSION_FILES:
+            with self.subTest(test_type):
+                mock_open.return_value = io.StringIO(version_file)
+
+                actual = drivers.get_nvidia_driver_info()
+
+                self.assertEqual(actual, expected)
+
+    @patch(
+        "builtins.open",
+        side_effect=PermissionError(
+            13, "Permission Denied: '/proc/driver/nvidia/version'"
+        ),
+    )
+    @patch("os.path.exists", return_value=True)
+    def test_file_errors(self, mock_path_exists, mock_open):
+        with patch.object(drivers, "GlxInfo", return_value=FAKE_GLXINFO_NVIDIA), patch.object(
+            subprocess,
+            "run",
+            side_effect=[
+                subprocess.CompletedProcess([], 0, stdout="Linux\n"),
+                subprocess.CompletedProcess([], 0, stdout="x86_64\n"),
+            ],
+        ):
+            actual = drivers.get_nvidia_driver_info()
+
+        self.assertEqual(
+            actual,
+            {
+                "nvrm": {
+                    "vendor": "NVIDIA Corporation",
+                    "platform": "Linux",
+                    "arch": "x86_64",
+                    "version": "525.105.17",
+                }
+            },
+        )
+
+
+class TestGetNvidiaGpuIds(unittest.TestCase):
+
+    sample_gpu_list = ["0000:01:00.0"]
+
+    @patch("os.listdir", return_value=sample_gpu_list)
+    def test_get_from_proc(self, mock_listdir):
+        self.assertEqual(
+            drivers.get_nvidia_gpu_ids(),
+            self.sample_gpu_list
+        )
+
+    @patch(
+        "subprocess.run",
+        return_value=subprocess.CompletedProcess(
+            args=["lspci", "-D", "-n", "-d", "10de::0300"],
+            returncode=0,
+            stdout="0000:01:00.0 0300: 10de:21c4 (rev a1)\n"
+        )
+    )
+    @patch("os.listdir", side_effect=PermissionError())
+    def test_get_from_lspci(self, mock_listdir, mock_lspci):
+        self.assertEqual(
+            drivers.get_nvidia_gpu_ids(),
+            self.sample_gpu_list
+        )
+
+
+class TestGetNvidiaGpuInfo(unittest.TestCase):
+
+    sample_gpu_id = "0000:01:00.0"
+
+    @patch("builtins.open", return_value=io.StringIO(SAMPLE_GPU_INFORMATION))
+    def test_get_from_proc(self, mock_open):
+        result = drivers.get_nvidia_gpu_info(self.sample_gpu_id)
+
+        self.assertEqual(result, SAMPLE_GPU_INFO_DICT)
+
+    @patch(
+        "subprocess.run",
+        return_value=subprocess.CompletedProcess(
+            [], 0,
+            stdout=SAMPLE_LSPCI_GPU
+        )
+    )
+    @patch("builtins.open", side_effect=PermissionError())
+    def test_get_from_lspci_glxinfo(self, mock_open, mock_lspci):
+        result = drivers.get_nvidia_gpu_info(self.sample_gpu_id)
+
+        self.assertDictContainsSubset(
+            {
+                "Model": "NVIDIA GeForce GTX 1660 SUPER",
+                "IRQ": "35",
+                "Bus Location": self.sample_gpu_id,
+                "Subsystem": "eVga.com. Corp. TU116 [GeForce GTX 1660 SUPER]",
+                "Interrupt": "pin A routed to IRQ 35",
+                "Region 0": "Memory at f6000000 (32-bit, non-prefetchable) [size=16M]",
+                "Region 1": "Memory at e0000000 (64-bit, prefetchable) [size=256M]",
+                "Region 3": "Memory at f0000000 (64-bit, prefetchable) [size=32M]",
+                "Region 5": "I/O ports at e000 [size=128]",
+                "Kernel driver in use": "nvidia",
+            },
+            result
+        )
+
+
+class TestIsNvidia(unittest.TestCase):
+
+    def test_success_on_current_machine(self):
+        self.assertIsInstance(drivers.is_nvidia(), bool)
+
+    @patch("os.path.exists", return_value=False)
+    def test_not_nvidia_by_directory(self, mock_exists):
+        self.assertFalse(drivers.is_nvidia())
+
+    @patch("builtins.open", return_value=io.StringIO(""))
+    @patch("os.path.exists", side_effect=PermissionError())
+    def test_not_nvidia_proc_modules(self, mock_exists, mock_open):
+        self.assertFalse(drivers.is_nvidia())
+
+    # TODO: Add AMD GLX info and uncomment this test.
+    # @patch.object(drivers, "GlxInfo", return_value=FAKE_GLXINFO_AMD)
+    # @patch("builtins.open", side_effect=PermissionError())
+    # @patch("os.path.exists", side_effect=PermissionError())
+    # def test_not_nvidia_glxinfo(self, mock_exists, mock_open, mock_glxinfo):
+    #     self.assertFalse(drivers.is_nvidia())
+
+    @patch("os.path.exists", return_value=True)
+    def test_is_nvidia_by_directory(self, mock_exists):
+        self.assertTrue(drivers.is_nvidia())
+
+    @patch("builtins.open", return_value=io.StringIO(SAMPLE_PROC_MODULES))
+    @patch("os.path.exists", side_effect=PermissionError())
+    def test_is_nvidia_proc_modules(self, mock_exists, mock_open):
+        self.assertTrue(drivers.is_nvidia())
+
+    @patch.object(drivers, "GlxInfo", return_value=FAKE_GLXINFO_NVIDIA)
+    @patch("builtins.open", side_effect=PermissionError())
+    @patch("os.path.exists", side_effect=PermissionError())
+    def test_is_nvidia_glxinfo(self, mock_exists, mock_open, mock_glxinfo):
+        self.assertTrue(drivers.is_nvidia())


Code review comments:
diff_hunk: @@ -2,83 +2,139 @@
 
 Everything in this module should rely on /proc or /sys only, no executable calls
 """
+import contextlib
 import os
 import re
+from typing import Dict, Iterable, List
 
+from lutris.util.graphics.glxinfo import GlxInfo
 from lutris.util.log import logger
+from lutris.util.system import read_process_output
 
 MIN_RECOMMENDED_NVIDIA_DRIVER = 415
 
 
-def get_nvidia_driver_info():
+def get_nvidia_driver_info() -> Dict[str, Dict[str, str]]:
     """Return information about NVidia drivers"""
     version_file_path = "/proc/driver/nvidia/version"
     if not os.path.exists(version_file_path):
-        return
-    with open(version_file_path, encoding='utf-8') as version_file:
-        content = version_file.readlines()
-        nvrm_version = content[0].split(': ')[1].strip().split()
-        if 'Open' in nvrm_version:
+        return {}
+    with contextlib.suppress(OSError):
+        with open(version_file_path, encoding="utf-8") as version_file:
+            content = version_file.readlines()
+        nvrm_version = content[0].split(": ")[1].strip().split()
+        if "Open" in nvrm_version:
             return {
-                'nvrm': {
-                    'vendor': nvrm_version[0],
-                    'platform': nvrm_version[1],
-                    'arch': nvrm_version[6],
-                    'version': nvrm_version[7]
+                "nvrm": {
+                    "vendor": nvrm_version[0],
+                    "platform": nvrm_version[1],
+                    "arch": nvrm_version[6],
+                    "version": nvrm_version[7],
                 }
             }
         return {
-            'nvrm': {
-                'vendor': nvrm_version[0],
-                'platform': nvrm_version[1],
-                'arch': nvrm_version[2],
-                'version': nvrm_version[5],
-                'date': ' '.join(nvrm_version[6:])
+            "nvrm": {
+                "vendor": nvrm_version[0],
+                "platform": nvrm_version[1],
+                "arch": nvrm_version[2],
+                "version": nvrm_version[5],
+                "date": " ".join(nvrm_version[6:]),
             }
         }
+    # If the /proc file failed, look it up with glxinfo.
+    glx_info = GlxInfo()
+    platform = read_process_output(["uname", "-s"])
+    arch = read_process_output(["uname", "-m"])
+    return {
+        "nvrm": {
+            "vendor": glx_info.opengl_vendor,  # type: ignore[attr-defined]
+            "platform": platform,
+            "arch": arch,
+            "version": glx_info.opengl_version.rsplit(maxsplit=1)[-1],  # type: ignore[attr-defined]
+        }
+    }
 
 
-def get_nvidia_gpu_ids():
+def get_nvidia_gpu_ids() -> List[str]:
     """Return the list of Nvidia GPUs"""
-    return os.listdir("/proc/driver/nvidia/gpus")
+    with contextlib.suppress(OSError):
+        return os.listdir("/proc/driver/nvidia/gpus")
+    values = read_process_output(
+        # 10de is NVIDIA's vendor ID, 0300 gets you video controllers.
+        ["lspci", "-D", "-n", "-d", "10de::0300"],
+    ).splitlines()
+    return [line.split(maxsplit=1)[0] for line in values]
 
 
-def get_nvidia_gpu_info(gpu_id):
+def get_nvidia_gpu_info(gpu_id: str) -> Dict[str, str]:
     """Return details about a GPU"""
-    with open("/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding='utf-8') as info_file:
-        content = info_file.readlines()
-    infos = {}
-    for line in content:
+    with contextlib.suppress(OSError):
+        with open(
+            "/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding="utf-8"
+        ) as info_file:
+            content = info_file.readlines()
+        infos = {}
+        for line in content:
+            key, value = line.split(":", 1)
+            infos[key] = value.strip()
+        return infos
+    lspci_data = read_process_output(["lspci", "-v", "-s", gpu_id])
+    model_info = re.search(r"NVIDIA Corporation \w+ \[(.+?)\]", lspci_data)
+    if model_info:
+        model = model_info.group(1)
+    irq_info = re.search("IRQ ([0-9]+)", lspci_data)
+    if irq_info:
+        irq = irq_info.group(1)
+
+    info = {
+        "Model": f"NVIDIA {model}",
+        "IRQ": irq,
+        "Bus Location": gpu_id,
+    }
+    for line in lspci_data.splitlines():
+        if ":" not in line:
+            continue
         key, value = line.split(":", 1)
-        infos[key] = value.strip()
-    return infos
+        info[key.strip()] = value.strip()
+    return info
 
 
-def is_nvidia():
+def is_nvidia() -> bool:
     """Return true if the Nvidia drivers are currently in use"""
-    return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
+        return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
path: lutris/util/graphics/drivers.py
text: @user1:
*Gah!* but maybe not so much *Gah!* here.

I still hate *contextlib.suppress* but this is trying a second path if the first fails, then falling back to GlxInfo() if they both fail.

It could be written straightforwardly as a pair of ``try: except OSError:``, but you maybe wind up nesting except blocks.

It would be appropriate to log *two* warnings here- one for each path. This assumes they are really separate things, but if they aren't why would we check both like this?

@author:
Depending on the system's configuration, one may have access to to `/proc/modules` but not to `/proc/driver/nvidia`. For example, the current implementation of snap confinement blocks access to anything in `/proc/driver`, but not to `/proc/modules`, so an instance of Lutris confined within snap would use `/proc/modules` to detect it, but not `/proc/driver/nvidia`. However, some other configurations that still use NVIDIA's drivers may not have access to `/proc/modules` due to other security limitations. (For example, a multi-headed machine).

Realistically though, the `glxinfo` route here is the only one that can actually guarantee us that the user's session is using the NVIDIA drivers. Here are a few scenarios where the other two give the wrong answer:

1. A user has multiple graphics cards: An NVIDIA one purely for machine learning purposes, and an Intel one for desktop use and gaming. In this case, both the current implementation and the `/proc/modules` implementation would return `True` here, but the `glxinfo` way will return `False` since the NVIDIA drivers are loaded, but not being used to render anything.
2. A multi-headed machine with multiple graphics cards, including an NVIDIA one. If the user's session is rendered with a non-NVIDIA card.
3. (Not 100% sure about this since I don't have the hardware to test it, but I think...) An Optimus setup where the user has explicitly selected selected not to use the NVIDIA card for Lutris. (E.g. a developer who's trying to replicate an issue that occurs with Intel graphics.)

The code is unclear whether we're asking "is the nvidia kernel module loaded?" or "are we using nvidia drivers to render this session?" Both `/proc` based implementations answer the former, but I think what we actually care about for Lutris is the latter.

Without knowing more about the side effects I'm hesitant to commit to it, but if the latter is actually what we care about (which seems to be the case from the usages), I think just checking `glxinfo` would be the "most correct" way to do it.

@user1:
I'm not so sure. This code is used for checks at startup, but a laptop with hybrid graphics might start up in Intel mode, then trip into NVidia mode upon starting a game- long after Lutris startup. I think Lutris is asking if there's NVidia stuff around here, even if not in use right now.

@author:
That makes sense. I'm reworking based on this info. Thanks!

@author:
OK, so based on this I've made it:

1. Detect Nvidia drivers (2 different ways)
2. Fall back to `glxinfo` *but* log an error if it couldn't detect the module either way, since `glxinfo` has a potential to be wrong for what's being retrieved.

I also put in a note in a comment that `is_nvidia` doesn't detect nouveau drivers. Based on the previous function comment that appears to be intended, so I didn't change the behaviour.

diff_hunk: @@ -2,83 +2,139 @@
 
 Everything in this module should rely on /proc or /sys only, no executable calls
 """
+import contextlib
 import os
 import re
+from typing import Dict, Iterable, List
 
+from lutris.util.graphics.glxinfo import GlxInfo
 from lutris.util.log import logger
+from lutris.util.system import read_process_output
 
 MIN_RECOMMENDED_NVIDIA_DRIVER = 415
 
 
-def get_nvidia_driver_info():
+def get_nvidia_driver_info() -> Dict[str, Dict[str, str]]:
     """Return information about NVidia drivers"""
     version_file_path = "/proc/driver/nvidia/version"
     if not os.path.exists(version_file_path):
-        return
-    with open(version_file_path, encoding='utf-8') as version_file:
-        content = version_file.readlines()
-        nvrm_version = content[0].split(': ')[1].strip().split()
-        if 'Open' in nvrm_version:
+        return {}
+    with contextlib.suppress(OSError):
+        with open(version_file_path, encoding="utf-8") as version_file:
+            content = version_file.readlines()
+        nvrm_version = content[0].split(": ")[1].strip().split()
+        if "Open" in nvrm_version:
             return {
-                'nvrm': {
-                    'vendor': nvrm_version[0],
-                    'platform': nvrm_version[1],
-                    'arch': nvrm_version[6],
-                    'version': nvrm_version[7]
+                "nvrm": {
+                    "vendor": nvrm_version[0],
+                    "platform": nvrm_version[1],
+                    "arch": nvrm_version[6],
+                    "version": nvrm_version[7],
                 }
             }
         return {
-            'nvrm': {
-                'vendor': nvrm_version[0],
-                'platform': nvrm_version[1],
-                'arch': nvrm_version[2],
-                'version': nvrm_version[5],
-                'date': ' '.join(nvrm_version[6:])
+            "nvrm": {
+                "vendor": nvrm_version[0],
+                "platform": nvrm_version[1],
+                "arch": nvrm_version[2],
+                "version": nvrm_version[5],
+                "date": " ".join(nvrm_version[6:]),
             }
         }
+    # If the /proc file failed, look it up with glxinfo.
+    glx_info = GlxInfo()
+    platform = read_process_output(["uname", "-s"])
+    arch = read_process_output(["uname", "-m"])
+    return {
+        "nvrm": {
+            "vendor": glx_info.opengl_vendor,  # type: ignore[attr-defined]
+            "platform": platform,
+            "arch": arch,
+            "version": glx_info.opengl_version.rsplit(maxsplit=1)[-1],  # type: ignore[attr-defined]
+        }
+    }
 
 
-def get_nvidia_gpu_ids():
+def get_nvidia_gpu_ids() -> List[str]:
     """Return the list of Nvidia GPUs"""
-    return os.listdir("/proc/driver/nvidia/gpus")
+    with contextlib.suppress(OSError):
+        return os.listdir("/proc/driver/nvidia/gpus")
+    values = read_process_output(
+        # 10de is NVIDIA's vendor ID, 0300 gets you video controllers.
+        ["lspci", "-D", "-n", "-d", "10de::0300"],
+    ).splitlines()
+    return [line.split(maxsplit=1)[0] for line in values]
 
 
-def get_nvidia_gpu_info(gpu_id):
+def get_nvidia_gpu_info(gpu_id: str) -> Dict[str, str]:
     """Return details about a GPU"""
-    with open("/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding='utf-8') as info_file:
-        content = info_file.readlines()
-    infos = {}
-    for line in content:
+    with contextlib.suppress(OSError):
+        with open(
+            "/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding="utf-8"
+        ) as info_file:
+            content = info_file.readlines()
+        infos = {}
+        for line in content:
+            key, value = line.split(":", 1)
+            infos[key] = value.strip()
+        return infos
+    lspci_data = read_process_output(["lspci", "-v", "-s", gpu_id])
+    model_info = re.search(r"NVIDIA Corporation \w+ \[(.+?)\]", lspci_data)
+    if model_info:
+        model = model_info.group(1)
+    irq_info = re.search("IRQ ([0-9]+)", lspci_data)
+    if irq_info:
+        irq = irq_info.group(1)
+
+    info = {
+        "Model": f"NVIDIA {model}",
+        "IRQ": irq,
+        "Bus Location": gpu_id,
+    }
+    for line in lspci_data.splitlines():
+        if ":" not in line:
+            continue
         key, value = line.split(":", 1)
-        infos[key] = value.strip()
-    return infos
+        info[key.strip()] = value.strip()
+    return info
 
 
-def is_nvidia():
+def is_nvidia() -> bool:
     """Return true if the Nvidia drivers are currently in use"""
-    return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
+        return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
+        with open("/proc/modules") as f:
+            modules = f.read()
+        return bool(re.search(r"^nvidia ", modules, flags=re.MULTILINE))
+    glx_info = GlxInfo()
+    return "NVIDIA" in glx_info.opengl_vendor  # type: ignore[attr-defined]
 
 
-def get_gpus():
+def get_gpus() -> Iterable[str]:
     """Return GPUs connected to the system"""
     if not os.path.exists("/sys/class/drm"):
         logger.error("No GPU available on this system!")
-        return
+        return []
     try:
         cardlist = os.listdir("/sys/class/drm/")
     except PermissionError:
-        logger.error("Your system does not allow reading from /sys/class/drm, no GPU detected.")
+        logger.error(
+            "Your system does not allow reading from /sys/class/drm, no GPU detected."
+        )
         return
     for cardname in cardlist:
         if re.match(r"^card\d$", cardname):
             yield cardname
 
 
-def get_gpu_info(card):
+def get_gpu_info(card: str) -> Dict[str, str]:
     """Return information about a GPU"""
     infos = {"DRIVER": "", "PCI_ID": "", "PCI_SUBSYS_ID": ""}
     try:
-        with open("/sys/class/drm/%s/device/uevent" % card, encoding='utf-8') as card_uevent:
+        with open(
+            "/sys/class/drm/%s/device/uevent" % card, encoding="utf-8"
+        ) as card_uevent:
             content = card_uevent.readlines()
     except FileNotFoundError:
path: lutris/util/graphics/drivers.py
text: @user1:
And now it's FileNotFoundError?

Why isn't it Permission error? Or OSError to catch both?

@author:
This and [the PermissionError catch above](https://github.com/lutris/lutris/pull/4866#discussion_r1199728570) were part of the existing code.

IMO the functions probably should just catch all `OSError` events, since it doesn't make much of a difference *why* there was an error, but I was opting to make minimal changes, especially since this is my first PR to Lutris. I'll adjust the whole file based on your code review.

diff_hunk: @@ -2,83 +2,139 @@
 
 Everything in this module should rely on /proc or /sys only, no executable calls
 """
+import contextlib
 import os
 import re
+from typing import Dict, Iterable, List
 
+from lutris.util.graphics.glxinfo import GlxInfo
 from lutris.util.log import logger
+from lutris.util.system import read_process_output
 
 MIN_RECOMMENDED_NVIDIA_DRIVER = 415
 
 
-def get_nvidia_driver_info():
+def get_nvidia_driver_info() -> Dict[str, Dict[str, str]]:
     """Return information about NVidia drivers"""
     version_file_path = "/proc/driver/nvidia/version"
     if not os.path.exists(version_file_path):
-        return
-    with open(version_file_path, encoding='utf-8') as version_file:
-        content = version_file.readlines()
-        nvrm_version = content[0].split(': ')[1].strip().split()
-        if 'Open' in nvrm_version:
+        return {}
+    with contextlib.suppress(OSError):
+        with open(version_file_path, encoding="utf-8") as version_file:
+            content = version_file.readlines()
+        nvrm_version = content[0].split(": ")[1].strip().split()
+        if "Open" in nvrm_version:
             return {
-                'nvrm': {
-                    'vendor': nvrm_version[0],
-                    'platform': nvrm_version[1],
-                    'arch': nvrm_version[6],
-                    'version': nvrm_version[7]
+                "nvrm": {
+                    "vendor": nvrm_version[0],
+                    "platform": nvrm_version[1],
+                    "arch": nvrm_version[6],
+                    "version": nvrm_version[7],
                 }
             }
         return {
-            'nvrm': {
-                'vendor': nvrm_version[0],
-                'platform': nvrm_version[1],
-                'arch': nvrm_version[2],
-                'version': nvrm_version[5],
-                'date': ' '.join(nvrm_version[6:])
+            "nvrm": {
+                "vendor": nvrm_version[0],
+                "platform": nvrm_version[1],
+                "arch": nvrm_version[2],
+                "version": nvrm_version[5],
+                "date": " ".join(nvrm_version[6:]),
             }
         }
+    # If the /proc file failed, look it up with glxinfo.
path: lutris/util/graphics/drivers.py
text: @user1:
Is this comment saying that this is an 'except OSError' block really? It looks like that to me, and if so, you should write it that way. Down with *contextlib.suppress*!

diff_hunk: @@ -2,83 +2,139 @@
 
 Everything in this module should rely on /proc or /sys only, no executable calls
 """
+import contextlib
 import os
 import re
+from typing import Dict, Iterable, List
 
+from lutris.util.graphics.glxinfo import GlxInfo
 from lutris.util.log import logger
+from lutris.util.system import read_process_output
 
 MIN_RECOMMENDED_NVIDIA_DRIVER = 415
 
 
-def get_nvidia_driver_info():
+def get_nvidia_driver_info() -> Dict[str, Dict[str, str]]:
     """Return information about NVidia drivers"""
     version_file_path = "/proc/driver/nvidia/version"
     if not os.path.exists(version_file_path):
-        return
-    with open(version_file_path, encoding='utf-8') as version_file:
-        content = version_file.readlines()
-        nvrm_version = content[0].split(': ')[1].strip().split()
-        if 'Open' in nvrm_version:
+        return {}
+    with contextlib.suppress(OSError):
path: lutris/util/graphics/drivers.py
text: @user1:
**Gah!**

I am *shocked, shocked* that Python has a standard library function to swallow exceptions outright. That's paving the cow-path to hell right here.

Don't do this. Use try-except for catch the OSError, and log *something* when access to /proc/driver/nvidia fails.

I'd use logger.exception() here, but if that's too much log spam (maybe this is a more common issue than I know?) logger.,warning() would be reasonable.

@author:
My reason for choosing `contextlib.suppress` here was that this isn't so much catching an error per se as it is the fairly common-within-the-python-world exception handling as logic. In pseudocode, it's a pythonic version of:

```python
if can_read_proc_file():
  return use_proc_file()
else:
  return use_glxinfo()
```

I hesitate to use `logger.exception` (or, frankly, even `logger.warning`) here since this is a pretty normal condition. In addition to this happening in any snap-confined environment, this will happen in many other confined environments. In fact I'm quite surprised it's not occurring with NixOS, as their confinement is often stricter than what's provided for snaps. Stricter SELinux configurations will also block access to these files.

I'll put in a warning log for now, but I'm concerned this will result in logspam, especially since these functions will tend to be called together.  

@user1:
Thank you.

@author:
What about an info level log?

@user1:
If this is thought to be normal, that would be fine.

@author:
OK, I've adjusted things as follows:

* Cases I specifically know could be expected (e.g. not having access to various `/proc` files) but that still allow normal usage: `info`
* Abnormal cases that still allow the same information to be retrieved (e.g. other `OSError` cases in `/proc/driver/nvidia/version`): `warning`
* Cases that could cause incorrect information to be provided: `error`

This should keep logspam to a minimum (All the cases I know of with either snap confinement or strict selinux rules should only provide `info` logs) without swallowing other issues (e.g. if `/proc/driver/nvidia/version` became a directory, it would still provide a warning).

diff_hunk: @@ -2,83 +2,139 @@
 
 Everything in this module should rely on /proc or /sys only, no executable calls
 """
+import contextlib
 import os
 import re
+from typing import Dict, Iterable, List
 
+from lutris.util.graphics.glxinfo import GlxInfo
 from lutris.util.log import logger
+from lutris.util.system import read_process_output
 
 MIN_RECOMMENDED_NVIDIA_DRIVER = 415
 
 
-def get_nvidia_driver_info():
+def get_nvidia_driver_info() -> Dict[str, Dict[str, str]]:
     """Return information about NVidia drivers"""
     version_file_path = "/proc/driver/nvidia/version"
     if not os.path.exists(version_file_path):
-        return
-    with open(version_file_path, encoding='utf-8') as version_file:
-        content = version_file.readlines()
-        nvrm_version = content[0].split(': ')[1].strip().split()
-        if 'Open' in nvrm_version:
+        return {}
+    with contextlib.suppress(OSError):
+        with open(version_file_path, encoding="utf-8") as version_file:
+            content = version_file.readlines()
+        nvrm_version = content[0].split(": ")[1].strip().split()
+        if "Open" in nvrm_version:
             return {
-                'nvrm': {
-                    'vendor': nvrm_version[0],
-                    'platform': nvrm_version[1],
-                    'arch': nvrm_version[6],
-                    'version': nvrm_version[7]
+                "nvrm": {
+                    "vendor": nvrm_version[0],
+                    "platform": nvrm_version[1],
+                    "arch": nvrm_version[6],
+                    "version": nvrm_version[7],
                 }
             }
         return {
-            'nvrm': {
-                'vendor': nvrm_version[0],
-                'platform': nvrm_version[1],
-                'arch': nvrm_version[2],
-                'version': nvrm_version[5],
-                'date': ' '.join(nvrm_version[6:])
+            "nvrm": {
+                "vendor": nvrm_version[0],
+                "platform": nvrm_version[1],
+                "arch": nvrm_version[2],
+                "version": nvrm_version[5],
+                "date": " ".join(nvrm_version[6:]),
             }
         }
+    # If the /proc file failed, look it up with glxinfo.
+    glx_info = GlxInfo()
+    platform = read_process_output(["uname", "-s"])
+    arch = read_process_output(["uname", "-m"])
+    return {
+        "nvrm": {
+            "vendor": glx_info.opengl_vendor,  # type: ignore[attr-defined]
+            "platform": platform,
+            "arch": arch,
+            "version": glx_info.opengl_version.rsplit(maxsplit=1)[-1],  # type: ignore[attr-defined]
+        }
+    }
 
 
-def get_nvidia_gpu_ids():
+def get_nvidia_gpu_ids() -> List[str]:
     """Return the list of Nvidia GPUs"""
-    return os.listdir("/proc/driver/nvidia/gpus")
+    with contextlib.suppress(OSError):
+        return os.listdir("/proc/driver/nvidia/gpus")
+    values = read_process_output(
+        # 10de is NVIDIA's vendor ID, 0300 gets you video controllers.
+        ["lspci", "-D", "-n", "-d", "10de::0300"],
+    ).splitlines()
+    return [line.split(maxsplit=1)[0] for line in values]
 
 
-def get_nvidia_gpu_info(gpu_id):
+def get_nvidia_gpu_info(gpu_id: str) -> Dict[str, str]:
     """Return details about a GPU"""
-    with open("/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding='utf-8') as info_file:
-        content = info_file.readlines()
-    infos = {}
-    for line in content:
+    with contextlib.suppress(OSError):
path: lutris/util/graphics/drivers.py
text: @user1:
**Gah!** the third.

This is another ``try: except OSError:``, please write it that way.

diff_hunk: @@ -2,83 +2,139 @@
 
 Everything in this module should rely on /proc or /sys only, no executable calls
 """
+import contextlib
 import os
 import re
+from typing import Dict, Iterable, List
 
+from lutris.util.graphics.glxinfo import GlxInfo
 from lutris.util.log import logger
+from lutris.util.system import read_process_output
 
 MIN_RECOMMENDED_NVIDIA_DRIVER = 415
 
 
-def get_nvidia_driver_info():
+def get_nvidia_driver_info() -> Dict[str, Dict[str, str]]:
     """Return information about NVidia drivers"""
     version_file_path = "/proc/driver/nvidia/version"
     if not os.path.exists(version_file_path):
-        return
-    with open(version_file_path, encoding='utf-8') as version_file:
-        content = version_file.readlines()
-        nvrm_version = content[0].split(': ')[1].strip().split()
-        if 'Open' in nvrm_version:
+        return {}
+    with contextlib.suppress(OSError):
+        with open(version_file_path, encoding="utf-8") as version_file:
+            content = version_file.readlines()
+        nvrm_version = content[0].split(": ")[1].strip().split()
+        if "Open" in nvrm_version:
             return {
-                'nvrm': {
-                    'vendor': nvrm_version[0],
-                    'platform': nvrm_version[1],
-                    'arch': nvrm_version[6],
-                    'version': nvrm_version[7]
+                "nvrm": {
+                    "vendor": nvrm_version[0],
+                    "platform": nvrm_version[1],
+                    "arch": nvrm_version[6],
+                    "version": nvrm_version[7],
                 }
             }
         return {
-            'nvrm': {
-                'vendor': nvrm_version[0],
-                'platform': nvrm_version[1],
-                'arch': nvrm_version[2],
-                'version': nvrm_version[5],
-                'date': ' '.join(nvrm_version[6:])
+            "nvrm": {
+                "vendor": nvrm_version[0],
+                "platform": nvrm_version[1],
+                "arch": nvrm_version[2],
+                "version": nvrm_version[5],
+                "date": " ".join(nvrm_version[6:]),
             }
         }
+    # If the /proc file failed, look it up with glxinfo.
+    glx_info = GlxInfo()
+    platform = read_process_output(["uname", "-s"])
+    arch = read_process_output(["uname", "-m"])
+    return {
+        "nvrm": {
+            "vendor": glx_info.opengl_vendor,  # type: ignore[attr-defined]
+            "platform": platform,
+            "arch": arch,
+            "version": glx_info.opengl_version.rsplit(maxsplit=1)[-1],  # type: ignore[attr-defined]
+        }
+    }
 
 
-def get_nvidia_gpu_ids():
+def get_nvidia_gpu_ids() -> List[str]:
     """Return the list of Nvidia GPUs"""
-    return os.listdir("/proc/driver/nvidia/gpus")
+    with contextlib.suppress(OSError):
+        return os.listdir("/proc/driver/nvidia/gpus")
+    values = read_process_output(
+        # 10de is NVIDIA's vendor ID, 0300 gets you video controllers.
+        ["lspci", "-D", "-n", "-d", "10de::0300"],
+    ).splitlines()
+    return [line.split(maxsplit=1)[0] for line in values]
 
 
-def get_nvidia_gpu_info(gpu_id):
+def get_nvidia_gpu_info(gpu_id: str) -> Dict[str, str]:
     """Return details about a GPU"""
-    with open("/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding='utf-8') as info_file:
-        content = info_file.readlines()
-    infos = {}
-    for line in content:
+    with contextlib.suppress(OSError):
+        with open(
+            "/proc/driver/nvidia/gpus/%s/information" % gpu_id, encoding="utf-8"
+        ) as info_file:
+            content = info_file.readlines()
+        infos = {}
+        for line in content:
+            key, value = line.split(":", 1)
+            infos[key] = value.strip()
+        return infos
+    lspci_data = read_process_output(["lspci", "-v", "-s", gpu_id])
+    model_info = re.search(r"NVIDIA Corporation \w+ \[(.+?)\]", lspci_data)
+    if model_info:
+        model = model_info.group(1)
+    irq_info = re.search("IRQ ([0-9]+)", lspci_data)
+    if irq_info:
+        irq = irq_info.group(1)
+
+    info = {
+        "Model": f"NVIDIA {model}",
+        "IRQ": irq,
+        "Bus Location": gpu_id,
+    }
+    for line in lspci_data.splitlines():
+        if ":" not in line:
+            continue
         key, value = line.split(":", 1)
-        infos[key] = value.strip()
-    return infos
+        info[key.strip()] = value.strip()
+    return info
 
 
-def is_nvidia():
+def is_nvidia() -> bool:
     """Return true if the Nvidia drivers are currently in use"""
-    return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
+        return os.path.exists("/proc/driver/nvidia")
+    with contextlib.suppress(OSError):
+        with open("/proc/modules") as f:
+            modules = f.read()
+        return bool(re.search(r"^nvidia ", modules, flags=re.MULTILINE))
+    glx_info = GlxInfo()
+    return "NVIDIA" in glx_info.opengl_vendor  # type: ignore[attr-defined]
 
 
-def get_gpus():
+def get_gpus() -> Iterable[str]:
     """Return GPUs connected to the system"""
     if not os.path.exists("/sys/class/drm"):
         logger.error("No GPU available on this system!")
-        return
+        return []
     try:
         cardlist = os.listdir("/sys/class/drm/")
     except PermissionError:
-        logger.error("Your system does not allow reading from /sys/class/drm, no GPU detected.")
+        logger.error(
+            "Your system does not allow reading from /sys/class/drm, no GPU detected."
+        )
         return
path: lutris/util/graphics/drivers.py
text: @user1:
I think you mean 'return []' here, as on line 118, right?