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
14 changes: 14 additions & 0 deletions crates/cardwire-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ pub enum Commands {
action: DebugAction,
},

#[command(about = "Launch a program on the specified GPU")]
Launch {
#[arg(long, help = "Select the gpu")]
gpu: Option<u32>,

#[arg(
required = true,
trailing_var_arg = true,
allow_hyphen_values = true,
help = "The program to launch and its arguments (e.g., `nvtop -s`)"
)]
program: Vec<String>,
},

#[command(about = "Generate shell completions", hide = true)]
Completion {
#[arg(help = "The shell to generate the completions for")]
Expand Down
14 changes: 12 additions & 2 deletions crates/cardwire-cli/src/dbus.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};

use zbus::{Proxy, connection::Connection};
use zbus::{Proxy, connection::Connection, zvariant::OwnedValue};

use crate::display::PciDevice;

Expand Down Expand Up @@ -266,4 +266,14 @@ impl<'a> DaemonClient<'a> {
.await?;
proxy.call("RefreshGpu", &()).await
}
pub async fn get_gpu_switcheroo(&self) -> zbus::Result<Vec<HashMap<String, OwnedValue>>> {
let proxy = zbus::Proxy::new(
self.proxy.connection(),
"net.hadess.SwitcherooControl",
"/net/hadess/SwitcherooControl",
"net.hadess.SwitcherooControl",
)
.await?;
proxy.get_property("GPUs").await
}
}
81 changes: 81 additions & 0 deletions crates/cardwire-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod display;
use args::{Args, CliMode, Commands, ConfigAction, DebugAction, ManagerAction};
use clap::{CommandFactory, Parser};
use dbus::DaemonClient;
use zbus::zvariant::{self};

use crate::display::print_devices_pci;

Expand Down Expand Up @@ -255,6 +256,86 @@ async fn main() -> anyhow::Result<()> {
}
}
},
Commands::Launch { gpu, program } => {
let available_gpu = client.get_gpu_switcheroo().await?;

#[derive(Debug)]
struct SwitcherooGpu {
name: String,
environment: Vec<String>,
default: bool,
discrete: bool,
}

let mut switcheroo_list: Vec<SwitcherooGpu> = Vec::new();

for gpu in available_gpu {
let mut parsed_gpu = SwitcherooGpu {
name: String::new(),
environment: Vec::new(),
default: false,
discrete: false,
};

for (key, value) in gpu.iter() {
if *key == "Name" {
if let Ok(s) = value.downcast_ref::<zvariant::Str>() {
parsed_gpu.name = s.as_str().to_string();
}
} else if *key == "Environment" {
if let Ok(arr) = value.downcast_ref::<zvariant::Array>() {
let env_val: zvariant::Value<'_> = arr.into();
if let Ok(env_vec) = env_val.try_into() {
parsed_gpu.environment = env_vec;
}
}
} else if *key == "Default"
&& let Ok(b) = value.downcast_ref::<bool>()
{
parsed_gpu.default = b;
} else if *key == "Discrete"
&& let Ok(b) = value.downcast_ref::<bool>()
{
parsed_gpu.discrete = b;
}
}

switcheroo_list.push(parsed_gpu);
}

let target_gpu = if let Some(gpu_id) = gpu {
switcheroo_list.get(gpu_id as usize)
} else {
switcheroo_list
.iter()
.find(|g| !g.default && g.discrete)
.or_else(|| switcheroo_list.iter().find(|g| g.discrete))
.or_else(|| switcheroo_list.iter().find(|g| g.default))
.or_else(|| switcheroo_list.first())
};

if let Some(gpu) = target_gpu {
let mut command = std::process::Command::new(&program[0]);

if program.len() > 1 {
command.args(&program[1..]);
}

for chunk in gpu.environment.chunks(2) {
if let [key, value] = chunk {
command.env(key, value);
}
}
let status = command.status().map_err(|e| {
anyhow::anyhow!("Failed to launch process '{}': {}", program[0], e)
})?;
if !status.success() {
return Err(anyhow::anyhow!("Process exited with status: {}", status));
} else {
return Err(anyhow::anyhow!("No matching GPU found."));
}
}
}
Commands::CompleteGpus => {
let objects = client.get_managed_objects().await.unwrap_or_default();
for (path, _) in objects {
Expand Down