diff --git a/Cargo.lock b/Cargo.lock index 6e39716..f8aa1d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,6 +339,7 @@ dependencies = [ "serde_json", "thiserror 2.0.18", "tokio", + "tokio-stream", "toml", "zbus", ] @@ -1430,6 +1431,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "1.1.2+spec-1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 34142f1..20d93eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ env_logger = "0.11" toml = "1.0.3" aya = "0.13.1" serde_json = "1.0.149" +tokio-stream = "0.1.18" [profile.dev] panic = "abort" diff --git a/crates/cardwire-daemon/Cargo.toml b/crates/cardwire-daemon/Cargo.toml index 91b5d76..419ef0d 100644 --- a/crates/cardwire-daemon/Cargo.toml +++ b/crates/cardwire-daemon/Cargo.toml @@ -20,6 +20,8 @@ anyhow.workspace = true env_logger.workspace = true toml.workspace = true serde_json.workspace = true +tokio-stream.workspace = true + [[bin]] name = "cardwired" path = "src/daemon.rs" diff --git a/crates/cardwire-daemon/src/config.rs b/crates/cardwire-daemon/src/config.rs index 30444e0..8a6d508 100644 --- a/crates/cardwire-daemon/src/config.rs +++ b/crates/cardwire-daemon/src/config.rs @@ -16,13 +16,22 @@ enum FileKind { ModeState, PciState, } -// TODO: Handle fs error for tomorrow -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Debug)] +#[serde(default)] pub struct CardwireConfig { auto_apply_gpu_state: bool, block_nvidia_vulkan: bool, + battery_auto_switch: bool, +} +impl Default for CardwireConfig { + fn default() -> Self { + CardwireConfig { + auto_apply_gpu_state: true, + block_nvidia_vulkan: false, + battery_auto_switch: false, + } + } } - impl CardwireConfig { /// Read TOML config file and return it's settings as a struct // TODO: Error handling on std::fs @@ -51,6 +60,9 @@ impl CardwireConfig { pub fn auto_apply_gpu_state(&self) -> bool { self.auto_apply_gpu_state } + pub fn battery_auto_switch(&self) -> bool { + self.battery_auto_switch + } } // This is the easiest way i found to have a good looking json, might change later @@ -186,10 +198,7 @@ fn create_default_file(kind: FileKind) -> anyhow::Result<()> { .context("could not create default folder for cardwire.toml")?; // Default config for cardwire // TODO: Move to default trait? - let default_config = toml::to_string_pretty(&CardwireConfig { - auto_apply_gpu_state: true, - block_nvidia_vulkan: false, - })?; + let default_config = toml::to_string_pretty(&CardwireConfig::default())?; // write fs::write(format!("{}/cardwire.toml", CONFIG_PATH), default_config) } diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index 024fdf7..fa9a767 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -1,6 +1,7 @@ //! entry point of cardwired mod config; mod dbus; +mod listeners; mod models; use crate::models::Daemon; diff --git a/crates/cardwire-daemon/src/listeners.rs b/crates/cardwire-daemon/src/listeners.rs new file mode 100644 index 0000000..daf05d1 --- /dev/null +++ b/crates/cardwire-daemon/src/listeners.rs @@ -0,0 +1,45 @@ +//! Used to listen to other dbus interface, mainly for auto battery switch and display detection + +use log::info; +use tokio_stream::StreamExt; +use zbus::{Connection, Result, proxy}; + +#[proxy( + interface = "org.freedesktop.UPower", + default_service = "org.freedesktop.UPower", + default_path = "/org/freedesktop/UPower" +)] +trait UPower { + #[zbus(property)] + fn on_battery(&self) -> Result; +} +#[proxy( + interface = "com.github.opengamingcollective.cardwire", + default_service = "com.github.opengamingcollective.cardwire", + default_path = "/com/github/opengamingcollective/cardwire" +)] +trait Cardwire { + #[zbus(property)] + fn set_mode(&self, mode: u32) -> Result<()>; +} + +pub async fn watch_battery_status() -> 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; + + while let Some(msg) = battery_stream.next().await { + if let Ok(state) = msg.get().await { + info!("battery event detected: {:?}", state); + match state { + true => cardwire.set_mode(0).await?, + false => cardwire.set_mode(1).await?, + }; + } + } + + Ok(()) +} diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index e885693..2e2c8d6 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -144,9 +144,12 @@ impl Daemon { Modes::Hybrid => 1, Modes::Manual => 2, }; - 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(()) } } diff --git a/nix/ci-2gpu.nix b/nix/ci-2gpu.nix index 79c557c..c93b3a1 100644 --- a/nix/ci-2gpu.nix +++ b/nix/ci-2gpu.nix @@ -71,6 +71,6 @@ t.assertIn("Hybrid", machine.succeed("cat /var/lib/cardwire/mode.json"), "mode.json didnt get saved") with subtest("Try to block default gpu"): - t.assertIn("cannot be blocked", machine.succeed("cardwire gpu 0 --block 2>&1"), "Default gpu got blocked") + t.assertIn("Per GPU block is only available on manual mode", machine.succeed("cardwire gpu 0 --block 2>&1"), "Default gpu got blocked") ''; } diff --git a/nix/default.nix b/nix/default.nix index 9eb07a5..006dc46 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -27,6 +27,7 @@ in ]; runtimeDeps = [ pkgs.hwdata + pkgs.upower ]; doCheck = false; doInstallCheck = true; diff --git a/packages/arch-linux/cardwire-PKGBUILD b/packages/arch-linux/cardwire-PKGBUILD index 64089a3..e6348a8 100644 --- a/packages/arch-linux/cardwire-PKGBUILD +++ b/packages/arch-linux/cardwire-PKGBUILD @@ -9,7 +9,7 @@ pkgdesc='GPU manager for Linux using eBPF LSM hooks' arch=('x86_64') url='https://github.com/OpenGamingCollective/cardwire' license=('GPL3') -depends=('hwdata' 'dbus' 'systemd') +depends=('hwdata' 'dbus' 'systemd' 'upower') makedepends=('libbpf' 'rust' 'clang') source=("https://github.com/OpenGamingCollective/cardwire/archive/refs/tags/v$pkgver.tar.gz") sha256sums=('37882d4d0d431c3ff48e24bd47cea03b7847080623254d8bdb4af9846134d700') diff --git a/packages/fedora/cardwire.spec b/packages/fedora/cardwire.spec index 3f869cd..17d9da6 100644 --- a/packages/fedora/cardwire.spec +++ b/packages/fedora/cardwire.spec @@ -13,6 +13,9 @@ BuildRequires: libbpf-devel BuildRequires: make BuildRequires: systemd-rpm-macros +Requires: hwdata +Requires: upower + %description Cardwire is a GPU manager for Linux that uses eBPF LSM hooks to block or unblock access to GPU device nodes.