diff --git a/asusctl/examples/aura-rgb-ball.rs b/asusctl/examples/aura-rgb-ball.rs index 651ea6b9..22dbd604 100644 --- a/asusctl/examples/aura-rgb-ball.rs +++ b/asusctl/examples/aura-rgb-ball.rs @@ -1,5 +1,5 @@ -use rog_dbus::AuraDbusClient; use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout}; +use rog_dbus::AuraDbusClient; use std::collections::LinkedList; #[derive(Debug, Clone)] diff --git a/asusctl/examples/aura-rgb-comet.rs b/asusctl/examples/aura-rgb-comet.rs index c31dba77..72b109bb 100644 --- a/asusctl/examples/aura-rgb-comet.rs +++ b/asusctl/examples/aura-rgb-comet.rs @@ -1,5 +1,5 @@ -use rog_dbus::AuraDbusClient; use rog_aura::{GX502Layout, KeyColourArray, KeyLayout}; +use rog_dbus::AuraDbusClient; fn main() -> Result<(), Box> { let (dbus, _) = AuraDbusClient::new()?; diff --git a/asusctl/examples/aura-rgb-iterate-keys.rs b/asusctl/examples/aura-rgb-iterate-keys.rs index b440a876..7ab9376f 100644 --- a/asusctl/examples/aura-rgb-iterate-keys.rs +++ b/asusctl/examples/aura-rgb-iterate-keys.rs @@ -1,5 +1,5 @@ -use rog_dbus::AuraDbusClient; use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout}; +use rog_dbus::AuraDbusClient; fn main() -> Result<(), Box> { let (dbus, _) = AuraDbusClient::new()?; diff --git a/asusctl/examples/aura-rgb-per-key-effect-2.rs b/asusctl/examples/aura-rgb-per-key-effect-2.rs index 2d6fa788..bbbb1e3f 100644 --- a/asusctl/examples/aura-rgb-per-key-effect-2.rs +++ b/asusctl/examples/aura-rgb-per-key-effect-2.rs @@ -1,5 +1,5 @@ -use rog_dbus::AuraDbusClient; use rog_aura::{Key, KeyColourArray}; +use rog_dbus::AuraDbusClient; fn main() -> Result<(), Box> { let (dbus, _) = AuraDbusClient::new()?; diff --git a/asusctl/examples/aura-rgb-pulser.rs b/asusctl/examples/aura-rgb-pulser.rs index 168d991c..354864c7 100644 --- a/asusctl/examples/aura-rgb-pulser.rs +++ b/asusctl/examples/aura-rgb-pulser.rs @@ -1,5 +1,5 @@ -use rog_dbus::AuraDbusClient; use rog_aura::{GX502Layout, KeyColourArray, KeyLayout}; +use rog_dbus::AuraDbusClient; fn main() -> Result<(), Box> { let (dbus, _) = AuraDbusClient::new()?; diff --git a/asusctl/src/aura_cli.rs b/asusctl/src/aura_cli.rs index 8c7d93ac..d981966a 100644 --- a/asusctl/src/aura_cli.rs +++ b/asusctl/src/aura_cli.rs @@ -1,5 +1,5 @@ use gumdrop::Options; -use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, error::Error}; +use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed}; use std::str::FromStr; #[derive(Options)] diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index 40b39713..7761779a 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -5,8 +5,8 @@ use crate::aura_cli::{LedBrightness, SetAuraBuiltin}; use anime_cli::{AnimeActions, AnimeCommand}; use gumdrop::{Opt, Options}; use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2, ANIME_DATA_LEN}; -use rog_dbus::AuraDbusClient; use rog_aura::{self, AuraEffect, AuraModeNum}; +use rog_dbus::AuraDbusClient; use rog_types::{ gfx_vendors::GfxVendors, profile::{FanLevel, ProfileCommand, ProfileEvent}, diff --git a/daemon-user/src/ctrl_anime.rs b/daemon-user/src/ctrl_anime.rs index d67e6601..4f8c4010 100644 --- a/daemon-user/src/ctrl_anime.rs +++ b/daemon-user/src/ctrl_anime.rs @@ -147,7 +147,6 @@ impl<'a> CtrlAnime<'static> { } } - // The pattern for a zbus method is: // - Get config lock if required // - Set inner_early_return to stop the inner run loop temporarily diff --git a/daemon-user/src/lib.rs b/daemon-user/src/lib.rs index 331ae57a..8f9f1c44 100644 --- a/daemon-user/src/lib.rs +++ b/daemon-user/src/lib.rs @@ -6,4 +6,4 @@ pub mod ctrl_anime; pub mod zbus_anime; -pub static DBUS_NAME: &str = "org.asuslinux.Daemon"; \ No newline at end of file +pub static DBUS_NAME: &str = "org.asuslinux.Daemon"; diff --git a/daemon-user/src/user_config.rs b/daemon-user/src/user_config.rs index d1464448..9a3bd31e 100644 --- a/daemon-user/src/user_config.rs +++ b/daemon-user/src/user_config.rs @@ -101,7 +101,11 @@ impl UserConfig { path.push("rog-user.cfg"); - let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(&path)?; + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&path)?; let json = serde_json::to_string_pretty(&self).unwrap(); dbg!(&json); diff --git a/daemon/src/config_anime.rs b/daemon/src/config_anime.rs new file mode 100644 index 00000000..1d7bb936 --- /dev/null +++ b/daemon/src/config_anime.rs @@ -0,0 +1,131 @@ +use log::{error, warn}; +use rog_anime::{error::AnimeError, ActionData, AnimTime, AnimeAction, Vec2}; +use serde_derive::{Deserialize, Serialize}; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Write}; +use std::time::Duration; + +pub static ANIME_CONFIG_PATH: &str = "/etc/asusd/anime.conf"; +pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf"; + +#[derive(Deserialize, Serialize, Default)] +pub struct AnimeConfigCached { + pub system: Option, + pub boot: Option, + pub suspend: Option, + pub shutdown: Option, +} + +impl AnimeConfigCached { + pub fn init_from_config(&mut self, config: &AnimeConfig) -> Result<(), AnimeError> { + if let Some(ref sys) = config.system { + self.system = Some(ActionData::from_anime_action(sys)?) + } + if let Some(ref boot) = config.boot { + self.boot = Some(ActionData::from_anime_action(boot)?) + } + if let Some(ref suspend) = config.boot { + self.suspend = Some(ActionData::from_anime_action(suspend)?) + } + if let Some(ref shutdown) = config.boot { + self.shutdown = Some(ActionData::from_anime_action(shutdown)?) + } + Ok(()) + } +} + +/// Config for base system actions for the anime display +#[derive(Deserialize, Serialize)] +pub struct AnimeConfig { + pub system: Option, + pub boot: Option, + pub suspend: Option, + pub shutdown: Option, +} + +impl Default for AnimeConfig { + fn default() -> Self { + AnimeConfig { + system: None, + boot: None, + suspend: None, + shutdown: None, + } + } +} + +impl AnimeConfig { + /// `load` will attempt to read the config, and panic if the dir is missing + pub fn load() -> Self { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&ANIME_CONFIG_PATH) + .unwrap_or_else(|_| { + panic!( + "The file {} or directory /etc/asusd/ is missing", + ANIME_CONFIG_PATH + ) + }); // okay to cause panic here + let mut buf = String::new(); + if let Ok(read_len) = file.read_to_string(&mut buf) { + if read_len == 0 { + return AnimeConfig::create_default(&mut file); + } else { + if let Ok(data) = serde_json::from_str(&buf) { + return data; + } + warn!("Could not deserialise {}", ANIME_CONFIG_PATH); + panic!("Please remove {} then restart asusd", ANIME_CONFIG_PATH); + } + } + AnimeConfig::create_default(&mut file) + } + + fn create_default(file: &mut File) -> Self { + // create a default config here + let config = AnimeConfig { + system: None, + boot: Some(AnimeAction::ImageAnimation { + file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(), + scale: 0.9, + angle: 0.65, + translation: Vec2::default(), + brightness: 0.5, + time: AnimTime::Time(Duration::from_secs(5)), + }), + suspend: None, + shutdown: None, + }; + // Should be okay to unwrap this as is since it is a Default + let json = serde_json::to_string(&config).unwrap(); + file.write_all(json.as_bytes()) + .unwrap_or_else(|_| panic!("Could not write {}", ANIME_CONFIG_PATH)); + config + } + + pub fn read(&mut self) { + let mut file = OpenOptions::new() + .read(true) + .open(&ANIME_CONFIG_PATH) + .unwrap_or_else(|err| panic!("Error reading {}: {}", ANIME_CONFIG_PATH, err)); + let mut buf = String::new(); + if let Ok(l) = file.read_to_string(&mut buf) { + if l == 0 { + warn!("File is empty {}", ANIME_CONFIG_PATH); + } else { + let x: AnimeConfig = serde_json::from_str(&buf) + .unwrap_or_else(|_| panic!("Could not deserialise {}", ANIME_CONFIG_PATH)); + *self = x; + } + } + } + + pub fn write(&self) { + let mut file = File::create(ANIME_CONFIG_PATH).expect("Couldn't overwrite config"); + let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed"); + file.write_all(json.as_bytes()) + .unwrap_or_else(|err| error!("Could not write config: {}", err)); + } +} diff --git a/daemon/src/config_aura.rs b/daemon/src/config_aura.rs index 15e5c55a..46920465 100644 --- a/daemon/src/config_aura.rs +++ b/daemon/src/config_aura.rs @@ -147,7 +147,6 @@ impl AuraConfig { } } - #[derive(Deserialize, Serialize)] pub struct AuraMultiZone { static_: [AuraEffect; 4], @@ -233,4 +232,4 @@ impl Default for AuraMultiZone { ], } } -} \ No newline at end of file +} diff --git a/daemon/src/config_old.rs b/daemon/src/config_old.rs index caa507a1..2b580b46 100644 --- a/daemon/src/config_old.rs +++ b/daemon/src/config_old.rs @@ -1,4 +1,3 @@ -use rog_aura::AuraEffect; use rog_types::{gfx_vendors::GfxVendors, profile::Profile}; use serde_derive::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -66,7 +65,6 @@ impl ConfigV324 { } } - #[derive(Deserialize, Serialize)] pub struct ConfigV341 { pub gfx_mode: GfxVendors, @@ -94,4 +92,4 @@ impl ConfigV341 { power_profiles: self.power_profiles, } } -} \ No newline at end of file +} diff --git a/daemon/src/ctrl_anime.rs b/daemon/src/ctrl_anime.rs index 7a326afc..eb965a99 100644 --- a/daemon/src/ctrl_anime.rs +++ b/daemon/src/ctrl_anime.rs @@ -4,66 +4,49 @@ use rog_anime::{ pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID, VENDOR_ID, }, - AnimeDataBuffer, AnimePacketType, + ActionData, AnimTime, AnimeDataBuffer, AnimePacketType, ANIME_DATA_LEN, }; use rog_types::supported::AnimeSupportedFunctions; use rusb::{Device, DeviceHandle}; -use std::error::Error; -use std::time::Duration; +use std::{ + error::Error, + sync::{Arc, Mutex}, + thread::sleep, + time::Instant, +}; +use std::{sync::atomic::AtomicBool, time::Duration}; use zbus::dbus_interface; use zvariant::ObjectPath; -use crate::GetSupported; +use crate::{ + config_anime::{AnimeConfig, AnimeConfigCached}, + error::RogError, + GetSupported, +}; -impl GetSupported for CtrlAnimeDisplay { +impl GetSupported for CtrlAnime { type A = AnimeSupportedFunctions; fn get_supported() -> Self::A { - AnimeSupportedFunctions(CtrlAnimeDisplay::get_device(VENDOR_ID, PROD_ID).is_ok()) + AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok()) } } -pub struct CtrlAnimeDisplay { +pub struct CtrlAnime { handle: DeviceHandle, + cache: AnimeConfigCached, + _config: AnimeConfig, + // set to force thread to exit + thread_exit: AtomicBool, + // Set to false when the thread exits + thread_running: AtomicBool, } -impl crate::ZbusAdd for CtrlAnimeDisplay { - fn add_to_server(self, server: &mut zbus::ObjectServer) { - server - .at( - &ObjectPath::from_str_unchecked("/org/asuslinux/Anime"), - self, - ) - .map_err(|err| { - warn!("CtrlAnimeDisplay: add_to_server {}", err); - err - }) - .ok(); - } -} - -#[dbus_interface(name = "org.asuslinux.Daemon")] -impl CtrlAnimeDisplay { - /// Writes a data stream of length - fn write(&self, input: AnimeDataBuffer) { - self.write_data_buffer(input); - } - - fn set_on_off(&self, status: bool) { - self.write_bytes(&pkt_for_set_on(status)); - } - - fn set_boot_on_off(&self, on: bool) { - self.write_bytes(&pkt_for_set_boot(on)); - self.write_bytes(&pkt_for_apply()); - } -} - -impl CtrlAnimeDisplay { +impl CtrlAnime { #[inline] - pub fn new() -> Result> { + pub fn new(config: AnimeConfig) -> Result> { // We don't expect this ID to ever change - let device = CtrlAnimeDisplay::get_device(0x0b05, 0x193b)?; + let device = CtrlAnime::get_device(0x0b05, 0x193b)?; let mut device = device.open()?; device.reset()?; @@ -79,7 +62,16 @@ impl CtrlAnimeDisplay { })?; info!("Device has an AniMe Matrix display"); - let ctrl = CtrlAnimeDisplay { handle: device }; + let mut cache = AnimeConfigCached::default(); + cache.init_from_config(&config)?; + + let ctrl = CtrlAnime { + handle: device, + cache, + _config: config, + thread_exit: AtomicBool::new(false), + thread_running: AtomicBool::new(false), + }; ctrl.do_initialization(); Ok(ctrl) @@ -95,6 +87,108 @@ impl CtrlAnimeDisplay { Err(rusb::Error::NoDevice) } + // DOUBLE THREAD NEST! + fn run_thread(inner: Arc>, action: Option, mut once: bool) { + std::thread::Builder::new() + .name("AniMe system thread start".into()) + .spawn(move || { + // Make the loop exit first + loop { + if let Ok(lock) = inner.try_lock() { + lock.thread_exit + .store(true, std::sync::atomic::Ordering::SeqCst); + break; + } + } + loop { + if let Ok(lock) = inner.try_lock() { + if !lock + .thread_running + .load(std::sync::atomic::Ordering::SeqCst) + { + lock.thread_exit + .store(false, std::sync::atomic::Ordering::SeqCst); + info!("AniMe system thread exited"); + break; + } + } + } + + std::thread::Builder::new() + .name("AniMe system actions".into()) + .spawn(move || { + info!("AniMe system thread started"); + 'main: loop { + if let Ok(lock) = inner.try_lock() { + if !once + && lock.thread_exit.load(std::sync::atomic::Ordering::SeqCst) + { + break 'main; + } + if let Some(ref action) = action { + match action { + ActionData::Animation(frames) => { + let mut count = 0; + let start = Instant::now(); + 'animation: loop { + for frame in frames.frames() { + lock.write_data_buffer(frame.frame().clone()); + if let AnimTime::Time(time) = frames.duration() + { + if Instant::now().duration_since(start) + > time + { + break 'animation; + } + } + sleep(frame.delay()); + } + if let AnimTime::Cycles(times) = frames.duration() { + count += 1; + if count >= times { + break 'animation; + } + } + } + } + ActionData::Image(image) => { + once = false; + lock.write_data_buffer(image.as_ref().clone()) + } + ActionData::Pause(_) => {} + ActionData::AudioEq => {} + ActionData::SystemInfo => {} + ActionData::TimeDate => {} + ActionData::Matrix => {} + } + } else { + break 'main; + } + if once { + let data = + AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec()); + lock.write_data_buffer(data); + break 'main; + } + } + } + 'exit: loop { + if let Ok(lock) = inner.try_lock() { + lock.thread_exit + .store(false, std::sync::atomic::Ordering::SeqCst); + lock.thread_running + .store(false, std::sync::atomic::Ordering::SeqCst); + break 'exit; + } + } + }) + .map(|err| info!("AniMe system thread: {:?}", err)) + .ok(); + }) + .map(|err| info!("AniMe system thread: {:?}", err)) + .ok(); + } + fn write_bytes(&self, message: &[u8]) { match self.handle.write_control( 0x21, // request_type @@ -126,3 +220,89 @@ impl CtrlAnimeDisplay { self.write_bytes(&pkts[1]); } } + +pub struct CtrlAnimeTask(pub Arc>); + +impl crate::CtrlTask for CtrlAnimeTask { + fn do_task(&self) -> Result<(), RogError> { + Ok(()) + } +} + +pub struct CtrlAnimeReloader(pub Arc>); + +impl crate::Reloadable for CtrlAnimeReloader { + fn reload(&mut self) -> Result<(), RogError> { + if let Ok(lock) = self.0.try_lock() { + let action = lock.cache.boot.clone(); + CtrlAnime::run_thread(self.0.clone(), action, true); + } + Ok(()) + } +} + +pub struct CtrlAnimeZbus(pub Arc>); + +/// The struct with the main dbus methods requires this trait +impl crate::ZbusAdd for CtrlAnimeZbus { + fn add_to_server(self, server: &mut zbus::ObjectServer) { + server + .at( + &ObjectPath::from_str_unchecked("/org/asuslinux/Anime"), + self, + ) + .map_err(|err| { + warn!("CtrlAnimeDisplay: add_to_server {}", err); + err + }) + .ok(); + } +} + +// None of these calls can be guarnateed to succeed unless we loop until okay +// If the try_lock *does* succeed then any other thread trying to lock will not grab it +// until we finish. +#[dbus_interface(name = "org.asuslinux.Daemon")] +impl CtrlAnimeZbus { + /// Writes a data stream of length. Will force system thread to exit until it is restarted + fn write(&self, input: AnimeDataBuffer) { + 'outer: loop { + if let Ok(lock) = self.0.try_lock() { + lock.thread_exit + .store(true, std::sync::atomic::Ordering::SeqCst); + lock.write_data_buffer(input); + break 'outer; + } + } + } + + fn set_on_off(&self, status: bool) { + 'outer: loop { + if let Ok(lock) = self.0.try_lock() { + lock.write_bytes(&pkt_for_set_on(status)); + break 'outer; + } + } + } + + fn set_boot_on_off(&self, on: bool) { + 'outer: loop { + if let Ok(lock) = self.0.try_lock() { + lock.write_bytes(&pkt_for_set_boot(on)); + lock.write_bytes(&pkt_for_apply()); + break 'outer; + } + } + } + + fn run_main_loop(&self, on: bool) { + 'outer: loop { + if let Ok(lock) = self.0.try_lock() { + lock.thread_exit + .store(on, std::sync::atomic::Ordering::SeqCst); + CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false); + break 'outer; + } + } + } +} diff --git a/daemon/src/ctrl_fan_cpu.rs b/daemon/src/ctrl_fan_cpu.rs index 54e1d157..946aabe2 100644 --- a/daemon/src/ctrl_fan_cpu.rs +++ b/daemon/src/ctrl_fan_cpu.rs @@ -1,7 +1,10 @@ use crate::error::RogError; use crate::{config::Config, GetSupported}; use log::{info, warn}; -use rog_types::{profile::{FanLevel, Profile, ProfileEvent}, supported::FanCpuSupportedFunctions}; +use rog_types::{ + profile::{FanLevel, Profile, ProfileEvent}, + supported::FanCpuSupportedFunctions, +}; use std::fs::OpenOptions; use std::io::Write; use std::path::Path; @@ -31,18 +34,18 @@ impl GetSupported for CtrlFanAndCpu { } } -pub struct DbusFanAndCpu { +pub struct FanAndCpuZbus { inner: Arc>, } -impl DbusFanAndCpu { +impl FanAndCpuZbus { pub fn new(inner: Arc>) -> Self { Self { inner } } } #[dbus_interface(name = "org.asuslinux.Daemon")] -impl DbusFanAndCpu { +impl FanAndCpuZbus { /// Set profile details fn set_profile(&self, profile: String) { if let Ok(event) = serde_json::from_str(&profile) { @@ -170,7 +173,7 @@ impl DbusFanAndCpu { fn notify_profile(&self, profile: &str) -> zbus::Result<()> {} } -impl crate::ZbusAdd for DbusFanAndCpu { +impl crate::ZbusAdd for FanAndCpuZbus { fn add_to_server(self, server: &mut zbus::ObjectServer) { server .at( diff --git a/daemon/src/ctrl_leds.rs b/daemon/src/ctrl_leds.rs index 16d42363..ee612213 100644 --- a/daemon/src/ctrl_leds.rs +++ b/daemon/src/ctrl_leds.rs @@ -7,7 +7,10 @@ use crate::{ laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES}, }; use log::{error, info, warn}; -use rog_aura::{AuraEffect, AuraModeNum, LED_MSG_LEN, LedBrightness, usb::{LED_APPLY, LED_SET}}; +use rog_aura::{ + usb::{LED_APPLY, LED_SET}, + AuraEffect, LedBrightness, LED_MSG_LEN, +}; use rog_types::supported::LedSupportedFunctions; use std::fs::OpenOptions; use std::io::{Read, Write}; @@ -50,6 +53,37 @@ pub struct CtrlKbdBacklight { config: AuraConfig, } +pub struct CtrlKbdBacklightTask(pub Arc>); + +impl crate::CtrlTask for CtrlKbdBacklightTask { + fn do_task(&self) -> Result<(), RogError> { + if let Ok(mut lock) = self.0.try_lock() { + let mut file = OpenOptions::new() + .read(true) + .open(&lock.bright_node) + .map_err(|err| match err.kind() { + std::io::ErrorKind::NotFound => { + RogError::MissingLedBrightNode((&lock.bright_node).into(), err) + } + _ => RogError::Path((&lock.bright_node).into(), err), + })?; + let mut buf = [0u8; 1]; + file.read_exact(&mut buf) + .map_err(|err| RogError::Read("buffer".into(), err))?; + if let Some(num) = char::from(buf[0]).to_digit(10) { + if lock.config.brightness != num.into() { + lock.config.read(); + lock.config.brightness = num.into(); + lock.config.write(); + } + return Ok(()); + } + return Err(RogError::ParseLed); + } + Ok(()) + } +} + pub struct DbusKbdBacklight { inner: Arc>, } @@ -166,73 +200,6 @@ impl DbusKbdBacklight { fn notify_led(&self, data: &str) -> zbus::Result<()>; } -impl crate::Reloadable for CtrlKbdBacklight { - fn reload(&mut self) -> Result<(), RogError> { - // set current mode (if any) - if self.supported_modes.standard.len() > 1 { - let current_mode = self.config.current_mode; - if self.supported_modes.standard.contains(&(current_mode)) { - let mode = self - .config - .builtins - .get(¤t_mode) - .ok_or(RogError::NotSupported)? - .to_owned(); - self.write_mode(&mode)?; - info!("Reloaded last used mode"); - } else { - warn!( - "An unsupported mode was set: {}, reset to first mode available", - <&str>::from(&self.config.current_mode) - ); - self.config.builtins.remove(¤t_mode); - self.config.current_mode = AuraModeNum::Static; - // TODO: do a recursive call with a boxed dyn future later - let mode = self - .config - .builtins - .get(¤t_mode) - .ok_or(RogError::NotSupported)? - .to_owned(); - self.write_mode(&mode)?; - info!("Reloaded last used mode"); - } - } - - // Reload brightness - let bright = self.config.brightness; - self.set_brightness(bright)?; - info!("Reloaded last used brightness"); - Ok(()) - } -} - -impl crate::CtrlTask for CtrlKbdBacklight { - fn do_task(&mut self) -> Result<(), RogError> { - let mut file = OpenOptions::new() - .read(true) - .open(&self.bright_node) - .map_err(|err| match err.kind() { - std::io::ErrorKind::NotFound => { - RogError::MissingLedBrightNode((&self.bright_node).into(), err) - } - _ => RogError::Path((&self.bright_node).into(), err), - })?; - let mut buf = [0u8; 1]; - file.read_exact(&mut buf) - .map_err(|err| RogError::Read("buffer".into(), err))?; - if let Some(num) = char::from(buf[0]).to_digit(10) { - if self.config.brightness != num.into() { - self.config.read(); - self.config.brightness = num.into(); - self.config.write(); - } - return Ok(()); - } - Err(RogError::ParseLed) - } -} - impl CtrlKbdBacklight { #[inline] pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result { diff --git a/daemon/src/ctrl_supported.rs b/daemon/src/ctrl_supported.rs index 230d727a..fe7967bc 100644 --- a/daemon/src/ctrl_supported.rs +++ b/daemon/src/ctrl_supported.rs @@ -4,7 +4,7 @@ use zbus::dbus_interface; use zvariant::ObjectPath; use crate::{ - ctrl_anime::CtrlAnimeDisplay, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCpu, + ctrl_anime::CtrlAnime, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCpu, ctrl_leds::CtrlKbdBacklight, ctrl_rog_bios::CtrlRogBios, GetSupported, }; @@ -50,7 +50,7 @@ impl GetSupported for SupportedFunctions { fn get_supported() -> Self::A { SupportedFunctions { keyboard_led: CtrlKbdBacklight::get_supported(), - anime_ctrl: CtrlAnimeDisplay::get_supported(), + anime_ctrl: CtrlAnime::get_supported(), charge_ctrl: CtrlCharge::get_supported(), fan_cpu_ctrl: CtrlFanAndCpu::get_supported(), rog_bios_ctrl: CtrlRogBios::get_supported(), diff --git a/daemon/src/daemon.rs b/daemon/src/daemon.rs index 1cdfa2eb..21684bfe 100644 --- a/daemon/src/daemon.rs +++ b/daemon/src/daemon.rs @@ -1,11 +1,11 @@ -use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight}; +use daemon::ctrl_leds::{CtrlKbdBacklight, CtrlKbdBacklightTask, DbusKbdBacklight}; use daemon::{ config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported, }; -use daemon::{config_aura::AuraConfig, ctrl_charge::CtrlCharge}; -use daemon::{ctrl_anime::CtrlAnimeDisplay, ctrl_gfx::gfx::CtrlGraphics}; +use daemon::{config_anime::AnimeConfig, config_aura::AuraConfig, ctrl_charge::CtrlCharge}; +use daemon::{ctrl_anime::*, ctrl_gfx::gfx::CtrlGraphics}; use daemon::{ - ctrl_fan_cpu::{CtrlFanAndCpu, DbusFanAndCpu}, + ctrl_fan_cpu::{CtrlFanAndCpu, FanAndCpuZbus}, laptops::LaptopLedData, }; @@ -41,27 +41,24 @@ pub fn main() -> Result<(), Box> { Ok(()) } -// Timing is such that: -// - interrupt write is minimum 1ms (sometimes lower) -// - read interrupt must timeout, minimum of 1ms -// - for a single usb packet, 2ms total. -// - to maintain constant times of 1ms, per-key colours should use -// the effect endpoint so that the complete colour block is written -// as fast as 1ms per row of the matrix inside it. (10ms total time) +/// The actual main loop for the daemon fn start_daemon() -> Result<(), Box> { let supported = SupportedFunctions::get_supported(); print_board_info(); println!("{}", serde_json::to_string_pretty(&supported).unwrap()); - let config = Config::load(); - let enable_gfx_switching = config.gfx_managed; - let config = Arc::new(Mutex::new(config)); - + // Collect tasks for task thread + let mut tasks: Vec> = Vec::new(); + // Start zbus server let connection = Connection::new_system()?; fdo::DBusProxy::new(&connection)? .request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?; let mut object_server = zbus::ObjectServer::new(&connection); + let config = Config::load(); + let enable_gfx_switching = config.gfx_managed; + let config = Arc::new(Mutex::new(config)); + supported.add_to_server(&mut object_server); match CtrlRogBios::new(config.clone()) { @@ -90,15 +87,52 @@ fn start_daemon() -> Result<(), Box> { } } - match CtrlAnimeDisplay::new() { + match CtrlFanAndCpu::new(config.clone()) { + Ok(mut ctrl) => { + ctrl.reload() + .unwrap_or_else(|err| warn!("Profile control: {}", err)); + let tmp = Arc::new(Mutex::new(ctrl)); + FanAndCpuZbus::new(tmp).add_to_server(&mut object_server); + } + Err(err) => { + error!("Profile control: {}", err); + } + } + + match CtrlAnime::new(AnimeConfig::load()) { Ok(ctrl) => { - ctrl.add_to_server(&mut object_server); + let inner = Arc::new(Mutex::new(ctrl)); + + let mut reload = CtrlAnimeReloader(inner.clone()); + reload + .reload() + .unwrap_or_else(|err| warn!("AniMe: {}", err)); + + let zbus = CtrlAnimeZbus(inner.clone()); + zbus.add_to_server(&mut object_server); + + tasks.push(Box::new(CtrlAnimeTask(inner))); } Err(err) => { error!("AniMe control: {}", err); } } + let laptop = LaptopLedData::get_data(); + let aura_config = AuraConfig::load(&laptop); + match CtrlKbdBacklight::new(laptop, aura_config) { + Ok(ctrl) => { + let tmp = Arc::new(Mutex::new(ctrl)); + DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server); + let task = CtrlKbdBacklightTask(tmp); + tasks.push(Box::new(task)); + } + Err(err) => { + error!("Keyboard control: {}", err); + } + } + + // Graphics switching requires some checks on boot specifically for g-sync capable laptops if enable_gfx_switching { match CtrlGraphics::new(config.clone()) { Ok(mut ctrl) => { @@ -141,48 +175,24 @@ fn start_daemon() -> Result<(), Box> { } } - // Collect tasks for task thread - let mut tasks: Vec>> = Vec::new(); - - if let Ok(mut ctrl) = CtrlFanAndCpu::new(config).map_err(|err| { - error!("Profile control: {}", err); - }) { - ctrl.reload() - .unwrap_or_else(|err| warn!("Profile control: {}", err)); - let tmp = Arc::new(Mutex::new(ctrl)); - DbusFanAndCpu::new(tmp).add_to_server(&mut object_server); - }; - - let laptop = LaptopLedData::get_data(); - let aura_config = AuraConfig::load(&laptop); - if let Ok(ctrl) = CtrlKbdBacklight::new(laptop, aura_config).map_err(|err| { - error!("Keyboard control: {}", err); - err - }) { - let tmp = Arc::new(Mutex::new(ctrl)); - DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server); - tasks.push(tmp); - } - // TODO: implement messaging between threads to check fails - // These tasks generally read a sys path or file to check for a - // change + + // Run tasks let handle = std::thread::Builder::new() .name("asusd watch".to_string()) .spawn(move || loop { std::thread::sleep(std::time::Duration::from_millis(100)); for ctrl in tasks.iter() { - if let Ok(mut lock) = ctrl.try_lock() { - lock.do_task() - .map_err(|err| { - warn!("do_task error: {}", err); - }) - .ok(); - } + ctrl.do_task() + .map_err(|err| { + warn!("do_task error: {}", err); + }) + .ok(); } }); + // Run zbus server object_server .with( &ObjectPath::from_str_unchecked("/org/asuslinux/Charge"), @@ -196,6 +206,7 @@ fn start_daemon() -> Result<(), Box> { }) .ok(); + // Loop to check errors and iterate zbus server loop { if let Err(err) = &handle { error!("{}", err); diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index 7f646a44..1e426df9 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -1,6 +1,7 @@ #![deny(unused_must_use)] /// Configuration loading, saving pub mod config; +pub mod config_anime; pub mod config_aura; pub(crate) mod config_old; /// Control of AniMe matrix display @@ -48,7 +49,7 @@ pub trait ZbusAdd { } pub trait CtrlTask { - fn do_task(&mut self) -> Result<(), RogError>; + fn do_task(&self) -> Result<(), RogError>; } pub trait CtrlTaskComplex { diff --git a/design-patterns.md b/design-patterns.md new file mode 100644 index 00000000..7f1e57ff --- /dev/null +++ b/design-patterns.md @@ -0,0 +1,89 @@ +# Daemon + +## Controller pattern + +There are a series of traits in the daemon for use with controller objects. Not all traits are required: + +- `Reloadable`, for controllers that need the ability to reload (typically on start) +- `ZbusAdd`, for controllers that have zbus derive. These need to run on the zbus server. +- `CtrlTask`, for controllers that need to run tasks every loop. +- `GetSupported`, see if the hardware/functions this controller requires are supported. + +The first 3 trait objects get owned by the daemon methods that required them, which is why an `Arc>` is required. + +Generally the actual controller object will need to live in its own world as its own struct. +Then for each trait that is required a new struct is required that can have the trait implemented, and that struct would have a reference to the main controller via `Arc>`. + +### Example + +Main controller: + +```rust +pub struct CtrlAnime { + +} + +impl CtrlAnime { + +} +``` + +The task trait: + +```rust +pub struct CtrlAnimeTask(Arc>); + +impl crate::CtrlTask for CtrlAnimeTask { + fn do_task(&self) -> Result<(), RogError> { + if let Ok(lock) = self.inner.try_lock() { + + } + Ok(()) + } +} +``` + +The reloader trait +```rust +pub struct CtrlAnimeReloader(Arc>); + +impl crate::Reloadable for CtrlAnimeReloader { + fn reload(&mut self) -> Result<(), RogError> { + if let Ok(lock) = self.inner.try_lock() { + + } + Ok(()) + } +} +``` + +The Zbus requirements: +```rust +pub struct CtrlAnimeZbus(Arc>); + +impl crate::ZbusAdd for CtrlAnimeZbus { + fn add_to_server(self, server: &mut zbus::ObjectServer) { + server + .at( + &ObjectPath::from_str_unchecked("/org/asuslinux/Anime"), + self, + ) + .map_err(|err| { + warn!("CtrlAnimeDisplay: add_to_server {}", err); + err + }) + .ok(); + } +} + +#[dbus_interface(name = "org.asuslinux.Daemon")] +impl CtrlAnimeZbus { + fn () { + if let Ok(lock) = self.inner.try_lock() { + + } + } +} +``` + +The controller can then be added to the daemon parts as required. diff --git a/rog-anime/src/error.rs b/rog-anime/src/error.rs index 6f684a64..f3e9ab8c 100644 --- a/rog-anime/src/error.rs +++ b/rog-anime/src/error.rs @@ -16,7 +16,7 @@ pub enum AnimeError { /// The input was incorrect size, expected size is `IncorrectSize(width, height)` IncorrectSize(u32, u32), #[cfg(feature = "dbus")] - Zbus(fdo::Error) + Zbus(fdo::Error), } impl fmt::Display for AnimeError { @@ -68,4 +68,4 @@ impl From for fdo::Error { fn from(err: AnimeError) -> Self { fdo::Error::Failed(format!("{}", err)) } -} \ No newline at end of file +} diff --git a/rog-anime/src/lib.rs b/rog-anime/src/lib.rs index c9305496..00e59733 100644 --- a/rog-anime/src/lib.rs +++ b/rog-anime/src/lib.rs @@ -30,4 +30,4 @@ pub use sequencer::*; pub mod error; /// Provides const methods to create the USB HID control packets -pub mod usb; \ No newline at end of file +pub mod usb; diff --git a/rog-anime/src/sequencer.rs b/rog-anime/src/sequencer.rs index 4666f10b..35974fde 100644 --- a/rog-anime/src/sequencer.rs +++ b/rog-anime/src/sequencer.rs @@ -6,7 +6,7 @@ use std::{ use glam::Vec2; use serde_derive::{Deserialize, Serialize}; -use crate::{error::AnimeError, AnimeDataBuffer, AnimeGif, AnimeImage, AnimTime}; +use crate::{error::AnimeError, AnimTime, AnimeDataBuffer, AnimeGif, AnimeImage}; /// All the possible AniMe actions that can be used. This enum is intended to be /// a helper for loading up `ActionData`. @@ -40,7 +40,7 @@ pub enum AnimeAction { /// All the possible AniMe actions that can be used. The enum is intended to be /// used in a array allowing the user to cycle through a series of actions. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum ActionData { /// Full gif sequence. Immutable. Animation(AnimeGif), @@ -58,6 +58,50 @@ pub enum ActionData { Matrix, } +impl ActionData { + pub fn from_anime_action(action: &AnimeAction) -> Result { + let a = match action { + AnimeAction::AsusAnimation { + file, + time: duration, + brightness, + } => ActionData::Animation(AnimeGif::create_diagonal_gif( + &file, + *duration, + *brightness, + )?), + AnimeAction::ImageAnimation { + file, + scale, + angle, + translation, + time: duration, + brightness, + } => ActionData::Animation(AnimeGif::create_png_gif( + &file, + *scale, + *angle, + *translation, + *duration, + *brightness, + )?), + AnimeAction::Image { + file, + scale, + angle, + translation, + brightness, + } => { + let image = AnimeImage::from_png(&file, *scale, *angle, *translation, *brightness)?; + let data = ::from(&image); + ActionData::Image(Box::new(data)) + } + AnimeAction::Pause(duration) => ActionData::Pause(*duration), + }; + Ok(a) + } +} + /// An optimised precomputed set of actions that the user can cycle through #[derive(Debug, Deserialize, Serialize, Default)] pub struct Sequences(Vec); diff --git a/rog-anime/src/usb.rs b/rog-anime/src/usb.rs index 4b0c1178..622b160a 100644 --- a/rog-anime/src/usb.rs +++ b/rog-anime/src/usb.rs @@ -24,7 +24,7 @@ pub const fn pkts_for_init() -> [[u8; PACKET_SIZE]; 2] { let mut count = 0; while count < INIT_STR.len() { packets[0][count] = INIT_STR[count]; - count +=1; + count += 1; } // packets[1][0] = DEV_PAGE; // write it to be sure? diff --git a/rog-aura/src/builtin_modes.rs b/rog-aura/src/builtin_modes.rs index 16cc4a4e..8ec0bce1 100644 --- a/rog-aura/src/builtin_modes.rs +++ b/rog-aura/src/builtin_modes.rs @@ -9,7 +9,7 @@ use std::str::FromStr; #[cfg(feature = "dbus")] use zvariant_derive::Type; -use crate::{LED_MSG_LEN, error::Error}; +use crate::{error::Error, LED_MSG_LEN}; #[cfg_attr(feature = "dbus", derive(Type))] #[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)] @@ -121,9 +121,7 @@ impl FromStr for Direction { /// Enum of modes that convert to the actual number required by a USB HID packet #[cfg_attr(feature = "dbus", derive(Type))] -#[derive( - Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Deserialize, Serialize, -)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Deserialize, Serialize)] pub enum AuraModeNum { Static = 0, Breathe = 1, diff --git a/rog-aura/src/error.rs b/rog-aura/src/error.rs index 44825cdc..7cb9f6a4 100644 --- a/rog-aura/src/error.rs +++ b/rog-aura/src/error.rs @@ -23,4 +23,4 @@ impl fmt::Display for Error { } } -impl error::Error for Error {} \ No newline at end of file +impl error::Error for Error {} diff --git a/rog-aura/src/lib.rs b/rog-aura/src/lib.rs index 3a5355bd..c570522f 100644 --- a/rog-aura/src/lib.rs +++ b/rog-aura/src/lib.rs @@ -13,4 +13,4 @@ pub mod usb; pub mod error; -pub const LED_MSG_LEN: usize = 17; \ No newline at end of file +pub const LED_MSG_LEN: usize = 17; diff --git a/rog-aura/src/per_key_rgb.rs b/rog-aura/src/per_key_rgb.rs index 5fa7d48b..4f8f19b1 100644 --- a/rog-aura/src/per_key_rgb.rs +++ b/rog-aura/src/per_key_rgb.rs @@ -33,7 +33,7 @@ impl KeyColourArray { KeyColourArray(set) } - /// Initialise and clear the keyboard for custom effects, this must be done for + /// Initialise and clear the keyboard for custom effects, this must be done for /// every time mode switches from builtin to custom #[inline] pub const fn get_init_msg() -> [u8; 64] { diff --git a/rog-aura/src/sequencer.rs b/rog-aura/src/sequencer.rs index ba41a2d0..767f1b3c 100644 --- a/rog-aura/src/sequencer.rs +++ b/rog-aura/src/sequencer.rs @@ -11,7 +11,7 @@ use crate::error::Error; /// used in a array allowing the user to cycle through a series of actions. #[derive(Debug, Deserialize, Serialize)] pub enum ActionData { - Static + Static, } /// An optimised precomputed set of actions that the user can cycle through diff --git a/rog-aura/src/usb.rs b/rog-aura/src/usb.rs index 8eb8131f..f948adb3 100644 --- a/rog-aura/src/usb.rs +++ b/rog-aura/src/usb.rs @@ -13,4 +13,4 @@ pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] { [ 0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ] -} \ No newline at end of file +} diff --git a/rog-dbus/src/lib.rs b/rog-dbus/src/lib.rs index abedf131..8c64a733 100644 --- a/rog-dbus/src/lib.rs +++ b/rog-dbus/src/lib.rs @@ -11,9 +11,7 @@ pub mod zbus_rogbios; pub mod zbus_supported; use rog_aura::AuraEffect; -use rog_types::{ - gfx_vendors::{GfxRequiredUserAction, GfxVendors}, -}; +use rog_types::gfx_vendors::{GfxRequiredUserAction, GfxVendors}; use std::sync::{Arc, Mutex}; use zbus::{Connection, Result, SignalReceiver}; diff --git a/rog-types/src/error.rs b/rog-types/src/error.rs index bbbff167..da861707 100644 --- a/rog-types/src/error.rs +++ b/rog-types/src/error.rs @@ -17,28 +17,4 @@ impl fmt::Display for GraphicsError { } } -impl Error for GraphicsError {} - -#[derive(Debug)] -pub enum AnimeError { - InvalidBitmap, - Io(std::io::Error), -} - -impl fmt::Display for AnimeError { - // This trait requires `fmt` with this exact signature. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - AnimeError::InvalidBitmap => write!(f, "Bitmap is invalid"), - AnimeError::Io(e) => write!(f, "Could not open: {}", e), - } - } -} - -impl Error for AnimeError {} - -impl From for AnimeError { - fn from(err: std::io::Error) -> Self { - AnimeError::Io(err) - } -} +impl Error for GraphicsError {} \ No newline at end of file