diff --git a/Cargo.toml b/Cargo.toml index 568e6c99d..c84299130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,16 +20,13 @@ members = [ "asus-shutdown", "asusd-user", "config-traits", - "dmi-id", "rog-platform", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles", "rog-control-center", - "rog-slash", "simulators", - "rog-scsi", ] default-members = ["asusctl", "asusd", "asus-shutdown", "asusd-user", "rog-control-center"] diff --git a/asusctl/Cargo.toml b/asusctl/Cargo.toml index 3823f18b5..d18dab7e1 100644 --- a/asusctl/Cargo.toml +++ b/asusctl/Cargo.toml @@ -10,13 +10,10 @@ edition.workspace = true [dependencies] rog_anime = { path = "../rog-anime" } -rog_scsi = { path = "../rog-scsi" } -rog_slash = { path = "../rog-slash" } rog_aura = { path = "../rog-aura" } rog_dbus = { path = "../rog-dbus" } rog_profiles = { path = "../rog-profiles" } rog_platform = { path = "../rog-platform" } -dmi_id = { path = "../dmi-id" } log.workspace = true env_logger.workspace = true diff --git a/asusctl/src/anime_cli.rs b/asusctl/src/anime_cli.rs index ea1dfa68e..4decd9ccd 100644 --- a/asusctl/src/anime_cli.rs +++ b/asusctl/src/anime_cli.rs @@ -1,6 +1,8 @@ use argh::FromArgs; -use rog_anime::usb::{AnimAwake, AnimBooting, AnimShutdown, AnimSleeping}; +use log::warn; +use rog_anime::usb::{get_anime_type, AnimAwake, AnimBooting, AnimShutdown, AnimSleeping}; use rog_anime::AnimeType; +use rog_dbus::find_iface_blocking; #[derive(FromArgs, Debug)] #[argh(subcommand, name = "anime", description = "anime commands")] @@ -30,8 +32,6 @@ pub struct AnimeCommand { pub off_when_suspended: Option, #[argh(option, description = "turn the anime off when the lid is closed")] pub off_when_lid_closed: Option, - #[argh(option, description = "off with his head!!!")] - pub off_with_his_head: Option, #[argh(subcommand)] pub command: Option, } @@ -149,3 +149,227 @@ pub struct AnimeGifDiagonal { )] pub loops: u32, } + +pub fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box> { + if cmd.command.is_none() + && cmd.enable_display.is_none() + && cmd.enable_powersave_anim.is_none() + && cmd.brightness.is_none() + && cmd.off_when_lid_closed.is_none() + && cmd.off_when_suspended.is_none() + && cmd.off_when_unplugged.is_none() + && !cmd.clear + { + warn!("Missing arg or command; run 'asusctl anime --help' for usage"); + return Ok(()); + } + + let animes = + find_iface_blocking::("xyz.ljones.Anime")?; + + let mut anime_type = get_anime_type(); + if let AnimeType::Unsupported = anime_type { + if let Some(model) = cmd.override_type { + anime_type = model; + } else { + warn!("Anime display type is Unsupported; consider specifying --override-type"); + } + } + + for proxy in animes { + if let Some(enable) = cmd.enable_display { + proxy.set_enable_display(enable)?; + } + if let Some(enable) = cmd.enable_powersave_anim { + proxy.set_builtins_enabled(enable)?; + } + if let Some(bright) = cmd.brightness { + proxy.set_brightness(bright)?; + } + if let Some(enable) = cmd.off_when_lid_closed { + proxy.set_off_when_lid_closed(enable)?; + } + if let Some(enable) = cmd.off_when_suspended { + proxy.set_off_when_suspended(enable)?; + } + if let Some(enable) = cmd.off_when_unplugged { + proxy.set_off_when_unplugged(enable)?; + } + + if cmd.clear { + let data = vec![255u8; anime_type.data_length()]; + let tmp = rog_anime::AnimeDataBuffer::from_vec(anime_type, data)?; + proxy.write(tmp)?; + } + + if let Some(action) = cmd.command.as_ref() { + match action { + AnimeActions::Image(image) => { + if image.path.is_empty() { + warn!("Missing arg or command; run 'asusctl anime image --help' for usage"); + return Ok(()); + } + verify_brightness(image.bright)?; + + let matrix = rog_anime::AnimeImage::from_png( + std::path::Path::new(&image.path), + image.scale, + image.angle, + rog_anime::Vec2::new(image.x_pos, image.y_pos), + image.bright, + anime_type, + )?; + + proxy.write(::try_from(&matrix)?)?; + } + AnimeActions::PixelImage(image) => { + if image.path.is_empty() { + warn!("Missing arg or command; run 'asusctl anime pixel-image --help' for usage"); + return Ok(()); + } + verify_brightness(image.bright)?; + + let matrix = rog_anime::AnimeDiagonal::from_png( + std::path::Path::new(&image.path), + None, + image.bright, + anime_type, + )?; + + proxy.write(matrix.into_data_buffer(anime_type)?)?; + } + AnimeActions::Gif(gif) => { + if gif.path.is_empty() { + warn!("Missing arg or command; run 'asusctl anime gif --help' for usage"); + return Ok(()); + } + verify_brightness(gif.bright)?; + + let matrix = rog_anime::AnimeGif::from_gif( + std::path::Path::new(&gif.path), + gif.scale, + gif.angle, + rog_anime::Vec2::new(gif.x_pos, gif.y_pos), + rog_anime::AnimTime::Count(1), + gif.bright, + anime_type, + )?; + + play_gif_animation(&proxy, &matrix, gif.loops)?; + } + AnimeActions::PixelGif(gif) => { + if gif.path.is_empty() { + warn!("Missing arg or command; run 'asusctl anime pixel-gif --help' for usage"); + return Ok(()); + } + verify_brightness(gif.bright)?; + + let matrix = rog_anime::AnimeGif::from_diagonal_gif( + std::path::Path::new(&gif.path), + rog_anime::AnimTime::Count(1), + gif.bright, + anime_type, + )?; + + play_gif_animation(&proxy, &matrix, gif.loops)?; + } + AnimeActions::SetBuiltins(builtins) => { + if builtins.set.is_none() { + warn!("Missing arg; run 'asusctl anime set-builtins --help' for usage"); + return Ok(()); + } + + proxy.set_builtin_animations(rog_anime::Animations { + boot: builtins.boot, + awake: builtins.awake, + sleep: builtins.sleep, + shutdown: builtins.shutdown, + })?; + } + } + } + } + Ok(()) +} + +/// Helper to determine the playback iteration strategy. +/// Returns `None` for infinite loops (`loops == 0`), or `Some(count)` for finite playback. +fn compute_loop_plan(loops: u32) -> Option { + if loops == 0 { + None + } else { + Some(loops) + } +} + +/// Play GIF animation frames. `loops == 0` means infinite playback until interrupted. +fn play_gif_animation( + proxy: &rog_dbus::zbus_anime::AnimeProxyBlocking, + matrix: &rog_anime::AnimeGif, + loops: u32, +) -> Result<(), Box> { + let mut remaining = compute_loop_plan(loops); + + loop { + for frame in matrix.frames() { + proxy.write(frame.frame().clone())?; + std::thread::sleep(frame.delay()); + } + match remaining { + None => continue, + Some(1) => break, + Some(ref mut count) => *count -= 1, + } + } + Ok(()) +} + +fn verify_brightness(brightness: f32) -> Result<(), Box> { + if !(0.0..=1.0).contains(&brightness) { + return Err(format!( + "Brightness must be between 0.0 and 1.0 (inclusive), was {brightness}" + ) + .into()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_verify_brightness_valid() { + assert!(verify_brightness(0.0).is_ok()); + assert!(verify_brightness(0.5).is_ok()); + assert!(verify_brightness(1.0).is_ok()); + } + + #[test] + fn test_verify_brightness_invalid() { + assert!(verify_brightness(-0.1).is_err()); + assert!(verify_brightness(1.1).is_err()); + assert!(verify_brightness(f32::NAN).is_err()); + assert!(verify_brightness(f32::INFINITY).is_err()); + assert!(verify_brightness(f32::NEG_INFINITY).is_err()); + } + + #[test] + fn test_loop_iteration_count() { + assert_eq!( + compute_loop_plan(0), + None, + "0 loops must plan for infinite playback" + ); + assert_eq!( + compute_loop_plan(1), + Some(1), + "1 loop must plan for 1 iteration" + ); + assert_eq!( + compute_loop_plan(5), + Some(5), + "5 loops must plan for 5 iterations" + ); + } +} diff --git a/asusctl/src/fan_curve_cli.rs b/asusctl/src/fan_curve_cli.rs index 1060cece3..8e5d4283a 100644 --- a/asusctl/src/fan_curve_cli.rs +++ b/asusctl/src/fan_curve_cli.rs @@ -1,4 +1,5 @@ use argh::FromArgs; +use log::warn; use rog_platform::platform::PlatformProfile; use rog_profiles::fan_curve_set::CurveData; use rog_profiles::FanCurvePU; @@ -42,3 +43,77 @@ pub struct FanCurveCommand { )] pub data: Option, } + +const REQ_MOD_PROFILE_MSG: &str = + "--enable-fan-curves, --enable-fan-curve, --fan, and --data options require --mod-profile"; + +pub fn handle_fan_curve( + conn: &zbus::blocking::Connection, + cmd: &FanCurveCommand, +) -> Result<(), Box> { + if !cmd.get_enabled && !cmd.default && cmd.mod_profile.is_none() { + warn!("Missing arg or command; run 'asusctl fan-curve --help' for usage"); + return Ok(()); + } + + if (cmd.enable_fan_curves.is_some() + || cmd.enable_fan_curve.is_some() + || cmd.fan.is_some() + || cmd.data.is_some()) + && cmd.mod_profile.is_none() + { + warn!("{REQ_MOD_PROFILE_MSG}"); + return Ok(()); + } + + let fan_proxy = rog_dbus::zbus_fan_curves::FanCurvesProxyBlocking::new(conn).map_err(|e| { + warn!("Fan curves unavailable: {e}"); + rog_profiles::error::ProfileError::NotSupported + })?; + + let plat_proxy = rog_dbus::zbus_platform::PlatformProxyBlocking::new(conn)?; + if cmd.get_enabled { + let profile = plat_proxy.platform_profile()?; + let curves = fan_proxy.fan_curve_data(profile)?; + for curve in curves.iter() { + println!("{}", String::from(curve)); + } + } + + if cmd.default { + let active = plat_proxy.platform_profile()?; + fan_proxy.set_curves_to_defaults(active)?; + } + + if let Some(profile) = cmd.mod_profile { + if cmd.enable_fan_curves.is_none() && cmd.enable_fan_curve.is_none() && cmd.data.is_none() { + let data = fan_proxy.fan_curve_data(profile)?; + let ron = + ron::ser::to_string_pretty(&data, ron::ser::PrettyConfig::new().depth_limit(4))?; + println!("\nFan curves for {:?}\n\n{}", profile, ron); + } + + if let Some(enabled) = cmd.enable_fan_curves { + fan_proxy.set_fan_curves_enabled(profile, enabled)?; + } + + if let Some(enabled) = cmd.enable_fan_curve { + if let Some(fan) = cmd.fan { + fan_proxy.set_profile_fan_curve_enabled(profile, fan, enabled)?; + } else { + warn!("--enable-fan-curve requires --fan "); + } + } + + if let Some(mut curve) = cmd.data.clone() { + if let Some(fan) = cmd.fan { + curve.set_fan(fan); + fan_proxy.set_fan_curve(profile, curve)?; + } else { + warn!("--data requires --fan "); + } + } + } + + Ok(()) +} diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index d4cbef1b0..7c1bca787 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -1,42 +1,22 @@ -use std::convert::TryFrom; -use std::path::Path; -use std::process::Command; -use std::thread::sleep; - -use anime_cli::{AnimeActions, AnimeCommand}; -use aura_cli::{LedPowerCommand1, LedPowerCommand2}; -use dmi_id::DMIID; -use fan_curve_cli::FanCurveCommand; -use log::{error, info, LevelFilter}; -use rog_anime::usb::get_anime_type; -use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType, Vec2}; -use rog_aura::keyboard::{AuraPowerState, LaptopAuraPower}; -use rog_aura::{self, AuraEffect, PowerZones}; -use rog_dbus::asus_armoury::AsusArmouryProxyBlocking; +use log::{error, LevelFilter}; use rog_dbus::list_iface_blocking; -use rog_dbus::scsi_aura::ScsiAuraProxyBlocking; -use rog_dbus::zbus_anime::AnimeProxyBlocking; -use rog_dbus::zbus_aura::AuraProxyBlocking; -use rog_dbus::zbus_backlight::BacklightProxyBlocking; -use rog_dbus::zbus_fan_curves::FanCurvesProxyBlocking; use rog_dbus::zbus_platform::PlatformProxyBlocking; -use rog_platform::platform::{PlatformProfile, Properties}; -use rog_profiles::error::ProfileError; -use rog_scsi::AuraMode; -use ron::ser::PrettyConfig; -use scsi_cli::ScsiCommand; -use zbus::blocking::proxy::ProxyImpl; +use rog_platform::platform::Properties; use zbus::blocking::Connection; use crate::cli_opts::*; -use crate::slash_cli::{ - handle_slash_get, handle_slash_list, handle_slash_set, SlashCommand, SlashSubCommand, +use crate::platform_cli::{ + check_service, handle_armoury_command, handle_backlight, handle_battery, handle_brightness, + handle_info, handle_led_mode, handle_led_power1, handle_led_power2, handle_throttle_profile, + print_info, }; +use crate::slash_cli::{handle_slash_get, handle_slash_list, handle_slash_set, SlashSubCommand}; mod anime_cli; mod aura_cli; mod cli_opts; mod fan_curve_cli; +mod platform_cli; mod scsi_cli; mod slash_cli; mod xgm_led_cli; @@ -112,80 +92,14 @@ fn print_error_help( supported_properties: &[Properties], ) { check_service("asusd"); - println!("\nError: {}\n", err); + println!("\nError: {err}\n"); print_info(); println!(); - println!("Supported interfaces:\n\n{:#?}\n", supported_interfaces); - println!( - "Supported properties on Platform:\n\n{:#?}\n", - supported_properties - ); -} - -fn print_info() { - let dmi = DMIID::new().unwrap_or_default(); - let board_name = dmi.board_name; - let prod_family = dmi.product_family; - println!("Software version: {}", env!("CARGO_PKG_VERSION")); - println!(" Product family: {}", prod_family.trim()); - println!(" Board name: {}", board_name.trim()); -} - -fn check_service(name: &str) -> bool { - if name != "asusd" && !check_systemd_unit_enabled(name) { - println!( - "\n\x1b[0;31m{} is not enabled, enable it with `systemctl enable {}\x1b[0m", - name, name - ); - return true; - } else if !check_systemd_unit_active(name) { - println!( - "\n\x1b[0;31m{} is not running, start it with `systemctl start {}\x1b[0m", - name, name - ); - return true; - } - false -} - -fn find_iface(iface_name: &str) -> Result, Box> -where - T: ProxyImpl<'static> + From>, -{ - let conn = zbus::blocking::Connection::system()?; - let f = zbus::blocking::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/")?; - let interfaces = f.get_managed_objects()?; - let mut paths = Vec::new(); - for v in interfaces.iter() { - // let o: Vec = v.1.keys().map(|e| - // e.to_owned()).collect(); println!("{}, {:?}", v.0, o); - for k in v.1.keys() { - if k.as_str() == iface_name { - // println!("Found {iface_name} device at {}, {}", v.0, k); - paths.push(v.0.clone()); - } - } - } - if paths.len() > 1 { - println!("Multiple asusd interfaces devices found"); - } - if !paths.is_empty() { - let mut ctrl = Vec::new(); - paths.sort_by(|a, b| a.cmp(b)); - for path in paths { - ctrl.push( - T::builder(&conn) - .path(path.clone())? - .destination("xyz.ljones.Asusd")? - .build()?, - ); - } - return Ok(ctrl); - } - - Err(format!("Did not find {iface_name}").into()) + println!("Supported interfaces:\n\n{supported_interfaces:#?}\n"); + println!("Supported properties on Platform:\n\n{supported_properties:#?}\n"); } +#[allow(clippy::needless_pass_by_value)] fn do_parsed( parsed: &CliStart, supported_interfaces: &[String], @@ -200,864 +114,22 @@ fn do_parsed( }, CliCommand::Brightness(cmd) => handle_brightness(cmd)?, CliCommand::Profile(cmd) => handle_throttle_profile(&conn, supported_properties, cmd)?, - CliCommand::FanCurve(cmd) => handle_fan_curve(&conn, cmd)?, - CliCommand::Anime(cmd) => handle_anime(cmd)?, - CliCommand::Slash(cmd) => handle_slash(cmd)?, - CliCommand::Scsi(cmd) => handle_scsi(cmd)?, + CliCommand::FanCurve(cmd) => fan_curve_cli::handle_fan_curve(&conn, cmd)?, + CliCommand::Anime(cmd) => anime_cli::handle_anime(cmd)?, + CliCommand::Slash(cmd) => match &cmd.command { + SlashSubCommand::Get(_) => handle_slash_get()?, + SlashSubCommand::Set(cmd) => handle_slash_set(cmd)?, + SlashSubCommand::List(_) => handle_slash_list(), + }, + CliCommand::Scsi(cmd) => scsi_cli::handle_scsi(cmd)?, CliCommand::Armoury(cmd) => handle_armoury_command(cmd)?, CliCommand::Backlight(cmd) => handle_backlight(cmd)?, CliCommand::Battery(cmd) => handle_battery(cmd, &conn)?, CliCommand::XgmLed(cmd) => xgm_led_cli::handle_xgm_led(&cmd.command)?, CliCommand::Info(info_opt) => { - handle_info(info_opt, supported_interfaces, supported_properties)? - } - } - - Ok(()) -} - -fn handle_battery( - cmd: &BatteryCommand, - conn: &Connection, -) -> Result<(), Box> { - match &cmd.command { - BatterySubCommand::Limit(l) => { - let proxy = PlatformProxyBlocking::new(conn)?; - proxy.set_charge_control_end_threshold(l.limit)?; - } - BatterySubCommand::OneShot(o) => { - let proxy = PlatformProxyBlocking::new(conn)?; - if let Some(p) = o.percent { - proxy.set_charge_control_end_threshold(p)?; - } - proxy.one_shot_full_charge()?; - } - BatterySubCommand::Info(_) => { - let proxy = PlatformProxyBlocking::new(conn)?; - let limit = proxy.charge_control_end_threshold()?; - println!("Current battery charge limit: {}%", limit); - } - } - - Ok(()) -} - -fn handle_info( - info_opt: &InfoCommand, - supported_interfaces: &[String], - supported_properties: &[Properties], -) -> Result<(), Box> { - println!("asusctl v{}", env!("CARGO_PKG_VERSION")); - println!(); - print_info(); - println!(); - - if info_opt.show_supported { - println!("Supported Core Functions:\n{:#?}", supported_interfaces); - println!( - "Supported Platform Properties:\n{:#?}", - supported_properties - ); - if let Ok(aura) = find_iface::("xyz.ljones.Aura") { - // TODO: multiple RGB check - if let Some(first_aura) = aura.first() { - let bright = first_aura.supported_brightness()?; - let modes = first_aura.supported_basic_modes()?; - let zones = first_aura.supported_basic_zones()?; - let power = first_aura.supported_power_zones()?; - println!("Supported Keyboard Brightness:\n{:#?}", bright); - println!("Supported Aura Modes:\n{:#?}", modes); - println!("Supported Aura Zones:\n{:#?}", zones); - println!("Supported Aura Power Zones:\n{:#?}", power); - } else { - println!("No aura interface found"); - } - } else { - println!("No aura interface found"); - } - } - - Ok(()) -} - -fn handle_backlight(cmd: &BacklightCommand) -> Result<(), Box> { - if cmd.screenpad_brightness.is_none() - && cmd.screenpad_gamma.is_none() - && cmd.sync_screenpad_brightness.is_none() - { - let backlights = find_iface::("xyz.ljones.Backlight")?; - for backlight in backlights { - println!("Current screenpad settings:"); - println!(" Brightness: {}", backlight.screenpad_brightness()?); - println!(" Gamma: {}", backlight.screenpad_gamma()?); - println!( - " Sync with primary: {}", - backlight.screenpad_sync_with_primary()? - ); - } - - return Ok(()); - } - - let backlights = find_iface::("xyz.ljones.Backlight")?; - for backlight in backlights { - if let Some(brightness) = cmd.screenpad_brightness { - backlight.set_screenpad_brightness(brightness)?; - } - - if let Some(gamma) = cmd.screenpad_gamma { - backlight.set_screenpad_gamma(gamma.to_string().as_str())?; - } - - if let Some(sync) = cmd.sync_screenpad_brightness { - backlight.set_screenpad_sync_with_primary(sync)?; - } - } - - Ok(()) -} - -fn handle_brightness(cmd: &BrightnessCommand) -> Result<(), Box> { - let Ok(aura_proxies) = find_iface::("xyz.ljones.Aura") else { - println!("No aura interface found"); - return Ok(()); - }; - - match &cmd.command { - BrightnessSubCommand::Set(s) => { - for aura in aura_proxies.iter() { - if let Some(level) = s.level.level() { - aura.set_brightness(rog_aura::LedBrightness::from(level))?; - } else { - let current = aura.brightness()?; - println!("Current keyboard led brightness: {current:?}"); - } - } - } - BrightnessSubCommand::Get(_) => { - for aura in aura_proxies.iter() { - let level = aura.brightness()?; - println!("Current keyboard led brightness: {level:?}"); - } - - return Ok(()); - } - BrightnessSubCommand::Next(_) => { - for aura in aura_proxies.iter() { - let brightness = aura.brightness()?; - aura.set_brightness(brightness.next())?; - } - } - BrightnessSubCommand::Prev(_) => { - for aura in aura_proxies.iter() { - let brightness = aura.brightness()?; - aura.set_brightness(brightness.prev())?; - } - } - } - - Ok(()) -} - -fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box> { - if cmd.command.is_none() - && cmd.enable_display.is_none() - && cmd.enable_powersave_anim.is_none() - && cmd.brightness.is_none() - && cmd.off_when_lid_closed.is_none() - && cmd.off_when_suspended.is_none() - && cmd.off_when_unplugged.is_none() - && cmd.off_with_his_head.is_none() - && !cmd.clear - { - println!("Missing arg or command; run 'asusctl anime --help' for usage"); - } - - let animes = find_iface::("xyz.ljones.Anime").map_err(|e| { - error!("Did not find any interface for xyz.ljones.Anime: {e:?}"); - e - })?; - - for proxy in animes { - if let Some(enable) = cmd.enable_display { - proxy.set_enable_display(enable)?; - } - if let Some(enable) = cmd.enable_powersave_anim { - proxy.set_builtins_enabled(enable)?; - } - if let Some(bright) = cmd.brightness { - proxy.set_brightness(bright)?; - } - if let Some(enable) = cmd.off_when_lid_closed { - proxy.set_off_when_lid_closed(enable)?; - } - if let Some(enable) = cmd.off_when_suspended { - proxy.set_off_when_suspended(enable)?; - } - if let Some(enable) = cmd.off_when_unplugged { - proxy.set_off_when_unplugged(enable)?; - } - if cmd.off_with_his_head.is_some() { - println!("Did Alice _really_ make it back from Wonderland?"); - } - - let mut anime_type = get_anime_type(); - if let AnimeType::Unsupported = anime_type { - if let Some(model) = cmd.override_type { - anime_type = model; - } - } - - if cmd.clear { - let data = vec![255u8; anime_type.data_length()]; - let tmp = AnimeDataBuffer::from_vec(anime_type, data)?; - proxy.write(tmp)?; - } - - if let Some(action) = cmd.command.as_ref() { - match action { - AnimeActions::Image(image) => { - if image.path.is_empty() { - println!( - "Missing arg or command; run 'asusctl anime image --help' for usage" - ); - return Ok(()); - } - verify_brightness(image.bright); - - let matrix = AnimeImage::from_png( - Path::new(&image.path), - image.scale, - image.angle, - Vec2::new(image.x_pos, image.y_pos), - image.bright, - anime_type, - )?; - - proxy.write(::try_from(&matrix)?)?; - } - AnimeActions::PixelImage(image) => { - if image.path.is_empty() { - println!("Missing arg or command; run 'asusctl anime pixel-image --help' for usage"); - return Ok(()); - } - verify_brightness(image.bright); - - let matrix = AnimeDiagonal::from_png( - Path::new(&image.path), - None, - image.bright, - anime_type, - )?; - - proxy.write(matrix.into_data_buffer(anime_type)?)?; - } - AnimeActions::Gif(gif) => { - if gif.path.is_empty() { - println!( - "Missing arg or command; run 'asusctl anime gif --help' for usage" - ); - return Ok(()); - } - verify_brightness(gif.bright); - - let matrix = AnimeGif::from_gif( - Path::new(&gif.path), - gif.scale, - gif.angle, - Vec2::new(gif.x_pos, gif.y_pos), - AnimTime::Count(1), - gif.bright, - anime_type, - )?; - - let mut loops = gif.loops as i32; - loop { - for frame in matrix.frames() { - proxy.write(frame.frame().clone())?; - sleep(frame.delay()); - } - if loops >= 0 { - loops -= 1; - } - if loops == 0 { - break; - } - } - } - AnimeActions::PixelGif(gif) => { - if gif.path.is_empty() { - println!("Missing arg or command; run 'asusctl anime pixel-gif --help' for usage"); - return Ok(()); - } - verify_brightness(gif.bright); - - let matrix = AnimeGif::from_diagonal_gif( - Path::new(&gif.path), - AnimTime::Count(1), - gif.bright, - anime_type, - )?; - - let mut loops = gif.loops as i32; - loop { - for frame in matrix.frames() { - proxy.write(frame.frame().clone())?; - sleep(frame.delay()); - } - if loops >= 0 { - loops -= 1; - } - if loops == 0 { - break; - } - } - } - AnimeActions::SetBuiltins(builtins) => { - if builtins.set.is_none() { - println!("Missing arg; run 'asusctl anime set-builtins --help' for usage"); - return Ok(()); - } - - proxy.set_builtin_animations(rog_anime::Animations { - boot: builtins.boot, - awake: builtins.awake, - sleep: builtins.sleep, - shutdown: builtins.shutdown, - })?; - } - } - } - } - Ok(()) -} - -fn verify_brightness(brightness: f32) { - if !(0.0..=1.0).contains(&brightness) { - println!( - "Image and global brightness must be between 0.0 and 1.0 (inclusive), was {}", - brightness - ); - } -} - -fn handle_slash(cmd: &SlashCommand) -> Result<(), Box> { - match &cmd.command { - SlashSubCommand::Get(_) => handle_slash_get()?, - SlashSubCommand::Set(cmd) => handle_slash_set(cmd)?, - SlashSubCommand::List(_) => handle_slash_list(), - } - - Ok(()) -} - -fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box> { - if !cmd.list && cmd.enable.is_none() && cmd.mode.is_none() && cmd.colours.is_empty() { - println!("Missing arg or command; run 'asusctl scsi --help' for usage"); - } - - let scsis = find_iface::("xyz.ljones.ScsiAura")?; - - for scsi in scsis { - if let Some(enable) = cmd.enable { - scsi.set_enabled(enable)?; - } - - if let Some(mode) = cmd.mode { - scsi.set_led_mode(mode)?; - } - - let mut mode = scsi.led_mode_data()?; - let mut do_update = false; - if !cmd.colours.is_empty() { - for (count, c) in cmd.colours.iter().enumerate() { - if count == 0 { - mode.colour1 = *c; - } - if count == 1 { - mode.colour2 = *c; - } - if count == 2 { - mode.colour3 = *c; - } - if count == 3 { - mode.colour4 = *c; - } - } - do_update = true; - } - - if let Some(speed) = cmd.speed { - mode.speed = speed; - do_update = true; - } - - if let Some(dir) = cmd.direction { - mode.direction = dir; - do_update = true; - } - - if do_update { - scsi.set_led_mode_data(mode.clone())?; - } - - // let mode_ret = scsi.led_mode_data()?; - // assert_eq!(mode, mode_ret); - println!("{mode}"); - } - - if cmd.list { - let res = AuraMode::list(); - for p in &res { - println!("{:?}", p); - } - } - - Ok(()) -} - -fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box> { - if mode.command.is_none() && !mode.prev_mode && !mode.next_mode { - println!("Missing arg or command; run 'asusctl aura --help' for usage"); - // print available modes when possible - if let Ok(aura) = find_iface::("xyz.ljones.Aura") { - if let Some(first_aura) = aura.first() { - let modes = first_aura.supported_basic_modes()?; - println!("Available modes:"); - for m in modes { - println!(" {:?}", m); - } - } - } - return Ok(()); - } - - if mode.next_mode && mode.prev_mode { - println!("Please specify either next or previous"); - return Ok(()); - } - let aura = find_iface::("xyz.ljones.Aura")?; - if mode.next_mode { - for aura in aura { - let mode = aura.led_mode()?; - let modes = aura.supported_basic_modes()?; - if let Some(pos) = modes.iter().position(|m| *m == mode) { - let next_pos = if pos + 1 >= modes.len() { 0 } else { pos + 1 }; - if let Some(&target_mode) = modes.get(next_pos) { - aura.set_led_mode(target_mode)?; - } - } else if let Some(&first) = modes.first() { - aura.set_led_mode(first)?; - } - } - } else if mode.prev_mode { - for aura in aura { - let mode = aura.led_mode()?; - let modes = aura.supported_basic_modes()?; - if let Some(pos) = modes.iter().position(|m| *m == mode) { - let prev_pos = if pos == 0 { - modes.len().saturating_sub(1) - } else { - pos - 1 - }; - if let Some(&target_mode) = modes.get(prev_pos) { - aura.set_led_mode(target_mode)?; - } - } else if let Some(&last) = modes.last() { - aura.set_led_mode(last)?; - } - } - } else if let Some(mode) = mode.command.as_ref() { - for aura in aura { - aura.set_led_mode_data(::from(mode))?; - } - } - - Ok(()) -} - -fn handle_led_power1(power: &LedPowerCommand1) -> Result<(), Box> { - let aura = find_iface::("xyz.ljones.Aura")?; - for aura in aura { - let dev_type = aura.device_type()?; - if !dev_type.is_old_laptop() && !dev_type.is_tuf_laptop() { - println!("This option applies only to keyboards 2021+"); - } - - if power.awake.is_none() - && power.sleep.is_none() - && power.boot.is_none() - && !power.keyboard - && !power.lightbar - { - println!("Missing arg or command; run 'asusctl aura power-tuf --help' for usage"); - return Ok(()); - } - - if dev_type.is_old_laptop() || dev_type.is_tuf_laptop() { - handle_led_power_1_do_1866(&aura, power)?; - return Ok(()); - } - } - - println!("These options are for keyboards of product ID 0x1866 or TUF only"); - Ok(()) -} - -fn handle_led_power_1_do_1866( - aura: &AuraProxyBlocking, - power: &LedPowerCommand1, -) -> Result<(), Box> { - let mut states = Vec::new(); - if power.keyboard { - states.push(AuraPowerState { - zone: PowerZones::Keyboard, - boot: power.boot.unwrap_or_default(), - awake: power.awake.unwrap_or_default(), - sleep: power.sleep.unwrap_or_default(), - shutdown: false, - }); - } - if power.lightbar { - states.push(AuraPowerState { - zone: PowerZones::Lightbar, - boot: power.boot.unwrap_or_default(), - awake: power.awake.unwrap_or_default(), - sleep: power.sleep.unwrap_or_default(), - shutdown: false, - }); - } - - let states = LaptopAuraPower { states }; - aura.set_led_power(states)?; - Ok(()) -} - -fn handle_led_power2(power: &LedPowerCommand2) -> Result<(), Box> { - let aura = find_iface::("xyz.ljones.Aura")?; - for aura in aura { - let dev_type = aura.device_type()?; - if !dev_type.is_new_laptop() { - println!("This option applies only to keyboards 2021+"); - continue; - } - - if power.command.is_none() { - println!("Missing arg or command; run 'asusctl aura power --help' for usage"); - println!("Commands available"); - return Ok(()); - } - - if let Some(_pow) = power.command.as_ref() { - let mut states = aura.led_power()?; - let mut set = - |zone: PowerZones, boot_v: bool, awake_v: bool, sleep_v: bool, shutdown_v: bool| { - for state in states.states.iter_mut() { - if state.zone == zone { - state.boot = boot_v; - state.awake = awake_v; - state.sleep = sleep_v; - state.shutdown = shutdown_v; - break; - } - } - }; - - if let Some(cmd) = &power.command { - match cmd { - aura_cli::SetAuraZoneEnabled::Keyboard(k) => { - set(PowerZones::Keyboard, k.boot, k.awake, k.sleep, k.shutdown) - } - aura_cli::SetAuraZoneEnabled::Logo(l) => { - set(PowerZones::Logo, l.boot, l.awake, l.sleep, l.shutdown) - } - aura_cli::SetAuraZoneEnabled::Lightbar(l) => { - set(PowerZones::Lightbar, l.boot, l.awake, l.sleep, l.shutdown) - } - aura_cli::SetAuraZoneEnabled::Lid(l) => { - set(PowerZones::Lid, l.boot, l.awake, l.sleep, l.shutdown) - } - aura_cli::SetAuraZoneEnabled::RearGlow(r) => { - set(PowerZones::RearGlow, r.boot, r.awake, r.sleep, r.shutdown) - } - aura_cli::SetAuraZoneEnabled::Ally(r) => { - set(PowerZones::Ally, r.boot, r.awake, r.sleep, r.shutdown) - } - } - } - - aura.set_led_power(states)?; - } - } - - Ok(()) -} - -fn handle_throttle_profile( - conn: &Connection, - supported: &[Properties], - cmd: &ProfileCommand, -) -> Result<(), Box> { - if !supported.contains(&Properties::ThrottlePolicy) { - println!("Profiles not supported by either this kernel or by the laptop."); - return Err(ProfileError::NotSupported.into()); - } - - let proxy = PlatformProxyBlocking::new(conn)?; - let current = proxy.platform_profile()?; - let choices = proxy.platform_profile_choices()?; - - match &cmd.command { - crate::cli_opts::ProfileSubCommand::Next(_) => { - proxy.set_platform_profile(PlatformProfile::next(current, &choices))?; - } - crate::cli_opts::ProfileSubCommand::Set(s) => { - if !s.ac && !s.battery { - proxy.set_platform_profile(s.profile)?; - } else { - if s.ac { - proxy.set_platform_profile_on_ac(s.profile)?; - } - if s.battery { - proxy.set_platform_profile_on_battery(s.profile)?; - } - } - } - crate::cli_opts::ProfileSubCommand::List(_) => { - for p in &choices { - println!("{:?}", p); - } - } - crate::cli_opts::ProfileSubCommand::Get(_) => { - println!("Active profile: {current:?}"); - println!(); - println!("AC profile {:?}", proxy.platform_profile_on_ac()?); - println!("Battery profile {:?}", proxy.platform_profile_on_battery()?); - } - } - - Ok(()) -} - -fn handle_fan_curve( - conn: &Connection, - cmd: &FanCurveCommand, -) -> Result<(), Box> { - let Ok(fan_proxy) = FanCurvesProxyBlocking::new(conn).map_err(|e| { - println!("Fan-curves not supported by either this kernel or by the laptop: {e:?}"); - }) else { - return Err(ProfileError::NotSupported.into()); - }; - - if !cmd.get_enabled && !cmd.default && cmd.mod_profile.is_none() { - println!("Missing arg or command; run 'asusctl fan-curve --help' for usage"); - return Ok(()); - } - - if (cmd.enable_fan_curves.is_some() || cmd.fan.is_some() || cmd.data.is_some()) - && cmd.mod_profile.is_none() - { - println!( - "--enable-fan-curves, --enable-fan-curve, --fan, and --data options require \ - --mod-profile" - ); - return Ok(()); - } - - let plat_proxy = PlatformProxyBlocking::new(conn)?; - if cmd.get_enabled { - let profile = plat_proxy.platform_profile()?; - let curves = fan_proxy.fan_curve_data(profile)?; - for curve in curves.iter() { - println!("{}", String::from(curve)); - } - } - - if cmd.default { - let active = plat_proxy.platform_profile()?; - fan_proxy.set_curves_to_defaults(active)?; - } - - if let Some(profile) = cmd.mod_profile { - if cmd.enable_fan_curves.is_none() && cmd.data.is_none() { - let data = fan_proxy.fan_curve_data(profile)?; - let ron = ron::ser::to_string_pretty(&data, PrettyConfig::new().depth_limit(4))?; - println!("\nFan curves for {:?}\n\n{}", profile, ron); - } - - if let Some(enabled) = cmd.enable_fan_curves { - fan_proxy.set_fan_curves_enabled(profile, enabled)?; - } - - if let Some(enabled) = cmd.enable_fan_curve { - if let Some(fan) = cmd.fan { - fan_proxy.set_profile_fan_curve_enabled(profile, fan, enabled)?; - } else { - println!( - "--enable-fan-curves, --enable-fan-curve, --fan, and --data options require \ - --mod-profile" - ); - } - } - - if let Some(mut curve) = cmd.data.clone() { - let fan = cmd.fan.unwrap_or_default(); - curve.set_fan(fan); - fan_proxy.set_fan_curve(profile, curve)?; + handle_info(info_opt, supported_interfaces, supported_properties)?; } } Ok(()) } - -fn check_systemd_unit_active(name: &str) -> bool { - if let Ok(out) = Command::new("systemctl") - .arg("is-active") - .arg(name) - .output() - { - let buf = String::from_utf8_lossy(&out.stdout); - return !buf.contains("inactive") && !buf.contains("failed"); - } - false -} - -fn check_systemd_unit_enabled(name: &str) -> bool { - if let Ok(out) = Command::new("systemctl") - .arg("is-enabled") - .arg(name) - .output() - { - let buf = String::from_utf8_lossy(&out.stdout); - return buf.contains("enabled") || buf.contains("linked"); - } - false -} - -fn print_firmware_attr(attr: &AsusArmouryProxyBlocking) -> Result<(), Box> { - let name = attr.name()?; - println!("{}:", <&str>::from(name)); - - // Be resilient to DBus read failures: if any read fails, show "unavailable" - let attrs = attr.available_attrs().unwrap_or_default(); - - let has_min = attrs.contains(&"min_value".to_string()); - let has_max = attrs.contains(&"max_value".to_string()); - let has_current = attrs.contains(&"current_value".to_string()); - let has_possible = attrs.contains(&"possible_values".to_string()); - let has_default = attrs.contains(&"default_value".to_string()); - - if has_current && (has_min || has_max) { - let c = attr.current_value().ok(); - let min = if has_min { attr.min_value().ok() } else { None }; - let max = if has_max { attr.max_value().ok() } else { None }; - match (min, c, max) { - (Some(min), Some(c), Some(max)) => println!(" current: {min}..[{c}]..{max}"), - (Some(min), Some(c), None) => println!(" current: {min}..[{c}]"), - (None, Some(c), Some(max)) => println!(" current: [{c}]..{max}"), - _ => println!(" current: unavailable"), - } - - if has_default { - match attr.default_value().ok() { - Some(d) => println!(" default: {}\n", d), - None => println!(" default: unavailable\n"), - } - } else { - println!(); - } - } else if has_possible && has_current { - let c = attr.current_value().ok(); - let v = attr.possible_values().ok(); - if let (Some(c), Some(v)) = (c, v) { - for p in v.iter().enumerate() { - if p.0 == 0 { - print!(" current: ["); - } - if *p.1 == c { - print!("({c})"); - } else { - print!("{}", p.1); - } - if p.0 < v.len() - 1 { - print!(","); - } - if p.0 == v.len() - 1 { - print!("]"); - } - } - if has_default { - match attr.default_value().ok() { - Some(d) => println!(" default: {}\n", d), - None => println!(" default: unavailable\n"), - } - } else { - println!("\n"); - } - } else { - println!(" current: unavailable\n"); - } - } else if has_current { - match attr.current_value().ok() { - Some(c) => println!(" current: {c}\n"), - None => println!(" current: unavailable\n"), - } - } else { - println!(" unavailable\n"); - } - - Ok(()) -} - -#[allow(clippy::manual_is_multiple_of, clippy::nonminimal_bool)] -fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box> { - // If nested subcommand provided, handle set/get/list. - match &cmd.command { - ArmourySubCommand::List(_) => { - if let Ok(attrs) = find_iface::("xyz.ljones.AsusArmoury") { - for attr in attrs.iter() { - print_firmware_attr(attr)?; - } - } - Ok(()) - } - ArmourySubCommand::Get(g) => { - let mut found = false; - let attrs = find_iface::("xyz.ljones.AsusArmoury") - .map_err(|e| format!("Could not reach asusd armoury interface: {e}"))?; - for attr in attrs.iter() { - let name = attr.name()?; - if <&str>::from(name) == g.property { - print_firmware_attr(attr)?; - found = true; - } - } - if !found { - return Err(format!("Firmware attribute '{}' not found", g.property).into()); - } - Ok(()) - } - ArmourySubCommand::Set(s) => { - let mut found = false; - let attrs = find_iface::("xyz.ljones.AsusArmoury") - .map_err(|e| format!("Could not reach asusd armoury interface: {e}"))?; - for attr in attrs.iter() { - let name = attr.name()?; - if <&str>::from(name) == s.property { - let mut value: i32 = s.value; - if value == -1 { - info!("Setting to default"); - value = attr.default_value()?; - } - attr.set_current_value(value)?; - print_firmware_attr(attr)?; - found = true; - } - } - if !found { - return Err(format!("Firmware attribute '{}' not found", s.property).into()); - } - Ok(()) - } - } -} diff --git a/asusctl/src/platform_cli.rs b/asusctl/src/platform_cli.rs new file mode 100644 index 000000000..7c49086a5 --- /dev/null +++ b/asusctl/src/platform_cli.rs @@ -0,0 +1,561 @@ +use std::process::Command; + +use log::{info, warn}; +use rog_aura::keyboard::{AuraPowerState, LaptopAuraPower}; +use rog_aura::{AuraEffect, PowerZones}; +use rog_dbus::asus_armoury::AsusArmouryProxyBlocking; +use rog_dbus::zbus_aura::AuraProxyBlocking; +use rog_dbus::zbus_backlight::BacklightProxyBlocking; +use rog_dbus::zbus_platform::PlatformProxyBlocking; +use rog_platform::platform::{PlatformProfile, Properties}; +use rog_platform::DMIID; +use rog_profiles::error::ProfileError; +use zbus::blocking::Connection; + +use crate::aura_cli::{LedPowerCommand1, LedPowerCommand2}; +use crate::cli_opts::{ + ArmouryCommand, ArmourySubCommand, BacklightCommand, BatteryCommand, BatterySubCommand, + BrightnessCommand, BrightnessSubCommand, InfoCommand, LedModeCommand, ProfileCommand, +}; + +pub fn check_service(name: &str) -> bool { + if name != "asusd" && !check_systemd_unit_enabled(name) { + warn!( + "{} is not enabled, enable it with `systemctl enable {}`", + name, name + ); + false + } else if !check_systemd_unit_active(name) { + warn!( + "{} is not running, start it with `systemctl start {}`", + name, name + ); + false + } else { + true + } +} + +pub fn check_systemd_unit_active(name: &str) -> bool { + if let Ok(out) = Command::new("systemctl") + .arg("is-active") + .arg(name) + .output() + { + let buf = String::from_utf8_lossy(&out.stdout); + return !buf.contains("inactive") && !buf.contains("failed"); + } + false +} + +pub fn check_systemd_unit_enabled(name: &str) -> bool { + if let Ok(out) = Command::new("systemctl") + .arg("is-enabled") + .arg(name) + .output() + { + let buf = String::from_utf8_lossy(&out.stdout); + return buf.contains("enabled") || buf.contains("linked"); + } + false +} + +pub fn print_info() { + let dmi = DMIID::new().unwrap_or_default(); + let board_name = dmi.board_name; + let prod_family = dmi.product_family; + println!("Software version: {}", env!("CARGO_PKG_VERSION")); + println!(" Product family: {}", prod_family.trim()); + println!(" Board name: {}", board_name.trim()); +} + +use rog_dbus::find_iface_blocking; + +pub fn handle_info( + info_opt: &InfoCommand, + supported_interfaces: &[String], + supported_properties: &[Properties], +) -> Result<(), Box> { + println!("asusctl v{}", env!("CARGO_PKG_VERSION")); + println!(); + print_info(); + println!(); + + if info_opt.show_supported { + println!("Supported Core Functions:\n{:#?}", supported_interfaces); + println!( + "Supported Platform Properties:\n{:#?}", + supported_properties + ); + match find_iface_blocking::("xyz.ljones.Aura") { + Ok(aura) => { + if let Some(first_aura) = aura.first() { + let bright = first_aura.supported_brightness()?; + let modes = first_aura.supported_basic_modes()?; + let zones = first_aura.supported_basic_zones()?; + let power = first_aura.supported_power_zones()?; + println!("Supported Keyboard Brightness:\n{:#?}", bright); + println!("Supported Aura Modes:\n{:#?}", modes); + println!("Supported Aura Zones:\n{:#?}", zones); + println!("Supported Aura Power Zones:\n{:#?}", power); + } else { + warn!("No aura interface found"); + } + } + Err(err) => { + warn!("No aura interface found: {err}"); + } + } + } + + Ok(()) +} + +pub fn handle_battery( + cmd: &BatteryCommand, + conn: &Connection, +) -> Result<(), Box> { + match &cmd.command { + BatterySubCommand::Limit(l) => { + let proxy = PlatformProxyBlocking::new(conn)?; + proxy.set_charge_control_end_threshold(l.limit)?; + } + BatterySubCommand::OneShot(o) => { + let proxy = PlatformProxyBlocking::new(conn)?; + if let Some(p) = o.percent { + proxy.set_charge_control_end_threshold(p)?; + } + proxy.one_shot_full_charge()?; + } + BatterySubCommand::Info(_) => { + let proxy = PlatformProxyBlocking::new(conn)?; + let limit = proxy.charge_control_end_threshold()?; + println!("Current battery charge limit: {}%", limit); + } + } + + Ok(()) +} + +pub fn handle_backlight(cmd: &BacklightCommand) -> Result<(), Box> { + let backlights = find_iface_blocking::("xyz.ljones.Backlight")?; + + if cmd.screenpad_brightness.is_none() + && cmd.screenpad_gamma.is_none() + && cmd.sync_screenpad_brightness.is_none() + { + for backlight in backlights { + println!("Current screenpad settings:"); + println!(" Brightness: {}", backlight.screenpad_brightness()?); + println!(" Gamma: {}", backlight.screenpad_gamma()?); + println!( + " Sync with primary: {}", + backlight.screenpad_sync_with_primary()? + ); + } + + return Ok(()); + } + + for backlight in backlights { + if let Some(brightness) = cmd.screenpad_brightness { + backlight.set_screenpad_brightness(brightness)?; + } + + if let Some(gamma) = cmd.screenpad_gamma { + backlight.set_screenpad_gamma(gamma.to_string().as_str())?; + } + + if let Some(sync) = cmd.sync_screenpad_brightness { + backlight.set_screenpad_sync_with_primary(sync)?; + } + } + + Ok(()) +} + +pub fn handle_brightness(cmd: &BrightnessCommand) -> Result<(), Box> { + let Ok(aura_proxies) = find_iface_blocking::("xyz.ljones.Aura") else { + println!("No aura interface found"); + return Ok(()); + }; + + match &cmd.command { + BrightnessSubCommand::Set(s) => { + for aura in aura_proxies.iter() { + if let Some(level) = s.level.level() { + aura.set_brightness(rog_aura::LedBrightness::from(level))?; + } else { + let current = aura.brightness()?; + println!("Current keyboard led brightness: {current:?}"); + } + } + } + BrightnessSubCommand::Get(_) => { + for aura in aura_proxies.iter() { + let level = aura.brightness()?; + println!("Current keyboard led brightness: {level:?}"); + } + + return Ok(()); + } + BrightnessSubCommand::Next(_) => { + for aura in aura_proxies.iter() { + let brightness = aura.brightness()?; + aura.set_brightness(brightness.next())?; + } + } + BrightnessSubCommand::Prev(_) => { + for aura in aura_proxies.iter() { + let brightness = aura.brightness()?; + aura.set_brightness(brightness.prev())?; + } + } + } + + Ok(()) +} + +pub fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box> { + if mode.command.is_none() && !mode.prev_mode && !mode.next_mode { + warn!("Missing arg or command; run 'asusctl aura --help' for usage"); + if let Ok(aura) = find_iface_blocking::("xyz.ljones.Aura") { + if let Some(first_aura) = aura.first() { + let modes = first_aura.supported_basic_modes()?; + println!("Available modes:"); + for m in modes { + println!(" {:?}", m); + } + } + } + return Ok(()); + } + + if mode.next_mode && mode.prev_mode { + warn!("Please specify either next or previous"); + return Ok(()); + } + let aura = find_iface_blocking::("xyz.ljones.Aura")?; + if mode.next_mode { + for aura in aura { + let mode = aura.led_mode()?; + let modes = aura.supported_basic_modes()?; + if let Some(pos) = modes.iter().position(|m| *m == mode) { + let next_pos = if pos + 1 >= modes.len() { 0 } else { pos + 1 }; + if let Some(&target_mode) = modes.get(next_pos) { + aura.set_led_mode(target_mode)?; + } + } else if let Some(&first) = modes.first() { + aura.set_led_mode(first)?; + } + } + } else if mode.prev_mode { + for aura in aura { + let mode = aura.led_mode()?; + let modes = aura.supported_basic_modes()?; + if let Some(pos) = modes.iter().position(|m| *m == mode) { + let prev_pos = if pos == 0 { + modes.len().saturating_sub(1) + } else { + pos - 1 + }; + if let Some(&target_mode) = modes.get(prev_pos) { + aura.set_led_mode(target_mode)?; + } + } else if let Some(&last) = modes.last() { + aura.set_led_mode(last)?; + } + } + } else if let Some(mode) = mode.command.as_ref() { + for aura in aura { + aura.set_led_mode_data(::from(mode))?; + } + } + + Ok(()) +} + +pub fn handle_led_power1(power: &LedPowerCommand1) -> Result<(), Box> { + if power.awake.is_none() + && power.sleep.is_none() + && power.boot.is_none() + && !power.keyboard + && !power.lightbar + { + warn!("Missing arg or command; run 'asusctl aura power-tuf --help' for usage"); + return Ok(()); + } + + let aura = find_iface_blocking::("xyz.ljones.Aura")?; + for aura in aura { + let dev_type = aura.device_type()?; + if !dev_type.is_old_laptop() && !dev_type.is_tuf_laptop() { + warn!("This option applies only to keyboards 2021+"); + } + + if dev_type.is_old_laptop() || dev_type.is_tuf_laptop() { + handle_led_power_1_do_1866(&aura, power)?; + return Ok(()); + } + } + + warn!("These options are for keyboards of product ID 0x1866 or TUF only"); + Ok(()) +} + +fn handle_led_power_1_do_1866( + aura: &AuraProxyBlocking, + power: &LedPowerCommand1, +) -> Result<(), Box> { + let mut states = Vec::new(); + if power.keyboard { + states.push(AuraPowerState { + zone: PowerZones::Keyboard, + boot: power.boot.unwrap_or_default(), + awake: power.awake.unwrap_or_default(), + sleep: power.sleep.unwrap_or_default(), + shutdown: false, + }); + } + if power.lightbar { + states.push(AuraPowerState { + zone: PowerZones::Lightbar, + boot: power.boot.unwrap_or_default(), + awake: power.awake.unwrap_or_default(), + sleep: power.sleep.unwrap_or_default(), + shutdown: false, + }); + } + + let states = LaptopAuraPower { states }; + aura.set_led_power(states)?; + Ok(()) +} + +pub fn handle_led_power2(power: &LedPowerCommand2) -> Result<(), Box> { + let aura = find_iface_blocking::("xyz.ljones.Aura")?; + for aura in aura { + let dev_type = aura.device_type()?; + if !dev_type.is_new_laptop() { + warn!("This option applies only to keyboards 2021+"); + continue; + } + + let Some(cmd) = &power.command else { + warn!("Missing arg or command; run 'asusctl aura power --help' for usage"); + println!("Commands available"); + return Ok(()); + }; + + let mut states = aura.led_power()?; + let mut set = + |zone: PowerZones, boot_v: bool, awake_v: bool, sleep_v: bool, shutdown_v: bool| { + if let Some(state) = states.states.iter_mut().find(|s| s.zone == zone) { + state.boot = boot_v; + state.awake = awake_v; + state.sleep = sleep_v; + state.shutdown = shutdown_v; + } else { + warn!("Zone {zone:?} is not supported by this device"); + } + }; + + match cmd { + crate::aura_cli::SetAuraZoneEnabled::Keyboard(k) => { + set(PowerZones::Keyboard, k.boot, k.awake, k.sleep, k.shutdown) + } + crate::aura_cli::SetAuraZoneEnabled::Logo(l) => { + set(PowerZones::Logo, l.boot, l.awake, l.sleep, l.shutdown) + } + crate::aura_cli::SetAuraZoneEnabled::Lightbar(l) => { + set(PowerZones::Lightbar, l.boot, l.awake, l.sleep, l.shutdown) + } + crate::aura_cli::SetAuraZoneEnabled::Lid(l) => { + set(PowerZones::Lid, l.boot, l.awake, l.sleep, l.shutdown) + } + crate::aura_cli::SetAuraZoneEnabled::RearGlow(r) => { + set(PowerZones::RearGlow, r.boot, r.awake, r.sleep, r.shutdown) + } + crate::aura_cli::SetAuraZoneEnabled::Ally(r) => { + set(PowerZones::Ally, r.boot, r.awake, r.sleep, r.shutdown) + } + } + + aura.set_led_power(states)?; + } + + Ok(()) +} + +pub fn handle_throttle_profile( + conn: &Connection, + supported: &[Properties], + cmd: &ProfileCommand, +) -> Result<(), Box> { + if !supported.contains(&Properties::ThrottlePolicy) { + warn!("Profiles not supported by either this kernel or by the laptop."); + return Err(ProfileError::NotSupported.into()); + } + + let proxy = PlatformProxyBlocking::new(conn)?; + let current = proxy.platform_profile()?; + let choices = proxy.platform_profile_choices()?; + + match &cmd.command { + crate::cli_opts::ProfileSubCommand::Next(_) => { + proxy.set_platform_profile(PlatformProfile::next(current, &choices))?; + } + crate::cli_opts::ProfileSubCommand::Set(s) => { + if !s.ac && !s.battery { + proxy.set_platform_profile(s.profile)?; + } else { + if s.ac { + proxy.set_platform_profile_on_ac(s.profile)?; + } + if s.battery { + proxy.set_platform_profile_on_battery(s.profile)?; + } + } + } + crate::cli_opts::ProfileSubCommand::List(_) => { + for p in &choices { + println!("{:?}", p); + } + } + crate::cli_opts::ProfileSubCommand::Get(_) => { + println!("Active profile: {current:?}"); + println!(); + println!("AC profile {:?}", proxy.platform_profile_on_ac()?); + println!("Battery profile {:?}", proxy.platform_profile_on_battery()?); + } + } + + Ok(()) +} + +pub fn print_firmware_attr( + attr: &AsusArmouryProxyBlocking, +) -> Result<(), Box> { + let name = attr.name()?; + println!("{}:", <&str>::from(name)); + + let attrs = attr.available_attrs().unwrap_or_default(); + + let has_min = attrs.contains(&"min_value".to_string()); + let has_max = attrs.contains(&"max_value".to_string()); + let has_current = attrs.contains(&"current_value".to_string()); + let has_possible = attrs.contains(&"possible_values".to_string()); + let has_default = attrs.contains(&"default_value".to_string()); + + if has_current && (has_min || has_max) { + let c = attr.current_value().ok(); + let min = if has_min { attr.min_value().ok() } else { None }; + let max = if has_max { attr.max_value().ok() } else { None }; + match (min, c, max) { + (Some(min), Some(c), Some(max)) => println!(" current: {min}..[{c}]..{max}"), + (Some(min), Some(c), None) => println!(" current: {min}..[{c}]"), + (None, Some(c), Some(max)) => println!(" current: [{c}]..{max}"), + _ => println!(" current: unavailable"), + } + + if has_default { + match attr.default_value().ok() { + Some(d) => println!(" default: {}\n", d), + None => println!(" default: unavailable\n"), + } + } else { + println!(); + } + } else if has_possible && has_current { + let c = attr.current_value().ok(); + let v = attr.possible_values().ok(); + if let (Some(c), Some(v)) = (c, v) { + for p in v.iter().enumerate() { + if p.0 == 0 { + print!(" current: ["); + } + if *p.1 == c { + print!("({c})"); + } else { + print!("{}", p.1); + } + if p.0 < v.len() - 1 { + print!(","); + } + if p.0 == v.len() - 1 { + print!("]"); + } + } + if has_default { + match attr.default_value().ok() { + Some(d) => println!(" default: {}\n", d), + None => println!(" default: unavailable\n"), + } + } else { + println!("\n"); + } + } else { + println!(" current: unavailable\n"); + } + } else if has_current { + match attr.current_value().ok() { + Some(c) => println!(" current: {c}\n"), + None => println!(" current: unavailable\n"), + } + } else { + println!(" unavailable\n"); + } + + Ok(()) +} + +pub fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box> { + match &cmd.command { + ArmourySubCommand::List(_) => { + let attrs = find_iface_blocking::("xyz.ljones.AsusArmoury") + .map_err(|e| format!("Could not reach asusd armoury interface: {e}"))?; + for attr in attrs.iter() { + print_firmware_attr(attr)?; + } + Ok(()) + } + ArmourySubCommand::Get(g) => { + let mut found = false; + let attrs = find_iface_blocking::("xyz.ljones.AsusArmoury") + .map_err(|e| format!("Could not reach asusd armoury interface: {e}"))?; + for attr in attrs.iter() { + let name = attr.name()?; + if <&str>::from(name) == g.property { + print_firmware_attr(attr)?; + found = true; + } + } + if !found { + return Err(format!("Firmware attribute '{}' not found", g.property).into()); + } + Ok(()) + } + ArmourySubCommand::Set(s) => { + let mut found = false; + let attrs = find_iface_blocking::("xyz.ljones.AsusArmoury") + .map_err(|e| format!("Could not reach asusd armoury interface: {e}"))?; + for attr in attrs.iter() { + let name = attr.name()?; + if <&str>::from(name) == s.property { + let mut value: i32 = s.value; + if value == -1 { + info!("Setting to default"); + value = attr.default_value()?; + } + attr.set_current_value(value)?; + print_firmware_attr(attr)?; + found = true; + } + } + if !found { + return Err(format!("Firmware attribute '{}' not found", s.property).into()); + } + Ok(()) + } + } +} diff --git a/asusctl/src/scsi_cli.rs b/asusctl/src/scsi_cli.rs index c4ae36614..b8ac72bc8 100644 --- a/asusctl/src/scsi_cli.rs +++ b/asusctl/src/scsi_cli.rs @@ -1,5 +1,7 @@ use argh::FromArgs; -use rog_scsi::{AuraMode, Colour, Direction, Speed}; +use log::warn; +use rog_dbus::find_iface_blocking; +use rog_platform::scsi::{AuraMode, Colour, Direction, Speed}; #[derive(FromArgs, Debug)] #[argh(subcommand, name = "scsi", description = "scsi LED commands")] @@ -28,3 +30,72 @@ pub struct ScsiCommand { #[argh(switch, description = "list available animations")] pub list: bool, } + +pub fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box> { + if !cmd.list + && cmd.enable.is_none() + && cmd.mode.is_none() + && cmd.speed.is_none() + && cmd.direction.is_none() + && cmd.colours.is_empty() + { + warn!("Missing arg or command; run 'asusctl scsi --help' for usage"); + return Ok(()); + } + + let scsis = + find_iface_blocking::("xyz.ljones.ScsiAura")?; + + for scsi in scsis { + if let Some(enable) = cmd.enable { + scsi.set_enabled(enable)?; + } + + if let Some(mode) = cmd.mode { + scsi.set_led_mode(mode)?; + } + + let mut mode = scsi.led_mode_data()?; + let mut do_update = false; + if !cmd.colours.is_empty() { + if cmd.colours.len() > 4 { + warn!("Only the first 4 colours are used; ignoring the rest"); + } + for (count, c) in cmd.colours.iter().enumerate() { + match count { + 0 => mode.colour1 = *c, + 1 => mode.colour2 = *c, + 2 => mode.colour3 = *c, + 3 => mode.colour4 = *c, + _ => break, + } + } + do_update = true; + } + + if let Some(speed) = cmd.speed { + mode.speed = speed; + do_update = true; + } + + if let Some(dir) = cmd.direction { + mode.direction = dir; + do_update = true; + } + + if do_update { + scsi.set_led_mode_data(mode.clone())?; + } + + println!("{mode}"); + } + + if cmd.list { + let res = AuraMode::list(); + for p in &res { + println!("{:?}", p); + } + } + + Ok(()) +} diff --git a/asusctl/src/slash_cli.rs b/asusctl/src/slash_cli.rs index cbc6e30a7..020042350 100644 --- a/asusctl/src/slash_cli.rs +++ b/asusctl/src/slash_cli.rs @@ -1,6 +1,7 @@ use argh::FromArgs; +use log::warn; use rog_dbus::zbus_slash::SlashProxyBlocking; -use rog_slash::SlashMode; +use rog_platform::slash::SlashMode; use zbus::blocking::Connection; #[derive(FromArgs, Debug)] @@ -78,7 +79,7 @@ pub fn handle_slash_set(cmd: &SlashSetCommand) -> Result<(), Box Result<(), Box> { "Slash LED: {}", if enabled { "enabled" } else { "disabled" } ); - println!("Brightness: {}", brightness); - println!("Interval: {}", interval); - println!("Mode: {}", mode); - println!("Show on boot: {}", show_on_boot); - println!("Show on shutdown: {}", show_on_shutdown); - println!("Show on sleep: {}", show_on_sleep); - println!("Show on battery: {}", show_on_battery); - println!("Show battery warning: {}", show_battery_warning); + println!("Brightness: {brightness}"); + println!("Interval: {interval}"); + println!("Mode: {mode}"); + println!("Show on boot: {show_on_boot}"); + println!("Show on shutdown: {show_on_shutdown}"); + println!("Show on sleep: {show_on_sleep}"); + println!("Show on battery: {show_on_battery}"); + println!("Show battery warning: {show_battery_warning}"); Ok(()) } diff --git a/asusd/Cargo.toml b/asusd/Cargo.toml index c9de52c2b..cc93978fd 100644 --- a/asusd/Cargo.toml +++ b/asusd/Cargo.toml @@ -16,12 +16,9 @@ path = "src/daemon.rs" [dependencies] config-traits = { path = "../config-traits" } rog_anime = { path = "../rog-anime", features = ["dbus"] } -rog_slash = { path = "../rog-slash", features = ["dbus"] } rog_aura = { path = "../rog-aura", features = ["dbus"] } -rog_scsi = { path = "../rog-scsi", features = ["dbus"] } -rog_platform = { path = "../rog-platform" } +rog_platform = { path = "../rog-platform", features = ["dbus"] } rog_profiles = { path = "../rog-profiles" } -dmi_id = { path = "../dmi-id" } udev.workspace = true inotify.workspace = true diff --git a/asusd/src/aura_anime/trait_impls.rs b/asusd/src/aura_anime/trait_impls.rs index 396ab6f97..d26b4b05f 100644 --- a/asusd/src/aura_anime/trait_impls.rs +++ b/asusd/src/aura_anime/trait_impls.rs @@ -316,28 +316,18 @@ impl crate::CtrlTask for AniMeZbus { let inner2 = self.0.clone(); let inner3 = self.0.clone(); let inner4 = self.0.clone(); - self.create_sys_event_tasks( - move |sleeping| { - // on_sleep - let inner = inner1.clone(); - async move { - let config = inner.config.lock().await.clone(); - if config.display_enabled { - inner.thread_exit.store(true, Ordering::Release); // ensure clean slate - - inner - .write_bytes(&pkt_set_enable_display( - !(sleeping && config.off_when_suspended), - )) - .await - .map_err(|err| { - warn!("create_sys_event_tasks::off_when_suspended {}", err); - }) - .ok(); - - if config.builtin_anims_enabled { + let tasks = self + .create_sys_event_tasks( + move |sleeping| { + // on_sleep + let inner = inner1.clone(); + async move { + let config = inner.config.lock().await.clone(); + if config.display_enabled { + inner.thread_exit.store(true, Ordering::Release); // ensure clean slate + inner - .write_bytes(&pkt_set_enable_powersave_anim( + .write_bytes(&pkt_set_enable_display( !(sleeping && config.off_when_suspended), )) .await @@ -345,105 +335,118 @@ impl crate::CtrlTask for AniMeZbus { warn!("create_sys_event_tasks::off_when_suspended {}", err); }) .ok(); - } else if !sleeping && !config.builtin_anims_enabled { - // Run custom wake animation - inner - .write_bytes(&pkt_set_enable_powersave_anim(false)) - .await - .ok(); // ensure builtins are disabled - inner.run_thread(inner.cache.wake.clone(), true).await; + if config.builtin_anims_enabled { + inner + .write_bytes(&pkt_set_enable_powersave_anim( + !(sleeping && config.off_when_suspended), + )) + .await + .map_err(|err| { + warn!("create_sys_event_tasks::off_when_suspended {}", err); + }) + .ok(); + } else if !sleeping && !config.builtin_anims_enabled { + // Run custom wake animation + inner + .write_bytes(&pkt_set_enable_powersave_anim(false)) + .await + .ok(); // ensure builtins are disabled + + inner.run_thread(inner.cache.wake.clone(), true).await; + } } } - } - }, - move |shutting_down| { - // on_shutdown - let inner = inner2.clone(); - async move { - let AniMeConfig { - display_enabled, - builtin_anims_enabled, - .. - } = *inner.config.lock().await; - if display_enabled && !builtin_anims_enabled { - if shutting_down { - inner.run_thread(inner.cache.shutdown.clone(), true).await; - } else { - inner.run_thread(inner.cache.boot.clone(), true).await; + }, + move |shutting_down| { + // on_shutdown + let inner = inner2.clone(); + async move { + let AniMeConfig { + display_enabled, + builtin_anims_enabled, + .. + } = *inner.config.lock().await; + if display_enabled && !builtin_anims_enabled { + if shutting_down { + inner.run_thread(inner.cache.shutdown.clone(), true).await; + } else { + inner.run_thread(inner.cache.boot.clone(), true).await; + } } } - } - }, - move |lid_closed| { - let inner = inner3.clone(); - // on lid change - async move { - let AniMeConfig { - off_when_lid_closed, - builtin_anims_enabled, - .. - } = *inner.config.lock().await; - if off_when_lid_closed { - if builtin_anims_enabled { + }, + move |lid_closed| { + let inner = inner3.clone(); + // on lid change + async move { + let AniMeConfig { + off_when_lid_closed, + builtin_anims_enabled, + .. + } = *inner.config.lock().await; + if off_when_lid_closed { + if builtin_anims_enabled { + inner + .write_bytes(&pkt_set_enable_powersave_anim(!lid_closed)) + .await + .map_err(|err| { + warn!("create_sys_event_tasks::off_when_suspended {}", err); + }) + .ok(); + } inner - .write_bytes(&pkt_set_enable_powersave_anim(!lid_closed)) + .write_bytes(&pkt_set_enable_display(!lid_closed)) .await .map_err(|err| { - warn!("create_sys_event_tasks::off_when_suspended {}", err); + warn!("create_sys_event_tasks::off_when_lid_closed {}", err); }) .ok(); } - inner - .write_bytes(&pkt_set_enable_display(!lid_closed)) - .await - .map_err(|err| { - warn!("create_sys_event_tasks::off_when_lid_closed {}", err); - }) - .ok(); } - } - }, - move |power_plugged| { - let inner = inner4.clone(); - // on power change - async move { - let AniMeConfig { - off_when_unplugged, - builtin_anims_enabled, - brightness_on_battery, - .. - } = *inner.config.lock().await; - if off_when_unplugged { - if builtin_anims_enabled { + }, + move |power_plugged| { + let inner = inner4.clone(); + // on power change + async move { + let AniMeConfig { + off_when_unplugged, + builtin_anims_enabled, + brightness_on_battery, + .. + } = *inner.config.lock().await; + if off_when_unplugged { + if builtin_anims_enabled { + inner + .write_bytes(&pkt_set_enable_powersave_anim(power_plugged)) + .await + .map_err(|err| { + warn!("create_sys_event_tasks::off_when_suspended {}", err); + }) + .ok(); + } inner - .write_bytes(&pkt_set_enable_powersave_anim(power_plugged)) + .write_bytes(&pkt_set_enable_display(power_plugged)) .await .map_err(|err| { - warn!("create_sys_event_tasks::off_when_suspended {}", err); + warn!("create_sys_event_tasks::off_when_unplugged {}", err); + }) + .ok(); + } else { + inner + .write_bytes(&pkt_set_brightness(brightness_on_battery)) + .await + .map_err(|err| { + warn!("create_sys_event_tasks::off_when_unplugged {}", err); }) .ok(); } - inner - .write_bytes(&pkt_set_enable_display(power_plugged)) - .await - .map_err(|err| { - warn!("create_sys_event_tasks::off_when_unplugged {}", err); - }) - .ok(); - } else { - inner - .write_bytes(&pkt_set_brightness(brightness_on_battery)) - .await - .map_err(|err| { - warn!("create_sys_event_tasks::off_when_unplugged {}", err); - }) - .ok(); } - } - }, - ) - .await; + }, + ) + .await?; + + Self::spawn_task_supervisor("AniMeZbus", tasks); Ok(()) } diff --git a/asusd/src/aura_laptop/trait_impls.rs b/asusd/src/aura_laptop/trait_impls.rs index 8a4daa664..e2635dfc2 100644 --- a/asusd/src/aura_laptop/trait_impls.rs +++ b/asusd/src/aura_laptop/trait_impls.rs @@ -239,73 +239,60 @@ impl CtrlTask for AuraZbus { async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> { let inner1 = self.0.clone(); let inner3 = self.0.clone(); - self.create_sys_event_tasks( - move |sleeping| { - let inner1 = inner1.clone(); - // unwrap as we want to bomb out of the task - async move { - if !sleeping { + let tasks = self + .create_sys_event_tasks( + move |sleeping| { + let inner1 = inner1.clone(); + async move { + if !sleeping { + info!("CtrlKbdLedTask reloading brightness and modes"); + if let Some(backlight) = &inner1.backlight { + if let Err(e) = backlight + .lock() + .await + .set_brightness(inner1.config.lock().await.brightness.into()) + { + error!("CtrlKbdLedTask brightness error: {e}"); + } + } + let mut config = inner1.config.lock().await; + if let Err(e) = inner1.write_current_config_mode(&mut config).await { + error!("CtrlKbdLedTask config mode error: {e}"); + } + } else { + if let Err(e) = inner1.update_config().await { + error!("CtrlKbdLedTask update config error: {e}"); + } + } + } + }, + move |_shutting_down| { + let inner3 = inner3.clone(); + async move { info!("CtrlKbdLedTask reloading brightness and modes"); - if let Some(backlight) = &inner1.backlight { - backlight + if let Some(backlight) = &inner3.backlight { + if let Err(e) = backlight .lock() .await - .set_brightness(inner1.config.lock().await.brightness.into()) - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); + .set_brightness(inner3.config.lock().await.brightness.into()) + { + error!("CtrlKbdLedTask brightness error: {e}"); + } } - let mut config = inner1.config.lock().await; - inner1 - .write_current_config_mode(&mut config) - .await - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); - } else if sleeping { - inner1 - .update_config() - .await - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); - } - } - }, - move |_shutting_down| { - let inner3 = inner3.clone(); - async move { - info!("CtrlKbdLedTask reloading brightness and modes"); - if let Some(backlight) = &inner3.backlight { - // unwrap as we want to bomb out of the task - backlight - .lock() - .await - .set_brightness(inner3.config.lock().await.brightness.into()) - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); } - } - }, - move |_lid_closed| { - // on lid change - async move {} - }, - move |_power_plugged| { - // power change - async move {} - }, - ) - .await; + }, + move |_lid_closed| { + // on lid change + async move {} + }, + move |_power_plugged| { + // power change + async move {} + }, + ) + .await?; + + Self::spawn_task_supervisor("AuraZbus", tasks); // let ctrl2 = self.0.clone(); // let ctrl = self.0.lock().await; diff --git a/asusd/src/aura_manager.rs b/asusd/src/aura_manager.rs index d7c83d939..bb71802af 100644 --- a/asusd/src/aura_manager.rs +++ b/asusd/src/aura_manager.rs @@ -7,11 +7,11 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use dmi_id::DMIID; use log::{debug, error, info, warn}; use mio::{Events, Interest, Poll, Token}; use rog_platform::error::PlatformError; use rog_platform::hid_raw::HidRaw; +use rog_platform::DMIID; use tokio::sync::Mutex; use udev::{Device, MonitorBuilder}; use zbus::zvariant::{ObjectPath, OwnedObjectPath}; @@ -27,6 +27,25 @@ use crate::ASUS_ZBUS_PATH; const MOD_NAME: &str = "aura"; +fn register_device_zbus( + devices: &mut Vec, + dev_type: DeviceHandle, + path: OwnedObjectPath, + start_result: Result<(), RogError>, + dev_name: &str, +) { + if start_result + .map_err(|e| error!("Failed to start {dev_name} tasks: {e:?}, not adding this device")) + .is_ok() + { + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + hid_key: None, + }); + } +} + /// Returns only the Device details concatenated in a form usable for /// adding/appending to a filename pub fn filename_partial(parent: &Device) -> Option { @@ -385,21 +404,10 @@ impl DeviceManager { if let Ok(dev_type) = DeviceHandle::new_slash_usb().await { if let DeviceHandle::Slash(slash) = dev_type.clone() { let path = dbus_path_for_slash(); - let ctrl = SlashZbus::new(slash); - if ctrl + let res = SlashZbus::new(slash) .start_tasks(connection, path.clone()) - .await - .map_err(|e| { - error!("Failed to start Slash tasks: {e:?}, not adding this device") - }) - .is_ok() - { - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: None, - }); - } + .await; + register_device_zbus(&mut devices, dev_type, path, res, "Slash"); } } else { info!("Tested device was not Slash"); @@ -408,22 +416,12 @@ impl DeviceManager { if do_anime { if let Ok(dev_type) = DeviceHandle::maybe_anime_usb().await { - // TODO: this is copy/pasted if let DeviceHandle::AniMe(anime) = dev_type.clone() { let path = dbus_path_for_anime(); - let ctrl = AniMeZbus::new(anime); - if ctrl + let res = AniMeZbus::new(anime) .start_tasks(connection, path.clone()) - .await - .map_err(|e| error!("Failed to start tasks: {e:?}, not adding this device")) - .is_ok() - { - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: None, - }); - } + .await; + register_device_zbus(&mut devices, dev_type, path, res, "AniMe Matrix"); } } else { info!("Tested device was not AniMe Matrix"); @@ -444,23 +442,10 @@ impl DeviceManager { if let Ok(dev_type) = DeviceHandle::maybe_laptop_aura(None, "tuf").await { if let DeviceHandle::Aura(aura) = dev_type.clone() { let path = dbus_path_for_tuf(); - let ctrl = AuraZbus::new(aura); - if ctrl + let res = AuraZbus::new(aura) .start_tasks(connection, path.clone()) - .await - .map_err(|e| { - error!( - "Failed to start TUF Aura tasks: {e:?}, not adding this device" - ) - }) - .is_ok() - { - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: None, - }); - } + .await; + register_device_zbus(&mut devices, dev_type, path, res, "TUF Aura"); } } } diff --git a/asusd/src/aura_scsi/config.rs b/asusd/src/aura_scsi/config.rs index 5cf775724..236bac975 100644 --- a/asusd/src/aura_scsi/config.rs +++ b/asusd/src/aura_scsi/config.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use config_traits::{StdConfig, StdConfigLoad}; use rog_aura::AuraDeviceType; -use rog_scsi::{AuraEffect, AuraMode}; +use rog_platform::scsi::{AuraEffect, AuraMode}; use serde::{Deserialize, Serialize}; const CONFIG_FILE: &str = "scsi.ron"; diff --git a/asusd/src/aura_scsi/mod.rs b/asusd/src/aura_scsi/mod.rs index 69e7c7f90..dbfcc86c0 100644 --- a/asusd/src/aura_scsi/mod.rs +++ b/asusd/src/aura_scsi/mod.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use config::ScsiConfig; -use rog_scsi::{AuraEffect, Device, Task}; +use rog_platform::scsi::{AuraEffect, Device, Task}; use tokio::sync::{Mutex, MutexGuard}; use crate::error::RogError; diff --git a/asusd/src/aura_scsi/trait_impls.rs b/asusd/src/aura_scsi/trait_impls.rs index 7345dc28b..eae07bc98 100644 --- a/asusd/src/aura_scsi/trait_impls.rs +++ b/asusd/src/aura_scsi/trait_impls.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use config_traits::StdConfig; use log::error; use rog_aura::AuraDeviceType; -use rog_scsi::{AuraEffect, AuraMode}; +use rog_platform::scsi::{AuraEffect, AuraMode}; use zbus::fdo::Error as ZbErr; use zbus::zvariant::OwnedObjectPath; use zbus::{interface, Connection}; diff --git a/asusd/src/aura_slash/config.rs b/asusd/src/aura_slash/config.rs index 545dc630a..c044749f6 100644 --- a/asusd/src/aura_slash/config.rs +++ b/asusd/src/aura_slash/config.rs @@ -1,5 +1,5 @@ use config_traits::{StdConfig, StdConfigLoad}; -use rog_slash::{DeviceState, SlashMode, SlashType}; +use rog_platform::slash::{DeviceState, SlashMode, SlashType}; use serde::{Deserialize, Serialize}; const CONFIG_FILE: &str = "slash.ron"; diff --git a/asusd/src/aura_slash/mod.rs b/asusd/src/aura_slash/mod.rs index 8996f59e7..2dbb35434 100644 --- a/asusd/src/aura_slash/mod.rs +++ b/asusd/src/aura_slash/mod.rs @@ -2,8 +2,10 @@ use std::sync::Arc; use config::SlashConfig; use rog_platform::hid_raw::HidRaw; +use rog_platform::slash::usb::{ + slash_pkt_enable, slash_pkt_init, slash_pkt_options, slash_pkt_set_mode, +}; use rog_platform::usb_raw::USBRaw; -use rog_slash::usb::{slash_pkt_enable, slash_pkt_init, slash_pkt_options, slash_pkt_set_mode}; use tokio::sync::{Mutex, MutexGuard}; use crate::error::RogError; diff --git a/asusd/src/aura_slash/trait_impls.rs b/asusd/src/aura_slash/trait_impls.rs index f154597a7..3a93381a2 100644 --- a/asusd/src/aura_slash/trait_impls.rs +++ b/asusd/src/aura_slash/trait_impls.rs @@ -1,11 +1,11 @@ use config_traits::StdConfig; use log::{debug, error, warn}; -use rog_slash::usb::{ +use rog_platform::slash::usb::{ slash_pkt_battery_saver, slash_pkt_boot, slash_pkt_enable, slash_pkt_lid_closed, slash_pkt_low_battery, slash_pkt_options, slash_pkt_save, slash_pkt_set_mode, slash_pkt_shutdown, slash_pkt_sleep, }; -use rog_slash::{DeviceState, SlashMode}; +use rog_platform::slash::{DeviceState, SlashMode}; use zbus::zvariant::OwnedObjectPath; use zbus::{interface, Connection}; diff --git a/asusd/src/aura_types.rs b/asusd/src/aura_types.rs index 6c255cfd3..7e56df88a 100644 --- a/asusd/src/aura_types.rs +++ b/asusd/src/aura_types.rs @@ -8,10 +8,9 @@ use rog_anime::AnimeType; use rog_aura::AuraDeviceType; use rog_platform::hid_raw::HidRaw; use rog_platform::keyboard_led::KeyboardBacklight; +use rog_platform::scsi::{open_device, ScsiType}; +use rog_platform::slash::{SlashError, SlashType}; use rog_platform::usb_raw::USBRaw; -use rog_scsi::{open_device, ScsiType}; -use rog_slash::error::SlashError; -use rog_slash::SlashType; use tokio::sync::Mutex; use crate::aura_anime::config::AniMeConfig; @@ -82,7 +81,7 @@ impl DeviceHandle { debug!("Testing for USB Slash"); let slash_type = SlashType::from_dmi(); if matches!(slash_type, SlashType::Unsupported) { - return Err(RogError::Slash(SlashError::NoDevice)); + return Err(RogError::Slash(SlashError::NoSlashDevice)); } if let Ok(usb) = USBRaw::new(slash_type.prod_id()) { diff --git a/asusd/src/ctrl_backlight.rs b/asusd/src/ctrl_backlight.rs index bdf366449..d1da6e598 100644 --- a/asusd/src/ctrl_backlight.rs +++ b/asusd/src/ctrl_backlight.rs @@ -150,14 +150,16 @@ impl CtrlBacklight { } } - pub async fn start_watch_primary(&self) -> Result<(), RogError> { + pub async fn start_watch_primary( + &self, + ) -> Result>, RogError> { if self.get_backlight(&BacklightType::Screenpad).is_none() { - return Ok(()); + return Ok(None); } if let Some(sync) = self.config.lock().await.screenpad_sync_primary { if !sync { - return Ok(()); + return Ok(None); } } @@ -165,7 +167,7 @@ impl CtrlBacklight { let watch = backlight.monitor_brightness()?; let backlights = self.clone(); - tokio::spawn(async move { + let handle = tokio::spawn(async move { let mut last_level = 0; let mut buffer = [0; 32]; use futures_util::StreamExt; @@ -203,16 +205,12 @@ impl CtrlBacklight { // other processes cause "MODIFY" event and make this spin 100%, so sleep tokio::time::sleep(Duration::from_millis(300)).await; } - // watch - // .into_event_stream(&mut buffer) - // .unwrap() - // .for_each(|_| async {}) - // .await; } }); + return Ok(Some(handle)); } - Ok(()) + Ok(None) } } diff --git a/asusd/src/ctrl_platform.rs b/asusd/src/ctrl_platform.rs index 3a9f2e24a..5a7982a8a 100644 --- a/asusd/src/ctrl_platform.rs +++ b/asusd/src/ctrl_platform.rs @@ -926,158 +926,168 @@ impl CtrlTask for CtrlPlatform { let platform2 = self.clone(); let platform3 = self.clone(); let signal_ctxt_copy = signal_ctxt.clone(); - self.create_sys_event_tasks( - move |sleeping| { - let platform1 = platform1.clone(); - async move { - // This block is commented out due to some kind of issue reported. Maybe the - // desktops used were storing a value whcih was then read here. - // Don't store it on suspend, assume that the current config setting is desired - // if sleeping && platform1.power.has_charge_control_end_threshold() { - // platform1.config.lock().await.charge_control_end_threshold = platform1 - // .power - // .get_charge_control_end_threshold() - // .unwrap_or(100); - // } else - if !sleeping && platform1.power.has_charge_control_end_threshold() { - platform1 - .power - .set_charge_control_end_threshold( - platform1.config.lock().await.charge_control_end_threshold, - ) - .ok(); - } - if let Ok(power_plugged) = platform1.power.get_online() { - if platform1.config.lock().await.last_power_plugged != power_plugged { - if !sleeping && platform1.platform.has_platform_profile() { - let change_epp = - platform1.config.lock().await.platform_profile_linked_epp; - platform1 - .update_policy_ac_or_bat(power_plugged > 0, change_epp) - .await; - } - if !sleeping { - platform1.run_ac_or_bat_cmd(power_plugged > 0).await; - if let Ok(profile) = - platform1.platform.get_platform_profile().map(|p| p.into()) - { - let attrs = FirmwareAttributes::new(); + let mut tasks = self + .create_sys_event_tasks( + move |sleeping| { + let platform1 = platform1.clone(); + async move { + // This block is commented out due to some kind of issue reported. Maybe the + // desktops used were storing a value whcih was then read here. + // Don't store it on suspend, assume that the current config setting is desired + // if sleeping && platform1.power.has_charge_control_end_threshold() { + // platform1.config.lock().await.charge_control_end_threshold = platform1 + // .power + // .get_charge_control_end_threshold() + // .unwrap_or(100); + // } else + if !sleeping && platform1.power.has_charge_control_end_threshold() { + platform1 + .power + .set_charge_control_end_threshold( + platform1.config.lock().await.charge_control_end_threshold, + ) + .map_err(|err| { + warn!("CtrlPlatform: failed to restore charge_control_end_threshold: {err}"); + }) + .ok(); + } + if let Ok(power_plugged) = platform1.power.get_online() { + if platform1.config.lock().await.last_power_plugged != power_plugged { + if !sleeping && platform1.platform.has_platform_profile() { + let change_epp = + platform1.config.lock().await.platform_profile_linked_epp; platform1 - .apply_fan_curves_and_ppt( - &attrs, - power_plugged > 0, - profile, - ) + .update_policy_ac_or_bat(power_plugged > 0, change_epp) .await; - if let Err(e) = platform1 - .armoury_registry - .emit_limits(&platform1.connection) - .await + } + if !sleeping { + platform1.run_ac_or_bat_cmd(power_plugged > 0).await; + if let Ok(profile) = + platform1.platform.get_platform_profile().map(|p| p.into()) { - error!( + let attrs = FirmwareAttributes::new(); + platform1 + .apply_fan_curves_and_ppt( + &attrs, + power_plugged > 0, + profile, + ) + .await; + if let Err(e) = platform1 + .armoury_registry + .emit_limits(&platform1.connection) + .await + { + error!( "Failed to emit armoury updates after power change: \ {e:?}" ); + } } } + platform1.config.lock().await.last_power_plugged = power_plugged; } - platform1.config.lock().await.last_power_plugged = power_plugged; } } - } - }, - move |shutting_down| { - let platform2 = platform2.clone(); - async move { - info!("RogPlatform reloading panel_od"); - let lock = platform2.config.lock().await; - if shutting_down - && platform2.power.has_charge_control_end_threshold() - && lock.base_charge_control_end_threshold > 0 - { - info!("RogPlatform restoring charge_control_end_threshold"); - platform2 - .power - .set_charge_control_end_threshold( - lock.base_charge_control_end_threshold, - ) - .map_err(|err| { - warn!("CtrlCharge: charge_control_end_threshold {}", err); - err - }) - .ok(); - } - } - }, - move |_lid_closed| { - // on lid change - async move {} - }, - move |power_plugged| { - let platform3 = platform3.clone(); - let signal_ctxt_copy = signal_ctxt.clone(); - // power change - async move { - if platform3.platform.has_platform_profile() { - let change_epp = platform3.config.lock().await.platform_profile_linked_epp; - platform3 - .update_policy_ac_or_bat(power_plugged, change_epp) - .await; - } - platform3.run_ac_or_bat_cmd(power_plugged).await; - platform3.manage_nvidia_powerd(power_plugged).await; - // In case one-shot charge was used, restore the old charge limit - if platform3.power.has_charge_control_end_threshold() && !power_plugged { - platform3.restore_charge_limit().await; + }, + move |shutting_down| { + let platform2 = platform2.clone(); + async move { + info!("RogPlatform reloading panel_od"); + let lock = platform2.config.lock().await; + if shutting_down + && platform2.power.has_charge_control_end_threshold() + && lock.base_charge_control_end_threshold > 0 + { + info!("RogPlatform restoring charge_control_end_threshold"); + platform2 + .power + .set_charge_control_end_threshold( + lock.base_charge_control_end_threshold, + ) + .map_err(|err| { + warn!("CtrlCharge: charge_control_end_threshold {}", err); + err + }) + .ok(); + } } + }, + move |_lid_closed| { + // on lid change + async move {} + }, + move |power_plugged| { + let platform3 = platform3.clone(); + let signal_ctxt_copy = signal_ctxt.clone(); + // power change + async move { + if platform3.platform.has_platform_profile() { + let change_epp = + platform3.config.lock().await.platform_profile_linked_epp; + platform3 + .update_policy_ac_or_bat(power_plugged, change_epp) + .await; + } + platform3.run_ac_or_bat_cmd(power_plugged).await; + platform3.manage_nvidia_powerd(power_plugged).await; + // In case one-shot charge was used, restore the old charge limit + if platform3.power.has_charge_control_end_threshold() && !power_plugged { + platform3.restore_charge_limit().await; + } - if let Ok(profile) = platform3 - .platform - .get_platform_profile() - .map(|p| p.into()) - .map_err(|e| { - error!("Platform: get_platform_profile error: {e}"); - }) - { - // TODO: manage this better, shouldn't need to create every time - let attrs = FirmwareAttributes::new(); - platform3 - .apply_fan_curves_and_ppt(&attrs, power_plugged, profile) - .await; - if let Err(e) = platform3 - .armoury_registry - .emit_limits(&platform3.connection) - .await + if let Ok(profile) = platform3 + .platform + .get_platform_profile() + .map(|p| p.into()) + .map_err(|e| { + error!("Platform: get_platform_profile error: {e}"); + }) { - error!("Failed to emit armoury updates after AC/DC toggle: {e:?}"); + // TODO: manage this better, shouldn't need to create every time + let attrs = FirmwareAttributes::new(); + platform3 + .apply_fan_curves_and_ppt(&attrs, power_plugged, profile) + .await; + if let Err(e) = platform3 + .armoury_registry + .emit_limits(&platform3.connection) + .await + { + error!("Failed to emit armoury updates after AC/DC toggle: {e:?}"); + } + platform3 + .enable_ppt_group_changed(&signal_ctxt_copy) + .await + .ok(); } - platform3 - .enable_ppt_group_changed(&signal_ctxt_copy) - .await - .ok(); } - } - }, - ) - .await; - - // This spawns a new task for every item. - // TODO: find a better way to manage this - self.watch_charge_control_end_threshold(signal_ctxt_copy.clone()) + }, + ) .await?; + if let Some(h) = self + .watch_charge_control_end_threshold(signal_ctxt_copy.clone()) + .await? + { + tasks.spawn(async move { + if let Err(err) = h.await { + warn!("charge_control_end_threshold watcher ended with error: {err:?}"); + } + }); + } + let watch_platform_profile = self.platform.monitor_platform_profile()?; let ctrl = self.clone(); // Need a copy here, not ideal. But first use in asus_armoury.rs is // moved to zbus let attrs = FirmwareAttributes::new(); - tokio::spawn(async move { + tasks.spawn(async move { use futures_util::StreamExt; let mut buffer = [0; 32]; if let Ok(mut stream) = watch_platform_profile.into_event_stream(&mut buffer) { while (stream.next().await).is_some() { - // this blocks debug!("Platform: watch_platform_profile changed"); if let Ok(profile) = ctrl .platform @@ -1110,6 +1120,8 @@ impl CtrlTask for CtrlPlatform { } }); + Self::spawn_task_supervisor("CtrlPlatform", tasks); + Ok(()) } } diff --git a/asusd/src/daemon.rs b/asusd/src/daemon.rs index 02f4025a0..079646e5a 100644 --- a/asusd/src/daemon.rs +++ b/asusd/src/daemon.rs @@ -21,8 +21,6 @@ use zbus::fdo::ObjectManager; #[tokio::main] async fn main() -> Result<(), Box> { - println!("Starting asusd daemon..."); - // console_subscriber::init(); let mut logger = env_logger::Builder::new(); logger @@ -32,23 +30,22 @@ async fn main() -> Result<(), Box> { .filter_level(log::LevelFilter::Debug) .init(); + info!("Starting asusd daemon..."); + let is_service = match env::var_os("IS_SERVICE") { Some(val) => val == "1", - None => true, + None => false, }; if !is_service { - println!("asusd schould be only run from the right systemd service"); - println!( - "do not run in your terminal, if you need an logs please use journalctl -b -u asusd" - ); - println!("asusd will now exit"); + warn!("asusd should only be run from the right systemd service"); + warn!("do not run in your terminal; if you need logs please use journalctl -b -u asusd"); + warn!("asusd will now exit"); return Ok(()); } info!(" daemon v{}", asusd::VERSION); info!(" rog-anime v{}", rog_anime::VERSION); - info!(" rog-slash v{}", rog_slash::VERSION); info!(" rog-aura v{}", rog_aura::VERSION); info!(" rog-profiles v{}", rog_profiles::VERSION); info!("rog-platform v{}", rog_platform::VERSION); @@ -59,9 +56,7 @@ async fn main() -> Result<(), Box> { /// The actual main loop for the daemon async fn start_daemon() -> Result<(), Box> { - // let supported = SupportedFunctions::get_supported(); print_board_info(); - // println!("{:?}", supported.supported_functions()); // Start zbus server let mut server = Connection::system().await?; @@ -117,7 +112,13 @@ async fn start_daemon() -> Result<(), Box> { match CtrlBacklight::new(config.clone()) { Ok(backlight) => { info!("Backlight: found supported backlight"); - backlight.start_watch_primary().await?; + if let Some(handle) = backlight.start_watch_primary().await? { + tokio::spawn(async move { + if let Err(err) = handle.await { + warn!("Backlight watcher task ended with error: {err:?}"); + } + }); + } backlight.add_to_server(&mut server).await; info!("Backlight: initialized"); } @@ -162,12 +163,12 @@ async fn start_daemon() -> Result<(), Box> { Err(e) => error!("XG Mobile LED: {e}"), } - // Request dbus name after finishing initalizing all functions + // Request dbus name after finishing initializing all functions server.request_name(DBUS_NAME).await?; - info!("Startup success on dbus name {DBUS_NAME}: begining dbus server loop"); + info!("Startup success on dbus name {DBUS_NAME}: beginning dbus server loop"); loop { - // This is just a blocker to idle and ensure the reator reacts + // This is just a blocker to idle and ensure the reactor reacts server.executor().tick().await; } } diff --git a/asusd/src/error.rs b/asusd/src/error.rs index e9cb8e734..c89a71432 100644 --- a/asusd/src/error.rs +++ b/asusd/src/error.rs @@ -1,8 +1,8 @@ use config_traits::ron; use rog_anime::error::AnimeError; use rog_platform::error::PlatformError; +use rog_platform::slash::SlashError; use rog_profiles::error::ProfileError; -use rog_slash::error::SlashError; #[derive(thiserror::Error, Debug)] pub enum RogError { @@ -109,12 +109,6 @@ impl From for RogError { } } -impl From for RogError { - fn from(err: SlashError) -> Self { - RogError::Slash(err) - } -} - impl From for RogError { fn from(err: PlatformError) -> Self { RogError::Platform(err) diff --git a/asusd/src/lib.rs b/asusd/src/lib.rs index 927cdf289..7489fcb39 100644 --- a/asusd/src/lib.rs +++ b/asusd/src/lib.rs @@ -20,10 +20,10 @@ pub mod error; use std::future::Future; use std::time::Duration; -use dmi_id::DMIID; use futures_util::stream::StreamExt; use log::{debug, info, warn}; use logind_zbus::manager::ManagerProxy; +use rog_platform::DMIID; use tokio::time::sleep; use zbus::object_server::{Interface, SignalEmitter}; use zbus::proxy::CacheProperties; @@ -68,14 +68,14 @@ macro_rules! task_watch_item { async fn fn_name( &self, signal_ctxt: SignalEmitter<'static>, - ) -> Result<(), RogError> { + ) -> Result>, RogError> { use futures_util::StreamExt; let ctrl = self.clone(); concat_idents::concat_idents!(watch_fn = monitor_, $name { match self.$self_inner.watch_fn() { Ok(watch) => { - tokio::spawn(async move { + let handle = tokio::spawn(async move { let mut buffer = [0; 32]; if let Ok(stream) = watch.into_event_stream(&mut buffer) { stream.for_each(|_| async { @@ -95,11 +95,14 @@ macro_rules! task_watch_item { log::error!("Failed to create event stream for {}", $name_str); } }); + Ok(Some(handle)) + } + Err(e) => { + info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e); + Ok(None) } - Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e), } - }); - Ok(()) + }) } }); }; @@ -112,14 +115,14 @@ macro_rules! task_watch_item_notify { async fn fn_name( &self, signal_ctxt: SignalEmitter<'static>, - ) -> Result<(), RogError> { + ) -> Result>, RogError> { use futures_util::StreamExt; let ctrl = self.clone(); concat_idents::concat_idents!(watch_fn = monitor_, $name { match self.$self_inner.watch_fn() { Ok(watch) => { - tokio::spawn(async move { + let handle = tokio::spawn(async move { let mut buffer = [0; 32]; if let Ok(stream) = watch.into_event_stream(&mut buffer) { stream.for_each(|_| async { @@ -129,11 +132,14 @@ macro_rules! task_watch_item_notify { }).await; } }); + Ok(Some(handle)) + } + Err(e) => { + info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e); + Ok(None) } - Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e), } - }); - Ok(()) + }) } }); }; @@ -199,6 +205,17 @@ pub trait CtrlTask { signal: SignalEmitter<'static>, ) -> impl Future> + Send; + /// Helper to spawn a background task that drains JoinSet and logs any task errors. + fn spawn_task_supervisor(name: &'static str, mut tasks: tokio::task::JoinSet<()>) { + tokio::spawn(async move { + while let Some(res) = tasks.join_next().await { + if let Err(err) = res { + warn!("{name} background task ended with error: {err:?}"); + } + } + }); + } + // /// Create a timed repeating task // async fn repeating_task(&self, millis: u64, mut task: impl FnMut() + Send + // 'static) { use std::time::Duration; @@ -221,33 +238,32 @@ pub trait CtrlTask { mut on_prepare_for_shutdown: F2, mut on_lid_change: F3, mut on_external_power_change: F4, - ) -> impl Future + Send + ) -> impl Future, RogError>> + Send where F1: FnMut(bool) -> Fut1 + Send + 'static, F2: FnMut(bool) -> Fut2 + Send + 'static, F3: FnMut(bool) -> Fut3 + Send + 'static, F4: FnMut(bool) -> Fut4 + Send + 'static, - Fut1: Future + Send, - Fut2: Future + Send, - Fut3: Future + Send, - Fut4: Future + Send, + Fut1: Future + Send + 'static, + Fut2: Future + Send + 'static, + Fut3: Future + Send + 'static, + Fut4: Future + Send + 'static, { - async { - let connection = Connection::system() - .await - .expect("Controller could not create dbus connection"); + async move { + let connection = Connection::system().await.map_err(RogError::Zbus)?; let manager = ManagerProxy::builder(&connection) .cache_properties(CacheProperties::No) .build() .await - .expect("Controller could not create ManagerProxy"); + .map_err(RogError::Zbus)?; + + let mut set = tokio::task::JoinSet::new(); let manager1 = manager.clone(); - tokio::spawn(async move { + set.spawn(async move { if let Ok(mut notif) = manager1.receive_prepare_for_shutdown().await { while let Some(event) = notif.next().await { - // blocks thread :| if let Ok(args) = event.args() { debug!("Doing on_prepare_for_shutdown({})", args.start); on_prepare_for_shutdown(args.start).await; @@ -257,10 +273,9 @@ pub trait CtrlTask { }); let manager2 = manager.clone(); - tokio::spawn(async move { + set.spawn(async move { if let Ok(mut notif) = manager2.receive_prepare_for_sleep().await { while let Some(event) = notif.next().await { - // blocks thread :| if let Ok(args) = event.args() { debug!("Doing on_prepare_for_sleep({})", args.start); on_prepare_for_sleep(args.start).await; @@ -270,7 +285,7 @@ pub trait CtrlTask { }); let manager3 = manager.clone(); - tokio::spawn(async move { + set.spawn(async move { let mut last_power = manager3.on_external_power().await.unwrap_or_default(); loop { @@ -284,9 +299,8 @@ pub trait CtrlTask { } }); - tokio::spawn(async move { + set.spawn(async move { let mut last_lid = manager.lid_closed().await.unwrap_or_default(); - // need to loop on these as they don't emit signals loop { if let Ok(next) = manager.lid_closed().await { if next != last_lid { @@ -297,6 +311,8 @@ pub trait CtrlTask { sleep(Duration::from_secs(2)).await; } }); + + Ok(set) } } } diff --git a/dmi-id/Cargo.toml b/dmi-id/Cargo.toml deleted file mode 100644 index 9da167465..000000000 --- a/dmi-id/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "dmi_id" -license.workspace = true -version.workspace = true -readme.workspace = true -authors.workspace = true -repository.workspace = true -homepage.workspace = true -edition.workspace = true - -[dependencies] -log.workspace = true -udev.workspace = true diff --git a/rog-anime/Cargo.toml b/rog-anime/Cargo.toml index 5485e0272..10bae3a0e 100644 --- a/rog-anime/Cargo.toml +++ b/rog-anime/Cargo.toml @@ -15,7 +15,7 @@ exclude = ["data"] [features] default = ["dbus", "detect"] dbus = ["zbus"] -detect = ["dmi_id"] +detect = ["rog_platform"] [lib] name = "rog_anime" @@ -33,5 +33,5 @@ glam.workspace = true zbus = { workspace = true, optional = true } -dmi_id = { path = "../dmi-id", optional = true } +rog_platform = { path = "../rog-platform", optional = true } thiserror.workspace = true diff --git a/rog-anime/src/data.rs b/rog-anime/src/data.rs index bd0451b20..c93414dfc 100644 --- a/rog-anime/src/data.rs +++ b/rog-anime/src/data.rs @@ -3,8 +3,8 @@ use std::str::FromStr; use std::thread::sleep; use std::time::{Duration, Instant}; -use dmi_id::DMIID; -use log::info; +use log::{info, warn}; +use rog_platform::DMIID; use serde::{Deserialize, Serialize}; #[cfg(feature = "dbus")] use zbus::zvariant::{OwnedValue, Type, Value}; @@ -317,7 +317,7 @@ pub fn run_animation(frames: &AnimeGif, callback: &dyn Fn(AnimeDataBuffer) -> Re fade_out_step = 1.0 / fade_out.as_secs_f32(); if time.total_fade_time() > run_time { - println!("Total fade in/out time larger than gif run time. Setting fades to half"); + warn!("Total fade in/out time larger than gif run time. Setting fades to half"); fade_in = run_time / 2; fade_in_step = 1.0 / (run_time / 2).as_secs_f32(); diff --git a/rog-anime/src/usb.rs b/rog-anime/src/usb.rs index 4379c1c27..a761cd745 100644 --- a/rog-anime/src/usb.rs +++ b/rog-anime/src/usb.rs @@ -10,7 +10,7 @@ use std::str::FromStr; -use dmi_id::DMIID; +use rog_platform::DMIID; use serde::{Deserialize, Serialize}; #[cfg(feature = "dbus")] use zbus::zvariant::{OwnedValue, Type, Value}; diff --git a/rog-aura/Cargo.toml b/rog-aura/Cargo.toml index eb4a089ac..1a0d2998e 100644 --- a/rog-aura/Cargo.toml +++ b/rog-aura/Cargo.toml @@ -18,7 +18,7 @@ dbus = ["zbus"] [dependencies] serde.workspace = true zbus = { workspace = true, optional = true } -dmi_id = { path = "../dmi-id" } +rog_platform = { path = "../rog-platform" } # cli and logging log.workspace = true diff --git a/rog-aura/src/aura_detection.rs b/rog-aura/src/aura_detection.rs index 81acb13a5..ed3ef7d08 100644 --- a/rog-aura/src/aura_detection.rs +++ b/rog-aura/src/aura_detection.rs @@ -1,7 +1,7 @@ use std::env; -use dmi_id::DMIID; use log::{error, info, warn}; +use rog_platform::DMIID; use serde::{Deserialize, Serialize}; use crate::keyboard::AdvancedAuraType; diff --git a/rog-control-center/Cargo.toml b/rog-control-center/Cargo.toml index 49ef17711..51fc3f3cb 100644 --- a/rog-control-center/Cargo.toml +++ b/rog-control-center/Cargo.toml @@ -30,8 +30,6 @@ rog_dbus = { path = "../rog-dbus" } rog_aura = { path = "../rog-aura" } rog_profiles = { path = "../rog-profiles" } rog_platform = { path = "../rog-platform" } -rog_slash = { path = "../rog-slash" } -dmi_id = { path = "../dmi-id" } argh.workspace = true log.workspace = true diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index b4f119468..77d04c79a 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -6,7 +6,6 @@ use std::thread::{self, sleep}; use std::time::Duration; use config_traits::{StdConfig, StdConfigLoad1}; -use dmi_id::DMIID; use log::{debug, error, info, warn, LevelFilter}; use rog_control_center::cli_options::CliStart; use rog_control_center::config::Config; @@ -21,6 +20,7 @@ use rog_control_center::window::{WindowCommand, WindowController}; use rog_control_center::zbus_proxies::{ AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH, }; +use rog_platform::DMIID; use tokio::runtime::Runtime; #[tokio::main] @@ -83,12 +83,12 @@ async fn main() -> Result<()> { let asusd_version = match platform_proxy.version() { Ok(v) => v, Err(e) => { - eprintln!("Could not get asusd version: {e:?}\nIs asusd.service running?"); + error!("Could not get asusd version: {e:?}\nIs asusd.service running?"); std::process::exit(1); } }; if asusd_version != self_version { - println!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}"); + warn!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}"); // return Ok(()); } diff --git a/rog-control-center/src/mocking.rs b/rog-control-center/src/mocking.rs index 5cb87d00b..6577b527e 100644 --- a/rog-control-center/src/mocking.rs +++ b/rog-control-center/src/mocking.rs @@ -1,14 +1,9 @@ use std::collections::BTreeMap; -use rog_aura::usb::{AuraDevRog2, AuraDevice, AuraPowerDev}; use rog_aura::{AuraEffect, AuraModeNum, AuraZone}; use rog_platform::gpu_pci::GfxPower; -use rog_platform::platform::GpuMode; -use rog_platform::supported::{ - AdvancedAura, AnimeSupportedFunctions, ChargeSupportedFunctions, LedSupportedFunctions, - PlatformProfileFunctions, RogBiosSupportedFunctions, SupportedFunctions, -}; -use rog_profiles::fan_curve_set::{CurveData, FanCurveSet}; +use rog_platform::platform::{GpuMode, PlatformProfile, Properties}; +use rog_profiles::fan_curve_set::CurveData; use crate::error::Result; @@ -100,47 +95,34 @@ impl Bios { pub struct Profile; impl Profile { - pub fn profiles(&self) -> Result> { + pub fn profiles(&self) -> Result> { Ok(vec![ - rog_profiles::Profile::Balanced, - rog_profiles::Profile::Performance, - rog_profiles::Profile::Quiet, + PlatformProfile::Balanced, + PlatformProfile::Performance, + PlatformProfile::Quiet, ]) } - pub fn active_profile(&self) -> Result { - Ok(rog_profiles::Profile::Performance) + pub fn active_profile(&self) -> Result { + Ok(PlatformProfile::Performance) } - pub fn enabled_fan_profiles(&self) -> Result> { + pub fn enabled_fan_profiles(&self) -> Result> { Ok(vec![ - rog_profiles::Profile::Performance, - rog_profiles::Profile::Balanced, + PlatformProfile::Performance, + PlatformProfile::Balanced, ]) } - pub fn fan_curve_data(&self, _p: rog_profiles::Profile) -> Result { - let mut curve = FanCurveSet::default(); - curve.cpu.pwm = [ - 30, 40, 60, 100, 140, 180, 200, 250, - ]; - curve.cpu.temp = [ - 20, 30, 40, 50, 70, 80, 90, 100, - ]; - curve.gpu.pwm = [ - 40, 80, 100, 140, 170, 200, 230, 250, - ]; - curve.gpu.temp = [ - 20, 30, 40, 50, 70, 80, 90, 100, - ]; - Ok(curve) - } - - pub fn set_fan_curve(&self, _p: rog_profiles::Profile, _c: CurveData) -> Result<()> { + pub fn fan_curve_data(&self, _p: PlatformProfile) -> Result> { + Ok(vec![CurveData::default()]) + } + + pub fn set_fan_curve(&self, _p: PlatformProfile, _c: CurveData) -> Result<()> { Ok(()) } - pub fn set_fan_curve_enabled(&self, _p: rog_profiles::Profile, _b: bool) -> Result<()> { + pub fn set_fan_curve_enabled(&self, _p: PlatformProfile, _b: bool) -> Result<()> { Ok(()) } @@ -152,7 +134,7 @@ impl Profile { Ok(()) } - pub fn set_active_profile(&self, _p: rog_profiles::Profile) -> Result<()> { + pub fn set_active_profile(&self, _p: PlatformProfile) -> Result<()> { Ok(()) } @@ -167,9 +149,9 @@ impl Led { let mut data = BTreeMap::new(); data.insert(AuraModeNum::Static, AuraEffect::default()); data.insert(AuraModeNum::Star, AuraEffect::default()); - data.insert(AuraModeNum::Strobe, AuraEffect::default()); + data.insert(AuraModeNum::Highlight, AuraEffect::default()); data.insert(AuraModeNum::Rain, AuraEffect::default()); - data.insert(AuraModeNum::Rainbow, AuraEffect::default()); + data.insert(AuraModeNum::RainbowCycle, AuraEffect::default()); data.insert(AuraModeNum::Ripple, AuraEffect::default()); data.insert(AuraModeNum::Breathe, AuraEffect::default()); data.insert(AuraModeNum::Comet, AuraEffect::default()); @@ -180,30 +162,13 @@ impl Led { } pub fn led_mode(&self) -> Result { - Ok(AuraModeNum::Rainbow) + Ok(AuraModeNum::RainbowCycle) } pub fn led_brightness(&self) -> Result { Ok(1) } - pub fn led_powered(&self) -> Result { - Ok(AuraPowerDev { - tuf: vec![], - x1866: vec![], - x19b6: vec![ - AuraDevRog2::BootKeyb, - AuraDevRog2::AwakeKeyb, - AuraDevRog2::SleepLogo, - AuraDevRog2::AwakeLogo, - ], - }) - } - - pub fn set_led_power(&self, _a: AuraPowerDev, _b: bool) -> Result<()> { - Ok(()) - } - pub fn set_led_mode(&self, _a: &AuraEffect) -> Result<()> { Ok(()) } @@ -230,45 +195,10 @@ impl Anime { pub struct Supported; impl Supported { - pub fn supported_functions(&self) -> Result { - Ok(SupportedFunctions { - anime_ctrl: AnimeSupportedFunctions(true), - charge_ctrl: ChargeSupportedFunctions { - charge_level_set: true, - }, - platform_profile: PlatformProfileFunctions { - platform_profile: true, - fan_curves: true, - }, - keyboard_led: LedSupportedFunctions { - dev_id: AuraDevice::X19b6, - brightness: true, - basic_modes: vec![ - AuraModeNum::Rain, - AuraModeNum::Rainbow, - AuraModeNum::Star, - AuraModeNum::Static, - AuraModeNum::Strobe, - ], - basic_zones: vec![ - AuraZone::Key1, - AuraZone::Key2, - AuraZone::Key3, - AuraZone::Key4, - AuraZone::BarLeft, - AuraZone::BarRight, - AuraZone::Logo, - ], - advanced_type: AdvancedAura::PerKey, - }, - rog_bios_ctrl: RogBiosSupportedFunctions { - post_sound: true, - gpu_mux: true, - panel_overdrive: true, - dgpu_disable: true, - mini_led_mode: true, - egpu_enable: true, - }, - }) + pub fn supported_properties(&self) -> Result> { + Ok(vec![ + Properties::ThrottlePolicy, + Properties::ChargeControlEndThreshold, + ]) } } diff --git a/rog-control-center/src/ui/setup_slash.rs b/rog-control-center/src/ui/setup_slash.rs index 43b42e059..f5de7eda7 100644 --- a/rog-control-center/src/ui/setup_slash.rs +++ b/rog-control-center/src/ui/setup_slash.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use log::{error, info}; use rog_dbus::find_iface_async; use rog_dbus::zbus_slash::SlashProxy; -use rog_slash::SlashMode; +use rog_platform::slash::SlashMode; use slint::{ComponentHandle, Model, ModelRc, SharedString, VecModel}; use crate::config::Config; diff --git a/rog-control-center/src/zbus_proxies.rs b/rog-control-center/src/zbus_proxies.rs index d1e016144..9cf57fa24 100644 --- a/rog-control-center/src/zbus_proxies.rs +++ b/rog-control-center/src/zbus_proxies.rs @@ -1,8 +1,5 @@ -use log::info; use std::sync::{Arc, Mutex}; -use zbus::blocking::proxy::ProxyImpl; -use zbus::blocking::{fdo, Connection}; use zbus::zvariant::{OwnedValue, Type, Value}; use zbus::{interface, proxy}; @@ -71,79 +68,24 @@ pub trait ROGCCZbus { fn set_state(&self, state: AppState) -> zbus::Result<()>; } -pub fn find_iface(iface_name: &str) -> Result, Box> -where - T: ProxyImpl<'static> + From>, -{ - let conn = Connection::system()?; - let f = fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/")?; - let interfaces = f.get_managed_objects()?; - let mut paths = Vec::new(); - for v in interfaces.iter() { - // let o: Vec = v.1.keys().map(|e| - // e.to_owned()).collect(); println!("{}, {:?}", v.0, o); - for k in v.1.keys() { - if k.as_str() == iface_name { - // println!("Found {iface_name} device at {}, {}", v.0, k); - paths.push(v.0.clone()); - } - } - } - if paths.len() > 1 { - info!("Multiple asusd interfaces devices found"); - } - if !paths.is_empty() { - let mut ctrl = Vec::new(); - paths.sort_by(|a, b| a.cmp(b)); - for path in paths { - ctrl.push( - T::builder(&conn) - .path(path.clone())? - .destination("xyz.ljones.Asusd")? - .build()?, - ); - } - return Ok(ctrl); - } - - Err("No Aura interface".into()) -} +/// D-Bus proxy for asusd's GPU power status interface (`xyz.ljones.Gpu`). +#[proxy( + interface = "xyz.ljones.Gpu", + default_service = "xyz.ljones.Asusd", + default_path = "/xyz/ljones/Gpu" +)] +pub trait GpuStatus { + /// Current GPU power status (e.g. "active", "suspended", "off"). + #[zbus(property)] + fn power_status(&self) -> zbus::Result; -pub async fn find_iface_async(iface_name: &str) -> Result, Box> -where - T: zbus::proxy::ProxyImpl<'static> + From>, -{ - let conn = zbus::Connection::system().await?; - let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?; - let interfaces = f.get_managed_objects().await?; - let mut paths = Vec::new(); - for v in interfaces.iter() { - // let o: Vec = v.1.keys().map(|e| - // e.to_owned()).collect(); println!("{}, {:?}", v.0, o); - for k in v.1.keys() { - if k.as_str() == iface_name { - // println!("Found {iface_name} device at {}, {}", v.0, k); - paths.push(v.0.clone()); - } - } - } - if paths.len() > 1 { - info!("Multiple asusd interfaces devices found"); - } - if !paths.is_empty() { - let mut ctrl = Vec::new(); - paths.sort_by(|a, b| a.cmp(b)); - for path in paths { - ctrl.push( - T::builder(&conn) - .path(path.clone())? - .destination("xyz.ljones.Asusd")? - .build() - .await?, - ); - } - return Ok(ctrl); - } + /// GPU vendor name. + #[zbus(property)] + fn vendor(&self) -> zbus::Result; - Err("No interface".into()) + /// Current GPU mode (e.g. "Optimus", "Integrated", "Vfio", "Ultimate"). + #[zbus(property)] + fn mode(&self) -> zbus::Result; } + +pub use rog_dbus::{find_iface_async, find_iface_blocking}; diff --git a/rog-dbus/Cargo.toml b/rog-dbus/Cargo.toml index 29c7759d9..aae52041f 100644 --- a/rog-dbus/Cargo.toml +++ b/rog-dbus/Cargo.toml @@ -12,10 +12,9 @@ description = "dbus interface methods for asusctl" [dependencies] asusd = { path = "../asusd" } rog_anime = { path = "../rog-anime", features = ["dbus"] } -rog_slash = { path = "../rog-slash", features = ["dbus"] } -rog_scsi = { path = "../rog-scsi", features = ["dbus"] } rog_aura = { path = "../rog-aura" } rog_profiles = { path = "../rog-profiles" } -rog_platform = { path = "../rog-platform" } +rog_platform = { path = "../rog-platform", features = ["dbus"] } +log.workspace = true zbus.workspace = true diff --git a/rog-dbus/src/lib.rs b/rog-dbus/src/lib.rs index 19cfc8175..337fd8fff 100644 --- a/rog-dbus/src/lib.rs +++ b/rog-dbus/src/lib.rs @@ -73,7 +73,7 @@ where } } if paths.len() > 1 { - println!("Multiple asusd interfaces devices found"); + log::warn!("Multiple asusd interface devices found"); } if !paths.is_empty() { let mut ctrl = Vec::new(); @@ -92,3 +92,38 @@ where Err(format!("Did not find {iface_name}").into()) } + +pub fn find_iface_blocking(iface_name: &str) -> Result, Box> +where + T: zbus::blocking::proxy::ProxyImpl<'static> + From>, +{ + let conn = zbus::blocking::Connection::system()?; + let f = zbus::blocking::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/")?; + let interfaces = f.get_managed_objects()?; + let mut paths = Vec::new(); + for v in interfaces.iter() { + for k in v.1.keys() { + if k.as_str() == iface_name { + paths.push(v.0.clone()); + } + } + } + if paths.len() > 1 { + log::warn!("Multiple asusd interface devices found"); + } + if !paths.is_empty() { + let mut ctrl = Vec::new(); + paths.sort_by(|a, b| a.cmp(b)); + for path in paths { + ctrl.push( + T::builder(&conn) + .path(path)? + .destination("xyz.ljones.Asusd")? + .build()?, + ); + } + return Ok(ctrl); + } + + Err(format!("Did not find {iface_name}").into()) +} diff --git a/rog-dbus/src/scsi_aura.rs b/rog-dbus/src/scsi_aura.rs index 2ee38c272..6a5dbd423 100644 --- a/rog-dbus/src/scsi_aura.rs +++ b/rog-dbus/src/scsi_aura.rs @@ -20,7 +20,7 @@ //! //! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html //! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, -use rog_scsi::{AuraEffect, AuraMode}; +use rog_platform::scsi::{AuraEffect, AuraMode}; use zbus::proxy; #[proxy( interface = "xyz.ljones.ScsiAura", diff --git a/rog-dbus/src/zbus_slash.rs b/rog-dbus/src/zbus_slash.rs index ac39ab894..532446fb5 100644 --- a/rog-dbus/src/zbus_slash.rs +++ b/rog-dbus/src/zbus_slash.rs @@ -1,4 +1,4 @@ -use rog_slash::SlashMode; +use rog_platform::slash::SlashMode; use zbus::proxy; #[proxy( diff --git a/rog-platform/Cargo.toml b/rog-platform/Cargo.toml index a25737fd4..38ee03989 100644 --- a/rog-platform/Cargo.toml +++ b/rog-platform/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true homepage.workspace = true edition.workspace = true +[features] +dbus = [] + [dependencies] log.workspace = true serde.workspace = true @@ -18,6 +21,8 @@ inotify.workspace = true rusb.workspace = true thiserror.workspace = true nvml-wrapper.workspace = true +ron.workspace = true +libc.workspace = true [dev-dependencies] serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/rog-platform/src/capabilities.rs b/rog-platform/src/capabilities.rs new file mode 100644 index 000000000..f6a775d46 --- /dev/null +++ b/rog-platform/src/capabilities.rs @@ -0,0 +1,57 @@ +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +use crate::gpu_pci::{asus_dgpu_disable_exists, asus_gpu_mux_exists}; + +const WMI_PATH: &str = "/sys/devices/platform/asus-nb-wmi"; + +/// Detected hardware feature matrix. +/// +/// Note: `true` indicates an exposed driver or sysfs interface. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DeviceCapabilities { + /// Support for GPU MUX mode switching. + pub has_gpu_mux: bool, + /// Support for dGPU disable / dGPU power cutoff. + pub has_dgpu_disable: bool, + /// Support for custom PPT (Package Power Tracking) limits. + pub has_ppt_control: bool, + /// Support for custom fan curves (CPU/GPU/MID fans). + pub has_fan_curves: bool, + /// Support for Panel Overdrive (Fast Response). + pub has_panel_od: bool, + /// Support for Mini-LED backlight mode control. + pub has_mini_led: bool, + /// Support for AniMe Matrix LED display. + pub has_anime_matrix: bool, + /// Support for Slash lighting bar. + pub has_slash_lighting: bool, +} + +impl DeviceCapabilities { + /// Detect capabilities based on sysfs nodes and platform attributes. + pub fn detect() -> Self { + let wmi = Path::new(WMI_PATH); + Self { + has_gpu_mux: asus_gpu_mux_exists(), + has_dgpu_disable: asus_dgpu_disable_exists(), + has_ppt_control: wmi.join("ppt_pl1_spl").exists() || wmi.join("ppt_pl2_sppt").exists(), + has_fan_curves: wmi.join("pwm1_auto_point1_pwm").exists(), + has_panel_od: wmi.join("panel_od").exists(), + has_mini_led: wmi.join("mini_led_mode").exists(), + has_anime_matrix: detect_anime_matrix(), + has_slash_lighting: detect_slash_lighting(), + } + } +} + +fn detect_anime_matrix() -> bool { + // Detect USB/HID AniMe matrix presence + Path::new("/sys/bus/usb/drivers/asus_anime").exists() +} + +fn detect_slash_lighting() -> bool { + // Detect USB/HID Slash lighting presence + Path::new("/sys/class/leds/asus::slash").exists() +} diff --git a/rog-platform/src/cpu.rs b/rog-platform/src/cpu.rs index 08059215f..fe2115823 100644 --- a/rog-platform/src/cpu.rs +++ b/rog-platform/src/cpu.rs @@ -86,9 +86,8 @@ impl CPUControl { if let Some(path) = self.paths.first() { let s = read_attr_string(&to_device(path)?, ATTR_GOVERNOR)?; Ok(s.as_str().into()) - // TODO: check cpu are sync } else { - Err(PlatformError::CPU("No CPU's?".to_string())) + Err(PlatformError::CPU("No CPUs found".to_string())) } } @@ -96,9 +95,8 @@ impl CPUControl { if let Some(path) = self.paths.first() { read_attr_string(&to_device(path)?, ATTR_AVAILABLE_GOVERNORS) .map(|s| s.split_whitespace().map(|s| s.into()).collect()) - // TODO: check cpu are sync } else { - Err(PlatformError::CPU("No CPU's?".to_string())) + Err(PlatformError::CPU("No CPUs found".to_string())) } } @@ -117,9 +115,8 @@ impl CPUControl { if let Some(path) = self.paths.first() { let s = read_attr_string(&to_device(path)?, ATTR_EPP)?; Ok(s.as_str().into()) - // TODO: check cpu are sync } else { - Err(PlatformError::CPU("No CPU's?".to_string())) + Err(PlatformError::CPU("No CPUs found".to_string())) } } @@ -135,7 +132,7 @@ impl CPUControl { } } } else { - Err(PlatformError::CPU("No CPU's?".to_string())) + Err(PlatformError::CPU("No CPUs found".to_string())) } } diff --git a/dmi-id/src/lib.rs b/rog-platform/src/dmi.rs similarity index 98% rename from dmi-id/src/lib.rs rename to rog-platform/src/dmi.rs index faa288131..6b8907a9e 100644 --- a/dmi-id/src/lib.rs +++ b/rog-platform/src/dmi.rs @@ -91,7 +91,7 @@ mod tests { #[test] #[ignore = "Does not run in docker images"] fn dmi_sysfs_properties_not_unknown() { - let dmi = DMIID::new().unwrap(); + let dmi = DMIID::new().expect("dmi creation failed"); assert_ne!(dmi.id_model, "Unknown".to_string()); dbg!(dmi.id_model); diff --git a/rog-platform/src/error.rs b/rog-platform/src/error.rs index 6ea4b6a7e..4bfecc85d 100644 --- a/rog-platform/src/error.rs +++ b/rog-platform/src/error.rs @@ -57,6 +57,36 @@ pub enum PlatformError { #[error("CPU control: {0}")] CPU(String), + + #[error("Could not parse mode")] + ParseMode, + + #[error("Could not parse colour")] + ParseColour, + + #[error("Could not parse speed")] + ParseSpeed, + + #[error("Could not parse direction")] + ParseDirection, + + #[error("RON Parse Error: {0}")] + Ron(#[source] ron::Error), + + #[error("RON Parse Error: {0}")] + RonParse(#[source] ron::error::SpannedError), + + #[error("No Slash device found")] + NoSlashDevice, + + #[error("Unsupported Slash device found")] + UnsupportedSlashDevice, + + #[error("The data buffer was incorrect length for generating USB packets")] + DataBufferLength, + + #[error("Could not parse {0}")] + ParseError(String), } impl From for PlatformError { @@ -71,6 +101,18 @@ impl From for PlatformError { } } +impl From for PlatformError { + fn from(e: ron::Error) -> Self { + PlatformError::Ron(e) + } +} + +impl From for PlatformError { + fn from(e: ron::error::SpannedError) -> Self { + PlatformError::RonParse(e) + } +} + impl From for FdoErr { fn from(error: PlatformError) -> Self { match error { diff --git a/rog-platform/src/lib.rs b/rog-platform/src/lib.rs index 40d1ee239..450612893 100644 --- a/rog-platform/src/lib.rs +++ b/rog-platform/src/lib.rs @@ -3,8 +3,10 @@ pub mod asus_armoury; pub mod backlight; +pub mod capabilities; pub mod cled; pub mod cpu; +pub mod dmi; pub mod error; pub mod gpu_pci; pub mod hid_raw; @@ -12,8 +14,12 @@ pub mod keyboard_led; pub(crate) mod macros; pub mod platform; pub mod power; +pub mod scsi; +pub mod slash; pub mod usb_raw; +pub use dmi::DMIID; + use std::path::Path; use error::{PlatformError, Result}; diff --git a/rog-scsi/src/builtin_modes.rs b/rog-platform/src/scsi/builtin_modes.rs similarity index 64% rename from rog-scsi/src/builtin_modes.rs rename to rog-platform/src/scsi/builtin_modes.rs index 78f9f432a..2a5868c6d 100644 --- a/rog-scsi/src/builtin_modes.rs +++ b/rog-platform/src/scsi/builtin_modes.rs @@ -5,9 +5,9 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "dbus")] use zbus::zvariant::{OwnedValue, Type, Value}; -use crate::error::Error; -use crate::scsi::{apply_task, dir_task, mode_task, rgb_task, save_task, speed_task}; -use crate::sg::Task; +use crate::scsi::protocol::{apply_task, dir_task, mode_task, rgb_task, save_task, speed_task}; +use crate::scsi::sg::Task; +use crate::scsi::Error; #[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))] #[derive(Debug, Clone, PartialEq, Eq, Copy, Deserialize, Serialize)] @@ -208,25 +208,13 @@ impl AuraMode { impl Display for AuraMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", <&str>::from(self)) - } -} - -impl From for String { - fn from(mode: AuraMode) -> Self { - <&str>::from(&mode).to_owned() - } -} - -impl From<&AuraMode> for &str { - fn from(mode: &AuraMode) -> Self { - match mode { + let s = match self { AuraMode::Off => "Off", AuraMode::Static => "Static", AuraMode::Breathe => "Breathe", + AuraMode::Flashing => "Flashing", AuraMode::RainbowCycle => "RainbowCycle", AuraMode::RainbowWave => "RainbowWave", - AuraMode::Flashing => "Flashing", AuraMode::RainbowCycleBreathe => "RainbowCycleBreathe", AuraMode::ChaseFade => "ChaseFade", AuraMode::RainbowCycleChaseFade => "RainbowCycleChaseFade", @@ -236,44 +224,40 @@ impl From<&AuraMode> for &str { AuraMode::RainbowPulseChase => "RainbowPulseChase", AuraMode::RandomFlicker => "RandomFlicker", AuraMode::DoubleFade => "DoubleFade", - } + }; + write!(f, "{s}") } } impl FromStr for AuraMode { type Err = Error; - fn from_str(mode: &str) -> Result { - match mode { - "Off" => Ok(Self::Off), - "Static" => Ok(Self::Static), - "Breathe" => Ok(Self::Breathe), - "RainbowCycle" => Ok(Self::RainbowCycle), - "RainbowWave" => Ok(Self::RainbowWave), - "Flashing" => Ok(Self::Flashing), - "RainbowCycleBreathe" => Ok(Self::RainbowCycleBreathe), - "ChaseFade" => Ok(Self::ChaseFade), - "RainbowCycleChaseFade" => Ok(Self::RainbowCycleChaseFade), - "Chase" => Ok(Self::Chase), - "RainbowCycleChase" => Ok(Self::RainbowCycleChase), - "RainbowCycleWave" => Ok(Self::RainbowCycleWave), - "RainbowPulseChase" => Ok(Self::RainbowPulseChase), - "RandomFlicker" => Ok(Self::RandomFlicker), - "DoubleFade" => Ok(Self::DoubleFade), + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "off" => Ok(AuraMode::Off), + "static" => Ok(AuraMode::Static), + "breathe" => Ok(AuraMode::Breathe), + "flashing" => Ok(AuraMode::Flashing), + "rainbowcycle" => Ok(AuraMode::RainbowCycle), + "rainbowwave" => Ok(AuraMode::RainbowWave), + "rainbowcyclebreathe" => Ok(AuraMode::RainbowCycleBreathe), + "chasefade" => Ok(AuraMode::ChaseFade), + "rainbowcyclechasefade" => Ok(AuraMode::RainbowCycleChaseFade), + "chase" => Ok(AuraMode::Chase), + "rainbowcyclechase" => Ok(AuraMode::RainbowCycleChase), + "rainbowcyclewave" => Ok(AuraMode::RainbowCycleWave), + "rainbowpulsechase" => Ok(AuraMode::RainbowPulseChase), + "randomflicker" => Ok(AuraMode::RandomFlicker), + "doublefade" => Ok(AuraMode::DoubleFade), _ => Err(Error::ParseMode), } } } -impl From<&str> for AuraMode { - fn from(mode: &str) -> Self { - AuraMode::from_str(mode).unwrap_or_default() - } -} - impl From for AuraMode { - fn from(mode: u8) -> Self { - match mode { + fn from(value: u8) -> Self { + match value { 0 => Self::Off, 1 => Self::Static, 2 => Self::Breathe, @@ -294,111 +278,122 @@ impl From for AuraMode { } } -impl From for AuraMode { - fn from(value: AuraEffect) -> Self { - value.mode +impl From for u8 { + fn from(value: AuraMode) -> Self { + value as u8 } } -/// Default factory modes structure. #[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))] -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct AuraEffect { - /// The effect type +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct ModeData { pub mode: AuraMode, - /// One of three speeds for modes that support speed (most that animate) - pub speed: Speed, - /// Up, down, left, right. Only Rainbow mode seems to use this - pub direction: Direction, - /// Primary colour for all modes + pub zone: u32, pub colour1: Colour, - /// Secondary colour in some modes like Breathing or Stars pub colour2: Colour, pub colour3: Colour, pub colour4: Colour, + pub speed: Speed, + pub direction: Direction, } -impl AuraEffect { - pub fn mode(&self) -> &AuraMode { - &self.mode - } - - pub fn mode_name(&self) -> &str { - <&str>::from(&self.mode) - } - - pub fn mode_num(&self) -> u8 { - self.mode as u8 +impl Display for ModeData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Mode: {}, Zone: {}, Colour1: {:?}, Colour2: {:?}, Colour3: {:?}, Colour4: {:?}, Speed: {:?}, Direction: {:?}", + self.mode, + self.zone, + self.colour1, + self.colour2, + self.colour3, + self.colour4, + self.speed, + self.direction + ) } +} +impl ModeData { pub fn default_with_mode(mode: AuraMode) -> Self { Self { mode, - ..Default::default() + zone: 0, + colour1: Colour::default(), + colour2: Colour::default(), + colour3: Colour::default(), + colour4: Colour::default(), + speed: Speed::default(), + direction: Direction::default(), } } -} -impl Default for AuraEffect { - fn default() -> Self { - Self { - mode: AuraMode::Static, - colour1: Colour { r: 166, g: 0, b: 0 }, - colour2: Colour { r: 0, g: 0, b: 0 }, - colour3: Colour { r: 166, g: 0, b: 0 }, - colour4: Colour { r: 0, g: 0, b: 0 }, - speed: Speed::Med, - direction: Direction::Forward, - } + pub fn mode(&self) -> &AuraMode { + &self.mode } -} -impl Display for AuraEffect { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "AuraEffect {{")?; - writeln!(f, " mode: {}", self.mode())?; - writeln!(f, " speed: {:?}", self.speed)?; - writeln!(f, " direction: {:?}", self.direction)?; - writeln!(f, " colour1: {:?}", self.colour1)?; - writeln!(f, " colour2: {:?}", self.colour2)?; - writeln!(f, " colour3: {:?}", self.colour3)?; - writeln!(f, " colour4: {:?}", self.colour4)?; - writeln!(f, "}}") + #[allow(clippy::too_many_arguments)] + pub fn new( + mode: AuraMode, + zone: u32, + colour1: Colour, + colour2: Colour, + colour3: Colour, + colour4: Colour, + speed: Speed, + direction: Direction, + ) -> Self { + ModeData { + mode, + zone, + colour1, + colour2, + colour3, + colour4, + speed, + direction, + } } -} - -impl From<&AuraEffect> for Vec { - fn from(effect: &AuraEffect) -> Self { - let mut tasks = Vec::new(); - tasks.append(&mut vec![ - mode_task(effect.mode as u8), - rgb_task(0, &effect.colour1.into()), - rgb_task(1, &effect.colour2.into()), - rgb_task(2, &effect.colour3.into()), - rgb_task(3, &effect.colour4.into()), - ]); + pub fn to_tasks(&self) -> Vec { + let (rgb, dir, speed) = match self.mode { + AuraMode::Off | AuraMode::Static => (true, false, false), + AuraMode::Breathe | AuraMode::Flashing | AuraMode::RandomFlicker => (true, false, true), + AuraMode::RainbowCycle | AuraMode::RainbowCycleBreathe => (false, false, true), + AuraMode::RainbowWave + | AuraMode::RainbowCycleChaseFade + | AuraMode::RainbowCycleChase + | AuraMode::RainbowCycleWave + | AuraMode::RainbowPulseChase => (false, true, true), + AuraMode::ChaseFade | AuraMode::Chase | AuraMode::DoubleFade => (true, true, true), + }; - if !matches!(effect.mode, AuraMode::Static | AuraMode::Off) { - tasks.push(speed_task(effect.speed as u8)); + let mut tasks = Vec::new(); + if rgb { + tasks.push(rgb_task( + self.zone, + &[ + self.colour1.r, self.colour1.g, self.colour1.b, + ], + )); } - if matches!( - effect.mode, - AuraMode::RainbowWave - | AuraMode::ChaseFade - | AuraMode::RainbowCycleChaseFade - | AuraMode::Chase - | AuraMode::RainbowCycleChase - | AuraMode::RainbowCycleWave - | AuraMode::RainbowPulseChase - ) { - tasks.push(dir_task(effect.direction as u8)); + if dir { + tasks.push(dir_task(self.direction as u8)); } - - tasks.append(&mut vec![ - apply_task(), - save_task(), - ]); + if speed { + tasks.push(speed_task(self.speed as u8)); + } + tasks.push(mode_task(self.mode as u8)); + tasks.push(apply_task()); + tasks.push(save_task()); tasks } } + +pub type AuraEffect = ModeData; + +impl From<&ModeData> for Vec { + fn from(effect: &ModeData) -> Self { + effect.to_tasks() + } +} diff --git a/rog-scsi/src/lib.rs b/rog-platform/src/scsi/mod.rs similarity index 94% rename from rog-scsi/src/lib.rs rename to rog-platform/src/scsi/mod.rs index 1fc382493..efc3925b9 100644 --- a/rog-scsi/src/lib.rs +++ b/rog-platform/src/scsi/mod.rs @@ -1,10 +1,9 @@ mod builtin_modes; -mod error; -mod scsi; +mod protocol; pub mod sg; +pub use crate::error::PlatformError as Error; pub use builtin_modes::*; -pub use error::*; use serde::{Deserialize, Serialize}; pub use sg::{Device, Task}; diff --git a/rog-scsi/src/scsi.rs b/rog-platform/src/scsi/protocol.rs similarity index 95% rename from rog-scsi/src/scsi.rs rename to rog-platform/src/scsi/protocol.rs index 0ae6af713..5797b6f70 100644 --- a/rog-scsi/src/scsi.rs +++ b/rog-platform/src/scsi/protocol.rs @@ -1,4 +1,4 @@ -use crate::sg::{Direction, Task}; +use crate::scsi::sg::{Direction, Task}; static ENE_APPLY_VAL: u8 = 0x01; // Value for Apply Changes Register static ENE_SAVE_VAL: u8 = 0xaa; @@ -39,11 +39,11 @@ pub(crate) fn rgb_task(led: u32, rgb: &[u8; 3]) -> Task { task } -/// 0-13 +/// 0-14 pub(crate) fn mode_task(mode: u8) -> Task { let mut task = Task::new(); task.set_cdb(data(ENE_REG_MODE, 1).as_slice()); - task.set_data(&[mode.min(13)], Direction::ToDevice); + task.set_data(&[mode.min(14)], Direction::ToDevice); task } diff --git a/rog-scsi/src/sg.rs b/rog-platform/src/scsi/sg.rs similarity index 100% rename from rog-scsi/src/sg.rs rename to rog-platform/src/scsi/sg.rs diff --git a/rog-slash/src/data.rs b/rog-platform/src/slash/data.rs similarity index 97% rename from rog-slash/src/data.rs rename to rog-platform/src/slash/data.rs index 5b60d90af..e08e8aa75 100644 --- a/rog-slash/src/data.rs +++ b/rog-platform/src/slash/data.rs @@ -1,14 +1,13 @@ use std::fmt::Display; use std::str::FromStr; -use dmi_id::DMIID; use serde::{Deserialize, Serialize}; #[cfg(feature = "dbus")] -use zbus::zvariant::Type; -use zbus::zvariant::{OwnedValue, Value}; +use zbus::zvariant::{OwnedValue, Type, Value}; -use crate::error::SlashError; -use crate::usb::{PROD_ID1, PROD_ID1_STR, PROD_ID2, PROD_ID2_STR}; +use crate::dmi::DMIID; +use crate::slash::usb::{PROD_ID1, PROD_ID1_STR, PROD_ID2, PROD_ID2_STR}; +use crate::slash::SlashError; #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] pub enum SlashType { diff --git a/rog-slash/src/lib.rs b/rog-platform/src/slash/mod.rs similarity index 67% rename from rog-slash/src/lib.rs rename to rog-platform/src/slash/mod.rs index 32c603376..1d930d761 100644 --- a/rog-slash/src/lib.rs +++ b/rog-platform/src/slash/mod.rs @@ -3,10 +3,8 @@ mod data; pub use data::*; -/// Base errors that are possible -pub mod error; +pub use crate::error::PlatformError as SlashError; +pub type Result = std::result::Result; /// Provides const methods to create the USB HID control packets pub mod usb; - -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/rog-slash/src/usb.rs b/rog-platform/src/slash/usb.rs similarity index 90% rename from rog-slash/src/usb.rs rename to rog-platform/src/slash/usb.rs index 1312159a4..5394e7ada 100644 --- a/rog-slash/src/usb.rs +++ b/rog-platform/src/slash/usb.rs @@ -8,11 +8,10 @@ //! //! Step 1 needs to be applied only on fresh system boot. -use dmi_id::DMIID; +use crate::dmi::DMIID; -#[cfg(feature = "dbus")] -use crate::error::SlashError; -use crate::{SlashMode, SlashType}; +use crate::slash::SlashError; +use crate::slash::{SlashMode, SlashType}; const PACKET_SIZE: usize = 32; const REPORT_ID_193B: u8 = 0x5e; @@ -35,7 +34,7 @@ pub type SlashUsbPacket = [u8; PACKET_SIZE]; #[inline] pub fn get_slash_type() -> SlashType { let dmi = DMIID::new() - .map_err(|_| SlashError::NoDevice) + .map_err(|_| SlashError::NoSlashDevice) .unwrap_or_default(); let board_name = dmi.board_name.to_uppercase(); if board_name.contains("G614F") { @@ -140,22 +139,25 @@ pub const fn slash_pkt_set_mode(slash_type: SlashType, mode: SlashMode) -> [Slas let mut pkt2 = [0; PACKET_SIZE]; pkt2[0] = report_id; - pkt2[1] = 0xd3; - pkt2[2] = 0x04; - pkt2[3] = 0x00; - pkt2[4] = 0x0c; - pkt2[5] = 0x01; - pkt2[6] = mode as u8; - pkt2[7] = 0x02; - pkt2[8] = 0x19; // difference, GA605 = 0x10 - pkt2[9] = 0x03; - pkt2[10] = 0x13; - pkt2[11] = 0x04; - pkt2[12] = 0x11; - pkt2[13] = 0x05; - pkt2[14] = 0x12; - pkt2[15] = 0x06; - pkt2[16] = 0x13; + pkt2[1] = 0xd2; + pkt2[2] = 0xd3; + pkt2[3] = 0x04; + pkt2[4] = 0x00; + pkt2[5] = 0x0c; + pkt2[6] = 0x01; + pkt2[7] = mode as u8; + pkt2[8] = 0x02; + pkt2[9] = match slash_type { + SlashType::GA605_2024 | SlashType::GA605_2025 => 0x10, + _ => 0x19, + }; + pkt2[10] = 0x03; + pkt2[11] = 0x13; + pkt2[12] = 0x04; + pkt2[13] = 0x11; + pkt2[14] = 0x05; + pkt2[15] = 0x12; + pkt2[16] = 0x06; [ pkt1, pkt2, diff --git a/rog-profiles/src/fan_curve_set.rs b/rog-profiles/src/fan_curve_set.rs index f2f3c1907..e7431f285 100644 --- a/rog-profiles/src/fan_curve_set.rs +++ b/rog-profiles/src/fan_curve_set.rs @@ -26,27 +26,14 @@ pub struct CurveData { impl From<&CurveData> for String { fn from(c: &CurveData) -> Self { - format!( - "{:?}: enabled: {}, {}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%", - c.fan, - c.enabled, - c.temp[0], - (c.pwm[0] as u32) * 100 / 255, - c.temp[1], - (c.pwm[1] as u32) * 100 / 255, - c.temp[2], - (c.pwm[2] as u32) * 100 / 255, - c.temp[3], - (c.pwm[3] as u32) * 100 / 255, - c.temp[4], - (c.pwm[4] as u32) * 100 / 255, - c.temp[5], - (c.pwm[5] as u32) * 100 / 255, - c.temp[6], - (c.pwm[6] as u32) * 100 / 255, - c.temp[7], - (c.pwm[7] as u32) * 100 / 255, - ) + let points = c + .temp + .iter() + .zip(&c.pwm) + .map(|(t, p)| format!("{t}c:{}%", (*p as u32) * 100 / 255)) + .collect::>() + .join(","); + format!("{:?}: enabled: {}, {points}", c.fan, c.enabled) } } diff --git a/rog-scsi/Cargo.toml b/rog-scsi/Cargo.toml deleted file mode 100644 index cafb9f1d2..000000000 --- a/rog-scsi/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "rog_scsi" -version.workspace = true -rust-version.workspace = true -license.workspace = true -readme.workspace = true -authors.workspace = true -repository.workspace = true -homepage.workspace = true -description.workspace = true -edition.workspace = true - -[features] -default = ["dbus", "ron"] -dbus = ["zbus"] - -[dependencies] -libc.workspace = true -serde.workspace = true -zbus = { workspace = true, optional = true } - -# cli and logging - -ron = { workspace = true, optional = true } -thiserror.workspace = true diff --git a/rog-scsi/src/error.rs b/rog-scsi/src/error.rs deleted file mode 100644 index 69a668763..000000000 --- a/rog-scsi/src/error.rs +++ /dev/null @@ -1,37 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Could not parse mode")] - ParseMode, - - #[error("Could not parse colour")] - ParseColour, - - #[error("Could not parse speed")] - ParseSpeed, - - #[error("Could not parse direction")] - ParseDirection, - - #[error("IO Error: {1}: {0}")] - IoPath(String, #[source] std::io::Error), - - #[error("RON Parse Error: {0}")] - Ron(#[source] ron::Error), - - #[error("RON Parse Error: {0}")] - RonParse(#[source] ron::error::SpannedError), -} - -impl From for Error { - fn from(e: ron::Error) -> Self { - Self::Ron(e) - } -} - -impl From for Error { - fn from(e: ron::error::SpannedError) -> Self { - Self::RonParse(e) - } -} diff --git a/rog-slash/Cargo.toml b/rog-slash/Cargo.toml deleted file mode 100644 index 1b3dfe27b..000000000 --- a/rog-slash/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "rog_slash" -license.workspace = true -version.workspace = true -readme.workspace = true -authors.workspace = true -repository.workspace = true -homepage.workspace = true -edition.workspace = true -documentation = "https://docs.rs/rog-slash" -description = "ASUS Slash display" -keywords = ["ROG", "ASUS", "AniMe", "Slash"] -exclude = ["data"] - -[features] -default = ["dbus", "detect"] -dbus = ["zbus"] -detect = ["dmi_id"] - -[lib] -name = "rog_slash" -path = "src/lib.rs" - -[dependencies] -serde.workspace = true -zbus = { workspace = true, optional = true } -dmi_id = { path = "../dmi-id", optional = true } -thiserror.workspace = true diff --git a/rog-slash/src/error.rs b/rog-slash/src/error.rs deleted file mode 100644 index 49cdf3111..000000000 --- a/rog-slash/src/error.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub type Result = std::result::Result; - -#[derive(thiserror::Error, Debug)] -pub enum SlashError { - #[error("{0}")] - Dbus(String), - - #[error("udev {0}: {1}")] - Udev(String, #[source] std::io::Error), - - #[error("No Slash device found")] - NoDevice, - - #[error("Unsupported Slash device found")] - UnsupportedDevice, - - #[error("The data buffer was incorrect length for generating USB packets")] - DataBufferLength, - - #[error("Could not parse {0}")] - ParseError(String), -} - -impl From for zbus::fdo::Error { - fn from(err: SlashError) -> Self { - zbus::fdo::Error::Failed(format!("{}", err)) - } -}