A Rust port of the original switcheroo-control daemon and CLI tool by Bastien Nocera.
From a switcheroo-control man page:
switcherooctl is the command-line interface for switcheroo-control, a daemon that manages hybrid graphics on Linux laptops with multiple GPUs. It provides a simple way to list available graphics adapters and launch applications on a specific GPU.
On hybrid graphics systems with both integrated (power-efficient) and discrete (high-performance) GPUs, applications default to the integrated GPU. Using switcherooctl, you can run specific applications on the discrete GPU for better graphics performance.
This port uses the udev crate for GPU monitoring, combined with the nix crate for low-level ioctl driver queries (amdgpu, i915, nouveau, and xe drivers) to determine whether a card is discrete or integrated.
It reimplements the standard net.hadess.SwitcherooControl interface using the zbus crate.
switcherooctl provides a user-friendly CLI via the clap crate, including implicit subcommands which are also present in the original project. The daemon likewise uses clap for its own --replace flag.
Also, just like the original project, it includes a regex-based cleanup module to prettify GPU information before it is published over D-Bus
| Crate | Description |
|---|---|
switcheroo-daemon |
Background system service that monitors hardware and exposes it over D-Bus |
switcherooctl |
CLI utility used to list GPUs and launch binaries using a specific GPU |
switcheroo-common |
Shared types, serialization/deserialization logic and D-Bus definitions |
flowchart TB
User((User))
subgraph Userspace [Userspace Context]
CLI(switcherooctl)
Daemon(switcheroo-daemon)
DBus{{D-Bus System Bus}}
TargetApp[[Target Application]]
end
subgraph OS_Kernel [OS & Kernel Interface]
subgraph UdevSub [udev Subsystem]
Udev[udev crate]
LibUdev[libudev C FFI]
end
subgraph IOCTL_Sub [DRM Subsystem]
DRM_Nodes[(/dev/dri/render*)]
Drivers[/amdgpu, i915, xe, nouveau, nvidia/]
end
end
User -->|Runs| CLI
CLI -->|Queries GPUs| DBus
DBus <-->|net.hadess.SwitcherooControl| Daemon
DBus -->|GPU info & env| CLI
CLI -.->|Spawns with env vars| TargetApp
Daemon -->|Queries GPUs & watch events| Udev
Udev -->|GPUs info & events| Daemon
Daemon -->|switcheroo-discrete-gpu tag check| LibUdev
LibUdev -->|is_discrete| Daemon
Daemon -->|IOCTL probe| DRM_Nodes
DRM_Nodes -->|VRAM / LMEM status| Daemon
DRM_Nodes --- Drivers
TargetApp -.->|Render offload| Drivers
Ensure you have the libudev library installed where it can be found by pkg-config.
Example:
sudo apt-get install libudev-dev # Debian-based Linux distributions
sudo zypper install systemd-devel # openSUSE/SLE
sudo dnf install systemd-devel # Fedora/RHELYou can either compile a release build using Cargo:
git clone https://github.com/LuMarans30/switcheroo-control-rs
cd switcheroo-control-rs
cargo build --releaseOR
Install them from crates.io:
cargo install switcherooctl switcheroo-daemonYou can install the provided Systemd and D-Bus configuration files to automatically start the daemon on boot as a service:
sudo cp ./data/switcheroo-daemon.service /usr/lib/systemd/system/
sudo cp ./data/net.hadess.SwitcherooControl.conf /usr/share/dbus-1/system.d/
sudo cp ./target/release/switcheroo-daemon /usr/libexec
sudo systemctl daemon-reload
sudo systemctl enable --now switcheroo-daemonTo test the daemon manually, stop the original system service first to avoid D-Bus conflicts:
sudo systemctl stop switcheroo-control
sudo env "PATH=$PATH" switcheroo-daemon # or sudo ./target/release/switcheroo-daemonTip
The daemon supports a --replace flag to replace an already running instance.
Without it, the daemon will exit immediately if another instance holds the bus name.
You can now use the CLI tool:
switcherooctl helpUsage: switcherooctl [OPTIONS] [COMMAND]
Commands:
list List the known GPUs
launch Launch a command on a specific GPU
help Print this message or the help of the given subcommand(s)
Options:
-g, --gpu <GPU>
-h, --help Print helpLaunch glmark2's refract benchmark on a discrete GPU:
switcherooctl launch --gpu 1 glmark2 -b refractFor simplicity, you can also omit subcommands:
switcherooctl # "switcherooctl list"
switcherooctl <program> # "switcherooctl launch <program>" The Rust CLI shows significant performance gains over the Python CLI by eliminating the interpreter startup overhead. The Rust daemon has roughly the same performance and memory footprint as the original C daemon.
Below are some benchmarks using hyperfine.
To demonstrate full interoperability, I tested the clients against both daemons.
List GPUs:
~/projects/switcheroo-control-rs > hyperfine --warmup 10 -N \
'switcherooctl list' \
'./target/release/switcherooctl list'
Benchmark 1: switcherooctl list
Time (mean ± σ): 138.9 ms ± 34.0 ms [User: 111.2 ms, System: 26.4 ms]
Range (min … max): 104.5 ms … 227.5 ms 19 runs
Benchmark 2: ./target/release/switcherooctl list
Time (mean ± σ): 5.6 ms ± 1.1 ms [User: 1.7 ms, System: 5.7 ms]
Range (min … max): 3.4 ms … 11.1 ms 711 runs
Summary
./target/release/switcherooctl list ran
24.62 ± 7.74 times faster than switcherooctl listLaunch glxinfo:
~/projects/switcheroo-control-rs > hyperfine --warmup 10 --min-runs 50 -N \
'switcherooctl launch glxinfo -B' \
'./target/release/switcherooctl launch glxinfo -B'
Benchmark 1: switcherooctl launch glxinfo -B
Time (mean ± σ): 262.3 ms ± 31.8 ms [User: 122.3 ms, System: 133.1 ms]
Range (min … max): 221.5 ms … 364.4 ms 50 runs
Benchmark 2: ./target/release/switcherooctl launch glxinfo -B
Time (mean ± σ): 146.0 ms ± 12.6 ms [User: 34.0 ms, System: 107.4 ms]
Range (min … max): 123.0 ms … 172.8 ms 50 runs
Summary
./target/release/switcherooctl launch glxinfo -B ran
1.80 ± 0.27 times faster than switcherooctl launch glxinfo -BList GPUs:
~/projects/switcheroo-control-rs > hyperfine --warmup 10 -N \
'switcherooctl list' \
'./target/release/switcherooctl list'
Benchmark 1: switcherooctl list
Time (mean ± σ): 153.8 ms ± 45.9 ms [User: 120.3 ms, System: 32.2 ms]
Range (min … max): 102.7 ms … 236.2 ms 27 runs
Benchmark 2: ./target/release/switcherooctl list
Time (mean ± σ): 5.9 ms ± 1.1 ms [User: 1.8 ms, System: 5.9 ms]
Range (min … max): 3.5 ms … 12.6 ms 478 runs
Summary
./target/release/switcherooctl list ran
26.01 ± 9.07 times faster than switcherooctl listLaunch glxinfo:
~/projects/switcheroo-control-rs > hyperfine --warmup 10 --min-runs 50 -N \
'switcherooctl launch glxinfo -B' \
'./target/release/switcherooctl launch glxinfo -B'
Benchmark 1: switcherooctl launch glxinfo -B
Time (mean ± σ): 270.2 ms ± 40.9 ms [User: 130.1 ms, System: 133.1 ms]
Range (min … max): 214.3 ms … 359.0 ms 50 runs
Benchmark 2: ./target/release/switcherooctl launch glxinfo -B
Time (mean ± σ): 142.1 ms ± 11.3 ms [User: 31.2 ms, System: 106.4 ms]
Range (min … max): 119.5 ms … 177.0 ms 50 runs
Summary
./target/release/switcherooctl launch glxinfo -B ran
1.90 ± 0.33 times faster than switcherooctl launch glxinfo -BThe Rust CLI also has a much smaller memory footprint:
~/projects/switcheroo-control-rs > /usr/bin/time -v ./target/release/switcherooctl list 2>&1 | grep "Maximum resident set size"
Maximum resident set size (kbytes): 6024
~/projects/switcheroo-control-rs > /usr/bin/time -v switcherooctl list 2>&1 | grep "Maximum resident set size"
Maximum resident set size (kbytes): 31740The daemons have roughly the same memory footprint:
~/projects/switcheroo-control-rs > ps -o pid,rss,comm -C switcheroo-control
PID RSS COMMAND
117108 9056 switcheroo-cont
~/projects/switcheroo-control-rs > ps -o pid,rss,comm -C switcheroo-daemon
PID RSS COMMAND
133147 9380 switcheroo-daemThe project is licensed under GPL-3.0-or-later to match upstream.