diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 43fbea1d..8359b9a0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ build: - make && make vendor artifacts: paths: - - vendor-$(grep -P 'version = "(\d.\d.\d)"' asus-nb-ctrl/Cargo.toml | cut -d'"' -f2).tar.xz + - vendor_asus-nb-ctrl_*.tar.xz - cargo-config variables: diff --git a/Cargo.lock b/Cargo.lock index 11ed6d77..25eff99a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,7 @@ version = "0.15.0" dependencies = [ "dbus", "gumdrop", + "rog_fan_curve", "serde", "serde_derive", "serde_json", @@ -40,6 +41,7 @@ dependencies = [ "gumdrop", "intel-pstate", "log", + "rog_fan_curve", "rusb", "serde", "serde_derive", @@ -646,6 +648,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" +[[package]] +name = "rog_fan_curve" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38efab84f3f5a9f4ff26eb916b32810a263eb2d340d62acb8d64d8a37795c5b9" +dependencies = [ + "serde", +] + [[package]] name = "rusb" version = "0.6.0" diff --git a/Makefile b/Makefile index c1c12fa6..69880081 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ SRC = Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.r BIN_C=asusctl BIN_D=asusd LEDCONFIG=asusd-ledmodes.toml -VERSION:=$(shell grep -P 'version = "(\d.\d.\d)"' asus-nb-ctrl/Cargo.toml | cut -d'"' -f2) +VERSION:=$(shell grep -Pm1 'version = "(\d.\d.\d)"' asus-nb-ctrl/Cargo.toml | cut -d'"' -f2) DEBUG ?= 0 ifeq ($(DEBUG),0) @@ -59,12 +59,12 @@ vendor: echo 'directory = "vendor"' >> .cargo/config mv .cargo/config ./cargo-config rm -rf .cargo - tar pcfJ vendor-$(VERSION).tar.xz vendor + tar pcfJ vendor_asus-nb-ctrl_$(VERSION).tar.xz vendor rm -rf vendor target/release/$(BIN_D): $(SRC) ifeq ($(VENDORED),1) @echo "version = $(VERSION)" - tar pxf vendor-$(VERSION).tar.xz + tar pxf vendor_asus-nb-ctrl_$(VERSION).tar.xz endif cargo build $(ARGS) diff --git a/README.md b/README.md index ca88f2a3..2fbd82a2 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,25 @@ Optional arguments: Available commands: led-mode Set the keyboard lighting from built-in modes + profile Create and configure profiles + +$ asusctl profile --help +Usage: asusctl profile [OPTIONS] + +Positional arguments: + profile + +Optional arguments: + -h, --help print help message + -c, --create create the profile if it doesn't exist + -t, --turbo enable cpu turbo (AMD) + -n, --no-turbo disable cpu turbo (AMD) + -m, --min-percentage MIN-PERCENTAGE + set min cpu scaling (intel) + -M, --max-percentage MAX-PERCENTAGE + set max cpu scaling (intel) + -p, --preset PWR + -C, --curve CURVE set fan curve $ asusctl led-mode --help Usage: asusctl led-mode [OPTIONS] diff --git a/asus-nb-ctrl/Cargo.toml b/asus-nb-ctrl/Cargo.toml index cf0cf80f..8eb93730 100644 --- a/asus-nb-ctrl/Cargo.toml +++ b/asus-nb-ctrl/Cargo.toml @@ -45,5 +45,6 @@ toml = "0.4.6" # Device control sysfs-class = "^0.1.2" # used for backlight control and baord ID +rog_fan_curve = { version = "0.1.4", features = ["serde"] } # cpu power management intel-pstate = "^0.2.1" diff --git a/asus-nb-ctrl/src/config.rs b/asus-nb-ctrl/src/config.rs index 3b385893..f38c301c 100644 --- a/asus-nb-ctrl/src/config.rs +++ b/asus-nb-ctrl/src/config.rs @@ -1,6 +1,8 @@ use asus_nb::aura_modes::AuraModes; use log::{error, warn}; +use rog_fan_curve::Curve; use serde_derive::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; @@ -8,12 +10,15 @@ pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf"; #[derive(Default, Deserialize, Serialize)] pub struct Config { + pub active_profile: String, + pub toggle_profiles: Vec, + // TODO: remove power_profile pub power_profile: u8, pub bat_charge_limit: u8, pub kbd_led_brightness: u8, pub kbd_backlight_mode: u8, pub kbd_backlight_modes: Vec, - pub power_profiles: FanModeProfile, + pub power_profiles: BTreeMap, } impl Config { @@ -51,6 +56,20 @@ impl Config { c.kbd_backlight_modes.push(AuraModes::from(*n)) } + let profile = Profile::default(); + c.power_profiles.insert("normal".into(), profile); + let mut profile = Profile::default(); + profile.fan_preset = 1; + c.power_profiles.insert("boost".into(), profile); + let mut profile = Profile::default(); + profile.fan_preset = 2; + c.power_profiles.insert("silent".into(), profile); + + c.toggle_profiles.push("normal".into()); + c.toggle_profiles.push("boost".into()); + c.toggle_profiles.push("silent".into()); + c.active_profile = "normal".into(); + // Should be okay to unwrap this as is since it is a Default let json = serde_json::to_string_pretty(&c).unwrap(); file.write_all(json.as_bytes()) @@ -103,26 +122,26 @@ impl Config { } } -#[derive(Default, Deserialize, Serialize)] -pub struct FanModeProfile { - pub normal: CPUSettings, - pub boost: CPUSettings, - pub silent: CPUSettings, -} - #[derive(Deserialize, Serialize)] -pub struct CPUSettings { +pub struct Profile { pub min_percentage: u8, pub max_percentage: u8, pub no_turbo: bool, + pub fan_preset: u8, + pub fan_curve: Option, } -impl Default for CPUSettings { +#[deprecated] +pub type CPUSettings = Profile; + +impl Default for Profile { fn default() -> Self { - CPUSettings { + Profile { min_percentage: 0, max_percentage: 100, no_turbo: false, + fan_preset: 0, + fan_curve: None, } } } diff --git a/asus-nb-ctrl/src/ctrl_fan_cpu.rs b/asus-nb-ctrl/src/ctrl_fan_cpu.rs index 8f08012e..8988c6e5 100644 --- a/asus-nb-ctrl/src/ctrl_fan_cpu.rs +++ b/asus-nb-ctrl/src/ctrl_fan_cpu.rs @@ -1,4 +1,6 @@ use crate::config::Config; +use crate::config::Profile; +use asus_nb::profile::ProfileEvent; use log::{error, info, warn}; use std::error::Error; use std::fs::OpenOptions; @@ -23,7 +25,7 @@ use async_trait::async_trait; #[async_trait] impl crate::Controller for CtrlFanAndCPU { - type A = u8; + type A = ProfileEvent; /// Spawns two tasks which continuously check for changes fn spawn_task_loop( @@ -39,10 +41,12 @@ impl crate::Controller for CtrlFanAndCPU { // spawn an endless loop vec![ tokio::spawn(async move { - while let Some(mode) = recv.recv().await { + while let Some(event) = recv.recv().await { let mut config = config1.lock().await; let mut lock = gate1.lock().await; - lock.set_fan_mode(mode, &mut config) + + config.read(); + lock.handle_profile_event(&event, &mut config) .unwrap_or_else(|err| warn!("{:?}", err)); } }), @@ -63,7 +67,8 @@ impl crate::Controller for CtrlFanAndCPU { let mut file = OpenOptions::new().write(true).open(self.path)?; file.write_all(format!("{:?}\n", config.power_profile).as_bytes()) .unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err)); - self.set_pstate_for_fan_mode(FanLevel::from(config.power_profile), config)?; + let profile = config.active_profile.clone(); + self.set_profile(&profile, config)?; info!( "Reloaded fan mode: {:?}", FanLevel::from(config.power_profile) @@ -102,13 +107,26 @@ impl CtrlFanAndCPU { if let Some(num) = char::from(buf[0]).to_digit(10) { if config.power_profile != num as u8 { config.read(); - config.power_profile = num as u8; - config.write(); - self.set_pstate_for_fan_mode(FanLevel::from(config.power_profile), config)?; - info!( - "Fan mode was changed: {:?}", - FanLevel::from(config.power_profile) - ); + + let mut i = config + .toggle_profiles + .iter() + .position(|x| x == &config.active_profile) + .map(|i| i + 1) + .unwrap_or(0); + if i >= config.toggle_profiles.len() { + i = 0; + } + + let new_profile = config + .toggle_profiles + .get(i) + .unwrap_or(&config.active_profile) + .clone(); + + self.set_profile(&new_profile, config)?; + + info!("Profile was changed: {:?}", &new_profile); } return Ok(()); } @@ -121,66 +139,121 @@ impl CtrlFanAndCPU { pub(super) fn set_fan_mode( &mut self, - n: u8, + preset: u8, config: &mut Config, ) -> Result<(), Box> { + let mode = config.active_profile.clone(); let mut fan_ctrl = OpenOptions::new().write(true).open(self.path)?; config.read(); - config.power_profile = n; + let mut mode_config = config + .power_profiles + .get_mut(&mode) + .ok_or_else(|| RogError::MissingProfile(mode.clone()))?; + config.power_profile = preset; + mode_config.fan_preset = preset; config.write(); fan_ctrl - .write_all(format!("{:?}\n", config.power_profile).as_bytes()) + .write_all(format!("{:?}\n", preset).as_bytes()) .unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err)); - info!( - "Fan mode set to: {:?}", - FanLevel::from(config.power_profile) - ); - self.set_pstate_for_fan_mode(FanLevel::from(n), config)?; + info!("Fan mode set to: {:?}", FanLevel::from(preset)); + self.set_pstate_for_fan_mode(&mode, config)?; + self.set_fan_curve_for_fan_mode(&mode, config)?; + Ok(()) + } + + fn handle_profile_event( + &mut self, + event: &ProfileEvent, + config: &mut Config, + ) -> Result<(), Box> { + match event { + ProfileEvent::ChangeMode(mode) => { + self.set_fan_mode(*mode, config)?; + } + ProfileEvent::Cli(command) => { + let profile_key = match command.profile.as_ref() { + Some(k) => k.clone(), + None => config.active_profile.clone(), + }; + + let mut profile = if command.create { + config + .power_profiles + .entry(profile_key.clone()) + .or_insert_with(|| Profile::default()) + } else { + config + .power_profiles + .get_mut(&profile_key) + .ok_or_else(|| RogError::MissingProfile(profile_key.clone()))? + }; + + if command.turbo { + profile.no_turbo = false; + } + if command.no_turbo { + profile.no_turbo = true; + } + if let Some(min_perc) = command.min_percentage { + profile.min_percentage = min_perc; + } + if let Some(max_perc) = command.max_percentage { + profile.max_percentage = max_perc; + } + if let Some(ref preset) = command.preset { + profile.fan_preset = preset.into(); + } + if let Some(ref curve) = command.curve { + profile.fan_curve = Some(curve.clone()); + } + + self.set_profile(&profile_key, config)?; + } + } + Ok(()) + } + + fn set_profile(&mut self, profile: &str, config: &mut Config) -> Result<(), Box> { + let mode_config = config + .power_profiles + .get(profile) + .ok_or_else(|| RogError::MissingProfile(profile.into()))?; + let mut fan_ctrl = OpenOptions::new().write(true).open(self.path)?; + fan_ctrl + .write_all(format!("{:?}\n", mode_config.fan_preset).as_bytes()) + .unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err)); + config.power_profile = mode_config.fan_preset; + + self.set_pstate_for_fan_mode(profile, config)?; + self.set_fan_curve_for_fan_mode(profile, config)?; + + config.active_profile = profile.into(); + + config.write(); Ok(()) } fn set_pstate_for_fan_mode( &self, - mode: FanLevel, + // mode: FanLevel, + mode: &str, config: &mut Config, ) -> Result<(), Box> { + info!("Setting pstate"); + let mode_config = config + .power_profiles + .get(mode) + .ok_or_else(|| RogError::MissingProfile(mode.into()))?; + // Set CPU pstate if let Ok(pstate) = intel_pstate::PState::new() { - match mode { - FanLevel::Normal => { - pstate.set_min_perf_pct(config.power_profiles.normal.min_percentage)?; - pstate.set_max_perf_pct(config.power_profiles.normal.max_percentage)?; - pstate.set_no_turbo(config.power_profiles.normal.no_turbo)?; - info!( - "Intel CPU Power: min: {:?}%, max: {:?}%, turbo: {:?}", - config.power_profiles.normal.min_percentage, - config.power_profiles.normal.max_percentage, - !config.power_profiles.normal.no_turbo - ); - } - FanLevel::Boost => { - pstate.set_min_perf_pct(config.power_profiles.boost.min_percentage)?; - pstate.set_max_perf_pct(config.power_profiles.boost.max_percentage)?; - pstate.set_no_turbo(config.power_profiles.boost.no_turbo)?; - info!( - "Intel CPU Power: min: {:?}%, max: {:?}%, turbo: {:?}", - config.power_profiles.boost.min_percentage, - config.power_profiles.boost.max_percentage, - !config.power_profiles.boost.no_turbo - ); - } - FanLevel::Silent => { - pstate.set_min_perf_pct(config.power_profiles.silent.min_percentage)?; - pstate.set_max_perf_pct(config.power_profiles.silent.max_percentage)?; - pstate.set_no_turbo(config.power_profiles.silent.no_turbo)?; - info!( - "Intel CPU Power: min: {:?}%, max: {:?}%, turbo: {:?}", - config.power_profiles.silent.min_percentage, - config.power_profiles.silent.max_percentage, - !config.power_profiles.silent.no_turbo - ); - } - } + pstate.set_min_perf_pct(mode_config.min_percentage)?; + pstate.set_max_perf_pct(mode_config.max_percentage)?; + pstate.set_no_turbo(mode_config.no_turbo)?; + info!( + "Intel CPU Power: min: {:?}%, max: {:?}%, turbo: {:?}", + mode_config.min_percentage, mode_config.max_percentage, !mode_config.no_turbo + ); } else { info!("Setting pstate for AMD CPU"); // must be AMD CPU @@ -191,42 +264,36 @@ impl CtrlFanAndCPU { warn!("Failed to open AMD boost: {:?}", err); err })?; - match mode { - FanLevel::Normal => { - let boost = if config.power_profiles.normal.no_turbo { - "0" - } else { - "1" - }; // opposite of Intel - file.write_all(boost.as_bytes()).unwrap_or_else(|err| { - error!("Could not write to {}, {:?}", AMD_BOOST_PATH, err) - }); - info!("AMD CPU Turbo: {:?}", boost); - } - FanLevel::Boost => { - let boost = if config.power_profiles.boost.no_turbo { - "0" - } else { - "1" - }; - file.write_all(boost.as_bytes()).unwrap_or_else(|err| { - error!("Could not write to {}, {:?}", AMD_BOOST_PATH, err) - }); - info!("AMD CPU Turbo: {:?}", boost); - } - FanLevel::Silent => { - let boost = if config.power_profiles.silent.no_turbo { - "0" - } else { - "1" - }; - file.write_all(boost.as_bytes()).unwrap_or_else(|err| { - error!("Could not write to {}, {:?}", AMD_BOOST_PATH, err) - }); - info!("AMD CPU Turbo: {:?}", boost); - } + + let boost = if mode_config.no_turbo { "0" } else { "1" }; // opposite of Intel + file.write_all(boost.as_bytes()) + .unwrap_or_else(|err| error!("Could not write to {}, {:?}", AMD_BOOST_PATH, err)); + info!("AMD CPU Turbo: {:?}", boost); + } + Ok(()) + } + + fn set_fan_curve_for_fan_mode( + &self, + // mode: FanLevel, + mode: &str, + config: &Config, + ) -> Result<(), Box> { + let mode_config = &config + .power_profiles + .get(mode) + .ok_or_else(|| RogError::MissingProfile(mode.into()))?; + + if let Some(ref curve) = mode_config.fan_curve { + use rog_fan_curve::{Board, Fan}; + if let Some(board) = Board::from_board_name() { + curve.apply(board, Fan::Cpu)?; + curve.apply(board, Fan::Gpu)?; + } else { + warn!("Fan curve unsupported on this board.") } } + Ok(()) } } diff --git a/asus-nb-ctrl/src/ctrl_leds.rs b/asus-nb-ctrl/src/ctrl_leds.rs index cd288048..ea182234 100644 --- a/asus-nb-ctrl/src/ctrl_leds.rs +++ b/asus-nb-ctrl/src/ctrl_leds.rs @@ -19,6 +19,7 @@ use tokio::task::JoinHandle; pub struct CtrlKbdBacklight { led_node: String, + #[allow(dead_code)] kbd_node: String, bright_node: String, supported_modes: Vec, @@ -141,7 +142,10 @@ impl CtrlKbdBacklight { }) } - fn get_node_failover(id_product: &str, fun: fn(&str) -> Result) -> Result { + fn get_node_failover( + id_product: &str, + fun: fn(&str) -> Result, + ) -> Result { for n in 0..2 { match fun(id_product) { Ok(o) => return Ok(o), @@ -156,10 +160,7 @@ impl CtrlKbdBacklight { } } // Shouldn't be possible to reach this... - let err = std::io::Error::new( - std::io::ErrorKind::NotFound, - "node not found", - ); + let err = std::io::Error::new(std::io::ErrorKind::NotFound, "node not found"); Err(err) } diff --git a/asus-nb-ctrl/src/daemon.rs b/asus-nb-ctrl/src/daemon.rs index ce636c06..60b4da52 100644 --- a/asus-nb-ctrl/src/daemon.rs +++ b/asus-nb-ctrl/src/daemon.rs @@ -114,8 +114,9 @@ pub async fn start_daemon() -> Result<(), Box> { tree, aura_command_recv, animatrix_recv, - fan_mode_recv, + _fan_mode_recv, charge_limit_recv, + profile_recv, led_changed_signal, fanmode_signal, charge_limit_signal, @@ -149,7 +150,7 @@ pub async fn start_daemon() -> Result<(), Box> { } if let Some(ctrl) = fan_control.take() { - handles.append(&mut ctrl.spawn_task_loop(config.clone(), fan_mode_recv, None, None)); + handles.append(&mut ctrl.spawn_task_loop(config.clone(), profile_recv, None, None)); } if let Some(ctrl) = charge_control.take() { diff --git a/asus-nb-ctrl/src/dbus.rs b/asus-nb-ctrl/src/dbus.rs index f891ea4e..98f54972 100644 --- a/asus-nb-ctrl/src/dbus.rs +++ b/asus-nb-ctrl/src/dbus.rs @@ -1,4 +1,5 @@ use crate::config::Config; +use asus_nb::profile::ProfileEvent; use asus_nb::{aura_modes::AuraModes, DBUS_IFACE, DBUS_PATH}; use dbus::tree::{Factory, MTSync, Method, MethodErr, Signal, Tree}; use log::warn; @@ -170,6 +171,25 @@ fn set_charge_limit(sender: Mutex>) -> Method { .annotate("org.freedesktop.DBus.Method.NoReply", "true") } +fn set_profile(sender: Sender) -> Method { + let factory = Factory::new_sync::<()>(); + factory + // method for profile + .method("ProfileCommand", (), { + move |m| { + let mut iter = m.msg.iter_init(); + let byte: String = iter.read()?; + if let Ok(byte) = serde_json::from_str(&byte) { + sender.clone().try_send(byte).unwrap_or_else(|_err| {}); + } + + Ok(vec![]) + } + }) + .inarg::("limit") + .annotate("org.freedesktop.DBus.Method.NoReply", "true") +} + #[allow(clippy::type_complexity)] pub fn dbus_create_tree( config: Arc>, @@ -179,6 +199,7 @@ pub fn dbus_create_tree( Receiver>>, Receiver, Receiver, + Receiver, Arc>, Arc>, Arc>, @@ -186,6 +207,7 @@ pub fn dbus_create_tree( let (aura_command_send, aura_command_recv) = channel::(1); let (animatrix_send, animatrix_recv) = channel::>>(1); let (fan_mode_send, fan_mode_recv) = channel::(1); + let (profile_send, profile_recv) = channel::(1); let (charge_send, charge_recv) = channel::(1); let factory = Factory::new_sync::<()>(); @@ -211,6 +233,7 @@ pub fn dbus_create_tree( .add_m(set_keyboard_backlight(Mutex::new(aura_command_send))) .add_m(set_animatrix(Mutex::new(animatrix_send))) .add_m(set_fan_mode(Mutex::new(fan_mode_send))) + .add_m(set_profile(profile_send)) .add_m(set_charge_limit(Mutex::new(charge_send))) .add_m(get_fan_mode(config.clone())) .add_m(get_charge_limit(config.clone())) @@ -228,6 +251,7 @@ pub fn dbus_create_tree( animatrix_recv, fan_mode_recv, charge_recv, + profile_recv, key_backlight_changed, fanmode_changed, chrg_limit_changed, diff --git a/asus-nb-ctrl/src/error.rs b/asus-nb-ctrl/src/error.rs index f6272608..9644c826 100644 --- a/asus-nb-ctrl/src/error.rs +++ b/asus-nb-ctrl/src/error.rs @@ -3,6 +3,7 @@ use std::fmt; #[derive(Debug)] pub enum RogError { ParseFanLevel, + MissingProfile(String), NotSupported, } @@ -13,6 +14,7 @@ impl fmt::Display for RogError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RogError::ParseFanLevel => write!(f, "Parse error"), + RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile), RogError::NotSupported => write!(f, "Not supported"), } } diff --git a/asus-nb-ctrl/src/main.rs b/asus-nb-ctrl/src/main.rs index ab67832a..b2dd0d03 100644 --- a/asus-nb-ctrl/src/main.rs +++ b/asus-nb-ctrl/src/main.rs @@ -1,6 +1,7 @@ use asus_nb::{ cli_options::{LedBrightness, SetAuraBuiltin}, core_dbus::AuraDbusClient, + profile::{ProfileCommand, ProfileEvent}, }; use daemon::ctrl_fan_cpu::FanLevel; use gumdrop::Options; @@ -27,6 +28,8 @@ struct CLIStart { enum Command { #[options(help = "Set the keyboard lighting from built-in modes")] LedMode(LedModeCommand), + #[options(help = "Create and configure profiles")] + Profile(ProfileCommand), } #[derive(Options)] @@ -54,11 +57,18 @@ pub async fn main() -> Result<(), Box> { let writer = AuraDbusClient::new()?; - if let Some(Command::LedMode(mode)) = parsed.command { - if let Some(command) = mode.command { - writer.write_builtin_mode(&command.into())? + match parsed.command { + Some(Command::LedMode(mode)) => { + if let Some(command) = mode.command { + writer.write_builtin_mode(&command.into())? + } } + Some(Command::Profile(command)) => { + writer.write_profile_command(&ProfileEvent::Cli(command))? + } + None => (), } + if let Some(brightness) = parsed.kbd_bright { writer.write_brightness(brightness.level())?; } diff --git a/asus-nb/Cargo.toml b/asus-nb/Cargo.toml index 8bfa020b..9502a943 100644 --- a/asus-nb/Cargo.toml +++ b/asus-nb/Cargo.toml @@ -16,6 +16,7 @@ serde = "^1.0" serde_derive = "^1.0" serde_json = "^1.0" yansi-term = "^0.1" +rog_fan_curve = { version = "0.1", features = ["serde"] } [dev-dependencies] tinybmp = "^0.2.3" \ No newline at end of file diff --git a/asus-nb/src/core_dbus.rs b/asus-nb/src/core_dbus.rs index e3edf6fb..0c819454 100644 --- a/asus-nb/src/core_dbus.rs +++ b/asus-nb/src/core_dbus.rs @@ -1,5 +1,6 @@ use super::*; use crate::fancy::KeyColourArray; +use crate::profile::ProfileEvent; use dbus::channel::Sender; use dbus::{blocking::Connection, channel::Token, Message}; use std::error::Error; @@ -95,8 +96,20 @@ impl AuraDbusClient { #[inline] pub fn write_fan_mode(&self, level: u8) -> Result<(), Box> { - let mut msg = Message::new_method_call(DBUS_NAME, DBUS_PATH, DBUS_IFACE, "SetFanMode")? - .append1(level); + let mut msg = Message::new_method_call(DBUS_NAME, DBUS_PATH, DBUS_IFACE, "ProfileCommand")? + .append1(serde_json::to_string(&ProfileEvent::ChangeMode(level))?); + msg.set_no_reply(true); + self.connection.send(msg).unwrap(); + Ok(()) + } + + #[inline] + pub fn write_profile_command( + &self, + cmd: &ProfileEvent, + ) -> Result<(), Box> { + let mut msg = Message::new_method_call(DBUS_NAME, DBUS_PATH, DBUS_IFACE, "ProfileCommand")? + .append1(serde_json::to_string(cmd)?); msg.set_no_reply(true); self.connection.send(msg).unwrap(); Ok(()) diff --git a/asus-nb/src/lib.rs b/asus-nb/src/lib.rs index 382a1f16..2430929b 100644 --- a/asus-nb/src/lib.rs +++ b/asus-nb/src/lib.rs @@ -6,6 +6,8 @@ pub const LED_MSG_LEN: usize = 17; pub mod aura_modes; use aura_modes::AuraModes; +pub mod profile; + /// Contains mostly only what is required for parsing CLI options pub mod cli_options; diff --git a/asus-nb/src/profile.rs b/asus-nb/src/profile.rs new file mode 100644 index 00000000..d66f402d --- /dev/null +++ b/asus-nb/src/profile.rs @@ -0,0 +1,96 @@ +use gumdrop::Options; +use rog_fan_curve::{Curve, Fan}; +use serde_derive::{Deserialize, Serialize}; +use std::str::FromStr; + +#[derive(Debug, Serialize, Deserialize)] +pub enum ProfileEvent { + Cli(ProfileCommand), + ChangeMode(u8), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum FanLevel { + Normal, + Boost, + Silent, +} + +impl FromStr for FanLevel { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "normal" => Ok(FanLevel::Normal), + "boost" => Ok(FanLevel::Boost), + "silent" => Ok(FanLevel::Silent), + _ => Err("Invalid fan level"), + } + } +} + +impl From for FanLevel { + fn from(n: u8) -> Self { + match n { + 0 => FanLevel::Normal, + 1 => FanLevel::Boost, + 2 => FanLevel::Silent, + _ => FanLevel::Normal, + } + } +} + +impl From for u8 { + fn from(n: FanLevel) -> Self { + match n { + FanLevel::Normal => 0, + FanLevel::Boost => 1, + FanLevel::Silent => 2, + } + } +} + +impl From<&FanLevel> for u8 { + fn from(n: &FanLevel) -> Self { + match n { + FanLevel::Normal => 0, + FanLevel::Boost => 1, + FanLevel::Silent => 2, + } + } +} + +fn parse_fan_curve(data: &str) -> Result { + let curve = Curve::from_config_str(data)?; + if let Err(err) = curve.check_safety(Fan::Cpu) { + return Err(format!("Unsafe curve {:?}", err)); + } + if let Err(err) = curve.check_safety(Fan::Gpu) { + return Err(format!("Unsafe curve {:?}", err)); + } + Ok(curve) +} + +#[derive(Debug, Clone, Options, Serialize, Deserialize)] +pub struct ProfileCommand { + #[options(help = "print help message")] + help: bool, + #[options(help = "create the profile if it doesn't exist")] + pub create: bool, + + #[options(help = "enable cpu turbo (AMD)")] + pub turbo: bool, + #[options(help = "disable cpu turbo (AMD)")] + pub no_turbo: bool, + #[options(help = "set min cpu scaling (intel)")] + pub min_percentage: Option, + #[options(help = "set max cpu scaling (intel)")] + pub max_percentage: Option, + + #[options(meta = "PWR", help = "")] + pub preset: Option, + #[options(parse(try_from_str = "parse_fan_curve"), help = "set fan curve")] + pub curve: Option, + #[options(free)] + pub profile: Option, +}