Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci-vm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Integration Tests in a Nix VM
on:
pull_request:
types: [opened, labeled]
branches:
- main
- dev
paths:
- 'crates/**'
- 'workflows/**'
- 'nix/**'
jobs:
run-test:
if: ${{ github.event.label.name == 'run-vm-test' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: cachix/install-nix-action@v31
- name: Run Integration Tests with 2 GPUs
run: nix build -L .#checks.x86_64-linux.vm-ci-2gpu
- name: Run Integration Tests with 3 GPUs
run: nix build -L .#checks.x86_64-linux.vm-ci-3gpu
- name: Run Integration Tests with 15 GPUs
run: nix build -L .#checks.x86_64-linux.vm-ci-15gpu
19 changes: 0 additions & 19 deletions .github/workflows/test-vm.yml

This file was deleted.

25 changes: 20 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ members = [
]

[workspace.package]
version = "0.5.0"
version = "0.6.0"
authors = ["luytan <luytan@khora.me>"]
repository = "https://github.com/OpenGamingCollective/cardwire"
edition = "2024"
Expand All @@ -17,7 +17,7 @@ license = "GPL-3.0"

[workspace.dependencies]
tokio = { version = "1.52.1", features = ["full"] }
zbus = { version = "5.15.0", features = ["tokio"] }
zbus = { version = "5.15.0", features = ["tokio", "option-as-array"] }
clap = { version = "4.6.1", features = ["derive"] }
config = { version = "0.15.19", features = ["toml"] }
serde = { version = "1.0.228", features = ["derive"] }
Expand All @@ -29,6 +29,7 @@ env_logger = "0.11"
toml = "1.0.3"
aya = "0.13.1"
serde_json = "1.0.149"
tokio-stream = "0.1.18"

[profile.dev]
panic = "abort"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The daemon reads its configuration from `/etc/cardwire/cardwire.toml`.
# /etc/cardwire/cardwire.toml
auto_apply_gpu_state = true
block_nvidia_vulkan = false
battery_auto_switch = false
```

`block_nvidia_vulkan` is an experimental feature that blocks the nvidia's vulkan icd, must be used with caution
Expand Down
2 changes: 2 additions & 0 deletions assets/com.github.opengamingcollective.cardwire.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<busconfig>
<policy user="root">
<allow own="com.github.opengamingcollective.cardwire"/>
<allow receive_sender="com.github.opengamingcollective.cardwire"/>
<allow send_destination="com.github.opengamingcollective.cardwire"/>
</policy>
<policy group="sudo">
<allow send_destination="com.github.opengamingcollective.cardwire"/>
Expand Down
4 changes: 3 additions & 1 deletion crates/cardwire-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ license.workspace = true
description = "CLI for cardwire GPU management"

[dependencies]
cardwire-core = { path = "../cardwire-core" }
tokio.workspace = true
zbus.workspace = true
clap.workspace = true
clap_complete.workspace = true
anyhow.workspace = true
serde_json.workspace = true
serde.workspace = true

[[bin]]
name = "cardwire"
path = "src/main.rs"
11 changes: 10 additions & 1 deletion crates/cardwire-cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
use clap::{ArgAction, Args as ClapArgs, Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
use std::fmt;
#[derive(Clone, Debug, ValueEnum)]
pub enum CliMode {
Integrated,
Hybrid,
Manual,
}

impl fmt::Display for CliMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CliMode::Integrated => write!(f, "Integrated"),
CliMode::Hybrid => write!(f, "Hybrid"),
CliMode::Manual => write!(f, "Manual"),
}
}
}
#[derive(Parser)]
#[command(version, about)]
pub struct Args {
Expand Down
19 changes: 12 additions & 7 deletions crates/cardwire-cli/src/dbus.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use cardwire_core::gpu::GpuRow;
use crate::display::{GpuDevice, PciDevice};
use std::collections::BTreeMap;

use zbus::{Proxy, connection::Connection};
pub struct DaemonClient<'a> {
proxy: Proxy<'a>,
Expand All @@ -17,16 +19,19 @@ impl<'a> DaemonClient<'a> {
Ok(Self { proxy })
}

pub async fn set_mode(&self, mode: &String) -> zbus::Result<()> {
self.proxy.call("SetMode", &(mode,)).await
pub async fn set_mode(&self, mode: &u32) -> zbus::fdo::Result<()> {
self.proxy.set_property("Mode", mode).await
}

pub async fn get_mode(&self) -> zbus::Result<String> {
self.proxy.call("GetMode", &()).await
pub async fn get_mode(&self) -> zbus::Result<u32> {
self.proxy.get_property("Mode").await
}

pub async fn list_gpus(&self) -> zbus::Result<Vec<GpuRow>> {
self.proxy.call("ListGpus", &()).await
pub async fn list_devices(&self) -> zbus::Result<BTreeMap<usize, GpuDevice>> {
self.proxy.call("ListDevices", &()).await
}
pub async fn list_devices_pci(&self) -> zbus::Result<BTreeMap<String, PciDevice>> {
self.proxy.call("ListDevicesPci", &()).await
}

pub async fn set_gpu_block(&self, id: u32, blocked: bool) -> zbus::Result<()> {
Expand Down
121 changes: 121 additions & 0 deletions crates/cardwire-cli/src/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! The purpose of this file is to format the received String from daemon into a displayable format
//! for the user

use std::collections::BTreeMap;

use anyhow::{Ok, Result};
// Define the struct here instead of importing from cardwire_core,
// I want cardwire-cli to be independent of the rest of cardwire
// This allow other dev to make their own client for cardwire
// Here the struct are used to parse the json
#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type, Debug)]
pub struct GpuDevice {
id: u32,
name: String,
pci: String,
render: u32,
card: u32,
default: bool,
blocked: bool,
nvidia: bool,
nvidia_minor: String,
}
#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type)]
pub struct PciDevice {
pci_address: String,
iommu_group: String,
vendor_id: String,
device_id: String,
vendor_name: String,
device_name: String,
driver: String,
class: String,
}

/// Take a Map and print it
pub fn print_devices(gpu_list: BTreeMap<usize, GpuDevice>, is_json: bool) -> Result<()> {
if is_json {
println!("{}", serde_json::to_string_pretty(&gpu_list)?);
} else {
pretty_print_gpu(gpu_list);
};

Ok(())
}
/// Take a Map and print it
pub fn print_devices_pci(pci_list: BTreeMap<String, PciDevice>) -> Result<()> {
println!("{}", serde_json::to_string_pretty(&pci_list)?);
Ok(())
}
/// Take a Map and print it into a good looking table
fn pretty_print_gpu(gpu_list: BTreeMap<usize, GpuDevice>) {
let mut id_w = 2usize;
let mut name_w = 4usize;
let mut pci_w = 3usize;
let mut render_w = 6usize;
let mut card_w = 4usize;
let default_w = 7usize;
let blocked_w = 7usize;

// Calculate widths
for (id, gpu) in &gpu_list {
id_w = id_w.max(*id);
name_w = name_w.max(gpu.name.len());
pci_w = pci_w.max(gpu.pci.len());
// Full render string is "renderD" + device number
let render_full = format!("renderD{}", gpu.render);
render_w = render_w.max(render_full.len());
let card_full = format!("card{}", gpu.card);
card_w = card_w.max(card_full.len());
}

// Header
println!(
"{:<id_w$} {:<name_w$} {:<pci_w$} {:<render_w$} {:<card_w$} {:<default_w$} {:<blocked_w$}",
"ID",
"NAME",
"PCI",
"RENDER",
"CARD",
"DEFAULT",
"BLOCKED",
id_w = id_w,
name_w = name_w,
pci_w = pci_w,
render_w = render_w,
card_w = card_w,
default_w = default_w,
blocked_w = blocked_w,
);
println!(
"{} {} {} {} {} {} {}",
"-".repeat(id_w),
"-".repeat(name_w),
"-".repeat(pci_w),
"-".repeat(render_w),
"-".repeat(card_w),
"-".repeat(default_w),
"-".repeat(blocked_w),
);
for (_, gpu) in gpu_list {
let render_full = format!("renderD{}", gpu.render);
let card_full = format!("card{}", gpu.card);
println!(
"{:<id_w$} {:<name_w$} {:<pci_w$} {:<render_w$} {:<card_w$} {:<default_w$} {:<blocked_w$}",
gpu.id,
gpu.name,
gpu.pci,
render_full,
card_full,
if gpu.default { "(*)" } else { "( )" },
gpu.blocked,
id_w = id_w,
name_w = name_w,
pci_w = pci_w,
render_w = render_w,
card_w = card_w,
default_w = default_w,
blocked_w = blocked_w,
);
}
}
Loading