SCSI support: ROG Arion external drive LED control

This commit is contained in:
Luke D. Jones
2024-12-21 20:35:51 +13:00
parent 19ffcf3376
commit 0f2d89858e
24 changed files with 1393 additions and 172 deletions

View File

@@ -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]

149
Cargo.lock generated
View File

@@ -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",

View File

@@ -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

View File

@@ -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" }

View File

@@ -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),
}

View File

@@ -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<dyn std::error::Error>> {
Ok(())
}
fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box<dyn std::error::Error>> {
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::<ScsiAuraProxyBlocking>("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<dyn std::error::Error>> {
if mode.command.is_none() && !mode.prev_mode && !mode.next_mode {
if !mode.help {

35
asusctl/src/scsi_cli.rs Normal file
View File

@@ -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<bool>,
#[options(meta = "", help = "Set LED mode (so 'list' for all options)")]
pub mode: Option<AuraMode>,
#[options(
meta = "",
help = "Set LED mode speed <slowest, slow, med, fast, fastest> (does not apply to all)"
)]
pub speed: Option<Speed>,
#[options(
meta = "",
help = "Set LED mode direction <forward, reverse> (does not apply to all)"
)]
pub direction: Option<Direction>,
#[options(
meta = "",
help = "Set LED colours <hex>, specify up to 4 with repeated arg"
)]
pub colours: Vec<Colour>,
#[options(help = "list available animations")]
pub list: bool,
}

View File

@@ -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" }

View File

@@ -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<AsusDevice> {
// "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<Vec<AsusDevice>, RogError> {
// track and ensure we use only one hidraw per prod_id
// let mut interfaces = HashSet::new();
let mut devices: Vec<AsusDevice> = 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,17 +365,74 @@ 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();
let subsys = if let Some(subsys) = event.subsystem() {
subsys.to_string_lossy().to_string()
} else {
continue;
};
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()
.position(|dev| dev.dbus_path == path)
{
index
} else {
warn!("No device for dbus path: {path:?}");
return Ok(());
};
info!("removing: {path:?}");
let dev = devices.lock().await.remove(index);
let path = path.clone();
match dev.device {
DeviceHandle::Scsi(_) => {
conn_copy
.object_server()
.remove::<ScsiZbus, _>(&path)
.await?;
}
_ => {}
}
}
} 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")?
{
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<usize> = devices
.lock()
@@ -333,31 +474,34 @@ impl DeviceManager {
.remove::<AniMeZbus, _>(&path)
.await?
}
DeviceHandle::Ally(_) => todo!(),
DeviceHandle::OldAura(_) => todo!(),
DeviceHandle::TufLedClass(_) => todo!(),
DeviceHandle::MulticolourLed => todo!(),
DeviceHandle::None => todo!(),
DeviceHandle::Scsi(_) => {
conn_copy
.object_server()
.remove::<ScsiZbus, _>(&path)
.await?
}
_ => 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)
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
#[allow(unreachable_code)]

View File

@@ -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<AuraMode, AuraEffect>,
}
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 {}

View File

@@ -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<Mutex<Device>>,
config: Arc<Mutex<ScsiConfig>>,
}
impl ScsiAura {
pub fn new(device: Arc<Mutex<Device>>, config: Arc<Mutex<ScsiConfig>>) -> Self {
Self { device, config }
}
pub async fn lock_config(&self) -> MutexGuard<ScsiConfig> {
self.config.lock().await
}
pub async fn write_effect(&self, effect: &AuraEffect) -> Result<(), RogError> {
let tasks: Vec<Task> = 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(())
}
}

View File

@@ -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<AuraEffect, ZbErr> {
// 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<AuraMode, AuraEffect> {
let config = self.0.config.lock().await;
config.modes.clone()
}
}

View File

@@ -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<Mutex<HidRaw>>),
OldAura(Arc<Mutex<HidRaw>>),
/// 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<Self, RogError> {
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<Mutex<HidRaw>>,
prod_id: &str,

View File

@@ -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;

View File

@@ -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:

View File

@@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View File

@@ -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" }

View File

@@ -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;

56
rog-dbus/src/scsi_aura.rs Normal file
View File

@@ -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<std::collections::HashMap<AuraMode, AuraEffect>>;
/// DeviceType property
#[zbus(property)]
fn device_type(&self) -> zbus::Result<u32>;
/// Enabled property
#[zbus(property)]
fn enabled(&self) -> zbus::Result<bool>;
#[zbus(property)]
fn set_enabled(&self, value: bool) -> zbus::Result<()>;
/// LedMode property
#[zbus(property)]
fn led_mode(&self) -> zbus::Result<u8>;
#[zbus(property)]
fn set_led_mode(&self, mode: AuraMode) -> zbus::Result<()>;
/// LedModeData property
#[zbus(property)]
fn led_mode_data(&self) -> zbus::Result<AuraEffect>;
#[zbus(property)]
fn set_led_mode_data(&self, effect: AuraEffect) -> zbus::Result<()>;
}

29
rog-scsi/Cargo.toml Normal file
View File

@@ -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

View File

@@ -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<Self, Self::Err> {
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<Colour> 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<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"forward" => Ok(Direction::Forward),
"reverse" => Ok(Direction::Reverse),
_ => Err(Error::ParseSpeed),
}
}
}
impl From<u8> for Direction {
fn from(dir: u8) -> Self {
match dir {
1 => Direction::Reverse,
_ => Direction::Forward,
}
}
}
impl From<Direction> 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<Self, Self::Err> {
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<Speed> 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<u8> 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<AuraMode> 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<Self, Self::Err> {
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<u8> 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<AuraEffect> 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<sg::Task> {
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
}
}

41
rog-scsi/src/error.rs Normal file
View File

@@ -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<ron::Error> for Error {
fn from(e: ron::Error) -> Self {
Self::Ron(e)
}
}
impl From<ron::error::SpannedError> for Error {
fn from(e: ron::error::SpannedError) -> Self {
Self::RonParse(e)
}
}

48
rog-scsi/src/lib.rs Normal file
View File

@@ -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<ScsiType> 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, std::io::Error> {
Device::open(path)
}

80
rog-scsi/src/scsi.rs Normal file
View File

@@ -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
}