From 0f2d89858e618d321861e5b5a01186748d9c250b Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Sat, 21 Dec 2024 20:35:51 +1300 Subject: [PATCH] SCSI support: ROG Arion external drive LED control --- CHANGELOG.md | 5 +- Cargo.lock | 149 ++++--- Cargo.toml | 4 +- asusctl/Cargo.toml | 1 + asusctl/src/cli_opts.rs | 3 + asusctl/src/main.rs | 78 ++++ asusctl/src/scsi_cli.rs | 35 ++ asusd/Cargo.toml | 1 + asusd/src/aura_manager.rs | 284 ++++++++++--- asusd/src/aura_scsi/config.rs | 114 +++++ asusd/src/aura_scsi/mod.rs | 45 ++ asusd/src/aura_scsi/trait_impls.rs | 116 +++++ asusd/src/aura_types.rs | 23 +- asusd/src/lib.rs | 1 + rog-aura/src/builtin_modes.rs | 50 --- .../translations/en/rog-control-center.po | 2 +- rog-dbus/Cargo.toml | 1 + rog-dbus/src/lib.rs | 1 + rog-dbus/src/scsi_aura.rs | 56 +++ rog-scsi/Cargo.toml | 29 ++ rog-scsi/src/builtin_modes.rs | 398 ++++++++++++++++++ rog-scsi/src/error.rs | 41 ++ rog-scsi/src/lib.rs | 48 +++ rog-scsi/src/scsi.rs | 80 ++++ 24 files changed, 1393 insertions(+), 172 deletions(-) create mode 100644 asusctl/src/scsi_cli.rs create mode 100644 asusd/src/aura_scsi/config.rs create mode 100644 asusd/src/aura_scsi/mod.rs create mode 100644 asusd/src/aura_scsi/trait_impls.rs create mode 100644 rog-dbus/src/scsi_aura.rs create mode 100644 rog-scsi/Cargo.toml create mode 100644 rog-scsi/src/builtin_modes.rs create mode 100644 rog-scsi/src/error.rs create mode 100644 rog-scsi/src/lib.rs create mode 100644 rog-scsi/src/scsi.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f8bab2a5..bd1831a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ ## [Unreleased] +### Added +- ROG Arion external driver LED support +- Add GA605W LED layout + ### 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 b2f81394..39502dbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,6 +187,7 @@ dependencies = [ "rog_dbus", "rog_platform", "rog_profiles", + "rog_scsi", "rog_slash", "ron", "zbus 5.2.0", @@ -210,6 +211,7 @@ dependencies = [ "rog_aura", "rog_platform", "rog_profiles", + "rog_scsi", "rog_slash", "serde", "tokio", @@ -549,12 +551,32 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.90", "which", ] +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.0", + "shlex", + "syn 2.0.90", +] + [[package]] name = "bit_field" version = "0.10.2" @@ -648,18 +670,18 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", @@ -731,9 +753,9 @@ checksum = "7b02b629252fe8ef6460461409564e2c21d0c8e77e0944f3d189ff06c4e932ad" [[package]] name = "cc" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -930,7 +952,7 @@ dependencies = [ [[package]] name = "const-field-offset" version = "0.1.5" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "const-field-offset-macro", "field-offset", @@ -939,7 +961,7 @@ dependencies = [ [[package]] name = "const-field-offset-macro" version = "0.1.5" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "proc-macro2", "quote", @@ -2150,8 +2172,8 @@ dependencies = [ [[package]] name = "i-slint-backend-linuxkms" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "calloop 0.14.2", "drm", @@ -2168,8 +2190,8 @@ dependencies = [ [[package]] name = "i-slint-backend-selector" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "cfg-if", "i-slint-backend-linuxkms", @@ -2181,8 +2203,8 @@ dependencies = [ [[package]] name = "i-slint-backend-winit" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "ashpd", "cfg-if", @@ -2212,8 +2234,8 @@ dependencies = [ [[package]] name = "i-slint-common" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "cfg-if", "derive_more", @@ -2224,8 +2246,8 @@ dependencies = [ [[package]] name = "i-slint-compiler" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "by_address", "codemap", @@ -2254,8 +2276,8 @@ dependencies = [ [[package]] name = "i-slint-core" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "auto_enums", "bitflags 2.6.0", @@ -2299,8 +2321,8 @@ dependencies = [ [[package]] name = "i-slint-core-macros" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "quote", "serde_json", @@ -2309,8 +2331,8 @@ dependencies = [ [[package]] name = "i-slint-renderer-femtovg" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "cfg-if", "const-field-offset", @@ -2339,8 +2361,8 @@ dependencies = [ [[package]] name = "i-slint-renderer-skia" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "bytemuck", "cfg-if", @@ -3195,6 +3217,7 @@ dependencies = [ "cfg_aliases", "libc", "memoffset", + "pin-utils", ] [[package]] @@ -3538,9 +3561,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -3670,9 +3693,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.15" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -4145,6 +4168,7 @@ dependencies = [ "rog_aura", "rog_platform", "rog_profiles", + "rog_scsi", "rog_slash", "zbus 5.2.0", ] @@ -4177,6 +4201,19 @@ dependencies = [ "zbus 5.2.0", ] +[[package]] +name = "rog_scsi" +version = "6.0.12" +dependencies = [ + "cargo-husky", + "log", + "ron", + "serde", + "sg", + "typeshare", + "zbus 5.2.0", +] + [[package]] name = "rog_simulators" version = "6.0.12" @@ -4218,7 +4255,7 @@ checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d" dependencies = [ "countme", "hashbrown 0.14.5", - "rustc-hash", + "rustc-hash 1.1.0", "text-size", ] @@ -4250,6 +4287,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustc_version" version = "0.4.1" @@ -4411,9 +4454,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -4441,6 +4484,16 @@ dependencies = [ "serde", ] +[[package]] +name = "sg" +version = "0.4.0" +source = "git+https://github.com/flukejones/sg-rs.git#b1ce961ae42b0aad22166bac84e5105a918debd3" +dependencies = [ + "bindgen 0.71.1", + "libc", + "nix", +] + [[package]] name = "sha1" version = "0.10.6" @@ -4512,7 +4565,7 @@ version = "0.78.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29880a81b088de322e9c5306236c70761a61b5fa4df3c15c93bad3ce890ce34c" dependencies = [ - "bindgen", + "bindgen 0.69.5", "cc", "flate2", "heck", @@ -4546,8 +4599,8 @@ dependencies = [ [[package]] name = "slint" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "const-field-offset", "i-slint-backend-selector", @@ -4563,8 +4616,8 @@ dependencies = [ [[package]] name = "slint-build" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "i-slint-compiler", "i-slint-core-macros", @@ -4575,8 +4628,8 @@ dependencies = [ [[package]] name = "slint-macros" -version = "1.9.0" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +version = "1.9.1" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "i-slint-compiler", "proc-macro2", @@ -5023,9 +5076,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -5357,7 +5410,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e24880fbcee511571ed9104b9a5273d1563d11ccaaf54b7c05cc6c100b652f9f" dependencies = [ - "bindgen", + "bindgen 0.69.5", ] [[package]] @@ -5537,7 +5590,7 @@ dependencies = [ [[package]] name = "vtable" version = "0.2.1" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "const-field-offset", "portable-atomic", @@ -5548,7 +5601,7 @@ dependencies = [ [[package]] name = "vtable-macro" version = "0.2.1" -source = "git+https://github.com/slint-ui/slint.git#09c9857082ff0e5cef6cdb4ed4396aab9eafb9d4" +source = "git+https://github.com/slint-ui/slint.git#e125da180d34df9e221cb925ea5c1af6e813bd8f" dependencies = [ "proc-macro2", "quote", @@ -6176,9 +6229,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.5" +version = "0.30.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" +checksum = "7c3d72dfa0f47e429290cd0d236884ca02f22dbd5dd33a43ad2b8bf4d79b6c18" dependencies = [ "ahash", "android-activity", diff --git a/Cargo.toml b/Cargo.toml index 8973b4a3..8b1f9282 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ "rog-profiles", "rog-control-center", "rog-slash", - "simulators", + "simulators", "rog-scsi", ] default-members = [ "asusctl", @@ -73,6 +73,8 @@ versions = "6.2" notify-rust = { version = "4.11.0", features = ["z", "async"] } +sg = {git = "https://github.com/flukejones/sg-rs.git"} + [profile.release] # thin = 57s, asusd = 9.0M # fat = 72s, asusd = 6.4M diff --git a/asusctl/Cargo.toml b/asusctl/Cargo.toml index a497efec..082cb718 100644 --- a/asusctl/Cargo.toml +++ b/asusctl/Cargo.toml @@ -10,6 +10,7 @@ edition.workspace = true [dependencies] rog_anime = { path = "../rog-anime" } +rog_scsi = { path = "../rog-scsi" } rog_slash = { path = "../rog-slash" } rog_aura = { path = "../rog-aura" } rog_dbus = { path = "../rog-dbus" } diff --git a/asusctl/src/cli_opts.rs b/asusctl/src/cli_opts.rs index 04240b6f..525089bb 100644 --- a/asusctl/src/cli_opts.rs +++ b/asusctl/src/cli_opts.rs @@ -4,6 +4,7 @@ use rog_platform::platform::ThrottlePolicy; use crate::anime_cli::AnimeCommand; use crate::aura_cli::{LedBrightness, LedPowerCommand1, LedPowerCommand2, SetAuraBuiltin}; use crate::fan_curve_cli::FanCurveCommand; +use crate::scsi_cli::ScsiCommand; use crate::slash_cli::SlashCommand; #[derive(Default, Options)] @@ -46,6 +47,8 @@ pub enum CliCommand { Anime(AnimeCommand), #[options(name = "slash", help = "Manage Slash Ledbar")] Slash(SlashCommand), + #[options(name = "scsi", help = "Manage SCSI external drive")] + Scsi(ScsiCommand), #[options(help = "Change bios settings")] Platform(PlatformCommand), } diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index eb087634..f8e264f0 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -14,6 +14,7 @@ use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, use rog_aura::keyboard::{AuraPowerState, LaptopAuraPower}; use rog_aura::{self, AuraDeviceType, AuraEffect, PowerZones}; use rog_dbus::list_iface_blocking; +use rog_dbus::scsi_aura::ScsiAuraProxyBlocking; use rog_dbus::zbus_anime::AnimeProxyBlocking; use rog_dbus::zbus_aura::AuraProxyBlocking; use rog_dbus::zbus_fan_curves::FanCurvesProxyBlocking; @@ -21,8 +22,10 @@ use rog_dbus::zbus_platform::PlatformProxyBlocking; use rog_dbus::zbus_slash::SlashProxyBlocking; use rog_platform::platform::{GpuMode, Properties, ThrottlePolicy}; use rog_profiles::error::ProfileError; +use rog_scsi::AuraMode; use rog_slash::SlashMode; use ron::ser::PrettyConfig; +use scsi_cli::ScsiCommand; use zbus::blocking::proxy::ProxyImpl; use zbus::blocking::Connection; @@ -34,6 +37,7 @@ mod anime_cli; mod aura_cli; mod cli_opts; mod fan_curve_cli; +mod scsi_cli; mod slash_cli; fn main() { @@ -180,6 +184,7 @@ fn do_parsed( Some(CliCommand::Graphics(_)) => do_gfx(), Some(CliCommand::Anime(cmd)) => handle_anime(cmd)?, Some(CliCommand::Slash(cmd)) => handle_slash(cmd)?, + Some(CliCommand::Scsi(cmd)) => handle_scsi(cmd)?, Some(CliCommand::Platform(cmd)) => { handle_platform_properties(&conn, supported_properties, cmd)? } @@ -579,6 +584,79 @@ fn handle_slash(cmd: &SlashCommand) -> Result<(), Box> { Ok(()) } +fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box> { + if (!cmd.list && cmd.enable.is_none() && cmd.mode.is_none() && cmd.colours.is_empty()) + || cmd.help + { + println!("Missing arg or command\n\n{}", cmd.self_usage()); + if let Some(lst) = cmd.self_command_list() { + println!("\n{}", lst); + } + } + + let scsis = find_iface::("org.asuslinux.ScsiAura")?; + + for scsi in scsis { + if let Some(enable) = cmd.enable { + scsi.set_enabled(enable)?; + } + + if let Some(mode) = cmd.mode { + dbg!(mode as u8); + scsi.set_led_mode(mode).unwrap(); + } + + let mut mode = scsi.led_mode_data()?; + let mut do_update = false; + if !cmd.colours.is_empty() { + let mut count = 0; + for c in &cmd.colours { + if count == 0 { + mode.colour1 = *c; + } + if count == 1 { + mode.colour2 = *c; + } + if count == 2 { + mode.colour3 = *c; + } + if count == 3 { + mode.colour4 = *c; + } + count += 1; + } + do_update = true; + } + + if let Some(speed) = cmd.speed { + mode.speed = speed; + do_update = true; + } + + if let Some(dir) = cmd.direction { + mode.direction = dir; + do_update = true; + } + + if do_update { + scsi.set_led_mode_data(mode.clone())?; + } + + // let mode_ret = scsi.led_mode_data()?; + // assert_eq!(mode, mode_ret); + println!("{mode}"); + } + + if cmd.list { + let res = AuraMode::list(); + for p in &res { + println!("{:?}", p); + } + } + + Ok(()) +} + fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box> { if mode.command.is_none() && !mode.prev_mode && !mode.next_mode { if !mode.help { diff --git a/asusctl/src/scsi_cli.rs b/asusctl/src/scsi_cli.rs new file mode 100644 index 00000000..ef87d3c1 --- /dev/null +++ b/asusctl/src/scsi_cli.rs @@ -0,0 +1,35 @@ +use gumdrop::Options; +use rog_scsi::{AuraMode, Colour, Direction, Speed}; + +#[derive(Options)] +pub struct ScsiCommand { + #[options(help = "print help message")] + pub help: bool, + + #[options(help = "Enable the SCSI drive LEDs")] + pub enable: Option, + + #[options(meta = "", help = "Set LED mode (so 'list' for all options)")] + pub mode: Option, + + #[options( + meta = "", + help = "Set LED mode speed (does not apply to all)" + )] + pub speed: Option, + + #[options( + meta = "", + help = "Set LED mode direction (does not apply to all)" + )] + pub direction: Option, + + #[options( + meta = "", + help = "Set LED colours , specify up to 4 with repeated arg" + )] + pub colours: Vec, + + #[options(help = "list available animations")] + pub list: bool, +} diff --git a/asusd/Cargo.toml b/asusd/Cargo.toml index 2153dd91..5f32e9dd 100644 --- a/asusd/Cargo.toml +++ b/asusd/Cargo.toml @@ -18,6 +18,7 @@ config-traits = { path = "../config-traits" } rog_anime = { path = "../rog-anime", features = ["dbus"] } rog_slash = { path = "../rog-slash", features = ["dbus"] } rog_aura = { path = "../rog-aura", features = ["dbus"] } +rog_scsi = { path = "../rog-scsi", features = ["dbus"] } rog_platform = { path = "../rog-platform" } rog_profiles = { path = "../rog-profiles" } dmi_id = { path = "../dmi-id" } diff --git a/asusd/src/aura_manager.rs b/asusd/src/aura_manager.rs index d355823a..be09bf8e 100644 --- a/asusd/src/aura_manager.rs +++ b/asusd/src/aura_manager.rs @@ -18,6 +18,7 @@ use zbus::Connection; use crate::aura_anime::trait_impls::AniMeZbus; use crate::aura_laptop::trait_impls::AuraZbus; +use crate::aura_scsi::trait_impls::ScsiZbus; use crate::aura_slash::trait_impls::SlashZbus; use crate::aura_types::DeviceHandle; use crate::error::RogError; @@ -70,6 +71,17 @@ fn dbus_path_for_anime() -> OwnedObjectPath { ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/anime")).into() } +fn dbus_path_for_scsi(prod_id: &str) -> OwnedObjectPath { + ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/{prod_id}_scsi")).into() +} + +fn dev_prop_matches(dev: &Device, prop: &str, value: &str) -> bool { + if let Some(p) = dev.property_value(prop) { + return p == value; + } + false +} + // TODO: // - make this the HID manager (and universal) // - *really* need to make most of this actual kernel drivers @@ -83,7 +95,6 @@ fn dbus_path_for_anime() -> OwnedObjectPath { /// /// 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, @@ -197,7 +208,75 @@ impl DeviceManager { { devices.append(&mut Self::init_hid_devices(connection, device).await?); } - // debug!("Found devices: {devices:?}"); + + Ok(devices) + } + + async fn init_scsi( + connection: &Connection, + device: &Device, + path: OwnedObjectPath, + ) -> Option { + // "ID_MODEL_ID" "1932" + // "ID_VENDOR_ID" "0b05" + if dev_prop_matches(&device, "ID_VENDOR_ID", "0b05") { + if let Some(dev_node) = device.devnode() { + let prod_id = device + .property_value("ID_MODEL_ID") + .unwrap_or_default() + .to_string_lossy(); + if let Ok(dev_type) = + DeviceHandle::maybe_scsi(dev_node.as_os_str().to_str().unwrap(), &prod_id).await + { + if let DeviceHandle::Scsi(scsi) = dev_type.clone() { + let ctrl = ScsiZbus::new(scsi); + ctrl.start_tasks(connection, path.clone()).await.unwrap(); + return Some(AsusDevice { + device: dev_type, + dbus_path: path, + }); + } + } + } + } + None + } + + async fn init_all_scsi(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("block").map_err(|err| { + warn!("{}", err); + PlatformError::Udev("match_subsystem failed".into(), err) + })?; + + let mut found = Vec::new(); + for device in enumerator + .scan_devices() + .map_err(|e| PlatformError::IoPath("enumerator".to_owned(), e))? + { + if let Some(serial) = device.property_value("ID_SERIAL_SHORT") { + let serial = serial.to_string_lossy().to_string(); + let path = dbus_path_for_scsi(&serial); + if found.contains(&path) { + continue; + } + + if let Some(dev) = Self::init_scsi(connection, &device, path.clone()).await { + devices.push(dev); + found.push(path); + } + } else { + warn!("No serial for SCSI device"); + } + } Ok(devices) } @@ -252,6 +331,11 @@ impl DeviceManager { info!("Tested device was not AniMe Matrix"); } } + + if let Ok(devs) = &mut Self::init_all_scsi(connection).await { + devices.append(devs); + } + devices } @@ -268,7 +352,7 @@ impl DeviceManager { // 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 monitor = MonitorBuilder::new()?.listen()?; let mut poll = Poll::new()?; let mut events = Events::with_capacity(1024); poll.registry() @@ -281,82 +365,142 @@ impl DeviceManager { continue; } for event in monitor.iter() { - let action = event.action().unwrap_or_default(); + let action = event + .action() + .unwrap_or_default() + .to_string_lossy() + .to_string(); - if let Some(parent) = - event.parent_with_subsystem_devtype("usb", "usb_device")? - { - let devices = devices.clone(); + let subsys = if let Some(subsys) = event.subsystem() { + subsys.to_string_lossy().to_string() + } else { + continue; + }; - 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 + let devices = devices.clone(); + let conn_copy = conn_copy.clone(); + block_on(async move { + // SCSCI devs + if subsys == "block" { + if action == "remove" { + if let Some(serial) = + event.device().property_value("ID_SERIAL_SHORT") + { + let serial = serial.to_string_lossy().to_string(); + let path = dbus_path_for_scsi(&serial); + + let index = if let Some(index) = devices .lock() .await .iter() - .enumerate() - .filter_map(|(i, dev)| { - if dev.dbus_path == path { - Some(i) - } else { - None - } - }) - .collect(); - if removals.is_empty() { + .position(|dev| dev.dbus_path == path) + { + index + } else { + warn!("No device for dbus path: {path:?}"); 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}"); + let dev = devices.lock().await.remove(index); + let path = path.clone(); + match dev.device { + DeviceHandle::Scsi(_) => { + conn_copy + .object_server() + .remove::(&path) + .await?; + } + _ => {} } - 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); } - }); - }; - } + } else if action == "add" { + let evdev = event.device(); + if let Some(serial) = evdev.property_value("ID_SERIAL_SHORT") { + let serial = serial.to_string_lossy().to_string(); + let path = dbus_path_for_scsi(&serial); + if let Some(new_devs) = + Self::init_scsi(&conn_copy, &evdev, path).await + { + devices.lock().await.append(&mut vec![new_devs]); + } + } + }; + } + + if subsys == "hidraw" { + if let Some(parent) = + event.parent_with_subsystem_devtype("usb", "usb_device")? + { + if action == "remove" { + if let Some(path) = dbus_path_for_dev(&parent) { + // 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::Scsi(_) => { + conn_copy + .object_server() + .remove::(&path) + .await? + } + _ => todo!(), + }; + info!("AuraManager removed: {path:?}, {res}"); + } + } + } else if action == "add" { + let evdev = event.device(); + 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); + } + }; + } + } + Ok::<(), RogError>(()) + }) + .map_err(|e| error!("{e:?}")) + .ok(); } } // Required for return type on spawn diff --git a/asusd/src/aura_scsi/config.rs b/asusd/src/aura_scsi/config.rs new file mode 100644 index 00000000..5cf77572 --- /dev/null +++ b/asusd/src/aura_scsi/config.rs @@ -0,0 +1,114 @@ +use std::collections::BTreeMap; + +use config_traits::{StdConfig, StdConfigLoad}; +use rog_aura::AuraDeviceType; +use rog_scsi::{AuraEffect, AuraMode}; +use serde::{Deserialize, Serialize}; + +const CONFIG_FILE: &str = "scsi.ron"; + +/// Config for base system actions for the anime display +#[derive(Deserialize, Serialize, Debug)] +pub struct ScsiConfig { + #[serde(skip)] + pub dev_type: AuraDeviceType, + pub enabled: bool, + pub current_mode: AuraMode, + pub modes: BTreeMap, +} + +impl ScsiConfig { + pub fn get_effect(&mut self, mode: AuraMode) -> Option<&AuraEffect> { + self.modes.get(&mode) + } + + pub fn save_effect(&mut self, effect: AuraEffect) { + self.current_mode = effect.mode; + self.modes.insert(*effect.mode(), effect); + } +} + +impl Default for ScsiConfig { + fn default() -> Self { + ScsiConfig { + enabled: true, + current_mode: AuraMode::Static, + dev_type: AuraDeviceType::ScsiExtDisk, + modes: BTreeMap::from([ + (AuraMode::Off, AuraEffect::default_with_mode(AuraMode::Off)), + ( + AuraMode::Static, + AuraEffect::default_with_mode(AuraMode::Static), + ), + ( + AuraMode::Breathe, + AuraEffect::default_with_mode(AuraMode::Breathe), + ), + ( + AuraMode::Flashing, + AuraEffect::default_with_mode(AuraMode::Flashing), + ), + ( + AuraMode::RainbowCycle, + AuraEffect::default_with_mode(AuraMode::RainbowCycle), + ), + ( + AuraMode::RainbowWave, + AuraEffect::default_with_mode(AuraMode::RainbowWave), + ), + ( + AuraMode::RainbowCycleBreathe, + AuraEffect::default_with_mode(AuraMode::RainbowCycleBreathe), + ), + ( + AuraMode::ChaseFade, + AuraEffect::default_with_mode(AuraMode::ChaseFade), + ), + ( + AuraMode::RainbowCycleChaseFade, + AuraEffect::default_with_mode(AuraMode::RainbowCycleChaseFade), + ), + ( + AuraMode::Chase, + AuraEffect::default_with_mode(AuraMode::Chase), + ), + ( + AuraMode::RainbowCycleChase, + AuraEffect::default_with_mode(AuraMode::RainbowCycleChase), + ), + ( + AuraMode::RainbowCycleWave, + AuraEffect::default_with_mode(AuraMode::RainbowCycleWave), + ), + ( + AuraMode::RainbowPulseChase, + AuraEffect::default_with_mode(AuraMode::RainbowPulseChase), + ), + ( + AuraMode::RandomFlicker, + AuraEffect::default_with_mode(AuraMode::RandomFlicker), + ), + ( + AuraMode::DoubleFade, + AuraEffect::default_with_mode(AuraMode::DoubleFade), + ), + ]), + } + } +} + +impl StdConfig for ScsiConfig { + fn new() -> Self { + Self::default() + } + + fn file_name(&self) -> String { + CONFIG_FILE.to_owned() + } + + fn config_dir() -> std::path::PathBuf { + std::path::PathBuf::from(crate::CONFIG_PATH_BASE) + } +} + +impl StdConfigLoad for ScsiConfig {} diff --git a/asusd/src/aura_scsi/mod.rs b/asusd/src/aura_scsi/mod.rs new file mode 100644 index 00000000..486d8cfa --- /dev/null +++ b/asusd/src/aura_scsi/mod.rs @@ -0,0 +1,45 @@ +use std::sync::Arc; + +use config::ScsiConfig; +use rog_scsi::{AuraEffect, Device, Task}; +use tokio::sync::{Mutex, MutexGuard}; + +use crate::error::RogError; + +pub mod config; +pub mod trait_impls; + +#[derive(Clone)] +pub struct ScsiAura { + device: Arc>, + config: Arc>, +} + +impl ScsiAura { + pub fn new(device: Arc>, config: Arc>) -> Self { + Self { device, config } + } + + pub async fn lock_config(&self) -> MutexGuard { + self.config.lock().await + } + + pub async fn write_effect(&self, effect: &AuraEffect) -> Result<(), RogError> { + let tasks: Vec = effect.into(); + for task in &tasks { + self.device.lock().await.perform(task).ok(); + } + Ok(()) + } + + /// Initialise the device if required. Locks the internal config so be wary + /// of deadlocks. + pub async fn do_initialization(&self) -> Result<(), RogError> { + let config = self.config.lock().await; + let mode = config.current_mode; + if let Some(effect) = config.modes.get(&mode) { + self.write_effect(effect).await?; + } + Ok(()) + } +} diff --git a/asusd/src/aura_scsi/trait_impls.rs b/asusd/src/aura_scsi/trait_impls.rs new file mode 100644 index 00000000..c3d20515 --- /dev/null +++ b/asusd/src/aura_scsi/trait_impls.rs @@ -0,0 +1,116 @@ +use std::collections::BTreeMap; + +use config_traits::StdConfig; +use log::error; +use rog_aura::AuraDeviceType; +use rog_scsi::{AuraEffect, AuraMode}; +use zbus::fdo::Error as ZbErr; +use zbus::zvariant::OwnedObjectPath; +use zbus::{interface, Connection}; + +use super::ScsiAura; +use crate::error::RogError; + +#[derive(Clone)] +pub struct ScsiZbus(ScsiAura); + +impl ScsiZbus { + pub fn new(scsi: ScsiAura) -> Self { + Self(scsi) + } + + pub async fn start_tasks( + self, + connection: &Connection, + path: OwnedObjectPath, + ) -> Result<(), RogError> { + 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.ScsiAura")] +impl ScsiZbus { + /// Return the device type for this Aura keyboard + #[zbus(property)] + async fn device_type(&self) -> AuraDeviceType { + self.0.config.lock().await.dev_type + } + + /// Get enabled or not + #[zbus(property)] + async fn enabled(&self) -> bool { + let lock = self.0.lock_config().await; + lock.enabled + } + + /// Set enabled true or false + #[zbus(property)] + async fn set_enabled(&self, enabled: bool) { + let mut config = self.0.lock_config().await; + config.enabled = enabled; + config.write(); + } + + #[zbus(property)] + async fn led_mode(&self) -> u8 { + let config = self.0.lock_config().await; + config.current_mode as u8 + } + + #[zbus(property)] + async fn set_led_mode(&self, mode: AuraMode) -> Result<(), zbus::Error> { + let mut config = self.0.lock_config().await; + if let Some(effect) = config.get_effect(mode) { + self.0 + .write_effect(effect) + .await + .map_err(|e| zbus::Error::Failure(format!("{e:?}")))?; + } else { + return Err(zbus::Error::Failure("Mode data does not exist".to_string())); + } + config.current_mode = mode; + 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.modes.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> { + self.0.write_effect(&effect).await?; + + let mut config = self.0.config.lock().await; + config.save_effect(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.modes.clone() + } +} diff --git a/asusd/src/aura_types.rs b/asusd/src/aura_types.rs index e222083c..c12cfc36 100644 --- a/asusd/src/aura_types.rs +++ b/asusd/src/aura_types.rs @@ -9,6 +9,7 @@ use rog_aura::AuraDeviceType; use rog_platform::hid_raw::HidRaw; use rog_platform::keyboard_led::KeyboardBacklight; use rog_platform::usb_raw::USBRaw; +use rog_scsi::{open_device, ScsiType}; use rog_slash::error::SlashError; use rog_slash::SlashType; use tokio::sync::Mutex; @@ -17,6 +18,8 @@ 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_scsi::config::ScsiConfig; +use crate::aura_scsi::ScsiAura; use crate::aura_slash::config::SlashConfig; use crate::aura_slash::Slash; use crate::error::RogError; @@ -31,12 +34,13 @@ pub enum _DeviceHandle { None, } -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum DeviceHandle { Aura(Aura), Slash(Slash), /// The AniMe devices require USBRaw as they are not HID devices AniMe(AniMe), + Scsi(ScsiAura), Ally(Arc>), OldAura(Arc>), /// TUF laptops have an aditional set of attributes added to the LED /sysfs/ @@ -146,6 +150,23 @@ impl DeviceHandle { } } + pub async fn maybe_scsi(dev_node: &str, prod_id: &str) -> Result { + debug!("Testing for SCSI"); + let prod_id = ScsiType::from(prod_id); + if prod_id == ScsiType::Unsupported { + log::info!("Unknown or invalid SCSI: {prod_id:?}, skipping"); + return Err(RogError::NotFound("No SCSI device".to_string())); + } + info!("Found SCSI device {prod_id:?} on {dev_node}"); + + let mut config = ScsiConfig::new().load(); + config.dev_type = AuraDeviceType::ScsiExtDisk; + let dev = Arc::new(Mutex::new(open_device(dev_node)?)); + let scsi = ScsiAura::new(dev, Arc::new(Mutex::new(config))); + scsi.do_initialization().await?; + Ok(Self::Scsi(scsi)) + } + pub async fn maybe_laptop_aura( device: Arc>, prod_id: &str, diff --git a/asusd/src/lib.rs b/asusd/src/lib.rs index 990aeff8..8a21eb3e 100644 --- a/asusd/src/lib.rs +++ b/asusd/src/lib.rs @@ -9,6 +9,7 @@ pub mod ctrl_platform; pub mod aura_anime; pub mod aura_laptop; pub mod aura_manager; +pub mod aura_scsi; pub mod aura_slash; pub mod aura_types; pub mod error; diff --git a/rog-aura/src/builtin_modes.rs b/rog-aura/src/builtin_modes.rs index 49353963..3e10ebaa 100644 --- a/rog-aura/src/builtin_modes.rs +++ b/rog-aura/src/builtin_modes.rs @@ -494,56 +494,6 @@ impl Display for AuraEffect { } } -pub struct AuraParameters { - pub zone: bool, - pub colour1: bool, - pub colour2: bool, - pub speed: bool, - pub direction: bool, -} - -#[allow(clippy::fn_params_excessive_bools)] -impl AuraParameters { - pub const fn new( - zone: bool, - colour1: bool, - colour2: bool, - speed: bool, - direction: bool, - ) -> Self { - Self { - zone, - colour1, - colour2, - speed, - direction, - } - } -} - -impl AuraEffect { - /// A helper to provide detail on what effects have which parameters, e.g - /// the static factory mode accepts only one colour. - pub const fn allowed_parameters(mode: AuraModeNum) -> AuraParameters { - match mode { - AuraModeNum::Static - | AuraModeNum::Highlight - | AuraModeNum::Pulse - | AuraModeNum::Comet - | AuraModeNum::Flash => AuraParameters::new(true, true, false, false, false), - AuraModeNum::Breathe => AuraParameters::new(true, true, true, true, false), - AuraModeNum::RainbowCycle | AuraModeNum::Rain => { - AuraParameters::new(true, false, false, true, false) - } - AuraModeNum::RainbowWave => AuraParameters::new(true, false, false, true, true), - AuraModeNum::Star => AuraParameters::new(true, true, true, true, true), - AuraModeNum::Laser | AuraModeNum::Ripple => { - AuraParameters::new(true, true, false, true, false) - } - } - } -} - /// Parses `AuraEffect` in to packet data for writing to the USB interface /// /// Byte structure where colour is RGB, one byte per R, G, B: diff --git a/rog-control-center/translations/en/rog-control-center.po b/rog-control-center/translations/en/rog-control-center.po index 97ae7ca3..0d41770b 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-18 23:02+0000\n" +"POT-Creation-Date: 2024-12-22 03:56+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/rog-dbus/Cargo.toml b/rog-dbus/Cargo.toml index aa56525b..b99a8291 100644 --- a/rog-dbus/Cargo.toml +++ b/rog-dbus/Cargo.toml @@ -13,6 +13,7 @@ description = "dbus interface methods for asusctl" asusd = { path = "../asusd" } rog_anime = { path = "../rog-anime", features = ["dbus"] } rog_slash = { path = "../rog-slash", features = ["dbus"] } +rog_scsi = { path = "../rog-scsi", features = ["dbus"] } rog_aura = { path = "../rog-aura" } rog_profiles = { path = "../rog-profiles" } rog_platform = { path = "../rog-platform" } diff --git a/rog-dbus/src/lib.rs b/rog-dbus/src/lib.rs index b5654560..787270aa 100644 --- a/rog-dbus/src/lib.rs +++ b/rog-dbus/src/lib.rs @@ -1,5 +1,6 @@ pub use asusd::{DBUS_IFACE, DBUS_NAME, DBUS_PATH}; +pub mod scsi_aura; pub mod zbus_anime; pub mod zbus_aura; pub mod zbus_fan_curves; diff --git a/rog-dbus/src/scsi_aura.rs b/rog-dbus/src/scsi_aura.rs new file mode 100644 index 00000000..39e8959f --- /dev/null +++ b/rog-dbus/src/scsi_aura.rs @@ -0,0 +1,56 @@ +//! # D-Bus interface proxy for: `org.asuslinux.ScsiAura` +//! +//! This code was generated by `zbus-xmlgen` `5.0.1` from D-Bus introspection +//! data. Source: `Interface '/org/asuslinux/M3D0AP048745_scsi' from service +//! 'org.asuslinux.Daemon' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the +//! zbus documentation. +//! +//! This type implements the [D-Bus standard interfaces], +//! (`org.freedesktop.DBus.*`) for which the following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use rog_scsi::{AuraEffect, AuraMode}; +use zbus::proxy; +#[proxy( + interface = "org.asuslinux.ScsiAura", + default_service = "org.asuslinux.Daemon", + default_path = "/org/asuslinux" +)] +pub trait ScsiAura { + /// AllModeData method + #[allow(clippy::type_complexity)] + fn all_mode_data(&self) -> zbus::Result>; + + /// DeviceType property + #[zbus(property)] + fn device_type(&self) -> zbus::Result; + + /// Enabled property + #[zbus(property)] + fn enabled(&self) -> zbus::Result; + #[zbus(property)] + fn set_enabled(&self, value: bool) -> zbus::Result<()>; + + /// LedMode property + #[zbus(property)] + fn led_mode(&self) -> zbus::Result; + #[zbus(property)] + fn set_led_mode(&self, mode: AuraMode) -> zbus::Result<()>; + + /// LedModeData property + #[zbus(property)] + fn led_mode_data(&self) -> zbus::Result; + #[zbus(property)] + fn set_led_mode_data(&self, effect: AuraEffect) -> zbus::Result<()>; +} diff --git a/rog-scsi/Cargo.toml b/rog-scsi/Cargo.toml new file mode 100644 index 00000000..23701c42 --- /dev/null +++ b/rog-scsi/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "rog_scsi" +version.workspace = true +rust-version.workspace = true +license.workspace = true +readme.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +description.workspace = true +edition.workspace = true + +[features] +default = ["dbus", "ron"] +dbus = ["zbus"] + +[dependencies] +sg.workspace = true +serde.workspace = true +zbus = { workspace = true, optional = true } + +# cli and logging +log.workspace = true +typeshare.workspace = true + +ron = { version = "*", optional = true } + +[dev-dependencies] +cargo-husky.workspace = true diff --git a/rog-scsi/src/builtin_modes.rs b/rog-scsi/src/builtin_modes.rs new file mode 100644 index 00000000..79012b6d --- /dev/null +++ b/rog-scsi/src/builtin_modes.rs @@ -0,0 +1,398 @@ +use std::fmt::Display; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; +#[cfg(feature = "dbus")] +use zbus::zvariant::{OwnedValue, Type, Value}; + +use crate::error::Error; +use crate::scsi::{apply_task, dir_task, mode_task, rgb_task, save_task, speed_task}; + +#[typeshare] +#[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Deserialize, Serialize)] +pub struct Colour { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl Default for Colour { + fn default() -> Self { + Colour { r: 166, g: 0, b: 0 } + } +} + +impl FromStr for Colour { + type Err = Error; + + fn from_str(s: &str) -> Result { + if s.len() < 6 { + return Err(Error::ParseColour); + } + let r = u8::from_str_radix(&s[0..2], 16).or(Err(Error::ParseColour))?; + let g = u8::from_str_radix(&s[2..4], 16).or(Err(Error::ParseColour))?; + let b = u8::from_str_radix(&s[4..6], 16).or(Err(Error::ParseColour))?; + Ok(Colour { r, g, b }) + } +} + +impl From<&[u8; 3]> for Colour { + fn from(c: &[u8; 3]) -> Self { + Self { + r: c[0], + g: c[1], + b: c[2], + } + } +} + +impl From for [u8; 3] { + fn from(c: Colour) -> Self { + [c.r, c.b, c.g] + } +} + +#[typeshare] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr( + feature = "dbus", + derive(Type, Value, OwnedValue), + zvariant(signature = "u") +)] +pub enum Direction { + #[default] + Forward = 0, + Reverse = 1, +} + +impl FromStr for Direction { + type Err = Error; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "forward" => Ok(Direction::Forward), + "reverse" => Ok(Direction::Reverse), + _ => Err(Error::ParseSpeed), + } + } +} + +impl From for Direction { + fn from(dir: u8) -> Self { + match dir { + 1 => Direction::Reverse, + _ => Direction::Forward, + } + } +} + +impl From for u8 { + fn from(d: Direction) -> Self { + d as u8 + } +} + +#[typeshare] +#[cfg_attr( + feature = "dbus", + derive(Type, Value, OwnedValue), + zvariant(signature = "s") +)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub enum Speed { + Slowest = 4, + Slow = 3, + #[default] + Med = 2, + Fast = 1, + Fastest = 0, +} + +impl FromStr for Speed { + type Err = Error; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "slowest" => Ok(Speed::Slowest), + "slow" => Ok(Speed::Slow), + "med" => Ok(Speed::Med), + "fast" => Ok(Speed::Fast), + "fastest" => Ok(Speed::Fastest), + _ => Err(Error::ParseSpeed), + } + } +} + +impl From for u8 { + fn from(s: Speed) -> u8 { + match s { + Speed::Slowest => 4, + Speed::Slow => 3, + Speed::Med => 2, + Speed::Fast => 1, + Speed::Fastest => 0, + } + } +} + +impl From for Speed { + fn from(value: u8) -> Self { + match value { + 4 => Self::Slowest, + 3 => Self::Slow, + 1 => Self::Fast, + 0 => Self::Fastest, + _ => Self::Med, + } + } +} + +/// Enum of modes that convert to the actual number required by a USB HID packet +#[typeshare] +#[cfg_attr( + feature = "dbus", + derive(Type, Value, OwnedValue), + zvariant(signature = "u") +)] +#[derive( + Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Deserialize, Serialize, +)] +pub enum AuraMode { + Off = 0, + #[default] + Static = 1, + Breathe = 2, + Flashing = 3, + RainbowCycle = 4, + RainbowWave = 5, + RainbowCycleBreathe = 6, + ChaseFade = 7, + RainbowCycleChaseFade = 8, + Chase = 9, + RainbowCycleChase = 10, + RainbowCycleWave = 11, + RainbowPulseChase = 12, + RandomFlicker = 13, + DoubleFade = 14, +} + +impl AuraMode { + pub fn list() -> [String; 15] { + [ + AuraMode::Off.to_string(), + AuraMode::Static.to_string(), + AuraMode::Breathe.to_string(), + AuraMode::Flashing.to_string(), + AuraMode::RainbowCycle.to_string(), + AuraMode::RainbowWave.to_string(), + AuraMode::RainbowCycleBreathe.to_string(), + AuraMode::ChaseFade.to_string(), + AuraMode::RainbowCycleChaseFade.to_string(), + AuraMode::Chase.to_string(), + AuraMode::RainbowCycleChase.to_string(), + AuraMode::RainbowCycleWave.to_string(), + AuraMode::RainbowPulseChase.to_string(), + AuraMode::RandomFlicker.to_string(), + AuraMode::DoubleFade.to_string(), + ] + } +} + +impl Display for AuraMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", <&str>::from(self)) + } +} + +impl From for String { + fn from(mode: AuraMode) -> Self { + <&str>::from(&mode).to_owned() + } +} + +impl From<&AuraMode> for &str { + fn from(mode: &AuraMode) -> Self { + match mode { + AuraMode::Off => "Off", + AuraMode::Static => "Static", + AuraMode::Breathe => "Breathe", + AuraMode::RainbowCycle => "RainbowCycle", + AuraMode::RainbowWave => "RainbowWave", + AuraMode::Flashing => "Flashing", + AuraMode::RainbowCycleBreathe => "RainbowCycleBreathe", + AuraMode::ChaseFade => "ChaseFade", + AuraMode::RainbowCycleChaseFade => "RainbowCycleChaseFade", + AuraMode::Chase => "Chase", + AuraMode::RainbowCycleChase => "RainbowCycleChase", + AuraMode::RainbowCycleWave => "RainbowCycleWave", + AuraMode::RainbowPulseChase => "RainbowPulseChase", + AuraMode::RandomFlicker => "RandomFlicker", + AuraMode::DoubleFade => "DoubleFade", + } + } +} + +impl FromStr for AuraMode { + type Err = Error; + + fn from_str(mode: &str) -> Result { + match mode { + "Off" => Ok(Self::Off), + "Static" => Ok(Self::Static), + "Breathe" => Ok(Self::Breathe), + "RainbowCycle" => Ok(Self::RainbowCycle), + "RainbowWave" => Ok(Self::RainbowWave), + "Flashing" => Ok(Self::Flashing), + "RainbowCycleBreathe" => Ok(Self::RainbowCycleBreathe), + "ChaseFade" => Ok(Self::ChaseFade), + "RainbowCycleChaseFade" => Ok(Self::RainbowCycleChaseFade), + "Chase" => Ok(Self::Chase), + "RainbowCycleChase" => Ok(Self::RainbowCycleChase), + "RainbowCycleWave" => Ok(Self::RainbowCycleWave), + "RainbowPulseChase" => Ok(Self::RainbowPulseChase), + "RandomFlicker" => Ok(Self::RandomFlicker), + "DoubleFade" => Ok(Self::DoubleFade), + _ => Err(Error::ParseMode), + } + } +} + +impl From<&str> for AuraMode { + fn from(mode: &str) -> Self { + AuraMode::from_str(mode).unwrap_or_default() + } +} + +impl From for AuraMode { + fn from(mode: u8) -> Self { + match mode { + 0 => Self::Off, + 1 => Self::Static, + 2 => Self::Breathe, + 3 => Self::Flashing, + 4 => Self::RainbowCycle, + 5 => Self::RainbowWave, + 6 => Self::RainbowCycleBreathe, + 7 => Self::ChaseFade, + 8 => Self::RainbowCycleChaseFade, + 9 => Self::Chase, + 10 => Self::RainbowCycleChase, + 11 => Self::RainbowCycleWave, + 12 => Self::RainbowPulseChase, + 13 => Self::RandomFlicker, + 14 => Self::DoubleFade, + _ => Self::Static, + } + } +} + +impl From for AuraMode { + fn from(value: AuraEffect) -> Self { + value.mode + } +} + +/// Default factory modes structure. +#[typeshare] +#[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct AuraEffect { + /// The effect type + pub mode: AuraMode, + /// One of three speeds for modes that support speed (most that animate) + pub speed: Speed, + /// Up, down, left, right. Only Rainbow mode seems to use this + pub direction: Direction, + /// Primary colour for all modes + pub colour1: Colour, + /// Secondary colour in some modes like Breathing or Stars + pub colour2: Colour, + pub colour3: Colour, + pub colour4: Colour, +} + +impl AuraEffect { + pub fn mode(&self) -> &AuraMode { + &self.mode + } + + pub fn mode_name(&self) -> &str { + <&str>::from(&self.mode) + } + + pub fn mode_num(&self) -> u8 { + self.mode as u8 + } + + pub fn default_with_mode(mode: AuraMode) -> Self { + Self { + mode, + ..Default::default() + } + } +} + +impl Default for AuraEffect { + fn default() -> Self { + Self { + mode: AuraMode::Static, + colour1: Colour { r: 166, g: 0, b: 0 }, + colour2: Colour { r: 0, g: 0, b: 0 }, + colour3: Colour { r: 166, g: 0, b: 0 }, + colour4: Colour { r: 0, g: 0, b: 0 }, + speed: Speed::Med, + direction: Direction::Forward, + } + } +} + +impl Display for AuraEffect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "AuraEffect {{")?; + writeln!(f, " mode: {}", self.mode())?; + writeln!(f, " speed: {:?}", self.speed)?; + writeln!(f, " direction: {:?}", self.direction)?; + writeln!(f, " colour1: {:?}", self.colour1)?; + writeln!(f, " colour2: {:?}", self.colour2)?; + writeln!(f, " colour3: {:?}", self.colour3)?; + writeln!(f, " colour4: {:?}", self.colour4)?; + writeln!(f, "}}") + } +} + +impl From<&AuraEffect> for Vec { + fn from(effect: &AuraEffect) -> Self { + let mut tasks = Vec::new(); + + tasks.append(&mut vec![ + mode_task(effect.mode as u8), + rgb_task(0, &effect.colour1.into()), + rgb_task(1, &effect.colour2.into()), + rgb_task(2, &effect.colour3.into()), + rgb_task(3, &effect.colour4.into()), + ]); + + if !matches!(effect.mode, AuraMode::Static | AuraMode::Off) { + tasks.push(speed_task(effect.speed as u8)); + } + if matches!( + effect.mode, + AuraMode::RainbowWave + | AuraMode::ChaseFade + | AuraMode::RainbowCycleChaseFade + | AuraMode::Chase + | AuraMode::RainbowCycleChase + | AuraMode::RainbowCycleWave + | AuraMode::RainbowPulseChase + ) { + tasks.push(dir_task(effect.direction as u8)); + } + + tasks.append(&mut vec![apply_task(), save_task()]); + tasks + } +} diff --git a/rog-scsi/src/error.rs b/rog-scsi/src/error.rs new file mode 100644 index 00000000..c80cc889 --- /dev/null +++ b/rog-scsi/src/error.rs @@ -0,0 +1,41 @@ +use std::{error, fmt}; + +#[derive(Debug)] +pub enum Error { + ParseMode, + ParseColour, + ParseSpeed, + ParseDirection, + IoPath(String, std::io::Error), + Ron(ron::Error), + RonParse(ron::error::SpannedError), +} + +impl fmt::Display for Error { + // This trait requires `fmt` with this exact signature. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::ParseColour => write!(f, "Could not parse colour"), + Error::ParseSpeed => write!(f, "Could not parse speed"), + Error::ParseDirection => write!(f, "Could not parse direction"), + Error::ParseMode => write!(f, "Could not parse mode"), + Error::IoPath(path, io) => write!(f, "IO Error: {path}, {io}"), + Error::Ron(e) => write!(f, "RON Parse Error: {e}"), + Error::RonParse(e) => write!(f, "RON Parse Error: {e}"), + } + } +} + +impl error::Error for Error {} + +impl From for Error { + fn from(e: ron::Error) -> Self { + Self::Ron(e) + } +} + +impl From for Error { + fn from(e: ron::error::SpannedError) -> Self { + Self::RonParse(e) + } +} diff --git a/rog-scsi/src/lib.rs b/rog-scsi/src/lib.rs new file mode 100644 index 00000000..85765a33 --- /dev/null +++ b/rog-scsi/src/lib.rs @@ -0,0 +1,48 @@ +mod builtin_modes; +mod error; +mod scsi; + +pub use builtin_modes::*; +pub use error::*; +use serde::{Deserialize, Serialize}; +pub use sg::{Device, Task}; + +pub const PROD_SCSI_ARION: &str = "1932"; + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub enum ScsiType { + Arion, + #[default] + Unsupported, +} + +impl ScsiType { + pub const fn prod_id_str(&self) -> &str { + match self { + ScsiType::Arion => PROD_SCSI_ARION, + ScsiType::Unsupported => "", + } + } +} + +impl From<&str> for ScsiType { + fn from(s: &str) -> Self { + match s.to_lowercase().as_str() { + PROD_SCSI_ARION | "0x1932" => Self::Arion, + _ => Self::Unsupported, + } + } +} + +impl From for &str { + fn from(s: ScsiType) -> Self { + match s { + ScsiType::Arion => PROD_SCSI_ARION, + ScsiType::Unsupported => "Unsupported", + } + } +} + +pub fn open_device(path: &str) -> Result { + Device::open(path) +} diff --git a/rog-scsi/src/scsi.rs b/rog-scsi/src/scsi.rs new file mode 100644 index 00000000..61fa8062 --- /dev/null +++ b/rog-scsi/src/scsi.rs @@ -0,0 +1,80 @@ +extern crate sg; + +pub use sg::Task; + +static ENE_APPLY_VAL: u8 = 0x01; // Value for Apply Changes Register +static ENE_SAVE_VAL: u8 = 0xaa; + +static ENE_REG_MODE: u32 = 0x8021; // Mode Selection Register +static ENE_REG_SPEED: u32 = 0x8022; // Speed Control Register +static ENE_REG_DIRECTION: u32 = 0x8023; // Direction Control Register + +static ENE_REG_APPLY: u32 = 0x80a0; +static _ENE_REG_COLORS_DIRECT_V2: u32 = 0x8100; // to read the colurs +static ENE_REG_COLORS_EFFECT_V2: u32 = 0x8160; + +fn data(reg: u32, arg_count: u8) -> [u8; 16] { + let mut cdb = [0u8; 16]; + cdb[0] = 0xec; + cdb[1] = 0x41; + cdb[2] = 0x53; + cdb[3] = ((reg >> 8) & 0x00ff) as u8; + cdb[4] = (reg & 0x00ff) as u8; + cdb[5] = 0x00; + cdb[6] = 0x00; + cdb[7] = 0x00; + cdb[8] = 0x00; + cdb[9] = 0x00; + cdb[10] = 0x00; + cdb[11] = 0x00; + cdb[12] = 0x00; + cdb[13] = arg_count; // how many u8 in data packet + cdb[14] = 0x00; + cdb[15] = 0x00; + cdb +} + +pub(crate) fn rgb_task(led: u32, rgb: &[u8; 3]) -> Task { + let mut task = Task::new(); + task.set_cdb(data(led * 3 + ENE_REG_COLORS_EFFECT_V2, 3).as_slice()); + task.set_data(rgb, sg::Direction::ToDevice); + task +} + +/// 0-13 +pub(crate) fn mode_task(mode: u8) -> Task { + let mut task = Task::new(); + task.set_cdb(data(ENE_REG_MODE, 1).as_slice()); + task.set_data(&[mode.min(13)], sg::Direction::ToDevice); + task +} + +/// 0-4, fast to slow +pub(crate) fn speed_task(speed: u8) -> Task { + let mut task = Task::new(); + task.set_cdb(data(ENE_REG_SPEED, 1).as_slice()); + task.set_data(&[speed.min(4)], sg::Direction::ToDevice); + task +} + +/// 0 = forward, 1 = backward +pub(crate) fn dir_task(mode: u8) -> Task { + let mut task = Task::new(); + task.set_cdb(data(ENE_REG_DIRECTION, 1).as_slice()); + task.set_data(&[mode.min(1)], sg::Direction::ToDevice); + task +} + +pub(crate) fn apply_task() -> Task { + let mut task = Task::new(); + task.set_cdb(data(ENE_REG_APPLY, 1).as_slice()); + task.set_data(&[ENE_APPLY_VAL], sg::Direction::ToDevice); + task +} + +pub(crate) fn save_task() -> Task { + let mut task = Task::new(); + task.set_cdb(data(ENE_REG_APPLY, 1).as_slice()); + task.set_data(&[ENE_SAVE_VAL], sg::Direction::ToDevice); + task +}