diff --git a/rog-core/src/animatrix_control.rs b/rog-core/src/ctrl_anime.rs similarity index 100% rename from rog-core/src/animatrix_control.rs rename to rog-core/src/ctrl_anime.rs diff --git a/rog-core/src/ctrl_charge.rs b/rog-core/src/ctrl_charge.rs new file mode 100644 index 00000000..33af93c7 --- /dev/null +++ b/rog-core/src/ctrl_charge.rs @@ -0,0 +1,69 @@ +use crate::config::Config; +use log::{error, info, warn}; +use std::error::Error; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::Path; + +static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold"; + +pub struct CtrlCharge { + path: &'static str, +} + +impl CtrlCharge { + pub(super) fn new() -> Result> { + let path = CtrlCharge::get_battery_path()?; + + Ok(CtrlCharge { path }) + } + + fn get_battery_path() -> Result<&'static str, std::io::Error> { + if Path::new(BAT_CHARGE_PATH).exists() { + Ok(BAT_CHARGE_PATH) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Charge control not available", + )) + } + } + + 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 set_charge_limit( + &self, + limit: u8, + config: &mut Config, + ) -> Result<(), Box> { + if limit < 20 || limit > 100 { + warn!( + "Unable to set battery charge limit, must be between 20-100: requested {}", + limit + ); + } + + let mut file = OpenOptions::new() + .write(true) + .open(self.path) + .map_err(|err| { + warn!("Failed to open battery charge limit path: {:?}", err); + err + })?; + file.write_all(limit.to_string().as_bytes()) + .unwrap_or_else(|err| error!("Could not write to {}, {:?}", BAT_CHARGE_PATH, err)); + info!("Battery charge limit: {}", limit); + + config.bat_charge_limit = limit; + config.write(); + + Ok(()) + } +} diff --git a/rog-core/src/rogcore.rs b/rog-core/src/ctrl_fan_cpu.rs similarity index 59% rename from rog-core/src/rogcore.rs rename to rog-core/src/ctrl_fan_cpu.rs index e7207b31..906a898a 100644 --- a/rog-core/src/rogcore.rs +++ b/rog-core/src/ctrl_fan_cpu.rs @@ -1,33 +1,24 @@ -// Return show-stopping errors, otherwise map error to a log level - -use crate::{config::Config, error::RogError}; +use crate::config::Config; use log::{error, info, warn}; use std::error::Error; use std::fs::OpenOptions; -use std::io::Write; +use std::io::{Read, Write}; use std::path::Path; -use std::process::Command; use std::str::FromStr; static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy"; static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode"; static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost"; -static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold"; -/// ROG device controller -/// -/// For the GX502GW the LED setup sequence looks like: -/// -/// -` LED_INIT1` -/// - `LED_INIT3` -/// - `LED_INIT4` -/// - `LED_INIT2` -/// - `LED_INIT4` -pub struct RogCore {} +pub struct CtrlFanAndCPU { + path: &'static str, +} -impl RogCore { - pub fn new(vendor: u16, product: u16) -> Self { - RogCore {} +impl CtrlFanAndCPU { + pub(super) fn new() -> Result> { + let path = CtrlFanAndCPU::get_fan_path()?; + + Ok(CtrlFanAndCPU { path }) } fn get_fan_path() -> Result<&'static str, std::io::Error> { @@ -43,44 +34,58 @@ impl RogCore { } } - pub fn fan_mode_reload(&mut self, config: &mut Config) -> Result<(), Box> { - let path = RogCore::get_fan_path()?; - let mut file = OpenOptions::new().write(true).open(path)?; + 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 {}, {:?}", path, err)); + .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 fn set_fan_mode(&mut self, n: u8, config: &mut Config) -> Result<(), Box> { - let path = RogCore::get_fan_path()?; - let mut fan_ctrl = OpenOptions::new().read(true).write(true).open(path)?; + pub(super) fn fan_mode_check_change( + &mut self, + config: &mut Config, + ) -> Result<(), Box> { + let mut file = OpenOptions::new().read(true).open(self.path)?; + let mut buf = [0u8; 1]; + file.read_exact(&mut buf)?; + if let Some(num) = char::from(buf[0]).to_digit(10) { + if config.fan_mode != num as u8 { + config.fan_mode = num as u8; + config.write(); + self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?; + info!( + "Fan mode was changed: {:?}", + FanLevel::from(config.fan_mode) + ); + } + return Ok(()); + } + let err = std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Fan-level could not be parsed", + ); + Err(Box::new(err)) + } + + pub(super) fn set_fan_mode( + &mut self, + n: u8, + config: &mut Config, + ) -> Result<(), Box> { + let mut fan_ctrl = OpenOptions::new().write(true).open(self.path)?; config.fan_mode = n; config.write(); fan_ctrl .write_all(format!("{:?}\n", config.fan_mode).as_bytes()) - .unwrap_or_else(|err| error!("Could not write to {}, {:?}", path, err)); + .unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err)); info!("Fan mode set to: {:?}", FanLevel::from(config.fan_mode)); self.set_pstate_for_fan_mode(FanLevel::from(n), config)?; Ok(()) } - pub fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box> { - // re-read the config here in case a user changed the pstate settings - config.read(); - - let mut n = config.fan_mode; - // wrap around the step number - if n < 2 { - n += 1; - } else { - n = 0; - } - self.set_fan_mode(n, config) - } - fn set_pstate_for_fan_mode( &self, mode: FanLevel, @@ -171,89 +176,10 @@ impl RogCore { } Ok(()) } - - pub 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 fn set_charge_limit(&self, limit: u8, config: &mut Config) -> Result<(), Box> { - if limit < 20 || limit > 100 { - warn!( - "Unable to set battery charge limit, must be between 20-100: requested {}", - limit - ); - } - - let mut file = OpenOptions::new() - .write(true) - .open(BAT_CHARGE_PATH) - .map_err(|err| { - warn!("Failed to open battery charge limit path: {:?}", err); - err - })?; - file.write_all(limit.to_string().as_bytes()) - .unwrap_or_else(|err| error!("Could not write to {}, {:?}", BAT_CHARGE_PATH, err)); - info!("Battery charge limit: {}", limit); - - config.bat_charge_limit = limit; - config.write(); - - Ok(()) - } - - /// A direct call to systemd to suspend the PC. - /// - /// This avoids desktop environments being required to handle it - /// (which means it works while in a TTY also) - pub fn suspend_with_systemd(&self) { - std::process::Command::new("systemctl") - .arg("suspend") - .spawn() - .map_or_else(|err| warn!("Failed to suspend: {}", err), |_| {}); - } - - /// A direct call to rfkill to suspend wireless devices. - /// - /// This avoids desktop environments being required to handle it (which - /// means it works while in a TTY also) - pub fn toggle_airplane_mode(&self) { - match Command::new("rfkill").arg("list").output() { - Ok(output) => { - if output.status.success() { - if let Ok(out) = String::from_utf8(output.stdout) { - if out.contains(": yes") { - Command::new("rfkill") - .arg("unblock") - .arg("all") - .spawn() - .map_or_else( - |err| warn!("Could not unblock rf devices: {}", err), - |_| {}, - ); - } else { - Command::new("rfkill") - .arg("block") - .arg("all") - .spawn() - .map_or_else( - |err| warn!("Could not block rf devices: {}", err), - |_| {}, - ); - } - } - } else { - warn!("Could not list rf devices"); - } - } - Err(err) => { - warn!("Could not list rf devices: {}", err); - } - } - } } +use crate::error::RogError; + #[derive(Debug)] pub enum FanLevel { Normal, diff --git a/rog-core/src/led_control.rs b/rog-core/src/ctrl_leds.rs similarity index 95% rename from rog-core/src/led_control.rs rename to rog-core/src/ctrl_leds.rs index 981d21e9..0dbc8a0a 100644 --- a/rog-core/src/led_control.rs +++ b/rog-core/src/ctrl_leds.rs @@ -3,7 +3,7 @@ static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; use crate::{config::Config, error::RogError}; -use log::{error, info, warn}; +use log::{info, warn}; use rog_client::{ aura_brightness_bytes, aura_modes::AuraModes, fancy::KeyColourArray, LED_MSG_LEN, }; @@ -18,17 +18,18 @@ pub struct LedWriter { impl LedWriter { #[inline] - pub fn new(idProduct: &str, supported_modes: Vec) -> Result { + 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() == idProduct + 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: {:?}", dev_node); + info!("Using device at: {:?} for LED control", dev_node); return Ok(LedWriter { dev_node: dev_node.to_string_lossy().to_string(), supported_modes, diff --git a/rog-core/src/daemon.rs b/rog-core/src/daemon.rs index ce00bdf0..968a28b0 100644 --- a/rog-core/src/daemon.rs +++ b/rog-core/src/daemon.rs @@ -1,10 +1,11 @@ use crate::{ - animatrix_control::{AniMeWriter, AnimatrixCommand}, config::Config, + ctrl_anime::{AniMeWriter, AnimatrixCommand}, + ctrl_charge::CtrlCharge, + ctrl_fan_cpu::CtrlFanAndCPU, + ctrl_leds::LedWriter, + dbus::dbus_create_tree, laptops::match_laptop, - led_control::LedWriter, - rog_dbus::dbus_create_tree, - rogcore::*, }; use dbus::{channel::Sender, nonblock::Process, nonblock::SyncConnection, tree::Signal}; @@ -34,34 +35,58 @@ pub async fn start_daemon() -> Result<(), Box> { info!("Config loaded"); - let mut rogcore = RogCore::new(laptop.usb_vendor(), laptop.usb_product()); + let mut led_control = LedWriter::new(laptop.usb_product(), laptop.supported_modes().to_owned()) + .map_or_else( + |err| { + error!("{}", err); + None + }, + |ledwriter| { + info!("LED Writer loaded"); + Some(ledwriter) + }, + ); - // Reload settings - rogcore - .fan_mode_reload(&mut config) - .unwrap_or_else(|err| warn!("Fan mode: {}", err)); - rogcore - .bat_charge_limit_reload(&mut config) - .unwrap_or_else(|err| warn!("Battery charge limit: {}", err)); - - let mut led_writer = LedWriter::new( - "1866", - laptop.supported_modes().to_owned(), - ).map_or_else( + let mut charge_control = CtrlCharge::new().map_or_else( |err| { error!("{}", err); None }, |ledwriter| { - info!("LED Writer loaded"); + info!("Charge control loaded"); Some(ledwriter) }, ); - if let Some(writer) = led_writer.as_mut() { - writer.reload_last_builtin(&mut config) - .await - .unwrap_or_else(|err| warn!("Reload settings: {}", err)); + let mut fan_control = CtrlFanAndCPU::new().map_or_else( + |err| { + error!("{}", err); + None + }, + |ledwriter| { + info!("Fan & CPU control loaded"); + Some(ledwriter) + }, + ); + + // Reload settings + if let Some(ctrlr) = fan_control.as_mut() { + ctrlr + .fan_mode_reload(&mut config) + .unwrap_or_else(|err| warn!("Fan mode: {}", err)); + } + + if let Some(ctrlr) = charge_control.as_mut() { + ctrlr + .bat_charge_limit_reload(&mut config) + .unwrap_or_else(|err| warn!("Battery charge limit: {}", err)); + } + + if let Some(writer) = led_control.as_mut() { + writer + .reload_last_builtin(&mut config) + .await + .unwrap_or_else(|err| warn!("Reload settings: {}", err)); } // Set up the mutexes @@ -133,24 +158,34 @@ pub async fn start_daemon() -> Result<(), Box> { loop { // TODO: MAKE SYS COMMANDS OPERATE USING CHANNEL LIKE AURA MODES // Fan mode - if let Ok(mut lock) = fan_mode.try_lock() { - if let Some(n) = lock.take() { - let mut config = config1.lock().await; - rogcore - .set_fan_mode(n, &mut config) - .unwrap_or_else(|err| warn!("{:?}", err)); + if let Some(ctrlr) = fan_control.as_mut() { + let mut config = config1.lock().await; + ctrlr + .fan_mode_check_change(&mut config) + .unwrap_or_else(|err| warn!("{:?}", err)); + + if let Ok(mut lock) = fan_mode.try_lock() { + if let Some(n) = lock.take() { + let mut config = config1.lock().await; + ctrlr + .set_fan_mode(n, &mut config) + .unwrap_or_else(|err| warn!("{:?}", err)); + } } } + // Charge limit - if let Ok(mut lock) = charge_limit.try_lock() { - if let Some(n) = lock.take() { - let mut config = config1.lock().await; - rogcore - .set_charge_limit(n, &mut config) - .unwrap_or_else(|err| warn!("{:?}", err)); + if let Some(ctrlr) = charge_control.as_mut() { + if let Ok(mut lock) = charge_limit.try_lock() { + if let Some(n) = lock.take() { + let mut config = config1.lock().await; + ctrlr + .set_charge_limit(n, &mut config) + .unwrap_or_else(|err| warn!("{:?}", err)); + } } } - std::thread::sleep(std::time::Duration::from_millis(500)); + tokio::time::delay_for(std::time::Duration::from_millis(500)).await; } }); @@ -159,30 +194,31 @@ pub async fn start_daemon() -> Result<(), Box> { connection.process_all(); while let Some(command) = aura_command_recv.recv().await { - if let Some(writer) = led_writer.as_mut() { - let mut config = config.lock().await; - match &command { - AuraModes::RGB(_) => { - writer - .do_command(command, &mut config) - .await - .unwrap_or_else(|err| warn!("{}", err)); + if let Some(writer) = led_control.as_mut() { + let mut config = config.lock().await; + match &command { + AuraModes::RGB(_) => { + writer + .do_command(command, &mut config) + .await + .unwrap_or_else(|err| warn!("{}", err)); + } + _ => { + let json = serde_json::to_string(&command)?; + writer + .do_command(command, &mut config) + .await + .unwrap_or_else(|err| warn!("{}", err)); + connection + .send( + led_changed_signal + .msg(&DBUS_PATH.into(), &DBUS_IFACE.into()) + .append1(json), + ) + .unwrap_or_else(|_| 0); + } } - _ => { - let json = serde_json::to_string(&command)?; - writer - .do_command(command, &mut config) - .await - .unwrap_or_else(|err| warn!("{}", err)); - connection - .send( - led_changed_signal - .msg(&DBUS_PATH.into(), &DBUS_IFACE.into()) - .append1(json), - ) - .unwrap_or_else(|_| 0); - } - }} + } } } } diff --git a/rog-core/src/rog_dbus.rs b/rog-core/src/dbus.rs similarity index 100% rename from rog-core/src/rog_dbus.rs rename to rog-core/src/dbus.rs diff --git a/rog-core/src/laptops.rs b/rog-core/src/laptops.rs index 4353d8a8..38901cc5 100644 --- a/rog-core/src/laptops.rs +++ b/rog-core/src/laptops.rs @@ -11,14 +11,12 @@ pub(crate) fn match_laptop() -> LaptopBase { let device_desc = device.device_descriptor().unwrap(); if device_desc.vendor_id() == 0x0b05 { match device_desc.product_id() { - 0x1869 | 0x1866 => return select_1866_device(device_desc.product_id()), + 0x1866 => return select_1866_device("1866".to_owned()), + 0x1869 => return select_1866_device("1869".to_owned()), 0x1854 => { info!("Found GL753 or similar"); return LaptopBase { - usb_vendor: 0x0B05, - usb_product: 0x1854, - //from `lsusb -vd 0b05:1866` - led_endpoint: 0x04, + usb_product: "1854".to_string(), supported_modes: vec![SINGLE, BREATHING, STROBE], support_animatrix: false, }; @@ -30,7 +28,7 @@ pub(crate) fn match_laptop() -> LaptopBase { panic!("could not match laptop"); } -fn select_1866_device(prod: u16) -> LaptopBase { +fn select_1866_device(prod: String) -> LaptopBase { let dmi = sysfs_class::DmiId::default(); let board_name = dmi.board_name().expect("Could not get board_name"); let prod_name = dmi.product_name().expect("Could not get board_name"); @@ -39,10 +37,7 @@ fn select_1866_device(prod: u16) -> LaptopBase { info!("Board name: {}", board_name.trim()); let mut laptop = LaptopBase { - usb_vendor: 0x0B05, usb_product: prod, - //from `lsusb -vd 0b05:1866` - led_endpoint: 0x04, supported_modes: vec![], support_animatrix: false, }; @@ -117,22 +112,14 @@ fn select_1866_device(prod: u16) -> LaptopBase { } pub(super) struct LaptopBase { - usb_vendor: u16, - usb_product: u16, - led_endpoint: u8, + usb_product: String, supported_modes: Vec, support_animatrix: bool, } impl LaptopBase { - pub(super) fn led_endpoint(&self) -> u8 { - self.led_endpoint - } - pub(super) fn usb_vendor(&self) -> u16 { - self.usb_vendor - } - pub(super) fn usb_product(&self) -> u16 { - self.usb_product + pub(super) fn usb_product(&self) -> &str { + &self.usb_product } pub(super) fn supported_modes(&self) -> &[u8] { &self.supported_modes diff --git a/rog-core/src/lib.rs b/rog-core/src/lib.rs index 2de8528e..52ed2f6e 100644 --- a/rog-core/src/lib.rs +++ b/rog-core/src/lib.rs @@ -1,18 +1,19 @@ #![deny(unused_must_use)] -/// -mod animatrix_control; /// Configuration loading, saving mod config; +/// +mod ctrl_anime; +/// +mod ctrl_charge; +/// +pub mod ctrl_fan_cpu; +/// +mod ctrl_leds; /// Start the daemon loop pub mod daemon; +/// +mod dbus; /// Laptop matching to determine capabilities mod laptops; -/// -mod led_control; -/// -mod rog_dbus; -/// The core module which allows writing to LEDs or polling the -/// laptop keyboard attached devices -pub mod rogcore; mod error; diff --git a/rog-core/src/main.rs b/rog-core/src/main.rs index bf015422..4e95fe97 100644 --- a/rog-core/src/main.rs +++ b/rog-core/src/main.rs @@ -1,5 +1,5 @@ +use daemon::ctrl_fan_cpu::FanLevel; use daemon::daemon::start_daemon; -use daemon::rogcore::FanLevel; use gumdrop::Options; use log::info; use log::LevelFilter;