Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Cardwire is a GPU manager for Linux laptops and multi-GPU desktops. It allows users to block the GPU of their choice, and to route applications on a specific GPU.

Why Cardwire?

Traditional GPU managers for Linux (like envycontrol, optimus-manager, supergfxctl) often require system restarts or display manager logouts, and were made with laptops in mind. And other built-in tools (like switcheroo-control) are great for launching apps but don’t actively protect the dedicated GPU from being woken up, cannot force an application to run on a specific GPU.

Cardwire solves this by using eBPF and LSM to dynamically block access to the GPU (more info about those here). This ensures the GPU is blocked at a userspace level.

Furthermore, unlike older managers, Cardwire never unbinds PCI devices or unload kernel drivers. Unbinding drivers on the fly is notoriously unstable and is a frequent cause of crashes on AMD GPUs or system deadlocks on NVIDIA GPUs. Cardwire’s eBPF approach is entirely seamless and significantly more stable.

Modes

Cardwire provides several GPU management modes:

  • Integrated mode - Block applications from accessing dedicated GPU, leaving only the iGPU available

  • Hybrid mode - Removes the blocks, letting the system function normally with all GPUs available.

  • Manual mode - Allows users to manually block or unblock individual GPUs by ID for granular control. It is only available on desktop systems and never blocks the default GPU.

  • Smart mode - Like integrated mode it blocks the dGPU by default, but a userspace analyzer inspects each application at launch and selectively allows GPU access for approved applications.

Warning

Integrated and Smart mode are only available for laptops, desktop/multi-gpus have access to Hybrid & Manual

Switching between modes is fast and does not require reboots or logouts.

Caution

Cardwire is in an early development stage, expect breaking changes.

Getting Started

To get started with cardwire, please take a look at the requirements to make sure your system is supported and configured, then head over to the installation instructions.

Requirements

To run Cardwire, your system needs to meet a few core requirements. Good news: if you are using a modern Linux distribution, you likely already meet all of these out of the box!

1. Supported Distributions (Kernel & System)

Cardwire requires:

  • Linux Kernel 5.8 or later (with CONFIG_BPF_LSM enabled).
  • systemd as the init system.

Tip

The following distributions are known to work out of the box with zero manual configuration required:

  • OGC Distros (Officially Supported): see ogc website
  • NixOS (Officially Supported)
  • Arch Linux / CachyOS
  • Fedora (and Atomic variants)
  • Debian/Ubuntu

If you are using one of these distributions, you can safely skip the advanced verification below and head straight to the Installation Guide.

Warning

Non-systemd distros are currently not supported. If you want to use Cardwire on a non-systemd distro, either open a PR with patches or configure the required services on your setup.

2. Display Server

Caution

Cardwire only supports Wayland. X11 is unsupported.


Advanced: Manual Kernel Verification

If you are not using a distribution listed above, or if you are compiling your own kernel, you will need to manually verify that eBPF LSM is enabled.

1. Verify CONFIG_BPF_LSM is enabled

On e.g. Ubuntu/Fedora:

grep CONFIG_BPF_LSM /boot/config-$(uname -r)

On other distros possibly:

zcat /proc/config.gz | grep CONFIG_BPF_LSM

Returns CONFIG_BPF_LSM=y if it’s enabled.

2. Verify BPF is in the boot cmdline

Check your current boot parameters:

cat /proc/cmdline | tr ' ' '\n'|grep lsm

Alternative methods:

grep CONFIG_LSM= /boot/config-$(uname -r)

or

zcat /proc/config.gz | grep CONFIG_LSM=

Outputs e.g. lsm=landlock,yama,apparmor,bpf or CONFIG_LSM="landlock,lockdown,yama,integrity,apparmor,bpf". If it contains ‘bpf’, bpf is already enabled and usable in your system!

3. Verify BPF LSM is active at runtime

The cardwire daemon refuses to start without this. Check the list of active LSMs:

cat /sys/kernel/security/lsm

Must contain bpf. If it does, eBPF LSM is ready even if the boot cmdline looks different.

Enabling BPF LSM (with GRUB)

If bpf is not in your boot cmdline, edit /etc/default/grub and append bpf to GRUB_CMDLINE_LINUX_DEFAULT, keeping all existing entries:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash lsm=landlock,lockdown,yama,integrity,apparmor,bpf"

Important

Do not set lsm=bpf alone, that drops other active security policies. Always append bpf to the existing list from the command above.

Apply and reboot:

DistroCommand
Ubuntusudo update-grub
Fedorasudo grub2-mkconfig -o /boot/grub2/grub.cfg
Archsudo grub-mkconfig -o /boot/grub/grub.cfg
sudo reboot

Installation

Arch/CachyOS/Arch-based

The official Open Gaming Collective Arch packages are available here: ogc-arch-packaging

Alternatively, using the AUR:

yay -S cardwire

And start the service:

sudo systemctl enable cardwired --now

Nix

Using nixpkgs-unstable (recommended)

services.cardwired = {
    enable = true;
    settings = {
        auto_apply_gpu_state = true;
        experimental_nvidia_block = true;
        battery_auto_switch = true;
        battery_auto_switch_mode = "hybrid";
        external_display_auto_switch = false;
    };
}

Using the repo’s flake (This will be dropped when cardwire hits nixpkgs-26.11):

flake.nix:

cardwire = {
    url = "github:opengamingcollective/cardwire";
    inputs.nixpkgs.follows = "nixpkgs";
};

configuration.nix:

imports = [ inputs.cardwire.nixosModules.default ];

services.cardwire = {
 enable = true;
 settings = {
     auto_apply_gpu_state = true;
     experimental_nvidia_block = true;
     battery_auto_switch = true;
     battery_auto_switch_mode = "hybrid";
     external_display_auto_switch = false;
 };
};

Fedora/Fedora-based

Cardwire is officially distributed through Terra on Fedora systems

To install Terra, follow the instructions here: https://docs.terrapkg.com/usage/installing

Using Terra

sudo dnf install cardwire

And start the service:

sudo systemctl enable cardwired --now

Bazzite

Cardwire is installed out-of-the-box

Atomic Fedora-based

Cardwire is officially distributed through Terra on Fedora systems

To install Terra, follow the instructions here: https://docs.terrapkg.com/usage/installing

Using Terra

sudo rpm-ostree install cardwire

And start the service:

sudo systemctl enable cardwired --now

Note

Thanks to the Fyra Labs / Terra team for packaging and maintaining Cardwire on Fedora !!

AerynOS

Cardwire is available in the official AerynOS repositories.

sudo moss install cardwire

And start the service:

sudo systemctl enable cardwired --now

Ubuntu / Debian

A .deb package is available on the releases page. You can download and install it using apt.

sudo apt install ./cardwire_*.deb
sudo systemctl enable cardwired --now

Build from source (Alternative)

Install build dependencies:

sudo apt install libbpf-dev libudev-dev pkg-config libegl1-mesa-dev libvulkan-dev libglvnd-dev libwayland-dev libxkbcommon-dev libx11-dev libxcb1-dev libx11-xcb-dev

Install Rust (if not already installed):

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Install bpf-linker

cargo binstall bpf-linker

Then clone and build:

git clone https://github.com/OpenGamingCollective/cardwire.git

make build
sudo make install

And start the service:

sudo systemctl enable cardwired --now

Other distros

For now, other distros must clone the repo and use make to build and install Cardwire. You will also need to enable BPF LSM manually, see the Enabling BPF LSM (with GRUB) section above.

Build dependencies:

  • cargo (plus bpf-linker and a pinned nightly toolchain for the eBPF program)
  • libudev-dev
  • pkg-config
  • Vulkan, EGL, Wayland and X11 development packages (GUI build)
git clone https://github.com/OpenGamingCollective/cardwire.git

make build
sudo make install

And start the service:

sudo systemctl enable cardwired --now

Usage

Querying GPUs

To have cardwire list all detected GPUs, use:

cardwire list

For each detected GPU, the command will return:

  • An identifier (ID). These are used for manual blocking and unblocking.
  • The GPU’s name (NAME)
  • The GPU’s PCI address (PCI)
  • The associated render node (RENDER)
  • The associated device node (CARD)
  • Whether the GPU has been identified as the default GPU (DEFAULT). Default GPUs will remain available when cardwire is set to integrated.
  • Whether the GPU is a discrete GPU (DISCRETE)
  • Whether the GPU is currently blocked (BLOCKED)

--json prints the full device map as JSON. --full prints the full pci device map as JSON.

Example:

$ cardwire list
ID  NAME                                         PCI           RENDER      CARD   DEFAULT  DISCRETE  BLOCKED
--  -------------------------------------------  ------------  ----------  -----  -------  --------  -------
0   Rembrandt [Radeon 680M]                      0000:07:00.0  renderD129  card2  (*)      ( )       false
1   Navi 23 [Radeon RX 6650 XT / 6700S / 6800S]  0000:03:00.0  renderD128  card1  ( )      (*)       true

Mode switching

To print the current mode:

cardwire get

GPU modes can be switched using the cardwire set command.

Integrated

To have cardwire block the dGPU, use:

cardwire set integrated

Note

The block only applies to new launched apps. Apps that are already running will keep using the GPU until you restart them. Restarting can help.

Tip

The dedicated GPU can still power down even if your desktop has it open, as long as nothing is actively using it. To double-check, run: cardwire gpu 1 --lsof

Hybrid

To have cardwire allow access to all GPUs, use

cardwire set hybrid

Smart Mode

Smart mode blocks the dedicated GPU by default like integrated mode, but uses a real-time analyzer to scan each application at launch and selectively allow GPU access for approved apps.

Cardwire natively integrates with desktop environments (GNOME, KDE) via a Switcheroo DBus shim. This means you can simply right-click an application in your app launcher and select “Launch using Discrete Graphics Card”, and Cardwire will automatically unblock the GPU for that application.

Tip

When an application is launched via the Switcheroo UI or with CARDWIRE_FORCE_DGPU=1, Cardwire will hide the integrated GPU (iGPU) from the app. The app will only be able to see and use the dedicated GPU, guaranteeing it runs on the correct hardware.

When launching apps in Smart mode, cardwire checks for the following to allow the dGPU:

  • CARDWIRE_ALLOW=1 env var (highest priority, unblocks the GPU but doesn’t force the app to use it)
  • CARDWIRE_FORCE_DGPU=1 env var (unblocks the GPU, forces the app to use it, and completely hides the iGPU)
  • CARDWIRE_FORCE_GPU=<gpu_id> env var (unblocks a specific GPU and forces the app to use it)
  • Steam games, identified by SteamAppId, are discovered into the internal application list and blocked by default until allowed
  • The per-app policies stored in cardwire’s internal application list

Note

The former auto-approval inputs are deprecated in favor of the internal application list. Steam auto-allow, PrefersNonDefaultGpu desktop entries and the automatic approval of GPU environment variables (DRI_PRIME, __NV_PRIME_RENDER_OFFLOAD) are no longer evaluated.

cardwire set smart

Note

This feature is a work in progress. The detection methods will be improved in future updates.

Manual

Important

To prevent system breakage, cardwire will not block the default GPU, even when explicitly instructed to do so.

If more granular control over several GPUs is required, cardwire also allows manually blocking individual GPUs by ID. To do so, it needs to be set to manual mode:

cardwire set manual

Once set to manual, GPU states can then be set by ID. To find the correct ID, see Querying GPUs.

To block the GPU with ID 1:

cardwire gpu 1 --block

To unblock:

cardwire gpu 1 --unblock

Launching apps on a specific GPU

cardwire launch starts a program with the right GPU environment set, without switching modes:

cardwire launch --gpu 1 nvtop
cardwire launch glxgears

Without --gpu, cardwire picks the best GPU for the job, in this order: a discrete non-default GPU, a discrete GPU, the default GPU, then the first available one. The command fetches the launch environment from the daemon.

Launching on a blocked or unavailable GPU is refused with a clear error, switch to Smart mode or unblock the GPU first.

System information

To check that the daemon is running:

cardwire manager status

To refresh the GPU list held by the daemon (useful after a hotplug event):

cardwire debug refresh-gpu

To check the power state of a GPU (for example whether it reached D3Cold):

cardwire gpu 1 --power

Configuration

Experimental Nvidia Block

Note

This setting is experimental because it tells cardwire to block specific Nvidia files, such as /dev/nvidiactl, that can be shared across multiple Nvidia GPUs. For this reason, it only works reliably on systems with exactly two GPUs: one integrated GPU and one dedicated Nvidia GPU.

Tip

Even though it is experimental, enabling this setting is recommended. It helps prevent unwanted GPU wakeups from Vulkan apps (GTK on gnome) and from tools that use /dev/nvidiactl, such as nvtop

To get if experimental Nvidia block is enabled:

cardwire config experimental-nvidia-block

To enable/disable it:

cardwire config experimental-nvidia-block true

Battery Auto Switch Mode

Cardwire can automatically switch GPU modes when the system switches between battery and AC power. When battery_auto_switch is enabled, cardwire switches to integrated mode on battery and back to a configurable mode when on AC power.

To get if battery auto switch is enabled:

cardwire config battery-auto-switch

To enable/disable it:

cardwire config battery-auto-switch true

The mode cardwire switches to on AC power is controlled by battery_auto_switch_mode. This can be set to integrated, hybrid, manual, or smart.

To get the current battery auto switch mode:

cardwire config battery-auto-switch-mode

To set the battery auto switch mode:

cardwire config battery-auto-switch-mode hybrid

External Display Auto Switch

When an external display is connected to a port wired directly to the dedicated GPU, Cardwire can temporarily switch Integrated and Smart modes to Hybrid. It restores the requested mode after the display is disconnected.

This feature is disabled by default. To get if external display auto switch is enabled:

cardwire config external-display-auto-switch

To enable/disable it:

cardwire config external-display-auto-switch true

Auto Apply Gpu State

When you switch back to manual mode, this setting automatically restores the GPU states you had set before. These saved states are stored in /var/lib/cardwire/gpu_state.json

To view the current saved states, run:

cat /var/lib/cardwire/gpu_state.json

Example output:

{
  "0000:03:00.0": {
    "block": false
  },
  "0000:07:00.0": {
    "block": false
  }
}

In this example, both GPUs are set to allow access (block: false) when manual mode is restored

To get the current setting:

cardwire config auto-apply-gpu-state

To set the setting:

cardwire config auto-apply-gpu-state true

Troubleshooting

Name is not activable

Is the daemon running?

systemctl status cardwired.service

If it’s not running, enable the daemon with systemctl enable cardwired.service and reboot your device.

dGPU is detected as the default gpu

On ROG laptop

is the asus MUX enabled?

asusctl armoury list

then find

gpu_mux_mode:
  current: [(0),1]

0 means that the MUX is disabled, the dGPU IS the default GPU in this case

To enable it:

asusctl armoury set gpu_mux_mode 1

A reboot is required for the change to take effect.

Non ROG Laptop

This shouldn’t happen, please create an issue with the output of

ls /sys/class/drm

and

cat /sys/class/drm/*/status

nvidia-powerd failure after switching modes

When switching to integrated mode on NVIDIA hardware, you may see errors or failures related to the nvidia-powerd service. This is a known quirk caused by the GPU entering D3Cold (a deep sleep state) which prevents nvidia-powerd from communicating with it.

Since v0.12.0, cardwired restarts nvidia-powerd automatically after every mode change (only when the service is enabled), so this is usually fixed without any action. If the problem persists, restart it manually:

sudo systemctl restart nvidia-powerd.service

Note

This was fixed in cardwire 0.12.1, cardwired now stop and start nvidia-powerd on mode switch instead of a naive restart

Sleep

How to diagnose a dGPU that won’t sleep

Your dGPU won’t sleep? Here’s how to find and fix the issue.

Check your NVIDIA GPU power information

Before this, please set cardwire to unblock your dGPU.

Replace the PCI with yours.

cat /proc/driver/nvidia/gpus/0000:01:00.0/power

Runtime D3 status:          Enabled (fine-grained)
Video Memory:               Off

GPU Hardware Support:
 Video Memory Self Refresh: Supported
 Video Memory Off:          Supported

S0ix Power Management:
 Platform Support:          Supported
 Status:                    Enabled

Notebook Dynamic Boost:     Not Supported

The most important section should be Runtime D3 status.

If Runtime D3 status is disabled, your GPU will never sleep.

To enable it, follow this method (only tested on Arch, please adapt it for other distros):

Caution

If you lack the knowledge, or you fear you will break your system, you can always make a post on the Discord to get assistance.

Go to https://gitlab.com/asus-linux/nvidia-laptop-power-cfg.

We will need two files:

  • nvidia.rules
  • nvidia.conf

You will need to copy them to their respective directory: For nvidia.conf:

/etc/modprobe.d/nvidia.conf

For nvidia.rules:

/usr/lib/udev/rules.d/80-nvidia-pm.rules

Once it’s done, execute:

sudo mkinitcpio -P

and restart your computer.

RTX 2000 Series

If it’s not working and you own an RTX 2000 GPU, it’s a known issue. You must use driver 580 and add NVreg_EnableGpuFirmware=0 to /etc/modprobe.d/nvidia.conf.

Check the PCI control value

cat /sys/bus/pci/devices/0000:03:00.0/power/control

If it says auto, you can skip this, if it says on/off, try this command to set the control to auto:

echo "auto" | sudo tee cat /sys/bus/pci/devices/0000:03:00.0/power/control

Packaging

This is a note for packagers trying to package cardwire

All cardwire crates can be built with rustc stable, excepted cardwire-ebpf.

RUSTC And BPF Target

cardwire-ebpf is built by cardwire-ebpf-userspace, and require either rustc nightly (BPF target is tier 3) or enabling the bpf target directly in rustc

With a rustc built with bpf enabled:

'RUSTC_BOOTSTRAP=1 cargo build -p cardwire-ebpf-userspace'

Should work

LLVM

Another issue that can happen while trying to build cardwire-ebpf-userspace is having bpf-linker errors.

Types of error i encountered:

memset error

This one happens when using a bpf-linker either statically linked to llvm <=22

❯ strings $(which bpf-linker) | grep -m3 -iE "LLVM version"
LLVM version 22.1.8

or dynamically linked:

test@archlinux ~> ldd $(which bpf-linker)
    linux-vdso.so.1 (0x00007f73bb2fd000)
    libLLVM.so.22.1 => /usr/lib/libLLVM.so.22.1 (0x00007f73b0a00000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f73bb2a7000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007f73b0600000)
    /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f73bb2ff000)
    libffi.so.8 => /usr/lib/libffi.so.8 (0x00007f73bb299000)
    libedit.so.0 => /usr/lib/libedit.so.0 (0x00007f73bb25d000)
    libz.so.1 => /usr/lib/libz.so.1 (0x00007f73bb240000)
    libzstd.so.1 => /usr/lib/libzstd.so.1 (0x00007f73baf1a000)
    libxml2.so.16 => /usr/lib/libxml2.so.16 (0x00007f73b08ca000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f73b0200000)
    libm.so.6 => /usr/lib/libm.so.6 (0x00007f73b00c9000)
    libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007f73baea9000)
    libicuuc.so.78 => /usr/lib/libicuuc.so.78 (0x00007f73afe00000)
    libicudata.so.78 => /usr/lib/libicudata.so.78 (0x00007f73ade00000)

To fix this issue, please use a bpf-linker that’s linked to LLVM 23

Smart

Goal and integration

Cardwire owns its per-application policy. It does not depend on desktop environment heuristics like PrefersNonDefaultGPU or on DRI_PRIME, __NV_PRIME_RENDER_OFFLOAD or SteamAppId being present in the app environment. Those auto-approval inputs were dropped in 0.12.0 and replaced by the internal application policy (Smart Policy), which makes cardwire self-sufficient while staying compatible with desktop environments through the Switcheroo shim.

Third parties that want to integrate with cardwire get three methods:

  • Env: the CARDWIRE_ environment variables is a way to route a process to a GPU, The per-GPU environment can be fetched from the Env property of the GPU API. The env will always have priority over the other methods.
  • PID via API: RequestProcessAccess directly insert the PID in the CW_ALLOWED_PID eBPF HashMap. call it with the pid right after spawning it, or apply it to a process that is already running (Caution, App often scan for GPUs at launch).
  • Smart Policy: As of 0.12.0, cardwire has its own Application Policy, the SmartPolicy interface lists known applications (GetAppPolicies), changes their persistent policy (SetAppPolicy) and announces discoveries (NewAppAdded).

Introduction

Having an integrated and hybrid mode is good, but what if we could have the best of both worlds?

This is what cardwire’s smart mode was made for. Cardwire uses a mix of kernel-space + userspace to directly allow processes on the fly.

Kernel-Space

Using the eBPF program and the tracepoint/sched/sched_process_exec hooks, the kernel program notifies cardwired when a new process is executed, sending its pid using the CW_EXEC_EVENTS RING_BUF (in Smart and Manual modes). Once the process is received by cardwired, it will be analyzed in real-time and its pid will be inserted into the CW_ALLOWED_PID map (value always 0) or the CW_FORCED_PID map (value is the GPU id)

When a process exits, the kernel’s tracepoint/sched/sched_process_exit removes the pid from both maps directly, preventing the maps from overflowing.

Userspace

It is responsible for making the actual decisions about whether a process is allowed to use a GPU. It is divided into three main components:

  • CardwireAnalyzer: A dedicated background task that listens to the CW_EXEC_EVENTS ring buffer (and the CW_REPORT_EVENTS ring for blocked-access logging). When it receives a new PID from the kernel eBPF, it invokes the analysis helpers. If the application passes, it populates the CW_ALLOWED_PID map (value always 0) or the CW_FORCED_PID map (value is the GPU id).
  • dynamic_analysis.rs: A set of helper functions used to analyze a process in real-time. By reading /proc/<pid>/environ and /proc/<pid>/cmdline, it checks for explicitly requested GPUs (like CARDWIRE_ALLOW=1, CARDWIRE_FORCE_DGPU=1, CARDWIRE_FORCE_GPU=<gpu_id>) or Steam games (SteamAppId), more to be added.
  • static_analysis.rs: A set of helper functions that analyze system data when the daemon starts. It scans the XDG data directories and watches them with inotify so new apps are picked up at install time. Every discovered app is blocked by default until the user allows it (will be changed in 0.13.0, with a toggleable setting).

Notes

Technically, it’s a pure race condition between the cardwire analyzer and the process, cardwire scans and allow a process in ~60-100 microseconds, from my testing, no process initialized its render before cardwire allowed it

Complete Execution Flow

Here is a comprehensive breakdown of how the Kernel and Userspace interact in real-time when an application launches: (Please zoom on it)

sequenceDiagram
    participant Proc as Process
    participant Kernel as eBPF Kernel Hooks
    participant Map as BPF Maps
    participant Daemon as CardwireAnalyzer (Userspace)

    Note over Proc,Daemon: 1. Process Launch
    Proc->>Kernel: sched_process_exec
    Kernel->>Map: Send PID via cw_exec_events (RingBuf)
    Map->Daemon: Listen to cw_exec_events and wait for new events

    Note over Daemon: 2. Real-time Analysis
    Daemon->>Daemon: Read /proc/<pid>/environ & cmdline
    Daemon->>Daemon: Check CARDWIRE_* env vars, Steam, XDG lists, SQLite policies

    alt Is Allowed?
        Daemon->>Map: Insert PID into cw_allowed_pid
    else Is Forced?
        Daemon->>Map: Insert PID into cw_forced_pid with the GPU id
    else Not Allowed
        Daemon->>Daemon: Do nothing
    end

    Note over Proc,Kernel: 3. GPU Access & Directory Listing
    Proc->>Kernel: getdents64 / file_open (/dev/dri/)
    Kernel->>Map: Check cw_allowed_pid and cw_forced_pid

    alt PID not in any map
        Kernel-->>Proc: hide GPU (Return -ENOENT)
        Kernel->>Daemon: Send block event (cw_report_events)
    else PID in cw_allowed_pid
        Kernel-->>Proc: Allow dGPU and iGPU
    else PID in cw_forced_pid (value = GPU id)
        Kernel-->>Proc: Allow the forced GPU, hide the others (-ENOENT)
    end

    Note over Proc,Daemon: 4. Application Exit
    Proc->>Kernel: sched_process_exit
    Kernel->>Kernel: Remove PID from cw_allowed_pid and cw_forced_pid

Application policies

Smart mode is only available on laptops (SystemType::Laptop). Per-application policies are stored in the app_policies table of the daemon’s SQLite database, with two values: Blocked and Allowed. Known apps are blocked by default until the user allows them, and newly discovered apps are announced through the NewAppAdded D-Bus signal.

The Forced policy will be added in 0.13.0

The policy for a process can be overridden at runtime through the org.opengamingcollective.cardwire.SmartPolicy D-Bus interface (RequestProcessAccess, GetProcessStatus, GetAppPolicies, SetAppPolicy). Note that GetProcessStatus returns an empty string (not "Default") for unclassified processes.

Force_GPU can be used on all systems with the Manual mode.

Building and Development

Building and Development

Using Nix

# Enter development shell
nix develop

# Build the project
nix build

# Run formatting checks
nix build .#checks.x86_64-linux.pre-commit-check

# Run integration tests in VM
nix build .#checks.x86_64-linux.vm-ci-2gpu
nix build .#checks.x86_64-linux.vm-ci-3gpu
nix build .#checks.x86_64-linux.vm-ci-15gpu

# Build the vm and enter
nix run .#nixosConfigurations.x86_64-linux.config.system.build.vm

Manual Compilation

If you don’t use Nix, ensure you have clang, libbpf (devel), libudev (devel), pkg-config and cargo installed (needed for eBPF compilation during the Rust build), plus bpf-linker and a pinned nightly toolchain (see cardwire-ebpf-userspace/build.rs). The GUI build additionally needs the Vulkan, EGL, Wayland and X11 development packages.

Formatting requires nightly rustfmt (the project uses nightly-only formatting options). Install it and run with:

rustup toolchain install nightly --component rustfmt
cargo +nightly fmt --all --check
# Build the project
make

# Install binaries, systemd service, D-Bus config, desktop file, icons and
# metainfo, and enable the systemd unit (requires sudo)
sudo make install

Project Structure

  • crates/cardwire-cli: User CLI to interact with the daemon
  • crates/cardwire-daemon: System daemon managing state and D-Bus communication
  • crates/cardwire-ebpf: BPF program and LSM hooks (never built directly, built by ebpf-userspace)
  • crates/cardwire-ebpf-userspace: Loads the BPF program, compiles it at build time
  • crates/cardwire-gui: The iced GUI and tray