From 19ffcf3376e31446e00be57a4e3759a93ac9f582 Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Mon, 4 Nov 2024 08:55:37 +0100 Subject: [PATCH] Refactor: Make all Aura type devices use "device_manager" Open the door to adding many other types of "aura" devices later. --- CHANGELOG.md | 3 + Cargo.lock | 41 +- Makefile | 13 +- asusctl/examples/anime-diag-png.rs | 4 +- asusctl/examples/anime-diag.rs | 4 +- asusctl/examples/anime-gif.rs | 4 +- asusctl/examples/anime-grid.rs | 4 +- asusctl/examples/anime-outline.rs | 4 +- asusctl/examples/anime-png.rs | 4 +- asusctl/examples/anime-spinning.rs | 4 +- asusctl/src/cli_opts.rs | 10 +- asusctl/src/main.rs | 433 +++++++------- asusctl/src/slash_cli.rs | 4 +- asusd-user/src/daemon.rs | 4 +- .../src/{ctrl_anime => aura_anime}/config.rs | 29 +- asusd/src/aura_anime/mod.rs | 247 ++++++++ .../{ctrl_anime => aura_anime}/trait_impls.rs | 324 +++++------ .../src/{ctrl_aura => aura_laptop}/config.rs | 25 +- asusd/src/aura_laptop/mod.rs | 219 ++++++++ asusd/src/aura_laptop/trait_impls.rs | 343 ++++++++++++ asusd/src/aura_manager.rs | 368 ++++++++++++ .../src/{ctrl_slash => aura_slash}/config.rs | 5 +- asusd/src/aura_slash/mod.rs | 70 +++ asusd/src/aura_slash/trait_impls.rs | 190 +++++++ asusd/src/aura_types.rs | 183 ++++++ asusd/src/ctrl_anime/mod.rs | 296 ---------- asusd/src/ctrl_aura/controller.rs | 526 ------------------ asusd/src/ctrl_aura/manager.rs | 138 ----- asusd/src/ctrl_aura/mod.rs | 34 -- asusd/src/ctrl_aura/trait_impls.rs | 318 ----------- asusd/src/ctrl_slash/mod.rs | 102 ---- asusd/src/ctrl_slash/trait_impls.rs | 166 ------ asusd/src/daemon.rs | 34 +- asusd/src/lib.rs | 11 +- dmi-id/src/lib.rs | 4 +- rog-anime/src/data.rs | 17 +- rog-anime/src/usb.rs | 14 +- rog-aura/data/aura_support.ron | 9 + rog-aura/src/builtin_modes.rs | 54 +- rog-aura/src/effects/mod.rs | 4 +- rog-aura/src/keyboard/advanced.rs | 22 +- rog-aura/src/lib.rs | 7 +- rog-aura/src/usb.rs | 12 +- .../translations/en/rog-control-center.po | 112 ++-- rog-dbus/src/zbus_aura.rs | 6 +- rog-platform/src/hid_raw.rs | 57 +- rog-slash/src/data.rs | 38 +- rog-slash/src/usb.rs | 69 ++- 48 files changed, 2349 insertions(+), 2240 deletions(-) rename asusd/src/{ctrl_anime => aura_anime}/config.rs (90%) create mode 100644 asusd/src/aura_anime/mod.rs rename asusd/src/{ctrl_anime => aura_anime}/trait_impls.rs (66%) rename asusd/src/{ctrl_aura => aura_laptop}/config.rs (94%) create mode 100644 asusd/src/aura_laptop/mod.rs create mode 100644 asusd/src/aura_laptop/trait_impls.rs create mode 100644 asusd/src/aura_manager.rs rename asusd/src/{ctrl_slash => aura_slash}/config.rs (89%) create mode 100644 asusd/src/aura_slash/mod.rs create mode 100644 asusd/src/aura_slash/trait_impls.rs create mode 100644 asusd/src/aura_types.rs delete mode 100644 asusd/src/ctrl_anime/mod.rs delete mode 100644 asusd/src/ctrl_aura/controller.rs delete mode 100644 asusd/src/ctrl_aura/manager.rs delete mode 100644 asusd/src/ctrl_aura/mod.rs delete mode 100644 asusd/src/ctrl_aura/trait_impls.rs delete mode 100644 asusd/src/ctrl_slash/mod.rs delete mode 100644 asusd/src/ctrl_slash/trait_impls.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 75672b0e..f8bab2a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Changed - Fix attribute writes. At some point the kernel API seems to have changed. +- Extremely large refactor of Aura device handling. Should enable easy add of different kinds now. +- Add GA605W LED layout +- Rename CLI args for aura related properties. This will likely change further as more devices are added ## [v6.0.12] diff --git a/Cargo.lock b/Cargo.lock index 534153c9..b2f81394 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -930,7 +930,7 @@ dependencies = [ [[package]] name = "const-field-offset" version = "0.1.5" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "const-field-offset-macro", "field-offset", @@ -939,7 +939,7 @@ dependencies = [ [[package]] name = "const-field-offset-macro" version = "0.1.5" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "proc-macro2", "quote", @@ -2151,7 +2151,7 @@ dependencies = [ [[package]] name = "i-slint-backend-linuxkms" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "calloop 0.14.2", "drm", @@ -2169,19 +2169,20 @@ dependencies = [ [[package]] name = "i-slint-backend-selector" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "cfg-if", "i-slint-backend-linuxkms", "i-slint-backend-winit", "i-slint-common", "i-slint-core", + "i-slint-core-macros", ] [[package]] name = "i-slint-backend-winit" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "ashpd", "cfg-if", @@ -2212,7 +2213,7 @@ dependencies = [ [[package]] name = "i-slint-common" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "cfg-if", "derive_more", @@ -2224,7 +2225,7 @@ dependencies = [ [[package]] name = "i-slint-compiler" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "by_address", "codemap", @@ -2254,7 +2255,7 @@ dependencies = [ [[package]] name = "i-slint-core" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "auto_enums", "bitflags 2.6.0", @@ -2299,7 +2300,7 @@ dependencies = [ [[package]] name = "i-slint-core-macros" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "quote", "serde_json", @@ -2309,7 +2310,7 @@ dependencies = [ [[package]] name = "i-slint-renderer-femtovg" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "cfg-if", "const-field-offset", @@ -2339,7 +2340,7 @@ dependencies = [ [[package]] name = "i-slint-renderer-skia" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "bytemuck", "cfg-if", @@ -2831,9 +2832,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libfuzzer-sys" @@ -4546,7 +4547,7 @@ dependencies = [ [[package]] name = "slint" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "const-field-offset", "i-slint-backend-selector", @@ -4563,7 +4564,7 @@ dependencies = [ [[package]] name = "slint-build" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "i-slint-compiler", "i-slint-core-macros", @@ -4575,7 +4576,7 @@ dependencies = [ [[package]] name = "slint-macros" version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "i-slint-compiler", "proc-macro2", @@ -5535,8 +5536,8 @@ dependencies = [ [[package]] name = "vtable" -version = "0.2.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +version = "0.2.1" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "const-field-offset", "portable-atomic", @@ -5546,8 +5547,8 @@ dependencies = [ [[package]] name = "vtable-macro" -version = "0.2.0" -source = "git+https://github.com/slint-ui/slint.git#dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2" +version = "0.2.1" +source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" dependencies = [ "proc-macro2", "quote", diff --git a/Makefile b/Makefile index 0c521349..7c04b1b1 100644 --- a/Makefile +++ b/Makefile @@ -19,17 +19,22 @@ LEDCFG := aura_support.ron SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs') -STRIP_BINARIES ?= 0 +STRIP_BINARIES ?= 1 DEBUG ?= 0 ifeq ($(DEBUG),0) - ARGS += --release --features "rog-control-center/x11" + ARGS += --release TARGET = release else - ARGS += --profile dev --features "rog-control-center/x11" + ARGS += --profile dev TARGET = debug endif +X11 ?= 0 +ifeq ($(X11),1) + ARGS += --features "rog-control-center/x11" +endif + VENDORED ?= 0 ifeq ($(VENDORED),1) ARGS += --frozen @@ -133,7 +138,7 @@ ifeq ($(VENDORED),1) tar pxf vendor_asusctl_$(VERSION).tar.xz endif cargo build $(ARGS) -ifneq ($(STRIP_BINARIES),0) +ifeq ($(STRIP_BINARIES),1) strip -s ./target/$(TARGET)/$(BIN_C) strip -s ./target/$(TARGET)/$(BIN_D) strip -s ./target/$(TARGET)/$(BIN_U) diff --git a/asusctl/examples/anime-diag-png.rs b/asusctl/examples/anime-diag-png.rs index 0440840b..54bb7ae9 100644 --- a/asusctl/examples/anime-diag-png.rs +++ b/asusctl/examples/anime-diag-png.rs @@ -3,7 +3,7 @@ use std::error::Error; use std::path::Path; use std::process::exit; -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_anime::{AnimeDiagonal, AnimeType}; use rog_dbus::zbus_anime::AnimeProxyBlocking; use zbus::blocking::Connection; @@ -26,7 +26,7 @@ fn main() -> Result<(), Box> { AnimeType::GA401, )?; - let anime_type = get_maybe_anime_type()?; + let anime_type = get_anime_type(); proxy.write(matrix.into_data_buffer(anime_type)?).unwrap(); diff --git a/asusctl/examples/anime-diag.rs b/asusctl/examples/anime-diag.rs index 98f04a7d..e1f585e3 100644 --- a/asusctl/examples/anime-diag.rs +++ b/asusctl/examples/anime-diag.rs @@ -1,7 +1,7 @@ use std::thread::sleep; use std::time::Duration; -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_anime::{AnimeDiagonal, AnimeType}; use rog_dbus::zbus_anime::AnimeProxyBlocking; use zbus::blocking::Connection; @@ -29,7 +29,7 @@ fn main() { } } - let anime_type = get_maybe_anime_type().unwrap(); + let anime_type = get_anime_type(); proxy .write(matrix.into_data_buffer(anime_type).unwrap()) .unwrap(); diff --git a/asusctl/examples/anime-gif.rs b/asusctl/examples/anime-gif.rs index 4f0b5847..a205c1ac 100644 --- a/asusctl/examples/anime-gif.rs +++ b/asusctl/examples/anime-gif.rs @@ -2,7 +2,7 @@ use std::env; use std::path::Path; use std::thread::sleep; -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_anime::{ActionData, ActionLoader, Sequences}; use rog_dbus::zbus_anime::AnimeProxyBlocking; use zbus::blocking::Connection; @@ -19,7 +19,7 @@ fn main() { let path = Path::new(&args[1]); let brightness = args[2].parse::().unwrap(); - let anime_type = get_maybe_anime_type().unwrap(); + let anime_type = get_anime_type(); let mut seq = Sequences::new(anime_type); seq.insert( 0, diff --git a/asusctl/examples/anime-grid.rs b/asusctl/examples/anime-grid.rs index 299f8d66..41468343 100644 --- a/asusctl/examples/anime-grid.rs +++ b/asusctl/examples/anime-grid.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_anime::{AnimeDataBuffer, AnimeGrid}; use rog_dbus::zbus_anime::AnimeProxyBlocking; use zbus::blocking::Connection; @@ -14,7 +14,7 @@ fn main() { let conn = Connection::system().unwrap(); let proxy = AnimeProxyBlocking::new(&conn).unwrap(); - let anime_type = get_maybe_anime_type().unwrap(); + let anime_type = get_anime_type(); let mut matrix = AnimeGrid::new(anime_type); let tmp = matrix.get_mut(); diff --git a/asusctl/examples/anime-outline.rs b/asusctl/examples/anime-outline.rs index 1e6d9545..8a9bbd44 100644 --- a/asusctl/examples/anime-outline.rs +++ b/asusctl/examples/anime-outline.rs @@ -1,4 +1,4 @@ -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_anime::AnimeDataBuffer; use rog_dbus::zbus_anime::AnimeProxyBlocking; use zbus::blocking::Connection; @@ -9,7 +9,7 @@ use zbus::blocking::Connection; fn main() { let conn = Connection::system().unwrap(); let proxy = AnimeProxyBlocking::new(&conn).unwrap(); - let anime_type = get_maybe_anime_type().unwrap(); + let anime_type = get_anime_type(); let mut matrix = AnimeDataBuffer::new(anime_type); matrix.data_mut()[1] = 100; // start = 1 for n in matrix.data_mut()[2..32].iter_mut() { diff --git a/asusctl/examples/anime-png.rs b/asusctl/examples/anime-png.rs index 915ff9b5..539fab72 100644 --- a/asusctl/examples/anime-png.rs +++ b/asusctl/examples/anime-png.rs @@ -4,7 +4,7 @@ use std::error::Error; use std::path::Path; use std::process::exit; -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2}; use rog_dbus::zbus_anime::AnimeProxyBlocking; use zbus::blocking::Connection; @@ -20,7 +20,7 @@ fn main() -> Result<(), Box> { exit(-1); } - let anime_type = get_maybe_anime_type()?; + let anime_type = get_anime_type(); let matrix = AnimeImage::from_png( Path::new(&args[1]), args[2].parse::().unwrap(), diff --git a/asusctl/examples/anime-spinning.rs b/asusctl/examples/anime-spinning.rs index 2a700a64..fae2de8b 100644 --- a/asusctl/examples/anime-spinning.rs +++ b/asusctl/examples/anime-spinning.rs @@ -7,7 +7,7 @@ use std::process::exit; use std::thread::sleep; use std::time::Duration; -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2}; use rog_dbus::zbus_anime::AnimeProxyBlocking; use zbus::blocking::Connection; @@ -23,7 +23,7 @@ fn main() -> Result<(), Box> { exit(-1); } - let anime_type = get_maybe_anime_type()?; + let anime_type = get_anime_type(); let mut matrix = AnimeImage::from_png( Path::new(&args[1]), args[2].parse::().unwrap(), diff --git a/asusctl/src/cli_opts.rs b/asusctl/src/cli_opts.rs index 963941f8..04240b6f 100644 --- a/asusctl/src/cli_opts.rs +++ b/asusctl/src/cli_opts.rs @@ -31,11 +31,11 @@ pub struct CliStart { #[derive(Options)] pub enum CliCommand { #[options(help = "Set the keyboard lighting from built-in modes")] - LedMode(LedModeCommand), + Aura(LedModeCommand), #[options(help = "Set the LED power states")] - LedPow1(LedPowerCommand1), + AuraPowerOld(LedPowerCommand1), #[options(help = "Set the LED power states")] - LedPow2(LedPowerCommand2), + AuraPower(LedPowerCommand2), #[options(help = "Set or select platform_profile")] Profile(ProfileCommand), #[options(help = "Set, select, or modify fan curves if supported")] @@ -47,7 +47,7 @@ pub enum CliCommand { #[options(name = "slash", help = "Manage Slash Ledbar")] Slash(SlashCommand), #[options(help = "Change bios settings")] - Bios(BiosCommand), + Platform(PlatformCommand), } #[derive(Debug, Clone, Options)] @@ -87,7 +87,7 @@ pub struct GraphicsCommand { } #[derive(Options, Debug)] -pub struct BiosCommand { +pub struct PlatformCommand { #[options(help = "print help message")] pub help: bool, #[options( diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index ba144ad0..eb087634 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -9,7 +9,7 @@ use aura_cli::{LedPowerCommand1, LedPowerCommand2}; use dmi_id::DMIID; use fan_curve_cli::FanCurveCommand; use gumdrop::{Opt, Options}; -use rog_anime::usb::get_maybe_anime_type; +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, AuraDeviceType, AuraEffect, PowerZones}; @@ -23,6 +23,7 @@ use rog_platform::platform::{GpuMode, Properties, ThrottlePolicy}; use rog_profiles::error::ProfileError; use rog_slash::SlashMode; use ron::ser::PrettyConfig; +use zbus::blocking::proxy::ProxyImpl; use zbus::blocking::Connection; use crate::aura_cli::{AuraPowerStates, LedBrightness}; @@ -122,31 +123,33 @@ fn check_service(name: &str) -> bool { false } -fn find_aura_iface() -> Result>, Box> { +fn find_iface(iface_name: &str) -> Result, Box> +where + T: ProxyImpl<'static> + From>, +{ let conn = zbus::blocking::Connection::system().unwrap(); let f = zbus::blocking::fdo::ObjectManagerProxy::new(&conn, "org.asuslinux.Daemon", "/").unwrap(); let interfaces = f.get_managed_objects().unwrap(); - let mut aura_paths = Vec::new(); + 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() == "org.asuslinux.Aura" { - println!("Found aura device at {}, {}", v.0, k); - aura_paths.push(v.0.clone()); + if k.as_str() == iface_name { + println!("Found {iface_name} device at {}, {}", v.0, k); + paths.push(v.0.clone()); } } } - if aura_paths.len() > 1 { - println!("Multiple aura devices found: {aura_paths:?}"); - println!("TODO: enable selection"); + if paths.len() > 1 { + println!("Multiple aura devices found: {paths:?}"); } - if !aura_paths.is_empty() { + if !paths.is_empty() { let mut ctrl = Vec::new(); - for path in aura_paths { + for path in paths { ctrl.push( - AuraProxyBlocking::builder(&conn) + T::builder(&conn) .path(path.clone())? .destination("org.asuslinux.Daemon")? .build()?, @@ -165,9 +168,9 @@ fn do_parsed( conn: Connection, ) -> Result<(), Box> { match &parsed.command { - Some(CliCommand::LedMode(mode)) => handle_led_mode(&find_aura_iface()?, mode)?, - Some(CliCommand::LedPow1(pow)) => handle_led_power1(&find_aura_iface()?, pow)?, - Some(CliCommand::LedPow2(pow)) => handle_led_power2(&find_aura_iface()?, pow)?, + Some(CliCommand::Aura(mode)) => handle_led_mode(mode)?, + Some(CliCommand::AuraPowerOld(pow)) => handle_led_power1(pow)?, + Some(CliCommand::AuraPower(pow)) => handle_led_power2(pow)?, Some(CliCommand::Profile(cmd)) => { handle_throttle_profile(&conn, supported_properties, cmd)? } @@ -175,9 +178,9 @@ fn do_parsed( handle_fan_curve(&conn, cmd)?; } Some(CliCommand::Graphics(_)) => do_gfx(), - Some(CliCommand::Anime(cmd)) => handle_anime(&conn, cmd)?, - Some(CliCommand::Slash(cmd)) => handle_slash(&conn, cmd)?, - Some(CliCommand::Bios(cmd)) => { + Some(CliCommand::Anime(cmd)) => handle_anime(cmd)?, + Some(CliCommand::Slash(cmd)) => handle_slash(cmd)?, + Some(CliCommand::Platform(cmd)) => { handle_platform_properties(&conn, supported_properties, cmd)? } None => { @@ -192,16 +195,17 @@ fn do_parsed( println!("{}", CliStart::usage()); println!(); if let Some(cmdlist) = CliStart::command_list() { - let dev_type = if let Ok(proxy) = find_aura_iface() { - // TODO: commands on all? - proxy - .first() - .unwrap() - .device_type() - .unwrap_or(AuraDeviceType::Unknown) - } else { - AuraDeviceType::Unknown - }; + let dev_type = + if let Ok(proxy) = find_iface::("org.asuslinux.Aura") { + // TODO: commands on all? + proxy + .first() + .unwrap() + .device_type() + .unwrap_or(AuraDeviceType::Unknown) + } else { + AuraDeviceType::Unknown + }; let commands: Vec = cmdlist.lines().map(|s| s.to_owned()).collect(); for command in commands.iter().filter(|command| { if command.trim().starts_with("fan-curve") @@ -211,6 +215,12 @@ fn do_parsed( return false; } + if command.trim().starts_with("aura") + && !supported_interfaces.contains(&"org.asuslinux.Aura".to_string()) + { + return false; + } + if command.trim().starts_with("anime") && !supported_interfaces.contains(&"org.asuslinux.Anime".to_string()) { @@ -223,7 +233,7 @@ fn do_parsed( return false; } - if command.trim().starts_with("bios") + if command.trim().starts_with("platform") && !supported_interfaces.contains(&"org.asuslinux.Platform".to_string()) { return false; @@ -231,11 +241,11 @@ fn do_parsed( if !dev_type.is_old_laptop() && !dev_type.is_tuf_laptop() - && command.trim().starts_with("led-pow-1") + && command.trim().starts_with("aura-power-old") { return false; } - if !dev_type.is_new_laptop() && command.trim().starts_with("led-pow-2") { + if !dev_type.is_new_laptop() && command.trim().starts_with("aura-power") { return false; } true @@ -252,7 +262,7 @@ fn do_parsed( } if let Some(brightness) = &parsed.kbd_bright { - if let Ok(aura) = find_aura_iface() { + if let Ok(aura) = find_iface::("org.asuslinux.Aura") { for aura in aura.iter() { match brightness.level() { None => { @@ -268,7 +278,7 @@ fn do_parsed( } if parsed.next_kbd_bright { - if let Ok(aura) = find_aura_iface() { + if let Ok(aura) = find_iface::("org.asuslinux.Aura") { for aura in aura.iter() { let brightness = aura.brightness()?; aura.set_brightness(brightness.next())?; @@ -279,7 +289,7 @@ fn do_parsed( } if parsed.prev_kbd_bright { - if let Ok(aura) = find_aura_iface() { + if let Ok(aura) = find_iface::("org.asuslinux.Aura") { for aura in aura.iter() { let brightness = aura.brightness()?; aura.set_brightness(brightness.prev())?; @@ -295,7 +305,7 @@ fn do_parsed( "Supported Platform Properties:\n{:#?}", supported_properties ); - if let Ok(aura) = find_aura_iface() { + if let Ok(aura) = find_iface::("org.asuslinux.Aura") { // TODO: multiple RGB check let bright = aura.first().unwrap().supported_brightness()?; let modes = aura.first().unwrap().supported_basic_modes()?; @@ -331,7 +341,7 @@ fn do_gfx() { println!("This command will be removed in future"); } -fn handle_anime(conn: &Connection, cmd: &AnimeCommand) -> Result<(), Box> { +fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box> { if (cmd.command.is_none() && cmd.enable_display.is_none() && cmd.enable_powersave_anim.is_none() @@ -348,165 +358,169 @@ fn handle_anime(conn: &Connection, cmd: &AnimeCommand) -> Result<(), Box("org.asuslinux.Anime")?; + 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?"); } - } - 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.help_requested() || image.path.is_empty() { - println!("Missing arg or command\n\n{}", image.self_usage()); - if let Some(lst) = image.self_command_list() { - println!("\n{}", lst); - } - 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)?)?; + let mut anime_type = get_anime_type(); + if let AnimeType::Unsupported = anime_type { + if let Some(model) = cmd.override_type { + anime_type = model; } - AnimeActions::PixelImage(image) => { - if image.help_requested() || image.path.is_empty() { - println!("Missing arg or command\n\n{}", image.self_usage()); - if let Some(lst) = image.self_command_list() { - println!("\n{}", lst); + } + + 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.help_requested() || image.path.is_empty() { + println!("Missing arg or command\n\n{}", image.self_usage()); + if let Some(lst) = image.self_command_list() { + println!("\n{}", lst); + } + return Ok(()); } - 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)?)?; } - 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.help_requested() || gif.path.is_empty() { - println!("Missing arg or command\n\n{}", gif.self_usage()); - if let Some(lst) = gif.self_command_list() { - println!("\n{}", lst); + AnimeActions::PixelImage(image) => { + if image.help_requested() || image.path.is_empty() { + println!("Missing arg or command\n\n{}", image.self_usage()); + if let Some(lst) = image.self_command_list() { + println!("\n{}", lst); + } + return Ok(()); } - 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)?)?; } - 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()); + AnimeActions::Gif(gif) => { + if gif.help_requested() || gif.path.is_empty() { + println!("Missing arg or command\n\n{}", gif.self_usage()); + if let Some(lst) = gif.self_command_list() { + println!("\n{}", lst); + } + return Ok(()); } - if loops >= 0 { - loops -= 1; - } - if loops == 0 { - break; + 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.help_requested() || gif.path.is_empty() { - println!("Missing arg or command\n\n{}", gif.self_usage()); - if let Some(lst) = gif.self_command_list() { - println!("\n{}", lst); + AnimeActions::PixelGif(gif) => { + if gif.help_requested() || gif.path.is_empty() { + println!("Missing arg or command\n\n{}", gif.self_usage()); + if let Some(lst) = gif.self_command_list() { + println!("\n{}", lst); + } + return Ok(()); } - return Ok(()); - } - verify_brightness(gif.bright); + verify_brightness(gif.bright); - let matrix = AnimeGif::from_diagonal_gif( - Path::new(&gif.path), - AnimTime::Count(1), - gif.bright, - anime_type, - )?; + 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; + 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.help_requested() || builtins.set.is_none() { - println!("\nAny unspecified args will be set to default (first shown var)\n"); - println!("\n{}", builtins.self_usage()); - if let Some(lst) = builtins.self_command_list() { - println!("\n{}", lst); + AnimeActions::SetBuiltins(builtins) => { + if builtins.help_requested() || builtins.set.is_none() { + println!( + "\nAny unspecified args will be set to default (first shown var)\n" + ); + println!("\n{}", builtins.self_usage()); + if let Some(lst) = builtins.self_command_list() { + println!("\n{}", lst); + } + return Ok(()); } - return Ok(()); - } - proxy.set_builtin_animations(rog_anime::Animations { - boot: builtins.boot, - awake: builtins.awake, - sleep: builtins.sleep, - shutdown: builtins.shutdown, - })?; + proxy.set_builtin_animations(rog_anime::Animations { + boot: builtins.boot, + awake: builtins.awake, + sleep: builtins.sleep, + shutdown: builtins.shutdown, + })?; + } } } } @@ -522,7 +536,7 @@ fn verify_brightness(brightness: f32) { } } -fn handle_slash(conn: &Connection, cmd: &SlashCommand) -> Result<(), Box> { +fn handle_slash(cmd: &SlashCommand) -> Result<(), Box> { if (cmd.brightness.is_none() && cmd.interval.is_none() && cmd.slash_mode.is_none() @@ -536,21 +550,24 @@ fn handle_slash(conn: &Connection, cmd: &SlashCommand) -> Result<(), Box("org.asuslinux.Slash")?; + for proxy in slashes { + if cmd.enable { + proxy.set_enabled(true)?; + } + if cmd.disable { + proxy.set_enabled(false)?; + } + if let Some(brightness) = cmd.brightness { + proxy.set_brightness(brightness)?; + } + if let Some(interval) = cmd.interval { + proxy.set_interval(interval)?; + } + if let Some(slash_mode) = cmd.slash_mode { + proxy.set_slash_mode(slash_mode)?; + } } if cmd.list { let res = SlashMode::list(); @@ -562,10 +579,7 @@ fn handle_slash(conn: &Connection, cmd: &SlashCommand) -> Result<(), Box Result<(), Box> { +fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box> { if mode.command.is_none() && !mode.prev_mode && !mode.next_mode { if !mode.help { println!("Missing arg or command\n"); @@ -576,6 +590,7 @@ fn handle_led_mode( if let Some(cmdlist) = LedModeCommand::command_list() { let commands: Vec = cmdlist.lines().map(|s| s.to_owned()).collect(); // TODO: multiple rgb check + let aura = find_iface::("org.asuslinux.Aura")?; let modes = aura.first().unwrap().supported_basic_modes()?; for command in commands.iter().filter(|command| { for mode in &modes { @@ -605,6 +620,7 @@ fn handle_led_mode( println!("Please specify either next or previous"); return Ok(()); } + let aura = find_iface::("org.asuslinux.Aura")?; if mode.next_mode { for aura in aura { let mode = aura.led_mode()?; @@ -640,10 +656,8 @@ fn handle_led_mode( Ok(()) } -fn handle_led_power1( - aura: &[AuraProxyBlocking], - power: &LedPowerCommand1, -) -> Result<(), Box> { +fn handle_led_power1(power: &LedPowerCommand1) -> Result<(), Box> { + let aura = find_iface::("org.asuslinux.Aura")?; for aura in aura { let dev_type = aura.device_type()?; if !dev_type.is_old_laptop() && !dev_type.is_tuf_laptop() { @@ -664,7 +678,7 @@ fn handle_led_power1( } if dev_type.is_old_laptop() || dev_type.is_tuf_laptop() { - handle_led_power_1_do_1866(aura, power)?; + handle_led_power_1_do_1866(&aura, power)?; return Ok(()); } } @@ -702,10 +716,8 @@ fn handle_led_power_1_do_1866( Ok(()) } -fn handle_led_power2( - aura: &[AuraProxyBlocking], - power: &LedPowerCommand2, -) -> Result<(), Box> { +fn handle_led_power2(power: &LedPowerCommand2) -> Result<(), Box> { + let aura = find_iface::("org.asuslinux.Aura")?; for aura in aura { let dev_type = aura.device_type()?; if !dev_type.is_new_laptop() { @@ -894,7 +906,7 @@ fn handle_fan_curve( fn handle_platform_properties( conn: &Connection, supported: &[Properties], - cmd: &BiosCommand, + cmd: &PlatformCommand, ) -> Result<(), Box> { { if (cmd.gpu_mux_mode_set.is_none() @@ -907,7 +919,10 @@ fn handle_platform_properties( { println!("Missing arg or command\n"); - let usage: Vec = BiosCommand::usage().lines().map(|s| s.to_owned()).collect(); + let usage: Vec = PlatformCommand::usage() + .lines() + .map(|s| s.to_owned()) + .collect(); for line in usage.iter().filter(|line| { line.contains("sound") && supported.contains(&Properties::PostAnimationSound) diff --git a/asusctl/src/slash_cli.rs b/asusctl/src/slash_cli.rs index f3464436..4777c19e 100644 --- a/asusctl/src/slash_cli.rs +++ b/asusctl/src/slash_cli.rs @@ -11,9 +11,9 @@ pub struct SlashCommand { pub disable: bool, #[options(meta = "", help = "Set brightness value <0-255>")] pub brightness: Option, - #[options(meta = "", help = "Set interval value <0-255>")] + #[options(meta = "", help = "Set interval value <0-5>")] pub interval: Option, - #[options(help = "Set SlashMode (so 'list' for all options)")] + #[options(meta = "", help = "Set SlashMode (so 'list' for all options)")] pub slash_mode: Option, #[options(help = "list available animations")] pub list: bool, diff --git a/asusd-user/src/daemon.rs b/asusd-user/src/daemon.rs index c2364935..ae002352 100644 --- a/asusd-user/src/daemon.rs +++ b/asusd-user/src/daemon.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex}; use asusd_user::config::*; use asusd_user::ctrl_anime::{CtrlAnime, CtrlAnimeInner}; use config_traits::{StdConfig, StdConfigLoad}; -use rog_anime::usb::get_maybe_anime_type; +use rog_anime::usb::get_anime_type; use rog_aura::aura_detection::LedSupportData; use rog_aura::keyboard::KeyLayout; use rog_dbus::zbus_anime::AnimeProxyBlocking; @@ -44,7 +44,7 @@ fn main() -> Result<(), Box> { // Set up the anime data and run loop/thread if supported.contains(&"org.asuslinux.Anime".to_string()) { if let Some(cfg) = config.active_anime { - let anime_type = get_maybe_anime_type()?; + let anime_type = get_anime_type(); let anime_config = ConfigAnime::new().set_name(cfg).load(); let anime = anime_config.create(anime_type)?; let anime_config = Arc::new(Mutex::new(anime_config)); diff --git a/asusd/src/ctrl_anime/config.rs b/asusd/src/aura_anime/config.rs similarity index 90% rename from asusd/src/ctrl_anime/config.rs rename to asusd/src/aura_anime/config.rs index 38beb326..ca44b63b 100644 --- a/asusd/src/ctrl_anime/config.rs +++ b/asusd/src/aura_anime/config.rs @@ -10,18 +10,18 @@ use serde::{Deserialize, Serialize}; const CONFIG_FILE: &str = "anime.ron"; -#[derive(Deserialize, Serialize, Default)] -pub struct AnimeConfigCached { +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct AniMeConfigCached { pub system: Vec, pub boot: Vec, pub wake: Vec, pub shutdown: Vec, } -impl AnimeConfigCached { +impl AniMeConfigCached { pub fn init_from_config( &mut self, - config: &AnimeConfig, + config: &AniMeConfig, anime_type: AnimeType, ) -> Result<(), AnimeError> { let mut sys = Vec::with_capacity(config.system.len()); @@ -53,7 +53,9 @@ impl AnimeConfigCached { /// Config for base system actions for the anime display #[derive(Deserialize, Serialize, Debug, Clone)] -pub struct AnimeConfig { +pub struct AniMeConfig { + #[serde(skip)] + pub anime_type: AnimeType, pub system: Vec, pub boot: Vec, pub wake: Vec, @@ -69,9 +71,10 @@ pub struct AnimeConfig { pub builtin_anims: Animations, } -impl Default for AnimeConfig { +impl Default for AniMeConfig { fn default() -> Self { - AnimeConfig { + AniMeConfig { + anime_type: AnimeType::GA402, system: Vec::new(), boot: Vec::new(), wake: Vec::new(), @@ -89,7 +92,7 @@ impl Default for AnimeConfig { } } -impl StdConfig for AnimeConfig { +impl StdConfig for AniMeConfig { fn new() -> Self { Self::create_default() } @@ -103,10 +106,10 @@ impl StdConfig for AnimeConfig { } } -impl StdConfigLoad for AnimeConfig {} +impl StdConfigLoad for AniMeConfig {} -impl From<&AnimeConfig> for DeviceState { - fn from(config: &AnimeConfig) -> Self { +impl From<&AniMeConfig> for DeviceState { + fn from(config: &AniMeConfig) -> Self { DeviceState { display_enabled: config.display_enabled, display_brightness: config.display_brightness, @@ -120,7 +123,7 @@ impl From<&AnimeConfig> for DeviceState { } } -impl AnimeConfig { +impl AniMeConfig { // fn clamp_config_brightness(mut config: &mut AnimeConfig) { // if config.brightness < 0.0 || config.brightness > 1.0 { // warn!( @@ -133,7 +136,7 @@ impl AnimeConfig { fn create_default() -> Self { // create a default config here - AnimeConfig { + AniMeConfig { system: vec![], boot: vec![ActionLoader::ImageAnimation { file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(), diff --git a/asusd/src/aura_anime/mod.rs b/asusd/src/aura_anime/mod.rs new file mode 100644 index 00000000..26f6e3c4 --- /dev/null +++ b/asusd/src/aura_anime/mod.rs @@ -0,0 +1,247 @@ +pub mod config; +/// Implements `CtrlTask`, Reloadable, `ZbusRun` +pub mod trait_impls; + +use std::convert::TryFrom; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::sleep; + +use config_traits::StdConfig; +use log::{error, info, warn}; +use rog_anime::usb::{ + pkt_flush, pkt_set_brightness, pkt_set_enable_display, pkt_set_enable_powersave_anim, + pkts_for_init, Brightness, +}; +use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType}; +use rog_platform::hid_raw::HidRaw; +use rog_platform::usb_raw::USBRaw; +use tokio::sync::{Mutex, MutexGuard}; + +use self::config::{AniMeConfig, AniMeConfigCached}; +use crate::error::RogError; + +#[derive(Debug, Clone)] +pub struct AniMe { + hid: Option>>, + usb: Option>>, + config: Arc>, + cache: AniMeConfigCached, + // set to force thread to exit + thread_exit: Arc, + // Set to false when the thread exits + thread_running: Arc, +} + +impl AniMe { + pub fn new( + hid: Option>>, + usb: Option>>, + config: Arc>, + ) -> Self { + Self { + hid, + usb, + config, + cache: AniMeConfigCached::default(), + thread_exit: Arc::new(AtomicBool::new(false)), + thread_running: Arc::new(AtomicBool::new(false)), + } + } + + /// Will fail if something is already holding the config lock + async fn do_init_cache(&mut self) { + if let Ok(mut config) = self.config.try_lock() { + if let Err(e) = self.cache.init_from_config(&config, config.anime_type) { + error!( + "Trying to cache the Anime Config failed, will reset to default config: {e:?}" + ); + config.rename_file_old(); + *config = AniMeConfig::new(); + config.write(); + } + } else { + error!("AniMe Matrix could not init cache") + } + } + + /// Initialise the device if required. + pub async fn do_initialization(&mut self) -> Result<(), RogError> { + self.do_init_cache().await; + let pkts = pkts_for_init(); + self.write_bytes(&pkts[0]).await?; + self.write_bytes(&pkts[1]).await + } + + pub async fn lock_config(&self) -> MutexGuard { + self.config.lock().await + } + + pub async fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> { + if let Some(hid) = &self.hid { + hid.lock().await.write_bytes(message)?; + } else if let Some(usb) = &self.usb { + usb.lock().await.write_bytes(message)?; + } + Ok(()) + } + + /// Write only a data packet. This will modify the leds brightness using the + /// global brightness set in config. + async fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> { + for led in buffer.data_mut().iter_mut() { + let mut bright = *led as f32; + if bright > 254.0 { + bright = 254.0; + } + *led = bright as u8; + } + let data = AnimePacketType::try_from(buffer)?; + for row in &data { + self.write_bytes(row).await?; + } + self.write_bytes(&pkt_flush()).await + } + + pub async fn set_builtins_enabled( + &self, + enabled: bool, + bright: Brightness, + ) -> Result<(), RogError> { + self.write_bytes(&pkt_set_enable_powersave_anim(enabled)) + .await?; + self.write_bytes(&pkt_set_enable_display(enabled)).await?; + self.write_bytes(&pkt_set_brightness(bright)).await?; + self.write_bytes(&pkt_set_enable_powersave_anim(enabled)) + .await + } + + /// Start an action thread. This is classed as a singleton and there should + /// be only one running - so the thread uses atomics to signal run/exit. + /// + /// Because this also writes to the usb device, other write tries (display + /// only) *must* get the mutex lock and set the `thread_exit` atomic. + async fn run_thread(&self, actions: Vec, mut once: bool) { + if actions.is_empty() { + warn!("AniMe system actions was empty"); + return; + } + + self.write_bytes(&pkt_set_enable_powersave_anim(false)) + .await + .map_err(|err| { + warn!("rog_anime::run_animation:callback {}", err); + }) + .ok(); + + let thread_exit = self.thread_exit.clone(); + let thread_running = self.thread_running.clone(); + let anime_type = self.config.lock().await.anime_type; + let inner = self.clone(); + + // Loop rules: + // - Lock the mutex **only when required**. That is, the lock must be held for + // the shortest duration possible. + // - An AtomicBool used for thread exit should be checked in every loop, + // including nested + + // The only reason for this outer thread is to prevent blocking while waiting + // for the next spawned thread to exit + // TODO: turn this in to async task (maybe? COuld still risk blocking main + // thread) + tokio::spawn(async move { + info!("AniMe new system thread started"); + // First two loops are to ensure we *do* aquire a lock on the mutex + // The reason the loop is required is because the USB writes can block + // for up to 10ms. We can't fail to get the atomics. + while thread_running.load(Ordering::SeqCst) { + // Make any running loop exit first + thread_exit.store(true, Ordering::SeqCst); + } + + info!("AniMe no previous system thread running (now)"); + thread_exit.store(false, Ordering::SeqCst); + thread_running.store(true, Ordering::SeqCst); + 'main: loop { + for action in &actions { + if thread_exit.load(Ordering::SeqCst) { + break 'main; + } + match action { + ActionData::Animation(frames) => { + // TODO: sort all this out + rog_anime::run_animation(frames, &|frame| { + if thread_exit.load(Ordering::Acquire) { + info!("rog-anime: animation sub-loop was asked to exit"); + return Ok(true); // Do safe exit + } + let inner = inner.clone(); + tokio::task::spawn_local(async move { + inner + .write_data_buffer(frame) + .await + .map_err(|err| { + warn!("rog_anime::run_animation:callback {}", err); + }) + .ok(); + }); + Ok(false) // Don't exit yet + }); + if thread_exit.load(Ordering::Acquire) { + info!("rog-anime: sub-loop exited and main loop exiting now"); + break 'main; + } + } + ActionData::Image(image) => { + once = false; + inner + .write_data_buffer(image.as_ref().clone()) + .await + .map_err(|e| error!("{}", e)) + .ok(); + } + ActionData::Pause(duration) => sleep(*duration), + ActionData::AudioEq + | ActionData::SystemInfo + | ActionData::TimeDate + | ActionData::Matrix => {} + } + } + if thread_exit.load(Ordering::SeqCst) { + break 'main; + } + if once || actions.is_empty() { + break 'main; + } + } + // Clear the display on exit + if let Ok(data) = + AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()]) + .map_err(|e| error!("{}", e)) + { + inner + .write_data_buffer(data) + .await + .map_err(|err| { + warn!("rog_anime::run_animation:callback {}", err); + }) + .ok(); + } + inner + .write_bytes(&pkt_set_enable_powersave_anim( + inner.config.lock().await.builtin_anims_enabled, + )) + .await + .map_err(|err| { + warn!("rog_anime::run_animation:callback {}", err); + }) + .ok(); + // Loop ended, set the atmonics + thread_running.store(false, Ordering::SeqCst); + info!("AniMe system thread exited"); + }) + .await + .map(|err| info!("AniMe system thread: {:?}", err)) + .ok(); + } +} diff --git a/asusd/src/ctrl_anime/trait_impls.rs b/asusd/src/aura_anime/trait_impls.rs similarity index 66% rename from asusd/src/ctrl_anime/trait_impls.rs rename to asusd/src/aura_anime/trait_impls.rs index 0887b647..c2df54ae 100644 --- a/asusd/src/ctrl_anime/trait_impls.rs +++ b/asusd/src/aura_anime/trait_impls.rs @@ -1,25 +1,22 @@ use std::sync::atomic::Ordering; -use std::sync::Arc; use config_traits::StdConfig; -use log::warn; +use log::{error, warn}; use logind_zbus::manager::ManagerProxy; use rog_anime::usb::{ pkt_set_brightness, pkt_set_builtin_animations, pkt_set_enable_display, pkt_set_enable_powersave_anim, Brightness, }; use rog_anime::{Animations, AnimeDataBuffer, DeviceState}; -use zbus::export::futures_util::lock::Mutex; use zbus::object_server::SignalEmitter; use zbus::proxy::CacheProperties; +use zbus::zvariant::OwnedObjectPath; use zbus::{interface, Connection}; -use super::config::AnimeConfig; -use super::CtrlAnime; +use super::config::AniMeConfig; +use super::AniMe; use crate::error::RogError; - -pub const ANIME_ZBUS_NAME: &str = "Anime"; -pub const ANIME_ZBUS_PATH: &str = "/org/asuslinux"; +use crate::Reloadable; async fn get_logind_manager<'a>() -> ManagerProxy<'a> { let connection = Connection::system() @@ -34,12 +31,29 @@ async fn get_logind_manager<'a>() -> ManagerProxy<'a> { } #[derive(Clone)] -pub struct CtrlAnimeZbus(pub Arc>); +pub struct AniMeZbus(AniMe); -/// The struct with the main dbus methods requires this trait -impl crate::ZbusRun for CtrlAnimeZbus { - async fn add_to_server(self, server: &mut Connection) { - Self::add_to_server_helper(self, ANIME_ZBUS_PATH, server).await; +impl AniMeZbus { + pub fn new(anime: AniMe) -> Self { + Self(anime) + } + + pub async fn start_tasks( + mut self, + connection: &Connection, + path: OwnedObjectPath, + ) -> Result<(), RogError> { + // let task = zbus.clone(); + self.reload() + .await + .unwrap_or_else(|err| warn!("Controller error: {}", err)); + connection + .object_server() + .at(path.clone(), self) + .await + .map_err(|e| error!("Couldn't add server at path: {path}, {e:?}")) + .ok(); + Ok(()) } } @@ -47,91 +61,84 @@ impl crate::ZbusRun for CtrlAnimeZbus { // If the try_lock *does* succeed then any other thread trying to lock will not // grab it until we finish. #[interface(name = "org.asuslinux.Anime")] -impl CtrlAnimeZbus { +impl AniMeZbus { /// Writes a data stream of length. Will force system thread to exit until /// it is restarted async fn write(&self, input: AnimeDataBuffer) -> zbus::fdo::Result<()> { - self.0 - .lock() - .await - .thread_exit - .store(true, Ordering::SeqCst); - self.0 - .lock() - .await - .write_data_buffer(input) - .map_err(|err| { - warn!("ctrl_anime::run_animation:callback {}", err); - err - })?; + let bright = self.0.config.lock().await.display_brightness; + self.0.set_builtins_enabled(false, bright).await?; + self.0.thread_exit.store(true, Ordering::SeqCst); + self.0.write_data_buffer(input).await.map_err(|err| { + warn!("ctrl_anime::run_animation:callback {}", err); + err + })?; Ok(()) } /// Set base brightness level #[zbus(property)] async fn brightness(&self) -> Brightness { - self.0.lock().await.config.display_brightness + if let Ok(config) = self.0.config.try_lock() { + return config.display_brightness; + } + Brightness::Off } /// Set base brightness level #[zbus(property)] async fn set_brightness(&self, brightness: Brightness) { self.0 - .lock() - .await - .node .write_bytes(&pkt_set_brightness(brightness)) + .await .map_err(|err| { warn!("ctrl_anime::set_brightness {}", err); }) .ok(); self.0 - .lock() - .await - .node .write_bytes(&pkt_set_enable_display(brightness != Brightness::Off)) + .await .map_err(|err| { warn!("ctrl_anime::set_brightness {}", err); }) .ok(); - self.0.lock().await.config.display_enabled = brightness != Brightness::Off; - self.0.lock().await.config.display_brightness = brightness; - self.0.lock().await.config.write(); + let mut config = self.0.config.lock().await; + config.display_enabled = brightness != Brightness::Off; + config.display_brightness = brightness; + config.write(); } #[zbus(property)] async fn builtins_enabled(&self) -> bool { - let lock = self.0.lock().await; - lock.config.builtin_anims_enabled + if let Ok(config) = self.0.config.try_lock() { + return config.builtin_anims_enabled; + } + false } /// Enable the builtin animations or not. This is quivalent to "Powersave /// animations" in Armory crate #[zbus(property)] async fn set_builtins_enabled(&self, enabled: bool) { - let brightness = self.0.lock().await.config.display_brightness; + let mut config = self.0.config.lock().await; + let brightness = config.display_brightness; self.0 - .lock() - .await - .node .set_builtins_enabled(enabled, brightness) + .await .map_err(|err| { warn!("ctrl_anime::set_builtins_enabled {}", err); }) .ok(); if !enabled { - let anime_type = self.0.lock().await.anime_type; + let anime_type = config.anime_type; let data = vec![255u8; anime_type.data_length()]; if let Ok(tmp) = AnimeDataBuffer::from_vec(anime_type, data).map_err(|err| { warn!("ctrl_anime::set_builtins_enabled {}", err); }) { self.0 - .lock() - .await - .node .write_bytes(tmp.data()) + .await .map_err(|err| { warn!("ctrl_anime::set_builtins_enabled {}", err); }) @@ -139,77 +146,78 @@ impl CtrlAnimeZbus { } } - self.0.lock().await.config.builtin_anims_enabled = enabled; - self.0.lock().await.config.write(); + config.builtin_anims_enabled = enabled; + config.write(); if enabled { - self.0 - .lock() - .await - .thread_exit - .store(true, Ordering::Release); + self.0.thread_exit.store(true, Ordering::Release); } } #[zbus(property)] async fn builtin_animations(&self) -> Animations { - self.0.lock().await.config.builtin_anims + if let Ok(config) = self.0.config.try_lock() { + return config.builtin_anims; + } + Animations::default() } /// Set which builtin animation is used for each stage #[zbus(property)] async fn set_builtin_animations(&self, settings: Animations) { self.0 - .lock() - .await - .node .write_bytes(&pkt_set_builtin_animations( settings.boot, settings.awake, settings.sleep, settings.shutdown, )) + .await .map_err(|err| { warn!("ctrl_anime::run_animation:callback {}", err); }) .ok(); self.0 - .lock() - .await - .node .write_bytes(&pkt_set_enable_powersave_anim(true)) + .await .map_err(|err| { warn!("ctrl_anime::run_animation:callback {}", err); }) .ok(); - self.0.lock().await.config.display_enabled = true; - self.0.lock().await.config.builtin_anims = settings; - self.0.lock().await.config.write(); + let mut config = self.0.config.lock().await; + config.display_enabled = true; + config.builtin_anims = settings; + config.write(); } #[zbus(property)] async fn enable_display(&self) -> bool { - self.0.lock().await.config.display_enabled + if let Ok(config) = self.0.config.try_lock() { + return config.display_enabled; + } + false } /// Set whether the AniMe is enabled at all #[zbus(property)] async fn set_enable_display(&self, enabled: bool) { self.0 - .lock() - .await - .node .write_bytes(&pkt_set_enable_display(enabled)) + .await .map_err(|err| { warn!("ctrl_anime::run_animation:callback {}", err); }) .ok(); - self.0.lock().await.config.display_enabled = enabled; - self.0.lock().await.config.write(); + let mut config = self.0.config.lock().await; + config.display_enabled = enabled; + config.write(); } #[zbus(property)] async fn off_when_unplugged(&self) -> bool { - self.0.lock().await.config.off_when_unplugged + if let Ok(config) = self.0.config.try_lock() { + return config.off_when_unplugged; + } + false } /// Set if to turn the AniMe Matrix off when external power is unplugged @@ -219,34 +227,40 @@ impl CtrlAnimeZbus { let pow = manager.on_external_power().await.unwrap_or_default(); self.0 - .lock() - .await - .node .write_bytes(&pkt_set_enable_display(!pow && !enabled)) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_lid_closed {}", err); }) .ok(); - self.0.lock().await.config.off_when_unplugged = enabled; - self.0.lock().await.config.write(); + let mut config = self.0.config.lock().await; + config.off_when_unplugged = enabled; + config.write(); } #[zbus(property)] async fn off_when_suspended(&self) -> bool { - self.0.lock().await.config.off_when_suspended + if let Ok(config) = self.0.config.try_lock() { + return config.off_when_suspended; + } + false } /// Set if to turn the AniMe Matrix off when the laptop is suspended #[zbus(property)] async fn set_off_when_suspended(&self, enabled: bool) { - self.0.lock().await.config.off_when_suspended = enabled; - self.0.lock().await.config.write(); + let mut config = self.0.config.lock().await; + config.off_when_suspended = enabled; + config.write(); } #[zbus(property)] async fn off_when_lid_closed(&self) -> bool { - self.0.lock().await.config.off_when_lid_closed + if let Ok(config) = self.0.config.try_lock() { + return config.off_when_lid_closed; + } + false } /// Set if to turn the AniMe Matrix off when the lid is closed @@ -256,47 +270,37 @@ impl CtrlAnimeZbus { let lid = manager.lid_closed().await.unwrap_or_default(); self.0 - .lock() - .await - .node .write_bytes(&pkt_set_enable_display(lid && !enabled)) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_lid_closed {}", err); }) .ok(); - self.0.lock().await.config.off_when_lid_closed = enabled; - self.0.lock().await.config.write(); + let mut config = self.0.config.lock().await; + config.off_when_lid_closed = enabled; + config.write(); } /// The main loop is the base system set action if the user isn't running /// the user daemon async fn run_main_loop(&self, start: bool) { if start { - self.0 - .lock() - .await - .thread_exit - .store(true, Ordering::SeqCst); - CtrlAnime::run_thread( - self.0.clone(), - self.0.lock().await.cache.system.clone(), - false, - ) - .await; + self.0.thread_exit.store(true, Ordering::SeqCst); + self.0.run_thread(self.0.cache.system.clone(), false).await; } } /// Get the device state as stored by asusd // #[zbus(property)] async fn device_state(&self) -> DeviceState { - DeviceState::from(&self.0.lock().await.config) + DeviceState::from(&*self.0.config.lock().await) } } -impl crate::CtrlTask for CtrlAnimeZbus { +impl crate::CtrlTask for AniMeZbus { fn zbus_path() -> &'static str { - ANIME_ZBUS_PATH + "ANIME_ZBUS_PATH" } async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> { @@ -309,21 +313,15 @@ impl crate::CtrlTask for CtrlAnimeZbus { // on_sleep let inner = inner1.clone(); async move { - let config = inner.lock().await.config.clone(); + let config = inner.config.lock().await.clone(); if config.display_enabled { - inner - .lock() - .await - .thread_exit - .store(true, Ordering::Release); // ensure clean slate + inner.thread_exit.store(true, Ordering::Release); // ensure clean slate inner - .lock() - .await - .node .write_bytes(&pkt_set_enable_display( !(sleeping && config.off_when_suspended), )) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_suspended {}", err); }) @@ -331,12 +329,10 @@ impl crate::CtrlTask for CtrlAnimeZbus { if config.builtin_anims_enabled { inner - .lock() - .await - .node .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); }) @@ -344,18 +340,11 @@ impl crate::CtrlTask for CtrlAnimeZbus { } else if !sleeping && !config.builtin_anims_enabled { // Run custom wake animation inner - .lock() - .await - .node .write_bytes(&pkt_set_enable_powersave_anim(false)) + .await .ok(); // ensure builtins are disabled - CtrlAnime::run_thread( - inner.clone(), - inner.lock().await.cache.wake.clone(), - true, - ) - .await; + inner.run_thread(inner.cache.wake.clone(), true).await; } } } @@ -364,26 +353,16 @@ impl crate::CtrlTask for CtrlAnimeZbus { // on_shutdown let inner = inner2.clone(); async move { - let AnimeConfig { + let AniMeConfig { display_enabled, builtin_anims_enabled, .. - } = inner.lock().await.config; + } = *inner.config.lock().await; if display_enabled && !builtin_anims_enabled { if shutting_down { - CtrlAnime::run_thread( - inner.clone(), - inner.lock().await.cache.shutdown.clone(), - true, - ) - .await; + inner.run_thread(inner.cache.shutdown.clone(), true).await; } else { - CtrlAnime::run_thread( - inner.clone(), - inner.lock().await.cache.boot.clone(), - true, - ) - .await; + inner.run_thread(inner.cache.boot.clone(), true).await; } } } @@ -392,28 +371,24 @@ impl crate::CtrlTask for CtrlAnimeZbus { let inner = inner3.clone(); // on lid change async move { - let AnimeConfig { + let AniMeConfig { off_when_lid_closed, builtin_anims_enabled, .. - } = inner.lock().await.config; + } = *inner.config.lock().await; if off_when_lid_closed { if builtin_anims_enabled { inner - .lock() - .await - .node .write_bytes(&pkt_set_enable_powersave_anim(!lid_closed)) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_suspended {}", err); }) .ok(); } inner - .lock() - .await - .node .write_bytes(&pkt_set_enable_display(!lid_closed)) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_lid_closed {}", err); }) @@ -425,39 +400,33 @@ impl crate::CtrlTask for CtrlAnimeZbus { let inner = inner4.clone(); // on power change async move { - let AnimeConfig { + let AniMeConfig { off_when_unplugged, builtin_anims_enabled, brightness_on_battery, .. - } = inner.lock().await.config; + } = *inner.config.lock().await; if off_when_unplugged { if builtin_anims_enabled { inner - .lock() - .await - .node .write_bytes(&pkt_set_enable_powersave_anim(power_plugged)) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_suspended {}", err); }) .ok(); } inner - .lock() - .await - .node .write_bytes(&pkt_set_enable_display(power_plugged)) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_unplugged {}", err); }) .ok(); } else { inner - .lock() - .await - .node .write_bytes(&pkt_set_brightness(brightness_on_battery)) + .await .map_err(|err| { warn!("create_sys_event_tasks::off_when_unplugged {}", err); }) @@ -472,51 +441,54 @@ impl crate::CtrlTask for CtrlAnimeZbus { } } -impl crate::Reloadable for CtrlAnimeZbus { +impl crate::Reloadable for AniMeZbus { async fn reload(&mut self) -> Result<(), RogError> { - if let Some(lock) = self.0.try_lock() { - let anim = &lock.config.builtin_anims; + if let Ok(config) = self.0.config.try_lock() { + let anim = &config.builtin_anims; // Set builtins - if lock.config.builtin_anims_enabled { - lock.node.write_bytes(&pkt_set_builtin_animations( - anim.boot, - anim.awake, - anim.sleep, - anim.shutdown, - ))?; + if config.builtin_anims_enabled { + self.0 + .write_bytes(&pkt_set_builtin_animations( + anim.boot, + anim.awake, + anim.sleep, + anim.shutdown, + )) + .await?; } // Builtins enabled or na? - lock.node.set_builtins_enabled( - lock.config.builtin_anims_enabled, - lock.config.display_brightness, - )?; + self.0 + .set_builtins_enabled(config.builtin_anims_enabled, config.display_brightness) + .await?; let manager = get_logind_manager().await; let lid_closed = manager.lid_closed().await.unwrap_or_default(); let power_plugged = manager.on_external_power().await.unwrap_or_default(); - let turn_off = (lid_closed && lock.config.off_when_lid_closed) - || (!power_plugged && lock.config.off_when_unplugged); - lock.node + let turn_off = (lid_closed && config.off_when_lid_closed) + || (!power_plugged && config.off_when_unplugged); + self.0 .write_bytes(&pkt_set_enable_display(!turn_off)) + .await .map_err(|err| { warn!("create_sys_event_tasks::reload {}", err); }) .ok(); - if turn_off || !lock.config.display_enabled { - lock.node.write_bytes(&pkt_set_enable_display(false))?; + if turn_off || !config.display_enabled { + self.0.write_bytes(&pkt_set_enable_display(false)).await?; // early return so we don't run animation thread return Ok(()); } - if !lock.config.builtin_anims_enabled && !lock.cache.boot.is_empty() { - lock.node + if !config.builtin_anims_enabled && !self.0.cache.boot.is_empty() { + self.0 .write_bytes(&pkt_set_enable_powersave_anim(false)) + .await .ok(); - let action = lock.cache.boot.clone(); - CtrlAnime::run_thread(self.0.clone(), action, true).await; + let action = self.0.cache.boot.clone(); + self.0.run_thread(action, true).await; } } Ok(()) diff --git a/asusd/src/ctrl_aura/config.rs b/asusd/src/aura_laptop/config.rs similarity index 94% rename from asusd/src/ctrl_aura/config.rs rename to asusd/src/aura_laptop/config.rs index e867fe2f..f2fdf93f 100644 --- a/asusd/src/ctrl_aura/config.rs +++ b/asusd/src/aura_laptop/config.rs @@ -14,6 +14,10 @@ use crate::error::RogError; #[derive(Deserialize, Serialize, Default, Debug, Clone)] // #[serde(default)] pub struct AuraConfig { + #[serde(skip)] + pub led_type: AuraDeviceType, + #[serde(skip)] + pub support_data: LedSupportData, pub config_name: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub ally_fix: Option, @@ -24,6 +28,8 @@ pub struct AuraConfig { pub multizone: Option>>, pub multizone_on: bool, pub enabled: LaptopAuraPower, + #[serde(skip)] + pub per_key_mode_active: bool, } impl StdConfig for AuraConfig { @@ -58,6 +64,8 @@ impl AuraConfig { let support_data = LedSupportData::get_data(prod_id); let enabled = LaptopAuraPower::new(device_type, &support_data); let mut config = AuraConfig { + led_type: device_type, + support_data, config_name: format!("aura_{prod_id}.ron"), ally_fix: None, brightness: LedBrightness::Med, @@ -66,17 +74,18 @@ impl AuraConfig { multizone: None, multizone_on: false, enabled, + per_key_mode_active: false, }; - for n in &support_data.basic_modes { + for n in &config.support_data.basic_modes { debug!("creating default for {n}"); config .builtins .insert(*n, AuraEffect::default_with_mode(*n)); - if !support_data.basic_zones.is_empty() { + if !config.support_data.basic_zones.is_empty() { let mut default = vec![]; - for (i, tmp) in support_data.basic_zones.iter().enumerate() { + for (i, tmp) in config.support_data.basic_zones.iter().enumerate() { default.push(AuraEffect { mode: *n, zone: *tmp, @@ -138,12 +147,9 @@ impl AuraConfig { /// Create a default for the `current_mode` if multizone and no config /// exists. - pub(super) fn create_multizone_default( - &mut self, - supported_data: &LedSupportData, - ) -> Result<(), RogError> { + pub fn create_multizone_default(&mut self) -> Result<(), RogError> { let mut default = vec![]; - for (i, tmp) in supported_data.basic_zones.iter().enumerate() { + for (i, tmp) in self.support_data.basic_zones.iter().enumerate() { default.push(AuraEffect { mode: self.current_mode, zone: *tmp, @@ -183,6 +189,9 @@ impl AuraConfig { } // Then replace just incase the initialised data contains new modes added config_loaded.builtins = config_init.builtins; + config_loaded.support_data = config_init.support_data; + config_loaded.led_type = config_init.led_type; + config_loaded.ally_fix = config_init.ally_fix; for enabled_init in &mut config_init.enabled.states { for enabled in &mut config_loaded.enabled.states { diff --git a/asusd/src/aura_laptop/mod.rs b/asusd/src/aura_laptop/mod.rs new file mode 100644 index 00000000..060a4f09 --- /dev/null +++ b/asusd/src/aura_laptop/mod.rs @@ -0,0 +1,219 @@ +use std::sync::Arc; + +use config::AuraConfig; +use config_traits::StdConfig; +use log::info; +use rog_aura::keyboard::{AuraLaptopUsbPackets, LedUsbPackets}; +use rog_aura::usb::{AURA_LAPTOP_LED_APPLY, AURA_LAPTOP_LED_SET}; +use rog_aura::{AuraDeviceType, AuraEffect, LedBrightness, PowerZones, AURA_LAPTOP_LED_MSG_LEN}; +use rog_platform::hid_raw::HidRaw; +use rog_platform::keyboard_led::KeyboardBacklight; +use tokio::sync::{Mutex, MutexGuard}; + +use crate::error::RogError; + +pub mod config; +pub mod trait_impls; + +#[derive(Debug, Clone)] +pub struct Aura { + pub hid: Option>>, + pub backlight: Option>>, + pub config: Arc>, +} + +impl Aura { + /// Initialise the device if required. + pub async fn do_initialization(&self) -> Result<(), RogError> { + Ok(()) + } + + pub async fn lock_config(&self) -> MutexGuard { + self.config.lock().await + } + + /// Will lock the internal config and update. If anything else has locked + /// this in scope then a deadlock can occur. + pub async fn update_config(&self) -> Result<(), RogError> { + let mut config = self.config.lock().await; + let bright = if let Some(bl) = self.backlight.as_ref() { + bl.lock().await.get_brightness().unwrap_or_default() + } else { + config.brightness.into() + }; + config.read(); + config.brightness = bright.into(); + config.write(); + Ok(()) + } + + pub async fn write_current_config_mode(&self, config: &mut AuraConfig) -> Result<(), RogError> { + if config.multizone_on { + let mode = config.current_mode; + let mut create = false; + // There is no multizone config for this mode so create one here + // using the colours of rainbow if it exists, or first available + // mode, or random + if config.multizone.is_none() { + create = true; + } else if let Some(multizones) = config.multizone.as_ref() { + if !multizones.contains_key(&mode) { + create = true; + } + } + if create { + info!("No user-set config for zone founding, attempting a default"); + config.create_multizone_default()?; + } + + if let Some(multizones) = config.multizone.as_mut() { + if let Some(set) = multizones.get(&mode) { + for mode in set.clone() { + self.write_effect_and_apply(config.led_type, &mode).await?; + } + } + } + } else { + let mode = config.current_mode; + if let Some(effect) = config.builtins.get(&mode).cloned() { + self.write_effect_and_apply(config.led_type, &effect) + .await?; + } + } + + Ok(()) + } + + /// Write the AuraEffect to the device. Will lock `backlight` or `hid`. + /// + /// If per-key or software-mode is active it must be marked as disabled in + /// config. + pub async fn write_effect_and_apply( + &self, + dev_type: AuraDeviceType, + mode: &AuraEffect, + ) -> Result<(), RogError> { + if matches!(dev_type, AuraDeviceType::LaptopKeyboardTuf) { + if let Some(platform) = &self.backlight { + let buf = [ + 1, + mode.mode as u8, + mode.colour1.r, + mode.colour1.g, + mode.colour1.b, + mode.speed as u8, + ]; + platform.lock().await.set_kbd_rgb_mode(&buf)?; + } + } else if let Some(hid_raw) = &self.hid { + let bytes: [u8; AURA_LAPTOP_LED_MSG_LEN] = mode.into(); + let hid_raw = hid_raw.lock().await; + hid_raw.write_bytes(&bytes)?; + hid_raw.write_bytes(&AURA_LAPTOP_LED_SET)?; + // Changes won't persist unless apply is set + hid_raw.write_bytes(&AURA_LAPTOP_LED_APPLY)?; + } else { + return Err(RogError::NoAuraKeyboard); + } + + Ok(()) + } + + pub async fn set_brightness(&self, value: u8) -> Result<(), RogError> { + if let Some(backlight) = &self.backlight { + backlight.lock().await.set_brightness(value)?; + return Ok(()); + } + Err(RogError::MissingFunction( + "No LED backlight control available".to_string(), + )) + } + + /// Set combination state for boot animation/sleep animation/all leds/keys + /// leds/side leds LED active + pub async fn set_power_states(&self, config: &AuraConfig) -> Result<(), RogError> { + if matches!(config.led_type, rog_aura::AuraDeviceType::LaptopKeyboardTuf) { + if let Some(backlight) = &self.backlight { + // TODO: tuf bool array + let buf = config.enabled.to_bytes(config.led_type); + backlight.lock().await.set_kbd_rgb_state(&buf)?; + } + } else if let Some(hid_raw) = &self.hid { + let hid_raw = hid_raw.lock().await; + if let Some(p) = config.enabled.states.first() { + if p.zone == PowerZones::Ally { + let msg = [0x5d, 0xd1, 0x09, 0x01, p.new_to_byte() as u8, 0x0, 0x0]; + hid_raw.write_bytes(&msg)?; + return Ok(()); + } + } + + let bytes = config.enabled.to_bytes(config.led_type); + let msg = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3]]; + hid_raw.write_bytes(&msg)?; + } + Ok(()) + } + + /// Write an effect block. This is for per-key, but can be repurposed to + /// write the raw factory mode packets - when doing this it is expected that + /// only the first `Vec` (`effect[0]`) is valid. + pub async fn write_effect_block( + &self, + config: &mut AuraConfig, + effect: &AuraLaptopUsbPackets, + ) -> Result<(), RogError> { + if config.brightness == LedBrightness::Off { + config.brightness = LedBrightness::Med; + config.write(); + } + + let pkt_type = effect[0][1]; + const PER_KEY_TYPE: u8 = 0xbc; + + if let Some(hid_raw) = &self.hid { + let hid_raw = hid_raw.lock().await; + if pkt_type != PER_KEY_TYPE { + config.per_key_mode_active = false; + hid_raw.write_bytes(&effect[0])?; + hid_raw.write_bytes(&AURA_LAPTOP_LED_SET)?; + // hid_raw.write_bytes(&LED_APPLY)?; + } else { + if !config.per_key_mode_active { + let init = LedUsbPackets::get_init_msg(); + hid_raw.write_bytes(&init)?; + config.per_key_mode_active = true; + } + for row in effect.iter() { + hid_raw.write_bytes(row)?; + } + } + } else if matches!(config.led_type, rog_aura::AuraDeviceType::LaptopKeyboardTuf) { + if let Some(tuf) = &self.backlight { + for row in effect.iter() { + let r = row[9]; + let g = row[10]; + let b = row[11]; + tuf.lock().await.set_kbd_rgb_mode(&[0, 0, r, g, b, 0])?; + } + } + } + Ok(()) + } + + pub async fn fix_ally_power(&mut self) -> Result<(), RogError> { + if self.config.lock().await.led_type == AuraDeviceType::Ally { + if let Some(hid_raw) = &self.hid { + let mut config = self.config.lock().await; + if config.ally_fix.is_none() { + let msg = [0x5d, 0xbd, 0x01, 0xff, 0xff, 0xff, 0xff]; + hid_raw.lock().await.write_bytes(&msg)?; + info!("Reset Ally power settings to base"); + config.ally_fix = Some(true); + } + config.write(); + } + } + Ok(()) + } +} diff --git a/asusd/src/aura_laptop/trait_impls.rs b/asusd/src/aura_laptop/trait_impls.rs new file mode 100644 index 00000000..961c8a95 --- /dev/null +++ b/asusd/src/aura_laptop/trait_impls.rs @@ -0,0 +1,343 @@ +use std::collections::BTreeMap; + +use config_traits::StdConfig; +use log::{debug, error, info, warn}; +use rog_aura::keyboard::{AuraLaptopUsbPackets, LaptopAuraPower}; +use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones}; +use zbus::fdo::Error as ZbErr; +use zbus::object_server::SignalEmitter; +use zbus::zvariant::OwnedObjectPath; +use zbus::{interface, Connection}; + +use super::Aura; +use crate::error::RogError; +use crate::{CtrlTask, Reloadable}; + +pub const AURA_ZBUS_NAME: &str = "Aura"; +pub const AURA_ZBUS_PATH: &str = "/org/asuslinux"; + +#[derive(Clone)] +pub struct AuraZbus(Aura); + +impl AuraZbus { + pub fn new(aura: Aura) -> Self { + Self(aura) + } + + pub async fn start_tasks( + mut self, + connection: &Connection, + // _signal_ctx: SignalEmitter<'static>, + path: OwnedObjectPath, + ) -> Result<(), RogError> { + // let task = zbus.clone(); + // let signal_ctx = signal_ctx.clone(); + self.reload() + .await + .unwrap_or_else(|err| warn!("Controller error: {}", err)); + connection + .object_server() + .at(path.clone(), self) + .await + .map_err(|e| error!("Couldn't add server at path: {path}, {e:?}")) + .ok(); + // TODO: skip this until we keep handles to tasks so they can be killed + // task.create_tasks(signal_ctx).await + Ok(()) + } +} + +/// The main interface for changing, reading, or notfying +/// +/// LED commands are split between Brightness, Modes, Per-Key +#[interface(name = "org.asuslinux.Aura")] +impl AuraZbus { + /// Return the device type for this Aura keyboard + #[zbus(property)] + async fn device_type(&self) -> AuraDeviceType { + self.0.config.lock().await.led_type + } + + /// Return the current LED brightness + #[zbus(property)] + async fn brightness(&self) -> Result { + if let Some(bl) = self.0.backlight.as_ref() { + return Ok(bl.lock().await.get_brightness().map(|n| n.into())?); + } + Err(ZbErr::Failed("No sysfs brightness control".to_string())) + } + + /// Set the keyboard brightness level (0-3) + #[zbus(property)] + async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> { + if let Some(bl) = self.0.backlight.as_ref() { + return Ok(bl.lock().await.set_brightness(brightness.into())?); + } + Err(ZbErr::Failed("No sysfs brightness control".to_string())) + } + + /// Total levels of brightness available + #[zbus(property)] + async fn supported_brightness(&self) -> Vec { + vec![ + LedBrightness::Off, + LedBrightness::Low, + LedBrightness::Med, + LedBrightness::High, + ] + } + + /// The total available modes + #[zbus(property)] + async fn supported_basic_modes(&self) -> Result, ZbErr> { + let config = self.0.config.lock().await; + Ok(config.builtins.keys().cloned().collect()) + } + + #[zbus(property)] + async fn supported_basic_zones(&self) -> Result, ZbErr> { + let config = self.0.config.lock().await; + Ok(config.support_data.basic_zones.clone()) + } + + #[zbus(property)] + async fn supported_power_zones(&self) -> Result, ZbErr> { + let config = self.0.config.lock().await; + Ok(config.support_data.power_zones.clone()) + } + + /// The current mode data + #[zbus(property)] + async fn led_mode(&self) -> Result { + // entirely possible to deadlock here, so use try instead of lock() + // let ctrl = self.0.lock().await; + // Ok(config.current_mode) + if let Ok(config) = self.0.config.try_lock() { + Ok(config.current_mode) + } else { + Err(ZbErr::Failed("Aura control couldn't lock self".to_string())) + } + } + + /// Set an Aura effect if the effect mode or zone is supported. + /// + /// On success the aura config file is read to refresh cached values, then + /// the effect is stored and config written to disk. + #[zbus(property)] + async fn set_led_mode(&mut self, num: AuraModeNum) -> Result<(), ZbErr> { + let mut config = self.0.config.lock().await; + config.current_mode = num; + self.0.write_current_config_mode(&mut config).await?; + if config.brightness == LedBrightness::Off { + config.brightness = LedBrightness::Med; + } + self.0.set_brightness(config.brightness.into()).await?; + config.write(); + Ok(()) + } + + /// The current mode data + #[zbus(property)] + async fn led_mode_data(&self) -> Result { + // entirely possible to deadlock here, so use try instead of lock() + if let Ok(config) = self.0.config.try_lock() { + let mode = config.current_mode; + match config.builtins.get(&mode) { + Some(effect) => Ok(effect.clone()), + None => Err(ZbErr::Failed("Could not get the current effect".into())), + } + } else { + Err(ZbErr::Failed("Aura control couldn't lock self".to_string())) + } + } + + /// Set an Aura effect if the effect mode or zone is supported. + /// + /// On success the aura config file is read to refresh cached values, then + /// the effect is stored and config written to disk. + #[zbus(property)] + async fn set_led_mode_data(&mut self, effect: AuraEffect) -> Result<(), ZbErr> { + let mut config = self.0.config.lock().await; + if !config.support_data.basic_modes.contains(&effect.mode) + || effect.zone != AuraZone::None + && !config.support_data.basic_zones.contains(&effect.zone) + { + return Err(ZbErr::NotSupported(format!( + "The Aura effect is not supported: {effect:?}" + ))); + } + + self.0 + .write_effect_and_apply(config.led_type, &effect) + .await?; + if config.brightness == LedBrightness::Off { + config.brightness = LedBrightness::Med; + } + self.0.set_brightness(config.brightness.into()).await?; + config.set_builtin(effect); + config.write(); + + Ok(()) + } + + /// Get the data set for every mode available + async fn all_mode_data(&self) -> BTreeMap { + let config = self.0.config.lock().await; + config.builtins.clone() + } + + // As property doesn't work for AuraPowerDev (complexity of serialization?) + #[zbus(property)] + async fn led_power(&self) -> LaptopAuraPower { + let config = self.0.config.lock().await; + config.enabled.clone() + } + + /// Set a variety of states, input is array of enum. + /// `enabled` sets if the sent array should be disabled or enabled + /// + /// For Modern ROG devices the "enabled" flag is ignored. + #[zbus(property)] + async fn set_led_power(&mut self, options: LaptopAuraPower) -> Result<(), ZbErr> { + let mut config = self.0.config.lock().await; + for opt in options.states { + let zone = opt.zone; + for config in config.enabled.states.iter_mut() { + if config.zone == zone { + *config = opt; + } + } + } + config.write(); + Ok(self.0.set_power_states(&config).await.map_err(|e| { + warn!("{}", e); + e + })?) + } + + /// On machine that have some form of either per-key keyboard or per-zone + /// this can be used to write custom effects over dbus. The input is a + /// nested `Vec>` where `Vec` is a raw USB packet + async fn direct_addressing_raw(&self, data: AuraLaptopUsbPackets) -> Result<(), ZbErr> { + let mut config = self.0.config.lock().await; + self.0.write_effect_block(&mut config, &data).await?; + Ok(()) + } +} + +impl CtrlTask for AuraZbus { + fn zbus_path() -> &'static str { + "/org/asuslinux" + } + + 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 { + info!("CtrlKbdLedTask reloading brightness and modes"); + if let Some(backlight) = &inner1.backlight { + backlight + .lock() + .await + .set_brightness(inner1.config.lock().await.brightness.into()) + .map_err(|e| { + error!("CtrlKbdLedTask: {e}"); + e + }) + .unwrap(); + } + 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; + + // let ctrl2 = self.0.clone(); + // let ctrl = self.0.lock().await; + // if ctrl.led_node.has_brightness_control() { + // let watch = ctrl.led_node.monitor_brightness()?; + // tokio::spawn(async move { + // let mut buffer = [0; 32]; + // watch + // .into_event_stream(&mut buffer) + // .unwrap() + // .for_each(|_| async { + // if let Some(lock) = ctrl2.try_lock() { + // load_save(true, lock).unwrap(); // unwrap as we want + // // to + // // bomb out of the + // // task + // } + // }) + // .await; + // }); + // } + + Ok(()) + } +} + +impl Reloadable for AuraZbus { + async fn reload(&mut self) -> Result<(), RogError> { + self.0.fix_ally_power().await?; + debug!("reloading keyboard mode"); + let mut config = self.0.lock_config().await; + self.0.write_current_config_mode(&mut config).await?; + debug!("reloading power states"); + self.0 + .set_power_states(&config) + .await + .map_err(|err| warn!("{err}")) + .ok(); + Ok(()) + } +} diff --git a/asusd/src/aura_manager.rs b/asusd/src/aura_manager.rs new file mode 100644 index 00000000..d355823a --- /dev/null +++ b/asusd/src/aura_manager.rs @@ -0,0 +1,368 @@ +// Plan: +// - Manager has udev monitor on USB looking for ROG devices +// - If a device is found, add it to watch +// - Add it to Zbus server +// - If udev sees device removed then remove the zbus path + +use std::sync::Arc; + +use futures_lite::future::block_on; +use log::{debug, error, info, warn}; +use mio::{Events, Interest, Poll, Token}; +use rog_platform::error::PlatformError; +use rog_platform::hid_raw::HidRaw; +use tokio::sync::Mutex; +use udev::{Device, MonitorBuilder}; +use zbus::zvariant::{ObjectPath, OwnedObjectPath}; +use zbus::Connection; + +use crate::aura_anime::trait_impls::AniMeZbus; +use crate::aura_laptop::trait_impls::AuraZbus; +use crate::aura_slash::trait_impls::SlashZbus; +use crate::aura_types::DeviceHandle; +use crate::error::RogError; + +pub const ASUS_ZBUS_PATH: &str = "/org/asuslinux"; + +/// Returns only the Device details concatenated in a form usable for +/// adding/appending to a filename +pub fn filename_partial(parent: &Device) -> Option { + if let Some(id_product) = parent.attribute_value("idProduct") { + let id_product = id_product.to_string_lossy(); + let mut path = if let Some(devnum) = parent.attribute_value("devnum") { + let devnum = devnum.to_string_lossy(); + if let Some(devpath) = parent.attribute_value("devpath") { + let devpath = devpath.to_string_lossy(); + format!("{id_product}_{devnum}_{devpath}") + } else { + format!("{id_product}_{devnum}") + } + } else { + format!("{id_product}") + }; + if path.contains('.') { + warn!("dbus path for {id_product} contains `.`, removing"); + path.replace('.', "").clone_into(&mut path); + } + return Some(ObjectPath::from_str_unchecked(&path).into()); + } + None +} + +fn dbus_path_for_dev(parent: &Device) -> Option { + if let Some(filename) = filename_partial(parent) { + return Some( + ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/{filename}")).into(), + ); + } + None +} + +fn dbus_path_for_tuf() -> OwnedObjectPath { + ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/tuf")).into() +} + +fn dbus_path_for_slash() -> OwnedObjectPath { + ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/slash")).into() +} + +fn dbus_path_for_anime() -> OwnedObjectPath { + ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/anime")).into() +} + +// TODO: +// - make this the HID manager (and universal) +// - *really* need to make most of this actual kernel drivers +// - LED class +// - RGB modes (how, attribute?) +// - power features (how, attribute?) +// - what about per-key stuff? +// - how would the AniMe be exposed? Just a series of LEDs? + +/// A device. +/// +/// Each controller within should track its dbus path so it can be removed if +/// required. +#[derive(Debug)] +pub struct AsusDevice { + device: DeviceHandle, + dbus_path: OwnedObjectPath, +} + +pub struct DeviceManager { + _dbus_connection: Connection, +} + +impl DeviceManager { + async fn init_hid_devices( + connection: &Connection, + device: Device, + ) -> Result, RogError> { + let mut devices = Vec::new(); + if let Some(usb_device) = device.parent_with_subsystem_devtype("usb", "usb_device")? { + if let Some(usb_id) = usb_device.attribute_value("idProduct") { + if let Some(vendor_id) = usb_device.attribute_value("idVendor") { + if vendor_id != "0b05" { + debug!("Not ASUS vendor ID"); + return Ok(devices); + } + // Almost all devices are identified by the productId. + // So let's see what we have and: + // 1. Generate an interface path + // 2. Create the device + // Use the top-level endpoint, not the parent + if let Ok(hidraw) = HidRaw::from_device(device) { + debug!("Testing device {usb_id:?}"); + let dev = Arc::new(Mutex::new(hidraw)); + // SLASH DEVICE + if let Ok(dev_type) = DeviceHandle::new_slash_hid( + dev.clone(), + usb_id.to_str().unwrap_or_default(), + ) + .await + { + if let DeviceHandle::Slash(slash) = dev_type.clone() { + let path = + dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_slash()); + let ctrl = SlashZbus::new(slash); + ctrl.start_tasks(connection, path.clone()).await.unwrap(); + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + }); + } + } + // ANIME MATRIX DEVICE + if let Ok(dev_type) = DeviceHandle::maybe_anime_hid( + dev.clone(), + usb_id.to_str().unwrap_or_default(), + ) + .await + { + if let DeviceHandle::AniMe(anime) = dev_type.clone() { + let path = + dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_anime()); + let ctrl = AniMeZbus::new(anime); + ctrl.start_tasks(connection, path.clone()).await.unwrap(); + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + }); + } + } + // AURA LAPTOP DEVICE + if let Ok(dev_type) = DeviceHandle::maybe_laptop_aura( + dev, + usb_id.to_str().unwrap_or_default(), + ) + .await + { + if let DeviceHandle::Aura(aura) = dev_type.clone() { + let path = + dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_tuf()); + let ctrl = AuraZbus::new(aura); + ctrl.start_tasks(connection, path.clone()).await.unwrap(); + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + }); + } + } + } + } + } + } + Ok(devices) + } + + /// To be called on daemon startup + async fn init_all_hid(connection: &Connection) -> Result, RogError> { + // track and ensure we use only one hidraw per prod_id + // let mut interfaces = HashSet::new(); + let mut devices: Vec = Vec::new(); + + let mut enumerator = udev::Enumerator::new().map_err(|err| { + warn!("{}", err); + PlatformError::Udev("enumerator failed".into(), err) + })?; + + enumerator.match_subsystem("hidraw").map_err(|err| { + warn!("{}", err); + PlatformError::Udev("match_subsystem failed".into(), err) + })?; + + for device in enumerator + .scan_devices() + .map_err(|e| PlatformError::IoPath("enumerator".to_owned(), e))? + { + devices.append(&mut Self::init_hid_devices(connection, device).await?); + } + // debug!("Found devices: {devices:?}"); + + Ok(devices) + } + + pub async fn find_all_devices(connection: &Connection) -> Vec { + let mut devices: Vec = Vec::new(); + // HID first, always + if let Ok(devs) = &mut Self::init_all_hid(connection).await { + devices.append(devs); + } + // USB after, need to check if HID picked something up and if so, skip it + let mut do_anime = true; + let mut do_slash = true; + for dev in devices.iter() { + if matches!(dev.device, DeviceHandle::Slash(_)) { + do_slash = false; + } + if matches!(dev.device, DeviceHandle::AniMe(_)) { + do_anime = false; + } + } + + if do_slash { + 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); + ctrl.start_tasks(connection, path.clone()).await.unwrap(); + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + }); + } + } else { + info!("Tested device was not Slash"); + } + } + + 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); + ctrl.start_tasks(connection, path.clone()).await.unwrap(); + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + }); + } + } else { + info!("Tested device was not AniMe Matrix"); + } + } + devices + } + + pub async fn new(connection: Connection) -> Result { + let conn_copy = connection.clone(); + let devices = Arc::new(Mutex::new(Self::find_all_devices(&conn_copy).await)); + let manager = Self { + _dbus_connection: connection, + }; + + // TODO: The /sysfs/ LEDs don't cause events, so they need to be manually + // checked for and added + + // detect all plugged in aura devices (eventually) + // only USB devices are detected for here + std::thread::spawn(move || { + let mut monitor = MonitorBuilder::new()?.match_subsystem("hidraw")?.listen()?; + let mut poll = Poll::new()?; + let mut events = Events::with_capacity(1024); + poll.registry() + .register(&mut monitor, Token(0), Interest::READABLE)?; + + let rt = tokio::runtime::Runtime::new().expect("Unable to create Runtime"); + let _enter = rt.enter(); + loop { + if poll.poll(&mut events, None).is_err() { + continue; + } + for event in monitor.iter() { + let action = event.action().unwrap_or_default(); + + if let Some(parent) = + event.parent_with_subsystem_devtype("usb", "usb_device")? + { + let devices = devices.clone(); + + if action == "remove" { + if let Some(path) = dbus_path_for_dev(&parent) { + let conn_copy = conn_copy.clone(); + tokio::spawn(async move { + // Find the indexs of devices matching the path + let removals: Vec = devices + .lock() + .await + .iter() + .enumerate() + .filter_map(|(i, dev)| { + if dev.dbus_path == path { + Some(i) + } else { + None + } + }) + .collect(); + if removals.is_empty() { + return Ok(()); + } + info!("removing: {path:?}"); + // Iter in reverse so as to not screw up indexing + for index in removals.iter().rev() { + let dev = devices.lock().await.remove(*index); + let path = path.clone(); + let res = match dev.device { + DeviceHandle::Aura(_) => { + conn_copy + .object_server() + .remove::(&path) + .await? + } + DeviceHandle::Slash(_) => { + conn_copy + .object_server() + .remove::(&path) + .await? + } + DeviceHandle::AniMe(_) => { + conn_copy + .object_server() + .remove::(&path) + .await? + } + DeviceHandle::Ally(_) => todo!(), + DeviceHandle::OldAura(_) => todo!(), + DeviceHandle::TufLedClass(_) => todo!(), + DeviceHandle::MulticolourLed => todo!(), + DeviceHandle::None => todo!(), + }; + info!("AuraManager removed: {path:?}, {res}"); + } + Ok::<(), RogError>(()) + }); + } + } else if action == "add" { + let evdev = event.device(); + let conn_copy = conn_copy.clone(); + block_on(async move { + if let Ok(mut new_devs) = Self::init_hid_devices(&conn_copy, evdev) + .await + .map_err(|e| error!("Couldn't add new device: {e:?}")) + { + devices.lock().await.append(&mut new_devs); + } + }); + }; + } + } + } + // Required for return type on spawn + #[allow(unreachable_code)] + Ok::<(), RogError>(()) + }); + Ok(manager) + } +} diff --git a/asusd/src/ctrl_slash/config.rs b/asusd/src/aura_slash/config.rs similarity index 89% rename from asusd/src/ctrl_slash/config.rs rename to asusd/src/aura_slash/config.rs index 2eee7f5c..b05e664a 100644 --- a/asusd/src/ctrl_slash/config.rs +++ b/asusd/src/aura_slash/config.rs @@ -1,5 +1,5 @@ use config_traits::{StdConfig, StdConfigLoad}; -use rog_slash::{DeviceState, SlashMode}; +use rog_slash::{DeviceState, SlashMode, SlashType}; use serde::{Deserialize, Serialize}; const CONFIG_FILE: &str = "slash.ron"; @@ -7,6 +7,8 @@ const CONFIG_FILE: &str = "slash.ron"; /// Config for base system actions for the anime display #[derive(Deserialize, Serialize, Debug)] pub struct SlashConfig { + #[serde(skip)] + pub slash_type: SlashType, pub slash_enabled: bool, pub slash_brightness: u8, pub slash_interval: u8, @@ -20,6 +22,7 @@ impl Default for SlashConfig { slash_brightness: 255, slash_interval: 0, slash_mode: SlashMode::Bounce, + slash_type: SlashType::Unsupported, } } } diff --git a/asusd/src/aura_slash/mod.rs b/asusd/src/aura_slash/mod.rs new file mode 100644 index 00000000..904118aa --- /dev/null +++ b/asusd/src/aura_slash/mod.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use config::SlashConfig; +use rog_platform::hid_raw::HidRaw; +use rog_platform::usb_raw::USBRaw; +use rog_slash::usb::{pkt_set_mode, pkt_set_options, pkts_for_init}; +use rog_slash::SlashType; +use tokio::sync::{Mutex, MutexGuard}; + +use crate::error::RogError; + +pub mod config; +pub mod trait_impls; + +#[derive(Debug, Clone)] +pub struct Slash { + hid: Option>>, + usb: Option>>, + config: Arc>, +} + +impl Slash { + pub fn new( + hid: Option>>, + usb: Option>>, + config: Arc>, + ) -> Self { + Self { hid, usb, config } + } + + pub async fn lock_config(&self) -> MutexGuard { + self.config.lock().await + } + + pub async fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> { + if let Some(hid) = &self.hid { + hid.lock().await.write_bytes(message)?; + } else if let Some(usb) = &self.usb { + usb.lock().await.write_bytes(message)?; + } + Ok(()) + } + + /// Initialise the device if required. Locks the internal config so be wary + /// of deadlocks. + pub async fn do_initialization(&self) -> Result<(), RogError> { + // Don't try to initialise these models as the asus drivers already did + let config = self.config.lock().await; + if !matches!(config.slash_type, SlashType::GA605 | SlashType::GU605) { + for pkt in &pkts_for_init(config.slash_type) { + self.write_bytes(pkt).await?; + } + } + + // Apply config upon initialization + let option_packets = pkt_set_options( + config.slash_type, + config.slash_enabled, + config.slash_brightness, + config.slash_interval, + ); + self.write_bytes(&option_packets).await?; + + let mode_packets = pkt_set_mode(config.slash_type, config.slash_mode); + // self.node.write_bytes(&mode_packets[0])?; + self.write_bytes(&mode_packets[1]).await?; + + Ok(()) + } +} diff --git a/asusd/src/aura_slash/trait_impls.rs b/asusd/src/aura_slash/trait_impls.rs new file mode 100644 index 00000000..33f48409 --- /dev/null +++ b/asusd/src/aura_slash/trait_impls.rs @@ -0,0 +1,190 @@ +use config_traits::StdConfig; +use log::{debug, error, warn}; +use rog_slash::usb::{pkt_save, pkt_set_mode, pkt_set_options}; +use rog_slash::{DeviceState, SlashMode}; +use zbus::zvariant::OwnedObjectPath; +use zbus::{interface, Connection}; + +use super::Slash; +use crate::error::RogError; +use crate::Reloadable; + +#[derive(Clone)] +pub struct SlashZbus(Slash); + +impl SlashZbus { + pub fn new(slash: Slash) -> Self { + Self(slash) + } + + pub async fn start_tasks( + mut self, + connection: &Connection, + path: OwnedObjectPath, + ) -> Result<(), RogError> { + // let task = zbus.clone(); + self.reload() + .await + .unwrap_or_else(|err| warn!("Controller error: {}", err)); + connection + .object_server() + .at(path.clone(), self) + .await + .map_err(|e| error!("Couldn't add server at path: {path}, {e:?}")) + .ok(); + Ok(()) + } +} + +#[interface(name = "org.asuslinux.Slash")] +impl SlashZbus { + /// Get enabled or not + #[zbus(property)] + async fn enabled(&self) -> bool { + let lock = self.0.lock_config().await; + lock.slash_enabled + } + + /// Set enabled true or false + #[zbus(property)] + async fn set_enabled(&self, enabled: bool) { + let mut config = self.0.lock_config().await; + let brightness = if enabled && config.slash_brightness == 0 { + 0x88 + } else { + config.slash_brightness + }; + self.0 + .write_bytes(&pkt_set_options( + config.slash_type, + enabled, + brightness, + config.slash_interval, + )) + .await + .map_err(|err| { + warn!("ctrl_slash::set_options {}", err); + }) + .ok(); + + config.slash_enabled = enabled; + config.slash_brightness = brightness; + config.write(); + } + + /// Get brightness level + #[zbus(property)] + async fn brightness(&self) -> u8 { + let config = self.0.lock_config().await; + config.slash_brightness + } + + /// Set brightness level + #[zbus(property)] + async fn set_brightness(&self, brightness: u8) { + let mut config = self.0.lock_config().await; + let enabled = brightness > 0; + self.0 + .write_bytes(&pkt_set_options( + config.slash_type, + enabled, + brightness, + config.slash_interval, + )) + .await + .map_err(|err| { + warn!("ctrl_slash::set_options {}", err); + }) + .ok(); + + config.slash_enabled = enabled; + config.slash_brightness = brightness; + config.write(); + } + + #[zbus(property)] + async fn interval(&self) -> u8 { + let config = self.0.lock_config().await; + config.slash_interval + } + + /// Set interval between slash animations (0-255) + #[zbus(property)] + async fn set_interval(&self, interval: u8) { + let mut config = self.0.lock_config().await; + self.0 + .write_bytes(&pkt_set_options( + config.slash_type, + config.slash_enabled, + config.slash_brightness, + interval, + )) + .await + .map_err(|err| { + warn!("ctrl_slash::set_options {}", err); + }) + .ok(); + + config.slash_interval = interval; + config.write(); + } + + #[zbus(property)] + async fn slash_mode(&self) -> u8 { + let config = self.0.lock_config().await; + config.slash_interval + } + + /// Set interval between slash animations (0-255) + #[zbus(property)] + async fn set_slash_mode(&self, slash_mode: SlashMode) { + let mut config = self.0.lock_config().await; + + let command_packets = pkt_set_mode(config.slash_type, slash_mode); + // self.node.write_bytes(&command_packets[0])?; + self.0 + .write_bytes(&command_packets[1]) + .await + .map_err(|err| { + warn!("ctrl_slash::set_options {}", err); + }) + .ok(); + self.0 + .write_bytes(&pkt_save(config.slash_type)) + .await + .map_err(|err| { + warn!("ctrl_slash::set_options {}", err); + }) + .ok(); + + config.slash_mode = slash_mode; + config.write(); + } + + /// Get the device state as stored by asusd + // #[zbus(property)] + async fn device_state(&self) -> DeviceState { + let config = self.0.lock_config().await; + DeviceState::from(&*config) + } +} + +impl Reloadable for SlashZbus { + async fn reload(&mut self) -> Result<(), RogError> { + debug!("reloading slash settings"); + let config = self.0.lock_config().await; + self.0 + .write_bytes(&pkt_set_options( + config.slash_type, + config.slash_enabled, + config.slash_brightness, + config.slash_interval, + )) + .await + .map_err(|err| { + warn!("ctrl_slash::set_options {}", err); + }) + .ok(); + Ok(()) + } +} diff --git a/asusd/src/aura_types.rs b/asusd/src/aura_types.rs new file mode 100644 index 00000000..e222083c --- /dev/null +++ b/asusd/src/aura_types.rs @@ -0,0 +1,183 @@ +use std::sync::Arc; + +use config_traits::{StdConfig, StdConfigLoad}; +use log::{debug, error, info}; +use rog_anime::error::AnimeError; +use rog_anime::usb::get_anime_type; +use rog_anime::AnimeType; +use rog_aura::AuraDeviceType; +use rog_platform::hid_raw::HidRaw; +use rog_platform::keyboard_led::KeyboardBacklight; +use rog_platform::usb_raw::USBRaw; +use rog_slash::error::SlashError; +use rog_slash::SlashType; +use tokio::sync::Mutex; + +use crate::aura_anime::config::AniMeConfig; +use crate::aura_anime::AniMe; +use crate::aura_laptop::config::AuraConfig; +use crate::aura_laptop::Aura; +use crate::aura_slash::config::SlashConfig; +use crate::aura_slash::Slash; +use crate::error::RogError; + +pub enum _DeviceHandle { + /// The AniMe devices require USBRaw as they are not HID devices + Usb(USBRaw), + HidRaw(HidRaw), + LedClass(KeyboardBacklight), + /// TODO + MulticolourLed, + None, +} + +#[derive(Debug, Clone)] +pub enum DeviceHandle { + Aura(Aura), + Slash(Slash), + /// The AniMe devices require USBRaw as they are not HID devices + AniMe(AniMe), + Ally(Arc>), + OldAura(Arc>), + /// TUF laptops have an aditional set of attributes added to the LED /sysfs/ + TufLedClass(Arc>), + /// TODO + MulticolourLed, + None, +} + +impl DeviceHandle { + /// Try Slash HID. If one exists it is initialsed and returned. + pub async fn new_slash_hid( + device: Arc>, + prod_id: &str, + ) -> Result { + debug!("Testing for HIDRAW Slash"); + let slash_type = SlashType::from_dmi(); + if matches!(slash_type, SlashType::Unsupported) + || slash_type + .prod_id_str() + .to_lowercase() + .trim_start_matches("0x") + != prod_id + { + log::info!("Unknown or invalid slash: {prod_id:?}, skipping"); + return Err(RogError::NotFound("No slash device".to_string())); + } + info!("Found slash type {slash_type:?}: {prod_id}"); + + let mut config = SlashConfig::new().load(); + config.slash_type = slash_type; + let slash = Slash::new(Some(device), None, Arc::new(Mutex::new(config))); + slash.do_initialization().await?; + Ok(Self::Slash(slash)) + } + + /// Try Slash USB. If one exists it is initialsed and returned. + pub async fn new_slash_usb() -> Result { + debug!("Testing for USB Slash"); + let slash_type = SlashType::from_dmi(); + if matches!(slash_type, SlashType::Unsupported) { + return Err(RogError::Slash(SlashError::NoDevice)); + } + + if let Ok(usb) = USBRaw::new(slash_type.prod_id()) { + info!("Found Slash USB {slash_type:?}"); + + let mut config = SlashConfig::new().load(); + config.slash_type = slash_type; + let slash = Slash::new( + None, + Some(Arc::new(Mutex::new(usb))), + Arc::new(Mutex::new(config)), + ); + slash.do_initialization().await?; + Ok(Self::Slash(slash)) + } else { + Err(RogError::NotFound("No slash device found".to_string())) + } + } + + /// Try AniMe Matrix HID. If one exists it is initialsed and returned. + pub async fn maybe_anime_hid( + device: Arc>, + prod_id: &str, + ) -> Result { + debug!("Testing for HIDRAW AniMe"); + let anime_type = AnimeType::from_dmi(); + dbg!(prod_id); + if matches!(anime_type, AnimeType::Unsupported) || prod_id != "193b" { + log::info!("Unknown or invalid AniMe: {prod_id:?}, skipping"); + return Err(RogError::NotFound("No anime-matrix device".to_string())); + } + info!("Found AniMe Matrix HIDRAW {anime_type:?}: {prod_id}"); + + let mut config = AniMeConfig::new().load(); + config.anime_type = anime_type; + let mut anime = AniMe::new(Some(device), None, Arc::new(Mutex::new(config))); + anime.do_initialization().await?; + Ok(Self::AniMe(anime)) + } + + pub async fn maybe_anime_usb() -> Result { + debug!("Testing for USB AniMe"); + let anime_type = get_anime_type(); + if matches!(anime_type, AnimeType::Unsupported) { + info!("No Anime Matrix capable laptop found"); + return Err(RogError::Anime(AnimeError::NoDevice)); + } + + if let Ok(usb) = USBRaw::new(0x193b) { + info!("Found AniMe Matrix USB {anime_type:?}"); + + let mut config = AniMeConfig::new().load(); + config.anime_type = anime_type; + let mut anime = AniMe::new( + None, + Some(Arc::new(Mutex::new(usb))), + Arc::new(Mutex::new(config)), + ); + anime.do_initialization().await?; + Ok(Self::AniMe(anime)) + } else { + Err(RogError::NotFound( + "No AnimeMatrix device found".to_string(), + )) + } + } + + pub async fn maybe_laptop_aura( + device: Arc>, + prod_id: &str, + ) -> Result { + debug!("Testing for laptop aura"); + let aura_type = AuraDeviceType::from(prod_id); + if !matches!( + aura_type, + AuraDeviceType::LaptopKeyboard2021 + | AuraDeviceType::LaptopKeyboardPre2021 + | AuraDeviceType::LaptopKeyboardTuf + ) { + log::info!("Unknown or invalid laptop aura: {prod_id:?}, skipping"); + return Err(RogError::NotFound("No laptop aura device".to_string())); + } + info!("Found laptop aura type {prod_id:?}"); + + let backlight = KeyboardBacklight::new() + .map_err(|e| error!("Keyboard backlight error: {e:?}")) + .map_or(None, |k| { + info!("Found sysfs backlight control"); + Some(Arc::new(Mutex::new(k))) + }); + + let mut config = AuraConfig::load_and_update_config(prod_id); + config.led_type = aura_type; + let aura = Aura { + hid: Some(device), + backlight, + config: Arc::new(Mutex::new(config)), + }; + aura.do_initialization().await?; + Ok(Self::Aura(aura)) + } +} diff --git a/asusd/src/ctrl_anime/mod.rs b/asusd/src/ctrl_anime/mod.rs deleted file mode 100644 index 8807e057..00000000 --- a/asusd/src/ctrl_anime/mod.rs +++ /dev/null @@ -1,296 +0,0 @@ -pub mod config; -/// Implements `CtrlTask`, Reloadable, `ZbusRun` -pub mod trait_impls; - -use std::convert::TryFrom; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::thread::sleep; - -use ::zbus::export::futures_util::lock::Mutex; -use config_traits::{StdConfig, StdConfigLoad}; -use log::{error, info, warn}; -use rog_anime::error::AnimeError; -use rog_anime::usb::{ - get_maybe_anime_type, pkt_flush, pkt_set_brightness, pkt_set_enable_display, - pkt_set_enable_powersave_anim, pkts_for_init, Brightness, -}; -use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType, AnimeType}; -use rog_platform::hid_raw::HidRaw; -use rog_platform::usb_raw::USBRaw; - -use self::config::{AnimeConfig, AnimeConfigCached}; -use crate::error::RogError; - -enum Node { - Usb(USBRaw), - Hid(HidRaw), -} - -impl Node { - pub fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> { - // TODO: map and pass on errors - match self { - Node::Usb(u) => { - u.write_bytes(message).ok(); - } - Node::Hid(h) => { - h.write_bytes(message).ok(); - } - } - Ok(()) - } - - pub fn set_builtins_enabled(&self, enabled: bool, bright: Brightness) -> Result<(), RogError> { - self.write_bytes(&pkt_set_enable_powersave_anim(enabled))?; - self.write_bytes(&pkt_set_enable_display(enabled))?; - self.write_bytes(&pkt_set_brightness(bright))?; - self.write_bytes(&pkt_set_enable_powersave_anim(enabled)) - } -} - -pub struct CtrlAnime { - // node: HidRaw, - node: Node, - anime_type: AnimeType, - cache: AnimeConfigCached, - config: AnimeConfig, - // set to force thread to exit - thread_exit: Arc, - // Set to false when the thread exits - thread_running: Arc, -} - -impl CtrlAnime { - #[inline] - pub fn new() -> Result { - let anime_type = get_maybe_anime_type()?; - if matches!(anime_type, AnimeType::Unsupported) { - info!("No Anime Matrix capable laptop found"); - return Err(RogError::Anime(AnimeError::NoDevice)); - } - - let usb = USBRaw::new(0x193b).ok(); - let hid = HidRaw::new("193b").ok(); - let node = if usb.is_some() { - info!("Anime using the USB interface"); - unsafe { Node::Usb(usb.unwrap_unchecked()) } - } else if hid.is_some() { - info!("Anime using the HID interface"); - unsafe { Node::Hid(hid.unwrap_unchecked()) } - } else { - return Err(RogError::Anime(AnimeError::NoDevice)); - }; - - // TODO: something better to set wakeups disabled - // if matches!(node, Node::Usb(_)) { - // if let Ok(mut enumerator) = udev::Enumerator::new() { - // enumerator.match_subsystem("usb").ok(); - // enumerator.match_attribute("idProduct", "193b").ok(); - - // if let Ok(mut enumer) = enumerator.scan_devices() { - // if let Some(mut dev) = enumer.next() { - // dev.set_attribute_value("power/wakeup", "disabled").ok(); - // } - // } - // } - // } - - let mut config = AnimeConfig::new().load(); - - info!("Device has an AniMe Matrix display: {anime_type:?}"); - let mut cache = AnimeConfigCached::default(); - if let Err(e) = cache.init_from_config(&config, anime_type) { - error!("Trying to cache the Anime Config failed, will reset to default config: {e:?}"); - config.rename_file_old(); - config = AnimeConfig::new(); - config.write(); - } - - let ctrl = CtrlAnime { - node, - anime_type, - cache, - config, - thread_exit: Arc::new(AtomicBool::new(false)), - thread_running: Arc::new(AtomicBool::new(false)), - }; - ctrl.do_initialization()?; - - Ok(ctrl) - } - - // let device = CtrlAnime::get_device(0x0b05, 0x193b)?; - - /// Start an action thread. This is classed as a singleton and there should - /// be only one running - so the thread uses atomics to signal run/exit. - /// - /// Because this also writes to the usb device, other write tries (display - /// only) *must* get the mutex lock and set the `thread_exit` atomic. - async fn run_thread(inner: Arc>, actions: Vec, mut once: bool) { - if actions.is_empty() { - warn!("AniMe system actions was empty"); - return; - } - - if let Some(lock) = inner.try_lock() { - lock.node - .write_bytes(&pkt_set_enable_powersave_anim(false)) - .map_err(|err| { - warn!("rog_anime::run_animation:callback {}", err); - }) - .ok(); - } - - // Loop rules: - // - Lock the mutex **only when required**. That is, the lock must be held for - // the shortest duration possible. - // - An AtomicBool used for thread exit should be checked in every loop, - // including nested - - // The only reason for this outer thread is to prevent blocking while waiting - // for the next spawned thread to exit - // TODO: turn this in to async task (maybe? COuld still risk blocking main - // thread) - std::thread::Builder::new() - .name("AniMe system thread start".into()) - .spawn(move || { - info!("AniMe new system thread started"); - // Getting copies of these Atomics is done *in* the thread to ensure - // we don't block other threads/main - let thread_exit; - let thread_running; - let anime_type; - loop { - if let Some(lock) = inner.try_lock() { - thread_exit = lock.thread_exit.clone(); - thread_running = lock.thread_running.clone(); - anime_type = lock.anime_type; - break; - } - } - // First two loops are to ensure we *do* aquire a lock on the mutex - // The reason the loop is required is because the USB writes can block - // for up to 10ms. We can't fail to get the atomics. - while thread_running.load(Ordering::SeqCst) { - // Make any running loop exit first - thread_exit.store(true, Ordering::SeqCst); - } - - info!("AniMe no previous system thread running (now)"); - thread_exit.store(false, Ordering::SeqCst); - thread_running.store(true, Ordering::SeqCst); - 'main: loop { - for action in &actions { - if thread_exit.load(Ordering::SeqCst) { - break 'main; - } - match action { - ActionData::Animation(frames) => { - rog_anime::run_animation(frames, &|frame| { - if thread_exit.load(Ordering::Acquire) { - info!("rog-anime: animation sub-loop was asked to exit"); - return Ok(true); // Do safe exit - } - inner - .try_lock() - .map(|lock| { - lock.write_data_buffer(frame) - .map_err(|err| { - warn!( - "rog_anime::run_animation:callback {}", - err - ); - }) - .ok(); - false // Don't exit yet - }) - .map_or_else( - || { - warn!("rog_anime::run_animation:callback failed"); - Err(AnimeError::NoFrames) - }, - Ok, - ) - }); - if thread_exit.load(Ordering::Acquire) { - info!("rog-anime: sub-loop exited and main loop exiting now"); - break 'main; - } - } - ActionData::Image(image) => { - once = false; - if let Some(lock) = inner.try_lock() { - lock.write_data_buffer(image.as_ref().clone()) - .map_err(|e| error!("{}", e)) - .ok(); - } - } - ActionData::Pause(duration) => sleep(*duration), - ActionData::AudioEq - | ActionData::SystemInfo - | ActionData::TimeDate - | ActionData::Matrix => {} - } - } - if thread_exit.load(Ordering::SeqCst) { - break 'main; - } - if once || actions.is_empty() { - break 'main; - } - } - // Clear the display on exit - if let Some(lock) = inner.try_lock() { - if let Ok(data) = - AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()]) - .map_err(|e| error!("{}", e)) - { - lock.write_data_buffer(data) - .map_err(|err| { - warn!("rog_anime::run_animation:callback {}", err); - }) - .ok(); - } - lock.node - .write_bytes(&pkt_set_enable_powersave_anim( - lock.config.builtin_anims_enabled, - )) - .map_err(|err| { - warn!("rog_anime::run_animation:callback {}", err); - }) - .ok(); - } - // Loop ended, set the atmonics - thread_running.store(false, Ordering::SeqCst); - info!("AniMe system thread exited"); - }) - .map(|err| info!("AniMe system thread: {:?}", err)) - .ok(); - } - - /// Write only a data packet. This will modify the leds brightness using the - /// global brightness set in config. - fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> { - for led in buffer.data_mut().iter_mut() { - let mut bright = *led as f32; - if bright > 254.0 { - bright = 254.0; - } - *led = bright as u8; - } - let data = AnimePacketType::try_from(buffer)?; - for row in &data { - self.node.write_bytes(row)?; - } - self.node.write_bytes(&pkt_flush())?; - Ok(()) - } - - fn do_initialization(&self) -> Result<(), RogError> { - let pkts = pkts_for_init(); - self.node.write_bytes(&pkts[0])?; - self.node.write_bytes(&pkts[1])?; - Ok(()) - } -} diff --git a/asusd/src/ctrl_aura/controller.rs b/asusd/src/ctrl_aura/controller.rs deleted file mode 100644 index e230af77..00000000 --- a/asusd/src/ctrl_aura/controller.rs +++ /dev/null @@ -1,526 +0,0 @@ -use std::collections::HashSet; - -use config_traits::StdConfig; -use dmi_id::DMIID; -use inotify::Inotify; -use log::{debug, info, warn}; -use rog_aura::aura_detection::LedSupportData; -use rog_aura::keyboard::{LedUsbPackets, UsbPackets}; -use rog_aura::usb::{LED_APPLY, LED_SET}; -use rog_aura::{AuraDeviceType, AuraEffect, LedBrightness, PowerZones, LED_MSG_LEN}; -use rog_platform::hid_raw::HidRaw; -use rog_platform::keyboard_led::KeyboardBacklight; -use udev::Device; -use zbus::zvariant::OwnedObjectPath; -use zbus::Connection; - -use super::config::AuraConfig; -use crate::ctrl_aura::manager::{dbus_path_for_dev, dbus_path_for_tuf, start_tasks}; -use crate::ctrl_aura::trait_impls::CtrlAuraZbus; -use crate::error::RogError; -use crate::CtrlTask; - -#[derive(Debug)] -pub enum LEDNode { - /// Brightness and/or TUF RGB controls - KbdLed(KeyboardBacklight), - /// Raw HID handle - Rog(Option, HidRaw), -} - -impl LEDNode { - // TODO: move various methods upwards to this - pub fn set_brightness(&self, value: u8) -> Result<(), RogError> { - match self { - LEDNode::KbdLed(k) => k.set_brightness(value)?, - LEDNode::Rog(k, r) => { - if let Some(k) = k { - k.set_brightness(value)?; - let x = k.get_brightness()?; - if x != value { - debug!( - "Kernel brightness control didn't read back correct value, setting \ - with raw hid" - ); - r.write_bytes(&[0x5a, 0xba, 0xc5, 0xc4, value])?; - } - } else { - debug!("No brightness control found, trying raw write"); - r.write_bytes(&[0x5a, 0xba, 0xc5, 0xc4, value])?; - } - } - } - Ok(()) - } - - pub fn get_brightness(&self) -> Result { - Ok(match self { - LEDNode::KbdLed(k) => k.get_brightness()?, - LEDNode::Rog(k, _) => { - if let Some(k) = k { - k.get_brightness()? - } else { - debug!("No brightness control found"); - return Err(RogError::MissingFunction( - "No keyboard brightness control found".to_string(), - )); - } - } - }) - } - - pub fn monitor_brightness(&self) -> Result { - Ok(match self { - LEDNode::KbdLed(k) => k.monitor_brightness()?, - LEDNode::Rog(k, _) => { - if let Some(k) = k { - k.monitor_brightness()? - } else { - debug!("No brightness control found"); - return Err(RogError::MissingFunction( - "No keyboard brightness control found".to_string(), - )); - } - } - }) - } - - pub fn has_brightness_control(&self) -> bool { - match self { - LEDNode::KbdLed(k) => k.has_brightness(), - LEDNode::Rog(k, _) => { - if let Some(k) = k { - k.has_brightness() - } else { - false - } - } - } - } -} - -/// Individual controller for one Aura device -pub struct CtrlKbdLed { - pub led_type: AuraDeviceType, - pub led_node: LEDNode, - pub supported_data: LedSupportData, // TODO: is storing this really required? - pub per_key_mode_active: bool, - pub config: AuraConfig, - pub dbus_path: OwnedObjectPath, -} - -impl CtrlKbdLed { - pub fn add_to_dbus_and_start( - self, - interfaces: &mut HashSet, - conn: Connection, - ) -> Result<(), RogError> { - let dbus_path = self.dbus_path.clone(); - let dbus_path_cpy = self.dbus_path.clone(); - info!( - "AuraManager starting device at: {:?}, {:?}", - dbus_path, self.led_type - ); - let conn_copy = conn.clone(); - let sig_ctx1 = CtrlAuraZbus::signal_context(&conn_copy)?; - let sig_ctx2 = CtrlAuraZbus::signal_context(&conn_copy)?; - let zbus = CtrlAuraZbus::new(self, sig_ctx1); - tokio::spawn( - async move { start_tasks(zbus, conn_copy.clone(), sig_ctx2, dbus_path).await }, - ); - interfaces.insert(dbus_path_cpy); - Ok(()) - } - - /// Build and init a `CtrlKbdLed` from a udev device. Maybe. - /// This will initialise the config also. - pub fn maybe_device( - device: Device, - interfaces: &mut HashSet, - ) -> Result, RogError> { - // usb_device gives us a product and vendor ID - if let Some(usb_device) = device.parent_with_subsystem_devtype("usb", "usb_device")? { - let dbus_path = dbus_path_for_dev(&usb_device).unwrap_or_default(); - if interfaces.contains(&dbus_path) { - debug!("Already a ctrl at {dbus_path:?}, ignoring this end-point"); - return Ok(None); - } - - // The asus_wmi driver latches MCU that controls the USB endpoints - if let Some(parent) = device.parent() { - if let Some(driver) = parent.driver() { - // There is a tree of devices added so filter by driver - if driver != "asus" { - return Ok(None); - } - } else { - return Ok(None); - } - } - // Device is something like 002, while its parent is the MCU - // Think of it like the device is an endpoint of the USB device attached - let mut prod_id = String::new(); - if let Some(usb_id) = usb_device.attribute_value("idProduct") { - prod_id = usb_id.to_string_lossy().to_string(); - let aura_device = AuraDeviceType::from(prod_id.as_str()); - if aura_device == AuraDeviceType::Unknown { - log::debug!("Unknown or invalid device: {usb_id:?}, skipping"); - return Ok(None); - } - } - - let dev_node = if let Some(dev_node) = usb_device.devnode() { - dev_node - } else { - debug!("Device has no devnode, skipping"); - return Ok(None); - }; - info!("AuraControl found device at: {:?}", dev_node); - let dev = HidRaw::from_device(device)?; - let mut controller = Self::from_hidraw(dev, dbus_path.clone())?; - controller.config = AuraConfig::load_and_update_config(&prod_id); - interfaces.insert(dbus_path); - return Ok(Some(controller)); - } - Ok(None) - } - - pub fn find_all() -> Result, RogError> { - info!("Searching for all Aura devices"); - let mut devices = Vec::new(); - let mut interfaces = HashSet::new(); // track and ensure we use only one hidraw per prod_id - - let mut enumerator = udev::Enumerator::new().map_err(|err| { - warn!("{}", err); - err - })?; - - enumerator.match_subsystem("hidraw").map_err(|err| { - warn!("{}", err); - err - })?; - - for end_point in enumerator.scan_devices()? { - // maybe? - if let Some(device) = Self::maybe_device(end_point, &mut interfaces)? { - devices.push(device); - } - } - - // Check for a TUF laptop LED. Assume there is only ever one. - if let Ok(kbd_backlight) = KeyboardBacklight::new() { - if kbd_backlight.has_kbd_rgb_mode() { - // Extra sure double-check that this isn't a laptop with crap - // ACPI with borked return on the TUF rgb methods - let dmi = DMIID::new().unwrap_or_default(); - info!("Found a TUF with product family: {}", dmi.product_family); - info!("and board name: {}", dmi.board_name); - - if dmi.product_family.contains("TUF") { - info!("AuraControl found a TUF laptop keyboard"); - let ctrl = CtrlKbdLed { - led_type: AuraDeviceType::LaptopKeyboardTuf, - led_node: LEDNode::KbdLed(kbd_backlight), - supported_data: LedSupportData::get_data("tuf"), - per_key_mode_active: false, - config: AuraConfig::load_and_update_config("tuf"), - dbus_path: dbus_path_for_tuf(), - }; - devices.push(ctrl); - } - } - } else { - let dmi = DMIID::new().unwrap_or_default(); - warn!("No asus::kbd_backlight found for {} ??", dmi.product_family); - } - - info!("Found {} Aura devices", devices.len()); - - Ok(devices) - } - - /// The generated data from this function has a default config. This config - /// should be overwritten. The reason for the default config is because - /// of async issues between this and udev/hidraw - fn from_hidraw(device: HidRaw, dbus_path: OwnedObjectPath) -> Result { - let rgb_led = KeyboardBacklight::new() - .map_err(|e| { - log::error!( - "{} is missing a keyboard backlight brightness control: {e:?}", - device.prod_id() - ); - }) - .ok(); - let prod_id = AuraDeviceType::from(device.prod_id()); - if prod_id == AuraDeviceType::Unknown { - log::error!("{} is AuraDevice::Unknown", device.prod_id()); - return Err(RogError::NoAuraNode); - } - - // New loads data from the DB also - // let config = Self::init_config(prod_id, data); - - let data = LedSupportData::get_data(device.prod_id()); - let ctrl = CtrlKbdLed { - led_type: prod_id, - led_node: LEDNode::Rog(rgb_led, device), - supported_data: data.clone(), - per_key_mode_active: false, - config: AuraConfig::default(), - dbus_path, - }; - Ok(ctrl) - } - - pub(super) fn fix_ally_power(&mut self) -> Result<(), RogError> { - if self.led_type == AuraDeviceType::Ally { - if let LEDNode::Rog(_, hid_raw) = &self.led_node { - if self.config.ally_fix.is_none() { - let msg = [0x5d, 0xbd, 0x01, 0xff, 0xff, 0xff, 0xff]; - hid_raw.write_bytes(&msg)?; - info!("Reset Ally power settings to base"); - self.config.ally_fix = Some(true); - } - self.config.write(); - } - } - Ok(()) - } - - /// Set combination state for boot animation/sleep animation/all leds/keys - /// leds/side leds LED active - pub(super) fn set_power_states(&mut self) -> Result<(), RogError> { - if let LEDNode::KbdLed(platform) = &mut self.led_node { - // TODO: tuf bool array - let buf = self.config.enabled.to_bytes(self.led_type); - platform.set_kbd_rgb_state(&buf)?; - } else if let LEDNode::Rog(_, hid_raw) = &self.led_node { - if let Some(p) = self.config.enabled.states.first() { - if p.zone == PowerZones::Ally { - let msg = [0x5d, 0xd1, 0x09, 0x01, p.new_to_byte() as u8, 0x0, 0x0]; - hid_raw.write_bytes(&msg)?; - return Ok(()); - } - } - - let bytes = self.config.enabled.to_bytes(self.led_type); - let msg = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3]]; - hid_raw.write_bytes(&msg)?; - } - Ok(()) - } - - /// Write an effect block. This is for per-key, but can be repurposed to - /// write the raw factory mode packets - when doing this it is expected that - /// only the first `Vec` (`effect[0]`) is valid. - pub fn write_effect_block(&mut self, effect: &UsbPackets) -> Result<(), RogError> { - if self.config.brightness == LedBrightness::Off { - self.config.brightness = LedBrightness::Med; - self.config.write(); - } - - let pkt_type = effect[0][1]; - const PER_KEY_TYPE: u8 = 0xbc; - - if pkt_type != PER_KEY_TYPE { - self.per_key_mode_active = false; - if let LEDNode::Rog(_, hid_raw) = &self.led_node { - hid_raw.write_bytes(&effect[0])?; - hid_raw.write_bytes(&LED_SET)?; - // hid_raw.write_bytes(&LED_APPLY)?; - } - } else { - if !self.per_key_mode_active { - if let LEDNode::Rog(_, hid_raw) = &self.led_node { - let init = LedUsbPackets::get_init_msg(); - hid_raw.write_bytes(&init)?; - } - self.per_key_mode_active = true; - } - if let LEDNode::Rog(_, hid_raw) = &self.led_node { - for row in effect.iter() { - hid_raw.write_bytes(row)?; - } - } else if let LEDNode::KbdLed(tuf) = &self.led_node { - for row in effect.iter() { - let r = row[9]; - let g = row[10]; - let b = row[11]; - tuf.set_kbd_rgb_mode(&[0, 0, r, g, b, 0])?; - } - } - } - Ok(()) - } - - /// Write the AuraEffect to the device - pub fn write_effect_and_apply(&mut self, mode: &AuraEffect) -> Result<(), RogError> { - if let LEDNode::KbdLed(platform) = &self.led_node { - let buf = [ - 1, - mode.mode as u8, - mode.colour1.r, - mode.colour1.g, - mode.colour1.b, - mode.speed as u8, - ]; - platform.set_kbd_rgb_mode(&buf)?; - } else if let LEDNode::Rog(_, hid_raw) = &self.led_node { - let bytes: [u8; LED_MSG_LEN] = mode.into(); - hid_raw.write_bytes(&bytes)?; - hid_raw.write_bytes(&LED_SET)?; - // Changes won't persist unless apply is set - hid_raw.write_bytes(&LED_APPLY)?; - } else { - return Err(RogError::NoAuraKeyboard); - } - self.per_key_mode_active = false; - Ok(()) - } - - pub(super) fn write_current_config_mode(&mut self) -> Result<(), RogError> { - if self.config.multizone_on { - let mode = self.config.current_mode; - let mut create = false; - // There is no multizone config for this mode so create one here - // using the colours of rainbow if it exists, or first available - // mode, or random - if self.config.multizone.is_none() { - create = true; - } else if let Some(multizones) = self.config.multizone.as_ref() { - if !multizones.contains_key(&mode) { - create = true; - } - } - if create { - info!("No user-set config for zone founding, attempting a default"); - self.config.create_multizone_default(&self.supported_data)?; - } - - if let Some(multizones) = self.config.multizone.as_mut() { - if let Some(set) = multizones.get(&mode) { - for mode in set.clone() { - self.write_effect_and_apply(&mode)?; - } - } - } - } else { - let mode = self.config.current_mode; - if let Some(effect) = self.config.builtins.get(&mode).cloned() { - self.write_effect_and_apply(&effect)?; - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use rog_aura::aura_detection::LedSupportData; - use rog_aura::{AuraDeviceType, AuraModeNum, AuraZone, PowerZones}; - use rog_platform::hid_raw::HidRaw; - use rog_platform::keyboard_led::KeyboardBacklight; - use zbus::zvariant::OwnedObjectPath; - - use super::CtrlKbdLed; - use crate::ctrl_aura::config::AuraConfig; - use crate::ctrl_aura::controller::LEDNode; - - #[test] - #[ignore = "Unable to run in CI as the HIDRAW device is required"] - fn create_multizone_if_no_config() { - // Checking to ensure set_mode errors when unsupported modes are tried - let config = AuraConfig::new("19b6"); - let supported_basic_modes = LedSupportData { - device_name: String::new(), - product_id: String::new(), - layout_name: "ga401".to_owned(), - basic_modes: vec![AuraModeNum::Static], - basic_zones: vec![], - advanced_type: rog_aura::keyboard::AdvancedAuraType::None, - power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow], - }; - let mut controller = CtrlKbdLed { - led_type: AuraDeviceType::LaptopKeyboard2021, - led_node: LEDNode::Rog( - Some(KeyboardBacklight::default()), - HidRaw::new("19b6").unwrap(), - ), - supported_data: supported_basic_modes, - per_key_mode_active: false, - config, - dbus_path: OwnedObjectPath::default(), - }; - - assert!(controller.config.multizone.is_none()); - assert!(controller - .config - .create_multizone_default(&controller.supported_data) - .is_err()); - assert!(controller.config.multizone.is_none()); - - controller.supported_data.basic_zones.push(AuraZone::Key1); - controller.supported_data.basic_zones.push(AuraZone::Key2); - assert!(controller - .config - .create_multizone_default(&controller.supported_data) - .is_ok()); - assert!(controller.config.multizone.is_some()); - - let m = controller.config.multizone.unwrap(); - assert!(m.contains_key(&AuraModeNum::Static)); - let e = m.get(&AuraModeNum::Static).unwrap(); - assert_eq!(e.len(), 2); - assert_eq!(e[0].zone, AuraZone::Key1); - assert_eq!(e[1].zone, AuraZone::Key2); - } - - #[test] - #[ignore = "Unable to run in CI as the HIDRAW device is required"] - // TODO: use sim device - fn next_mode_create_multizone_if_no_config() { - // Checking to ensure set_mode errors when unsupported modes are tried - let config = AuraConfig::new("19b6"); - let supported_basic_modes = LedSupportData { - device_name: String::new(), - product_id: String::new(), - layout_name: "ga401".to_owned(), - basic_modes: vec![AuraModeNum::Static], - basic_zones: vec![AuraZone::Key1, AuraZone::Key2], - advanced_type: rog_aura::keyboard::AdvancedAuraType::None, - power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow], - }; - let mut controller = CtrlKbdLed { - led_type: AuraDeviceType::LaptopKeyboard2021, - led_node: LEDNode::Rog( - Some(KeyboardBacklight::default()), - HidRaw::new("19b6").unwrap(), - ), - supported_data: supported_basic_modes, - per_key_mode_active: false, - config, - dbus_path: OwnedObjectPath::default(), - }; - - assert!(controller.config.multizone.is_none()); - controller.config.multizone_on = true; - // This is called in toggle_mode. It will error here because we have no - // keyboard node in tests. - assert_eq!( - controller - .write_current_config_mode() - .unwrap_err() - .to_string(), - "No supported Aura keyboard" - ); - assert!(controller.config.multizone.is_some()); - - let m = controller.config.multizone.unwrap(); - assert!(m.contains_key(&AuraModeNum::Static)); - let e = m.get(&AuraModeNum::Static).unwrap(); - assert_eq!(e.len(), 2); - assert_eq!(e[0].zone, AuraZone::Key1); - assert_eq!(e[1].zone, AuraZone::Key2); - } -} diff --git a/asusd/src/ctrl_aura/manager.rs b/asusd/src/ctrl_aura/manager.rs deleted file mode 100644 index 25f47a2d..00000000 --- a/asusd/src/ctrl_aura/manager.rs +++ /dev/null @@ -1,138 +0,0 @@ -// Plan: -// - Manager has udev monitor on USB looking for ROG devices -// - If a device is found, add it to watch -// - Add it to Zbus server -// - If udev sees device removed then remove the zbus path - -use std::collections::HashSet; - -use log::{error, info, warn}; -use mio::{Events, Interest, Poll, Token}; -use udev::{Device, MonitorBuilder}; -use zbus::object_server::SignalEmitter; -use zbus::zvariant::{ObjectPath, OwnedObjectPath}; -use zbus::Connection; - -use crate::ctrl_aura::controller::CtrlKbdLed; -use crate::ctrl_aura::trait_impls::{CtrlAuraZbus, AURA_ZBUS_PATH}; -use crate::error::RogError; -use crate::{CtrlTask, Reloadable}; - -pub struct AuraManager { - _connection: Connection, -} - -impl AuraManager { - pub async fn new(connection: Connection) -> Result { - let conn_copy = connection.clone(); - let mut interfaces = HashSet::new(); - - // Do the initial keyboard detection: - let all = CtrlKbdLed::find_all()?; - for ctrl in all { - let path = ctrl.dbus_path.clone(); - interfaces.insert(path.clone()); // ensure we record the initial stuff - let sig_ctx = CtrlAuraZbus::signal_context(&connection)?; - let sig_ctx2 = sig_ctx.clone(); - let zbus = CtrlAuraZbus::new(ctrl, sig_ctx); - start_tasks(zbus, connection.clone(), sig_ctx2, path).await?; - } - - let manager = Self { - _connection: connection, - }; - - // detect all plugged in aura devices (eventually) - // only USB devices are detected for here - std::thread::spawn(move || { - let mut monitor = MonitorBuilder::new()?.match_subsystem("hidraw")?.listen()?; - let mut poll = Poll::new()?; - let mut events = Events::with_capacity(1024); - poll.registry() - .register(&mut monitor, Token(0), Interest::READABLE)?; - - loop { - if poll.poll(&mut events, None).is_err() { - continue; - } - for event in monitor.iter() { - let action = event.action().unwrap_or_default(); - - if let Some(parent) = - event.parent_with_subsystem_devtype("usb", "usb_device")? - { - if action == "remove" { - if let Some(path) = dbus_path_for_dev(&parent) { - if interfaces.remove(&path) { - info!("AuraManager removing: {path:?}"); - let conn_copy = conn_copy.clone(); - tokio::spawn(async move { - let res = conn_copy - .object_server() - .remove::(&path) - .await - .map_err(|e| { - error!("Failed to remove {path:?}, {e:?}"); - e - })?; - info!("AuraManager removed: {path:?}, {res}"); - Ok::<(), RogError>(()) - }); - } - } - } else if action == "add" { - if let Ok(Some(ctrl)) = - CtrlKbdLed::maybe_device(event.device(), &mut interfaces) - { - ctrl.add_to_dbus_and_start(&mut interfaces, conn_copy.clone()) - .map_err(|e| { - error!("Couldn't start aura device on dbus: {e:?}") - }) - .ok(); - } - }; - } - } - } - // Required for return type on spawn - #[allow(unreachable_code)] - Ok::<(), RogError>(()) - }); - Ok(manager) - } -} - -pub(crate) fn dbus_path_for_dev(parent: &Device) -> Option { - if let Some(filename) = super::filename_partial(parent) { - return Some( - ObjectPath::from_str_unchecked(&format!("{AURA_ZBUS_PATH}/{filename}")).into(), - ); - } - None -} - -pub(crate) fn dbus_path_for_tuf() -> OwnedObjectPath { - ObjectPath::from_str_unchecked(&format!("{AURA_ZBUS_PATH}/tuf")).into() -} - -pub async fn start_tasks( - mut zbus: CtrlAuraZbus, - connection: Connection, - _signal_ctx: SignalEmitter<'static>, - path: OwnedObjectPath, -) -> Result<(), RogError> { - // let task = zbus.clone(); - // let signal_ctx = signal_ctx.clone(); - zbus.reload() - .await - .unwrap_or_else(|err| warn!("Controller error: {}", err)); - connection - .object_server() - .at(path.clone(), zbus) - .await - .map_err(|e| error!("Couldn't add server at path: {path}, {e:?}")) - .ok(); - // TODO: skip this until we keep handles to tasks so they can be killed - // task.create_tasks(signal_ctx).await - Ok(()) -} diff --git a/asusd/src/ctrl_aura/mod.rs b/asusd/src/ctrl_aura/mod.rs deleted file mode 100644 index eeaed390..00000000 --- a/asusd/src/ctrl_aura/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use log::warn; -use udev::Device; -use zbus::zvariant::{ObjectPath, OwnedObjectPath}; - -pub mod config; -pub mod controller; -pub mod manager; -/// Implements `CtrlTask`, `Reloadable`, `ZbusRun` -pub mod trait_impls; - -/// Returns only the Device details concatenated in a form usable for -/// adding/appending to a filename -pub(super) fn filename_partial(parent: &Device) -> Option { - if let Some(id_product) = parent.attribute_value("idProduct") { - let id_product = id_product.to_string_lossy(); - let mut path = if let Some(devnum) = parent.attribute_value("devnum") { - let devnum = devnum.to_string_lossy(); - if let Some(devpath) = parent.attribute_value("devpath") { - let devpath = devpath.to_string_lossy(); - format!("{id_product}_{devnum}_{devpath}") - } else { - format!("{id_product}_{devnum}") - } - } else { - format!("{id_product}") - }; - if path.contains('.') { - warn!("dbus path for {id_product} contains `.`, removing"); - path.replace('.', "").clone_into(&mut path); - } - return Some(ObjectPath::from_str_unchecked(&path).into()); - } - None -} diff --git a/asusd/src/ctrl_aura/trait_impls.rs b/asusd/src/ctrl_aura/trait_impls.rs deleted file mode 100644 index 90aadafe..00000000 --- a/asusd/src/ctrl_aura/trait_impls.rs +++ /dev/null @@ -1,318 +0,0 @@ -use std::collections::BTreeMap; -use std::sync::Arc; - -use config_traits::StdConfig; -use log::{debug, error, info, warn}; -use rog_aura::keyboard::{LaptopAuraPower, UsbPackets}; -use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones}; -use zbus::export::futures_util::lock::{Mutex, MutexGuard}; -use zbus::export::futures_util::StreamExt; -use zbus::fdo::Error as ZbErr; -use zbus::interface; -use zbus::object_server::SignalEmitter; - -use super::controller::CtrlKbdLed; -use crate::error::RogError; -use crate::CtrlTask; - -pub const AURA_ZBUS_NAME: &str = "Aura"; -pub const AURA_ZBUS_PATH: &str = "/org/asuslinux"; - -#[derive(Clone)] -pub struct CtrlAuraZbus(Arc>, SignalEmitter<'static>); - -impl CtrlAuraZbus { - pub fn new(controller: CtrlKbdLed, signal: SignalEmitter<'static>) -> Self { - Self(Arc::new(Mutex::new(controller)), signal) - } - - fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> { - let bright = lock.led_node.get_brightness().unwrap_or_default(); - lock.config.read(); - lock.config.brightness = bright.into(); - lock.config.write(); - Ok(()) - } -} - -/// The main interface for changing, reading, or notfying -/// -/// LED commands are split between Brightness, Modes, Per-Key -#[interface(name = "org.asuslinux.Aura")] -impl CtrlAuraZbus { - /// Return the device type for this Aura keyboard - #[zbus(property)] - async fn device_type(&self) -> AuraDeviceType { - let ctrl = self.0.lock().await; - ctrl.led_type - } - - /// Return the current LED brightness - #[zbus(property)] - async fn brightness(&self) -> Result { - let ctrl = self.0.lock().await; - Ok(ctrl.led_node.get_brightness().map(|n| n.into())?) - } - - /// Set the keyboard brightness level (0-3) - #[zbus(property)] - async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> { - let ctrl = self.0.lock().await; - Ok(ctrl.led_node.set_brightness(brightness.into())?) - } - - /// Total levels of brightness available - #[zbus(property)] - async fn supported_brightness(&self) -> Vec { - vec![ - LedBrightness::Off, - LedBrightness::Low, - LedBrightness::Med, - LedBrightness::High, - ] - } - - /// The total available modes - #[zbus(property)] - async fn supported_basic_modes(&self) -> Result, ZbErr> { - let ctrl = self.0.lock().await; - Ok(ctrl.config.builtins.keys().cloned().collect()) - } - - #[zbus(property)] - async fn supported_basic_zones(&self) -> Result, ZbErr> { - let ctrl = self.0.lock().await; - Ok(ctrl.supported_data.basic_zones.clone()) - } - - #[zbus(property)] - async fn supported_power_zones(&self) -> Result, ZbErr> { - let ctrl = self.0.lock().await; - Ok(ctrl.supported_data.power_zones.clone()) - } - - /// The current mode data - #[zbus(property)] - async fn led_mode(&self) -> Result { - // entirely possible to deadlock here, so use try instead of lock() - // let ctrl = self.0.lock().await; - // Ok(ctrl.config.current_mode) - if let Some(ctrl) = self.0.try_lock() { - Ok(ctrl.config.current_mode) - } else { - Err(ZbErr::Failed("Aura control couldn't lock self".to_string())) - } - } - - /// Set an Aura effect if the effect mode or zone is supported. - /// - /// On success the aura config file is read to refresh cached values, then - /// the effect is stored and config written to disk. - #[zbus(property)] - async fn set_led_mode(&mut self, num: AuraModeNum) -> Result<(), ZbErr> { - let mut ctrl = self.0.lock().await; - ctrl.config.current_mode = num; - ctrl.write_current_config_mode()?; - if ctrl.config.brightness == LedBrightness::Off { - ctrl.config.brightness = LedBrightness::Med; - } - if ctrl.led_node.has_brightness_control() { - ctrl.led_node - .set_brightness(ctrl.config.brightness.into())?; - } - ctrl.config.write(); - - self.led_mode_changed(&self.1).await.ok(); - self.led_mode_data_changed(&self.1).await.ok(); - Ok(()) - } - - /// The current mode data - #[zbus(property)] - async fn led_mode_data(&self) -> Result { - // entirely possible to deadlock here, so use try instead of lock() - if let Some(ctrl) = self.0.try_lock() { - let mode = ctrl.config.current_mode; - match ctrl.config.builtins.get(&mode) { - Some(effect) => Ok(effect.clone()), - None => Err(ZbErr::Failed("Could not get the current effect".into())), - } - } else { - Err(ZbErr::Failed("Aura control couldn't lock self".to_string())) - } - } - - /// Set an Aura effect if the effect mode or zone is supported. - /// - /// On success the aura config file is read to refresh cached values, then - /// the effect is stored and config written to disk. - #[zbus(property)] - async fn set_led_mode_data(&mut self, effect: AuraEffect) -> Result<(), ZbErr> { - let mut ctrl = self.0.lock().await; - if !ctrl.supported_data.basic_modes.contains(&effect.mode) - || effect.zone != AuraZone::None - && !ctrl.supported_data.basic_zones.contains(&effect.zone) - { - return Err(ZbErr::NotSupported(format!( - "The Aura effect is not supported: {effect:?}" - ))); - } - - ctrl.write_effect_and_apply(&effect)?; - if ctrl.config.brightness == LedBrightness::Off { - ctrl.config.brightness = LedBrightness::Med; - } - if ctrl.led_node.has_brightness_control() { - ctrl.led_node - .set_brightness(ctrl.config.brightness.into())?; - } - ctrl.config.set_builtin(effect); - ctrl.config.write(); - - self.led_mode_changed(&self.1).await.ok(); - Ok(()) - } - - /// Get the data set for every mode available - async fn all_mode_data(&self) -> BTreeMap { - let ctrl = self.0.lock().await; - ctrl.config.builtins.clone() - } - - // As property doesn't work for AuraPowerDev (complexity of serialization?) - #[zbus(property)] - async fn led_power(&self) -> LaptopAuraPower { - let ctrl = self.0.lock().await; - ctrl.config.enabled.clone() - } - - /// Set a variety of states, input is array of enum. - /// `enabled` sets if the sent array should be disabled or enabled - /// - /// For Modern ROG devices the "enabled" flag is ignored. - #[zbus(property)] - async fn set_led_power(&mut self, options: LaptopAuraPower) -> Result<(), ZbErr> { - let mut ctrl = self.0.lock().await; - for opt in options.states { - let zone = opt.zone; - for config in ctrl.config.enabled.states.iter_mut() { - if config.zone == zone { - *config = opt; - } - } - } - ctrl.config.write(); - Ok(ctrl.set_power_states().map_err(|e| { - warn!("{}", e); - e - })?) - } - - /// On machine that have some form of either per-key keyboard or per-zone - /// this can be used to write custom effects over dbus. The input is a - /// nested `Vec>` where `Vec` is a raw USB packet - async fn direct_addressing_raw(&self, data: UsbPackets) -> Result<(), ZbErr> { - let mut ctrl = self.0.lock().await; - ctrl.write_effect_block(&data)?; - Ok(()) - } -} - -impl CtrlTask for CtrlAuraZbus { - fn zbus_path() -> &'static str { - "/org/asuslinux" - } - - async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> { - let load_save = - |start: bool, mut lock: MutexGuard<'_, CtrlKbdLed>| -> Result<(), RogError> { - // If waking up - if !start { - info!("CtrlKbdLedTask reloading brightness and modes"); - if lock.led_node.has_brightness_control() { - lock.led_node - .set_brightness(lock.config.brightness.into()) - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - })?; - } - lock.write_current_config_mode().map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - })?; - } else if start { - Self::update_config(&mut lock).map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - })?; - } - Ok(()) - }; - - let inner1 = self.0.clone(); - let inner3 = self.0.clone(); - self.create_sys_event_tasks( - move |sleeping| { - let inner1 = inner1.clone(); - async move { - let lock = inner1.lock().await; - load_save(sleeping, lock).unwrap(); // unwrap as we want to - // bomb out of the task - } - }, - move |_shutting_down| { - let inner3 = inner3.clone(); - async move { - let lock = inner3.lock().await; - load_save(false, lock).unwrap(); // unwrap as we want to - // bomb out of the task - } - }, - move |_lid_closed| { - // on lid change - async move {} - }, - move |_power_plugged| { - // power change - async move {} - }, - ) - .await; - - let ctrl2 = self.0.clone(); - let ctrl = self.0.lock().await; - if ctrl.led_node.has_brightness_control() { - let watch = ctrl.led_node.monitor_brightness()?; - tokio::spawn(async move { - let mut buffer = [0; 32]; - watch - .into_event_stream(&mut buffer) - .unwrap() - .for_each(|_| async { - if let Some(lock) = ctrl2.try_lock() { - load_save(true, lock).unwrap(); // unwrap as we want - // to - // bomb out of the - // task - } - }) - .await; - }); - } - - Ok(()) - } -} - -impl crate::Reloadable for CtrlAuraZbus { - async fn reload(&mut self) -> Result<(), RogError> { - let mut ctrl = self.0.lock().await; - ctrl.fix_ally_power()?; - debug!("reloading keyboard mode"); - ctrl.write_current_config_mode()?; - debug!("reloading power states"); - ctrl.set_power_states().map_err(|err| warn!("{err}")).ok(); - Ok(()) - } -} diff --git a/asusd/src/ctrl_slash/mod.rs b/asusd/src/ctrl_slash/mod.rs deleted file mode 100644 index d458ceb8..00000000 --- a/asusd/src/ctrl_slash/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -pub mod config; -pub mod trait_impls; - -use config_traits::{StdConfig, StdConfigLoad}; -use log::info; -use rog_platform::hid_raw::HidRaw; -use rog_platform::usb_raw::USBRaw; -use rog_slash::error::SlashError; -use rog_slash::usb::{get_maybe_slash_type, pkt_set_mode, pkt_set_options, pkts_for_init}; -use rog_slash::{SlashMode, SlashType}; - -use crate::ctrl_slash::config::SlashConfig; -use crate::error::RogError; - -enum Node { - Usb(USBRaw), - Hid(HidRaw), -} - -impl Node { - pub fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> { - // TODO: map and pass on errors - match self { - Node::Usb(u) => { - u.write_bytes(message).ok(); - } - Node::Hid(h) => { - h.write_bytes(message).ok(); - } - } - Ok(()) - } -} - -pub struct CtrlSlash { - node: Node, - config: SlashConfig, -} - -impl CtrlSlash { - #[inline] - pub fn new() -> Result { - let slash_type = get_maybe_slash_type()?; - if matches!(slash_type, SlashType::Unsupported) { - info!("No Slash capable laptop found"); - return Err(RogError::Slash(SlashError::NoDevice)); - } - - let usb = USBRaw::new(rog_slash::usb::PROD_ID).ok(); - let hid = HidRaw::new(rog_slash::usb::PROD_ID_STR).ok(); - let node = if usb.is_some() { - info!("Slash is using raw USB"); - unsafe { Node::Usb(usb.unwrap_unchecked()) } - } else if hid.is_some() { - info!("Slash is using HIDRAW"); - unsafe { Node::Hid(hid.unwrap_unchecked()) } - } else { - return Err(RogError::Slash(SlashError::NoDevice)); - }; - - let ctrl = CtrlSlash { - node, - config: SlashConfig::new().load(), - }; - ctrl.do_initialization()?; - - Ok(ctrl) - } - - fn do_initialization(&self) -> Result<(), RogError> { - let init_packets = pkts_for_init(); - self.node.write_bytes(&init_packets[0])?; - self.node.write_bytes(&init_packets[1])?; - - // Apply config upon initialization - let option_packets = pkt_set_options( - self.config.slash_enabled, - self.config.slash_brightness, - self.config.slash_interval, - ); - self.node.write_bytes(&option_packets)?; - - let mode_packets = pkt_set_mode(self.config.slash_mode); - self.node.write_bytes(&mode_packets[0])?; - self.node.write_bytes(&mode_packets[1])?; - - Ok(()) - } - - pub fn set_options(&self, enabled: bool, brightness: u8, interval: u8) -> Result<(), RogError> { - let command_packets = pkt_set_options(enabled, brightness, interval); - self.node.write_bytes(&command_packets)?; - Ok(()) - } - - pub fn set_slash_mode(&self, slash_mode: SlashMode) -> Result<(), RogError> { - let command_packets = pkt_set_mode(slash_mode); - self.node.write_bytes(&command_packets[0])?; - self.node.write_bytes(&command_packets[1])?; - Ok(()) - } -} diff --git a/asusd/src/ctrl_slash/trait_impls.rs b/asusd/src/ctrl_slash/trait_impls.rs deleted file mode 100644 index 1e9ed003..00000000 --- a/asusd/src/ctrl_slash/trait_impls.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::sync::Arc; - -use config_traits::StdConfig; -use log::warn; -use rog_slash::usb::{pkt_set_mode, pkt_set_options}; -use rog_slash::{DeviceState, SlashMode}; -use zbus::export::futures_util::lock::Mutex; -use zbus::object_server::SignalEmitter; -use zbus::{interface, Connection}; - -use crate::ctrl_slash::CtrlSlash; -use crate::error::RogError; - -pub const SLASH_ZBUS_NAME: &str = "Slash"; -pub const SLASH_ZBUS_PATH: &str = "/org/asuslinux"; - -#[derive(Clone)] -pub struct CtrlSlashZbus(pub Arc>); - -/// The struct with the main dbus methods requires this trait -impl crate::ZbusRun for CtrlSlashZbus { - async fn add_to_server(self, server: &mut Connection) { - Self::add_to_server_helper(self, SLASH_ZBUS_PATH, server).await; - } -} - -#[interface(name = "org.asuslinux.Slash")] -impl CtrlSlashZbus { - /// Get enabled or not - #[zbus(property)] - async fn enabled(&self) -> bool { - let lock = self.0.lock().await; - lock.config.slash_enabled - } - - /// Set enabled true or false - #[zbus(property)] - async fn set_enabled(&self, enabled: bool) { - let mut lock = self.0.lock().await; - let brightness = if enabled && lock.config.slash_brightness == 0 { - 0x88 - } else { - lock.config.slash_brightness - }; - lock.node - .write_bytes(&pkt_set_options( - enabled, - brightness, - lock.config.slash_interval, - )) - .map_err(|err| { - warn!("ctrl_slash::set_options {}", err); - }) - .ok(); - - lock.config.slash_enabled = enabled; - lock.config.slash_brightness = brightness; - lock.config.write(); - } - - /// Get brightness level - #[zbus(property)] - async fn brightness(&self) -> u8 { - let lock = self.0.lock().await; - lock.config.slash_brightness - } - - /// Set brightness level - #[zbus(property)] - async fn set_brightness(&self, brightness: u8) { - let mut lock = self.0.lock().await; - let enabled = brightness > 0; - lock.node - .write_bytes(&pkt_set_options( - enabled, - brightness, - lock.config.slash_interval, - )) - .map_err(|err| { - warn!("ctrl_slash::set_options {}", err); - }) - .ok(); - - lock.config.slash_enabled = enabled; - lock.config.slash_brightness = brightness; - lock.config.write(); - } - - #[zbus(property)] - async fn interval(&self) -> u8 { - let lock = self.0.lock().await; - lock.config.slash_interval - } - - /// Set interval between slash animations (0-255) - #[zbus(property)] - async fn set_interval(&self, interval: u8) { - let mut lock = self.0.lock().await; - lock.node - .write_bytes(&pkt_set_options( - lock.config.slash_enabled, - lock.config.slash_brightness, - interval, - )) - .map_err(|err| { - warn!("ctrl_slash::set_options {}", err); - }) - .ok(); - - lock.config.slash_interval = interval; - lock.config.write(); - } - - #[zbus(property)] - async fn slash_mode(&self) -> u8 { - let lock = self.0.lock().await; - lock.config.slash_interval - } - - /// Set interval between slash animations (0-255) - #[zbus(property)] - async fn set_slash_mode(&self, slash_mode: SlashMode) { - let mut lock = self.0.lock().await; - - let command_packets = pkt_set_mode(slash_mode); - - lock.node - .write_bytes(&command_packets[0]) - .map_err(|err| { - warn!("ctrl_slash::set_options {}", err); - }) - .ok(); - lock.node - .write_bytes(&command_packets[1]) - .map_err(|err| { - warn!("ctrl_slash::set_options {}", err); - }) - .ok(); - - lock.config.slash_mode = slash_mode; - lock.config.write(); - } - - /// Get the device state as stored by asusd - // #[zbus(property)] - async fn device_state(&self) -> DeviceState { - let lock = self.0.lock().await; - DeviceState::from(&lock.config) - } -} - -impl crate::CtrlTask for CtrlSlashZbus { - fn zbus_path() -> &'static str { - SLASH_ZBUS_PATH - } - - async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> { - Ok(()) - } -} - -impl crate::Reloadable for CtrlSlashZbus { - async fn reload(&mut self) -> Result<(), RogError> { - Ok(()) - } -} diff --git a/asusd/src/daemon.rs b/asusd/src/daemon.rs index 3a8c7ba7..edc0191a 100644 --- a/asusd/src/daemon.rs +++ b/asusd/src/daemon.rs @@ -4,14 +4,10 @@ use std::sync::Arc; use ::zbus::export::futures_util::lock::Mutex; use ::zbus::Connection; +use asusd::aura_manager::DeviceManager; use asusd::config::Config; -use asusd::ctrl_anime::trait_impls::CtrlAnimeZbus; -use asusd::ctrl_anime::CtrlAnime; -use asusd::ctrl_aura::manager::AuraManager; use asusd::ctrl_fancurves::CtrlFanCurveZbus; use asusd::ctrl_platform::CtrlPlatform; -use asusd::ctrl_slash::trait_impls::CtrlSlashZbus; -use asusd::ctrl_slash::CtrlSlash; use asusd::{print_board_info, start_tasks, CtrlTask, DBUS_NAME}; use config_traits::{StdConfig, StdConfigLoad1}; use log::{error, info}; @@ -97,33 +93,7 @@ async fn start_daemon() -> Result<(), Box> { } } - match CtrlAnime::new() { - Ok(ctrl) => { - let zbus = CtrlAnimeZbus(Arc::new(Mutex::new(ctrl))); - let sig_ctx = CtrlAnimeZbus::signal_context(&connection)?; - start_tasks(zbus, &mut connection, sig_ctx).await?; - } - Err(err) => { - info!("AniMe control: {}", err); - } - } - - let _ = AuraManager::new(connection.clone()).await?; - - match CtrlSlash::new() { - Ok(ctrl) => { - let zbus = CtrlSlashZbus(Arc::new(Mutex::new(ctrl))); - // Currently, the Slash has no need for a loop watching power events, however, - // it could be cool to have the slash do some power-on/off animation - // (It has a built-in power on animation which plays when u plug in the power - // supply) - let sig_ctx = CtrlSlashZbus::signal_context(&connection)?; - start_tasks(zbus, &mut connection, sig_ctx).await?; - } - Err(err) => { - info!("AniMe control: {}", err); - } - } + let _ = DeviceManager::new(connection.clone()).await?; // Request dbus name after finishing initalizing all functions connection.request_name(DBUS_NAME).await?; diff --git a/asusd/src/lib.rs b/asusd/src/lib.rs index e2c8ca14..990aeff8 100644 --- a/asusd/src/lib.rs +++ b/asusd/src/lib.rs @@ -1,17 +1,16 @@ #![deny(unused_must_use)] /// Configuration loading, saving pub mod config; -/// Control of anime matrix display -pub mod ctrl_anime; -/// Keyboard LED brightness control, RGB, and LED display modes -pub mod ctrl_aura; /// Control platform profiles + fan-curves if available pub mod ctrl_fancurves; /// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode pub mod ctrl_platform; -/// Control of Slash led bar -pub mod ctrl_slash; +pub mod aura_anime; +pub mod aura_laptop; +pub mod aura_manager; +pub mod aura_slash; +pub mod aura_types; pub mod error; use std::future::Future; diff --git a/dmi-id/src/lib.rs b/dmi-id/src/lib.rs index c94a8ab1..faa28813 100644 --- a/dmi-id/src/lib.rs +++ b/dmi-id/src/lib.rs @@ -1,4 +1,4 @@ -use log::{info, warn}; +use log::warn; #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Clone)] pub struct DMIID { @@ -33,8 +33,6 @@ impl DMIID { })?; if let Some(device) = (result).next() { - info!("Found dmi ID info at {:?}", device.sysname()); - return Ok(Self { id_model: device .property_value("ID_MODEL") diff --git a/rog-anime/src/data.rs b/rog-anime/src/data.rs index 9b67dc4f..93074ab0 100644 --- a/rog-anime/src/data.rs +++ b/rog-anime/src/data.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use std::thread::sleep; use std::time::{Duration, Instant}; +use dmi_id::DMIID; use log::info; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -57,11 +58,12 @@ pub struct DeviceState { #[typeshare] #[cfg_attr(feature = "dbus", derive(Type), zvariant(signature = "s"))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default)] pub enum AnimeType { GA401, GA402, GU604, + #[default] Unsupported, } @@ -79,6 +81,19 @@ impl FromStr for AnimeType { } impl AnimeType { + pub fn from_dmi() -> Self { + let board_name = DMIID::new().unwrap_or_default().board_name.to_uppercase(); + if board_name.contains("GA401I") || board_name.contains("GA401Q") { + AnimeType::GA401 + } else if board_name.contains("GA402R") || board_name.contains("GA402X") { + AnimeType::GA402 + } else if board_name.contains("GU604V") { + AnimeType::GU604 + } else { + AnimeType::Unsupported + } + } + /// The width of diagonal images pub fn width(&self) -> usize { match self { diff --git a/rog-anime/src/usb.rs b/rog-anime/src/usb.rs index 528ae443..b9d3b3fa 100644 --- a/rog-anime/src/usb.rs +++ b/rog-anime/src/usb.rs @@ -247,19 +247,19 @@ impl From for i32 { /// /// The currently known USB device is `19b6`. #[inline] -pub fn get_maybe_anime_type() -> Result { - let dmi = DMIID::new().map_err(|_| AnimeError::NoDevice)?; // TODO: better error +pub fn get_anime_type() -> AnimeType { + let dmi = DMIID::new().unwrap_or_default(); let board_name = dmi.board_name; if board_name.contains("GA401I") || board_name.contains("GA401Q") { - return Ok(AnimeType::GA401); + AnimeType::GA401 } else if board_name.contains("GA402R") || board_name.contains("GA402X") { - return Ok(AnimeType::GA402); + AnimeType::GA402 } else if board_name.contains("GU604V") { - return Ok(AnimeType::GU604); + AnimeType::GU604 + } else { + AnimeType::Unsupported } - log::warn!("AniMe Matrix device found but could be a slash"); - Ok(AnimeType::Unsupported) } /// Get the two device initialization packets. These are required for device diff --git a/rog-aura/data/aura_support.ron b/rog-aura/data/aura_support.ron index a4c2e4ee..71f44cac 100644 --- a/rog-aura/data/aura_support.ron +++ b/rog-aura/data/aura_support.ron @@ -656,6 +656,15 @@ advanced_type: Zoned([SingleZone]), power_zones: [Keyboard], ), + ( + device_name: "GA605W", + product_id: "", + layout_name: "ga401q", + basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Pulse], + basic_zones: [], + advanced_type: Zoned([SingleZone]), + power_zones: [Keyboard], + ), ( device_name: "GV301Q", product_id: "", diff --git a/rog-aura/src/builtin_modes.rs b/rog-aura/src/builtin_modes.rs index 166507c6..49353963 100644 --- a/rog-aura/src/builtin_modes.rs +++ b/rog-aura/src/builtin_modes.rs @@ -7,7 +7,7 @@ use typeshare::typeshare; use zbus::zvariant::{OwnedValue, Type, Value}; use crate::error::Error; -use crate::LED_MSG_LEN; +use crate::AURA_LAPTOP_LED_MSG_LEN; #[typeshare] #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] @@ -552,9 +552,9 @@ impl AuraEffect { /// |---|---|-----|-----|---------|------|----------|---|-----------| /// |5d |b3 |Zone |Mode |Colour 1 |Speed |Direction |00 |Colour 2 | /// ``` -impl From<&AuraEffect> for [u8; LED_MSG_LEN] { +impl From<&AuraEffect> for [u8; AURA_LAPTOP_LED_MSG_LEN] { fn from(aura: &AuraEffect) -> Self { - let mut msg = [0u8; LED_MSG_LEN]; + let mut msg = [0u8; AURA_LAPTOP_LED_MSG_LEN]; msg[0] = 0x5d; msg[1] = 0xb3; msg[2] = aura.zone as u8; @@ -573,7 +573,7 @@ impl From<&AuraEffect> for [u8; LED_MSG_LEN] { impl From<&AuraEffect> for Vec { fn from(aura: &AuraEffect) -> Self { - let mut msg = vec![0u8; LED_MSG_LEN]; + let mut msg = vec![0u8; AURA_LAPTOP_LED_MSG_LEN]; msg[0] = 0x5d; msg[1] = 0xb3; msg[2] = aura.zone as u8; @@ -592,7 +592,9 @@ impl From<&AuraEffect> for Vec { #[cfg(test)] mod tests { - use crate::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, LED_MSG_LEN}; + use crate::{ + AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, AURA_LAPTOP_LED_MSG_LEN, + }; #[test] fn check_led_static_packet() { @@ -608,7 +610,7 @@ mod tests { speed: Speed::Med, direction: Direction::Right, }; - let ar = <[u8; LED_MSG_LEN]>::from(&st); + let ar = <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st); println!("{:02x?}", ar); let check = [ @@ -636,7 +638,10 @@ mod tests { 0x5d, 0xb3, 0x01, 0x00, 0xff, 0x00, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); st.zone = AuraZone::Key2; st.colour1 = Colour { @@ -648,7 +653,10 @@ mod tests { 0x5d, 0xb3, 0x02, 0x00, 0xff, 0xff, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); st.zone = AuraZone::Key3; st.colour1 = Colour { @@ -660,7 +668,10 @@ mod tests { 0x5d, 0xb3, 0x03, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); st.zone = AuraZone::Key4; st.colour1 = Colour { @@ -672,7 +683,10 @@ mod tests { 0x5d, 0xb3, 0x04, 0x00, 0xff, 0x00, 0xff, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); st.zone = AuraZone::Logo; st.colour1 = Colour { @@ -684,7 +698,10 @@ mod tests { 0x5d, 0xb3, 0x05, 0x00, 0x2c, 0xff, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); st.zone = AuraZone::BarLeft; st.colour1 = Colour { @@ -696,7 +713,10 @@ mod tests { 0x5d, 0xb3, 0x06, 0x00, 0xff, 0x00, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); st.zone = AuraZone::BarRight; st.colour1 = Colour { @@ -708,13 +728,19 @@ mod tests { 0x5d, 0xb3, 0x07, 0x00, 0xff, 0x00, 0xcd, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); st.mode = AuraModeNum::RainbowWave; let capture = [ 0x5d, 0xb3, 0x07, 0x03, 0xff, 0x00, 0xcd, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; - assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]); + assert_eq!( + <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9], + capture[..9] + ); } } diff --git a/rog-aura/src/effects/mod.rs b/rog-aura/src/effects/mod.rs index 39b036de..0fbc7cb9 100644 --- a/rog-aura/src/effects/mod.rs +++ b/rog-aura/src/effects/mod.rs @@ -12,7 +12,7 @@ pub use breathe::*; mod static_; pub use static_::*; -use crate::keyboard::{KeyLayout, LedCode, LedUsbPackets, UsbPackets}; +use crate::keyboard::{AuraLaptopUsbPackets, KeyLayout, LedCode, LedUsbPackets}; use crate::Colour; // static mut RNDINDEX: usize = 0; @@ -106,7 +106,7 @@ impl AdvancedEffects { } } - pub fn create_packets(&self) -> UsbPackets { + pub fn create_packets(&self) -> AuraLaptopUsbPackets { let mut usb_packets = if self.zoned { // TODO: figure out if that single byte difference for multizone actually // matters diff --git a/rog-aura/src/keyboard/advanced.rs b/rog-aura/src/keyboard/advanced.rs index 822b82c6..e548493e 100644 --- a/rog-aura/src/keyboard/advanced.rs +++ b/rog-aura/src/keyboard/advanced.rs @@ -195,7 +195,7 @@ impl LedCode { /// Represents the per-key raw USB packets #[typeshare] -pub type UsbPackets = Vec>; +pub type AuraLaptopUsbPackets = Vec>; /// A `UsbPackets` contains all data to change the full set of keyboard /// key colours individually. @@ -209,7 +209,7 @@ pub type UsbPackets = Vec>; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct LedUsbPackets { /// The packet data used to send data to the USB keyboard - usb_packets: UsbPackets, + usb_packets: AuraLaptopUsbPackets, /// Wether or not this packet collection is zoned. The determines which /// starting bytes are used and what the indexing is for lightbar RGB /// colours @@ -472,22 +472,22 @@ impl LedUsbPackets { } #[inline] - pub fn get(&self) -> UsbPackets { + pub fn get(&self) -> AuraLaptopUsbPackets { self.usb_packets.clone() } #[inline] - pub fn get_ref(&self) -> &UsbPackets { + pub fn get_ref(&self) -> &AuraLaptopUsbPackets { &self.usb_packets } #[inline] - pub fn get_mut(&mut self) -> &mut UsbPackets { + pub fn get_mut(&mut self) -> &mut AuraLaptopUsbPackets { &mut self.usb_packets } } -impl From for UsbPackets { +impl From for AuraLaptopUsbPackets { fn from(k: LedUsbPackets) -> Self { k.usb_packets } @@ -643,7 +643,7 @@ impl From<&LedCode> for &str { #[cfg(test)] mod tests { - use crate::keyboard::{LedCode, LedUsbPackets, UsbPackets}; + use crate::keyboard::{AuraLaptopUsbPackets, LedCode, LedUsbPackets}; macro_rules! colour_check_zoned { ($zone:expr, $pkt_idx_start:expr) => { @@ -653,7 +653,7 @@ mod tests { c[1] = 255; c[2] = 255; - let pkt: UsbPackets = zone.into(); + let pkt: AuraLaptopUsbPackets = zone.into(); assert_eq!(pkt[0][$pkt_idx_start], 0xff); assert_eq!(pkt[0][$pkt_idx_start + 1], 0xff); assert_eq!(pkt[0][$pkt_idx_start + 2], 0xff); @@ -663,7 +663,7 @@ mod tests { #[test] fn zone_to_packet_check() { let zone = LedUsbPackets::new_zoned(true); - let pkt: UsbPackets = zone.into(); + let pkt: AuraLaptopUsbPackets = zone.into(); assert_eq!(pkt[0][0], 0x5d); assert_eq!(pkt[0][1], 0xbc); assert_eq!(pkt[0][2], 0x01); @@ -686,7 +686,7 @@ mod tests { #[test] fn perkey_to_packet_check() { let per_key = LedUsbPackets::new_per_key(); - let pkt: UsbPackets = per_key.into(); + let pkt: AuraLaptopUsbPackets = per_key.into(); assert_eq!(pkt[0][0], 0x5d); assert_eq!(pkt[0][1], 0xbc); assert_eq!(pkt[0][2], 0x00); @@ -712,7 +712,7 @@ mod tests { c[1] = 255; c[2] = 255; - let pkt: UsbPackets = per_key.into(); + let pkt: AuraLaptopUsbPackets = per_key.into(); assert_eq!(pkt[5][30], 0xff); // D, red assert_eq!(pkt[5][31], 0xff); // D assert_eq!(pkt[5][32], 0xff); // D diff --git a/rog-aura/src/lib.rs b/rog-aura/src/lib.rs index 3d251e45..0efb7f3b 100644 --- a/rog-aura/src/lib.rs +++ b/rog-aura/src/lib.rs @@ -24,7 +24,7 @@ pub mod usb; pub mod keyboard; -pub const LED_MSG_LEN: usize = 17; +pub const AURA_LAPTOP_LED_MSG_LEN: usize = 17; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const RED: Colour = Colour { @@ -108,8 +108,9 @@ impl From<&str> for AuraDeviceType { "1932" => AuraDeviceType::ScsiExtDisk, "1866" | "18c6" | "1869" | "1854" => Self::LaptopKeyboardPre2021, "1abe" | "1b4c" => Self::Ally, - "19b3" => Self::AnimeOrSlash, - _ => Self::LaptopKeyboard2021, + "19b3" | "193b" => Self::AnimeOrSlash, + "19b6" => Self::LaptopKeyboard2021, + _ => Self::Unknown, } } } diff --git a/rog-aura/src/usb.rs b/rog-aura/src/usb.rs index b4582e31..470a383b 100644 --- a/rog-aura/src/usb.rs +++ b/rog-aura/src/usb.rs @@ -1,10 +1,4 @@ // Only these two packets must be 17 bytes -pub const LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; -pub const LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - -/// Writes out the correct byte string for brightness -pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] { - [ - 0x5a, 0xba, 0xc5, 0xc4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ] -} +pub const AURA_LAPTOP_LED_APPLY: [u8; 17] = + [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +pub const AURA_LAPTOP_LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; diff --git a/rog-control-center/translations/en/rog-control-center.po b/rog-control-center/translations/en/rog-control-center.po index dd381253..97ae7ca3 100644 --- a/rog-control-center/translations/en/rog-control-center.po +++ b/rog-control-center/translations/en/rog-control-center.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-12-17 21:33+0000\n" +"POT-Creation-Date: 2024-12-18 23:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -412,198 +412,198 @@ msgctxt "PageAppSettings" msgid "Enable dGPU notifications" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:51 +#: rog-control-center/ui/types/aura_types.slint:52 msgctxt "Aura power zone" msgid "Logo" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:52 rog-control-center/ui/types/aura_types.slint:62 -msgctxt "Aura power zone" -msgid "Keyboard" -msgstr "" - #: rog-control-center/ui/types/aura_types.slint:53 rog-control-center/ui/types/aura_types.slint:63 msgctxt "Aura power zone" -msgid "Lightbar" +msgid "Keyboard" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:54 +#: rog-control-center/ui/types/aura_types.slint:54 rog-control-center/ui/types/aura_types.slint:64 msgctxt "Aura power zone" -msgid "Lid" +msgid "Lightbar" msgstr "" #: rog-control-center/ui/types/aura_types.slint:55 msgctxt "Aura power zone" +msgid "Lid" +msgstr "" + +#: rog-control-center/ui/types/aura_types.slint:56 +msgctxt "Aura power zone" msgid "Rear Glow" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:56 rog-control-center/ui/types/aura_types.slint:64 +#: rog-control-center/ui/types/aura_types.slint:57 rog-control-center/ui/types/aura_types.slint:65 msgctxt "Aura power zone" msgid "Keyboard and Lightbar" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:57 +#: rog-control-center/ui/types/aura_types.slint:58 msgctxt "Aura power zone" msgid "Ally" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:67 +#: rog-control-center/ui/types/aura_types.slint:68 msgctxt "Aura brightness" msgid "Off" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:68 -msgctxt "Aura brightness" -msgid "Low" -msgstr "" - #: rog-control-center/ui/types/aura_types.slint:69 msgctxt "Aura brightness" -msgid "Med" +msgid "Low" msgstr "" #: rog-control-center/ui/types/aura_types.slint:70 msgctxt "Aura brightness" -msgid "High" +msgid "Med" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:75 rog-control-center/ui/types/aura_types.slint:90 -msgctxt "Basic aura mode" -msgid "Static" +#: rog-control-center/ui/types/aura_types.slint:71 +msgctxt "Aura brightness" +msgid "High" msgstr "" #: rog-control-center/ui/types/aura_types.slint:76 rog-control-center/ui/types/aura_types.slint:91 msgctxt "Basic aura mode" -msgid "Breathe" +msgid "Static" msgstr "" #: rog-control-center/ui/types/aura_types.slint:77 rog-control-center/ui/types/aura_types.slint:92 msgctxt "Basic aura mode" -msgid "Strobe" +msgid "Breathe" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:78 +#: rog-control-center/ui/types/aura_types.slint:78 rog-control-center/ui/types/aura_types.slint:93 msgctxt "Basic aura mode" -msgid "Rainbow" +msgid "Strobe" msgstr "" #: rog-control-center/ui/types/aura_types.slint:79 msgctxt "Basic aura mode" -msgid "Star" +msgid "Rainbow" msgstr "" #: rog-control-center/ui/types/aura_types.slint:80 msgctxt "Basic aura mode" -msgid "Rain" +msgid "Star" msgstr "" #: rog-control-center/ui/types/aura_types.slint:81 msgctxt "Basic aura mode" -msgid "Highlight" +msgid "Rain" msgstr "" #: rog-control-center/ui/types/aura_types.slint:82 msgctxt "Basic aura mode" -msgid "Laser" +msgid "Highlight" msgstr "" #: rog-control-center/ui/types/aura_types.slint:83 msgctxt "Basic aura mode" -msgid "Ripple" +msgid "Laser" msgstr "" #: rog-control-center/ui/types/aura_types.slint:84 msgctxt "Basic aura mode" -msgid "Nothing" +msgid "Ripple" msgstr "" #: rog-control-center/ui/types/aura_types.slint:85 msgctxt "Basic aura mode" -msgid "Pulse" +msgid "Nothing" msgstr "" #: rog-control-center/ui/types/aura_types.slint:86 msgctxt "Basic aura mode" -msgid "Comet" +msgid "Pulse" msgstr "" #: rog-control-center/ui/types/aura_types.slint:87 msgctxt "Basic aura mode" -msgid "Flash" +msgid "Comet" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:99 -msgctxt "Aura zone" -msgid "None" +#: rog-control-center/ui/types/aura_types.slint:88 +msgctxt "Basic aura mode" +msgid "Flash" msgstr "" #: rog-control-center/ui/types/aura_types.slint:100 msgctxt "Aura zone" -msgid "Key1" +msgid "None" msgstr "" #: rog-control-center/ui/types/aura_types.slint:101 msgctxt "Aura zone" -msgid "Key2" +msgid "Key1" msgstr "" #: rog-control-center/ui/types/aura_types.slint:102 msgctxt "Aura zone" -msgid "Key3" +msgid "Key2" msgstr "" #: rog-control-center/ui/types/aura_types.slint:103 msgctxt "Aura zone" -msgid "Key4" +msgid "Key3" msgstr "" #: rog-control-center/ui/types/aura_types.slint:104 msgctxt "Aura zone" -msgid "Logo" +msgid "Key4" msgstr "" #: rog-control-center/ui/types/aura_types.slint:105 msgctxt "Aura zone" -msgid "Lightbar Left" +msgid "Logo" msgstr "" #: rog-control-center/ui/types/aura_types.slint:106 msgctxt "Aura zone" -msgid "Lightbar Right" +msgid "Lightbar Left" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:110 -msgctxt "Aura direction" -msgid "Right" +#: rog-control-center/ui/types/aura_types.slint:107 +msgctxt "Aura zone" +msgid "Lightbar Right" msgstr "" #: rog-control-center/ui/types/aura_types.slint:111 msgctxt "Aura direction" -msgid "Left" +msgid "Right" msgstr "" #: rog-control-center/ui/types/aura_types.slint:112 msgctxt "Aura direction" -msgid "Up" +msgid "Left" msgstr "" #: rog-control-center/ui/types/aura_types.slint:113 msgctxt "Aura direction" -msgid "Down" +msgid "Up" msgstr "" -#: rog-control-center/ui/types/aura_types.slint:117 -msgctxt "Aura speed" -msgid "Low" +#: rog-control-center/ui/types/aura_types.slint:114 +msgctxt "Aura direction" +msgid "Down" msgstr "" #: rog-control-center/ui/types/aura_types.slint:118 msgctxt "Aura speed" -msgid "Medium" +msgid "Low" msgstr "" #: rog-control-center/ui/types/aura_types.slint:119 msgctxt "Aura speed" +msgid "Medium" +msgstr "" + +#: rog-control-center/ui/types/aura_types.slint:120 +msgctxt "Aura speed" msgid "High" msgstr "" diff --git a/rog-dbus/src/zbus_aura.rs b/rog-dbus/src/zbus_aura.rs index cb9248a6..82bf706b 100644 --- a/rog-dbus/src/zbus_aura.rs +++ b/rog-dbus/src/zbus_aura.rs @@ -22,7 +22,7 @@ use std::collections::BTreeMap; -use rog_aura::keyboard::{LaptopAuraPower, UsbPackets}; +use rog_aura::keyboard::{AuraLaptopUsbPackets, LaptopAuraPower}; use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones}; use zbus::blocking::Connection; use zbus::{proxy, Result}; @@ -39,7 +39,7 @@ pub trait Aura { fn all_mode_data(&self) -> zbus::Result>; /// DirectAddressingRaw method - fn direct_addressing_raw(&self, data: UsbPackets) -> zbus::Result<()>; + fn direct_addressing_raw(&self, data: AuraLaptopUsbPackets) -> zbus::Result<()>; /// Brightness property #[zbus(property)] @@ -104,7 +104,7 @@ impl<'a> AuraProxyPerkey<'a> { /// Intentionally blocks for 10ms after sending to allow the block to /// be written to the keyboard EC. This should not be async. #[inline] - pub fn direct_addressing_raw(&self, direct_raw: UsbPackets) -> Result<()> { + pub fn direct_addressing_raw(&self, direct_raw: AuraLaptopUsbPackets) -> Result<()> { self.0.direct_addressing_raw(direct_raw)?; std::thread::sleep(std::time::Duration::from_millis(BLOCKING_TIME)); Ok(()) diff --git a/rog-platform/src/hid_raw.rs b/rog-platform/src/hid_raw.rs index 67884d7f..abb349c6 100644 --- a/rog-platform/src/hid_raw.rs +++ b/rog-platform/src/hid_raw.rs @@ -44,40 +44,24 @@ impl HidRaw { PlatformError::IoPath(endpoint.devpath().to_string_lossy().to_string(), e) })? { - if let Some(parent_id) = usb_device.attribute_value("idProduct") { - if parent_id == id_product { - if let Some(dev_node) = endpoint.devnode() { - info!("Using device at: {:?} for hidraw control", dev_node); - return Ok(Self { - file: RefCell::new(OpenOptions::new().write(true).open(dev_node)?), - devfs_path: dev_node.to_owned(), - prod_id: id_product.to_string(), - syspath: endpoint.syspath().into(), - _device_bcd: usb_device - .attribute_value("bcdDevice") - .unwrap_or_default() - .to_string_lossy() - .parse() - .unwrap_or_default(), - }); + if let Some(dev_node) = endpoint.devnode() { + if let Some(this_id_product) = usb_device.attribute_value("idProduct") { + if this_id_product != id_product { + continue; + } + let dev_path = endpoint.devpath().to_string_lossy(); + if dev_path.contains("virtual") { + info!( + "Using device at: {:?} for control", + dev_node + ); } - } - } - } else { - // Try to see if there is a virtual device created with uhid for testing - let dev_path = endpoint.devpath().to_string_lossy(); - if dev_path.contains("virtual") && dev_path.contains(&id_product.to_uppercase()) { - if let Some(dev_node) = endpoint.devnode() { - info!( - "Using device at: {:?} for control", - dev_node - ); return Ok(Self { file: RefCell::new(OpenOptions::new().write(true).open(dev_node)?), devfs_path: dev_node.to_owned(), - prod_id: id_product.to_string(), + prod_id: this_id_product.to_string_lossy().into(), syspath: endpoint.syspath().into(), - _device_bcd: endpoint + _device_bcd: usb_device .attribute_value("bcdDevice") .unwrap_or_default() .to_string_lossy() @@ -95,19 +79,21 @@ impl HidRaw { } /// Make `HidRaw` device from a udev device - pub fn from_device(device: Device) -> Result { - if let Some(parent) = device + pub fn from_device(endpoint: Device) -> Result { + if let Some(parent) = endpoint .parent_with_subsystem_devtype("usb", "usb_device") - .map_err(|e| PlatformError::IoPath(device.devpath().to_string_lossy().to_string(), e))? + .map_err(|e| { + PlatformError::IoPath(endpoint.devpath().to_string_lossy().to_string(), e) + })? { - if let Some(dev_node) = device.devnode() { + if let Some(dev_node) = endpoint.devnode() { if let Some(id_product) = parent.attribute_value("idProduct") { return Ok(Self { file: RefCell::new(OpenOptions::new().write(true).open(dev_node)?), devfs_path: dev_node.to_owned(), prod_id: id_product.to_string_lossy().into(), - syspath: device.syspath().into(), - _device_bcd: device + syspath: endpoint.syspath().into(), + _device_bcd: endpoint .attribute_value("bcdDevice") .unwrap_or_default() .to_string_lossy() @@ -129,7 +115,6 @@ impl HidRaw { /// Write an array of raw bytes to the device using the hidraw interface pub fn write_bytes(&self, message: &[u8]) -> Result<()> { if let Ok(mut file) = self.file.try_borrow_mut() { - // let mut file = self.file.borrow_mut(); // TODO: re-get the file if error? file.write_all(message).map_err(|e| { PlatformError::IoPath(self.devfs_path.to_string_lossy().to_string(), e) diff --git a/rog-slash/src/data.rs b/rog-slash/src/data.rs index 029e2d57..6ff95bf4 100644 --- a/rog-slash/src/data.rs +++ b/rog-slash/src/data.rs @@ -1,6 +1,7 @@ use std::fmt::Display; use std::str::FromStr; +use dmi_id::DMIID; use serde::{Deserialize, Serialize}; use typeshare::typeshare; #[cfg(feature = "dbus")] @@ -8,15 +9,50 @@ use zbus::zvariant::Type; use zbus::zvariant::{OwnedValue, Value}; use crate::error::SlashError; +use crate::usb::{PROD_ID1, PROD_ID1_STR, PROD_ID2, PROD_ID2_STR}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] pub enum SlashType { GA403, GA605, GU605, + #[default] Unsupported, } +impl SlashType { + pub const fn prod_id(&self) -> u16 { + match self { + SlashType::GA403 => PROD_ID1, + SlashType::GA605 => PROD_ID2, + SlashType::GU605 => PROD_ID1, + SlashType::Unsupported => 0, + } + } + + pub const fn prod_id_str(&self) -> &str { + match self { + SlashType::GA403 => PROD_ID1_STR, + SlashType::GA605 => PROD_ID2_STR, + SlashType::GU605 => PROD_ID1_STR, + SlashType::Unsupported => "", + } + } + + pub fn from_dmi() -> Self { + let board_name = DMIID::new().unwrap_or_default().board_name.to_uppercase(); + if board_name.contains("GA403") { + SlashType::GA403 + } else if board_name.contains("GA605") { + SlashType::GA605 + } else if board_name.contains("GU605") { + SlashType::GU605 + } else { + SlashType::Unsupported + } + } +} + impl FromStr for SlashType { type Err = SlashError; diff --git a/rog-slash/src/usb.rs b/rog-slash/src/usb.rs index 1ec28aaa..61109e05 100644 --- a/rog-slash/src/usb.rs +++ b/rog-slash/src/usb.rs @@ -14,11 +14,16 @@ use dmi_id::DMIID; use crate::error::SlashError; use crate::{SlashMode, SlashType}; -const PACKET_SIZE: usize = 128; -const DEV_PAGE: u8 = 0x5e; +const PACKET_SIZE: usize = 32; +const REPORT_ID_193B: u8 = 0x5e; +const REPORT_ID_19B6: u8 = 0x5d; + pub const VENDOR_ID: u16 = 0x0b05; -pub const PROD_ID: u16 = 0x193b; -pub const PROD_ID_STR: &str = "193B"; + +pub const PROD_ID1: u16 = 0x193b; +pub const PROD_ID1_STR: &str = "193B"; +pub const PROD_ID2: u16 = 0x19b6; +pub const PROD_ID2_STR: &str = "19B6"; pub type SlashUsbPacket = [u8; PACKET_SIZE]; @@ -28,26 +33,40 @@ pub type SlashUsbPacket = [u8; PACKET_SIZE]; /// /// The currently known USB device is `193B`. #[inline] -pub fn get_maybe_slash_type() -> Result { - let dmi = DMIID::new().map_err(|_| SlashError::NoDevice)?; // TODO: better error +pub fn get_slash_type() -> SlashType { + let dmi = DMIID::new() + .map_err(|_| SlashError::NoDevice) + .unwrap_or_default(); let board_name = dmi.board_name; if board_name.contains("GA403") { - return Ok(SlashType::GA403); + SlashType::GA403 } else if board_name.contains("GA605") { - return Ok(SlashType::GA605); + SlashType::GA605 } else if board_name.contains("GU605") { - return Ok(SlashType::GU605); + SlashType::GU605 + } else { + SlashType::Unsupported + } +} + +pub const fn report_id(slash_type: SlashType) -> u8 { + match slash_type { + SlashType::GA403 => REPORT_ID_193B, + SlashType::GA605 => REPORT_ID_19B6, + SlashType::GU605 => REPORT_ID_193B, + SlashType::Unsupported => REPORT_ID_19B6, } - Ok(SlashType::Unsupported) } /// Get the two device initialization packets. These are required for device /// start after the laptop boots. #[inline] -pub const fn pkts_for_init() -> [SlashUsbPacket; 2] { +pub fn pkts_for_init(slash_type: SlashType) -> [SlashUsbPacket; 2] { + let report_id = report_id(slash_type); + let mut pkt1 = [0; PACKET_SIZE]; - pkt1[0] = DEV_PAGE; + pkt1[0] = report_id; pkt1[1] = 0xd7; pkt1[2] = 0x00; pkt1[3] = 0x00; @@ -55,7 +74,7 @@ pub const fn pkts_for_init() -> [SlashUsbPacket; 2] { pkt1[5] = 0xac; let mut pkt2 = [0; PACKET_SIZE]; - pkt2[0] = DEV_PAGE; + pkt2[0] = report_id; pkt2[1] = 0xd2; pkt2[2] = 0x02; pkt2[3] = 0x01; @@ -66,9 +85,9 @@ pub const fn pkts_for_init() -> [SlashUsbPacket; 2] { } #[inline] -pub const fn pkt_save() -> SlashUsbPacket { +pub const fn pkt_save(slash_type: SlashType) -> SlashUsbPacket { let mut pkt = [0; PACKET_SIZE]; - pkt[0] = DEV_PAGE; + pkt[0] = report_id(slash_type); pkt[1] = 0xd4; pkt[2] = 0x00; pkt[3] = 0x00; @@ -79,16 +98,17 @@ pub const fn pkt_save() -> SlashUsbPacket { } #[inline] -pub const fn pkt_set_mode(mode: SlashMode) -> [SlashUsbPacket; 2] { +pub const fn pkt_set_mode(slash_type: SlashType, mode: SlashMode) -> [SlashUsbPacket; 2] { + let report_id = report_id(slash_type); let mut pkt1 = [0; PACKET_SIZE]; - pkt1[0] = DEV_PAGE; - pkt1[1] = 0x02; + pkt1[0] = report_id; + pkt1[1] = 0xd2; pkt1[2] = 0x03; pkt1[3] = 0x00; pkt1[4] = 0x0c; let mut pkt2 = [0; PACKET_SIZE]; - pkt2[0] = DEV_PAGE; + pkt2[0] = report_id; pkt2[1] = 0xd3; pkt2[2] = 0x04; pkt2[3] = 0x00; @@ -96,7 +116,7 @@ pub const fn pkt_set_mode(mode: SlashMode) -> [SlashUsbPacket; 2] { pkt2[5] = 0x01; pkt2[6] = mode as u8; pkt2[7] = 0x02; - pkt2[8] = 0x19; + pkt2[8] = 0x19; // difference, GA605 = 0x10 pkt2[9] = 0x03; pkt2[10] = 0x13; pkt2[11] = 0x04; @@ -110,11 +130,16 @@ pub const fn pkt_set_mode(mode: SlashMode) -> [SlashUsbPacket; 2] { } #[inline] -pub const fn pkt_set_options(enabled: bool, brightness: u8, interval: u8) -> SlashUsbPacket { +pub const fn pkt_set_options( + slash_type: SlashType, + enabled: bool, + brightness: u8, + interval: u8, +) -> SlashUsbPacket { let status_byte = if enabled { 0x01 } else { 0x00 }; let mut pkt = [0; PACKET_SIZE]; - pkt[0] = DEV_PAGE; + pkt[0] = report_id(slash_type); pkt[1] = 0xd3; pkt[2] = 0x03; pkt[3] = 0x01;