diff --git a/Cargo.lock b/Cargo.lock index 01aa9050..de527d4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,7 +667,7 @@ checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" [[package]] name = "rog-aura" -version = "0.8.0" +version = "0.9.0" dependencies = [ "dbus", "gumdrop", @@ -678,7 +678,7 @@ dependencies = [ [[package]] name = "rog-daemon" -version = "0.8.0" +version = "0.9.0" dependencies = [ "dbus", "dbus-tokio", diff --git a/README.md b/README.md index 7b08017b..ba408577 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ $ sudo systemctl start rog-core.service $ sudo systemctl enable rog-core.service ``` +## Updating + +Occasionally I might break things for you by tweaking or changing the config file layout. Usually this will mean you need to remove `/etc/rog-core.toml' and restart the daemon to create a new one. You *can* back up the old one and copy settings back over (then restart daemon again). + ## Use Running the program as a daemon manually will require root. Standard (non-daemon) mode expects to be communicating with the daemon mode over dbus. @@ -116,7 +120,9 @@ Bus 001 Device 005: ID 0b05:1866 ASUSTek Computer, Inc. N-KEY Device Then do `sudo lsusb -vd 0b05:1866 > ~/laptop_info` and give that to me. -Also required: +Other helpful info can be gained from `sudo usbhid-dump`, for which you may need to unload kernel drivers. Please google this. + +Also required (for my book-keeping of data): - `cat /sys/class/dmi/id/product_name` - `cat /sys/class/dmi/id/product_family` - `cat /sys/class/dmi/id/board_name` diff --git a/aura/src/cli_options.rs b/aura/src/cli_options.rs index 3aadbbf7..b6d94f95 100644 --- a/aura/src/cli_options.rs +++ b/aura/src/cli_options.rs @@ -3,6 +3,33 @@ use gumdrop::Options; use std::fmt::Debug; use std::str::FromStr; +#[derive(Debug, Options)] +pub struct LedBrightness { + level: u8, +} +impl LedBrightness { + pub fn level(&self) -> u8 { + self.level + } +} +impl FromStr for LedBrightness { + type Err = AuraError; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "off" => Ok(LedBrightness { level: 0x00 }), + "low" => Ok(LedBrightness { level: 0x01 }), + "med" => Ok(LedBrightness { level: 0x02 }), + "high" => Ok(LedBrightness { level: 0x03 }), + _ => { + println!("Missing required argument, must be one of:\noff,low,med,high\n"); + Err(AuraError::ParseBrightness) + } + } + } +} + #[derive(Debug)] pub struct Colour(pub u8, pub u8, pub u8); impl Default for Colour { diff --git a/rog-core/src/config.rs b/rog-core/src/config.rs index c3100fcf..8a19aaf6 100644 --- a/rog-core/src/config.rs +++ b/rog-core/src/config.rs @@ -11,10 +11,13 @@ pub struct Config { pub brightness: u8, pub current_mode: [u8; 4], pub builtin_modes: BuiltInModeBytes, + pub mode_performance: FanModeSettings, } impl Config { - pub fn read(mut self) -> Self { + /// `load` will attempt to read the config, but if it is not found it + /// will create a new default config and write that out. + pub fn load(mut self) -> Self { let mut file = OpenOptions::new() .read(true) .write(true) @@ -33,12 +36,30 @@ impl Config { .expect("Writing default config failed"); self = c; } else { - self = toml::from_str(&buf).unwrap(); + self = + toml::from_str(&buf).expect(&format!("Could not deserialise {}", CONFIG_PATH)); } } self } + pub fn read(&mut self) { + let mut file = OpenOptions::new() + .read(true) + .open(&CONFIG_PATH) + .expect("config file error"); + let mut buf = String::new(); + if let Ok(l) = file.read_to_string(&mut buf) { + if l == 0 { + panic!("Missing {}", CONFIG_PATH); + } else { + let x: Config = + toml::from_str(&buf).expect(&format!("Could not deserialise {}", CONFIG_PATH)); + *self = x; + } + } + } + pub fn write(&self) { let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config"); let toml = toml::to_string(self).expect("Parse config to JSON failed"); @@ -55,3 +76,27 @@ impl Config { } } } + +#[derive(Default, Deserialize, Serialize)] +pub struct FanModeSettings { + pub normal: IntelPState, + pub boost: IntelPState, + pub silent: IntelPState, +} + +#[derive(Deserialize, Serialize)] +pub struct IntelPState { + pub min_percentage: u8, + pub max_percentage: u8, + pub no_turbo: bool, +} + +impl Default for IntelPState { + fn default() -> Self { + IntelPState { + min_percentage: 0, + max_percentage: 100, + no_turbo: false, + } + } +} diff --git a/rog-core/src/core.rs b/rog-core/src/core.rs index 18516346..d2bf327c 100644 --- a/rog-core/src/core.rs +++ b/rog-core/src/core.rs @@ -37,18 +37,14 @@ static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode /// - `LED_INIT4` /// - `LED_INIT2` /// - `LED_INIT4` -pub(crate) struct RogCore { +pub struct RogCore { handle: DeviceHandle, virt_keys: VirtKeys, _pin: PhantomPinned, } impl RogCore { - pub(crate) fn new( - vendor: u16, - product: u16, - led_endpoint: u8, - ) -> Result> { + pub fn new(vendor: u16, product: u16, led_endpoint: u8) -> Result> { let mut dev_handle = RogCore::get_device(vendor, product)?; dev_handle.set_active_configuration(0).unwrap_or(()); @@ -81,16 +77,7 @@ impl RogCore { }) } - pub(crate) async fn reload(&mut self, config: &mut Config) -> Result<(), Box> { - // let mode_curr = self.config.current_mode[3]; - // let mode = self - // .config - // .builtin_modes - // .get_field_from(BuiltInModeByte::from(mode_curr).into()) - // .unwrap() - // .to_owned(); - // self.aura_write_messages(&[&mode])?; - + pub async fn reload(&mut self, config: &mut Config) -> Result<(), Box> { let path = if Path::new(FAN_TYPE_1_PATH).exists() { FAN_TYPE_1_PATH } else if Path::new(FAN_TYPE_2_PATH).exists() { @@ -101,12 +88,12 @@ impl RogCore { let mut file = OpenOptions::new().write(true).open(path)?; file.write_all(format!("{:?}\n", config.fan_mode).as_bytes())?; - self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode))?; + self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?; info!("Reloaded last saved settings"); Ok(()) } - pub(crate) fn virt_keys(&mut self) -> &mut VirtKeys { + pub fn virt_keys(&mut self) -> &mut VirtKeys { &mut self.virt_keys } @@ -123,7 +110,7 @@ impl RogCore { Err(rusb::Error::NoDevice) } - pub(crate) fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box> { + pub fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box> { let path = if Path::new(FAN_TYPE_1_PATH).exists() { FAN_TYPE_1_PATH } else if Path::new(FAN_TYPE_2_PATH).exists() { @@ -146,34 +133,55 @@ impl RogCore { } info!("Fan mode stepped to: {:#?}", FanLevel::from(n)); fan_ctrl.write_all(format!("{:?}\n", n).as_bytes())?; - self.set_pstate_for_fan_mode(FanLevel::from(n))?; + self.set_pstate_for_fan_mode(FanLevel::from(n), config)?; config.fan_mode = n; config.write(); } Ok(()) } - fn set_pstate_for_fan_mode(&self, mode: FanLevel) -> Result<(), Box> { + fn set_pstate_for_fan_mode( + &self, + mode: FanLevel, + config: &mut Config, + ) -> Result<(), Box> { // Set CPU pstate if let Ok(pstate) = intel_pstate::PState::new() { + // re-read the config here in case a user changed the pstate settings + config.read(); match mode { FanLevel::Normal => { - pstate.set_min_perf_pct(0)?; - pstate.set_max_perf_pct(100)?; - pstate.set_no_turbo(false)?; - info!("CPU pstate: normal"); + pstate.set_min_perf_pct(config.mode_performance.normal.min_percentage)?; + pstate.set_max_perf_pct(config.mode_performance.normal.max_percentage)?; + pstate.set_no_turbo(config.mode_performance.normal.no_turbo)?; + info!( + "CPU Power: min-freq: {:?}, max-freq: {:?}, turbo: {:?}", + config.mode_performance.normal.min_percentage, + config.mode_performance.normal.max_percentage, + !config.mode_performance.normal.no_turbo + ); } FanLevel::Boost => { - pstate.set_min_perf_pct(50)?; - pstate.set_max_perf_pct(100)?; - pstate.set_no_turbo(false)?; - info!("CPU pstate: boost"); + pstate.set_min_perf_pct(config.mode_performance.boost.min_percentage)?; + pstate.set_max_perf_pct(config.mode_performance.boost.max_percentage)?; + pstate.set_no_turbo(config.mode_performance.boost.no_turbo)?; + info!( + "CPU Power: min-freq: {:?}, max-freq: {:?}, turbo: {:?}", + config.mode_performance.boost.min_percentage, + config.mode_performance.boost.max_percentage, + !config.mode_performance.boost.no_turbo + ); } FanLevel::Silent => { - pstate.set_min_perf_pct(0)?; - pstate.set_max_perf_pct(70)?; - pstate.set_no_turbo(true)?; - info!("CPU pstate: silent, no-turbo"); + pstate.set_min_perf_pct(config.mode_performance.silent.min_percentage)?; + pstate.set_max_perf_pct(config.mode_performance.silent.max_percentage)?; + pstate.set_no_turbo(config.mode_performance.silent.no_turbo)?; + info!( + "CPU Power: min-freq: {:?}, max-freq: {:?}, turbo: {:?}", + config.mode_performance.silent.min_percentage, + config.mode_performance.silent.max_percentage, + !config.mode_performance.silent.no_turbo + ); } } } @@ -184,7 +192,7 @@ impl RogCore { /// /// This avoids desktop environments being required to handle it /// (which means it works while in a TTY also) - pub(crate) fn suspend_with_systemd(&self) { + pub fn suspend_with_systemd(&self) { std::process::Command::new("systemctl") .arg("suspend") .spawn() @@ -195,7 +203,7 @@ impl RogCore { /// /// This avoids desktop environments being required to handle it (which /// means it works while in a TTY also) - pub(crate) fn toggle_airplane_mode(&self) { + pub fn toggle_airplane_mode(&self) { match Command::new("rfkill").arg("list").output() { Ok(output) => { if output.status.success() { @@ -230,7 +238,7 @@ impl RogCore { } } - pub(crate) fn get_raw_device_handle(&mut self) -> NonNull> { + pub fn get_raw_device_handle(&mut self) -> NonNull> { // Breaking every damn lifetime guarantee rust gives us unsafe { NonNull::new_unchecked(&mut self.handle as *mut DeviceHandle) @@ -239,7 +247,7 @@ impl RogCore { } /// Lifetime is tied to `DeviceHandle` from `RogCore` -pub(crate) struct KeyboardReader<'d, C: 'd> +pub struct KeyboardReader<'d, C: 'd> where C: rusb::UsbContext, { @@ -271,7 +279,7 @@ where /// /// `report_filter_bytes` is used to filter the data read from the interupt so /// only the relevant byte array is returned. - pub(crate) async fn poll_keyboard(&self) -> Option<[u8; 32]> { + pub async fn poll_keyboard(&self) -> Option<[u8; 32]> { let mut buf = [0u8; 32]; match unsafe { self.handle.as_ref() }.read_interrupt( self.endpoint, @@ -299,7 +307,7 @@ where /// Because we're holding a pointer to something that *may* go out of scope while the /// pointer is held. We're relying on access to struct to be behind a Mutex, and for behaviour /// that may cause invalididated pointer to cause the program to panic rather than continue. -pub(crate) struct LedWriter<'d, C: 'd> +pub struct LedWriter<'d, C: 'd> where C: rusb::UsbContext, { @@ -326,7 +334,8 @@ where } } - async fn aura_write(&mut self, message: &[u8]) -> Result<(), AuraError> { + /// Should only be used if the bytes you are writing are verified correct + pub async fn aura_write(&mut self, message: &[u8]) -> Result<(), AuraError> { match unsafe { self.handle.as_ref() }.write_interrupt( self.led_endpoint, message, @@ -384,7 +393,8 @@ where Ok(()) } - pub(crate) async fn aura_set_and_save( + /// Used to set a builtin mode and save the settings for it + pub async fn aura_set_and_save( &mut self, supported_modes: &[BuiltInModeByte], bytes: &[u8], @@ -405,7 +415,7 @@ where Err(AuraError::NotSupported) } - pub(crate) async fn aura_bright_inc( + pub async fn aura_bright_inc( &mut self, supported_modes: &[BuiltInModeByte], max_bright: u8, @@ -423,7 +433,7 @@ where Ok(()) } - pub(crate) async fn aura_bright_dec( + pub async fn aura_bright_dec( &mut self, supported_modes: &[BuiltInModeByte], min_bright: u8, @@ -444,7 +454,7 @@ where /// Select next Aura effect /// /// If the current effect is the last one then the effect selected wraps around to the first. - pub(crate) async fn aura_mode_next( + pub async fn aura_mode_next( &mut self, supported_modes: &[BuiltInModeByte], config: &mut Config, @@ -471,7 +481,7 @@ where /// Select previous Aura effect /// /// If the current effect is the first one then the effect selected wraps around to the last. - pub(crate) async fn aura_mode_prev( + pub async fn aura_mode_prev( &mut self, supported_modes: &[BuiltInModeByte], config: &mut Config, @@ -496,33 +506,6 @@ where } } -#[derive(Debug, Options)] -pub struct LedBrightness { - level: u8, -} -impl LedBrightness { - pub fn level(&self) -> u8 { - self.level - } -} -impl FromStr for LedBrightness { - type Err = AuraError; - - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - match s.as_str() { - "off" => Ok(LedBrightness { level: 0x00 }), - "low" => Ok(LedBrightness { level: 0x01 }), - "med" => Ok(LedBrightness { level: 0x02 }), - "high" => Ok(LedBrightness { level: 0x03 }), - _ => { - println!("Missing required argument, must be one of:\noff,low,med,high\n"); - Err(AuraError::ParseBrightness) - } - } - } -} - #[derive(Debug)] enum FanLevel { Normal, diff --git a/rog-core/src/daemon.rs b/rog-core/src/daemon.rs index a79f961d..1c0dcea5 100644 --- a/rog-core/src/daemon.rs +++ b/rog-core/src/daemon.rs @@ -5,7 +5,7 @@ use dbus::{ }; use dbus_tokio::connection; use log::{error, info, warn}; -use rog_aura::{DBUS_IFACE, DBUS_PATH}; +use rog_aura::{BuiltInModeByte, DBUS_IFACE, DBUS_PATH}; use std::error::Error; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -25,7 +25,8 @@ type EffectType = Arc>>>>; // DBUS processing takes 6ms if not tokiod pub async fn start_daemon() -> Result<(), Box> { let laptop = match_laptop(); - let mut config = Config::default().read(); + let mut config = Config::default().load(); + info!("Config loaded"); let mut rogcore = RogCore::new( laptop.usb_vendor(), @@ -42,14 +43,22 @@ pub async fn start_daemon() -> Result<(), Box> { daemon }, ); + // Reload settings rogcore.reload(&mut config).await?; + let mut led_writer = LedWriter::new(rogcore.get_raw_device_handle(), laptop.led_endpoint()); + { + let mode_curr = config.current_mode[3]; + let mode = config + .builtin_modes + .get_field_from(BuiltInModeByte::from(mode_curr).into()) + .unwrap() + .to_owned(); + led_writer.aura_write(&mode).await?; + } // Set up the mutexes - let led_writer = Arc::new(Mutex::new(LedWriter::new( - rogcore.get_raw_device_handle(), - laptop.led_endpoint(), - ))); + let led_writer = Arc::new(Mutex::new(led_writer)); let config = Arc::new(Mutex::new(config)); let (resource, connection) = connection::new_system_sync()?; tokio::spawn(async { diff --git a/rog-core/src/laptops/mod.rs b/rog-core/src/laptops.rs similarity index 99% rename from rog-core/src/laptops/mod.rs rename to rog-core/src/laptops.rs index acf901b2..73ce72c0 100644 --- a/rog-core/src/laptops/mod.rs +++ b/rog-core/src/laptops.rs @@ -272,7 +272,7 @@ impl LaptopBase { } } -pub(crate) enum GX502Keys { +pub enum GX502Keys { Rog = 0x38, MicToggle = 0x7C, Fan = 0xAE, diff --git a/rog-core/src/lib.rs b/rog-core/src/lib.rs index ee59efc0..cf48cebc 100644 --- a/rog-core/src/lib.rs +++ b/rog-core/src/lib.rs @@ -1,7 +1,11 @@ +/// Configuration loading, saving mod config; /// The core module which allows writing to LEDs or polling the /// laptop keyboard attached devices -pub mod core; +mod core; +/// Start the daemon loop pub mod daemon; -pub mod laptops; +/// Laptop matching to determine capabilities +mod laptops; +/// A virtual "consumer device" to help emit the correct key codes mod virt_device; diff --git a/rog-core/src/main.rs b/rog-core/src/main.rs index 18c5e886..cd19d8fe 100644 --- a/rog-core/src/main.rs +++ b/rog-core/src/main.rs @@ -1,8 +1,11 @@ -use daemon::{core::LedBrightness, daemon::start_daemon}; +use daemon::daemon::start_daemon; use env_logger::{Builder, Target}; use gumdrop::Options; use log::LevelFilter; -use rog_aura::{cli_options::SetAuraBuiltin, AuraDbusWriter, LED_MSG_LEN}; +use rog_aura::{ + cli_options::{LedBrightness, SetAuraBuiltin}, + AuraDbusWriter, LED_MSG_LEN, +}; static VERSION: &'static str = "0.8.0"; diff --git a/rog-core/src/virt_device.rs b/rog-core/src/virt_device.rs index 78d24f3f..ec8bb5e4 100644 --- a/rog-core/src/virt_device.rs +++ b/rog-core/src/virt_device.rs @@ -24,12 +24,12 @@ use uhid_virt::{Bus, CreateParams, UHIDDevice}; /// `rogcore.virt_keys().press([0x01, 0, 0, 0x82, 0, 0, 0, 0]); // Sleep` /// /// `rogcore.virt_keys().press([0x01, 0, 0, 0x66, 0, 0, 0, 0]); // Power (menu)` -pub(crate) struct VirtKeys { +pub struct VirtKeys { device: UHIDDevice, } impl VirtKeys { - pub(crate) fn new() -> Self { + pub fn new() -> Self { VirtKeys { device: UHIDDevice::create(CreateParams { name: String::from("Virtual ROG buttons"), @@ -96,7 +96,7 @@ impl VirtKeys { } /// A single on/off key press - pub(crate) fn press(&mut self, input: [u8; 32]) { + pub fn press(&mut self, input: [u8; 32]) { self.device.write(&input).unwrap(); let mut reset = [0u8; 32]; reset[0] = input[0]; @@ -106,7 +106,7 @@ impl VirtKeys { #[allow(dead_code)] #[derive(Copy, Clone)] -pub(crate) enum ConsumerKeys { +pub enum ConsumerKeys { Power = 0x30, Sleep = 0x32, Menu = 0x0040,