diff --git a/crates/cardwire-cli/src/args.rs b/crates/cardwire-cli/src/args.rs index 83c80491..c01741d7 100644 --- a/crates/cardwire-cli/src/args.rs +++ b/crates/cardwire-cli/src/args.rs @@ -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, + + #[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, + }, + #[command(about = "Generate shell completions", hide = true)] Completion { #[arg(help = "The shell to generate the completions for")] diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index d24c1268..64693af5 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -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; @@ -266,4 +266,14 @@ impl<'a> DaemonClient<'a> { .await?; proxy.call("RefreshGpu", &()).await } + pub async fn get_gpu_switcheroo(&self) -> zbus::Result>> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "net.hadess.SwitcherooControl", + "/net/hadess/SwitcherooControl", + "net.hadess.SwitcherooControl", + ) + .await?; + proxy.get_property("GPUs").await + } } diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index cf02ed12..b68422d9 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -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; @@ -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, + default: bool, + discrete: bool, + } + + let mut switcheroo_list: Vec = 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::() { + parsed_gpu.name = s.as_str().to_string(); + } + } else if *key == "Environment" { + if let Ok(arr) = value.downcast_ref::() { + 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::() + { + parsed_gpu.default = b; + } else if *key == "Discrete" + && let Ok(b) = value.downcast_ref::() + { + 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 {