An Omarchy bar widget showing whether the NVIDIA discrete GPU is
asleep or awake, plus gpuwho — a read-only CLI for seeing what is keeping
it awake.
On an Optimus laptop the dGPU should spend almost all its time powered off. When something quietly holds it awake you lose battery with no visible symptom. This makes that visible.
omarchy plugin add https://github.com/ReidenXerx/omarchy-dgpu-status.git --enableThe widget hides itself on machines with no NVIDIA dGPU.
| dGPU state | colour |
|---|---|
D3cold — deep sleep, powered off |
muted (nothing to see) |
D3hot — light sleep, still powered |
normal |
D0 active — in use |
urgent |
Hover for the sleep depth and asleep-percentage. Click to open gpuwho in a floating
terminal and see exactly which processes hold the GPU open.
Also usable directly (bin/gpuwho):
gpuwho list processes that hold the dGPU open
gpuwho status power state, runtime PM settings, asleep %
gpuwho sleep is it in deep sleep right now? (exit 0 = yes)
gpuwho watch [secs] log every wake and NAME the process that caused it
gpuwho why why it can wake with nothing running, and how to trace it
gpuwho help usage
gpuwho watch is the one worth knowing: it samples the power state and, the moment the GPU
wakes, records which processes hold /dev/nvidia*. GPUWHO_POLL=0.25 gpuwho watch samples
faster (0.1 to 60 seconds) to catch short-lived wakers.
21:33:17 D3cold (baseline)
21:33:21 D0 vulkaninfo(27933)
summary over 9s
wakes : 1
awake : 5s (55.6% of the window)
gpuwho only reports. It does not change which GPU an application uses, and it writes no
wrappers, launchers or configuration. (Earlier development versions had pin/allow
commands that did; they are not shipped.)
gpuwho watch names the process behind a wake when there is one. When there is none,
two root-only checks settle it. The plugin only documents them and never runs them.
Is something reading PCI config space? A desktop shell that probes hardware with
lspci on a loop wakes the card with no lasting process — the most common cause, and a
fixable one:
sudo auditctl -a always,exit -F path=/usr/bin/lspci -F perm=x -k lspciwake
sudo ausearch -k lspciwake # match the timestamps against gpuwho watch
sudo auditctl -d always,exit -F path=/usr/bin/lspci -F perm=x -k lspciwake # remove the ruleWhat exactly resumed the card? A tracepoint on runtime-PM resumes, with the kernel
call chain. Use the PCI address gpuwho status prints in its first line:
sudo sh -c 'T=/sys/kernel/tracing
echo 0 > $T/tracing_on; : > $T/trace
echo 1 > $T/events/rpm/rpm_resume/enable
echo "name ~ \"0000:01:00.0\"" > $T/events/rpm/rpm_resume/filter
echo 1 > $T/options/stacktrace; echo 1 > $T/tracing_on
sleep 300; cat $T/trace
echo 0 > $T/tracing_on
echo 0 > $T/events/rpm/rpm_resume/enable
echo 0 > $T/options/stacktrace
: > $T/events/rpm/rpm_resume/filter'The tracepoint fires inside the kernel, so unlike a /proc poller a short-lived process
cannot outrun it. The filter field is name, not dev_name: getting that wrong silently
traces every device instead of this one. On laptops with an NVPCF device (NVDA0820), a
trace ending in rm_acpi_nvpcf_notify is firmware negotiating power budget — expected,
and not fixable from userspace.
Everything here reads only power_state, the runtime PM attributes (power/runtime_status,
power/runtime_suspended_time, power/runtime_active_time, power/control),
d3cold_allowed and /proc.
config, current_link_speed and current_link_width are never read. Querying those
touches PCI config space or the live PCIe link and resumes the GPU — a status tool built
the obvious way manufactures the wakeups it claims to observe. The same applies to lspci
and nvidia-smi: both wake a sleeping card. If you extend this, keep to the attributes above.
gpuwho likewise scans the link targets in /proc/*/fd rather than opening /dev/nvidia*.
- The device is resolved through its bound driver — the entry of
/sys/bus/pci/drivers/nvidianamed like a PCI address whose link leads to the same device under/sys/devices/— not a hardcoded address, so it works on any machine. - Processes owned by other users are invisible without root.
- Writes nothing. Neither the widget nor
gpuwhocreates, edits or deletes files, and neither uses temporary files. The only writer is the opt-in menu installer below, which goes throughbin/plugin_safety.py: no symlinks followed, owner checks, a randomO_EXCLtemporary file in the same directory, atomic replace, size-capped reads. - No shell from QML, no process per poll. At startup the widget runs
/usr/bin/python3 bin/dgpu-status deviceonce (fixed argv, 3 s watchdog) to find the device directory, validates the answer, then reads the four attributes withFileViewevery 5 s. The helper runs again, with backoff, only if the device is missing. - Report button. Starts
/usr/bin/omarchy-launch-floating-terminal-with-presentationdetached, with the absolute path of the plugin'sgpuwho. That launcher hands its argument tobash -c, so the path is used only if it consists ofA-Z a-z 0-9 . _ / -, and is single-quoted as well. The terminal lives in its own scope and closes with the user. gpuwhosetsPATH=/usr/binandLC_ALL=C, accepts only its fixed subcommands (watchtakes 0–86400 seconds,GPUWHO_POLL0.1–60), reads sysfs through an attribute allowlist, runs its few external calls (find,grep) undertimeout, and prints process names and arguments with control characters stripped, so a process cannot inject terminal escape sequences through its name.- Tests:
python3 tests/dgpu_status_test.py.
Nothing beyond a stock Omarchy install and an NVIDIA card bound to the nvidia driver.
Optional Omarchy menu routes (power state, what is using it, watch, why):
bin/dgpu-status-menu-install # add them
bin/dgpu-status-menu-install remove # take them outIt writes only between its own marker comments in
~/.config/omarchy/extensions/omarchy-menu.jsonc and restores the previous content rather
than leaving that file unparseable.
bin/dgpu-status-menu-install remove
omarchy plugin remove reidenxerx.dgpu-statusThe widget keeps no state of its own.
MIT — see LICENSE.