From 94fca06d1d289332de4eb02c42dab471b874fa83 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 20 May 2026 18:41:13 +0200 Subject: [PATCH 01/33] feat(dbus): start of a new code to split the dbus interface --- crates/cardwire-daemon/src/daemon.rs | 7 +- crates/cardwire-daemon/src/dbus.rs | 204 ++++++++----------------- crates/cardwire-daemon/src/gpu_dbus.rs | 87 +++++++++++ crates/cardwire-daemon/src/models.rs | 132 ++++++++-------- 4 files changed, 220 insertions(+), 210 deletions(-) create mode 100644 crates/cardwire-daemon/src/gpu_dbus.rs diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index eb44b70..38a951b 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -1,6 +1,7 @@ //! entry point of cardwired mod dbus; mod file; +mod gpu_dbus; mod listeners; mod models; @@ -17,11 +18,7 @@ async fn main() -> Result<()> { .format_timestamp(None) .filter_level(log::LevelFilter::Info) .init(); - let mut daemon = Daemon::new().await?; - // Now apply the config - if let Err(e) = daemon.apply_config().await { - log::error!("Failed to apply startup configuration: {e}"); - } + let daemon = Daemon::new().await?; let conn_builder = connection::Builder::system()?; let _conn = conn_builder .name("com.github.opengamingcollective.cardwire")? diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index 5c32010..a5787c1 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -2,11 +2,8 @@ use std::collections::BTreeMap; use crate::models::{Daemon, Modes}; -use cardwire_core::{ - gpu::{DbusGpuDevice, block_gpu, is_gpu_blocked}, pci::DbusPciDevice -}; +use cardwire_core::gpu::DbusGpuDevice; use log::{error, info, warn}; -use tokio::fs; use zbus::{fdo, interface}; #[interface(name = "com.github.opengamingcollective.cardwire")] @@ -18,15 +15,14 @@ impl Daemon { pub(crate) async fn set_mode(&self, mode: u32) -> fdo::Result<()> { // Valide inputs and turn into a Modes let mode = Modes::parse(&mode)?; - let mut current_mode = self.state.mode_state.write().await; - let mut blocker = self.state.ebpf_blocker.write().await; - let pci_list = &self.state.pci_devices; + let mut current_mode = self.mode_state.mode_config.write().await; + let mut gpu_list = self.mode_state.gpu_list.write().await; match mode { // Integrated/Hybrid only works on laptop with two gpus, will refuse if the computer has // more than 2 gpus Modes::Integrated | Modes::Hybrid => { - if self.state.gpu_list.len() != 2 { + if gpu_list.len() != 2 { error!( "Couldn't set mode to {}, the mode require exactly 2 GPUs", mode @@ -37,38 +33,18 @@ impl Daemon { ))); } // Loop to find the non default gpu and block it, - for gpu in self.state.gpu_list.values() { - if !gpu.is_default() { - block_gpu(&mut blocker, gpu, mode == Modes::Integrated, pci_list) - .map_err(|e| fdo::Error::Failed(e.to_string()))?; + for gpu in gpu_list.values_mut() { + if !gpu.device.is_default() { + gpu.block_gpu().await?; }; } } // If the auto apply is false, return all gpus to unblocked // Else apply the gpu_state but still unblock other gpus Modes::Manual => { - let config = self.state.config.read().await; - let gpu_state = self.state.gpu_state.read().await; - for (id, gpu) in &self.state.gpu_list { - if gpu_state.gpu_block_state(&gpu.pci.pci_address().to_string()) - && config.auto_apply_gpu_state() - { - if gpu.is_default() { - error!( - "cannot set block state for GPU {}: device is marked as default", - id - ); - // For safety, unblock if default - block_gpu(&mut blocker, gpu, false, pci_list) - .map_err(|e| fdo::Error::Failed(e.to_string()))?; - } else { - block_gpu(&mut blocker, gpu, true, pci_list) - .map_err(|e| fdo::Error::Failed(e.to_string()))?; - } - } else { - block_gpu(&mut blocker, gpu, false, pci_list) - .map_err(|e| fdo::Error::Failed(e.to_string()))?; - } + //let gpu_state = self.state.gpu_state.read().await; + for (_, gpu) in gpu_list.iter_mut() { + gpu.unblock_gpu().await?; } } } @@ -80,7 +56,7 @@ impl Daemon { } #[zbus(property)] pub(crate) async fn mode(&self) -> fdo::Result { - let current_mode = self.state.mode_state.read().await; + let current_mode = self.mode_state.mode_config.read().await; match current_mode.mode() { Modes::Integrated => Ok(0), Modes::Hybrid => Ok(1), @@ -88,119 +64,69 @@ impl Daemon { } } - pub(crate) async fn set_gpu_block(&self, gpu_id: u32, block: bool) -> fdo::Result<()> { - let mode = self.state.mode_state.read().await; - if mode.mode() != Modes::Manual { - return Err(fdo::Error::AccessDenied( - "Per GPU block is only available on manual mode".to_string(), - )); - } - let mut blocker = self.state.ebpf_blocker.write().await; - let mut gpu_state = self.state.gpu_state.write().await; - let pci_list = &self.state.pci_devices; - let gpu = self - .state - .gpu_list - .get(&(gpu_id as usize)) - .ok_or_else(|| fdo::Error::InvalidArgs(format!("Unknown GPU id: {}", gpu_id)))?; - - // prevent default gpu from being blocked - if gpu.is_default() { - error!( - "cannot set block state for GPU {}: device is marked as default", - gpu_id - ); - // for safety, unblock if default & save - block_gpu(&mut blocker, gpu, false, pci_list) - .map_err(|e| fdo::Error::Failed(e.to_string()))?; - if let Err(e) = gpu_state.save_state(&self.state.gpu_list, &blocker).await { - warn!("could not save gpu_state to file: {e}"); - } - return Err(fdo::Error::AccessDenied(format!( - "GPU {} is the default device and cannot be blocked", - gpu_id - ))); - } - - block_gpu(&mut blocker, gpu, block, pci_list) - .map_err(|err| fdo::Error::Failed(err.to_string()))?; - - info!( - "Set GPU {} ({}) block={}", - gpu_id, - gpu.pci.pci_address(), - block - ); - if let Err(e) = gpu_state.save_state(&self.state.gpu_list, &blocker).await { - warn!("could not save gpu_state to file: {e}"); - } - Ok(()) - } - pub(crate) async fn list_devices(&self) -> fdo::Result> { - let blocker = self.state.ebpf_blocker.read().await; - let list = self.state.gpu_list.clone(); + let gpu_list = self.gpu_state.gpu_list.read().await; let mut dbus_list: BTreeMap = BTreeMap::new(); - for (id, gpu) in list { + for (id, gpu) in gpu_list.iter() { let temp_gpu = DbusGpuDevice { - id: id as u32, - pci: gpu.pci.pci_address().to_string(), - render: *gpu.render(), - name: gpu.name().to_string(), - card: *gpu.card(), - default: gpu.default().unwrap_or(false), - blocked: is_gpu_blocked(&blocker, &gpu).unwrap_or(false), - nvidia: gpu.nvidia(), - nvidia_minor: if gpu.nvidia_minor().is_some() { - gpu.nvidia_minor().unwrap().to_string() + id: *id as u32, + pci: gpu.device.pci.pci_address().to_string(), + render: *gpu.device.render(), + name: gpu.device.name().to_string(), + card: *gpu.device.card(), + default: gpu.device.default().unwrap_or(false), + blocked: gpu.blocked(), + nvidia: gpu.device.nvidia(), + nvidia_minor: if gpu.device.nvidia_minor().is_some() { + gpu.device.nvidia_minor().unwrap().to_string() } else { "".to_string() }, }; - dbus_list.insert(id, temp_gpu); + dbus_list.insert(*id, temp_gpu); } Ok(dbus_list) } - pub(crate) async fn list_devices_pci(&self) -> fdo::Result> { - let pci_list = &self.state.pci_devices; - let mut dbus_list: BTreeMap = BTreeMap::new(); - for (id, pci) in pci_list { - let temp_pci = DbusPciDevice { - pci_address: pci.pci_address().to_string(), - iommu_group: if let Some(iommu) = pci.iommu_group() { - iommu.to_string() - } else { - "".to_string() - }, - vendor_id: pci.vendor_id().clone().unwrap_or("".to_string()), - device_id: pci.device_id().clone().unwrap_or("".to_string()), - vendor_name: pci.vendor_name().clone().unwrap_or("".to_string()), - device_name: pci.device_name().clone().unwrap_or("".to_string()), - driver: pci.driver().clone().unwrap_or("".to_string()), - class: pci.class().clone().unwrap_or("".to_string()), - parent_pci: pci.parent_pci().clone().unwrap_or("".to_string()), - child_pci: pci.child_pci().clone().unwrap_or("".to_string()), - }; - dbus_list.insert(id.clone(), temp_pci); - } - - Ok(dbus_list) - } - - pub async fn get_status(&self, gpu_id: u32) -> fdo::Result { - let gpu = self - .state - .gpu_list - .get(&(gpu_id as usize)) - .ok_or_else(|| fdo::Error::InvalidArgs(format!("Unknown GPU id: {}", gpu_id)))?; - let gpu_pci = gpu.pci.pci_address(); - let power_state = - fs::read_to_string(format!("/sys/bus/pci/devices/{gpu_pci}/power_state")).await; - if let Ok(state) = power_state { - Ok(state) - } else { - Err(fdo::Error::Failed("Couldn't read power_state".to_string())) - } - } + //pub(crate) async fn list_devices_pci(&self) -> fdo::Result> { + // let pci_list = &self.state.pci_devices; + // let mut dbus_list: BTreeMap = BTreeMap::new(); + // for (id, pci) in pci_list { + // let temp_pci = DbusPciDevice { + // pci_address: pci.pci_address().to_string(), + // iommu_group: if let Some(iommu) = pci.iommu_group() { + // iommu.to_string() + // } else { + // "".to_string() + // }, + // vendor_id: pci.vendor_id().clone().unwrap_or("".to_string()), + // device_id: pci.device_id().clone().unwrap_or("".to_string()), + // vendor_name: pci.vendor_name().clone().unwrap_or("".to_string()), + // device_name: pci.device_name().clone().unwrap_or("".to_string()), + // driver: pci.driver().clone().unwrap_or("".to_string()), + // class: pci.class().clone().unwrap_or("".to_string()), + // parent_pci: pci.parent_pci().clone().unwrap_or("".to_string()), + // child_pci: pci.child_pci().clone().unwrap_or("".to_string()), + // }; + // dbus_list.insert(id.clone(), temp_pci); + // } + // + // Ok(dbus_list) + //} + // + //pub async fn get_status(&self, gpu_id: u32) -> fdo::Result { + // let gpu = self + // .state + // .gpu_list + // .get(&(gpu_id as usize)) + // .ok_or_else(|| fdo::Error::InvalidArgs(format!("Unknown GPU id: {}", gpu_id)))?; + // let gpu_pci = gpu.pci.pci_address(); + // let power_state = + // fs::read_to_string(format!("/sys/bus/pci/devices/{gpu_pci}/power_state")).await; + // if let Ok(state) = power_state { + // Ok(state) + // } else { + // Err(fdo::Error::Failed("Couldn't read power_state".to_string())) + // } + //} } diff --git a/crates/cardwire-daemon/src/gpu_dbus.rs b/crates/cardwire-daemon/src/gpu_dbus.rs new file mode 100644 index 0000000..ba3d0f1 --- /dev/null +++ b/crates/cardwire-daemon/src/gpu_dbus.rs @@ -0,0 +1,87 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use anyhow::{Context, Error}; +use cardwire_core::{ + gpu::{GpuBlocker, GpuDevice, block_gpu, is_gpu_blocked}, pci::PciDevice +}; +use tokio::sync::RwLock; +use zbus::{fdo, interface}; + +pub struct Gpu { + pub device: GpuDevice, + blocker: Arc>, + pub pci_list: Arc>>, + blocked: bool, +} + +impl Gpu { + pub fn new( + blocker: Arc>, + device: GpuDevice, + pci_list: Arc>>, + ) -> Self { + Self { + device, + blocker, + pci_list, + blocked: false, + } + } + + // block the gpu + pub async fn block_gpu(&mut self) -> fdo::Result<()> { + let mut blocker = self.blocker.write().await; + let gpu = &self.device; + let pci_list = self.pci_list.read().await; + + println!("blocking gpu {}, with {}", gpu.name(), true); + block_gpu(&mut blocker, gpu, true, &pci_list) + .map_err(|e| fdo::Error::Failed(e.to_string()))?; + if let Ok(result) = is_gpu_blocked(&blocker, &gpu) { + if !result { + return Err(fdo::Error::Failed( + "gpu is supposed to be blocked, bpf says it's not".to_string(), + )); + } else { + self.blocked = result; + } + } + Ok(()) + } + // unblock the gpu + pub async fn unblock_gpu(&mut self) -> fdo::Result<()> { + let mut blocker = self.blocker.write().await; + let gpu = &self.device; + let pci_list = self.pci_list.read().await; + + block_gpu(&mut blocker, gpu, false, &pci_list) + .map_err(|e| fdo::Error::Failed(e.to_string()))?; + if let Ok(result) = is_gpu_blocked(&blocker, &gpu) { + if result { + return Err(fdo::Error::Failed( + "gpu is supposed to be unblocked, bpf says it's not".to_string(), + )); + } else { + self.blocked = result; + } + } + Ok(()) + } + pub fn blocked(&self) -> bool { + self.blocked + } +} + +#[interface(name = "com.github.opengamingcollective.cardwire.gpu")] + +impl Gpu { + #[zbus(property)] + pub async fn set_block(&self, block: bool) -> fdo::Result<()> { + Ok(()) + } + + #[zbus(property)] + pub async fn block(&self) -> fdo::Result { + Ok(true) + } +} diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 24b9da7..3459b55 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,12 +1,16 @@ //! where the struct and impl are declared -use crate::file::{CardwireConfig, CardwireGpuState, CardwireModeState}; +use crate::{ + file::{CardwireConfig, CardwireGpuState, CardwireModeState}, gpu_dbus::Gpu +}; use anyhow::{Context, Result}; use cardwire_core::{ gpu::{self, GpuBlocker, check_default_drm_class}, pci }; use log::{info, warn}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt}; +use std::{ + collections::{BTreeMap, HashMap}, fmt, sync::Arc +}; use tokio::sync::RwLock; use zbus::fdo::Error; @@ -58,94 +62,90 @@ impl Modes { pub struct DaemonState { // these are file related - pub config: RwLock, pub gpu_state: RwLock, pub mode_state: RwLock, // temp data - pub gpu_list: BTreeMap, + pub gpu_list: BTreeMap, pub ebpf_blocker: RwLock, // for future uses, related to vfio pub pci_devices: BTreeMap, } +pub struct ModeState { + pub mode_config: Arc>, + pub gpu_list: Arc>>, + pub config: Arc>, +} +pub struct ConfigState { + pub config: Arc>, +} +pub struct GpuState { + pub mode_config: Arc>, + pub gpu_list: Arc>>, +} +pub struct PciState { + pub pci_list: Arc>>, +} pub struct Daemon { - pub state: DaemonState, + pub mode_state: ModeState, + pub gpu_state: GpuState, + pub config_state: ConfigState, + pub pci_state: PciState, } impl Daemon { pub async fn new() -> Result { - let config = CardwireConfig::build().context("Error building config")?; - let mut gpu_state = CardwireGpuState::build().context("Error building gpu_state")?; - let mode_state = CardwireModeState::build().context("Error building mode")?; + //let mut gpu_state = CardwireGpuState::build().context("Error building gpu_state")?; + let user_config = CardwireConfig::build().context("Error building toml config")?; + let user_config: Arc> = Arc::new(RwLock::new(user_config)); + // Get mode from config + let mode_config = CardwireModeState::build().context("Error building mode")?; + let mode_config: Arc> = Arc::new(RwLock::new(mode_config)); // TODO: Exit if no pci devices or manual refresh command - let pci_devices = pci::read_pci_devices()?; + let pci_devices: BTreeMap = pci::read_pci_devices()?; // TODO: Should the daemon exits if no gpu?? let mut gpu_list = gpu::read_gpu(&pci_devices)?; // Executed after the read_gpu to use the current gpu_list if let Err(err) = check_default_drm_class(&mut gpu_list) { warn!("Failed to determine default GPU: {}", err); } + let pci_list: Arc>> = + Arc::new(RwLock::new(pci_devices)); // Exit if ebpf returns an error - let ebpf_blocker = GpuBlocker::new()?; - - if !gpu_list.is_empty() && gpu_state.is_default_state() { - gpu_state - .save_state(&gpu_list, &ebpf_blocker) - .await - .context("Could not save gpu state")?; - } else if gpu_list.is_empty() { - // the daemon needs to be running to print out the pci list - warn!("could not detect gpus, daemon is still running for debugging") + let blocker = Arc::new(RwLock::new(GpuBlocker::new()?)); + // create gpu list + let mut new_gpu_list: BTreeMap = BTreeMap::new(); + for (id, gpu) in gpu_list { + new_gpu_list.insert( + id, + Gpu::new(Arc::clone(&blocker), gpu, Arc::clone(&pci_list)), + ); } + let gpu_list: Arc>> = Arc::new(RwLock::new(new_gpu_list)); - Ok(Self { - state: DaemonState { - config: RwLock::new(config), - gpu_state: RwLock::new(gpu_state), - mode_state: RwLock::new(mode_state), - pci_devices, - gpu_list, - ebpf_blocker: RwLock::new(ebpf_blocker), - }, - }) - } - pub async fn apply_config(&mut self) -> anyhow::Result<()> { - let config = self.state.config.read().await; - let mode = self.state.mode_state.read().await; - let mut blocker = self.state.ebpf_blocker.write().await; + // Create GpuState + let gpu_state: GpuState = GpuState { + mode_config: Arc::clone(&mode_config), + gpu_list: Arc::clone(&gpu_list), + }; - info!("applying this config: {:?}", config); - // Apply nvidia block - blocker.set_nvidia_setting(config.experimental_nvidia_block())?; - // Apply file blocks - for file in BLOCKED_PCI_FILES { - blocker.set_file_block(file)?; - } - for gpu in self.state.gpu_list.values() { - if gpu.nvidia() { - for file in BLOCKED_NVIDIA_FILES { - blocker.set_nvidia_file_block(file)?; - } - break; - } - } - // Dropping the locks prevent set_mode being stuck - drop(blocker); - drop(config); - // Apply mode - let mode_to_apply = mode.mode(); - drop(mode); - let mode_to_apply: usize = match mode_to_apply { - Modes::Integrated => 0, - Modes::Hybrid => 1, - Modes::Manual => 2, + let mode_state: ModeState = ModeState { + mode_config: Arc::clone(&mode_config), + gpu_list: Arc::clone(&gpu_list), + config: Arc::clone(&user_config), }; - self.set_mode(mode_to_apply as u32).await?; - // get config lock again - let config = self.state.config.read().await; - if config.battery_auto_switch() { - tokio::task::spawn(crate::listeners::watch_battery_status()); - } - Ok(()) + let config_state: ConfigState = ConfigState { + config: Arc::clone(&user_config), + }; + let pci_state: PciState = PciState { + pci_list: Arc::clone(&pci_list), + }; + + Ok(Self { + mode_state, + gpu_state, + config_state, + pci_state, + }) } } From 0644e11b888b5eca88ae2f0d24d24c1576468e8f Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 20 May 2026 20:41:31 +0200 Subject: [PATCH 02/33] feat(daemon): gpu dbus --- crates/cardwire-cli/src/dbus.rs | 13 ++++- crates/cardwire-cli/src/main.rs | 4 +- crates/cardwire-daemon/src/daemon.rs | 15 ++++- crates/cardwire-daemon/src/dbus.rs | 78 +++++++------------------- crates/cardwire-daemon/src/gpu_dbus.rs | 49 +++++++++------- crates/cardwire-daemon/src/models.rs | 40 ++++--------- 6 files changed, 88 insertions(+), 111 deletions(-) diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 5a0bc86..37a05a1 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -34,8 +34,17 @@ impl<'a> DaemonClient<'a> { self.proxy.call("ListDevicesPci", &()).await } - pub async fn set_gpu_block(&self, id: u32, blocked: bool) -> zbus::Result<()> { - self.proxy.call("SetGpuBlock", &(id, blocked)).await + pub async fn set_gpu_block(&self, id: u32, blocked: bool) -> zbus::fdo::Result<()> { + let path = format!("/com/github/opengamingcollective/cardwire/gpu/{}", id); + let block_proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", // Destination + path.as_str(), // The new dynamic path + "com.github.opengamingcollective.cardwire.gpu", // Interface name + ) + .await + .map_err(|e| zbus::fdo::Error::Failed(format!("Failed to create proxy: {}", e)))?; + block_proxy.set_property("Block", &(blocked)).await } pub async fn get_status(&self, id: u32) -> zbus::Result { self.proxy.call("GetStatus", &id).await diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index fb2744a..51d3d60 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -78,13 +78,13 @@ async fn main() -> anyhow::Result<()> { // Handle --block match client.set_gpu_block(id, true).await { Ok(_) => println!("GPU {} has been blocked", id), - Err(e) => handle_error(e), + Err(e) => handle_error(e.into()), }; } else if action.unblock { // Handle --unblock match client.set_gpu_block(id, false).await { Ok(_) => println!("GPU {} has been unblocked", id), - Err(e) => handle_error(e), + Err(e) => handle_error(e.into()), }; } } diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index 38a951b..04704f1 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -19,12 +19,23 @@ async fn main() -> Result<()> { .filter_level(log::LevelFilter::Info) .init(); let daemon = Daemon::new().await?; + let conn_builder = connection::Builder::system()?; - let _conn = conn_builder + let conn = conn_builder .name("com.github.opengamingcollective.cardwire")? - .serve_at("/com/github/opengamingcollective/cardwire", daemon)? + .serve_at("/com/github/opengamingcollective/cardwire", daemon.clone())? .build() .await?; + + let object_server = conn.object_server(); + + let gpu_list = daemon.gpu_list.read().await; + + for (id, gpu) in gpu_list.iter() { + let path = format!("/com/github/opengamingcollective/cardwire/gpu/{}", id); + object_server.at(path, gpu.clone()).await?; + } + drop(gpu_list); info!("Daemon started"); pending::<()>().await; Ok(()) diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs index a5787c1..b8b1a19 100644 --- a/crates/cardwire-daemon/src/dbus.rs +++ b/crates/cardwire-daemon/src/dbus.rs @@ -16,8 +16,7 @@ impl Daemon { // Valide inputs and turn into a Modes let mode = Modes::parse(&mode)?; let mut current_mode = self.mode_state.mode_config.write().await; - let mut gpu_list = self.mode_state.gpu_list.write().await; - + let mut gpu_list = self.gpu_list.write().await; match mode { // Integrated/Hybrid only works on laptop with two gpus, will refuse if the computer has // more than 2 gpus @@ -34,8 +33,13 @@ impl Daemon { } // Loop to find the non default gpu and block it, for gpu in gpu_list.values_mut() { - if !gpu.device.is_default() { - gpu.block_gpu().await?; + let mut state = gpu.inner.write().await; + if !state.device.is_default() { + if mode == Modes::Integrated { + state.block_gpu().await?; + } else { + state.unblock_gpu().await?; + } }; } } @@ -44,7 +48,8 @@ impl Daemon { Modes::Manual => { //let gpu_state = self.state.gpu_state.read().await; for (_, gpu) in gpu_list.iter_mut() { - gpu.unblock_gpu().await?; + let mut state = gpu.inner.write().await; + state.unblock_gpu().await?; } } } @@ -65,20 +70,21 @@ impl Daemon { } pub(crate) async fn list_devices(&self) -> fdo::Result> { - let gpu_list = self.gpu_state.gpu_list.read().await; + let gpu_list = self.mode_state.gpu_list.read().await; let mut dbus_list: BTreeMap = BTreeMap::new(); for (id, gpu) in gpu_list.iter() { + let state = gpu.inner.write().await; let temp_gpu = DbusGpuDevice { id: *id as u32, - pci: gpu.device.pci.pci_address().to_string(), - render: *gpu.device.render(), - name: gpu.device.name().to_string(), - card: *gpu.device.card(), - default: gpu.device.default().unwrap_or(false), - blocked: gpu.blocked(), - nvidia: gpu.device.nvidia(), - nvidia_minor: if gpu.device.nvidia_minor().is_some() { - gpu.device.nvidia_minor().unwrap().to_string() + pci: state.device.pci.pci_address().to_string(), + render: *state.device.render(), + name: state.device.name().to_string(), + card: *state.device.card(), + default: state.device.default().unwrap_or(false), + blocked: state.blocked(), + nvidia: state.device.nvidia(), + nvidia_minor: if state.device.nvidia_minor().is_some() { + state.device.nvidia_minor().unwrap().to_string() } else { "".to_string() }, @@ -87,46 +93,4 @@ impl Daemon { } Ok(dbus_list) } - - //pub(crate) async fn list_devices_pci(&self) -> fdo::Result> { - // let pci_list = &self.state.pci_devices; - // let mut dbus_list: BTreeMap = BTreeMap::new(); - // for (id, pci) in pci_list { - // let temp_pci = DbusPciDevice { - // pci_address: pci.pci_address().to_string(), - // iommu_group: if let Some(iommu) = pci.iommu_group() { - // iommu.to_string() - // } else { - // "".to_string() - // }, - // vendor_id: pci.vendor_id().clone().unwrap_or("".to_string()), - // device_id: pci.device_id().clone().unwrap_or("".to_string()), - // vendor_name: pci.vendor_name().clone().unwrap_or("".to_string()), - // device_name: pci.device_name().clone().unwrap_or("".to_string()), - // driver: pci.driver().clone().unwrap_or("".to_string()), - // class: pci.class().clone().unwrap_or("".to_string()), - // parent_pci: pci.parent_pci().clone().unwrap_or("".to_string()), - // child_pci: pci.child_pci().clone().unwrap_or("".to_string()), - // }; - // dbus_list.insert(id.clone(), temp_pci); - // } - // - // Ok(dbus_list) - //} - // - //pub async fn get_status(&self, gpu_id: u32) -> fdo::Result { - // let gpu = self - // .state - // .gpu_list - // .get(&(gpu_id as usize)) - // .ok_or_else(|| fdo::Error::InvalidArgs(format!("Unknown GPU id: {}", gpu_id)))?; - // let gpu_pci = gpu.pci.pci_address(); - // let power_state = - // fs::read_to_string(format!("/sys/bus/pci/devices/{gpu_pci}/power_state")).await; - // if let Ok(state) = power_state { - // Ok(state) - // } else { - // Err(fdo::Error::Failed("Couldn't read power_state".to_string())) - // } - //} } diff --git a/crates/cardwire-daemon/src/gpu_dbus.rs b/crates/cardwire-daemon/src/gpu_dbus.rs index ba3d0f1..bd2abb2 100644 --- a/crates/cardwire-daemon/src/gpu_dbus.rs +++ b/crates/cardwire-daemon/src/gpu_dbus.rs @@ -1,12 +1,12 @@ use std::{collections::BTreeMap, sync::Arc}; -use anyhow::{Context, Error}; use cardwire_core::{ gpu::{GpuBlocker, GpuDevice, block_gpu, is_gpu_blocked}, pci::PciDevice }; use tokio::sync::RwLock; use zbus::{fdo, interface}; +#[derive(Clone)] pub struct Gpu { pub device: GpuDevice, blocker: Arc>, @@ -14,30 +14,35 @@ pub struct Gpu { blocked: bool, } -impl Gpu { +#[derive(Clone)] +pub struct GpuState { + pub inner: Arc>, +} + +impl GpuState { pub fn new( blocker: Arc>, device: GpuDevice, pci_list: Arc>>, ) -> Self { Self { - device, - blocker, - pci_list, - blocked: false, + inner: Arc::new(RwLock::new(Gpu { + device, + blocker, + pci_list, + blocked: false, + })), } } - +} +impl Gpu { // block the gpu pub async fn block_gpu(&mut self) -> fdo::Result<()> { let mut blocker = self.blocker.write().await; - let gpu = &self.device; let pci_list = self.pci_list.read().await; - - println!("blocking gpu {}, with {}", gpu.name(), true); - block_gpu(&mut blocker, gpu, true, &pci_list) + block_gpu(&mut blocker, &self.device, true, &pci_list) .map_err(|e| fdo::Error::Failed(e.to_string()))?; - if let Ok(result) = is_gpu_blocked(&blocker, &gpu) { + if let Ok(result) = is_gpu_blocked(&blocker, &self.device) { if !result { return Err(fdo::Error::Failed( "gpu is supposed to be blocked, bpf says it's not".to_string(), @@ -51,12 +56,11 @@ impl Gpu { // unblock the gpu pub async fn unblock_gpu(&mut self) -> fdo::Result<()> { let mut blocker = self.blocker.write().await; - let gpu = &self.device; let pci_list = self.pci_list.read().await; - block_gpu(&mut blocker, gpu, false, &pci_list) + block_gpu(&mut blocker, &self.device, false, &pci_list) .map_err(|e| fdo::Error::Failed(e.to_string()))?; - if let Ok(result) = is_gpu_blocked(&blocker, &gpu) { + if let Ok(result) = is_gpu_blocked(&blocker, &self.device) { if result { return Err(fdo::Error::Failed( "gpu is supposed to be unblocked, bpf says it's not".to_string(), @@ -73,15 +77,20 @@ impl Gpu { } #[interface(name = "com.github.opengamingcollective.cardwire.gpu")] - -impl Gpu { +impl GpuState { #[zbus(property)] - pub async fn set_block(&self, block: bool) -> fdo::Result<()> { - Ok(()) + pub async fn set_block(&mut self, block: bool) -> fdo::Result<()> { + let mut state = self.inner.write().await; + if block { + state.block_gpu().await + } else { + state.unblock_gpu().await + } } #[zbus(property)] pub async fn block(&self) -> fdo::Result { - Ok(true) + let state = self.inner.read().await; + Ok(state.blocked()) } } diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 3459b55..096583b 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,6 +1,6 @@ //! where the struct and impl are declared use crate::{ - file::{CardwireConfig, CardwireGpuState, CardwireModeState}, gpu_dbus::Gpu + file::{CardwireConfig, CardwireGpuState, CardwireModeState}, gpu_dbus::{Gpu, GpuState} }; use anyhow::{Context, Result}; use cardwire_core::{ @@ -60,37 +60,27 @@ impl Modes { } } -pub struct DaemonState { - // these are file related - pub gpu_state: RwLock, - pub mode_state: RwLock, - // temp data - pub gpu_list: BTreeMap, - pub ebpf_blocker: RwLock, - // for future uses, related to vfio - pub pci_devices: BTreeMap, -} +#[derive(Clone)] pub struct ModeState { pub mode_config: Arc>, - pub gpu_list: Arc>>, + pub gpu_list: Arc>>, pub config: Arc>, } - +#[derive(Clone)] pub struct ConfigState { pub config: Arc>, } -pub struct GpuState { - pub mode_config: Arc>, - pub gpu_list: Arc>>, -} +#[derive(Clone)] pub struct PciState { pub pci_list: Arc>>, } + +#[derive(Clone)] pub struct Daemon { pub mode_state: ModeState, - pub gpu_state: GpuState, pub config_state: ConfigState, pub pci_state: PciState, + pub gpu_list: Arc>>, } impl Daemon { @@ -114,20 +104,14 @@ impl Daemon { // Exit if ebpf returns an error let blocker = Arc::new(RwLock::new(GpuBlocker::new()?)); // create gpu list - let mut new_gpu_list: BTreeMap = BTreeMap::new(); + let mut new_gpu_list: BTreeMap = BTreeMap::new(); for (id, gpu) in gpu_list { new_gpu_list.insert( id, - Gpu::new(Arc::clone(&blocker), gpu, Arc::clone(&pci_list)), + GpuState::new(Arc::clone(&blocker), gpu, Arc::clone(&pci_list)), ); } - let gpu_list: Arc>> = Arc::new(RwLock::new(new_gpu_list)); - - // Create GpuState - let gpu_state: GpuState = GpuState { - mode_config: Arc::clone(&mode_config), - gpu_list: Arc::clone(&gpu_list), - }; + let gpu_list: Arc>> = Arc::new(RwLock::new(new_gpu_list)); let mode_state: ModeState = ModeState { mode_config: Arc::clone(&mode_config), @@ -143,9 +127,9 @@ impl Daemon { Ok(Self { mode_state, - gpu_state, config_state, pci_state, + gpu_list: Arc::clone(&gpu_list), }) } } From b3c2f2ce5a1ba9fe269ce8bd854c307f25cdd955 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 21 May 2026 16:34:43 +0200 Subject: [PATCH 03/33] refactor(dbus): clean re-implementation --- crates/cardwire-daemon/src/daemon.rs | 22 +-- crates/cardwire-daemon/src/dbus.rs | 96 ----------- crates/cardwire-daemon/src/file/state.rs | 2 +- .../cardwire-daemon/src/interface/config.rs | 0 .../src/{gpu_dbus.rs => interface/gpu.rs} | 53 +++--- crates/cardwire-daemon/src/interface/mod.rs | 5 + crates/cardwire-daemon/src/interface/mode.rs | 131 +++++++++++++++ crates/cardwire-daemon/src/models.rs | 158 ++++++------------ 8 files changed, 229 insertions(+), 238 deletions(-) delete mode 100644 crates/cardwire-daemon/src/dbus.rs create mode 100644 crates/cardwire-daemon/src/interface/config.rs rename crates/cardwire-daemon/src/{gpu_dbus.rs => interface/gpu.rs} (76%) create mode 100644 crates/cardwire-daemon/src/interface/mod.rs create mode 100644 crates/cardwire-daemon/src/interface/mode.rs diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index 04704f1..a6b4ae4 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -1,11 +1,10 @@ //! entry point of cardwired -mod dbus; mod file; -mod gpu_dbus; +mod interface; mod listeners; mod models; -use crate::models::Daemon; +use crate::models::DaemonManager; use anyhow::Result; use log::info; use std::future::pending; @@ -18,7 +17,7 @@ async fn main() -> Result<()> { .format_timestamp(None) .filter_level(log::LevelFilter::Info) .init(); - let daemon = Daemon::new().await?; + let daemon = DaemonManager::new().await?; let conn_builder = connection::Builder::system()?; let conn = conn_builder @@ -28,14 +27,15 @@ async fn main() -> Result<()> { .await?; let object_server = conn.object_server(); - - let gpu_list = daemon.gpu_list.read().await; - - for (id, gpu) in gpu_list.iter() { - let path = format!("/com/github/opengamingcollective/cardwire/gpu/{}", id); - object_server.at(path, gpu.clone()).await?; + let gpu_interfaces = daemon.gpu_interfaces.read().await; + let path = format!("/com/github/opengamingcollective/cardwire/Mode"); + object_server.at(path, daemon.mode_interface).await?; + for (id, gpu_interface) in gpu_interfaces.iter() { + let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); + object_server.at(path, gpu_interface.clone()).await?; } - drop(gpu_list); + // drop gpu list to prevent deadlock + drop(gpu_interfaces); info!("Daemon started"); pending::<()>().await; Ok(()) diff --git a/crates/cardwire-daemon/src/dbus.rs b/crates/cardwire-daemon/src/dbus.rs deleted file mode 100644 index b8b1a19..0000000 --- a/crates/cardwire-daemon/src/dbus.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! handle the dbus part of cardwired -use std::collections::BTreeMap; - -use crate::models::{Daemon, Modes}; -use cardwire_core::gpu::DbusGpuDevice; -use log::{error, info, warn}; -use zbus::{fdo, interface}; - -#[interface(name = "com.github.opengamingcollective.cardwire")] -impl Daemon { - /* - Set the mode - */ - #[zbus(property)] - pub(crate) async fn set_mode(&self, mode: u32) -> fdo::Result<()> { - // Valide inputs and turn into a Modes - let mode = Modes::parse(&mode)?; - let mut current_mode = self.mode_state.mode_config.write().await; - let mut gpu_list = self.gpu_list.write().await; - match mode { - // Integrated/Hybrid only works on laptop with two gpus, will refuse if the computer has - // more than 2 gpus - Modes::Integrated | Modes::Hybrid => { - if gpu_list.len() != 2 { - error!( - "Couldn't set mode to {}, the mode require exactly 2 GPUs", - mode - ); - return Err(fdo::Error::NotSupported(format!( - "Couldn't set mode to {}, the mode require exactly 2 GPUs", - mode - ))); - } - // Loop to find the non default gpu and block it, - for gpu in gpu_list.values_mut() { - let mut state = gpu.inner.write().await; - if !state.device.is_default() { - if mode == Modes::Integrated { - state.block_gpu().await?; - } else { - state.unblock_gpu().await?; - } - }; - } - } - // If the auto apply is false, return all gpus to unblocked - // Else apply the gpu_state but still unblock other gpus - Modes::Manual => { - //let gpu_state = self.state.gpu_state.read().await; - for (_, gpu) in gpu_list.iter_mut() { - let mut state = gpu.inner.write().await; - state.unblock_gpu().await?; - } - } - } - if let Err(e) = current_mode.save_state(mode).await { - warn!("mode couldn't be saved to config: {e}"); - } - info!("Switched to {}", mode); - Ok(()) - } - #[zbus(property)] - pub(crate) async fn mode(&self) -> fdo::Result { - let current_mode = self.mode_state.mode_config.read().await; - match current_mode.mode() { - Modes::Integrated => Ok(0), - Modes::Hybrid => Ok(1), - Modes::Manual => Ok(2), - } - } - - pub(crate) async fn list_devices(&self) -> fdo::Result> { - let gpu_list = self.mode_state.gpu_list.read().await; - let mut dbus_list: BTreeMap = BTreeMap::new(); - for (id, gpu) in gpu_list.iter() { - let state = gpu.inner.write().await; - let temp_gpu = DbusGpuDevice { - id: *id as u32, - pci: state.device.pci.pci_address().to_string(), - render: *state.device.render(), - name: state.device.name().to_string(), - card: *state.device.card(), - default: state.device.default().unwrap_or(false), - blocked: state.blocked(), - nvidia: state.device.nvidia(), - nvidia_minor: if state.device.nvidia_minor().is_some() { - state.device.nvidia_minor().unwrap().to_string() - } else { - "".to_string() - }, - }; - dbus_list.insert(*id, temp_gpu); - } - Ok(dbus_list) - } -} diff --git a/crates/cardwire-daemon/src/file/state.rs b/crates/cardwire-daemon/src/file/state.rs index c4fe648..4ea9472 100644 --- a/crates/cardwire-daemon/src/file/state.rs +++ b/crates/cardwire-daemon/src/file/state.rs @@ -1,7 +1,7 @@ //! helper to manage cardwired configs, include the user config .toml, and the .json states like //! gpu, mode or pci use crate::{ - file::common::{FileKind, create_default_file}, models::Modes + file::common::{FileKind, create_default_file}, interface::Modes }; use anyhow::{Context, Ok}; use cardwire_core::gpu::{GpuBlocker, GpuDevice, is_gpu_blocked}; diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/cardwire-daemon/src/gpu_dbus.rs b/crates/cardwire-daemon/src/interface/gpu.rs similarity index 76% rename from crates/cardwire-daemon/src/gpu_dbus.rs rename to crates/cardwire-daemon/src/interface/gpu.rs index bd2abb2..f84b7f6 100644 --- a/crates/cardwire-daemon/src/gpu_dbus.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -1,3 +1,5 @@ +//! DBUS Interface for single gpu interaction + use std::{collections::BTreeMap, sync::Arc}; use cardwire_core::{ @@ -6,36 +8,31 @@ use cardwire_core::{ use tokio::sync::RwLock; use zbus::{fdo, interface}; +// Represent a single gpu #[derive(Clone)] -pub struct Gpu { +pub struct GpuInterface { pub device: GpuDevice, blocker: Arc>, pub pci_list: Arc>>, blocked: bool, } -#[derive(Clone)] -pub struct GpuState { - pub inner: Arc>, -} - -impl GpuState { - pub fn new( - blocker: Arc>, +impl GpuInterface { + pub fn build( device: GpuDevice, + blocker: Arc>, pci_list: Arc>>, - ) -> Self { - Self { - inner: Arc::new(RwLock::new(Gpu { - device, - blocker, - pci_list, - blocked: false, - })), - } + ) -> anyhow::Result { + Ok(Self { + device, + blocker, + pci_list, + blocked: false, + }) } } -impl Gpu { + +impl GpuInterface { // block the gpu pub async fn block_gpu(&mut self) -> fdo::Result<()> { let mut blocker = self.blocker.write().await; @@ -76,21 +73,25 @@ impl Gpu { } } -#[interface(name = "com.github.opengamingcollective.cardwire.gpu")] -impl GpuState { +#[interface( + name = "com.github.opengamingcollective.Gpu", + proxy( + default_service = "com.github.opengamingcollective.cardwire", + default_path = "/com/github/opengamingcollective/cardwire" + ) +)] +impl GpuInterface { #[zbus(property)] pub async fn set_block(&mut self, block: bool) -> fdo::Result<()> { - let mut state = self.inner.write().await; if block { - state.block_gpu().await + self.block_gpu().await } else { - state.unblock_gpu().await + self.unblock_gpu().await } } #[zbus(property)] pub async fn block(&self) -> fdo::Result { - let state = self.inner.read().await; - Ok(state.blocked()) + Ok(self.blocked()) } } diff --git a/crates/cardwire-daemon/src/interface/mod.rs b/crates/cardwire-daemon/src/interface/mod.rs new file mode 100644 index 0000000..4d58943 --- /dev/null +++ b/crates/cardwire-daemon/src/interface/mod.rs @@ -0,0 +1,5 @@ +mod gpu; +mod mode; + +pub use gpu::GpuInterface; +pub use mode::{ModeInterface, Modes}; diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs new file mode 100644 index 0000000..3a48fc4 --- /dev/null +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -0,0 +1,131 @@ +//! Define the mode dbus +use crate::{ + file::{CardwireConfig, CardwireModeState}, interface::GpuInterface +}; +use anyhow::Result; +use log::{error, info, warn}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt, sync::Arc}; +use tokio::sync::RwLock; +use zbus::{fdo, fdo::Error, interface}; + +#[derive(Deserialize, Serialize, PartialEq, zbus::zvariant::Type, Clone, Copy, Default)] +pub enum Modes { + Integrated, + Hybrid, + #[default] + Manual, +} + +impl fmt::Display for Modes { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Modes::Integrated => write!(f, "Integrated"), + Modes::Hybrid => write!(f, "Hybrid"), + Modes::Manual => write!(f, "Manual"), + } + } +} + +impl Modes { + pub fn parse(input: &u32) -> zbus::fdo::Result { + match input { + 0 => Ok(Self::Integrated), + 1 => Ok(Self::Hybrid), + 2 => Ok(Self::Manual), + unknown => Err(Error::InvalidArgs(format!( + "unknown mode: {unknown} \n expected integrated|hybrid|manual" + ))), + } + } +} + +// to change a mode, we need the config, the mode_state, the gpu_list +#[derive(Clone)] +pub struct ModeInterface { + pub mode_state: Arc>, + pub gpu_list: Arc>>, + pub config: Arc>, +} + +impl ModeInterface { + pub fn build( + mode_state: Arc>, + gpu_list: Arc>>, + config: Arc>, + ) -> Result { + Ok(ModeInterface { + mode_state, + gpu_list, + config, + }) + } +} + +#[interface( + name = "com.github.opengamingcollective.Mode", + proxy( + default_service = "com.github.opengamingcollective.cardwire", + default_path = "/com/github/opengamingcollective/cardwire" + ) +)] +impl ModeInterface { + /* + Set the mode + */ + #[zbus(property)] + pub(crate) async fn set_mode(&self, mode: u32) -> fdo::Result<()> { + // Valide inputs and turn into a Modes + let mode = Modes::parse(&mode)?; + let mut current_mode = self.mode_state.write().await; + let mut gpu_list = self.gpu_list.write().await; + match mode { + // Integrated/Hybrid only works on laptop with two gpus, will refuse if the computer has + // more than 2 gpus + Modes::Integrated | Modes::Hybrid => { + if gpu_list.len() != 2 { + error!( + "Couldn't set mode to {}, the mode require exactly 2 GPUs", + mode + ); + return Err(fdo::Error::NotSupported(format!( + "Couldn't set mode to {}, the mode require exactly 2 GPUs", + mode + ))); + } + // Loop to find the non default gpu and block it, + for gpu in gpu_list.values_mut() { + if !gpu.device.is_default() { + if mode == Modes::Integrated { + gpu.block_gpu().await?; + } else { + gpu.unblock_gpu().await?; + } + }; + } + } + // If the auto apply is false, return all gpus to unblocked + // Else apply the gpu_state but still unblock other gpus + Modes::Manual => { + //let gpu_state = self.state.gpu_state.read().await; + for (_, gpu) in gpu_list.iter_mut() { + gpu.unblock_gpu().await?; + } + } + } + if let Err(e) = current_mode.save_state(mode).await { + warn!("mode couldn't be saved to config: {e}"); + } + info!("Switched to {}", mode); + Ok(()) + } + #[zbus(property)] + pub(crate) async fn mode(&self) -> fdo::Result { + let current_mode = self.mode_state.read().await; + match current_mode.mode() { + Modes::Integrated => Ok(0), + Modes::Hybrid => Ok(1), + Modes::Manual => Ok(2), + } + } +} diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 096583b..ef684a3 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,135 +1,85 @@ //! where the struct and impl are declared use crate::{ - file::{CardwireConfig, CardwireGpuState, CardwireModeState}, gpu_dbus::{Gpu, GpuState} + file::{CardwireConfig, CardwireModeState}, interface::{GpuInterface, ModeInterface} }; use anyhow::{Context, Result}; use cardwire_core::{ gpu::{self, GpuBlocker, check_default_drm_class}, pci }; -use log::{info, warn}; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashMap}, fmt, sync::Arc -}; +use log::warn; +use std::{collections::BTreeMap, sync::Arc}; use tokio::sync::RwLock; -use zbus::fdo::Error; - -const BLOCKED_PCI_FILES: &[&str] = &[ - "config", - "current_link_speed", - "max_link_speed", - "max_link_width", - "current_link_width", -]; -// Files that get blocked when the NVIDIA block is on -const BLOCKED_NVIDIA_FILES: &[&str] = &[ - "libGLX_nvidia.so.0", - "nvidia_icd.json", - "nvidia_icd.x86_64.json", - "nvidiactl", -]; - -#[derive(Deserialize, Serialize, PartialEq, zbus::zvariant::Type, Clone, Copy, Default)] -pub enum Modes { - Integrated, - Hybrid, - #[default] - Manual, -} - -impl fmt::Display for Modes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Modes::Integrated => write!(f, "Integrated"), - Modes::Hybrid => write!(f, "Hybrid"), - Modes::Manual => write!(f, "Manual"), - } - } -} - -impl Modes { - pub fn parse(input: &u32) -> zbus::fdo::Result { - match input { - 0 => Ok(Self::Integrated), - 1 => Ok(Self::Hybrid), - 2 => Ok(Self::Manual), - unknown => Err(Error::InvalidArgs(format!( - "unknown mode: {unknown} \n expected integrated|hybrid|manual" - ))), - } - } -} +use zbus::{ + fdo::{self}, interface +}; -#[derive(Clone)] -pub struct ModeState { - pub mode_config: Arc>, - pub gpu_list: Arc>>, - pub config: Arc>, -} -#[derive(Clone)] -pub struct ConfigState { - pub config: Arc>, -} -#[derive(Clone)] -pub struct PciState { - pub pci_list: Arc>>, -} +//const BLOCKED_PCI_FILES: &[&str] = &[ +// "config", +// "current_link_speed", +// "max_link_speed", +// "max_link_width", +// "current_link_width", +//]; +//// Files that get blocked when the NVIDIA block is on +//const BLOCKED_NVIDIA_FILES: &[&str] = &[ +// "libGLX_nvidia.so.0", +// "nvidia_icd.json", +// "nvidia_icd.x86_64.json", +// "nvidiactl", +//]; #[derive(Clone)] -pub struct Daemon { - pub mode_state: ModeState, - pub config_state: ConfigState, - pub pci_state: PciState, - pub gpu_list: Arc>>, +pub struct DaemonManager { + pub mode_interface: ModeInterface, + pub gpu_interfaces: Arc>>, } -impl Daemon { +impl DaemonManager { pub async fn new() -> Result { - //let mut gpu_state = CardwireGpuState::build().context("Error building gpu_state")?; + let mode_state: CardwireModeState = + CardwireModeState::build().context("Error building mode")?; + let mode_state: Arc> = Arc::new(RwLock::new(mode_state)); + let user_config = CardwireConfig::build().context("Error building toml config")?; let user_config: Arc> = Arc::new(RwLock::new(user_config)); - // Get mode from config - let mode_config = CardwireModeState::build().context("Error building mode")?; - let mode_config: Arc> = Arc::new(RwLock::new(mode_config)); - // TODO: Exit if no pci devices or manual refresh command + let pci_devices: BTreeMap = pci::read_pci_devices()?; - // TODO: Should the daemon exits if no gpu?? + let mut gpu_list = gpu::read_gpu(&pci_devices)?; - // Executed after the read_gpu to use the current gpu_list + if let Err(err) = check_default_drm_class(&mut gpu_list) { warn!("Failed to determine default GPU: {}", err); } + let pci_list: Arc>> = Arc::new(RwLock::new(pci_devices)); - // Exit if ebpf returns an error + let blocker = Arc::new(RwLock::new(GpuBlocker::new()?)); - // create gpu list - let mut new_gpu_list: BTreeMap = BTreeMap::new(); - for (id, gpu) in gpu_list { - new_gpu_list.insert( - id, - GpuState::new(Arc::clone(&blocker), gpu, Arc::clone(&pci_list)), - ); + + let mut gpu_interfaces_map: BTreeMap = BTreeMap::new(); + + for (id, device) in gpu_list { + let gpu = GpuInterface::build(device, Arc::clone(&blocker), Arc::clone(&pci_list))?; + gpu_interfaces_map.insert(id, gpu); } - let gpu_list: Arc>> = Arc::new(RwLock::new(new_gpu_list)); - let mode_state: ModeState = ModeState { - mode_config: Arc::clone(&mode_config), - gpu_list: Arc::clone(&gpu_list), - config: Arc::clone(&user_config), - }; - let config_state: ConfigState = ConfigState { - config: Arc::clone(&user_config), - }; - let pci_state: PciState = PciState { - pci_list: Arc::clone(&pci_list), - }; + let gpu_interfaces: Arc>> = + Arc::new(RwLock::new(gpu_interfaces_map)); Ok(Self { - mode_state, - config_state, - pci_state, - gpu_list: Arc::clone(&gpu_list), + mode_interface: ModeInterface::build( + mode_state, + Arc::clone(&gpu_interfaces), + Arc::clone(&user_config), + )?, + gpu_interfaces: Arc::clone(&gpu_interfaces), }) } } + +#[interface(name = "com.github.opengamingcollective.cardwire")] +impl DaemonManager { + pub async fn status(&self) -> fdo::Result<()> { + Ok(()) + } +} From f77b830e9b7335943e3b02082b36726937c29532 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 21 May 2026 18:39:23 +0200 Subject: [PATCH 04/33] refactor(gpu-dbus): remove blocked var, get status directly from ebpf --- crates/cardwire-daemon/src/interface/gpu.rs | 41 +++++++++------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index f84b7f6..d668503 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -14,7 +14,6 @@ pub struct GpuInterface { pub device: GpuDevice, blocker: Arc>, pub pci_list: Arc>>, - blocked: bool, } impl GpuInterface { @@ -27,7 +26,6 @@ impl GpuInterface { device, blocker, pci_list, - blocked: false, }) } } @@ -39,15 +37,13 @@ impl GpuInterface { let pci_list = self.pci_list.read().await; block_gpu(&mut blocker, &self.device, true, &pci_list) .map_err(|e| fdo::Error::Failed(e.to_string()))?; - if let Ok(result) = is_gpu_blocked(&blocker, &self.device) { - if !result { - return Err(fdo::Error::Failed( - "gpu is supposed to be blocked, bpf says it's not".to_string(), - )); - } else { - self.blocked = result; - } - } + if let Ok(result) = is_gpu_blocked(&blocker, &self.device) + && !result + { + return Err(fdo::Error::Failed( + "gpu is supposed to be blocked, bpf says it's not".to_string(), + )); + }; Ok(()) } // unblock the gpu @@ -57,19 +53,18 @@ impl GpuInterface { block_gpu(&mut blocker, &self.device, false, &pci_list) .map_err(|e| fdo::Error::Failed(e.to_string()))?; - if let Ok(result) = is_gpu_blocked(&blocker, &self.device) { - if result { - return Err(fdo::Error::Failed( - "gpu is supposed to be unblocked, bpf says it's not".to_string(), - )); - } else { - self.blocked = result; - } - } + if let Ok(result) = is_gpu_blocked(&blocker, &self.device) + && result + { + return Err(fdo::Error::Failed( + "gpu is supposed to be unblocked, bpf says it's not".to_string(), + )); + }; Ok(()) } - pub fn blocked(&self) -> bool { - self.blocked + pub async fn gpu_blocked(&self) -> fdo::Result { + let blocker = self.blocker.read().await; + is_gpu_blocked(&blocker, &self.device).map_err(|e| fdo::Error::Failed(e.to_string())) } } @@ -92,6 +87,6 @@ impl GpuInterface { #[zbus(property)] pub async fn block(&self) -> fdo::Result { - Ok(self.blocked()) + self.gpu_blocked().await } } From e972ac7c948caf461786546da06a05afd6433a0a Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 21 May 2026 18:57:12 +0200 Subject: [PATCH 05/33] feat(mode-dbus): auto apply gpu state on manual mode --- crates/cardwire-daemon/src/file/state.rs | 2 +- crates/cardwire-daemon/src/interface/mode.rs | 24 ++++++++++++++++++-- crates/cardwire-daemon/src/models.rs | 7 +++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/cardwire-daemon/src/file/state.rs b/crates/cardwire-daemon/src/file/state.rs index 4ea9472..cbdeb8a 100644 --- a/crates/cardwire-daemon/src/file/state.rs +++ b/crates/cardwire-daemon/src/file/state.rs @@ -144,7 +144,7 @@ impl CardwireGpuState { self.gpu.contains_key("Null") } /// search key in gpu hashmap, - pub fn gpu_block_state(&self, pci: &String) -> bool { + pub fn gpu_block_state(&self, pci: &str) -> bool { match self.gpu.get_key_value(pci) { Some(value) => value.1.block, None => false, diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index 3a48fc4..6d75b58 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -1,6 +1,6 @@ //! Define the mode dbus use crate::{ - file::{CardwireConfig, CardwireModeState}, interface::GpuInterface + file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::GpuInterface }; use anyhow::Result; use log::{error, info, warn}; @@ -44,6 +44,7 @@ impl Modes { #[derive(Clone)] pub struct ModeInterface { pub mode_state: Arc>, + gpu_state: Arc>, pub gpu_list: Arc>>, pub config: Arc>, } @@ -51,11 +52,13 @@ pub struct ModeInterface { impl ModeInterface { pub fn build( mode_state: Arc>, + gpu_state: Arc>, gpu_list: Arc>>, config: Arc>, ) -> Result { Ok(ModeInterface { mode_state, + gpu_state, gpu_list, config, }) @@ -108,8 +111,25 @@ impl ModeInterface { // Else apply the gpu_state but still unblock other gpus Modes::Manual => { //let gpu_state = self.state.gpu_state.read().await; + let config = self.config.read().await; + let gpu_state = self.gpu_state.read().await; for (_, gpu) in gpu_list.iter_mut() { - gpu.unblock_gpu().await?; + if gpu_state.gpu_block_state(gpu.device.pci().pci_address()) + && config.auto_apply_gpu_state() + { + if gpu.device.is_default() { + // For safety, warn and unblock if default + warn!( + "auto_apply_gpu_state tried to block gpu: {}, which is the default gpu, unblocking for safety...", + gpu.device.name() + ); + gpu.unblock_gpu().await?; + } else { + gpu.block().await?; + } + } else { + gpu.unblock_gpu().await?; + } } } } diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index ef684a3..f0e1d42 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,6 +1,6 @@ //! where the struct and impl are declared use crate::{ - file::{CardwireConfig, CardwireModeState}, interface::{GpuInterface, ModeInterface} + file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{GpuInterface, ModeInterface} }; use anyhow::{Context, Result}; use cardwire_core::{ @@ -43,6 +43,9 @@ impl DaemonManager { let user_config = CardwireConfig::build().context("Error building toml config")?; let user_config: Arc> = Arc::new(RwLock::new(user_config)); + let gpu_state: CardwireGpuState = CardwireGpuState::build()?; + let gpu_state: Arc> = Arc::new(RwLock::new(gpu_state)); + let pci_devices: BTreeMap = pci::read_pci_devices()?; let mut gpu_list = gpu::read_gpu(&pci_devices)?; @@ -69,6 +72,7 @@ impl DaemonManager { Ok(Self { mode_interface: ModeInterface::build( mode_state, + Arc::clone(&gpu_state), Arc::clone(&gpu_interfaces), Arc::clone(&user_config), )?, @@ -78,6 +82,7 @@ impl DaemonManager { } #[interface(name = "com.github.opengamingcollective.cardwire")] +// simple dbus to check if the daemon is alive impl DaemonManager { pub async fn status(&self) -> fdo::Result<()> { Ok(()) From d45fa499ffd7b29f527a0d06855d7a1bcb7e7375 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 21 May 2026 19:26:59 +0200 Subject: [PATCH 06/33] feat(gpu-dbus): safety check for individual gpu --- crates/cardwire-daemon/src/daemon.rs | 2 +- crates/cardwire-daemon/src/file/state.rs | 20 ++++--------- crates/cardwire-daemon/src/interface/gpu.rs | 33 +++++++++++++++++++-- crates/cardwire-daemon/src/models.rs | 9 ++++-- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index a6b4ae4..a8de3f2 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -28,7 +28,7 @@ async fn main() -> Result<()> { let object_server = conn.object_server(); let gpu_interfaces = daemon.gpu_interfaces.read().await; - let path = format!("/com/github/opengamingcollective/cardwire/Mode"); + let path = "/com/github/opengamingcollective/cardwire/Mode"; object_server.at(path, daemon.mode_interface).await?; for (id, gpu_interface) in gpu_interfaces.iter() { let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); diff --git a/crates/cardwire-daemon/src/file/state.rs b/crates/cardwire-daemon/src/file/state.rs index cbdeb8a..ad184ac 100644 --- a/crates/cardwire-daemon/src/file/state.rs +++ b/crates/cardwire-daemon/src/file/state.rs @@ -4,7 +4,7 @@ use crate::{ file::common::{FileKind, create_default_file}, interface::Modes }; use anyhow::{Context, Ok}; -use cardwire_core::gpu::{GpuBlocker, GpuDevice, is_gpu_blocked}; +use cardwire_core::gpu::GpuDevice; use log::{info, warn}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fs}; @@ -115,25 +115,17 @@ impl CardwireGpuState { Ok(()) } /// Save the new state into the daemon and to the gpu_state.json file - pub async fn save_state( - &mut self, - gpu_list: &BTreeMap, - blocker: &GpuBlocker, - ) -> anyhow::Result<()> { + pub async fn save_state(&mut self, gpu: &GpuDevice, state: bool) -> anyhow::Result<()> { // Prevent overwriting default config if it's not replaceable if self.gpu.contains_key("Null") { info!("detected default gpu_state file, overwriting it..."); self.gpu.clear(); } // Save to daemon state - for gpu in gpu_list.values() { - self.gpu.insert( - gpu.pci.pci_address().to_string(), - CardwireGpuUnit { - block: is_gpu_blocked(blocker, gpu)?, - }, - ); - } + self.gpu.insert( + gpu.pci.pci_address().to_string(), + CardwireGpuUnit { block: state }, + ); // Save the whole hashmap into json let state_file = serde_json::to_string_pretty(&self.gpu)?; tokio::fs::write(format!("{STATE_PATH}/gpu_state.json"), state_file).await?; diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index d668503..3d8228f 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -5,15 +5,19 @@ use std::{collections::BTreeMap, sync::Arc}; use cardwire_core::{ gpu::{GpuBlocker, GpuDevice, block_gpu, is_gpu_blocked}, pci::PciDevice }; +use log::{info, warn}; use tokio::sync::RwLock; use zbus::{fdo, interface}; +use crate::file::CardwireGpuState; + // Represent a single gpu #[derive(Clone)] pub struct GpuInterface { pub device: GpuDevice, blocker: Arc>, pub pci_list: Arc>>, + gpu_state: Arc>, } impl GpuInterface { @@ -21,11 +25,13 @@ impl GpuInterface { device: GpuDevice, blocker: Arc>, pci_list: Arc>>, + gpu_state: Arc>, ) -> anyhow::Result { Ok(Self { device, blocker, pci_list, + gpu_state, }) } } @@ -79,9 +85,32 @@ impl GpuInterface { #[zbus(property)] pub async fn set_block(&mut self, block: bool) -> fdo::Result<()> { if block { - self.block_gpu().await + // Don't block if default + if self.device.is_default() { + return Err(fdo::Error::AccessDenied(format!( + "GPU {} is the default device and cannot be blocked", + self.device.name() + ))); + } + // Now block + self.block_gpu().await?; + info!("Set GPU {} block={}", self.device.name(), block); + // save new state to file + let mut gpu_state = self.gpu_state.write().await; + if let Err(e) = gpu_state.save_state(&self.device, true).await { + warn!("could not save gpu_state to file: {e}"); + }; + Ok(()) } else { - self.unblock_gpu().await + // unblock + self.unblock_gpu().await?; + info!("Set GPU {} block={}", self.device.name(), block); + // save new state to file + let mut gpu_state = self.gpu_state.write().await; + if let Err(e) = gpu_state.save_state(&self.device, false).await { + warn!("could not save gpu_state to file: {e}"); + }; + Ok(()) } } diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index f0e1d42..b699657 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -20,7 +20,7 @@ use zbus::{ // "max_link_width", // "current_link_width", //]; -//// Files that get blocked when the NVIDIA block is on +/// Files that get blocked when the NVIDIA block is on //const BLOCKED_NVIDIA_FILES: &[&str] = &[ // "libGLX_nvidia.so.0", // "nvidia_icd.json", @@ -62,7 +62,12 @@ impl DaemonManager { let mut gpu_interfaces_map: BTreeMap = BTreeMap::new(); for (id, device) in gpu_list { - let gpu = GpuInterface::build(device, Arc::clone(&blocker), Arc::clone(&pci_list))?; + let gpu = GpuInterface::build( + device, + Arc::clone(&blocker), + Arc::clone(&pci_list), + Arc::clone(&gpu_state), + )?; gpu_interfaces_map.insert(id, gpu); } From a99be6f06e325d0dc7ad95c4473056d4349f1c36 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 21 May 2026 19:39:42 +0200 Subject: [PATCH 07/33] feat(dbus): move mode to root, same for manager --- crates/cardwire-daemon/src/daemon.rs | 2 +- crates/cardwire-daemon/src/interface/gpu.rs | 8 +------- crates/cardwire-daemon/src/interface/mode.rs | 8 +------- crates/cardwire-daemon/src/models.rs | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index a8de3f2..d80e60c 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -28,7 +28,7 @@ async fn main() -> Result<()> { let object_server = conn.object_server(); let gpu_interfaces = daemon.gpu_interfaces.read().await; - let path = "/com/github/opengamingcollective/cardwire/Mode"; + let path = "/com/github/opengamingcollective/cardwire"; object_server.at(path, daemon.mode_interface).await?; for (id, gpu_interface) in gpu_interfaces.iter() { let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 3d8228f..f079b1a 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -74,13 +74,7 @@ impl GpuInterface { } } -#[interface( - name = "com.github.opengamingcollective.Gpu", - proxy( - default_service = "com.github.opengamingcollective.cardwire", - default_path = "/com/github/opengamingcollective/cardwire" - ) -)] +#[interface(name = "com.github.opengamingcollective.cardwire.Gpu")] impl GpuInterface { #[zbus(property)] pub async fn set_block(&mut self, block: bool) -> fdo::Result<()> { diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index 6d75b58..f49f6c2 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -65,13 +65,7 @@ impl ModeInterface { } } -#[interface( - name = "com.github.opengamingcollective.Mode", - proxy( - default_service = "com.github.opengamingcollective.cardwire", - default_path = "/com/github/opengamingcollective/cardwire" - ) -)] +#[interface(name = "com.github.opengamingcollective.cardwire.Mode")] impl ModeInterface { /* Set the mode diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index b699657..d9cfe4b 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -86,7 +86,7 @@ impl DaemonManager { } } -#[interface(name = "com.github.opengamingcollective.cardwire")] +#[interface(name = "com.github.opengamingcollective.cardwire.Manager")] // simple dbus to check if the daemon is alive impl DaemonManager { pub async fn status(&self) -> fdo::Result<()> { From 6e9d21cd4675d2478c29c854f0be40a98d19aa28 Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 22 May 2026 17:26:48 +0200 Subject: [PATCH 08/33] feat(dbus): config API, not finished yet --- crates/cardwire-daemon/src/daemon.rs | 2 + .../cardwire-daemon/src/interface/config.rs | 72 +++++++++++++++++++ crates/cardwire-daemon/src/interface/mod.rs | 2 + crates/cardwire-daemon/src/interface/mode.rs | 12 ++-- crates/cardwire-daemon/src/models.rs | 9 ++- 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index d80e60c..f81b9a6 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -30,6 +30,8 @@ async fn main() -> Result<()> { let gpu_interfaces = daemon.gpu_interfaces.read().await; let path = "/com/github/opengamingcollective/cardwire"; object_server.at(path, daemon.mode_interface).await?; + object_server.at(path, daemon.config_interface).await?; + for (id, gpu_interface) in gpu_interfaces.iter() { let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); object_server.at(path, gpu_interface.clone()).await?; diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index e69de29..8a86b74 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use crate::file::CardwireConfig; +use tokio::sync::RwLock; +use zbus::{fdo, interface}; + +pub struct ConfigMemory { + pub auto_apply_gpu_state: Arc>, + pub experimental_nvidia_block: Arc>, + pub battery_auto_switch: Arc>, +} +impl ConfigMemory { + pub fn build(user_config: CardwireConfig) -> ConfigMemory { + let auto_apply_gpu_state = Arc::new(RwLock::new(user_config.auto_apply_gpu_state())); + let experimental_nvidia_block = + Arc::new(RwLock::new(user_config.experimental_nvidia_block())); + let battery_auto_switch = Arc::new(RwLock::new(user_config.battery_auto_switch())); + + ConfigMemory { + auto_apply_gpu_state, + experimental_nvidia_block, + battery_auto_switch, + } + } +} + +#[derive(Clone)] +pub struct ConfigInterface { + pub config: Arc, +} +impl ConfigInterface { + pub fn build(config: Arc) -> anyhow::Result { + Ok(Self { config }) + } +} + +#[interface(name = "com.github.opengamingcollective.cardwire.Config")] +impl ConfigInterface { + #[zbus(property)] + pub async fn auto_apply_gpu_state(&self) -> fdo::Result { + let current_config = self.config.auto_apply_gpu_state.read().await; + Ok(*current_config) + } + #[zbus(property)] + pub async fn set_auto_apply_gpu_state(&mut self, state: bool) -> fdo::Result<()> { + let mut current_config = self.config.auto_apply_gpu_state.write().await; + *current_config = state; + Ok(()) + } + #[zbus(property)] + pub async fn experimental_nvidia_block(&self) -> fdo::Result { + let current_config = self.config.experimental_nvidia_block.read().await; + Ok(*current_config) + } + #[zbus(property)] + pub async fn set_experimental_nvidia_block(&mut self, state: bool) -> fdo::Result<()> { + let mut current_config = self.config.experimental_nvidia_block.write().await; + *current_config = state; + Ok(()) + } + #[zbus(property)] + pub async fn battery_auto_switch(&self) -> fdo::Result { + let current_config = self.config.battery_auto_switch.read().await; + Ok(*current_config) + } + #[zbus(property)] + pub async fn set_battery_auto_switch(&mut self, state: bool) -> fdo::Result<()> { + let mut current_config = self.config.battery_auto_switch.write().await; + *current_config = state; + Ok(()) + } +} diff --git a/crates/cardwire-daemon/src/interface/mod.rs b/crates/cardwire-daemon/src/interface/mod.rs index 4d58943..371f3b5 100644 --- a/crates/cardwire-daemon/src/interface/mod.rs +++ b/crates/cardwire-daemon/src/interface/mod.rs @@ -1,5 +1,7 @@ +mod config; mod gpu; mod mode; +pub use config::{ConfigInterface, ConfigMemory}; pub use gpu::GpuInterface; pub use mode::{ModeInterface, Modes}; diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index f49f6c2..a1d4d03 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -1,6 +1,6 @@ //! Define the mode dbus use crate::{ - file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::GpuInterface + file::{CardwireGpuState, CardwireModeState}, interface::{GpuInterface, config::ConfigMemory} }; use anyhow::Result; use log::{error, info, warn}; @@ -46,7 +46,7 @@ pub struct ModeInterface { pub mode_state: Arc>, gpu_state: Arc>, pub gpu_list: Arc>>, - pub config: Arc>, + pub config: Arc, } impl ModeInterface { @@ -54,7 +54,7 @@ impl ModeInterface { mode_state: Arc>, gpu_state: Arc>, gpu_list: Arc>>, - config: Arc>, + config: Arc, ) -> Result { Ok(ModeInterface { mode_state, @@ -105,12 +105,10 @@ impl ModeInterface { // Else apply the gpu_state but still unblock other gpus Modes::Manual => { //let gpu_state = self.state.gpu_state.read().await; - let config = self.config.read().await; + let config = self.config.auto_apply_gpu_state.read().await; let gpu_state = self.gpu_state.read().await; for (_, gpu) in gpu_list.iter_mut() { - if gpu_state.gpu_block_state(gpu.device.pci().pci_address()) - && config.auto_apply_gpu_state() - { + if gpu_state.gpu_block_state(gpu.device.pci().pci_address()) && *config { if gpu.device.is_default() { // For safety, warn and unblock if default warn!( diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index d9cfe4b..5c13357 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,6 +1,6 @@ //! where the struct and impl are declared use crate::{ - file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{GpuInterface, ModeInterface} + file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{ConfigInterface, ConfigMemory, GpuInterface, ModeInterface} }; use anyhow::{Context, Result}; use cardwire_core::{ @@ -32,6 +32,7 @@ use zbus::{ pub struct DaemonManager { pub mode_interface: ModeInterface, pub gpu_interfaces: Arc>>, + pub config_interface: ConfigInterface, } impl DaemonManager { @@ -40,8 +41,9 @@ impl DaemonManager { CardwireModeState::build().context("Error building mode")?; let mode_state: Arc> = Arc::new(RwLock::new(mode_state)); - let user_config = CardwireConfig::build().context("Error building toml config")?; - let user_config: Arc> = Arc::new(RwLock::new(user_config)); + let user_config: CardwireConfig = + CardwireConfig::build().context("Error building toml config")?; + let user_config = Arc::new(ConfigMemory::build(user_config)); let gpu_state: CardwireGpuState = CardwireGpuState::build()?; let gpu_state: Arc> = Arc::new(RwLock::new(gpu_state)); @@ -82,6 +84,7 @@ impl DaemonManager { Arc::clone(&user_config), )?, gpu_interfaces: Arc::clone(&gpu_interfaces), + config_interface: ConfigInterface::build(Arc::clone(&user_config))?, }) } } From 22aa9a1881d6c98831d870271d2daa8e682f424b Mon Sep 17 00:00:00 2001 From: luytan Date: Sat, 23 May 2026 10:46:14 +0200 Subject: [PATCH 09/33] feat(config-dbus): save config to file --- crates/cardwire-daemon/src/file/config.rs | 30 +++++++++++++++++-- .../cardwire-daemon/src/interface/config.rs | 16 ++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/cardwire-daemon/src/file/config.rs b/crates/cardwire-daemon/src/file/config.rs index 146f565..1449d83 100644 --- a/crates/cardwire-daemon/src/file/config.rs +++ b/crates/cardwire-daemon/src/file/config.rs @@ -1,10 +1,11 @@ //! helper to manage cardwired configs, include the user config .toml, and the .json states like //! gpu, mode or pci use crate::file::common::{FileKind, create_default_file}; -use anyhow::{Context, Ok}; +use anyhow::Context; use serde::{Deserialize, Serialize}; use std::fs; +use zbus::fdo; const CONFIG_PATH: &str = "/etc/cardwire"; #[derive(Deserialize, Serialize, Debug)] @@ -24,8 +25,18 @@ impl Default for CardwireConfig { } } impl CardwireConfig { + pub fn new( + auto_apply_gpu_state: bool, + experimental_nvidia_block: bool, + battery_auto_switch: bool, + ) -> CardwireConfig { + CardwireConfig { + auto_apply_gpu_state, + experimental_nvidia_block, + battery_auto_switch, + } + } /// Read TOML config file and return it's settings as a struct - // TODO: Error handling on std::fs pub fn build() -> anyhow::Result { let config_file = format!("{}/cardwire.toml", CONFIG_PATH); Self::parse_config(&config_file) @@ -37,13 +48,26 @@ impl CardwireConfig { } let config_content = fs::read_to_string(config_file).context("Could not read cardwire.toml")?; - Ok(toml::from_str(&config_content).context("Failed to parse the toml config")?) + toml::from_str(&config_content).context("Failed to parse the toml config") } /// Create a default cardwire.toml if not present fn create_default_config() -> anyhow::Result<()> { create_default_file(FileKind::Config)?; Ok(()) } + /// Save the config into cardwire.toml + pub async fn save_config(&self) -> fdo::Result<()> { + let path = format!("{}/cardwire.toml", CONFIG_PATH); + match toml::to_string_pretty(&self) { + Ok(config_toml) => { + if let Err(e) = tokio::fs::write(path, config_toml).await { + return Err(fdo::Error::Failed(e.to_string())); + } + } + Err(e) => return Err(fdo::Error::Failed(e.to_string())), + }; + Ok(()) + } pub fn experimental_nvidia_block(&self) -> bool { self.experimental_nvidia_block } diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index 8a86b74..4d8d37d 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -4,6 +4,7 @@ use crate::file::CardwireConfig; use tokio::sync::RwLock; use zbus::{fdo, interface}; +// Use a custom Config struct instead of CarwireConfig to allow more control over the settings pub struct ConfigMemory { pub auto_apply_gpu_state: Arc>, pub experimental_nvidia_block: Arc>, @@ -69,4 +70,19 @@ impl ConfigInterface { *current_config = state; Ok(()) } + /// Save the daemon's configuration to cardwire.toml + pub async fn save_to_file(&self) -> fdo::Result<()> { + // lock the whole config + let battery_auto_switch = self.config.battery_auto_switch.read().await; + let experimental_nvidia_block = self.config.experimental_nvidia_block.read().await; + let auto_apply_gpu_state = self.config.auto_apply_gpu_state.read().await; + + let config = CardwireConfig::new( + *auto_apply_gpu_state, + *experimental_nvidia_block, + *battery_auto_switch, + ); + config.save_config().await?; + Ok(()) + } } From e85311cfd9dbccfe3ca7077a3d930377fc0bc808 Mon Sep 17 00:00:00 2001 From: luytan Date: Sat, 23 May 2026 10:50:50 +0200 Subject: [PATCH 10/33] feat(dbus): Debug interface and pre daemon tasks --- crates/cardwire-daemon/src/daemon.rs | 9 +- crates/cardwire-daemon/src/interface/debug.rs | 53 +++++++++++ crates/cardwire-daemon/src/interface/mod.rs | 2 + crates/cardwire-daemon/src/models.rs | 95 ++++++++++++++++--- 4 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 crates/cardwire-daemon/src/interface/debug.rs diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index f81b9a6..ea57bdd 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -19,6 +19,9 @@ async fn main() -> Result<()> { .init(); let daemon = DaemonManager::new().await?; + // Before we publish the API + daemon.pre_daemon_tasks().await?; + let conn_builder = connection::Builder::system()?; let conn = conn_builder .name("com.github.opengamingcollective.cardwire")? @@ -29,9 +32,13 @@ async fn main() -> Result<()> { let object_server = conn.object_server(); let gpu_interfaces = daemon.gpu_interfaces.read().await; let path = "/com/github/opengamingcollective/cardwire"; + // cardwire.Mode object_server.at(path, daemon.mode_interface).await?; + // cardwire.Config object_server.at(path, daemon.config_interface).await?; - + // cardwire.Debug + object_server.at(path, daemon.debug_interface).await?; + // cardwire.Gpu for (id, gpu_interface) in gpu_interfaces.iter() { let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); object_server.at(path, gpu_interface.clone()).await?; diff --git a/crates/cardwire-daemon/src/interface/debug.rs b/crates/cardwire-daemon/src/interface/debug.rs new file mode 100644 index 0000000..7c43bd6 --- /dev/null +++ b/crates/cardwire-daemon/src/interface/debug.rs @@ -0,0 +1,53 @@ +use cardwire_core::{gpu::GpuBlocker, pci::PciDevice}; +use std::{collections::BTreeMap, sync::Arc}; +use tokio::sync::RwLock; +use zbus::{fdo, interface, object_server::SignalEmitter}; + +use crate::{ + file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface} +}; + +#[derive(Clone)] +pub struct DebugInterface { + pub mode_state: Arc>, + pub gpu_state: Arc>, + pub gpu_list: Arc>>, + pub config: Arc, + pub blocker: Arc>, + pub pci_list: Arc>>, +} +impl DebugInterface { + pub fn build( + mode_state: Arc>, + gpu_state: Arc>, + gpu_list: Arc>>, + config: Arc, + blocker: Arc>, + pci_list: Arc>>, + ) -> anyhow::Result { + Ok(DebugInterface { + mode_state, + gpu_state, + gpu_list, + config, + blocker, + pci_list, + }) + } +} + +#[interface(name = "com.github.opengamingcollective.cardwire.Debug")] +impl DebugInterface { + async fn diagnostic_gpu( + &self, + #[zbus(signal_emitter)] emitter: SignalEmitter<'_>, + ) -> fdo::Result<()> { + let emitter = emitter.into_owned(); + emitter.diagnostic_info("Hello").await; + Ok(()) + } + #[zbus(signal)] + async fn diagnostic_info(emitter: &SignalEmitter<'_>, text: &str) -> zbus::Result<()>; + #[zbus(signal)] + async fn diagnostic_ended(emitter: &SignalEmitter<'_>) -> zbus::Result<()>; +} diff --git a/crates/cardwire-daemon/src/interface/mod.rs b/crates/cardwire-daemon/src/interface/mod.rs index 371f3b5..9213824 100644 --- a/crates/cardwire-daemon/src/interface/mod.rs +++ b/crates/cardwire-daemon/src/interface/mod.rs @@ -1,7 +1,9 @@ mod config; +mod debug; mod gpu; mod mode; pub use config::{ConfigInterface, ConfigMemory}; +pub use debug::DebugInterface; pub use gpu::GpuInterface; pub use mode::{ModeInterface, Modes}; diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 5c13357..cfc90fc 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,6 +1,8 @@ //! where the struct and impl are declared use crate::{ - file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{ConfigInterface, ConfigMemory, GpuInterface, ModeInterface} + file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{ + ConfigInterface, ConfigMemory, DebugInterface, GpuInterface, ModeInterface, Modes + } }; use anyhow::{Context, Result}; use cardwire_core::{ @@ -13,26 +15,27 @@ use zbus::{ fdo::{self}, interface }; -//const BLOCKED_PCI_FILES: &[&str] = &[ -// "config", -// "current_link_speed", -// "max_link_speed", -// "max_link_width", -// "current_link_width", -//]; +const BLOCKED_PCI_FILES: &[&str] = &[ + "config", + "current_link_speed", + "max_link_speed", + "max_link_width", + "current_link_width", +]; /// Files that get blocked when the NVIDIA block is on -//const BLOCKED_NVIDIA_FILES: &[&str] = &[ -// "libGLX_nvidia.so.0", -// "nvidia_icd.json", -// "nvidia_icd.x86_64.json", -// "nvidiactl", -//]; +const BLOCKED_NVIDIA_FILES: &[&str] = &[ + "libGLX_nvidia.so.0", + "nvidia_icd.json", + "nvidia_icd.x86_64.json", + "nvidiactl", +]; #[derive(Clone)] pub struct DaemonManager { pub mode_interface: ModeInterface, pub gpu_interfaces: Arc>>, pub config_interface: ConfigInterface, + pub debug_interface: DebugInterface, } impl DaemonManager { @@ -78,15 +81,73 @@ impl DaemonManager { Ok(Self { mode_interface: ModeInterface::build( - mode_state, + Arc::clone(&mode_state), Arc::clone(&gpu_state), Arc::clone(&gpu_interfaces), Arc::clone(&user_config), )?, gpu_interfaces: Arc::clone(&gpu_interfaces), config_interface: ConfigInterface::build(Arc::clone(&user_config))?, + debug_interface: DebugInterface::build( + Arc::clone(&mode_state), + Arc::clone(&gpu_state), + Arc::clone(&gpu_interfaces), + Arc::clone(&user_config), + Arc::clone(&blocker), + Arc::clone(&pci_list), + )?, }) } + + /// Tasks that need to be run before running the daemon, like applying the mode, + pub async fn pre_daemon_tasks(&self) -> Result<()> { + let gpus_list = self.debug_interface.gpu_list.read().await; + let config = self + .debug_interface + .config + .experimental_nvidia_block + .read() + .await; + let mode = self.debug_interface.mode_state.read().await; + let mut blocker = self.debug_interface.blocker.write().await; + // steal a gpu blocker + blocker.set_nvidia_setting(*config)?; + + for file in BLOCKED_PCI_FILES { + blocker.set_file_block(file)?; + } + let mut default: bool = false; + // if there is an nvidia device, block nvidia file once + for (_, gpu) in gpus_list.iter() { + let state = gpu.gpu_state.read().await; + default = state.is_default_state(); + if gpu.device.nvidia() { + for file in BLOCKED_NVIDIA_FILES { + blocker.set_nvidia_file_block(file)?; + } + break; + } + } + if default { + for (_, gpu) in gpus_list.iter() { + let mut state = gpu.gpu_state.write().await; + state.save_state(&gpu.device, false).await?; + } + } + // Dropping the locks prevent set_mode being stuck + drop(blocker); + drop(config); + drop(gpus_list); + let mode_to_apply = mode.mode(); + drop(mode); + let mode_to_apply: u32 = match mode_to_apply { + Modes::Integrated => 0, + Modes::Hybrid => 1, + Modes::Manual => 2, + }; + self.mode_interface.set_mode(mode_to_apply).await?; + Ok(()) + } } #[interface(name = "com.github.opengamingcollective.cardwire.Manager")] @@ -95,4 +156,8 @@ impl DaemonManager { pub async fn status(&self) -> fdo::Result<()> { Ok(()) } + pub async fn refresh_gpu(&self) -> fdo::Result<()> { + let _gpu_interfaces = self.gpu_interfaces.write().await; + Ok(()) + } } From 5fc506a602937220780d047588c2551069443053 Mon Sep 17 00:00:00 2001 From: luytan Date: Sat, 23 May 2026 10:53:10 +0200 Subject: [PATCH 11/33] fix(daemon): use debug gpu_state --- crates/cardwire-daemon/src/models.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index cfc90fc..ce7ff44 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -110,17 +110,15 @@ impl DaemonManager { .await; let mode = self.debug_interface.mode_state.read().await; let mut blocker = self.debug_interface.blocker.write().await; - // steal a gpu blocker + let mut state = self.debug_interface.gpu_state.write().await; blocker.set_nvidia_setting(*config)?; for file in BLOCKED_PCI_FILES { blocker.set_file_block(file)?; } - let mut default: bool = false; + let default: bool = state.is_default_state(); // if there is an nvidia device, block nvidia file once for (_, gpu) in gpus_list.iter() { - let state = gpu.gpu_state.read().await; - default = state.is_default_state(); if gpu.device.nvidia() { for file in BLOCKED_NVIDIA_FILES { blocker.set_nvidia_file_block(file)?; @@ -130,7 +128,6 @@ impl DaemonManager { } if default { for (_, gpu) in gpus_list.iter() { - let mut state = gpu.gpu_state.write().await; state.save_state(&gpu.device, false).await?; } } From 92ed14ca699978064023cfc6fef9832325e287e0 Mon Sep 17 00:00:00 2001 From: luytan Date: Sat, 23 May 2026 11:11:20 +0200 Subject: [PATCH 12/33] feat(gpu-dbus): prevent manual block if mode isn't manual --- crates/cardwire-daemon/src/interface/gpu.rs | 14 +++++++++++++- crates/cardwire-daemon/src/models.rs | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index f079b1a..ccca9f4 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -9,7 +9,9 @@ use log::{info, warn}; use tokio::sync::RwLock; use zbus::{fdo, interface}; -use crate::file::CardwireGpuState; +use crate::{ + file::{CardwireGpuState, CardwireModeState}, interface::Modes +}; // Represent a single gpu #[derive(Clone)] @@ -18,6 +20,7 @@ pub struct GpuInterface { blocker: Arc>, pub pci_list: Arc>>, gpu_state: Arc>, + mode_state: Arc>, } impl GpuInterface { @@ -26,12 +29,14 @@ impl GpuInterface { blocker: Arc>, pci_list: Arc>>, gpu_state: Arc>, + mode_state: Arc>, ) -> anyhow::Result { Ok(Self { device, blocker, pci_list, gpu_state, + mode_state, }) } } @@ -78,6 +83,13 @@ impl GpuInterface { impl GpuInterface { #[zbus(property)] pub async fn set_block(&mut self, block: bool) -> fdo::Result<()> { + let mode = self.mode_state.read().await; + if mode.mode() != Modes::Manual { + return Err(fdo::Error::AccessDenied( + "Per GPU block is only available on manual mode".to_string(), + )); + } + drop(mode); if block { // Don't block if default if self.device.is_default() { diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index ce7ff44..c9873b6 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -72,6 +72,7 @@ impl DaemonManager { Arc::clone(&blocker), Arc::clone(&pci_list), Arc::clone(&gpu_state), + Arc::clone(&mode_state), )?; gpu_interfaces_map.insert(id, gpu); } @@ -135,6 +136,7 @@ impl DaemonManager { drop(blocker); drop(config); drop(gpus_list); + drop(state); let mode_to_apply = mode.mode(); drop(mode); let mode_to_apply: u32 = match mode_to_apply { From 4cc3f98905352d774745de75345c4f2729891c69 Mon Sep 17 00:00:00 2001 From: luytan Date: Sat, 23 May 2026 12:53:20 +0200 Subject: [PATCH 13/33] feat(config-dbus): replace Rwlock with atomic for config --- .../cardwire-daemon/src/interface/config.rs | 57 ++++++++++--------- crates/cardwire-daemon/src/interface/mode.rs | 11 +++- crates/cardwire-daemon/src/models.rs | 6 +- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index 4d8d37d..4b97f11 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -1,21 +1,22 @@ -use std::sync::Arc; +use std::sync::{ + Arc, atomic::{AtomicBool, Ordering} +}; use crate::file::CardwireConfig; -use tokio::sync::RwLock; use zbus::{fdo, interface}; // Use a custom Config struct instead of CarwireConfig to allow more control over the settings pub struct ConfigMemory { - pub auto_apply_gpu_state: Arc>, - pub experimental_nvidia_block: Arc>, - pub battery_auto_switch: Arc>, + pub auto_apply_gpu_state: Arc, + pub experimental_nvidia_block: Arc, + pub battery_auto_switch: Arc, } impl ConfigMemory { pub fn build(user_config: CardwireConfig) -> ConfigMemory { - let auto_apply_gpu_state = Arc::new(RwLock::new(user_config.auto_apply_gpu_state())); + let auto_apply_gpu_state = Arc::new(AtomicBool::new(user_config.auto_apply_gpu_state())); let experimental_nvidia_block = - Arc::new(RwLock::new(user_config.experimental_nvidia_block())); - let battery_auto_switch = Arc::new(RwLock::new(user_config.battery_auto_switch())); + Arc::new(AtomicBool::new(user_config.experimental_nvidia_block())); + let battery_auto_switch = Arc::new(AtomicBool::new(user_config.battery_auto_switch())); ConfigMemory { auto_apply_gpu_state, @@ -39,48 +40,48 @@ impl ConfigInterface { impl ConfigInterface { #[zbus(property)] pub async fn auto_apply_gpu_state(&self) -> fdo::Result { - let current_config = self.config.auto_apply_gpu_state.read().await; - Ok(*current_config) + Ok(self.config.auto_apply_gpu_state.load(Ordering::Relaxed)) } #[zbus(property)] pub async fn set_auto_apply_gpu_state(&mut self, state: bool) -> fdo::Result<()> { - let mut current_config = self.config.auto_apply_gpu_state.write().await; - *current_config = state; + self.config + .auto_apply_gpu_state + .store(state, Ordering::Relaxed); Ok(()) } #[zbus(property)] pub async fn experimental_nvidia_block(&self) -> fdo::Result { - let current_config = self.config.experimental_nvidia_block.read().await; - Ok(*current_config) + Ok(self + .config + .experimental_nvidia_block + .load(Ordering::Relaxed)) } #[zbus(property)] pub async fn set_experimental_nvidia_block(&mut self, state: bool) -> fdo::Result<()> { - let mut current_config = self.config.experimental_nvidia_block.write().await; - *current_config = state; + self.config + .experimental_nvidia_block + .store(state, Ordering::Relaxed); Ok(()) } #[zbus(property)] pub async fn battery_auto_switch(&self) -> fdo::Result { - let current_config = self.config.battery_auto_switch.read().await; - Ok(*current_config) + Ok(self.config.battery_auto_switch.load(Ordering::Relaxed)) } #[zbus(property)] pub async fn set_battery_auto_switch(&mut self, state: bool) -> fdo::Result<()> { - let mut current_config = self.config.battery_auto_switch.write().await; - *current_config = state; + self.config + .battery_auto_switch + .store(state, Ordering::Relaxed); Ok(()) } /// Save the daemon's configuration to cardwire.toml pub async fn save_to_file(&self) -> fdo::Result<()> { - // lock the whole config - let battery_auto_switch = self.config.battery_auto_switch.read().await; - let experimental_nvidia_block = self.config.experimental_nvidia_block.read().await; - let auto_apply_gpu_state = self.config.auto_apply_gpu_state.read().await; - let config = CardwireConfig::new( - *auto_apply_gpu_state, - *experimental_nvidia_block, - *battery_auto_switch, + self.config.auto_apply_gpu_state.load(Ordering::Relaxed), + self.config + .experimental_nvidia_block + .load(Ordering::Relaxed), + self.config.battery_auto_switch.load(Ordering::Relaxed), ); config.save_config().await?; Ok(()) diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index a1d4d03..555f0c1 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -105,10 +105,14 @@ impl ModeInterface { // Else apply the gpu_state but still unblock other gpus Modes::Manual => { //let gpu_state = self.state.gpu_state.read().await; - let config = self.config.auto_apply_gpu_state.read().await; + let config = self + .config + .auto_apply_gpu_state + .load(std::sync::atomic::Ordering::Relaxed); let gpu_state = self.gpu_state.read().await; for (_, gpu) in gpu_list.iter_mut() { - if gpu_state.gpu_block_state(gpu.device.pci().pci_address()) && *config { + if gpu_state.gpu_block_state(gpu.device.pci().pci_address()) && config { + println!("config: {config}"); if gpu.device.is_default() { // For safety, warn and unblock if default warn!( @@ -117,7 +121,8 @@ impl ModeInterface { ); gpu.unblock_gpu().await?; } else { - gpu.block().await?; + println!("blocking: {} ", gpu.device.pci().pci_address()); + gpu.block_gpu().await?; } } else { gpu.unblock_gpu().await?; diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index c9873b6..fad25f8 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -107,12 +107,11 @@ impl DaemonManager { .debug_interface .config .experimental_nvidia_block - .read() - .await; + .load(std::sync::atomic::Ordering::Relaxed); let mode = self.debug_interface.mode_state.read().await; let mut blocker = self.debug_interface.blocker.write().await; let mut state = self.debug_interface.gpu_state.write().await; - blocker.set_nvidia_setting(*config)?; + blocker.set_nvidia_setting(config)?; for file in BLOCKED_PCI_FILES { blocker.set_file_block(file)?; @@ -134,7 +133,6 @@ impl DaemonManager { } // Dropping the locks prevent set_mode being stuck drop(blocker); - drop(config); drop(gpus_list); drop(state); let mode_to_apply = mode.mode(); From ebb67b35ebbab6a62886a8d1f5fd7f10678d40ec Mon Sep 17 00:00:00 2001 From: luytan Date: Sat, 23 May 2026 13:32:25 +0200 Subject: [PATCH 14/33] feat(daemon): auto battery switch task --- crates/cardwire-daemon/src/daemon.rs | 14 ++++++++++++-- .../src/{listeners.rs => tasks/battery_switch.rs} | 13 +++++++++---- crates/cardwire-daemon/src/tasks/mod.rs | 3 +++ 3 files changed, 24 insertions(+), 6 deletions(-) rename crates/cardwire-daemon/src/{listeners.rs => tasks/battery_switch.rs} (79%) create mode 100644 crates/cardwire-daemon/src/tasks/mod.rs diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index ea57bdd..d206c2c 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -1,13 +1,14 @@ //! entry point of cardwired mod file; mod interface; -mod listeners; mod models; +mod tasks; use crate::models::DaemonManager; use anyhow::Result; use log::info; -use std::future::pending; +use std::{future::pending, sync::Arc}; +use tokio::task; use zbus::connection; #[tokio::main] async fn main() -> Result<()> { @@ -22,6 +23,11 @@ async fn main() -> Result<()> { // Before we publish the API daemon.pre_daemon_tasks().await?; + // Prepare the future before moving debug + let battery_switch = tasks::watch_battery_status(Arc::clone( + &daemon.debug_interface.config.battery_auto_switch, + )); + let conn_builder = connection::Builder::system()?; let conn = conn_builder .name("com.github.opengamingcollective.cardwire")? @@ -45,6 +51,10 @@ async fn main() -> Result<()> { } // drop gpu list to prevent deadlock drop(gpu_interfaces); + + // Now spawn background tasks + let _ = task::spawn(battery_switch); + info!("Daemon started"); pending::<()>().await; Ok(()) diff --git a/crates/cardwire-daemon/src/listeners.rs b/crates/cardwire-daemon/src/tasks/battery_switch.rs similarity index 79% rename from crates/cardwire-daemon/src/listeners.rs rename to crates/cardwire-daemon/src/tasks/battery_switch.rs index daf05d1..80a669a 100644 --- a/crates/cardwire-daemon/src/listeners.rs +++ b/crates/cardwire-daemon/src/tasks/battery_switch.rs @@ -1,5 +1,7 @@ //! Used to listen to other dbus interface, mainly for auto battery switch and display detection +use std::sync::{Arc, atomic::AtomicBool}; + use log::info; use tokio_stream::StreamExt; use zbus::{Connection, Result, proxy}; @@ -14,7 +16,7 @@ trait UPower { fn on_battery(&self) -> Result; } #[proxy( - interface = "com.github.opengamingcollective.cardwire", + interface = "com.github.opengamingcollective.cardwire.Mode", default_service = "com.github.opengamingcollective.cardwire", default_path = "/com/github/opengamingcollective/cardwire" )] @@ -23,15 +25,18 @@ trait Cardwire { fn set_mode(&self, mode: u32) -> Result<()>; } -pub async fn watch_battery_status() -> zbus::Result<()> { +pub async fn watch_battery_status(setting: Arc) -> zbus::Result<()> { let connection = Connection::system().await?; let upower_proxy = UPowerProxy::new(&connection).await?; let cardwire = CardwireProxy::new(&connection).await?; - info!("Started listening to on_battery property"); let mut battery_stream = upower_proxy.receive_on_battery_changed().await; - + info!("Started listening to on_battery property"); + // only when setting is enabled while let Some(msg) = battery_stream.next().await { + if !setting.load(std::sync::atomic::Ordering::Relaxed) { + continue; + } if let Ok(state) = msg.get().await { info!("battery event detected: {:?}", state); match state { diff --git a/crates/cardwire-daemon/src/tasks/mod.rs b/crates/cardwire-daemon/src/tasks/mod.rs new file mode 100644 index 0000000..63c8fbf --- /dev/null +++ b/crates/cardwire-daemon/src/tasks/mod.rs @@ -0,0 +1,3 @@ +mod battery_switch; + +pub use battery_switch::watch_battery_status; From 7bd2cb8e7c1aab1f14ac95116ee76c24c3cd0f38 Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 15:11:41 +0200 Subject: [PATCH 15/33] feat(gpu-dbus): lsof function --- crates/cardwire-daemon/src/interface/gpu.rs | 81 ++++++++++++++++++++- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index ccca9f4..11e0d9d 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -1,7 +1,12 @@ //! DBUS Interface for single gpu interaction -use std::{collections::BTreeMap, sync::Arc}; +use std::{ + collections::BTreeMap, ffi::OsStr, fs::{self, read_dir}, path::{Path, PathBuf}, sync::Arc +}; +use crate::{ + file::{CardwireGpuState, CardwireModeState}, interface::Modes +}; use cardwire_core::{ gpu::{GpuBlocker, GpuDevice, block_gpu, is_gpu_blocked}, pci::PciDevice }; @@ -9,9 +14,11 @@ use log::{info, warn}; use tokio::sync::RwLock; use zbus::{fdo, interface}; -use crate::{ - file::{CardwireGpuState, CardwireModeState}, interface::Modes -}; +#[derive(serde::Serialize, zbus::zvariant::Type)] +pub struct LsofResult { + pub card: Vec, + pub render: Vec, +} // Represent a single gpu #[derive(Clone)] @@ -77,6 +84,64 @@ impl GpuInterface { let blocker = self.blocker.read().await; is_gpu_blocked(&blocker, &self.device).map_err(|e| fdo::Error::Failed(e.to_string())) } + async fn lsof_read(&self, s: &str) -> fdo::Result> { + let proc_path = Path::new("/proc"); + let mut proc_found: Vec = Vec::new(); + // If proc path doesn't exist, exit + if !proc_path.exists() || !proc_path.is_dir() { + return Err(fdo::Error::Failed("couldn't find /proc path".to_string())); + } + // read /proc + for entry in read_dir(proc_path) + .map_err(|e| fdo::Error::IOError(e.to_string()))? + .flatten() + { + // Check if folder name is a numerical, if not skip + if let Ok(string) = entry.file_name().into_string() + && string.parse::().is_err() + { + continue; + } + let path = entry.path(); + // now read eg: /proc/1 + if path.is_dir() { + // now get fd directory + let fd_dir: PathBuf = read_dir(&path) + .map_err(|e| fdo::Error::IOError(e.to_string()))? + .filter(|r| r.is_ok()) + .map(|r| r.unwrap().path()) + .filter(|r| r.file_name() == Some(OsStr::new("fd"))) + .collect(); + for entry in read_dir(fd_dir) + .map_err(|e| fdo::Error::IOError(e.to_string()))? + .flatten() + { + if let Ok(link) = entry.path().read_link() + && let Some(file) = link.to_str() + { + let file = file.to_string(); + if file.contains(s) { + // Found the file, now get process name + let status_read = fs::read_to_string(path.join("status")); + let mut process_name: String = String::new(); + if let Ok(status) = status_read { + process_name = + status.lines().filter(|l| l.contains("Name:")).collect(); + process_name = process_name + .split(":") + .last() + .unwrap_or("") + .trim() + .to_string(); + } + proc_found.push(process_name); + } + } + } + } + } + Ok(proc_found) + } } #[interface(name = "com.github.opengamingcollective.cardwire.Gpu")] @@ -124,4 +189,12 @@ impl GpuInterface { pub async fn block(&self) -> fdo::Result { self.gpu_blocked().await } + pub async fn lsof(&self) -> fdo::Result { + let card_path = format!("/dev/dri/card{}", self.device.card()); + let render_path = format!("/dev/dri/renderD{}", self.device.render()); + let (card, render) = + tokio::try_join!(self.lsof_read(&card_path), self.lsof_read(&render_path))?; + + Ok(LsofResult { card, render }) + } } From 51823634448e7c44119187ad7a8063ded664ec24 Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 15:16:30 +0200 Subject: [PATCH 16/33] docs: added comment to some functions --- crates/cardwire-cli/src/dbus.rs | 6 +++--- crates/cardwire-daemon/src/interface/config.rs | 1 + crates/cardwire-daemon/src/interface/debug.rs | 1 + crates/cardwire-daemon/src/interface/gpu.rs | 6 ++++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 37a05a1..cba6f2a 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -38,9 +38,9 @@ impl<'a> DaemonClient<'a> { let path = format!("/com/github/opengamingcollective/cardwire/gpu/{}", id); let block_proxy = zbus::Proxy::new( self.proxy.connection(), - "com.github.opengamingcollective.cardwire", // Destination - path.as_str(), // The new dynamic path - "com.github.opengamingcollective.cardwire.gpu", // Interface name + "com.github.opengamingcollective.cardwire", + path.as_str(), + "com.github.opengamingcollective.cardwire.gpu", ) .await .map_err(|e| zbus::fdo::Error::Failed(format!("Failed to create proxy: {}", e)))?; diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index 4b97f11..6214dc0 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -12,6 +12,7 @@ pub struct ConfigMemory { pub battery_auto_switch: Arc, } impl ConfigMemory { + /// build a ConfigMemory from CardwireConfig pub fn build(user_config: CardwireConfig) -> ConfigMemory { let auto_apply_gpu_state = Arc::new(AtomicBool::new(user_config.auto_apply_gpu_state())); let experimental_nvidia_block = diff --git a/crates/cardwire-daemon/src/interface/debug.rs b/crates/cardwire-daemon/src/interface/debug.rs index 7c43bd6..732496d 100644 --- a/crates/cardwire-daemon/src/interface/debug.rs +++ b/crates/cardwire-daemon/src/interface/debug.rs @@ -38,6 +38,7 @@ impl DebugInterface { #[interface(name = "com.github.opengamingcollective.cardwire.Debug")] impl DebugInterface { + /// Find out if the GPU can sleep or not by checking if the system config is correct async fn diagnostic_gpu( &self, #[zbus(signal_emitter)] emitter: SignalEmitter<'_>, diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 11e0d9d..58a449e 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -49,7 +49,7 @@ impl GpuInterface { } impl GpuInterface { - // block the gpu + /// block the gpu pub async fn block_gpu(&mut self) -> fdo::Result<()> { let mut blocker = self.blocker.write().await; let pci_list = self.pci_list.read().await; @@ -64,7 +64,7 @@ impl GpuInterface { }; Ok(()) } - // unblock the gpu + /// unblock the gpu pub async fn unblock_gpu(&mut self) -> fdo::Result<()> { let mut blocker = self.blocker.write().await; let pci_list = self.pci_list.read().await; @@ -80,10 +80,12 @@ impl GpuInterface { }; Ok(()) } + /// check if the gpu is blocked pub async fn gpu_blocked(&self) -> fdo::Result { let blocker = self.blocker.read().await; is_gpu_blocked(&blocker, &self.device).map_err(|e| fdo::Error::Failed(e.to_string())) } + /// read fd link to find which apps opened the gpu async fn lsof_read(&self, s: &str) -> fdo::Result> { let proc_path = Path::new("/proc"); let mut proc_found: Vec = Vec::new(); From 02c4feb8507e0d35754b56cdc08a9f249be2d05b Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 18:43:02 +0200 Subject: [PATCH 17/33] feat(gpu-dbus): replace return type of lsof to an Hashmap and add lsof for two nvidia devices --- crates/cardwire-daemon/src/interface/gpu.rs | 27 ++++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 58a449e..9c117dc 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -1,7 +1,7 @@ //! DBUS Interface for single gpu interaction use std::{ - collections::BTreeMap, ffi::OsStr, fs::{self, read_dir}, path::{Path, PathBuf}, sync::Arc + collections::{BTreeMap, HashMap}, ffi::OsStr, fs::{self, read_dir}, path::{Path, PathBuf}, sync::Arc }; use crate::{ @@ -14,12 +14,6 @@ use log::{info, warn}; use tokio::sync::RwLock; use zbus::{fdo, interface}; -#[derive(serde::Serialize, zbus::zvariant::Type)] -pub struct LsofResult { - pub card: Vec, - pub render: Vec, -} - // Represent a single gpu #[derive(Clone)] pub struct GpuInterface { @@ -191,12 +185,27 @@ impl GpuInterface { pub async fn block(&self) -> fdo::Result { self.gpu_blocked().await } - pub async fn lsof(&self) -> fdo::Result { + pub async fn lsof(&self) -> fdo::Result>> { let card_path = format!("/dev/dri/card{}", self.device.card()); let render_path = format!("/dev/dri/renderD{}", self.device.render()); + let mut proc_map: HashMap> = HashMap::new(); + let (card, render) = tokio::try_join!(self.lsof_read(&card_path), self.lsof_read(&render_path))?; + proc_map.insert(card_path, card); + proc_map.insert(render_path, render); + + if let Some(minor) = self.device.nvidia_minor() { + let nvidia_path = format!("/dev/nvidia{}", minor); + let nvidiactl_path = "/dev/nvidiactl".to_string(); + let (nvidia, nvidiactl) = tokio::try_join!( + self.lsof_read(&nvidia_path), + self.lsof_read(&nvidiactl_path) + )?; + proc_map.insert(nvidia_path, nvidia); + proc_map.insert(nvidiactl_path, nvidiactl); + } - Ok(LsofResult { card, render }) + Ok(proc_map) } } From ef63710b5a8f2dc450250e932f9775d5efb9de03 Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 19:07:01 +0200 Subject: [PATCH 18/33] feat(daemon): add object manager for gpus --- crates/cardwire-daemon/src/daemon.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index d206c2c..8c0b7f3 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -32,6 +32,10 @@ async fn main() -> Result<()> { let conn = conn_builder .name("com.github.opengamingcollective.cardwire")? .serve_at("/com/github/opengamingcollective/cardwire", daemon.clone())? + .serve_at( + "/com/github/opengamingcollective/cardwire", + zbus::fdo::ObjectManager, + )? .build() .await?; From 751d80ab16c6b77dc7db4f1a191bdd6fca725833 Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 19:20:15 +0200 Subject: [PATCH 19/33] feat(gpu-dbus): add GetDevice to the gpu api --- crates/cardwire-core/src/gpu/models.rs | 2 -- crates/cardwire-daemon/src/interface/gpu.rs | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/cardwire-core/src/gpu/models.rs b/crates/cardwire-core/src/gpu/models.rs index 0406320..2eadbcf 100644 --- a/crates/cardwire-core/src/gpu/models.rs +++ b/crates/cardwire-core/src/gpu/models.rs @@ -68,13 +68,11 @@ impl GpuDevice { #[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] pub struct DbusGpuDevice { - pub id: u32, pub name: String, pub pci: String, pub render: u32, pub card: u32, pub default: bool, - pub blocked: bool, pub nvidia: bool, pub nvidia_minor: String, } diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 9c117dc..972c1c3 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -8,7 +8,7 @@ use crate::{ file::{CardwireGpuState, CardwireModeState}, interface::Modes }; use cardwire_core::{ - gpu::{GpuBlocker, GpuDevice, block_gpu, is_gpu_blocked}, pci::PciDevice + gpu::{DbusGpuDevice, GpuBlocker, GpuDevice, block_gpu, is_gpu_blocked}, pci::PciDevice }; use log::{info, warn}; use tokio::sync::RwLock; @@ -208,4 +208,20 @@ impl GpuInterface { Ok(proc_map) } + pub async fn get_device(&self) -> fdo::Result { + let gpu = &self.device; + Ok(DbusGpuDevice { + pci: gpu.pci.pci_address().to_string(), + render: *gpu.render(), + name: gpu.name().to_string(), + card: *gpu.card(), + default: gpu.default().unwrap_or(false), + nvidia: gpu.nvidia(), + nvidia_minor: if gpu.nvidia_minor().is_some() { + gpu.nvidia_minor().unwrap().to_string() + } else { + "".to_string() + }, + }) + } } From 13e1bcc49bae9b7d4774e2fc81b104a386d20efa Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 19:29:25 +0200 Subject: [PATCH 20/33] docs: update the dbus part with the new api --- docs/development/dbus.md | 136 ++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 51 deletions(-) diff --git a/docs/development/dbus.md b/docs/development/dbus.md index 22a763a..86c348c 100644 --- a/docs/development/dbus.md +++ b/docs/development/dbus.md @@ -3,81 +3,115 @@ ## Service - **Bus Name:** `com.github.opengamingcollective.cardwire` -- **Object Path:** `/com/github/opengamingcollective/cardwire` -- **Interface:** `com.github.opengamingcollective.cardwire` -## Methods +--- -### SetGpuBlock +## Object Path: `/com/github/opengamingcollective/cardwire` -Set the block state for a specific GPU. Only available when `Mode` is set to `Manual` +### Interface: `com.github.opengamingcollective.cardwire.Manager` -The default GPU cannot be blocked +**Methods:** -**Inputs:** +- **`RefreshGpu`** + Refresh the internal GPU list from the system (Not implemented yet) + - **Inputs:** None + - **Outputs:** None -- gpu_id (in): `u` -- The GPU identifier (`id` field) -- block (in): `b` -- `true` to block, `false` to unblock +- **`Status`** + Simple dbus method to check if the daemon is alive + - **Inputs:** None + - **Outputs:** None -**Outputs:** None +### Interface: `com.github.opengamingcollective.cardwire.Mode` -### ListDevices +**Properties:** -List all detected GPU devices +- **`Mode`** + Controls the Cardwire's Mode + - **Type:** `u` + - **Access:** Read/Write + - **Emits:** `PropertiesChanged` on change + - **Values:** + - `0` Integrated: Block the dGPU. Requires exactly 2 GPUs + - `1` Hybrid: Unblock the dGPU. Requires exactly 2 GPUs + - `2` Manual: Allow per-GPU blocking via individual GPU objects. Applies saved GPU state on mode change if `auto_apply_gpu_state` is enabled -**Inputs:** None +### Interface: `com.github.opengamingcollective.cardwire.Config` -**Outputs:** +**Properties:** -- (out): `a{t(ussuubbbs)}` +- **`AutoApplyGpuState`** + Automatically applies the saved block/unblock states to GPUs + - **Type:** `b` + - **Access:** Read/Write -**GPU Struct `(ussuubbbs)` fields:** +- **`BatteryAutoSwitch`** + Controls whether the daemon automatically switches modes when switching to battery power + - **Type:** `b` + - **Access:** Read/Write -- `id`: `u` -- GPU identifier -- `name`: `s` -- GPU name -- `pci`: `s` -- PCI address (e.g. `0000:01:00.0`) -- `render`: `u` -- DRM render node minor number -- `card`: `u` -- DRM card node minor number -- `default`: `b` -- Whether this is the default display GPU -- `blocked`: `b` -- Whether the GPU is currently blocked by the daemon -- `nvidia`: `b` -- Whether the GPU is an NVIDIA device -- `nvidia_minor`: `s` -- NVIDIA driver minor number (empty string if not applicable) +- **`ExperimentalNvidiaBlock`** + Toggles the experimental blocking for NVIDIA GPU, only works if the system has exactly 1 Nvidia GPU + - **Type:** `b` + - **Access:** Read/Write -### ListDevicesPci +**Methods:** -List all detected PCI devices +- **`SaveToFile`** + Save the current daemon configuration (properties above) to the `cardwire.toml` config file + - **Inputs:** None + - **Outputs:** None -**Inputs:** None +### Interface: `com.github.opengamingcollective.cardwire.Debug` -**Outputs:** +**Methods:** -- (out): `a{s(ssssssss)}` +- **`DiagnosticGpu`** + Find out if the GPU can sleep or not by checking if the system config is correct. Emits diagnostic signals during execution (NOT IMPLEMENTED) + - **Inputs:** None + - **Outputs:** None -**PCI Struct `(ssssssss)` fields:** +**Signals:** -- `pci_address`: `s` -- PCI address (e.g. `0000:01:00.0`) -- `iommu_group`: `s` -- IOMMU group number (empty string if none) -- `vendor_id`: `s` -- PCI vendor ID (empty string if unknown) -- `device_id`: `s` -- PCI device ID (empty string if unknown) -- `vendor_name`: `s` -- Vendor name (empty string if unknown) -- `device_name`: `s` -- Device name (empty string if unknown) -- `driver`: `s` -- Kernel driver in use (empty string if unknown) -- `class`: `s` -- PCI class (empty string if unknown) -- `parent_pci` : `s` -- Parent PCI (empty string if unknown) -- `child_pci` : `s` -- Parent PCI (empty string if unknown) +- **`DiagnosticInfo`** + Emitted with diagnostic output text during the diagnostic process + - **Parameters:** `s` +- **`DiagnosticEnded`** + Emitted when the diagnostic process finishes + - **Parameters:** None -## Properties +--- -### Mode +## Object Path: `/com/github/opengamingcollective/cardwire/Gpu/{id}` -Controls the global GPU blocking mode +Represents a single GPU device, where `{id}` is the numeric identifier of the GPU (0 is always the default one). These objects can be dynamically discovered by calling `GetManagedObjects` on the standard `org.freedesktop.DBus.ObjectManager` interface located at the root path (`/com/github/opengamingcollective/cardwire`) -- **Type:** `u` (uint32) -- **Access:** Read/Write -- **Emits:** `PropertiesChanged` on change +### Interface: `com.github.opengamingcollective.cardwire.Gpu` -**Values:** +**Properties:** -- `0` -- Integrated: Block the dGPU. Requires exactly 2 GPUs -- `1` -- Hybrid: Unblock the dGPU. Requires exactly 2 GPUs -- `2` -- Manual: Allow per-GPU blocking via `SetGpuBlock`, applies saved GPU state if `auto_apply` is enabled +- **`Block`** + Set or get the block state for this specific GPU. Only writable when `Mode` is set to `Manual`. The default gpu cannot be blocked. + - **Type:** `b` + - **Access:** Read/Write + +**Methods:** + +- **`GetDevice`** + Get the detailed properties of this GPU + - **Inputs:** None + - **Outputs:** + - (out): `(ssuubbs)` -- A struct containing: + - `name`: `s` - GPU name + - `pci`: `s` - PCI address + - `render`: `u` - DRM render node minor number + - `card`: `u` - DRM card node minor number + - `default`: `b` - Whether this is the default display GPU + - `nvidia`: `b` - Whether the GPU is an NVIDIA device + - `nvidia_minor`: `s` - NVIDIA driver minor number (empty string if not applicable) + +- **`Lsof`** + Read file descriptors to find which applications have currently opened the GPU. + - **Inputs:** None + - **Outputs:** + - (out): `a{sas}` -- A dictionary mapping file paths (like `/dev/dri/card0`) to an array of process names From 0dbc9b18865376912363c7e3ba3af8d89bc101a8 Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 19:31:32 +0200 Subject: [PATCH 21/33] docs: update titles to be smaller --- docs/development/dbus.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/development/dbus.md b/docs/development/dbus.md index 86c348c..41e556e 100644 --- a/docs/development/dbus.md +++ b/docs/development/dbus.md @@ -6,9 +6,11 @@ --- -## Object Path: `/com/github/opengamingcollective/cardwire` +## Object Path +`/com/github/opengamingcollective/cardwire` -### Interface: `com.github.opengamingcollective.cardwire.Manager` +### Manager +`com.github.opengamingcollective.cardwire.Manager` **Methods:** @@ -22,7 +24,8 @@ - **Inputs:** None - **Outputs:** None -### Interface: `com.github.opengamingcollective.cardwire.Mode` +### Mode +`com.github.opengamingcollective.cardwire.Mode` **Properties:** @@ -36,7 +39,8 @@ - `1` Hybrid: Unblock the dGPU. Requires exactly 2 GPUs - `2` Manual: Allow per-GPU blocking via individual GPU objects. Applies saved GPU state on mode change if `auto_apply_gpu_state` is enabled -### Interface: `com.github.opengamingcollective.cardwire.Config` +### Config +`com.github.opengamingcollective.cardwire.Config` **Properties:** @@ -62,7 +66,8 @@ - **Inputs:** None - **Outputs:** None -### Interface: `com.github.opengamingcollective.cardwire.Debug` +### Debug +`com.github.opengamingcollective.cardwire.Debug` **Methods:** @@ -82,12 +87,11 @@ --- -## Object Path: `/com/github/opengamingcollective/cardwire/Gpu/{id}` +## Gpu +`/com/github/opengamingcollective/cardwire/Gpu/{id}` Represents a single GPU device, where `{id}` is the numeric identifier of the GPU (0 is always the default one). These objects can be dynamically discovered by calling `GetManagedObjects` on the standard `org.freedesktop.DBus.ObjectManager` interface located at the root path (`/com/github/opengamingcollective/cardwire`) -### Interface: `com.github.opengamingcollective.cardwire.Gpu` - **Properties:** - **`Block`** From 885cc2799a42aed6f401e4761d9b289bba32ed78 Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 19:49:02 +0200 Subject: [PATCH 22/33] feat(cli): implement new api --- crates/cardwire-cli/src/args.rs | 67 ++++++++- crates/cardwire-cli/src/dbus.rs | 212 +++++++++++++++++++++++++++-- crates/cardwire-cli/src/display.rs | 22 +-- crates/cardwire-cli/src/main.rs | 169 +++++++++++++++++++---- 4 files changed, 411 insertions(+), 59 deletions(-) diff --git a/crates/cardwire-cli/src/args.rs b/crates/cardwire-cli/src/args.rs index b68443e..a07dabc 100644 --- a/crates/cardwire-cli/src/args.rs +++ b/crates/cardwire-cli/src/args.rs @@ -36,12 +36,16 @@ pub enum Commands { #[command(about = "Print the gpu list")] List { - #[arg(long, help("Print the whole gpu list"), action(ArgAction::SetTrue))] + #[arg( + long, + help("Print the whole pci list"), + action(clap::ArgAction::SetTrue) + )] full: bool, #[arg( long, help("Print the gpu list in json format"), - action(ArgAction::SetTrue) + action(clap::ArgAction::SetTrue) )] json: bool, }, @@ -55,12 +59,67 @@ pub enum Commands { #[command(flatten)] action: GpuAction, }, + + #[command(about = "Manage daemon configuration", arg_required_else_help = true)] + Config { + #[command(subcommand)] + action: ConfigAction, + }, + + #[command(about = "Manager operations", arg_required_else_help = true)] + Manager { + #[command(subcommand)] + action: ManagerAction, + }, + + #[command(about = "Debug operations", arg_required_else_help = true)] + Debug { + #[command(subcommand)] + action: DebugAction, + }, + #[command(about = "Generate shell completions", hide = true)] Completion { #[arg(help = "The shell to generate the completions for")] shell: Shell, }, } + +#[derive(Subcommand, Debug)] +pub enum ConfigAction { + #[command(about = "Get or set AutoApplyGpuState")] + AutoApplyGpuState { + #[arg(help = "Value to set")] + set: Option, + }, + #[command(about = "Get or set ExperimentalNvidiaBlock")] + ExperimentalNvidiaBlock { + #[arg(help = "Value to set")] + set: Option, + }, + #[command(about = "Get or set BatteryAutoSwitch")] + BatteryAutoSwitch { + #[arg(help = "Value to set")] + set: Option, + }, + #[command(about = "Save current configuration to file")] + Save, +} + +#[derive(Subcommand, Debug)] +pub enum ManagerAction { + #[command(about = "Check if daemon is alive")] + Status, + #[command(about = "Refresh GPU list in daemon")] + RefreshGpu, +} + +#[derive(Subcommand, Debug)] +pub enum DebugAction { + #[command(about = "Run GPU diagnostics")] + DiagnosticGpu, +} + #[derive(ClapArgs, Debug)] #[group(required = true, multiple = false)] pub struct GpuAction { @@ -70,6 +129,6 @@ pub struct GpuAction { #[arg(long, help = "Unblock a specific gpu")] pub unblock: bool, - #[arg(long, help = "Get gpu status")] - pub status: bool, + #[arg(long, help = "List open files on the GPU")] + pub lsof: bool, } diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index cba6f2a..9a5b441 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -1,7 +1,16 @@ -use crate::display::{GpuDevice, PciDevice}; -use std::collections::BTreeMap; - use zbus::{Proxy, connection::Connection}; + +#[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type, Debug)] +pub struct DbusGpuDevice { + pub name: String, + pub pci: String, + pub render: u32, + pub card: u32, + pub default: bool, + pub nvidia: bool, + pub nvidia_minor: String, +} + pub struct DaemonClient<'a> { proxy: Proxy<'a>, } @@ -19,34 +28,207 @@ impl<'a> DaemonClient<'a> { Ok(Self { proxy }) } - pub async fn set_mode(&self, mode: &u32) -> zbus::fdo::Result<()> { - self.proxy.set_property("Mode", mode).await + pub async fn get_managed_objects( + &self, + ) -> zbus::fdo::Result< + std::collections::HashMap< + zbus::zvariant::OwnedObjectPath, + std::collections::HashMap< + zbus::names::OwnedInterfaceName, + std::collections::HashMap, + >, + >, + > { + let proxy = zbus::fdo::ObjectManagerProxy::builder(self.proxy.connection()) + .destination("com.github.opengamingcollective.cardwire")? + .path("/com/github/opengamingcollective/cardwire")? + .build() + .await?; + proxy.get_managed_objects().await } - pub async fn get_mode(&self) -> zbus::Result { - self.proxy.get_property("Mode").await + pub async fn get_device(&self, id: u32) -> zbus::Result { + let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + path.as_str(), + "com.github.opengamingcollective.cardwire.Gpu", + ) + .await?; + proxy.call("GetDevice", &()).await } - pub async fn list_devices(&self) -> zbus::Result> { - self.proxy.call("ListDevices", &()).await + pub async fn set_mode(&self, mode: &u32) -> zbus::fdo::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Mode", + ) + .await + .map_err(|e| zbus::fdo::Error::Failed(format!("Failed to create Mode proxy: {}", e)))?; + proxy.set_property("Mode", mode).await } - pub async fn list_devices_pci(&self) -> zbus::Result> { - self.proxy.call("ListDevicesPci", &()).await + + pub async fn get_mode(&self) -> zbus::Result { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Mode", + ) + .await?; + proxy.get_property("Mode").await } pub async fn set_gpu_block(&self, id: u32, blocked: bool) -> zbus::fdo::Result<()> { - let path = format!("/com/github/opengamingcollective/cardwire/gpu/{}", id); + let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); let block_proxy = zbus::Proxy::new( self.proxy.connection(), "com.github.opengamingcollective.cardwire", path.as_str(), - "com.github.opengamingcollective.cardwire.gpu", + "com.github.opengamingcollective.cardwire.Gpu", ) .await .map_err(|e| zbus::fdo::Error::Failed(format!("Failed to create proxy: {}", e)))?; block_proxy.set_property("Block", &(blocked)).await } - pub async fn get_status(&self, id: u32) -> zbus::Result { - self.proxy.call("GetStatus", &id).await + + pub async fn get_status(&self, id: u32) -> zbus::Result { + let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); + let block_proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + path.as_str(), + "com.github.opengamingcollective.cardwire.Gpu", + ) + .await?; + block_proxy.get_property("Block").await + } + + pub async fn lsof( + &self, + id: u32, + ) -> zbus::Result>> { + let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + path.as_str(), + "com.github.opengamingcollective.cardwire.Gpu", + ) + .await?; + proxy.call("Lsof", &()).await + } + + pub async fn get_auto_apply_gpu_state(&self) -> zbus::Result { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Config", + ) + .await?; + proxy.get_property("AutoApplyGpuState").await + } + pub async fn set_auto_apply_gpu_state(&self, state: bool) -> zbus::fdo::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Config", + ) + .await + .map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?; + proxy.set_property("AutoApplyGpuState", state).await + } + + pub async fn get_experimental_nvidia_block(&self) -> zbus::Result { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Config", + ) + .await?; + proxy.get_property("ExperimentalNvidiaBlock").await + } + pub async fn set_experimental_nvidia_block(&self, state: bool) -> zbus::fdo::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Config", + ) + .await + .map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?; + proxy.set_property("ExperimentalNvidiaBlock", state).await + } + + pub async fn get_battery_auto_switch(&self) -> zbus::Result { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Config", + ) + .await?; + proxy.get_property("BatteryAutoSwitch").await + } + pub async fn set_battery_auto_switch(&self, state: bool) -> zbus::fdo::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Config", + ) + .await + .map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?; + proxy.set_property("BatteryAutoSwitch", state).await + } + + pub async fn save_to_file(&self) -> zbus::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Config", + ) + .await?; + proxy.call("SaveToFile", &()).await + } + + pub async fn refresh_gpu(&self) -> zbus::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Manager", + ) + .await?; + proxy.call("RefreshGpu", &()).await + } + + pub async fn manager_status(&self) -> zbus::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Manager", + ) + .await?; + proxy.call("Status", &()).await + } + + pub async fn diagnostic_gpu(&self) -> zbus::Result<()> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Debug", + ) + .await?; + proxy.call("DiagnosticGpu", &()).await } } diff --git a/crates/cardwire-cli/src/display.rs b/crates/cardwire-cli/src/display.rs index 9d6d7a4..35eb21e 100644 --- a/crates/cardwire-cli/src/display.rs +++ b/crates/cardwire-cli/src/display.rs @@ -10,15 +10,15 @@ use anyhow::{Ok, Result}; // 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, + pub id: u32, + pub name: String, + pub pci: String, + pub render: u32, + pub card: u32, + pub default: bool, + pub blocked: bool, + pub nvidia: bool, + pub nvidia_minor: String, } #[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type)] pub struct PciDevice { @@ -99,12 +99,12 @@ fn pretty_print_gpu(gpu_list: BTreeMap) { "-".repeat(default_w), "-".repeat(blocked_w), ); - for (_, gpu) in gpu_list { + for (id, gpu) in gpu_list { let render_full = format!("renderD{}", gpu.render); let card_full = format!("card{}", gpu.card); println!( "{: anyhow::Result<()> { // Now connect let connection: zbus::Connection = zbus::connection::Builder::system()?.build().await?; let client: DaemonClient<'_> = DaemonClient::connect(&connection).await?; - match args.command { Commands::Set { mode } => { let mode_u32 = match mode { @@ -52,42 +49,148 @@ async fn main() -> anyhow::Result<()> { } Commands::List { full, json } => { if full { - match client.list_devices_pci().await { - Ok(response) => { - print_devices_pci(response)?; - } - Err(e) => handle_error(e), - } + println!("Full PCI list is not supported in the new D-Bus API"); } else { - match client.list_devices().await { - Ok(response) => { - print_devices(response, json)?; + let mut map = std::collections::BTreeMap::new(); + let objects = client.get_managed_objects().await.unwrap_or_default(); + for (path, interfaces) in objects { + let path_str = path.as_str(); + if let Some(id_str) = + path_str.strip_prefix("/com/github/opengamingcollective/cardwire/Gpu/") + { + if let Ok(id) = id_str.parse::() { + let mut blocked = false; + for (iface, props) in interfaces { + if iface.as_str() == "com.github.opengamingcollective.cardwire.Gpu" + { + if let Some(block_val) = props.get("Block") { + blocked = block_val.downcast_ref::().unwrap_or(false); + } + } + } + if let Ok(dbus_dev) = client.get_device(id).await { + let dev = display::GpuDevice { + id, + name: dbus_dev.name, + pci: dbus_dev.pci, + render: dbus_dev.render, + card: dbus_dev.card, + default: dbus_dev.default, + blocked, + nvidia: dbus_dev.nvidia, + nvidia_minor: dbus_dev.nvidia_minor, + }; + map.insert(id as usize, dev); + } + } } - Err(e) => handle_error(e), + } + if let Err(e) = display::print_devices(map, json) { + handle_error(zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed( + e.to_string(), + )))); } } } Commands::Gpu { id, action } => { - if action.status { - // Handle --status - match client.get_status(id).await { - Ok(status) => println!("GPU {} status: {}", id, status), - Err(e) => handle_error(e), - }; - } else if action.block { - // Handle --block + if action.block { match client.set_gpu_block(id, true).await { Ok(_) => println!("GPU {} has been blocked", id), Err(e) => handle_error(e.into()), }; } else if action.unblock { - // Handle --unblock match client.set_gpu_block(id, false).await { Ok(_) => println!("GPU {} has been unblocked", id), Err(e) => handle_error(e.into()), }; + } else if action.lsof { + match client.lsof(id).await { + Ok(map) => { + for (path, procs) in map { + println!(" {}: {:?}", path, procs); + } + } + Err(e) => handle_error(e), + }; } } + Commands::Config { action } => match action { + ConfigAction::AutoApplyGpuState { set } => { + if let Some(val) = set { + if let Err(e) = client.set_auto_apply_gpu_state(val).await { + handle_error(e.into()); + } else { + println!("AutoApplyGpuState set to {}", val); + } + } else { + match client.get_auto_apply_gpu_state().await { + Ok(val) => println!("AutoApplyGpuState: {}", val), + Err(e) => handle_error(e), + } + } + } + ConfigAction::ExperimentalNvidiaBlock { set } => { + if let Some(val) = set { + if let Err(e) = client.set_experimental_nvidia_block(val).await { + handle_error(e.into()); + } else { + println!("ExperimentalNvidiaBlock set to {}", val); + } + } else { + match client.get_experimental_nvidia_block().await { + Ok(val) => println!("ExperimentalNvidiaBlock: {}", val), + Err(e) => handle_error(e), + } + } + } + ConfigAction::BatteryAutoSwitch { set } => { + if let Some(val) = set { + if let Err(e) = client.set_battery_auto_switch(val).await { + handle_error(e.into()); + } else { + println!("BatteryAutoSwitch set to {}", val); + } + } else { + match client.get_battery_auto_switch().await { + Ok(val) => println!("BatteryAutoSwitch: {}", val), + Err(e) => handle_error(e), + } + } + } + ConfigAction::Save => { + if let Err(e) = client.save_to_file().await { + handle_error(e); + } else { + println!("Configuration saved"); + } + } + }, + Commands::Manager { action } => match action { + ManagerAction::Status => { + if let Err(e) = client.manager_status().await { + handle_error(e); + } else { + println!("Daemon is alive"); + } + } + ManagerAction::RefreshGpu => { + if let Err(e) = client.refresh_gpu().await { + handle_error(e); + } else { + println!("GPU list refreshed"); + } + } + }, + Commands::Debug { action } => match action { + DebugAction::DiagnosticGpu => { + if let Err(e) = client.diagnostic_gpu().await { + handle_error(e); + } else { + // TODO: implement debug + println!("DiagnosticGpu signal emitted"); + } + } + }, _ => {} } @@ -96,14 +199,22 @@ async fn main() -> anyhow::Result<()> { fn handle_error(err: zbus::Error) { match err { zbus::Error::MethodError(name, description, _) => { - eprintln!("{}", description.unwrap_or_else(|| name.to_string())) + if let Some(msg) = description { + eprintln!("{}", msg); + } else { + eprintln!("{}", name); + } } - zbus::Error::FDO(fdo_err) => match *fdo_err { - zbus::fdo::Error::ServiceUnknown(content) => { - eprint!("error: {} \n is the service up?", content) + zbus::Error::FDO(fdo_err) => match &*fdo_err { + zbus::fdo::Error::AccessDenied(msg) + | zbus::fdo::Error::Failed(msg) + | zbus::fdo::Error::InvalidArgs(msg) + | zbus::fdo::Error::NotSupported(msg) => eprintln!("{}", msg), + zbus::fdo::Error::ServiceUnknown(_) => { + eprintln!("error: cardwired daemon is not running. Is the service up?"); } - other => eprintln!("FDO error: {}", other), + _ => eprintln!("{}", fdo_err), }, - e => eprintln!("error: {e:?}"), + _ => eprintln!("{}", err), } } From 08c95cea1f0b3e63c23f3212a785c4e04334d43f Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 20:34:07 +0200 Subject: [PATCH 23/33] fix(gpu-dbus): handle is_gpu_blocked error --- crates/cardwire-daemon/src/interface/gpu.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 972c1c3..39bc164 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -49,13 +49,13 @@ impl GpuInterface { let pci_list = self.pci_list.read().await; block_gpu(&mut blocker, &self.device, true, &pci_list) .map_err(|e| fdo::Error::Failed(e.to_string()))?; - if let Ok(result) = is_gpu_blocked(&blocker, &self.device) - && !result - { + let blocked = is_gpu_blocked(&blocker, &self.device) + .map_err(|e| fdo::Error::Failed(e.to_string()))?; + if !blocked { return Err(fdo::Error::Failed( "gpu is supposed to be blocked, bpf says it's not".to_string(), )); - }; + } Ok(()) } /// unblock the gpu @@ -65,13 +65,13 @@ impl GpuInterface { block_gpu(&mut blocker, &self.device, false, &pci_list) .map_err(|e| fdo::Error::Failed(e.to_string()))?; - if let Ok(result) = is_gpu_blocked(&blocker, &self.device) - && result - { + let blocked = is_gpu_blocked(&blocker, &self.device) + .map_err(|e| fdo::Error::Failed(e.to_string()))?; + if blocked { return Err(fdo::Error::Failed( "gpu is supposed to be unblocked, bpf says it's not".to_string(), )); - }; + } Ok(()) } /// check if the gpu is blocked From 358c1233b08e780a355bd422aa6b828d041c65d8 Mon Sep 17 00:00:00 2001 From: luytan Date: Sun, 24 May 2026 20:35:54 +0200 Subject: [PATCH 24/33] fix(daemon): fix save state to use ebpf block check --- crates/cardwire-daemon/src/models.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index fad25f8..85a81d5 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -128,7 +128,8 @@ impl DaemonManager { } if default { for (_, gpu) in gpus_list.iter() { - state.save_state(&gpu.device, false).await?; + let blocked = cardwire_core::gpu::is_gpu_blocked(&blocker, &gpu.device)?; + state.save_state(&gpu.device, blocked).await?; } } // Dropping the locks prevent set_mode being stuck From 24f73b83d61b3a25edcf49fd5e96a824cb799924 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 16:38:58 +0200 Subject: [PATCH 25/33] feat(gpu-dbus): power_state signal --- crates/cardwire-daemon/src/daemon.rs | 11 +++++- crates/cardwire-daemon/src/interface/gpu.rs | 19 +++++++++- crates/cardwire-daemon/src/interface/mod.rs | 2 +- crates/cardwire-daemon/src/tasks/mod.rs | 3 +- .../src/tasks/watch_power_state.rs | 38 +++++++++++++++++++ 5 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 crates/cardwire-daemon/src/tasks/watch_power_state.rs diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index 8c0b7f3..36b48d9 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -4,7 +4,7 @@ mod interface; mod models; mod tasks; -use crate::models::DaemonManager; +use crate::{models::DaemonManager, tasks::watch_power_state}; use anyhow::Result; use log::info; use std::{future::pending, sync::Arc}; @@ -51,7 +51,14 @@ async fn main() -> Result<()> { // cardwire.Gpu for (id, gpu_interface) in gpu_interfaces.iter() { let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); - object_server.at(path, gpu_interface.clone()).await?; + object_server + .at(path.clone(), gpu_interface.clone()) + .await?; + // spawn power state watcher + task::spawn(watch_power_state( + gpu_interface.clone(), + object_server.interface(path).await?, + )); } // drop gpu list to prevent deadlock drop(gpu_interfaces); diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 39bc164..593cb1f 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -12,7 +12,7 @@ use cardwire_core::{ }; use log::{info, warn}; use tokio::sync::RwLock; -use zbus::{fdo, interface}; +use zbus::{fdo, interface, object_server::SignalEmitter}; // Represent a single gpu #[derive(Clone)] @@ -224,4 +224,21 @@ impl GpuInterface { }, }) } + + pub async fn power_state(&self) -> fdo::Result { + let power_path = format!( + "/sys/bus/pci/devices/{}/power_state", + self.device.pci.pci_address() + ); + fs::read_to_string(&power_path).map_err(|e| { + fdo::Error::IOError(format!( + "error while trying to read {} power_state: {}", + self.device.name(), + e + )) + }) + } + + #[zbus(signal)] + pub async fn power_state_changed(emitter: &SignalEmitter<'_>, state: &str) -> zbus::Result<()>; } diff --git a/crates/cardwire-daemon/src/interface/mod.rs b/crates/cardwire-daemon/src/interface/mod.rs index 9213824..614081e 100644 --- a/crates/cardwire-daemon/src/interface/mod.rs +++ b/crates/cardwire-daemon/src/interface/mod.rs @@ -5,5 +5,5 @@ mod mode; pub use config::{ConfigInterface, ConfigMemory}; pub use debug::DebugInterface; -pub use gpu::GpuInterface; +pub use gpu::{GpuInterface, GpuInterfaceSignals}; pub use mode::{ModeInterface, Modes}; diff --git a/crates/cardwire-daemon/src/tasks/mod.rs b/crates/cardwire-daemon/src/tasks/mod.rs index 63c8fbf..7d10d2a 100644 --- a/crates/cardwire-daemon/src/tasks/mod.rs +++ b/crates/cardwire-daemon/src/tasks/mod.rs @@ -1,3 +1,4 @@ mod battery_switch; - +mod watch_power_state; pub use battery_switch::watch_battery_status; +pub use watch_power_state::watch_power_state; diff --git a/crates/cardwire-daemon/src/tasks/watch_power_state.rs b/crates/cardwire-daemon/src/tasks/watch_power_state.rs new file mode 100644 index 0000000..30c2eeb --- /dev/null +++ b/crates/cardwire-daemon/src/tasks/watch_power_state.rs @@ -0,0 +1,38 @@ +use std::{fs, time::Duration}; + +use log::error; + +use crate::interface::{GpuInterface, GpuInterfaceSignals}; +use tokio::time::sleep; +use zbus::object_server::{self}; + +pub async fn watch_power_state( + gpu: GpuInterface, + interface: object_server::InterfaceRef, +) -> anyhow::Result<()> { + let power_path = format!( + "/sys/bus/pci/devices/{}/power_state", + gpu.device.pci.pci_address() + ); + let mut current_power_state = fs::read_to_string(&power_path)?; + let signal = interface.signal_emitter(); + loop { + sleep(Duration::from_millis(500)).await; + let new_power_state = match fs::read_to_string(&power_path) { + Ok(state) => state, + Err(e) => { + error!( + "failed to read power_state for {}: {}", + gpu.device.name(), + e + ); + continue; + } + }; + if current_power_state != new_power_state { + signal.power_state_changed(&new_power_state).await?; + + current_power_state = new_power_state; + } + } +} From 7d95a251a5ce2c8a85e3a26a38736485fa3f5765 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 16:39:17 +0200 Subject: [PATCH 26/33] chore(cli): collapse if --- crates/cardwire-cli/src/main.rs | 44 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index ace13ba..4f031e7 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -57,32 +57,30 @@ async fn main() -> anyhow::Result<()> { let path_str = path.as_str(); if let Some(id_str) = path_str.strip_prefix("/com/github/opengamingcollective/cardwire/Gpu/") + && let Ok(id) = id_str.parse::() { - if let Ok(id) = id_str.parse::() { - let mut blocked = false; - for (iface, props) in interfaces { - if iface.as_str() == "com.github.opengamingcollective.cardwire.Gpu" - { - if let Some(block_val) = props.get("Block") { - blocked = block_val.downcast_ref::().unwrap_or(false); - } - } - } - if let Ok(dbus_dev) = client.get_device(id).await { - let dev = display::GpuDevice { - id, - name: dbus_dev.name, - pci: dbus_dev.pci, - render: dbus_dev.render, - card: dbus_dev.card, - default: dbus_dev.default, - blocked, - nvidia: dbus_dev.nvidia, - nvidia_minor: dbus_dev.nvidia_minor, - }; - map.insert(id as usize, dev); + let mut blocked = false; + for (iface, props) in interfaces { + if iface.as_str() == "com.github.opengamingcollective.cardwire.Gpu" + && let Some(block_val) = props.get("Block") + { + blocked = block_val.downcast_ref::().unwrap_or(false); } } + if let Ok(dbus_dev) = client.get_device(id).await { + let dev = display::GpuDevice { + id, + name: dbus_dev.name, + pci: dbus_dev.pci, + render: dbus_dev.render, + card: dbus_dev.card, + default: dbus_dev.default, + blocked, + nvidia: dbus_dev.nvidia, + nvidia_minor: dbus_dev.nvidia_minor, + }; + map.insert(id as usize, dev); + } } } if let Err(e) = display::print_devices(map, json) { From acf0244aeaee94ff5a37f8079caf2e251fdcc149 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 18:13:52 +0200 Subject: [PATCH 27/33] feat(cli): gpu power status --- crates/cardwire-cli/src/args.rs | 3 +++ crates/cardwire-cli/src/dbus.rs | 6 +++--- crates/cardwire-cli/src/main.rs | 7 +++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/cardwire-cli/src/args.rs b/crates/cardwire-cli/src/args.rs index a07dabc..1f2e80d 100644 --- a/crates/cardwire-cli/src/args.rs +++ b/crates/cardwire-cli/src/args.rs @@ -131,4 +131,7 @@ pub struct GpuAction { #[arg(long, help = "List open files on the GPU")] pub lsof: bool, + + #[arg(long, help = "Get GPU power state")] + pub power: bool, } diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 9a5b441..9d61986 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -95,16 +95,16 @@ impl<'a> DaemonClient<'a> { block_proxy.set_property("Block", &(blocked)).await } - pub async fn get_status(&self, id: u32) -> zbus::Result { + pub async fn get_power_state(&self, id: u32) -> zbus::Result { let path = format!("/com/github/opengamingcollective/cardwire/Gpu/{}", id); - let block_proxy = zbus::Proxy::new( + let proxy = zbus::Proxy::new( self.proxy.connection(), "com.github.opengamingcollective.cardwire", path.as_str(), "com.github.opengamingcollective.cardwire.Gpu", ) .await?; - block_proxy.get_property("Block").await + proxy.call("PowerState", &(())).await } pub async fn lsof( diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index 4f031e7..730268e 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -110,6 +110,13 @@ async fn main() -> anyhow::Result<()> { } Err(e) => handle_error(e), }; + } else if action.power { + match client.get_power_state(id).await { + Ok(power_state) => { + println!("{}", power_state); + } + Err(e) => handle_error(e), + }; } } Commands::Config { action } => match action { From a53f4627bc9009b88acb2c260dffd0a0c00aae7f Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 18:15:23 +0200 Subject: [PATCH 28/33] chore(cli): add warning for diagnostic gpu --- crates/cardwire-cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index 730268e..d358f50 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -192,7 +192,7 @@ async fn main() -> anyhow::Result<()> { handle_error(e); } else { // TODO: implement debug - println!("DiagnosticGpu signal emitted"); + println!("DiagnosticGpu not implemented yet"); } } }, From b84e87d78ae02e170bda8cc4bcfd3ca17d03cc4a Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 18:31:43 +0200 Subject: [PATCH 29/33] feat(debug-dbus): add list pci device --- crates/cardwire-core/src/pci/models.rs | 1 - crates/cardwire-daemon/src/daemon.rs | 2 +- crates/cardwire-daemon/src/interface/debug.rs | 41 ++++++++++++------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/crates/cardwire-core/src/pci/models.rs b/crates/cardwire-core/src/pci/models.rs index 22171ae..e568b68 100644 --- a/crates/cardwire-core/src/pci/models.rs +++ b/crates/cardwire-core/src/pci/models.rs @@ -73,7 +73,6 @@ impl PciDevice { #[derive(Clone, serde::Serialize, serde::Deserialize, zbus::zvariant::Type)] pub struct DbusPciDevice { - pub pci_address: String, // Strings to be able to put nothing pub iommu_group: String, pub vendor_id: String, diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index 36b48d9..93dd61f 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -64,7 +64,7 @@ async fn main() -> Result<()> { drop(gpu_interfaces); // Now spawn background tasks - let _ = task::spawn(battery_switch); + task::spawn(battery_switch); info!("Daemon started"); pending::<()>().await; diff --git a/crates/cardwire-daemon/src/interface/debug.rs b/crates/cardwire-daemon/src/interface/debug.rs index 732496d..3a43e2a 100644 --- a/crates/cardwire-daemon/src/interface/debug.rs +++ b/crates/cardwire-daemon/src/interface/debug.rs @@ -1,7 +1,9 @@ -use cardwire_core::{gpu::GpuBlocker, pci::PciDevice}; +use cardwire_core::{ + gpu::GpuBlocker, pci::{DbusPciDevice, PciDevice} +}; use std::{collections::BTreeMap, sync::Arc}; use tokio::sync::RwLock; -use zbus::{fdo, interface, object_server::SignalEmitter}; +use zbus::{fdo, interface}; use crate::{ file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface} @@ -38,17 +40,28 @@ impl DebugInterface { #[interface(name = "com.github.opengamingcollective.cardwire.Debug")] impl DebugInterface { - /// Find out if the GPU can sleep or not by checking if the system config is correct - async fn diagnostic_gpu( - &self, - #[zbus(signal_emitter)] emitter: SignalEmitter<'_>, - ) -> fdo::Result<()> { - let emitter = emitter.into_owned(); - emitter.diagnostic_info("Hello").await; - Ok(()) + pub(crate) async fn get_pci_devices(&self) -> fdo::Result> { + let pci_list = &self.pci_list.read().await; + let mut dbus_list: BTreeMap = BTreeMap::new(); + for (id, pci) in pci_list.iter() { + let temp_pci = DbusPciDevice { + iommu_group: if let Some(iommu) = pci.iommu_group() { + iommu.to_string() + } else { + "".to_string() + }, + vendor_id: pci.vendor_id().clone().unwrap_or("".to_string()), + device_id: pci.device_id().clone().unwrap_or("".to_string()), + vendor_name: pci.vendor_name().clone().unwrap_or("".to_string()), + device_name: pci.device_name().clone().unwrap_or("".to_string()), + driver: pci.driver().clone().unwrap_or("".to_string()), + class: pci.class().clone().unwrap_or("".to_string()), + parent_pci: pci.parent_pci().clone().unwrap_or("".to_string()), + child_pci: pci.child_pci().clone().unwrap_or("".to_string()), + }; + dbus_list.insert(id.clone(), temp_pci); + } + + Ok(dbus_list) } - #[zbus(signal)] - async fn diagnostic_info(emitter: &SignalEmitter<'_>, text: &str) -> zbus::Result<()>; - #[zbus(signal)] - async fn diagnostic_ended(emitter: &SignalEmitter<'_>) -> zbus::Result<()>; } From d06bf3a42d1089020ddbcc0009d358bc811bbff8 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 18:31:54 +0200 Subject: [PATCH 30/33] feat(cli): add list pci device --- crates/cardwire-cli/src/args.rs | 2 +- crates/cardwire-cli/src/dbus.rs | 17 ++++++++++++++++- crates/cardwire-cli/src/display.rs | 1 - crates/cardwire-cli/src/main.rs | 9 ++++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/cardwire-cli/src/args.rs b/crates/cardwire-cli/src/args.rs index 1f2e80d..fb5bdf7 100644 --- a/crates/cardwire-cli/src/args.rs +++ b/crates/cardwire-cli/src/args.rs @@ -1,4 +1,4 @@ -use clap::{ArgAction, Args as ClapArgs, Parser, Subcommand, ValueEnum}; +use clap::{Args as ClapArgs, Parser, Subcommand, ValueEnum}; use clap_complete::Shell; use std::fmt; #[derive(Clone, Debug, ValueEnum)] diff --git a/crates/cardwire-cli/src/dbus.rs b/crates/cardwire-cli/src/dbus.rs index 9d61986..99b11e8 100644 --- a/crates/cardwire-cli/src/dbus.rs +++ b/crates/cardwire-cli/src/dbus.rs @@ -1,5 +1,10 @@ +use std::collections::BTreeMap; + use zbus::{Proxy, connection::Connection}; +use crate::display::PciDevice; + +// Cardwire dbus doesnt send blocked state nor the id #[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type, Debug)] pub struct DbusGpuDevice { pub name: String, @@ -58,6 +63,16 @@ impl<'a> DaemonClient<'a> { .await?; proxy.call("GetDevice", &()).await } + pub async fn get_pci_device(&self) -> zbus::Result> { + let proxy = zbus::Proxy::new( + self.proxy.connection(), + "com.github.opengamingcollective.cardwire", + "/com/github/opengamingcollective/cardwire", + "com.github.opengamingcollective.cardwire.Debug", + ) + .await?; + proxy.call("GetPciDevices", &()).await + } pub async fn set_mode(&self, mode: &u32) -> zbus::fdo::Result<()> { let proxy = zbus::Proxy::new( @@ -104,7 +119,7 @@ impl<'a> DaemonClient<'a> { "com.github.opengamingcollective.cardwire.Gpu", ) .await?; - proxy.call("PowerState", &(())).await + proxy.call("PowerState", &()).await } pub async fn lsof( diff --git a/crates/cardwire-cli/src/display.rs b/crates/cardwire-cli/src/display.rs index 35eb21e..74e8a8e 100644 --- a/crates/cardwire-cli/src/display.rs +++ b/crates/cardwire-cli/src/display.rs @@ -22,7 +22,6 @@ pub struct GpuDevice { } #[derive(serde::Deserialize, serde::Serialize, zbus::zvariant::Type)] pub struct PciDevice { - pci_address: String, iommu_group: String, vendor_id: String, device_id: String, diff --git a/crates/cardwire-cli/src/main.rs b/crates/cardwire-cli/src/main.rs index d358f50..c8edaa5 100644 --- a/crates/cardwire-cli/src/main.rs +++ b/crates/cardwire-cli/src/main.rs @@ -5,6 +5,8 @@ use args::{Args, CliMode, Commands, ConfigAction, DebugAction, ManagerAction}; use clap::{CommandFactory, Parser}; use dbus::DaemonClient; +use crate::display::print_devices_pci; + const BIN_NAME: &str = "cardwire"; #[tokio::main] @@ -49,7 +51,12 @@ async fn main() -> anyhow::Result<()> { } Commands::List { full, json } => { if full { - println!("Full PCI list is not supported in the new D-Bus API"); + match client.get_pci_device().await { + Ok(response) => { + print_devices_pci(response)?; + } + Err(e) => handle_error(e), + } } else { let mut map = std::collections::BTreeMap::new(); let objects = client.get_managed_objects().await.unwrap_or_default(); From dbf35717b906d432b2b41da8e7a68c6f2d13e84e Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 18:51:40 +0200 Subject: [PATCH 31/33] docs: update with the new APIs --- docs/development/dbus.md | 47 +++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/docs/development/dbus.md b/docs/development/dbus.md index 41e556e..c93ed15 100644 --- a/docs/development/dbus.md +++ b/docs/development/dbus.md @@ -71,23 +71,22 @@ **Methods:** -- **`DiagnosticGpu`** - Find out if the GPU can sleep or not by checking if the system config is correct. Emits diagnostic signals during execution (NOT IMPLEMENTED) +- **`GetPciDevices`** + Get a dictionary of all detected PCI devices. - **Inputs:** None - - **Outputs:** None - -**Signals:** - -- **`DiagnosticInfo`** - Emitted with diagnostic output text during the diagnostic process - - **Parameters:** `s` -- **`DiagnosticEnded`** - Emitted when the diagnostic process finishes - - **Parameters:** None - ---- - -## Gpu + - **Outputs:** + - (out): `a{s(sssssssss)}` -- A dictionary mapping PCI addresses to a struct containing: + - `iommu_group`: `s` - IOMMU group number (empty string if none) + - `vendor_id`: `s` - PCI vendor ID (empty string if unknown) + - `device_id`: `s` - PCI device ID (empty string if unknown) + - `vendor_name`: `s` - Vendor name (empty string if unknown) + - `device_name`: `s` - Device name (empty string if unknown) + - `driver`: `s` - Kernel driver in use (empty string if unknown) + - `class`: `s` - PCI class (empty string if unknown) + - `parent_pci`: `s` - Parent PCI address (empty string if unknown) + - `child_pci`: `s` - Child PCI address (empty string if unknown) + +### Gpu `/com/github/opengamingcollective/cardwire/Gpu/{id}` Represents a single GPU device, where `{id}` is the numeric identifier of the GPU (0 is always the default one). These objects can be dynamically discovered by calling `GetManagedObjects` on the standard `org.freedesktop.DBus.ObjectManager` interface located at the root path (`/com/github/opengamingcollective/cardwire`) @@ -102,7 +101,7 @@ Represents a single GPU device, where `{id}` is the numeric identifier of the GP **Methods:** - **`GetDevice`** - Get the detailed properties of this GPU + Get the detailed informations of this GPU - **Inputs:** None - **Outputs:** - (out): `(ssuubbs)` -- A struct containing: @@ -114,8 +113,20 @@ Represents a single GPU device, where `{id}` is the numeric identifier of the GP - `nvidia`: `b` - Whether the GPU is an NVIDIA device - `nvidia_minor`: `s` - NVIDIA driver minor number (empty string if not applicable) +- **`PowerState`** + Get the current power state of the GPU + - **Inputs:** None + - **Outputs:** + - (out): `s` -- The power state (e.g., "D0", "D3cold") + - **`Lsof`** - Read file descriptors to find which applications have currently opened the GPU. + Read file descriptors to find which applications have currently opened the GPU - **Inputs:** None - **Outputs:** - (out): `a{sas}` -- A dictionary mapping file paths (like `/dev/dri/card0`) to an array of process names + +**Signals:** + +- **`PowerStateChanged`** + Emitted when the power state of the GPU changes + - **Parameters:** `s` (string) -- The new power state From b46c4c67be4218d5c69a715a0a0ae5c70e17a3d0 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 19:20:12 +0200 Subject: [PATCH 32/33] feat: add nvidia block to config dbus --- crates/cardwire-core/src/gpu/ebpf.rs | 9 +++++++-- .../cardwire-daemon/src/interface/config.rs | 15 ++++++++++++--- crates/cardwire-daemon/src/models.rs | 5 ++++- crates/cardwire-ebpf/src/lib.rs | 19 ++++++++++++------- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/crates/cardwire-core/src/gpu/ebpf.rs b/crates/cardwire-core/src/gpu/ebpf.rs index a7c6f75..b289914 100644 --- a/crates/cardwire-core/src/gpu/ebpf.rs +++ b/crates/cardwire-core/src/gpu/ebpf.rs @@ -17,8 +17,13 @@ impl GpuBlocker { } pub fn set_nvidia_setting(&mut self, block: bool) -> Result<(), CardwireError> { - self.inner - .block_kind(&block.to_string(), BlockKind::NvidiaSetting)?; + if block { + self.inner + .block_kind(&block.to_string(), BlockKind::NvidiaSetting)?; + } else { + self.inner + .unblock_kind(&block.to_string(), BlockKind::NvidiaSetting)?; + } Ok(()) } pub fn set_file_block(&mut self, file: &str) -> Result<(), CardwireError> { diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index 6214dc0..4b3b9f3 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -3,6 +3,8 @@ use std::sync::{ }; use crate::file::CardwireConfig; +use cardwire_core::gpu::GpuBlocker; +use tokio::sync::RwLock; use zbus::{fdo, interface}; // Use a custom Config struct instead of CarwireConfig to allow more control over the settings @@ -30,10 +32,14 @@ impl ConfigMemory { #[derive(Clone)] pub struct ConfigInterface { pub config: Arc, + pub blocker: Arc>, } impl ConfigInterface { - pub fn build(config: Arc) -> anyhow::Result { - Ok(Self { config }) + pub fn build( + config: Arc, + blocker: Arc>, + ) -> anyhow::Result { + Ok(Self { config, blocker }) } } @@ -62,7 +68,10 @@ impl ConfigInterface { self.config .experimental_nvidia_block .store(state, Ordering::Relaxed); - Ok(()) + let mut blocker = self.blocker.write().await; + blocker + .set_nvidia_setting(state) + .map_err(|e| fdo::Error::Failed(format!("failed to set nvidia block: {}", e))) } #[zbus(property)] pub async fn battery_auto_switch(&self) -> fdo::Result { diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 85a81d5..24f4466 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -88,7 +88,10 @@ impl DaemonManager { Arc::clone(&user_config), )?, gpu_interfaces: Arc::clone(&gpu_interfaces), - config_interface: ConfigInterface::build(Arc::clone(&user_config))?, + config_interface: ConfigInterface::build( + Arc::clone(&user_config), + Arc::clone(&blocker), + )?, debug_interface: DebugInterface::build( Arc::clone(&mode_state), Arc::clone(&gpu_state), diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index 86adbad..c03444c 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -147,18 +147,14 @@ impl EbpfBlocker { map.insert(key, 1, 0).map_err(CardwireEbpfError::aya)?; } BlockKind::NvidiaSetting => { - if let Ok(block) = entity.parse::() { + if let Ok(_) = entity.parse::() { let mut map: HashMap<_, u32, u8> = HashMap::try_from( self.ebpf .map_mut(&kind_string) .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, ) .map_err(CardwireEbpfError::aya)?; - if block { - map.insert(0, 1, 0).map_err(CardwireEbpfError::aya)?; - } else { - let _ = map.remove(&0); - } + map.insert(0, 1, 0).map_err(CardwireEbpfError::aya)?; } } BlockKind::Render | BlockKind::Card | BlockKind::Nvidia => { @@ -205,7 +201,7 @@ impl EbpfBlocker { let _ = map.remove(&value); } // no file unblock - BlockKind::NvidiaFile | BlockKind::File | BlockKind::NvidiaSetting => (), + BlockKind::NvidiaFile | BlockKind::File => (), BlockKind::Render | BlockKind::Card | BlockKind::Nvidia => { let mut map: HashMap<_, u32, u8> = HashMap::try_from( self.ebpf @@ -218,6 +214,15 @@ impl EbpfBlocker { let _ = map.remove(&value); } } + BlockKind::NvidiaSetting => { + let mut map: HashMap<_, u32, u8> = HashMap::try_from( + self.ebpf + .map_mut(&kind_string) + .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, + ) + .map_err(CardwireEbpfError::aya)?; + let _ = map.remove(&0); + } } Ok(()) From 48b16649e98657436ed5789b7f607fed993f72b1 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 28 May 2026 19:24:43 +0200 Subject: [PATCH 33/33] chore(ebpf): fix redundant match --- crates/cardwire-ebpf/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index c03444c..82eeea2 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -147,7 +147,7 @@ impl EbpfBlocker { map.insert(key, 1, 0).map_err(CardwireEbpfError::aya)?; } BlockKind::NvidiaSetting => { - if let Ok(_) = entity.parse::() { + if entity.parse::().is_ok() { let mut map: HashMap<_, u32, u8> = HashMap::try_from( self.ebpf .map_mut(&kind_string)