Omahub
← All plugins
R

GPU Selecta

by rufussed

Global and per-app GPU renderer switching for multi-GPU systems (AMD, Intel, NVIDIA), via PRIME render offload.

Security review

Potentially dangerous behavior detected · 1 finding

Deterministic scan — not a security guarantee

High
Risk level
High
Analyzed commit
4c36102
Scanned
3 weeks ago

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
4c36102
Reviewed
3 weeks ago

The deterministic 'high' finding is a false positive: the flagged systemd-run/systemctl strings appear only in a denylist of commands that must not be shimmed, not as actual persistence. The plugin does install shell hooks and rewrite .desktop files, but these are documented, user-triggered features with backups and tests. No obfuscation, credential theft, or destructive behavior was found in the sampled code.

  • The flagged 'persistence' finding is a false positive; systemd-run/systemctl appear only in SHIM_DENYLIST.
  • Selecting a global renderer installs Bash/Zsh/Fish prompt hooks and prepends a shim directory to PATH; this is persistent and user-visible, but only happens after explicit user action.
  • Per-app pinning rewrites ~/.local/share/applications/*.desktop and creates wrapper scripts; uninstall does not auto-revert, though the README documents manual cleanup and backups are kept.
  • No obfuscation, network access, credential handling, or destructive commands were found in the sampled code.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/Rufussed/gpu-selecta --enable
Hardware #bar

GPU Selecta

GPU Selecta is an Omarchy plugin dashboard for Linux systems with multiple GPUs—and vinyl scratch vibes. Set the default GPU for newly launched applications, or choose which GPU individual apps use. Graphics-heavy games and 3D software such as Blender or Unreal Engine can run on the most powerful GPU while everything else stays on the lower-powered integrated GPU. Keep your system cool and let it rip when it needs to.

Inspiration: GPU Selecta began with OmaGPU by ucmz851 as its starting point. OmaGPU's GPU discovery, telemetry, and hardware-control approach provided the foundation that GPU Selecta expanded with global and per-app render routing.

<p align="top"> <img src="assets/GPU-Selecta.webp" width="49%" alt="GPU Selecta overview showing global GPU selection and live telemetry"> <img src="assets/GPU-Selecta-Apps.webp" width="49%" alt="GPU Selecta apps panel showing per-application GPU selection"> </p> <p align="center"><em>Global GPU selection and telemetry · Per-application GPU routing</em></p>

Renderer switching uses stable PCI addresses with Mesa's DRI_PRIME support for AMD, Intel, and Nouveau GPUs. Proprietary NVIDIA GPUs use NVIDIA PRIME render offload. Other detected GPUs still appear in the dashboard, but GPU Selecta will not offer routing controls when the installed driver cannot select them safely.

What this is (and isn't)

Terminal launches (Bash, Zsh, and Fish)

Selecting a global renderer installs small Bash, Zsh, and Fish startup/prompt hooks. They refresh the five GPU routing variables from the saved default before each prompt, so terminals launch apps on the selected renderer. Existing prompt hooks and unrelated environment settings are preserved.

Per-app pins are enforced for terminal launches too, via a shim directory placed ahead of the real binaries on PATH. Typing a bare blender finds a small generated script of that name first, which applies that app's pinned GPU and then execs the real binary, so the pin wins over the global default. Because it execs, there is no extra process in the tree: stdout, stderr, exit status, signals and argv[0] are all unchanged from an unshimmed launch.

Two limits are worth knowing. Invoking a full path (/usr/bin/blender) skips the PATH lookup entirely, so the shim never runs and the global default applies. And apps that launch through a shared wrapper command have no name of their own to shim, so their pins stay launcher-only, which the Apps tab states on the row rather than appearing to work. That covers every Flatpak app (flatpak run <app-id>) and individual Steam games (steam steam://rungameid/<id>, where shimming steam would drag the client and every other title onto one game's GPU). Pinning the Steam client itself is still enforced in a terminal.

To enable this on an existing installation without changing GPU selection:

python3 scripts/gpu_switch_engine.py --install-bash-integration

Open a new shell, or run source ~/.config/omarchy/gpu-switch/gpu-default.bash in an existing one. After changing GPU selection while a shell is idle, press Enter before launching an app to refresh that prompt's environment. Running apps and existing non-shell processes (such as an already-running multiplexer server) retain their environment. Foot's persistent server also keeps its original environment; restart it after changing the default, or use ~/.config/omarchy/gpu-switch/gpu-selecta-run for direct commands, for example foot -e ~/.config/omarchy/gpu-switch/gpu-selecta-run blender. To remove the integration, delete the GPU Selecta source lines from .bashrc and .zshrc, remove ~/.config/fish/conf.d/gpu-selecta.fish, and delete ~/.config/omarchy/gpu-switch/shims/. The original .bashrc is backed up as ~/.config/omarchy/gpu-switch/bashrc.before-gpu-selecta on first install.

GPU Selecta is a renderer selector and monitoring dashboard, not a hardware GPU mux. On a muxless hybrid laptop, the integrated GPU continues to drive the display. The routing features choose which GPU newly launched apps render on:

  • Global toggle: sets PRIME offload environment variables for the whole session, applied live via Omarchy's Hyprland toggle mechanism — no restart or logout needed, but it only affects apps launched after you flip it.
  • Per-app pinning: rewrites a specific app's .desktop launcher entry (the standard XDG override mechanism — this is the same technique GNOME's own "Launch using Discrete Graphics Card" uses) so that app always renders on the GPU you choose, regardless of the global toggle. The full list of installed apps is scanned automatically — nothing is pre-pinned; every app starts at "Default" (follow the global toggle) until you change it.

Per-app pinning is the more power-efficient option for apps you use regularly: the discrete GPU only wakes while that specific app is open, rather than for an entire session (or, worse, for literally everything if the global toggle is left on).

Neither routing mechanism can affect a process that's already running — env vars only apply at process launch.

Install

omarchy plugin add https://github.com/Rufussed/gpu-selecta.git --enable

If the widget doesn't appear, restart the shell once:

omarchy restart shell

From a local checkout (development)

ln -s "$PWD" ~/.config/omarchy/plugins/rufussed.gpu-switch
omarchy-shell shell rescanPlugins

QuickShell's file watcher doesn't follow symlinks, so after edits run omarchy restart shell.

Remove

omarchy plugin remove rufussed.gpu-switch

This unregisters the widget and removes its checkout. It does not automatically revert any per-app .desktop overrides or the global toggle file — reset each pinned app back to Default and turn the global toggle back to Integrated from the panel before removing the plugin, so nothing is left pinned to a GPU with no way to change it back. (If you forget: delete ~/.local/state/omarchy/toggles/hypr/gpu-switch-render-default.lua for the global toggle, and any ~/.local/share/applications/<app>.desktop file that starts with # Written by the GPU Switch plugin for per-app overrides, plus ~/.config/omarchy/gpu-switch/wrappers/ and ~/.config/omarchy/gpu-switch/shims/ — originals are preserved in ~/.config/omarchy/gpu-switch/backups/ if you want to restore them by hand.)

Dependencies

python3, hyprctl, lspci, and mpv are present on a standard Omarchy install. mpv plays the vinyl scratch button sounds. nvidia-smi is used opportunistically for NVIDIA telemetry and persistence mode when present. No external services are required.

Use

  • Click the bar icon to open the panel.
  • Overview tab: the Global Default toggle (Integrated / Discrete, labeled with whatever vendor is actually detected — e.g. "Integrated (AMD)" / "Discrete (NVIDIA)") plus a live card per GPU with theme-aware telemetry bars for temperature, busy %, power draw, and VRAM usage. Each card also lists the busiest readable GPU processes with per-process graphics/compute load and VRAM usage. Desktop infrastructure such as Hyprland is marked as a read-only system process.
    • Each GPU card has a Manage button (top right) that reveals basic Power Governor (Auto/High/Low/Peak) and Fan (Auto/35%/60%/ 80%/100%) controls, when the hardware actually exposes them over sysfs — an honest "not supported" note otherwise (e.g. NVIDIA laptop GPUs never expose fan control on Linux; many integrated GPUs have no fan node at all since the fan is EC-controlled).
  • Apps tab: every installed app, auto-discovered and filterable, each with AMD / Default / NVIDIA pills. Default means "follow the Overview tab's global toggle."

How it works

Global toggle writes a small file to ~/.local/state/omarchy/toggles/hypr/gpu-switch-render-default.lua using Omarchy's own Hyprland toggle-file convention — hl.env() calls there are re-applied on every hyprctl reload, which is what makes this take effect live.

Per-app pinning writes an override to ~/.local/share/applications/<app>.desktop where every Exec= line points at a small generated wrapper script in ~/.config/omarchy/gpu-switch/wrappers/ that exports (or unsets) the PRIME offload env vars and then execs the real binary, e.g.:

#!/bin/sh
export __NV_PRIME_RENDER_OFFLOAD=1
export __GLX_VENDOR_LIBRARY_NAME=nvidia
export __VK_LAYER_NV_optimus=NVIDIA_only
exec /usr/bin/chromium "$@"

A wrapper is used instead of prefixing Exec= directly with env VAR=val ... because some launchers only look at the first whitespace-delimited token of Exec= to find the real executable (Omarchy's own browser-open shortcut does this) — with an env ... prefix that token is literally env, which such a launcher then runs with no command instead of the app. Routing through a wrapper keeps that first token a real, directly-executable path, so both naive and fully spec-compliant launchers work. Any %f/%F/%u/%U field code stays in the visible Exec= line, after the wrapper path, so argument/URL substitution still works normally. TryExec= is left untouched, since the desktop entry spec requires it to stay a bare executable path. The original file is snapshotted to ~/.config/omarchy/gpu-switch/backups/ the first time an app is touched, so resetting an app to Default restores it exactly (or removes the override entirely, revealing the system default, if one exists) and removes that app's wrapper scripts.

This covers apps launched through a .desktop entry — the app grid, Omarchy's menu, launchers like Walker/fuzzel.

Terminal enforcement covers the other route. Pinning an app also writes a shim into ~/.config/omarchy/gpu-switch/shims/, named after the app's real program as found in its Exec= line, which the shell hooks put at the front of PATH:

#!/bin/sh
# Written by the GPU Switch plugin — per-app terminal GPU shim
# app: blender
export __NV_PRIME_RENDER_OFFLOAD=1
export __GLX_VENDOR_LIBRARY_NAME=nvidia
export __VK_LAYER_NV_optimus=NVIDIA_only
export LIBVA_DRIVER_NAME=nvidia
exec /usr/bin/blender "$@"

The whole directory is regenerated from apps.json on every pin change, so resetting an app to Default removes its shim and nothing goes stale. Files in there that GPU Selecta didn't write are never deleted. A shim is by command name, so it also catches that program started by name from a script, not only from an interactive prompt. Shells, interpreters, privilege helpers and sandbox launchers (sh, python, sudo, flatpak, gamescope and similar) are never shimmed — their Exec= first token says nothing about which app is starting, so a shim there would reroute unrelated programs. If two pinned apps resolve to the same command name on different GPUs, the first pin stands and the clash is reported rather than silently overwritten.

To regenerate the shims by hand:

python3 scripts/gpu_switch_engine.py --rebuild-shims

Power governor / fan control write directly to the same amdgpu sysfs nodes tools like LACT use (power_dpm_force_performance_level, hwmon/pwm1), falling back to pkexec if your user doesn't already have write permission there. NVIDIA fan control isn't offered — Linux has no supported path for it on laptop GPUs, on any driver.

Config

Per-app state lives in ~/.config/omarchy/gpu-switch/apps.json if you'd rather edit it directly:

{
  "blender": { "label": "Blender", "gpu": "nvidia", "desktopFiles": ["blender.desktop"] }
}

Any app not listed here defaults to "auto" (follow the global toggle) — this file only needs entries for apps you've actually pinned.

The learned fallback power maximum lives in ~/.local/state/omarchy/gpu-selecta/telemetry-extrema.json. Delete that file to reset it. Temperature uses the GPU's reported critical temperature when available and a 100°C fallback otherwise.

Security

GPU/app telemetry is 100% unprivileged reads (sysfs, .desktop files, same-user /proc/<pid>/fdinfo DRM counters, and nvidia-smi when present). Processes owned by other users remain hidden when /proc permissions do not allow them to be read. Render routing (global toggle, per-app pinning) only ever writes to files already owned by your user account (~/.config, ~/.local/share/applications, ~/.local/state) — no root or pkexec needed for those. Power governor and fan control do write to sysfs and may prompt for pkexec authentication if your udev rules don't already grant write access there, same as any other GPU tuning tool.

Credits

GPU vendor/driver detection and the power governor/fan sysfs handling follow the same approach as OmaGPU by ucmz851 (MIT licensed) — LACT-inspired reads of power_dpm_force_performance_level and hwmon. OmaGPU remains the more complete tuning/telemetry dashboard if that's all you need; this plugin's focus is GPU launch routing, with basic tuning included for convenience on hybrid systems.

License

MIT