diff --git a/Cargo.lock b/Cargo.lock index a16239fa..0f560714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "async-trait" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -645,6 +656,7 @@ dependencies = [ name = "rog-daemon" version = "0.15.3" dependencies = [ + "async-trait", "dbus", "dbus-tokio", "env_logger", diff --git a/rog-core/Cargo.toml b/rog-core/Cargo.toml index 881fcf1f..bf805401 100644 --- a/rog-core/Cargo.toml +++ b/rog-core/Cargo.toml @@ -21,6 +21,7 @@ path = "src/main.rs" rog-client = { path = "../rog-client" } rusb = "^0.6.0" udev = "^0.4.0" +async-trait = "0.1.36" # cli and logging gumdrop = "^0.8.0" diff --git a/rog-core/src/ctrl_anime.rs b/rog-core/src/ctrl_anime.rs index a2af7351..e8e149ba 100644 --- a/rog-core/src/ctrl_anime.rs +++ b/rog-core/src/ctrl_anime.rs @@ -9,12 +9,15 @@ const INIT: u8 = 0xc2; const APPLY: u8 = 0xc3; const SET: u8 = 0xc4; -use log::{error, warn}; +use crate::config::Config; +use log::{error, info, warn}; use rog_client::error::AuraError; use rusb::{Device, DeviceHandle}; use std::error::Error; +use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc::Receiver; +use tokio::sync::Mutex; use tokio::task::JoinHandle; #[allow(dead_code)] @@ -26,16 +29,45 @@ pub enum AnimatrixCommand { //ReloadLast, } -pub struct AniMeWriter { +pub struct CtrlAnimeDisplay { handle: DeviceHandle, initialised: bool, } -impl AniMeWriter { +use ::dbus::{nonblock::SyncConnection, tree::Signal}; +use async_trait::async_trait; + +#[async_trait] +impl crate::Controller for CtrlAnimeDisplay { + type A = Vec>; + + /// Spawns two tasks which continuously check for changes + fn spawn_task( + mut self, + _: Arc>, + mut recv: Receiver, + _: Option>, + _: Option>>, + ) -> JoinHandle<()> { + tokio::spawn(async move { + while let Some(image) = recv.recv().await { + self.do_command(AnimatrixCommand::WriteImage(image)) + .await + .unwrap_or_else(|err| warn!("{}", err)); + } + }) + } + + async fn reload_from_config(&mut self, _: &mut Config) -> Result<(), Box> { + Ok(()) + } +} + +impl CtrlAnimeDisplay { #[inline] - pub fn new() -> Result> { + pub fn new() -> Result> { // We don't expect this ID to ever change - let device = AniMeWriter::get_device(0x0b05, 0x193b).map_err(|err| { + let device = CtrlAnimeDisplay::get_device(0x0b05, 0x193b).map_err(|err| { warn!("Could not get AniMe display handle: {:?}", err); err })?; @@ -53,27 +85,13 @@ impl AniMeWriter { err })?; - Ok(AniMeWriter { + info!("Device has an AniMe Matrix display"); + Ok(CtrlAnimeDisplay { handle: device, initialised: false, }) } - /// Spawns two tasks which continuously check for changes - pub(crate) fn spawn_task( - mut ctrlr: AniMeWriter, - mut recv: Receiver>>, - ) -> JoinHandle<()> { - tokio::spawn(async move { - while let Some(image) = recv.recv().await { - ctrlr - .do_command(AnimatrixCommand::WriteImage(image)) - .await - .unwrap_or_else(|err| warn!("{}", err)); - } - }) - } - #[inline] fn get_device(vendor: u16, product: u16) -> Result, rusb::Error> { for device in rusb::devices()?.iter() { diff --git a/rog-core/src/ctrl_charge.rs b/rog-core/src/ctrl_charge.rs index 4a07b1fa..1371a986 100644 --- a/rog-core/src/ctrl_charge.rs +++ b/rog-core/src/ctrl_charge.rs @@ -15,29 +15,60 @@ pub struct CtrlCharge { path: &'static str, } -impl CtrlCharge { - pub(super) fn new() -> Result> { - let path = CtrlCharge::get_battery_path()?; +use ::dbus::{nonblock::SyncConnection, tree::Signal}; +use async_trait::async_trait; - Ok(CtrlCharge { path }) - } +#[async_trait] +impl crate::Controller for CtrlCharge { + type A = u8; /// Spawns two tasks which continuously check for changes - pub(crate) fn spawn_task( - ctrlr: CtrlCharge, + fn spawn_task( + self, config: Arc>, - mut recv: Receiver, + mut recv: Receiver, + _: Option>, + _: Option>>, ) -> JoinHandle<()> { tokio::spawn(async move { while let Some(n) = recv.recv().await { let mut config = config.lock().await; - ctrlr - .set_charge_limit(n, &mut config) + self.set_charge_limit(n, &mut config) .unwrap_or_else(|err| warn!("{:?}", err)); } }) } + async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box> { + config.read(); + info!("Reloaded battery charge limit"); + self.set_charge_limit(config.bat_charge_limit, config) + } +} + +impl CtrlCharge { + pub(super) fn new() -> Result> { + let path = CtrlCharge::get_battery_path()?; + info!("Device has battery charge threshold control"); + Ok(CtrlCharge { path }) + } + + // /// Spawns two tasks which continuously check for changes + // pub(crate) fn spawn_task( + // self, + // config: Arc>, + // mut recv: Receiver, + // ) -> JoinHandle<()> { + // tokio::spawn(async move { + // while let Some(n) = recv.recv().await { + // let mut config = config.lock().await; + // self + // .set_charge_limit(n, &mut config) + // .unwrap_or_else(|err| warn!("{:?}", err)); + // } + // }) + // } + fn get_battery_path() -> Result<&'static str, std::io::Error> { if Path::new(BAT_CHARGE_PATH).exists() { Ok(BAT_CHARGE_PATH) @@ -49,14 +80,14 @@ impl CtrlCharge { } } - pub(super) fn bat_charge_limit_reload( - &self, - config: &mut Config, - ) -> Result<(), Box> { - config.read(); - info!("Reloaded battery charge limit"); - self.set_charge_limit(config.bat_charge_limit, config) - } + // pub(super) fn reload_from_config( + // &self, + // config: &mut Config, + // ) -> Result<(), Box> { + // config.read(); + // info!("Reloaded battery charge limit"); + // self.set_charge_limit(config.bat_charge_limit, config) + // } pub(super) fn set_charge_limit( &self, diff --git a/rog-core/src/ctrl_fan_cpu.rs b/rog-core/src/ctrl_fan_cpu.rs index 5783c1a5..dbcaa874 100644 --- a/rog-core/src/ctrl_fan_cpu.rs +++ b/rog-core/src/ctrl_fan_cpu.rs @@ -18,20 +18,22 @@ pub struct CtrlFanAndCPU { path: &'static str, } -impl CtrlFanAndCPU { - pub(super) fn new() -> Result> { - let path = CtrlFanAndCPU::get_fan_path()?; +use ::dbus::{nonblock::SyncConnection, tree::Signal}; +use async_trait::async_trait; - Ok(CtrlFanAndCPU { path }) - } +#[async_trait] +impl crate::Controller for CtrlFanAndCPU { + type A = u8; /// Spawns two tasks which continuously check for changes - pub(crate) fn spawn_task( - ctrlr: CtrlFanAndCPU, + fn spawn_task( + self, config: Arc>, - mut recv: Receiver, + mut recv: Receiver, + _: Option>, + _: Option>>, ) -> JoinHandle<()> { - let gate1 = Arc::new(Mutex::new(ctrlr)); + let gate1 = Arc::new(Mutex::new(self)); let gate2 = gate1.clone(); let config1 = config.clone(); // spawn an endless loop @@ -58,6 +60,23 @@ impl CtrlFanAndCPU { }) } + async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box> { + let mut file = OpenOptions::new().write(true).open(self.path)?; + file.write_all(format!("{:?}\n", config.fan_mode).as_bytes()) + .unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err)); + self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?; + info!("Reloaded fan mode: {:?}", FanLevel::from(config.fan_mode)); + Ok(()) + } +} + +impl CtrlFanAndCPU { + pub(super) fn new() -> Result> { + let path = CtrlFanAndCPU::get_fan_path()?; + info!("Device has thermal throttle control"); + Ok(CtrlFanAndCPU { path }) + } + fn get_fan_path() -> Result<&'static str, std::io::Error> { if Path::new(FAN_TYPE_1_PATH).exists() { Ok(FAN_TYPE_1_PATH) @@ -71,15 +90,6 @@ impl CtrlFanAndCPU { } } - pub(super) fn fan_mode_reload(&mut self, config: &mut Config) -> Result<(), Box> { - let mut file = OpenOptions::new().write(true).open(self.path)?; - file.write_all(format!("{:?}\n", config.fan_mode).as_bytes()) - .unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err)); - self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?; - info!("Reloaded fan mode: {:?}", FanLevel::from(config.fan_mode)); - Ok(()) - } - pub(super) fn fan_mode_check_change( &mut self, config: &mut Config, diff --git a/rog-core/src/ctrl_leds.rs b/rog-core/src/ctrl_leds.rs index 286dccc0..7c2842ae 100644 --- a/rog-core/src/ctrl_leds.rs +++ b/rog-core/src/ctrl_leds.rs @@ -9,6 +9,7 @@ use rog_client::{ aura_brightness_bytes, aura_modes::AuraModes, fancy::KeyColourArray, DBUS_IFACE, DBUS_PATH, LED_MSG_LEN, }; +use std::error::Error; use std::fs::OpenOptions; use std::io::Write; use std::sync::Arc; @@ -16,67 +17,47 @@ use tokio::sync::mpsc::Receiver; use tokio::sync::Mutex; use tokio::task::JoinHandle; -pub struct LedWriter { +pub struct CtrlKbdBacklight { dev_node: String, supported_modes: Vec, flip_effect_write: bool, } -impl LedWriter { - #[inline] - pub fn new(id_product: &str, supported_modes: Vec) -> Result { - let mut enumerator = udev::Enumerator::new()?; - enumerator.match_subsystem("hidraw")?; +use async_trait::async_trait; - for device in enumerator.scan_devices()? { - if let Some(parent) = device.parent_with_subsystem_devtype("usb", "usb_device")? { - if parent.attribute_value("idProduct").unwrap() == id_product - // && device.parent().unwrap().sysnum().unwrap() == 3 - { - if let Some(dev_node) = device.devnode() { - info!("Using device at: {:?} for LED control", dev_node); - return Ok(LedWriter { - dev_node: dev_node.to_string_lossy().to_string(), - supported_modes, - flip_effect_write: false, - }); - } - } - } - } - Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Device node not found", - )) - } +#[async_trait] +impl crate::Controller for CtrlKbdBacklight { + type A = AuraModes; /// Spawns two tasks which continuously check for changes - pub(crate) fn spawn_task( - mut ctrlr: LedWriter, + fn spawn_task( + mut self, config: Arc>, - mut recv: Receiver, - connection: Arc, - signal: Arc>, + mut recv: Receiver, + connection: Option>, + signal: Option>>, ) -> JoinHandle<()> { tokio::spawn(async move { while let Some(command) = recv.recv().await { let mut config = config.lock().await; match &command { AuraModes::RGB(_) => { - ctrlr - .do_command(command, &mut config) + self.do_command(command, &mut config) .await .unwrap_or_else(|err| warn!("{}", err)); } _ => { let json = serde_json::to_string(&command).unwrap(); - ctrlr - .do_command(command, &mut config) + self.do_command(command, &mut config) .await .unwrap_or_else(|err| warn!("{}", err)); connection + .as_ref() + .expect("LED Controller must have DBUS connection") .send( signal + .as_ref() + .expect("LED Controller must have DBUS signal") .msg(&DBUS_PATH.into(), &DBUS_IFACE.into()) .append1(json), ) @@ -87,111 +68,7 @@ impl LedWriter { }) } - pub async fn do_command( - &mut self, - mode: AuraModes, - config: &mut Config, - ) -> Result<(), RogError> { - self.set_and_save(mode, config).await - } - - /// Should only be used if the bytes you are writing are verified correct - #[inline] - async fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> { - if let Ok(mut file) = OpenOptions::new().write(true).open(&self.dev_node) { - file.write_all(message).unwrap(); - return Ok(()); - } - Err(RogError::NotSupported) - } - - /// Write an effect block - #[inline] - async fn write_effect(&mut self, effect: &[Vec]) -> Result<(), RogError> { - if self.flip_effect_write { - for row in effect.iter().rev() { - self.write_bytes(row).await?; - } - } else { - for row in effect.iter() { - self.write_bytes(row).await?; - } - } - self.flip_effect_write = !self.flip_effect_write; - Ok(()) - } - - /// Used to set a builtin mode and save the settings for it - /// - /// This needs to be universal so that settings applied by dbus stick - #[inline] - async fn set_and_save(&mut self, mode: AuraModes, config: &mut Config) -> Result<(), RogError> { - match mode { - AuraModes::LedBrightness(n) => { - let bytes: [u8; LED_MSG_LEN] = (&mode).into(); - self.write_bytes(&bytes).await?; - config.brightness = n; - config.write(); - info!("LED brightness set to {:#?}", n); - } - AuraModes::RGB(v) => { - if v.is_empty() || v[0].is_empty() { - let bytes = KeyColourArray::get_init_msg(); - self.write_bytes(&bytes).await?; - } else { - self.write_effect(&v).await?; - } - } - _ => { - let mode_num: u8 = u8::from(&mode); - self.write_mode(&mode).await?; - config.current_mode = mode_num; - config.set_mode_data(mode); - config.write(); - } - } - Ok(()) - } - - #[inline] - async fn write_mode(&mut self, mode: &AuraModes) -> Result<(), RogError> { - match mode { - AuraModes::RGB(v) => { - if v.is_empty() || v[0].is_empty() { - let bytes = KeyColourArray::get_init_msg(); - self.write_bytes(&bytes).await?; - } else { - self.write_effect(v).await?; - } - } - _ => { - let mode_num: u8 = u8::from(mode); - match mode { - AuraModes::MultiStatic(_) => { - if self.supported_modes.contains(&mode_num) { - let bytes: [[u8; LED_MSG_LEN]; 4] = mode.into(); - for array in bytes.iter() { - self.write_bytes(array).await?; - } - } - } - _ => { - if self.supported_modes.contains(&mode_num) { - let bytes: [u8; LED_MSG_LEN] = mode.into(); - self.write_bytes(&bytes).await?; - } - } - } - self.write_bytes(&LED_SET).await?; - // Changes won't persist unless apply is set - self.write_bytes(&LED_APPLY).await?; - } - } - Ok(()) - } - - #[inline] - pub async fn reload_last_builtin(&mut self, config: &mut Config) -> Result<(), RogError> { + async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box> { // set current mode (if any) if self.supported_modes.len() > 1 { if self.supported_modes.contains(&config.current_mode) { @@ -232,3 +109,145 @@ impl LedWriter { Ok(()) } } + +impl CtrlKbdBacklight { + #[inline] + pub fn new(id_product: &str, supported_modes: Vec) -> Result { + let mut enumerator = udev::Enumerator::new()?; + enumerator.match_subsystem("hidraw")?; + + for device in enumerator.scan_devices()? { + if let Some(parent) = device.parent_with_subsystem_devtype("usb", "usb_device")? { + if parent.attribute_value("idProduct").unwrap() == id_product + // && device.parent().unwrap().sysnum().unwrap() == 3 + { + if let Some(dev_node) = device.devnode() { + info!("Device has keyboard backlight control"); + info!("Using device at: {:?} for LED control", dev_node); + return Ok(CtrlKbdBacklight { + dev_node: dev_node.to_string_lossy().to_string(), + supported_modes, + flip_effect_write: false, + }); + } + } + } + } + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Device node not found", + )) + } + + pub async fn do_command( + &mut self, + mode: AuraModes, + config: &mut Config, + ) -> Result<(), Box> { + self.set_and_save(mode, config).await + } + + /// Should only be used if the bytes you are writing are verified correct + #[inline] + async fn write_bytes(&self, message: &[u8]) -> Result<(), Box> { + if let Ok(mut file) = OpenOptions::new().write(true).open(&self.dev_node) { + file.write_all(message).unwrap(); + return Ok(()); + } + Err(Box::new(RogError::NotSupported)) + } + + /// Write an effect block + #[inline] + async fn write_effect(&mut self, effect: &[Vec]) -> Result<(), Box> { + if self.flip_effect_write { + for row in effect.iter().rev() { + self.write_bytes(row).await?; + } + } else { + for row in effect.iter() { + self.write_bytes(row).await?; + } + } + self.flip_effect_write = !self.flip_effect_write; + Ok(()) + } + + /// Used to set a builtin mode and save the settings for it + /// + /// This needs to be universal so that settings applied by dbus stick + #[inline] + async fn set_and_save( + &mut self, + mode: AuraModes, + config: &mut Config, + ) -> Result<(), Box> { + match mode { + AuraModes::LedBrightness(n) => { + let bytes: [u8; LED_MSG_LEN] = (&mode).into(); + self.write_bytes(&bytes).await?; + config.brightness = n; + config.write(); + info!("LED brightness set to {:#?}", n); + } + AuraModes::RGB(v) => { + if v.is_empty() || v[0].is_empty() { + let bytes = KeyColourArray::get_init_msg(); + self.write_bytes(&bytes).await?; + } else { + self.write_effect(&v).await?; + } + } + _ => { + let mode_num: u8 = u8::from(&mode); + self.write_mode(&mode).await?; + config.current_mode = mode_num; + config.set_mode_data(mode); + config.write(); + } + } + Ok(()) + } + + #[inline] + async fn write_mode(&mut self, mode: &AuraModes) -> Result<(), Box> { + match mode { + AuraModes::RGB(v) => { + if v.is_empty() || v[0].is_empty() { + let bytes = KeyColourArray::get_init_msg(); + self.write_bytes(&bytes).await?; + } else { + self.write_effect(v).await?; + } + } + _ => { + let mode_num: u8 = u8::from(mode); + match mode { + AuraModes::MultiStatic(_) => { + if self.supported_modes.contains(&mode_num) { + let bytes: [[u8; LED_MSG_LEN]; 4] = mode.into(); + for array in bytes.iter() { + self.write_bytes(array).await?; + } + } + } + _ => { + if self.supported_modes.contains(&mode_num) { + let bytes: [u8; LED_MSG_LEN] = mode.into(); + self.write_bytes(&bytes).await?; + } + } + } + self.write_bytes(&LED_SET).await?; + // Changes won't persist unless apply is set + self.write_bytes(&LED_APPLY).await?; + } + } + Ok(()) + } + + // #[inline] + // pub async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), RogError> { + + // } +} diff --git a/rog-core/src/daemon.rs b/rog-core/src/daemon.rs index decda7ec..2237b51e 100644 --- a/rog-core/src/daemon.rs +++ b/rog-core/src/daemon.rs @@ -1,12 +1,13 @@ use crate::{ - config::Config, ctrl_anime::AniMeWriter, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCPU, - ctrl_leds::LedWriter, dbus::dbus_create_tree, laptops::match_laptop, + config::Config, ctrl_anime::CtrlAnimeDisplay, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCPU, + ctrl_leds::CtrlKbdBacklight, dbus::dbus_create_tree, laptops::match_laptop, }; use dbus::{channel::Sender, nonblock::SyncConnection, tree::Signal}; +use crate::Controller; use dbus_tokio::connection; -use log::{error, info, warn}; +use log::{error, warn}; use rog_client::{DBUS_IFACE, DBUS_NAME, DBUS_PATH}; use std::error::Error; use std::sync::Arc; @@ -25,16 +26,13 @@ pub async fn start_daemon() -> Result<(), Box> { let laptop = match_laptop(); let mut config = Config::default().load(laptop.supported_modes()); - let mut led_control = LedWriter::new(laptop.usb_product(), laptop.supported_modes().to_owned()) + let mut led_control = CtrlKbdBacklight::new(laptop.usb_product(), laptop.supported_modes().to_owned()) .map_or_else( |err| { error!("{}", err); None }, - |ledwriter| { - info!("Device has keyboard backlight control"); - Some(ledwriter) - }, + Some, ); let mut charge_control = CtrlCharge::new().map_or_else( @@ -42,10 +40,7 @@ pub async fn start_daemon() -> Result<(), Box> { error!("{}", err); None }, - |ledwriter| { - info!("Device has battery charge threshold control"); - Some(ledwriter) - }, + Some, ); let mut fan_control = CtrlFanAndCPU::new().map_or_else( @@ -53,34 +48,28 @@ pub async fn start_daemon() -> Result<(), Box> { error!("{}", err); None }, - |ledwriter| { - info!("Device has thermal throttle control"); - Some(ledwriter) - }, + Some, ); // Reload settings - if let Some(ctrlr) = fan_control.as_mut() { - ctrlr - .fan_mode_reload(&mut config) + if let Some(ctrl) = fan_control.as_mut() { + ctrl.reload_from_config(&mut config) + .await .unwrap_or_else(|err| warn!("Fan mode: {}", err)); } - if let Some(ctrlr) = charge_control.as_mut() { - ctrlr - .bat_charge_limit_reload(&mut config) + if let Some(ctrl) = charge_control.as_mut() { + ctrl.reload_from_config(&mut config) + .await .unwrap_or_else(|err| warn!("Battery charge limit: {}", err)); } - if let Some(writer) = led_control.as_mut() { - writer - .reload_last_builtin(&mut config) + if let Some(ctrl) = led_control.as_mut() { + ctrl.reload_from_config(&mut config) .await .unwrap_or_else(|err| warn!("Reload settings: {}", err)); } - // Set up the mutexes - let config = Arc::new(Mutex::new(config)); let (resource, connection) = connection::new_system_sync()?; tokio::spawn(async { let err = resource.await; @@ -91,6 +80,7 @@ pub async fn start_daemon() -> Result<(), Box> { .request_name(DBUS_NAME, false, true, true) .await?; + let config = Arc::new(Mutex::new(config)); let ( tree, aura_command_recv, @@ -101,6 +91,7 @@ pub async fn start_daemon() -> Result<(), Box> { fanmode_signal, charge_limit_signal, ) = dbus_create_tree(config.clone()); + // We add the tree to the connection so that incoming method calls will be handled. tree.start_receive_send(&*connection); @@ -122,28 +113,26 @@ pub async fn start_daemon() -> Result<(), Box> { charge_limit_signal, ); - let mut handles = Vec::new(); // Begin all tasks - if let Ok(ctrlr) = AniMeWriter::new() { - info!("Device has an AniMe Matrix display"); - handles.push(AniMeWriter::spawn_task(ctrlr, animatrix_recv)); + let mut handles = Vec::new(); + if let Ok(ctrl) = CtrlAnimeDisplay::new() { + handles.push(ctrl.spawn_task(config.clone(), animatrix_recv, None, None)); } - if let Some(ctrlr) = fan_control.take() { - handles.push(CtrlFanAndCPU::spawn_task(ctrlr, config.clone(), fan_mode_recv)); + if let Some(ctrl) = fan_control.take() { + handles.push(ctrl.spawn_task(config.clone(), fan_mode_recv, None, None)); } - if let Some(ctrlr) = charge_control.take() { - handles.push(CtrlCharge::spawn_task(ctrlr, config.clone(), charge_limit_recv)); + if let Some(ctrl) = charge_control.take() { + handles.push(ctrl.spawn_task(config.clone(), charge_limit_recv, None, None)); } - if let Some(ctrlr) = led_control.take() { - handles.push(LedWriter::spawn_task( - ctrlr, + if let Some(ctrl) = led_control.take() { + handles.push(ctrl.spawn_task( config.clone(), aura_command_recv, - connection.clone(), - led_changed_signal, + Some(connection.clone()), + Some(led_changed_signal), )); } @@ -154,6 +143,7 @@ pub async fn start_daemon() -> Result<(), Box> { Ok(()) } +// TODO: Move these in to the controllers tasks fn start_signal_task( connection: Arc, config: Arc>, @@ -179,6 +169,7 @@ fn start_signal_task( ) .unwrap_or_else(|_| 0); } + if config.bat_charge_limit != last_charge_limit { last_charge_limit = config.bat_charge_limit; connection @@ -200,7 +191,9 @@ async fn send_boot_signals( charge_limit_signal: Arc>, led_changed_signal: Arc>, ) -> Result<(), Box> { + let config = config.lock().await; + if let Some(data) = config.get_led_mode_data(config.current_mode) { connection .send( @@ -210,6 +203,7 @@ async fn send_boot_signals( ) .unwrap_or_else(|_| 0); } + connection .send( fanmode_signal @@ -217,6 +211,7 @@ async fn send_boot_signals( .append1(config.fan_mode), ) .unwrap_or_else(|_| 0); + connection .send( charge_limit_signal @@ -224,5 +219,6 @@ async fn send_boot_signals( .append1(config.bat_charge_limit), ) .unwrap_or_else(|_| 0); + Ok(()) } diff --git a/rog-core/src/error.rs b/rog-core/src/error.rs index 0ab231be..f6272608 100644 --- a/rog-core/src/error.rs +++ b/rog-core/src/error.rs @@ -1,10 +1,13 @@ use std::fmt; +#[derive(Debug)] pub enum RogError { ParseFanLevel, NotSupported, } +impl std::error::Error for RogError {} + impl fmt::Display for RogError { // This trait requires `fmt` with this exact signature. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/rog-core/src/lib.rs b/rog-core/src/lib.rs index 52ed2f6e..f3903934 100644 --- a/rog-core/src/lib.rs +++ b/rog-core/src/lib.rs @@ -17,3 +17,29 @@ mod dbus; mod laptops; mod error; + +use async_trait::async_trait; +use config::Config; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc::Receiver, Mutex}; +use tokio::task::JoinHandle; + +use ::dbus::{nonblock::SyncConnection, tree::Signal}; + +#[async_trait] +pub trait Controller { + type A; + + async fn reload_from_config(&mut self, config: &mut Config) -> Result<(), Box>; + + /// Spawn an infinitely running task (usually) which checks a Receiver for input, + /// and may send a signal over dbus + fn spawn_task( + self, + config: Arc>, + recv: Receiver, + connection: Option>, + signal: Option>>, + ) -> JoinHandle<()>; +}