Compare commits

..

35 Commits

Author SHA1 Message Date
Luke D. Jones a7b98c67ee Prep 6.1.0-rc1 2024-12-25 19:28:32 +13:00
Luke D. Jones e7bbd99178 Stop GUI thread hogging CPU 2024-12-25 11:29:10 +13:00
Luke D. Jones e2d3f99d91 Try better to capture TUF keyboard control 2024-12-25 10:31:43 +13:00
Luke D. Jones 1cad5afc0d FirmwareAttributes: further work 2024-12-24 21:50:32 +13:00
Luke D. Jones fd31ac458d Prepare for rc1 2024-12-24 18:01:19 +13:00
Luke D. Jones 6292b3361d Re-add TUF LED support 2024-12-24 17:13:05 +13:00
Luke D. Jones ab7a4bbad3 Refactor ROGCC to use dbus to communicate with self instead of pipe file 2024-12-24 17:13:05 +13:00
Luke D. Jones 0f2d89858e SCSI support: ROG Arion external drive LED control 2024-12-24 17:13:05 +13:00
Luke D. Jones 19ffcf3376 Refactor: Make all Aura type devices use "device_manager"
Open the door to adding many other types of "aura" devices later.
2024-12-24 17:13:05 +13:00
Luke D. Jones 0ddfe76c31 Minor prep 2024-12-18 11:21:40 +13:00
Luke D. Jones c05c8ba648 Fix: prevent an aura manager deadlock 2024-12-18 10:33:14 +13:00
Luke D. Jones ccdc576319 Switch tray to knsi crate. Many things to do 2024-12-17 22:14:13 +13:00
Luke Jones 39b16ffc91 Merge branch 'one-shot-charge' into 'main'
feat: One-shot full charge charge

See merge request asus-linux/asusctl!207
2024-12-14 22:07:47 +00:00
AlbertGG 72ff1ab3ab feat: One-shot full charge charge 2024-12-14 22:07:46 +00:00
Luke D. Jones e7c4619ee9 Update deps 2024-11-28 16:32:42 +13:00
Luke Jones 71fcb382ea Merge branch 'G733ZW_Aura' into 'main'
G733Z(W) Aura Update

See merge request asus-linux/asusctl!208
2024-11-21 02:46:57 +00:00
Michael Campbell e5b2e3ef11 G733Z(W) Aura Update 2024-11-21 01:25:15 +00:00
Luke Jones dfd39522a8 Merge branch 'feature/ga605w-aura' into 'main'
feat(rog-aura): add GA605W aura support

See merge request asus-linux/asusctl!206
2024-11-13 20:13:43 +00:00
Tobias Kantusch 2759c28196 feat(rog-aura): add GA605W aura support
This adds support for aura effects on GA605W laptops.
The model is basically the same as the GU605, just with
an AMD chip, so the configuration is simply copied.
2024-11-09 23:07:59 +01:00
Luke Jones 207e199016 Merge branch 'FA506N-rainbow' into 'main'
Fa506 n rainbow

See merge request asus-linux/asusctl!203
2024-11-04 08:15:30 +00:00
Luke Jones 5f8cbdb94b Merge branch 'disable-kms' into 'main'
rog-control-center: Disable slint KMS backend

See merge request asus-linux/asusctl!204
2024-11-04 07:54:11 +00:00
Luke Jones 0853c16d5e Merge branch 'GLLM1-main-patch-64861' into 'main'
Update asusd.rules for ProArt laptops

See merge request asus-linux/asusctl!205
2024-11-04 07:51:53 +00:00
GLLM 0be04726ca Update asusd.rules for ProArt laptops (tested on P16, aka H7606W) 2024-11-03 20:14:06 +00:00
Kenny Levinsen a672a86cc4 rog-control-center: Disable slint KMS backend
The slint KMS backend is meant to run an application without a display
server and with direct, exclusive display and input control, which is a
very specific use-case for e.g. embedded applications.

Disable the KMS backend and avoid a bunch of dependencies.
2024-11-02 20:54:24 +01:00
Lu Robles c7ff3b44dc FA506N: use RainbowCycle instead of RainbowWave 2024-10-26 16:42:18 +02:00
Luke Jones f0ebda9ecd Merge branch 'fa506n' into 'main'
rog-aura: fa506n: use the correct rainbow mode

See merge request asus-linux/asusctl!202
2024-10-23 14:08:45 +00:00
Sid Pranjale b984176cd0 rog-aura: fa506n: use the correct rainbow mode 2024-10-23 17:40:29 +05:30
Luke Jones 599b1cc9ad Merge branch 'fa506n' into 'main'
rog-aura: add modes for FA506N

See merge request asus-linux/asusctl!201
2024-10-23 10:37:42 +00:00
Sid Pranjale d93df8752e rog-aura: add modes for FA506N 2024-10-23 15:23:13 +05:30
Luke Jones 8a9564bbfa Merge branch 'main' into 'main'
add install instructions for debian(light fan can work for my G614JV)

See merge request asus-linux/asusctl!182
2024-09-08 08:07:21 +00:00
Pd.ch 5455e345b2 add install instructions for debian(light fan can work for my G614JV) 2024-09-08 08:07:21 +00:00
Luke D. Jones 520101fea1 rog-platform: Adjust current_value again, read and write directly to file path 2024-09-07 17:43:31 +12:00
Luke D. Jones e866b4eeb1 rog-platform: change current_value() to &mut 2024-09-07 17:05:33 +12:00
Luke D. Jones c5c46738ee Fix ally power config 2024-09-03 12:45:07 +12:00
Luke D. Jones 27ed95bd3e Fixes to config loading and updating 2024-08-31 11:16:51 +12:00
96 changed files with 6260 additions and 4080 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ image: rust:latest
- target/release/.cargo-lock
before_script:
- apt-get update -qq && apt-get install -y -qq libinput-dev libseat-dev libudev-dev libgtk-3-dev grep llvm clang libclang-dev libsdl2-dev libsdl2-gfx-dev
- apt-get update -qq && apt-get install -y -qq libudev-dev libgtk-3-dev grep llvm clang libclang-dev libsdl2-dev libsdl2-gfx-dev
stages:
- format
+12
View File
@@ -2,6 +2,18 @@
## [Unreleased]
## [v6.1.0-rc1]
### Added
- ROG Arion external driver LED support
- Add GA605W LED layout
- Add GA605 + GU605 Slash support
### 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.
- Rename CLI args for aura related properties. This will likely change further as more devices are added
## [v6.0.12]
### Changed
Generated
+1703 -1082
View File
File diff suppressed because it is too large Load Diff
+9 -6
View File
@@ -1,6 +1,6 @@
[workspace.package]
version = "6.0.12"
rust-version = "1.77"
version = "6.1.0-rc1"
rust-version = "1.82"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]
@@ -25,7 +25,7 @@ members = [
"rog-profiles",
"rog-control-center",
"rog-slash",
"simulators",
"simulators", "rog-scsi",
]
default-members = [
"asusctl",
@@ -40,15 +40,16 @@ tokio = { version = "^1.39.0", default-features = false, features = [
"macros",
"sync",
"time",
"rt",
"rt-multi-thread",
] }
concat-idents = "^1.1"
dirs = "^4.0"
smol = "^1.3"
smol = "^2.0"
mio = "0.8.11"
zbus = "4.4"
logind-zbus = { version = "4.0.3" } #, default-features = false, features = ["non_blocking"] }
zbus = "5.1"
logind-zbus = { version = "5.0.0" } #, default-features = false, features = ["non_blocking"] }
serde = { version = "^1.0", features = ["serde_derive"] }
ron = "*"
@@ -72,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
+9 -4
View File
@@ -19,17 +19,22 @@ LEDCFG := aura_support.ron
SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
STRIP_BINARIES ?= 0
STRIP_BINARIES ?= 1
DEBUG ?= 0
ifeq ($(DEBUG),0)
ARGS += --release --features "rog-control-center/x11"
ARGS += --release
TARGET = release
else
ARGS += --profile dev --features "rog-control-center/x11"
ARGS += --profile dev
TARGET = debug
endif
X11 ?= 0
ifeq ($(X11),1)
ARGS += --features "rog-control-center/x11"
endif
VENDORED ?= 0
ifeq ($(VENDORED),1)
ARGS += --frozen
@@ -133,7 +138,7 @@ ifeq ($(VENDORED),1)
tar pxf vendor_asusctl_$(VERSION).tar.xz
endif
cargo build $(ARGS)
ifneq ($(STRIP_BINARIES),0)
ifeq ($(STRIP_BINARIES),1)
strip -s ./target/$(TARGET)/$(BIN_C)
strip -s ./target/$(TARGET)/$(BIN_D)
strip -s ./target/$(TARGET)/$(BIN_U)
+11 -2
View File
@@ -84,7 +84,7 @@ Rust and cargo are required, they can be installed from [rustup.rs](https://rust
**fedora:**
dnf install cmake clang-devel libinput-devel libseat-devel libgbm-devel libxkbcommon-devel systemd-devel libdrm-devel expat-devel pcre2-devel libzstd-devel gtk3-devel
dnf install cmake clang-devel libxkbcommon-devel systemd-devel expat-devel pcre2-devel libzstd-devel gtk3-devel
make
sudo make install
@@ -93,7 +93,16 @@ Rust and cargo are required, they can be installed from [rustup.rs](https://rust
Works with KDE Plasma (without GTK packages)
zypper in -t pattern devel_basis
zypper in rustup make cmake clang-devel libinput-devel libseat-devel libgbm-devel libxkbcommon-devel systemd-devel libdrm-devel expat-devel pcre2-devel libzstd-devel gtk3-devel
zypper in rustup make cmake clang-devel libxkbcommon-devel systemd-devel expat-devel pcre2-devel libzstd-devel gtk3-devel
make
sudo make install
**Debian(unsuported):**
officially unsuported,but you can still try and test it by yourself(some features may not be available).
sudo apt install libclang-dev libudev-dev libfontconfig-dev build-essential cmake libxkbcommon-dev
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
make
sudo make install
+1 -2
View File
@@ -10,12 +10,12 @@ edition.workspace = true
[dependencies]
rog_anime = { path = "../rog-anime" }
rog_scsi = { path = "../rog-scsi" }
rog_slash = { path = "../rog-slash" }
rog_aura = { path = "../rog-aura" }
rog_dbus = { path = "../rog-dbus" }
rog_profiles = { path = "../rog-profiles" }
rog_platform = { path = "../rog-platform" }
asusd = { path = "../asusd" }
dmi_id = { path = "../dmi-id" }
ron.workspace = true
@@ -25,4 +25,3 @@ zbus.workspace = true
[dev-dependencies]
rog_dbus = { path = "../rog-dbus" }
cargo-husky.workspace = true
+2 -2
View File
@@ -3,7 +3,7 @@ use std::error::Error;
use std::path::Path;
use std::process::exit;
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimeDiagonal, AnimeType};
use rog_dbus::zbus_anime::AnimeProxyBlocking;
use zbus::blocking::Connection;
@@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn Error>> {
AnimeType::GA401,
)?;
let anime_type = get_maybe_anime_type()?;
let anime_type = get_anime_type();
proxy.write(matrix.into_data_buffer(anime_type)?).unwrap();
+2 -2
View File
@@ -1,7 +1,7 @@
use std::thread::sleep;
use std::time::Duration;
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimeDiagonal, AnimeType};
use rog_dbus::zbus_anime::AnimeProxyBlocking;
use zbus::blocking::Connection;
@@ -29,7 +29,7 @@ fn main() {
}
}
let anime_type = get_maybe_anime_type().unwrap();
let anime_type = get_anime_type();
proxy
.write(matrix.into_data_buffer(anime_type).unwrap())
.unwrap();
+2 -2
View File
@@ -2,7 +2,7 @@ use std::env;
use std::path::Path;
use std::thread::sleep;
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::{ActionData, ActionLoader, Sequences};
use rog_dbus::zbus_anime::AnimeProxyBlocking;
use zbus::blocking::Connection;
@@ -19,7 +19,7 @@ fn main() {
let path = Path::new(&args[1]);
let brightness = args[2].parse::<f32>().unwrap();
let anime_type = get_maybe_anime_type().unwrap();
let anime_type = get_anime_type();
let mut seq = Sequences::new(anime_type);
seq.insert(
0,
+2 -2
View File
@@ -1,6 +1,6 @@
use std::convert::TryFrom;
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimeDataBuffer, AnimeGrid};
use rog_dbus::zbus_anime::AnimeProxyBlocking;
use zbus::blocking::Connection;
@@ -14,7 +14,7 @@ fn main() {
let conn = Connection::system().unwrap();
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
let anime_type = get_maybe_anime_type().unwrap();
let anime_type = get_anime_type();
let mut matrix = AnimeGrid::new(anime_type);
let tmp = matrix.get_mut();
+2 -2
View File
@@ -1,4 +1,4 @@
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::AnimeDataBuffer;
use rog_dbus::zbus_anime::AnimeProxyBlocking;
use zbus::blocking::Connection;
@@ -9,7 +9,7 @@ use zbus::blocking::Connection;
fn main() {
let conn = Connection::system().unwrap();
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
let anime_type = get_maybe_anime_type().unwrap();
let anime_type = get_anime_type();
let mut matrix = AnimeDataBuffer::new(anime_type);
matrix.data_mut()[1] = 100; // start = 1
for n in matrix.data_mut()[2..32].iter_mut() {
+2 -2
View File
@@ -4,7 +4,7 @@ use std::error::Error;
use std::path::Path;
use std::process::exit;
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2};
use rog_dbus::zbus_anime::AnimeProxyBlocking;
use zbus::blocking::Connection;
@@ -20,7 +20,7 @@ fn main() -> Result<(), Box<dyn Error>> {
exit(-1);
}
let anime_type = get_maybe_anime_type()?;
let anime_type = get_anime_type();
let matrix = AnimeImage::from_png(
Path::new(&args[1]),
args[2].parse::<f32>().unwrap(),
+2 -2
View File
@@ -7,7 +7,7 @@ use std::process::exit;
use std::thread::sleep;
use std::time::Duration;
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2};
use rog_dbus::zbus_anime::AnimeProxyBlocking;
use zbus::blocking::Connection;
@@ -23,7 +23,7 @@ fn main() -> Result<(), Box<dyn Error>> {
exit(-1);
}
let anime_type = get_maybe_anime_type()?;
let anime_type = get_anime_type();
let mut matrix = AnimeImage::from_png(
Path::new(&args[1]),
args[2].parse::<f32>().unwrap(),
+10 -5
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)]
@@ -22,6 +23,8 @@ pub struct CliStart {
pub prev_kbd_bright: bool,
#[options(meta = "", help = "Set your battery charge limit <20-100>")]
pub chg_limit: Option<u8>,
#[options(help = "Toggle one-shot battery charge to 100%")]
pub one_shot_chg: bool,
#[options(command)]
pub command: Option<CliCommand>,
}
@@ -29,11 +32,11 @@ pub struct CliStart {
#[derive(Options)]
pub enum CliCommand {
#[options(help = "Set the keyboard lighting from built-in modes")]
LedMode(LedModeCommand),
Aura(LedModeCommand),
#[options(help = "Set the LED power states")]
LedPow1(LedPowerCommand1),
AuraPowerOld(LedPowerCommand1),
#[options(help = "Set the LED power states")]
LedPow2(LedPowerCommand2),
AuraPower(LedPowerCommand2),
#[options(help = "Set or select platform_profile")]
Profile(ProfileCommand),
#[options(help = "Set, select, or modify fan curves if supported")]
@@ -44,8 +47,10 @@ 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")]
Bios(BiosCommand),
Platform(PlatformCommand),
}
#[derive(Debug, Clone, Options)]
@@ -85,7 +90,7 @@ pub struct GraphicsCommand {
}
#[derive(Options, Debug)]
pub struct BiosCommand {
pub struct PlatformCommand {
#[options(help = "print help message")]
pub help: bool,
#[options(
+309 -210
View File
@@ -9,11 +9,12 @@ use aura_cli::{LedPowerCommand1, LedPowerCommand2};
use dmi_id::DMIID;
use fan_curve_cli::FanCurveCommand;
use gumdrop::{Opt, Options};
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_anime::{AnimTime, AnimeDataBuffer, AnimeDiagonal, AnimeGif, AnimeImage, AnimeType, Vec2};
use rog_aura::keyboard::{AuraPowerState, LaptopAuraPower};
use rog_aura::{self, AuraDeviceType, AuraEffect, PowerZones};
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,11 @@ 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;
use crate::aura_cli::{AuraPowerStates, LedBrightness};
@@ -33,6 +37,7 @@ mod anime_cli;
mod aura_cli;
mod cli_opts;
mod fan_curve_cli;
mod scsi_cli;
mod slash_cli;
fn main() {
@@ -122,31 +127,33 @@ fn check_service(name: &str) -> bool {
false
}
fn find_aura_iface() -> Result<Vec<AuraProxyBlocking<'static>>, Box<dyn std::error::Error>> {
fn find_iface<T>(iface_name: &str) -> Result<Vec<T>, Box<dyn std::error::Error>>
where
T: ProxyImpl<'static> + From<zbus::Proxy<'static>>,
{
let conn = zbus::blocking::Connection::system().unwrap();
let f =
zbus::blocking::fdo::ObjectManagerProxy::new(&conn, "org.asuslinux.Daemon", "/").unwrap();
let interfaces = f.get_managed_objects().unwrap();
let mut aura_paths = Vec::new();
let mut paths = Vec::new();
for v in interfaces.iter() {
// let o: Vec<zbus::names::OwnedInterfaceName> = v.1.keys().map(|e|
// e.to_owned()).collect(); println!("{}, {:?}", v.0, o);
for k in v.1.keys() {
if k.as_str() == "org.asuslinux.Aura" {
println!("Found aura device at {}, {}", v.0, k);
aura_paths.push(v.0.clone());
if k.as_str() == iface_name {
println!("Found {iface_name} device at {}, {}", v.0, k);
paths.push(v.0.clone());
}
}
}
if aura_paths.len() > 1 {
println!("Multiple aura devices found: {aura_paths:?}");
println!("TODO: enable selection");
if paths.len() > 1 {
println!("Multiple aura devices found: {paths:?}");
}
if !aura_paths.is_empty() {
if !paths.is_empty() {
let mut ctrl = Vec::new();
for path in aura_paths {
for path in paths {
ctrl.push(
AuraProxyBlocking::builder(&conn)
T::builder(&conn)
.path(path.clone())?
.destination("org.asuslinux.Daemon")?
.build()?,
@@ -165,9 +172,9 @@ fn do_parsed(
conn: Connection,
) -> Result<(), Box<dyn std::error::Error>> {
match &parsed.command {
Some(CliCommand::LedMode(mode)) => handle_led_mode(&find_aura_iface()?, mode)?,
Some(CliCommand::LedPow1(pow)) => handle_led_power1(&find_aura_iface()?, pow)?,
Some(CliCommand::LedPow2(pow)) => handle_led_power2(&find_aura_iface()?, pow)?,
Some(CliCommand::Aura(mode)) => handle_led_mode(mode)?,
Some(CliCommand::AuraPowerOld(pow)) => handle_led_power1(pow)?,
Some(CliCommand::AuraPower(pow)) => handle_led_power2(pow)?,
Some(CliCommand::Profile(cmd)) => {
handle_throttle_profile(&conn, supported_properties, cmd)?
}
@@ -175,9 +182,10 @@ fn do_parsed(
handle_fan_curve(&conn, cmd)?;
}
Some(CliCommand::Graphics(_)) => do_gfx(),
Some(CliCommand::Anime(cmd)) => handle_anime(&conn, cmd)?,
Some(CliCommand::Slash(cmd)) => handle_slash(&conn, cmd)?,
Some(CliCommand::Bios(cmd)) => {
Some(CliCommand::Anime(cmd)) => handle_anime(cmd)?,
Some(CliCommand::Slash(cmd)) => handle_slash(cmd)?,
Some(CliCommand::Scsi(cmd)) => handle_scsi(cmd)?,
Some(CliCommand::Platform(cmd)) => {
handle_platform_properties(&conn, supported_properties, cmd)?
}
None => {
@@ -185,22 +193,24 @@ fn do_parsed(
&& parsed.kbd_bright.is_none()
&& parsed.chg_limit.is_none()
&& !parsed.next_kbd_bright
&& !parsed.prev_kbd_bright)
&& !parsed.prev_kbd_bright
&& !parsed.one_shot_chg)
|| parsed.help
{
println!("{}", CliStart::usage());
println!();
if let Some(cmdlist) = CliStart::command_list() {
let dev_type = if let Ok(proxy) = find_aura_iface() {
// TODO: commands on all?
proxy
.first()
.unwrap()
.device_type()
.unwrap_or(AuraDeviceType::Unknown)
} else {
AuraDeviceType::Unknown
};
let dev_type =
if let Ok(proxy) = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura") {
// TODO: commands on all?
proxy
.first()
.unwrap()
.device_type()
.unwrap_or(AuraDeviceType::Unknown)
} else {
AuraDeviceType::Unknown
};
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
for command in commands.iter().filter(|command| {
if command.trim().starts_with("fan-curve")
@@ -210,6 +220,12 @@ fn do_parsed(
return false;
}
if command.trim().starts_with("aura")
&& !supported_interfaces.contains(&"org.asuslinux.Aura".to_string())
{
return false;
}
if command.trim().starts_with("anime")
&& !supported_interfaces.contains(&"org.asuslinux.Anime".to_string())
{
@@ -222,7 +238,7 @@ fn do_parsed(
return false;
}
if command.trim().starts_with("bios")
if command.trim().starts_with("platform")
&& !supported_interfaces.contains(&"org.asuslinux.Platform".to_string())
{
return false;
@@ -230,11 +246,11 @@ fn do_parsed(
if !dev_type.is_old_laptop()
&& !dev_type.is_tuf_laptop()
&& command.trim().starts_with("led-pow-1")
&& command.trim().starts_with("aura-power-old")
{
return false;
}
if !dev_type.is_new_laptop() && command.trim().starts_with("led-pow-2") {
if !dev_type.is_new_laptop() && command.trim().starts_with("aura-power") {
return false;
}
true
@@ -251,7 +267,7 @@ fn do_parsed(
}
if let Some(brightness) = &parsed.kbd_bright {
if let Ok(aura) = find_aura_iface() {
if let Ok(aura) = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura") {
for aura in aura.iter() {
match brightness.level() {
None => {
@@ -267,7 +283,7 @@ fn do_parsed(
}
if parsed.next_kbd_bright {
if let Ok(aura) = find_aura_iface() {
if let Ok(aura) = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura") {
for aura in aura.iter() {
let brightness = aura.brightness()?;
aura.set_brightness(brightness.next())?;
@@ -278,7 +294,7 @@ fn do_parsed(
}
if parsed.prev_kbd_bright {
if let Ok(aura) = find_aura_iface() {
if let Ok(aura) = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura") {
for aura in aura.iter() {
let brightness = aura.brightness()?;
aura.set_brightness(brightness.prev())?;
@@ -294,7 +310,7 @@ fn do_parsed(
"Supported Platform Properties:\n{:#?}",
supported_properties
);
if let Ok(aura) = find_aura_iface() {
if let Ok(aura) = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura") {
// TODO: multiple RGB check
let bright = aura.first().unwrap().supported_brightness()?;
let modes = aura.first().unwrap().supported_basic_modes()?;
@@ -314,6 +330,11 @@ fn do_parsed(
proxy.set_charge_control_end_threshold(chg_limit)?;
}
if parsed.one_shot_chg {
let proxy = PlatformProxyBlocking::new(&conn)?;
proxy.one_shot_full_charge()?;
}
Ok(())
}
@@ -325,7 +346,7 @@ fn do_gfx() {
println!("This command will be removed in future");
}
fn handle_anime(conn: &Connection, cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box<dyn std::error::Error>> {
if (cmd.command.is_none()
&& cmd.enable_display.is_none()
&& cmd.enable_powersave_anim.is_none()
@@ -342,165 +363,169 @@ fn handle_anime(conn: &Connection, cmd: &AnimeCommand) -> Result<(), Box<dyn std
println!("\n{}", lst);
}
}
let proxy = AnimeProxyBlocking::new(conn)?;
if let Some(enable) = cmd.enable_display {
proxy.set_enable_display(enable)?;
}
if let Some(enable) = cmd.enable_powersave_anim {
proxy.set_builtins_enabled(enable)?;
}
if let Some(bright) = cmd.brightness {
proxy.set_brightness(bright)?;
}
if let Some(enable) = cmd.off_when_lid_closed {
proxy.set_off_when_lid_closed(enable)?;
}
if let Some(enable) = cmd.off_when_suspended {
proxy.set_off_when_suspended(enable)?;
}
if let Some(enable) = cmd.off_when_unplugged {
proxy.set_off_when_unplugged(enable)?;
}
if cmd.off_with_his_head.is_some() {
println!("Did Alice _really_ make it back from Wonderland?");
}
let mut anime_type = get_maybe_anime_type()?;
if let AnimeType::Unsupported = anime_type {
if let Some(model) = cmd.override_type {
anime_type = model;
let animes = find_iface::<AnimeProxyBlocking>("org.asuslinux.Anime")?;
for proxy in animes {
if let Some(enable) = cmd.enable_display {
proxy.set_enable_display(enable)?;
}
if let Some(enable) = cmd.enable_powersave_anim {
proxy.set_builtins_enabled(enable)?;
}
if let Some(bright) = cmd.brightness {
proxy.set_brightness(bright)?;
}
if let Some(enable) = cmd.off_when_lid_closed {
proxy.set_off_when_lid_closed(enable)?;
}
if let Some(enable) = cmd.off_when_suspended {
proxy.set_off_when_suspended(enable)?;
}
if let Some(enable) = cmd.off_when_unplugged {
proxy.set_off_when_unplugged(enable)?;
}
if cmd.off_with_his_head.is_some() {
println!("Did Alice _really_ make it back from Wonderland?");
}
}
if cmd.clear {
let data = vec![255u8; anime_type.data_length()];
let tmp = AnimeDataBuffer::from_vec(anime_type, data)?;
proxy.write(tmp)?;
}
if let Some(action) = cmd.command.as_ref() {
match action {
AnimeActions::Image(image) => {
if image.help_requested() || image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage());
if let Some(lst) = image.self_command_list() {
println!("\n{}", lst);
}
return Ok(());
}
verify_brightness(image.bright);
let matrix = AnimeImage::from_png(
Path::new(&image.path),
image.scale,
image.angle,
Vec2::new(image.x_pos, image.y_pos),
image.bright,
anime_type,
)?;
proxy.write(<AnimeDataBuffer>::try_from(&matrix)?)?;
let mut anime_type = get_anime_type();
if let AnimeType::Unsupported = anime_type {
if let Some(model) = cmd.override_type {
anime_type = model;
}
AnimeActions::PixelImage(image) => {
if image.help_requested() || image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage());
if let Some(lst) = image.self_command_list() {
println!("\n{}", lst);
}
if cmd.clear {
let data = vec![255u8; anime_type.data_length()];
let tmp = AnimeDataBuffer::from_vec(anime_type, data)?;
proxy.write(tmp)?;
}
if let Some(action) = cmd.command.as_ref() {
match action {
AnimeActions::Image(image) => {
if image.help_requested() || image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage());
if let Some(lst) = image.self_command_list() {
println!("\n{}", lst);
}
return Ok(());
}
return Ok(());
verify_brightness(image.bright);
let matrix = AnimeImage::from_png(
Path::new(&image.path),
image.scale,
image.angle,
Vec2::new(image.x_pos, image.y_pos),
image.bright,
anime_type,
)?;
proxy.write(<AnimeDataBuffer>::try_from(&matrix)?)?;
}
verify_brightness(image.bright);
let matrix = AnimeDiagonal::from_png(
Path::new(&image.path),
None,
image.bright,
anime_type,
)?;
proxy.write(matrix.into_data_buffer(anime_type)?)?;
}
AnimeActions::Gif(gif) => {
if gif.help_requested() || gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage());
if let Some(lst) = gif.self_command_list() {
println!("\n{}", lst);
AnimeActions::PixelImage(image) => {
if image.help_requested() || image.path.is_empty() {
println!("Missing arg or command\n\n{}", image.self_usage());
if let Some(lst) = image.self_command_list() {
println!("\n{}", lst);
}
return Ok(());
}
return Ok(());
verify_brightness(image.bright);
let matrix = AnimeDiagonal::from_png(
Path::new(&image.path),
None,
image.bright,
anime_type,
)?;
proxy.write(matrix.into_data_buffer(anime_type)?)?;
}
verify_brightness(gif.bright);
let matrix = AnimeGif::from_gif(
Path::new(&gif.path),
gif.scale,
gif.angle,
Vec2::new(gif.x_pos, gif.y_pos),
AnimTime::Count(1),
gif.bright,
anime_type,
)?;
let mut loops = gif.loops as i32;
loop {
for frame in matrix.frames() {
proxy.write(frame.frame().clone())?;
sleep(frame.delay());
AnimeActions::Gif(gif) => {
if gif.help_requested() || gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage());
if let Some(lst) = gif.self_command_list() {
println!("\n{}", lst);
}
return Ok(());
}
if loops >= 0 {
loops -= 1;
}
if loops == 0 {
break;
verify_brightness(gif.bright);
let matrix = AnimeGif::from_gif(
Path::new(&gif.path),
gif.scale,
gif.angle,
Vec2::new(gif.x_pos, gif.y_pos),
AnimTime::Count(1),
gif.bright,
anime_type,
)?;
let mut loops = gif.loops as i32;
loop {
for frame in matrix.frames() {
proxy.write(frame.frame().clone())?;
sleep(frame.delay());
}
if loops >= 0 {
loops -= 1;
}
if loops == 0 {
break;
}
}
}
}
AnimeActions::PixelGif(gif) => {
if gif.help_requested() || gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage());
if let Some(lst) = gif.self_command_list() {
println!("\n{}", lst);
AnimeActions::PixelGif(gif) => {
if gif.help_requested() || gif.path.is_empty() {
println!("Missing arg or command\n\n{}", gif.self_usage());
if let Some(lst) = gif.self_command_list() {
println!("\n{}", lst);
}
return Ok(());
}
return Ok(());
}
verify_brightness(gif.bright);
verify_brightness(gif.bright);
let matrix = AnimeGif::from_diagonal_gif(
Path::new(&gif.path),
AnimTime::Count(1),
gif.bright,
anime_type,
)?;
let matrix = AnimeGif::from_diagonal_gif(
Path::new(&gif.path),
AnimTime::Count(1),
gif.bright,
anime_type,
)?;
let mut loops = gif.loops as i32;
loop {
for frame in matrix.frames() {
proxy.write(frame.frame().clone())?;
sleep(frame.delay());
}
if loops >= 0 {
loops -= 1;
}
if loops == 0 {
break;
let mut loops = gif.loops as i32;
loop {
for frame in matrix.frames() {
proxy.write(frame.frame().clone())?;
sleep(frame.delay());
}
if loops >= 0 {
loops -= 1;
}
if loops == 0 {
break;
}
}
}
}
AnimeActions::SetBuiltins(builtins) => {
if builtins.help_requested() || builtins.set.is_none() {
println!("\nAny unspecified args will be set to default (first shown var)\n");
println!("\n{}", builtins.self_usage());
if let Some(lst) = builtins.self_command_list() {
println!("\n{}", lst);
AnimeActions::SetBuiltins(builtins) => {
if builtins.help_requested() || builtins.set.is_none() {
println!(
"\nAny unspecified args will be set to default (first shown var)\n"
);
println!("\n{}", builtins.self_usage());
if let Some(lst) = builtins.self_command_list() {
println!("\n{}", lst);
}
return Ok(());
}
return Ok(());
}
proxy.set_builtin_animations(rog_anime::Animations {
boot: builtins.boot,
awake: builtins.awake,
sleep: builtins.sleep,
shutdown: builtins.shutdown,
})?;
proxy.set_builtin_animations(rog_anime::Animations {
boot: builtins.boot,
awake: builtins.awake,
sleep: builtins.sleep,
shutdown: builtins.shutdown,
})?;
}
}
}
}
@@ -516,7 +541,7 @@ fn verify_brightness(brightness: f32) {
}
}
fn handle_slash(conn: &Connection, cmd: &SlashCommand) -> Result<(), Box<dyn std::error::Error>> {
fn handle_slash(cmd: &SlashCommand) -> Result<(), Box<dyn std::error::Error>> {
if (cmd.brightness.is_none()
&& cmd.interval.is_none()
&& cmd.slash_mode.is_none()
@@ -530,21 +555,24 @@ fn handle_slash(conn: &Connection, cmd: &SlashCommand) -> Result<(), Box<dyn std
println!("\n{}", lst);
}
}
let proxy = SlashProxyBlocking::new(conn)?;
if cmd.enable {
proxy.set_enabled(true)?;
}
if cmd.disable {
proxy.set_enabled(false)?;
}
if let Some(brightness) = cmd.brightness {
proxy.set_brightness(brightness)?;
}
if let Some(interval) = cmd.interval {
proxy.set_interval(interval)?;
}
if let Some(slash_mode) = cmd.slash_mode {
proxy.set_slash_mode(slash_mode)?;
let slashes = find_iface::<SlashProxyBlocking>("org.asuslinux.Slash")?;
for proxy in slashes {
if cmd.enable {
proxy.set_enabled(true)?;
}
if cmd.disable {
proxy.set_enabled(false)?;
}
if let Some(brightness) = cmd.brightness {
proxy.set_brightness(brightness)?;
}
if let Some(interval) = cmd.interval {
proxy.set_interval(interval)?;
}
if let Some(slash_mode) = cmd.slash_mode {
proxy.set_slash_mode(slash_mode)?;
}
}
if cmd.list {
let res = SlashMode::list();
@@ -556,10 +584,80 @@ fn handle_slash(conn: &Connection, cmd: &SlashCommand) -> Result<(), Box<dyn std
Ok(())
}
fn handle_led_mode(
aura: &[AuraProxyBlocking],
mode: &LedModeCommand,
) -> Result<(), Box<dyn std::error::Error>> {
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 {
println!("Missing arg or command\n");
@@ -570,6 +668,7 @@ fn handle_led_mode(
if let Some(cmdlist) = LedModeCommand::command_list() {
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
// TODO: multiple rgb check
let aura = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura")?;
let modes = aura.first().unwrap().supported_basic_modes()?;
for command in commands.iter().filter(|command| {
for mode in &modes {
@@ -599,6 +698,7 @@ fn handle_led_mode(
println!("Please specify either next or previous");
return Ok(());
}
let aura = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura")?;
if mode.next_mode {
for aura in aura {
let mode = aura.led_mode()?;
@@ -634,10 +734,8 @@ fn handle_led_mode(
Ok(())
}
fn handle_led_power1(
aura: &[AuraProxyBlocking],
power: &LedPowerCommand1,
) -> Result<(), Box<dyn std::error::Error>> {
fn handle_led_power1(power: &LedPowerCommand1) -> Result<(), Box<dyn std::error::Error>> {
let aura = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura")?;
for aura in aura {
let dev_type = aura.device_type()?;
if !dev_type.is_old_laptop() && !dev_type.is_tuf_laptop() {
@@ -658,7 +756,7 @@ fn handle_led_power1(
}
if dev_type.is_old_laptop() || dev_type.is_tuf_laptop() {
handle_led_power_1_do_1866(aura, power)?;
handle_led_power_1_do_1866(&aura, power)?;
return Ok(());
}
}
@@ -696,10 +794,8 @@ fn handle_led_power_1_do_1866(
Ok(())
}
fn handle_led_power2(
aura: &[AuraProxyBlocking],
power: &LedPowerCommand2,
) -> Result<(), Box<dyn std::error::Error>> {
fn handle_led_power2(power: &LedPowerCommand2) -> Result<(), Box<dyn std::error::Error>> {
let aura = find_iface::<AuraProxyBlocking>("org.asuslinux.Aura")?;
for aura in aura {
let dev_type = aura.device_type()?;
if !dev_type.is_new_laptop() {
@@ -888,7 +984,7 @@ fn handle_fan_curve(
fn handle_platform_properties(
conn: &Connection,
supported: &[Properties],
cmd: &BiosCommand,
cmd: &PlatformCommand,
) -> Result<(), Box<dyn std::error::Error>> {
{
if (cmd.gpu_mux_mode_set.is_none()
@@ -901,7 +997,10 @@ fn handle_platform_properties(
{
println!("Missing arg or command\n");
let usage: Vec<String> = BiosCommand::usage().lines().map(|s| s.to_owned()).collect();
let usage: Vec<String> = PlatformCommand::usage()
.lines()
.map(|s| s.to_owned())
.collect();
for line in usage.iter().filter(|line| {
line.contains("sound") && supported.contains(&Properties::PostAnimationSound)
+35
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,
}
+2 -2
View File
@@ -11,9 +11,9 @@ pub struct SlashCommand {
pub disable: bool,
#[options(meta = "", help = "Set brightness value <0-255>")]
pub brightness: Option<u8>,
#[options(meta = "", help = "Set interval value <0-255>")]
#[options(meta = "", help = "Set interval value <0-5>")]
pub interval: Option<u8>,
#[options(help = "Set SlashMode (so 'list' for all options)")]
#[options(meta = "", help = "Set SlashMode (so 'list' for all options)")]
pub slash_mode: Option<SlashMode>,
#[options(help = "list available animations")]
pub list: bool,
-6
View File
@@ -32,9 +32,3 @@ config-traits = { path = "../config-traits" }
zbus.workspace = true
env_logger.workspace = true
[dev-dependencies]
cargo-husky.workspace = true
[package.metadata.cargo-machete]
ignored = ["serde"]
+2 -2
View File
@@ -66,7 +66,7 @@ pub struct CtrlAnimeInner<'a> {
do_early_return: Arc<AtomicBool>,
}
impl<'a> CtrlAnimeInner<'static> {
impl CtrlAnimeInner<'static> {
pub fn new(
sequences: Sequences,
client: AnimeProxyBlocking<'static>,
@@ -81,7 +81,7 @@ impl<'a> CtrlAnimeInner<'static> {
/// To be called on each main loop iteration to pump out commands to the
/// anime
pub fn run(&'a self) -> Result<(), Error> {
pub fn run(&self) -> Result<(), Error> {
if self.do_early_return.load(Ordering::SeqCst) {
return Ok(());
}
+2 -2
View File
@@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
use asusd_user::config::*;
use asusd_user::ctrl_anime::{CtrlAnime, CtrlAnimeInner};
use config_traits::{StdConfig, StdConfigLoad};
use rog_anime::usb::get_maybe_anime_type;
use rog_anime::usb::get_anime_type;
use rog_aura::aura_detection::LedSupportData;
use rog_aura::keyboard::KeyLayout;
use rog_dbus::zbus_anime::AnimeProxyBlocking;
@@ -44,7 +44,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Set up the anime data and run loop/thread
if supported.contains(&"org.asuslinux.Anime".to_string()) {
if let Some(cfg) = config.active_anime {
let anime_type = get_maybe_anime_type()?;
let anime_type = get_anime_type();
let anime_config = ConfigAnime::new().set_name(cfg).load();
let anime = anime_config.create(anime_type)?;
let anime_config = Arc::new(Mutex::new(anime_config));
+1 -3
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" }
@@ -43,6 +44,3 @@ concat-idents.workspace = true
[dev-dependencies]
cargo-husky.workspace = true
[package.metadata.cargo-machete]
ignored = ["serde"]
@@ -10,18 +10,18 @@ use serde::{Deserialize, Serialize};
const CONFIG_FILE: &str = "anime.ron";
#[derive(Deserialize, Serialize, Default)]
pub struct AnimeConfigCached {
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct AniMeConfigCached {
pub system: Vec<ActionData>,
pub boot: Vec<ActionData>,
pub wake: Vec<ActionData>,
pub shutdown: Vec<ActionData>,
}
impl AnimeConfigCached {
impl AniMeConfigCached {
pub fn init_from_config(
&mut self,
config: &AnimeConfig,
config: &AniMeConfig,
anime_type: AnimeType,
) -> Result<(), AnimeError> {
let mut sys = Vec::with_capacity(config.system.len());
@@ -53,7 +53,9 @@ impl AnimeConfigCached {
/// Config for base system actions for the anime display
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct AnimeConfig {
pub struct AniMeConfig {
#[serde(skip)]
pub anime_type: AnimeType,
pub system: Vec<ActionLoader>,
pub boot: Vec<ActionLoader>,
pub wake: Vec<ActionLoader>,
@@ -69,9 +71,10 @@ pub struct AnimeConfig {
pub builtin_anims: Animations,
}
impl Default for AnimeConfig {
impl Default for AniMeConfig {
fn default() -> Self {
AnimeConfig {
AniMeConfig {
anime_type: AnimeType::GA402,
system: Vec::new(),
boot: Vec::new(),
wake: Vec::new(),
@@ -89,7 +92,7 @@ impl Default for AnimeConfig {
}
}
impl StdConfig for AnimeConfig {
impl StdConfig for AniMeConfig {
fn new() -> Self {
Self::create_default()
}
@@ -103,10 +106,10 @@ impl StdConfig for AnimeConfig {
}
}
impl StdConfigLoad for AnimeConfig {}
impl StdConfigLoad for AniMeConfig {}
impl From<&AnimeConfig> for DeviceState {
fn from(config: &AnimeConfig) -> Self {
impl From<&AniMeConfig> for DeviceState {
fn from(config: &AniMeConfig) -> Self {
DeviceState {
display_enabled: config.display_enabled,
display_brightness: config.display_brightness,
@@ -120,7 +123,7 @@ impl From<&AnimeConfig> for DeviceState {
}
}
impl AnimeConfig {
impl AniMeConfig {
// fn clamp_config_brightness(mut config: &mut AnimeConfig) {
// if config.brightness < 0.0 || config.brightness > 1.0 {
// warn!(
@@ -133,7 +136,7 @@ impl AnimeConfig {
fn create_default() -> Self {
// create a default config here
AnimeConfig {
AniMeConfig {
system: vec![],
boot: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
+247
View File
@@ -0,0 +1,247 @@
pub mod config;
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
pub mod trait_impls;
use std::convert::TryFrom;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::sleep;
use config_traits::StdConfig;
use log::{error, info, warn};
use rog_anime::usb::{
pkt_flush, pkt_set_brightness, pkt_set_enable_display, pkt_set_enable_powersave_anim,
pkts_for_init, Brightness,
};
use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType};
use rog_platform::hid_raw::HidRaw;
use rog_platform::usb_raw::USBRaw;
use tokio::sync::{Mutex, MutexGuard};
use self::config::{AniMeConfig, AniMeConfigCached};
use crate::error::RogError;
#[derive(Debug, Clone)]
pub struct AniMe {
hid: Option<Arc<Mutex<HidRaw>>>,
usb: Option<Arc<Mutex<USBRaw>>>,
config: Arc<Mutex<AniMeConfig>>,
cache: AniMeConfigCached,
// set to force thread to exit
thread_exit: Arc<AtomicBool>,
// Set to false when the thread exits
thread_running: Arc<AtomicBool>,
}
impl AniMe {
pub fn new(
hid: Option<Arc<Mutex<HidRaw>>>,
usb: Option<Arc<Mutex<USBRaw>>>,
config: Arc<Mutex<AniMeConfig>>,
) -> Self {
Self {
hid,
usb,
config,
cache: AniMeConfigCached::default(),
thread_exit: Arc::new(AtomicBool::new(false)),
thread_running: Arc::new(AtomicBool::new(false)),
}
}
/// Will fail if something is already holding the config lock
async fn do_init_cache(&mut self) {
if let Ok(mut config) = self.config.try_lock() {
if let Err(e) = self.cache.init_from_config(&config, config.anime_type) {
error!(
"Trying to cache the Anime Config failed, will reset to default config: {e:?}"
);
config.rename_file_old();
*config = AniMeConfig::new();
config.write();
}
} else {
error!("AniMe Matrix could not init cache")
}
}
/// Initialise the device if required.
pub async fn do_initialization(&mut self) -> Result<(), RogError> {
self.do_init_cache().await;
let pkts = pkts_for_init();
self.write_bytes(&pkts[0]).await?;
self.write_bytes(&pkts[1]).await
}
pub async fn lock_config(&self) -> MutexGuard<AniMeConfig> {
self.config.lock().await
}
pub async fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
if let Some(hid) = &self.hid {
hid.lock().await.write_bytes(message)?;
} else if let Some(usb) = &self.usb {
usb.lock().await.write_bytes(message)?;
}
Ok(())
}
/// Write only a data packet. This will modify the leds brightness using the
/// global brightness set in config.
async fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> {
for led in buffer.data_mut().iter_mut() {
let mut bright = *led as f32;
if bright > 254.0 {
bright = 254.0;
}
*led = bright as u8;
}
let data = AnimePacketType::try_from(buffer)?;
for row in &data {
self.write_bytes(row).await?;
}
self.write_bytes(&pkt_flush()).await
}
pub async fn set_builtins_enabled(
&self,
enabled: bool,
bright: Brightness,
) -> Result<(), RogError> {
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))
.await?;
self.write_bytes(&pkt_set_enable_display(enabled)).await?;
self.write_bytes(&pkt_set_brightness(bright)).await?;
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))
.await
}
/// Start an action thread. This is classed as a singleton and there should
/// be only one running - so the thread uses atomics to signal run/exit.
///
/// Because this also writes to the usb device, other write tries (display
/// only) *must* get the mutex lock and set the `thread_exit` atomic.
async fn run_thread(&self, actions: Vec<ActionData>, mut once: bool) {
if actions.is_empty() {
warn!("AniMe system actions was empty");
return;
}
self.write_bytes(&pkt_set_enable_powersave_anim(false))
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
let thread_exit = self.thread_exit.clone();
let thread_running = self.thread_running.clone();
let anime_type = self.config.lock().await.anime_type;
let inner = self.clone();
// Loop rules:
// - Lock the mutex **only when required**. That is, the lock must be held for
// the shortest duration possible.
// - An AtomicBool used for thread exit should be checked in every loop,
// including nested
// The only reason for this outer thread is to prevent blocking while waiting
// for the next spawned thread to exit
// TODO: turn this in to async task (maybe? COuld still risk blocking main
// thread)
tokio::spawn(async move {
info!("AniMe new system thread started");
// First two loops are to ensure we *do* aquire a lock on the mutex
// The reason the loop is required is because the USB writes can block
// for up to 10ms. We can't fail to get the atomics.
while thread_running.load(Ordering::SeqCst) {
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
}
info!("AniMe no previous system thread running (now)");
thread_exit.store(false, Ordering::SeqCst);
thread_running.store(true, Ordering::SeqCst);
'main: loop {
for action in &actions {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
match action {
ActionData::Animation(frames) => {
// TODO: sort all this out
rog_anime::run_animation(frames, &|frame| {
if thread_exit.load(Ordering::Acquire) {
info!("rog-anime: animation sub-loop was asked to exit");
return Ok(true); // Do safe exit
}
let inner = inner.clone();
tokio::task::spawn_local(async move {
inner
.write_data_buffer(frame)
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
});
Ok(false) // Don't exit yet
});
if thread_exit.load(Ordering::Acquire) {
info!("rog-anime: sub-loop exited and main loop exiting now");
break 'main;
}
}
ActionData::Image(image) => {
once = false;
inner
.write_data_buffer(image.as_ref().clone())
.await
.map_err(|e| error!("{}", e))
.ok();
}
ActionData::Pause(duration) => sleep(*duration),
ActionData::AudioEq
| ActionData::SystemInfo
| ActionData::TimeDate
| ActionData::Matrix => {}
}
}
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
if once || actions.is_empty() {
break 'main;
}
}
// Clear the display on exit
if let Ok(data) =
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()])
.map_err(|e| error!("{}", e))
{
inner
.write_data_buffer(data)
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
}
inner
.write_bytes(&pkt_set_enable_powersave_anim(
inner.config.lock().await.builtin_anims_enabled,
))
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
// Loop ended, set the atmonics
thread_running.store(false, Ordering::SeqCst);
info!("AniMe system thread exited");
})
.await
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
}
}
@@ -1,23 +1,22 @@
use std::sync::atomic::Ordering;
use std::sync::Arc;
use config_traits::StdConfig;
use log::warn;
use log::{error, warn};
use logind_zbus::manager::ManagerProxy;
use rog_anime::usb::{
pkt_set_brightness, pkt_set_builtin_animations, pkt_set_enable_display,
pkt_set_enable_powersave_anim, Brightness,
};
use rog_anime::{Animations, AnimeDataBuffer, DeviceState};
use zbus::export::futures_util::lock::Mutex;
use zbus::{interface, CacheProperties, Connection, SignalContext};
use zbus::object_server::SignalEmitter;
use zbus::proxy::CacheProperties;
use zbus::zvariant::OwnedObjectPath;
use zbus::{interface, Connection};
use super::config::AnimeConfig;
use super::CtrlAnime;
use super::config::AniMeConfig;
use super::AniMe;
use crate::error::RogError;
pub const ANIME_ZBUS_NAME: &str = "Anime";
pub const ANIME_ZBUS_PATH: &str = "/org/asuslinux";
use crate::Reloadable;
async fn get_logind_manager<'a>() -> ManagerProxy<'a> {
let connection = Connection::system()
@@ -32,12 +31,29 @@ async fn get_logind_manager<'a>() -> ManagerProxy<'a> {
}
#[derive(Clone)]
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
pub struct AniMeZbus(AniMe);
/// The struct with the main dbus methods requires this trait
impl crate::ZbusRun for CtrlAnimeZbus {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, ANIME_ZBUS_PATH, server).await;
impl AniMeZbus {
pub fn new(anime: AniMe) -> Self {
Self(anime)
}
pub async fn start_tasks(
mut self,
connection: &Connection,
path: OwnedObjectPath,
) -> Result<(), RogError> {
// let task = zbus.clone();
self.reload()
.await
.unwrap_or_else(|err| warn!("Controller error: {}", err));
connection
.object_server()
.at(path.clone(), self)
.await
.map_err(|e| error!("Couldn't add server at path: {path}, {e:?}"))
.ok();
Ok(())
}
}
@@ -45,91 +61,84 @@ impl crate::ZbusRun for CtrlAnimeZbus {
// If the try_lock *does* succeed then any other thread trying to lock will not
// grab it until we finish.
#[interface(name = "org.asuslinux.Anime")]
impl CtrlAnimeZbus {
impl AniMeZbus {
/// Writes a data stream of length. Will force system thread to exit until
/// it is restarted
async fn write(&self, input: AnimeDataBuffer) -> zbus::fdo::Result<()> {
self.0
.lock()
.await
.thread_exit
.store(true, Ordering::SeqCst);
self.0
.lock()
.await
.write_data_buffer(input)
.map_err(|err| {
warn!("ctrl_anime::run_animation:callback {}", err);
err
})?;
let bright = self.0.config.lock().await.display_brightness;
self.0.set_builtins_enabled(false, bright).await?;
self.0.thread_exit.store(true, Ordering::SeqCst);
self.0.write_data_buffer(input).await.map_err(|err| {
warn!("ctrl_anime::run_animation:callback {}", err);
err
})?;
Ok(())
}
/// Set base brightness level
#[zbus(property)]
async fn brightness(&self) -> Brightness {
self.0.lock().await.config.display_brightness
if let Ok(config) = self.0.config.try_lock() {
return config.display_brightness;
}
Brightness::Off
}
/// Set base brightness level
#[zbus(property)]
async fn set_brightness(&self, brightness: Brightness) {
self.0
.lock()
.await
.node
.write_bytes(&pkt_set_brightness(brightness))
.await
.map_err(|err| {
warn!("ctrl_anime::set_brightness {}", err);
})
.ok();
self.0
.lock()
.await
.node
.write_bytes(&pkt_set_enable_display(brightness != Brightness::Off))
.await
.map_err(|err| {
warn!("ctrl_anime::set_brightness {}", err);
})
.ok();
self.0.lock().await.config.display_enabled = brightness != Brightness::Off;
self.0.lock().await.config.display_brightness = brightness;
self.0.lock().await.config.write();
let mut config = self.0.config.lock().await;
config.display_enabled = brightness != Brightness::Off;
config.display_brightness = brightness;
config.write();
}
#[zbus(property)]
async fn builtins_enabled(&self) -> bool {
let lock = self.0.lock().await;
lock.config.builtin_anims_enabled
if let Ok(config) = self.0.config.try_lock() {
return config.builtin_anims_enabled;
}
false
}
/// Enable the builtin animations or not. This is quivalent to "Powersave
/// animations" in Armory crate
#[zbus(property)]
async fn set_builtins_enabled(&self, enabled: bool) {
let brightness = self.0.lock().await.config.display_brightness;
let mut config = self.0.config.lock().await;
let brightness = config.display_brightness;
self.0
.lock()
.await
.node
.set_builtins_enabled(enabled, brightness)
.await
.map_err(|err| {
warn!("ctrl_anime::set_builtins_enabled {}", err);
})
.ok();
if !enabled {
let anime_type = self.0.lock().await.anime_type;
let anime_type = config.anime_type;
let data = vec![255u8; anime_type.data_length()];
if let Ok(tmp) = AnimeDataBuffer::from_vec(anime_type, data).map_err(|err| {
warn!("ctrl_anime::set_builtins_enabled {}", err);
}) {
self.0
.lock()
.await
.node
.write_bytes(tmp.data())
.await
.map_err(|err| {
warn!("ctrl_anime::set_builtins_enabled {}", err);
})
@@ -137,77 +146,78 @@ impl CtrlAnimeZbus {
}
}
self.0.lock().await.config.builtin_anims_enabled = enabled;
self.0.lock().await.config.write();
config.builtin_anims_enabled = enabled;
config.write();
if enabled {
self.0
.lock()
.await
.thread_exit
.store(true, Ordering::Release);
self.0.thread_exit.store(true, Ordering::Release);
}
}
#[zbus(property)]
async fn builtin_animations(&self) -> Animations {
self.0.lock().await.config.builtin_anims
if let Ok(config) = self.0.config.try_lock() {
return config.builtin_anims;
}
Animations::default()
}
/// Set which builtin animation is used for each stage
#[zbus(property)]
async fn set_builtin_animations(&self, settings: Animations) {
self.0
.lock()
.await
.node
.write_bytes(&pkt_set_builtin_animations(
settings.boot,
settings.awake,
settings.sleep,
settings.shutdown,
))
.await
.map_err(|err| {
warn!("ctrl_anime::run_animation:callback {}", err);
})
.ok();
self.0
.lock()
.await
.node
.write_bytes(&pkt_set_enable_powersave_anim(true))
.await
.map_err(|err| {
warn!("ctrl_anime::run_animation:callback {}", err);
})
.ok();
self.0.lock().await.config.display_enabled = true;
self.0.lock().await.config.builtin_anims = settings;
self.0.lock().await.config.write();
let mut config = self.0.config.lock().await;
config.display_enabled = true;
config.builtin_anims = settings;
config.write();
}
#[zbus(property)]
async fn enable_display(&self) -> bool {
self.0.lock().await.config.display_enabled
if let Ok(config) = self.0.config.try_lock() {
return config.display_enabled;
}
false
}
/// Set whether the AniMe is enabled at all
#[zbus(property)]
async fn set_enable_display(&self, enabled: bool) {
self.0
.lock()
.await
.node
.write_bytes(&pkt_set_enable_display(enabled))
.await
.map_err(|err| {
warn!("ctrl_anime::run_animation:callback {}", err);
})
.ok();
self.0.lock().await.config.display_enabled = enabled;
self.0.lock().await.config.write();
let mut config = self.0.config.lock().await;
config.display_enabled = enabled;
config.write();
}
#[zbus(property)]
async fn off_when_unplugged(&self) -> bool {
self.0.lock().await.config.off_when_unplugged
if let Ok(config) = self.0.config.try_lock() {
return config.off_when_unplugged;
}
false
}
/// Set if to turn the AniMe Matrix off when external power is unplugged
@@ -217,34 +227,40 @@ impl CtrlAnimeZbus {
let pow = manager.on_external_power().await.unwrap_or_default();
self.0
.lock()
.await
.node
.write_bytes(&pkt_set_enable_display(!pow && !enabled))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
})
.ok();
self.0.lock().await.config.off_when_unplugged = enabled;
self.0.lock().await.config.write();
let mut config = self.0.config.lock().await;
config.off_when_unplugged = enabled;
config.write();
}
#[zbus(property)]
async fn off_when_suspended(&self) -> bool {
self.0.lock().await.config.off_when_suspended
if let Ok(config) = self.0.config.try_lock() {
return config.off_when_suspended;
}
false
}
/// Set if to turn the AniMe Matrix off when the laptop is suspended
#[zbus(property)]
async fn set_off_when_suspended(&self, enabled: bool) {
self.0.lock().await.config.off_when_suspended = enabled;
self.0.lock().await.config.write();
let mut config = self.0.config.lock().await;
config.off_when_suspended = enabled;
config.write();
}
#[zbus(property)]
async fn off_when_lid_closed(&self) -> bool {
self.0.lock().await.config.off_when_lid_closed
if let Ok(config) = self.0.config.try_lock() {
return config.off_when_lid_closed;
}
false
}
/// Set if to turn the AniMe Matrix off when the lid is closed
@@ -254,50 +270,40 @@ impl CtrlAnimeZbus {
let lid = manager.lid_closed().await.unwrap_or_default();
self.0
.lock()
.await
.node
.write_bytes(&pkt_set_enable_display(lid && !enabled))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
})
.ok();
self.0.lock().await.config.off_when_lid_closed = enabled;
self.0.lock().await.config.write();
let mut config = self.0.config.lock().await;
config.off_when_lid_closed = enabled;
config.write();
}
/// The main loop is the base system set action if the user isn't running
/// the user daemon
async fn run_main_loop(&self, start: bool) {
if start {
self.0
.lock()
.await
.thread_exit
.store(true, Ordering::SeqCst);
CtrlAnime::run_thread(
self.0.clone(),
self.0.lock().await.cache.system.clone(),
false,
)
.await;
self.0.thread_exit.store(true, Ordering::SeqCst);
self.0.run_thread(self.0.cache.system.clone(), false).await;
}
}
/// Get the device state as stored by asusd
// #[zbus(property)]
async fn device_state(&self) -> DeviceState {
DeviceState::from(&self.0.lock().await.config)
DeviceState::from(&*self.0.config.lock().await)
}
}
impl crate::CtrlTask for CtrlAnimeZbus {
impl crate::CtrlTask for AniMeZbus {
fn zbus_path() -> &'static str {
ANIME_ZBUS_PATH
"ANIME_ZBUS_PATH"
}
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> {
let inner1 = self.0.clone();
let inner2 = self.0.clone();
let inner3 = self.0.clone();
@@ -307,21 +313,15 @@ impl crate::CtrlTask for CtrlAnimeZbus {
// on_sleep
let inner = inner1.clone();
async move {
let config = inner.lock().await.config.clone();
let config = inner.config.lock().await.clone();
if config.display_enabled {
inner
.lock()
.await
.thread_exit
.store(true, Ordering::Release); // ensure clean slate
inner.thread_exit.store(true, Ordering::Release); // ensure clean slate
inner
.lock()
.await
.node
.write_bytes(&pkt_set_enable_display(
!(sleeping && config.off_when_suspended),
))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_suspended {}", err);
})
@@ -329,12 +329,10 @@ impl crate::CtrlTask for CtrlAnimeZbus {
if config.builtin_anims_enabled {
inner
.lock()
.await
.node
.write_bytes(&pkt_set_enable_powersave_anim(
!(sleeping && config.off_when_suspended),
))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_suspended {}", err);
})
@@ -342,18 +340,11 @@ impl crate::CtrlTask for CtrlAnimeZbus {
} else if !sleeping && !config.builtin_anims_enabled {
// Run custom wake animation
inner
.lock()
.await
.node
.write_bytes(&pkt_set_enable_powersave_anim(false))
.await
.ok(); // ensure builtins are disabled
CtrlAnime::run_thread(
inner.clone(),
inner.lock().await.cache.wake.clone(),
true,
)
.await;
inner.run_thread(inner.cache.wake.clone(), true).await;
}
}
}
@@ -362,26 +353,16 @@ impl crate::CtrlTask for CtrlAnimeZbus {
// on_shutdown
let inner = inner2.clone();
async move {
let AnimeConfig {
let AniMeConfig {
display_enabled,
builtin_anims_enabled,
..
} = inner.lock().await.config;
} = *inner.config.lock().await;
if display_enabled && !builtin_anims_enabled {
if shutting_down {
CtrlAnime::run_thread(
inner.clone(),
inner.lock().await.cache.shutdown.clone(),
true,
)
.await;
inner.run_thread(inner.cache.shutdown.clone(), true).await;
} else {
CtrlAnime::run_thread(
inner.clone(),
inner.lock().await.cache.boot.clone(),
true,
)
.await;
inner.run_thread(inner.cache.boot.clone(), true).await;
}
}
}
@@ -390,28 +371,24 @@ impl crate::CtrlTask for CtrlAnimeZbus {
let inner = inner3.clone();
// on lid change
async move {
let AnimeConfig {
let AniMeConfig {
off_when_lid_closed,
builtin_anims_enabled,
..
} = inner.lock().await.config;
} = *inner.config.lock().await;
if off_when_lid_closed {
if builtin_anims_enabled {
inner
.lock()
.await
.node
.write_bytes(&pkt_set_enable_powersave_anim(!lid_closed))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_suspended {}", err);
})
.ok();
}
inner
.lock()
.await
.node
.write_bytes(&pkt_set_enable_display(!lid_closed))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
})
@@ -423,39 +400,33 @@ impl crate::CtrlTask for CtrlAnimeZbus {
let inner = inner4.clone();
// on power change
async move {
let AnimeConfig {
let AniMeConfig {
off_when_unplugged,
builtin_anims_enabled,
brightness_on_battery,
..
} = inner.lock().await.config;
} = *inner.config.lock().await;
if off_when_unplugged {
if builtin_anims_enabled {
inner
.lock()
.await
.node
.write_bytes(&pkt_set_enable_powersave_anim(power_plugged))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_suspended {}", err);
})
.ok();
}
inner
.lock()
.await
.node
.write_bytes(&pkt_set_enable_display(power_plugged))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_unplugged {}", err);
})
.ok();
} else {
inner
.lock()
.await
.node
.write_bytes(&pkt_set_brightness(brightness_on_battery))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::off_when_unplugged {}", err);
})
@@ -470,51 +441,54 @@ impl crate::CtrlTask for CtrlAnimeZbus {
}
}
impl crate::Reloadable for CtrlAnimeZbus {
impl crate::Reloadable for AniMeZbus {
async fn reload(&mut self) -> Result<(), RogError> {
if let Some(lock) = self.0.try_lock() {
let anim = &lock.config.builtin_anims;
if let Ok(config) = self.0.config.try_lock() {
let anim = &config.builtin_anims;
// Set builtins
if lock.config.builtin_anims_enabled {
lock.node.write_bytes(&pkt_set_builtin_animations(
anim.boot,
anim.awake,
anim.sleep,
anim.shutdown,
))?;
if config.builtin_anims_enabled {
self.0
.write_bytes(&pkt_set_builtin_animations(
anim.boot,
anim.awake,
anim.sleep,
anim.shutdown,
))
.await?;
}
// Builtins enabled or na?
lock.node.set_builtins_enabled(
lock.config.builtin_anims_enabled,
lock.config.display_brightness,
)?;
self.0
.set_builtins_enabled(config.builtin_anims_enabled, config.display_brightness)
.await?;
let manager = get_logind_manager().await;
let lid_closed = manager.lid_closed().await.unwrap_or_default();
let power_plugged = manager.on_external_power().await.unwrap_or_default();
let turn_off = (lid_closed && lock.config.off_when_lid_closed)
|| (!power_plugged && lock.config.off_when_unplugged);
lock.node
let turn_off = (lid_closed && config.off_when_lid_closed)
|| (!power_plugged && config.off_when_unplugged);
self.0
.write_bytes(&pkt_set_enable_display(!turn_off))
.await
.map_err(|err| {
warn!("create_sys_event_tasks::reload {}", err);
})
.ok();
if turn_off || !lock.config.display_enabled {
lock.node.write_bytes(&pkt_set_enable_display(false))?;
if turn_off || !config.display_enabled {
self.0.write_bytes(&pkt_set_enable_display(false)).await?;
// early return so we don't run animation thread
return Ok(());
}
if !lock.config.builtin_anims_enabled && !lock.cache.boot.is_empty() {
lock.node
if !config.builtin_anims_enabled && !self.0.cache.boot.is_empty() {
self.0
.write_bytes(&pkt_set_enable_powersave_anim(false))
.await
.ok();
let action = lock.cache.boot.clone();
CtrlAnime::run_thread(self.0.clone(), action, true).await;
let action = self.0.cache.boot.clone();
self.0.run_thread(action, true).await;
}
}
Ok(())
@@ -14,6 +14,10 @@ use crate::error::RogError;
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
// #[serde(default)]
pub struct AuraConfig {
#[serde(skip)]
pub led_type: AuraDeviceType,
#[serde(skip)]
pub support_data: LedSupportData,
pub config_name: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub ally_fix: Option<bool>,
@@ -24,6 +28,8 @@ pub struct AuraConfig {
pub multizone: Option<BTreeMap<AuraModeNum, Vec<AuraEffect>>>,
pub multizone_on: bool,
pub enabled: LaptopAuraPower,
#[serde(skip)]
pub per_key_mode_active: bool,
}
impl StdConfig for AuraConfig {
@@ -58,6 +64,8 @@ impl AuraConfig {
let support_data = LedSupportData::get_data(prod_id);
let enabled = LaptopAuraPower::new(device_type, &support_data);
let mut config = AuraConfig {
led_type: device_type,
support_data,
config_name: format!("aura_{prod_id}.ron"),
ally_fix: None,
brightness: LedBrightness::Med,
@@ -66,17 +74,18 @@ impl AuraConfig {
multizone: None,
multizone_on: false,
enabled,
per_key_mode_active: false,
};
for n in &support_data.basic_modes {
for n in &config.support_data.basic_modes {
debug!("creating default for {n}");
config
.builtins
.insert(*n, AuraEffect::default_with_mode(*n));
if !support_data.basic_zones.is_empty() {
if !config.support_data.basic_zones.is_empty() {
let mut default = vec![];
for (i, tmp) in support_data.basic_zones.iter().enumerate() {
for (i, tmp) in config.support_data.basic_zones.iter().enumerate() {
default.push(AuraEffect {
mode: *n,
zone: *tmp,
@@ -138,12 +147,9 @@ impl AuraConfig {
/// Create a default for the `current_mode` if multizone and no config
/// exists.
pub(super) fn create_multizone_default(
&mut self,
supported_data: &LedSupportData,
) -> Result<(), RogError> {
pub fn create_multizone_default(&mut self) -> Result<(), RogError> {
let mut default = vec![];
for (i, tmp) in supported_data.basic_zones.iter().enumerate() {
for (i, tmp) in self.support_data.basic_zones.iter().enumerate() {
default.push(AuraEffect {
mode: self.current_mode,
zone: *tmp,
@@ -166,6 +172,60 @@ impl AuraConfig {
}
Ok(())
}
/// Reload the config from disk then verify and update it if required.
/// Always rewrites the file to disk.
pub fn load_and_update_config(prod_id: &str) -> AuraConfig {
// New loads data from the DB also
let mut config_init = AuraConfig::new(prod_id);
// config_init.set_filename(prod_id);
let mut config_loaded = config_init.clone().load();
// update the initialised data with what we loaded from disk
for mode_init in &mut config_init.builtins {
// update init values from loaded values if they exist
if let Some(loaded) = config_loaded.builtins.get(mode_init.0) {
*mode_init.1 = loaded.clone();
}
}
// Then replace just incase the initialised data contains new modes added
config_loaded.builtins = config_init.builtins;
config_loaded.support_data = config_init.support_data;
config_loaded.led_type = config_init.led_type;
config_loaded.ally_fix = config_init.ally_fix;
for enabled_init in &mut config_init.enabled.states {
for enabled in &mut config_loaded.enabled.states {
if enabled.zone == enabled_init.zone {
*enabled_init = *enabled;
break;
}
}
}
config_loaded.enabled = config_init.enabled;
if let (Some(mut multizone_init), Some(multizone_loaded)) =
(config_init.multizone, config_loaded.multizone.as_mut())
{
for mode in multizone_init.iter_mut() {
// update init values from loaded values if they exist
if let Some(loaded) = multizone_loaded.get(mode.0) {
let mut new_set = Vec::new();
let data = LedSupportData::get_data(prod_id);
// only reuse a zone mode if the mode is supported
for mode in loaded {
if data.basic_modes.contains(&mode.mode) {
new_set.push(mode.clone());
}
}
*mode.1 = new_set;
}
}
*multizone_loaded = multizone_init;
}
config_loaded.write();
config_loaded
}
}
#[cfg(test)]
+219
View File
@@ -0,0 +1,219 @@
use std::sync::Arc;
use config::AuraConfig;
use config_traits::StdConfig;
use log::info;
use rog_aura::keyboard::{AuraLaptopUsbPackets, LedUsbPackets};
use rog_aura::usb::{AURA_LAPTOP_LED_APPLY, AURA_LAPTOP_LED_SET};
use rog_aura::{AuraDeviceType, AuraEffect, LedBrightness, PowerZones, AURA_LAPTOP_LED_MSG_LEN};
use rog_platform::hid_raw::HidRaw;
use rog_platform::keyboard_led::KeyboardBacklight;
use tokio::sync::{Mutex, MutexGuard};
use crate::error::RogError;
pub mod config;
pub mod trait_impls;
#[derive(Debug, Clone)]
pub struct Aura {
pub hid: Option<Arc<Mutex<HidRaw>>>,
pub backlight: Option<Arc<Mutex<KeyboardBacklight>>>,
pub config: Arc<Mutex<AuraConfig>>,
}
impl Aura {
/// Initialise the device if required.
pub async fn do_initialization(&self) -> Result<(), RogError> {
Ok(())
}
pub async fn lock_config(&self) -> MutexGuard<AuraConfig> {
self.config.lock().await
}
/// Will lock the internal config and update. If anything else has locked
/// this in scope then a deadlock can occur.
pub async fn update_config(&self) -> Result<(), RogError> {
let mut config = self.config.lock().await;
let bright = if let Some(bl) = self.backlight.as_ref() {
bl.lock().await.get_brightness().unwrap_or_default()
} else {
config.brightness.into()
};
config.read();
config.brightness = bright.into();
config.write();
Ok(())
}
pub async fn write_current_config_mode(&self, config: &mut AuraConfig) -> Result<(), RogError> {
if config.multizone_on {
let mode = config.current_mode;
let mut create = false;
// There is no multizone config for this mode so create one here
// using the colours of rainbow if it exists, or first available
// mode, or random
if config.multizone.is_none() {
create = true;
} else if let Some(multizones) = config.multizone.as_ref() {
if !multizones.contains_key(&mode) {
create = true;
}
}
if create {
info!("No user-set config for zone founding, attempting a default");
config.create_multizone_default()?;
}
if let Some(multizones) = config.multizone.as_mut() {
if let Some(set) = multizones.get(&mode) {
for mode in set.clone() {
self.write_effect_and_apply(config.led_type, &mode).await?;
}
}
}
} else {
let mode = config.current_mode;
if let Some(effect) = config.builtins.get(&mode).cloned() {
self.write_effect_and_apply(config.led_type, &effect)
.await?;
}
}
Ok(())
}
/// Write the AuraEffect to the device. Will lock `backlight` or `hid`.
///
/// If per-key or software-mode is active it must be marked as disabled in
/// config.
pub async fn write_effect_and_apply(
&self,
dev_type: AuraDeviceType,
mode: &AuraEffect,
) -> Result<(), RogError> {
if matches!(dev_type, AuraDeviceType::LaptopKeyboardTuf) {
if let Some(platform) = &self.backlight {
let buf = [
1,
mode.mode as u8,
mode.colour1.r,
mode.colour1.g,
mode.colour1.b,
mode.speed as u8,
];
platform.lock().await.set_kbd_rgb_mode(&buf)?;
}
} else if let Some(hid_raw) = &self.hid {
let bytes: [u8; AURA_LAPTOP_LED_MSG_LEN] = mode.into();
let hid_raw = hid_raw.lock().await;
hid_raw.write_bytes(&bytes)?;
hid_raw.write_bytes(&AURA_LAPTOP_LED_SET)?;
// Changes won't persist unless apply is set
hid_raw.write_bytes(&AURA_LAPTOP_LED_APPLY)?;
} else {
return Err(RogError::NoAuraKeyboard);
}
Ok(())
}
pub async fn set_brightness(&self, value: u8) -> Result<(), RogError> {
if let Some(backlight) = &self.backlight {
backlight.lock().await.set_brightness(value)?;
return Ok(());
}
Err(RogError::MissingFunction(
"No LED backlight control available".to_string(),
))
}
/// Set combination state for boot animation/sleep animation/all leds/keys
/// leds/side leds LED active
pub async fn set_power_states(&self, config: &AuraConfig) -> Result<(), RogError> {
if matches!(config.led_type, rog_aura::AuraDeviceType::LaptopKeyboardTuf) {
if let Some(backlight) = &self.backlight {
// TODO: tuf bool array
let buf = config.enabled.to_bytes(config.led_type);
backlight.lock().await.set_kbd_rgb_state(&buf)?;
}
} else if let Some(hid_raw) = &self.hid {
let hid_raw = hid_raw.lock().await;
if let Some(p) = config.enabled.states.first() {
if p.zone == PowerZones::Ally {
let msg = [0x5d, 0xd1, 0x09, 0x01, p.new_to_byte() as u8, 0x0, 0x0];
hid_raw.write_bytes(&msg)?;
return Ok(());
}
}
let bytes = config.enabled.to_bytes(config.led_type);
let msg = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3]];
hid_raw.write_bytes(&msg)?;
}
Ok(())
}
/// Write an effect block. This is for per-key, but can be repurposed to
/// write the raw factory mode packets - when doing this it is expected that
/// only the first `Vec` (`effect[0]`) is valid.
pub async fn write_effect_block(
&self,
config: &mut AuraConfig,
effect: &AuraLaptopUsbPackets,
) -> Result<(), RogError> {
if config.brightness == LedBrightness::Off {
config.brightness = LedBrightness::Med;
config.write();
}
let pkt_type = effect[0][1];
const PER_KEY_TYPE: u8 = 0xbc;
if let Some(hid_raw) = &self.hid {
let hid_raw = hid_raw.lock().await;
if pkt_type != PER_KEY_TYPE {
config.per_key_mode_active = false;
hid_raw.write_bytes(&effect[0])?;
hid_raw.write_bytes(&AURA_LAPTOP_LED_SET)?;
// hid_raw.write_bytes(&LED_APPLY)?;
} else {
if !config.per_key_mode_active {
let init = LedUsbPackets::get_init_msg();
hid_raw.write_bytes(&init)?;
config.per_key_mode_active = true;
}
for row in effect.iter() {
hid_raw.write_bytes(row)?;
}
}
} else if matches!(config.led_type, rog_aura::AuraDeviceType::LaptopKeyboardTuf) {
if let Some(tuf) = &self.backlight {
for row in effect.iter() {
let r = row[9];
let g = row[10];
let b = row[11];
tuf.lock().await.set_kbd_rgb_mode(&[0, 0, r, g, b, 0])?;
}
}
}
Ok(())
}
pub async fn fix_ally_power(&mut self) -> Result<(), RogError> {
if self.config.lock().await.led_type == AuraDeviceType::Ally {
if let Some(hid_raw) = &self.hid {
let mut config = self.config.lock().await;
if config.ally_fix.is_none() {
let msg = [0x5d, 0xbd, 0x01, 0xff, 0xff, 0xff, 0xff];
hid_raw.lock().await.write_bytes(&msg)?;
info!("Reset Ally power settings to base");
config.ally_fix = Some(true);
}
config.write();
}
}
Ok(())
}
}
+343
View File
@@ -0,0 +1,343 @@
use std::collections::BTreeMap;
use config_traits::StdConfig;
use log::{debug, error, info, warn};
use rog_aura::keyboard::{AuraLaptopUsbPackets, LaptopAuraPower};
use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones};
use zbus::fdo::Error as ZbErr;
use zbus::object_server::SignalEmitter;
use zbus::zvariant::OwnedObjectPath;
use zbus::{interface, Connection};
use super::Aura;
use crate::error::RogError;
use crate::{CtrlTask, Reloadable};
pub const AURA_ZBUS_NAME: &str = "Aura";
pub const AURA_ZBUS_PATH: &str = "/org/asuslinux";
#[derive(Clone)]
pub struct AuraZbus(Aura);
impl AuraZbus {
pub fn new(aura: Aura) -> Self {
Self(aura)
}
pub async fn start_tasks(
mut self,
connection: &Connection,
// _signal_ctx: SignalEmitter<'static>,
path: OwnedObjectPath,
) -> Result<(), RogError> {
// let task = zbus.clone();
// let signal_ctx = signal_ctx.clone();
self.reload()
.await
.unwrap_or_else(|err| warn!("Controller error: {}", err));
connection
.object_server()
.at(path.clone(), self)
.await
.map_err(|e| error!("Couldn't add server at path: {path}, {e:?}"))
.ok();
// TODO: skip this until we keep handles to tasks so they can be killed
// task.create_tasks(signal_ctx).await
Ok(())
}
}
/// The main interface for changing, reading, or notfying
///
/// LED commands are split between Brightness, Modes, Per-Key
#[interface(name = "org.asuslinux.Aura")]
impl AuraZbus {
/// Return the device type for this Aura keyboard
#[zbus(property)]
async fn device_type(&self) -> AuraDeviceType {
self.0.config.lock().await.led_type
}
/// Return the current LED brightness
#[zbus(property)]
async fn brightness(&self) -> Result<LedBrightness, ZbErr> {
if let Some(bl) = self.0.backlight.as_ref() {
return Ok(bl.lock().await.get_brightness().map(|n| n.into())?);
}
Err(ZbErr::Failed("No sysfs brightness control".to_string()))
}
/// Set the keyboard brightness level (0-3)
#[zbus(property)]
async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> {
if let Some(bl) = self.0.backlight.as_ref() {
return Ok(bl.lock().await.set_brightness(brightness.into())?);
}
Err(ZbErr::Failed("No sysfs brightness control".to_string()))
}
/// Total levels of brightness available
#[zbus(property)]
async fn supported_brightness(&self) -> Vec<LedBrightness> {
vec![
LedBrightness::Off,
LedBrightness::Low,
LedBrightness::Med,
LedBrightness::High,
]
}
/// The total available modes
#[zbus(property)]
async fn supported_basic_modes(&self) -> Result<Vec<AuraModeNum>, ZbErr> {
let config = self.0.config.lock().await;
Ok(config.builtins.keys().cloned().collect())
}
#[zbus(property)]
async fn supported_basic_zones(&self) -> Result<Vec<AuraZone>, ZbErr> {
let config = self.0.config.lock().await;
Ok(config.support_data.basic_zones.clone())
}
#[zbus(property)]
async fn supported_power_zones(&self) -> Result<Vec<PowerZones>, ZbErr> {
let config = self.0.config.lock().await;
Ok(config.support_data.power_zones.clone())
}
/// The current mode data
#[zbus(property)]
async fn led_mode(&self) -> Result<AuraModeNum, ZbErr> {
// entirely possible to deadlock here, so use try instead of lock()
// let ctrl = self.0.lock().await;
// Ok(config.current_mode)
if let Ok(config) = self.0.config.try_lock() {
Ok(config.current_mode)
} else {
Err(ZbErr::Failed("Aura control couldn't lock self".to_string()))
}
}
/// Set an Aura effect if the effect mode or zone is supported.
///
/// On success the aura config file is read to refresh cached values, then
/// the effect is stored and config written to disk.
#[zbus(property)]
async fn set_led_mode(&mut self, num: AuraModeNum) -> Result<(), ZbErr> {
let mut config = self.0.config.lock().await;
config.current_mode = num;
self.0.write_current_config_mode(&mut config).await?;
if config.brightness == LedBrightness::Off {
config.brightness = LedBrightness::Med;
}
self.0.set_brightness(config.brightness.into()).await?;
config.write();
Ok(())
}
/// The current mode data
#[zbus(property)]
async fn led_mode_data(&self) -> Result<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.builtins.get(&mode) {
Some(effect) => Ok(effect.clone()),
None => Err(ZbErr::Failed("Could not get the current effect".into())),
}
} else {
Err(ZbErr::Failed("Aura control couldn't lock self".to_string()))
}
}
/// Set an Aura effect if the effect mode or zone is supported.
///
/// On success the aura config file is read to refresh cached values, then
/// the effect is stored and config written to disk.
#[zbus(property)]
async fn set_led_mode_data(&mut self, effect: AuraEffect) -> Result<(), ZbErr> {
let mut config = self.0.config.lock().await;
if !config.support_data.basic_modes.contains(&effect.mode)
|| effect.zone != AuraZone::None
&& !config.support_data.basic_zones.contains(&effect.zone)
{
return Err(ZbErr::NotSupported(format!(
"The Aura effect is not supported: {effect:?}"
)));
}
self.0
.write_effect_and_apply(config.led_type, &effect)
.await?;
if config.brightness == LedBrightness::Off {
config.brightness = LedBrightness::Med;
}
self.0.set_brightness(config.brightness.into()).await?;
config.set_builtin(effect);
config.write();
Ok(())
}
/// Get the data set for every mode available
async fn all_mode_data(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
let config = self.0.config.lock().await;
config.builtins.clone()
}
// As property doesn't work for AuraPowerDev (complexity of serialization?)
#[zbus(property)]
async fn led_power(&self) -> LaptopAuraPower {
let config = self.0.config.lock().await;
config.enabled.clone()
}
/// Set a variety of states, input is array of enum.
/// `enabled` sets if the sent array should be disabled or enabled
///
/// For Modern ROG devices the "enabled" flag is ignored.
#[zbus(property)]
async fn set_led_power(&mut self, options: LaptopAuraPower) -> Result<(), ZbErr> {
let mut config = self.0.config.lock().await;
for opt in options.states {
let zone = opt.zone;
for config in config.enabled.states.iter_mut() {
if config.zone == zone {
*config = opt;
}
}
}
config.write();
Ok(self.0.set_power_states(&config).await.map_err(|e| {
warn!("{}", e);
e
})?)
}
/// On machine that have some form of either per-key keyboard or per-zone
/// this can be used to write custom effects over dbus. The input is a
/// nested `Vec<Vec<8>>` where `Vec<u8>` is a raw USB packet
async fn direct_addressing_raw(&self, data: AuraLaptopUsbPackets) -> Result<(), ZbErr> {
let mut config = self.0.config.lock().await;
self.0.write_effect_block(&mut config, &data).await?;
Ok(())
}
}
impl CtrlTask for AuraZbus {
fn zbus_path() -> &'static str {
"/org/asuslinux"
}
async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> {
let inner1 = self.0.clone();
let inner3 = self.0.clone();
self.create_sys_event_tasks(
move |sleeping| {
let inner1 = inner1.clone();
// unwrap as we want to bomb out of the task
async move {
if !sleeping {
info!("CtrlKbdLedTask reloading brightness and modes");
if let Some(backlight) = &inner1.backlight {
backlight
.lock()
.await
.set_brightness(inner1.config.lock().await.brightness.into())
.map_err(|e| {
error!("CtrlKbdLedTask: {e}");
e
})
.unwrap();
}
let mut config = inner1.config.lock().await;
inner1
.write_current_config_mode(&mut config)
.await
.map_err(|e| {
error!("CtrlKbdLedTask: {e}");
e
})
.unwrap();
} else if sleeping {
inner1
.update_config()
.await
.map_err(|e| {
error!("CtrlKbdLedTask: {e}");
e
})
.unwrap();
}
}
},
move |_shutting_down| {
let inner3 = inner3.clone();
async move {
info!("CtrlKbdLedTask reloading brightness and modes");
if let Some(backlight) = &inner3.backlight {
// unwrap as we want to bomb out of the task
backlight
.lock()
.await
.set_brightness(inner3.config.lock().await.brightness.into())
.map_err(|e| {
error!("CtrlKbdLedTask: {e}");
e
})
.unwrap();
}
}
},
move |_lid_closed| {
// on lid change
async move {}
},
move |_power_plugged| {
// power change
async move {}
},
)
.await;
// let ctrl2 = self.0.clone();
// let ctrl = self.0.lock().await;
// if ctrl.led_node.has_brightness_control() {
// let watch = ctrl.led_node.monitor_brightness()?;
// tokio::spawn(async move {
// let mut buffer = [0; 32];
// watch
// .into_event_stream(&mut buffer)
// .unwrap()
// .for_each(|_| async {
// if let Some(lock) = ctrl2.try_lock() {
// load_save(true, lock).unwrap(); // unwrap as we want
// // to
// // bomb out of the
// // task
// }
// })
// .await;
// });
// }
Ok(())
}
}
impl Reloadable for AuraZbus {
async fn reload(&mut self) -> Result<(), RogError> {
self.0.fix_ally_power().await?;
debug!("reloading keyboard mode");
let mut config = self.0.lock_config().await;
self.0.write_current_config_mode(&mut config).await?;
debug!("reloading power states");
self.0
.set_power_states(&config)
.await
.map_err(|err| warn!("{err}"))
.ok();
Ok(())
}
}
+542
View File
@@ -0,0 +1,542 @@
// Plan:
// - Manager has udev monitor on USB looking for ROG devices
// - If a device is found, add it to watch
// - Add it to Zbus server
// - If udev sees device removed then remove the zbus path
use std::sync::Arc;
use dmi_id::DMIID;
use futures_lite::future::block_on;
use log::{debug, error, info, warn};
use mio::{Events, Interest, Poll, Token};
use rog_platform::error::PlatformError;
use rog_platform::hid_raw::HidRaw;
use tokio::sync::Mutex;
use udev::{Device, MonitorBuilder};
use zbus::zvariant::{ObjectPath, OwnedObjectPath};
use zbus::Connection;
use crate::aura_anime::trait_impls::AniMeZbus;
use crate::aura_laptop::trait_impls::AuraZbus;
use crate::aura_scsi::trait_impls::ScsiZbus;
use crate::aura_slash::trait_impls::SlashZbus;
use crate::aura_types::DeviceHandle;
use crate::error::RogError;
pub const ASUS_ZBUS_PATH: &str = "/org/asuslinux";
/// Returns only the Device details concatenated in a form usable for
/// adding/appending to a filename
pub fn filename_partial(parent: &Device) -> Option<OwnedObjectPath> {
if let Some(id_product) = parent.attribute_value("idProduct") {
let id_product = id_product.to_string_lossy();
let mut path = if let Some(devnum) = parent.attribute_value("devnum") {
let devnum = devnum.to_string_lossy();
if let Some(devpath) = parent.attribute_value("devpath") {
let devpath = devpath.to_string_lossy();
format!("{id_product}_{devnum}_{devpath}")
} else {
format!("{id_product}_{devnum}")
}
} else {
format!("{id_product}")
};
if path.contains('.') {
warn!("dbus path for {id_product} contains `.`, removing");
path.replace('.', "").clone_into(&mut path);
}
return Some(ObjectPath::from_str_unchecked(&path).into());
}
None
}
fn dbus_path_for_dev(parent: &Device) -> Option<OwnedObjectPath> {
if let Some(filename) = filename_partial(parent) {
return Some(
ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/{filename}")).into(),
);
}
None
}
fn dbus_path_for_tuf() -> OwnedObjectPath {
ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/tuf")).into()
}
fn dbus_path_for_slash() -> OwnedObjectPath {
ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/slash")).into()
}
fn dbus_path_for_anime() -> OwnedObjectPath {
ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/anime")).into()
}
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
// - LED class
// - RGB modes (how, attribute?)
// - power features (how, attribute?)
// - what about per-key stuff?
// - how would the AniMe be exposed? Just a series of LEDs?
/// A device.
///
/// Each controller within should track its dbus path so it can be removed if
/// required.
pub struct AsusDevice {
device: DeviceHandle,
dbus_path: OwnedObjectPath,
}
pub struct DeviceManager {
_dbus_connection: Connection,
}
impl DeviceManager {
async fn init_hid_devices(
connection: &Connection,
device: Device,
) -> Result<Vec<AsusDevice>, RogError> {
let mut devices = Vec::new();
if let Some(usb_device) = device.parent_with_subsystem_devtype("usb", "usb_device")? {
if let Some(usb_id) = usb_device.attribute_value("idProduct") {
if let Some(vendor_id) = usb_device.attribute_value("idVendor") {
if vendor_id != "0b05" {
debug!("Not ASUS vendor ID");
return Ok(devices);
}
// Almost all devices are identified by the productId.
// So let's see what we have and:
// 1. Generate an interface path
// 2. Create the device
// Use the top-level endpoint, not the parent
if let Ok(hidraw) = HidRaw::from_device(device) {
debug!("Testing device {usb_id:?}");
let dev = Arc::new(Mutex::new(hidraw));
// SLASH DEVICE
if let Ok(dev_type) = DeviceHandle::new_slash_hid(
dev.clone(),
usb_id.to_str().unwrap_or_default(),
)
.await
{
if let DeviceHandle::Slash(slash) = dev_type.clone() {
let path =
dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_slash());
let ctrl = SlashZbus::new(slash);
ctrl.start_tasks(connection, path.clone()).await.unwrap();
devices.push(AsusDevice {
device: dev_type,
dbus_path: path,
});
}
}
// ANIME MATRIX DEVICE
if let Ok(dev_type) = DeviceHandle::maybe_anime_hid(
dev.clone(),
usb_id.to_str().unwrap_or_default(),
)
.await
{
if let DeviceHandle::AniMe(anime) = dev_type.clone() {
let path =
dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_anime());
let ctrl = AniMeZbus::new(anime);
ctrl.start_tasks(connection, path.clone()).await.unwrap();
devices.push(AsusDevice {
device: dev_type,
dbus_path: path,
});
}
}
// AURA LAPTOP DEVICE
if let Ok(dev_type) = DeviceHandle::maybe_laptop_aura(
Some(dev),
usb_id.to_str().unwrap_or_default(),
)
.await
{
if let DeviceHandle::Aura(aura) = dev_type.clone() {
let path =
dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_tuf());
let ctrl = AuraZbus::new(aura);
ctrl.start_tasks(connection, path.clone()).await.unwrap();
devices.push(AsusDevice {
device: dev_type,
dbus_path: path,
});
}
}
}
}
}
}
Ok(devices)
}
/// To be called on daemon startup
async fn init_all_hid(connection: &Connection) -> Result<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("hidraw").map_err(|err| {
warn!("{}", err);
PlatformError::Udev("match_subsystem failed".into(), err)
})?;
for device in enumerator
.scan_devices()
.map_err(|e| PlatformError::IoPath("enumerator".to_owned(), e))?
{
devices.append(&mut Self::init_hid_devices(connection, device).await?);
}
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)
}
pub async fn find_all_devices(connection: &Connection) -> Vec<AsusDevice> {
let mut devices: Vec<AsusDevice> = Vec::new();
// HID first, always
if let Ok(devs) = &mut Self::init_all_hid(connection).await {
devices.append(devs);
}
// USB after, need to check if HID picked something up and if so, skip it
let mut do_anime = true;
let mut do_slash = true;
let mut do_kb_backlight = true;
for dev in devices.iter() {
if matches!(dev.device, DeviceHandle::Slash(_)) {
do_slash = false;
}
if matches!(dev.device, DeviceHandle::AniMe(_)) {
do_anime = false;
}
if matches!(dev.device, DeviceHandle::Aura(_) | DeviceHandle::OldAura(_)) {
do_kb_backlight = false;
}
}
if do_slash {
if let Ok(dev_type) = DeviceHandle::new_slash_usb().await {
if let DeviceHandle::Slash(slash) = dev_type.clone() {
let path = dbus_path_for_slash();
let ctrl = SlashZbus::new(slash);
ctrl.start_tasks(connection, path.clone()).await.unwrap();
devices.push(AsusDevice {
device: dev_type,
dbus_path: path,
});
}
} else {
info!("Tested device was not Slash");
}
}
if do_anime {
if let Ok(dev_type) = DeviceHandle::maybe_anime_usb().await {
// TODO: this is copy/pasted
if let DeviceHandle::AniMe(anime) = dev_type.clone() {
let path = dbus_path_for_anime();
let ctrl = AniMeZbus::new(anime);
ctrl.start_tasks(connection, path.clone()).await.unwrap();
devices.push(AsusDevice {
device: dev_type,
dbus_path: path,
});
}
} else {
info!("Tested device was not AniMe Matrix");
}
}
if do_kb_backlight {
// TUF AURA LAPTOP DEVICE
// product_name = ASUS TUF Gaming F15 FX507ZE_FX507ZE
// product_family = ASUS TUF Gaming F15
let product_name = DMIID::new().unwrap_or_default().product_name;
let product_family = DMIID::new().unwrap_or_default().product_family;
info!(
"No USB keyboard aura, system is {product_name}, try using sysfs backlight control"
);
if product_name.contains("TUF") || product_family.contains("TUF") {
info!("TUF laptop, try using sysfs backlight control");
if let Ok(dev_type) = DeviceHandle::maybe_laptop_aura(None, "tuf").await {
if let DeviceHandle::Aura(aura) = dev_type.clone() {
let path = dbus_path_for_tuf();
let ctrl = AuraZbus::new(aura);
ctrl.start_tasks(connection, path.clone()).await.unwrap();
devices.push(AsusDevice {
device: dev_type,
dbus_path: path,
});
}
}
}
}
if let Ok(devs) = &mut Self::init_all_scsi(connection).await {
devices.append(devs);
}
devices
}
pub async fn new(connection: Connection) -> Result<Self, RogError> {
let conn_copy = connection.clone();
let devices = Arc::new(Mutex::new(Self::find_all_devices(&conn_copy).await));
let manager = Self {
_dbus_connection: connection,
};
// TODO: The /sysfs/ LEDs don't cause events, so they need to be manually
// checked for and added
// detect all plugged in aura devices (eventually)
// only USB devices are detected for here
std::thread::spawn(move || {
let mut monitor = MonitorBuilder::new()?.listen()?;
let mut poll = Poll::new()?;
let mut events = Events::with_capacity(1024);
poll.registry()
.register(&mut monitor, Token(0), Interest::READABLE)?;
let rt = tokio::runtime::Runtime::new().expect("Unable to create Runtime");
let _enter = rt.enter();
loop {
if poll.poll(&mut events, None).is_err() {
continue;
}
for event in monitor.iter() {
let action = event
.action()
.unwrap_or_default()
.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")?
{
if action == "remove" {
if let Some(path) = dbus_path_for_dev(&parent) {
// Find the indexs of devices matching the path
let removals: Vec<usize> = 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::<AuraZbus, _>(&path)
.await?
}
DeviceHandle::Slash(_) => {
conn_copy
.object_server()
.remove::<SlashZbus, _>(&path)
.await?
}
DeviceHandle::AniMe(_) => {
conn_copy
.object_server()
.remove::<AniMeZbus, _>(&path)
.await?
}
DeviceHandle::Scsi(_) => {
conn_copy
.object_server()
.remove::<ScsiZbus, _>(&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
#[allow(unreachable_code)]
Ok::<(), RogError>(())
});
Ok(manager)
}
}
+114
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 {}
+45
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(())
}
}
+116
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()
}
}
@@ -1,5 +1,5 @@
use config_traits::{StdConfig, StdConfigLoad};
use rog_slash::{DeviceState, SlashMode};
use rog_slash::{DeviceState, SlashMode, SlashType};
use serde::{Deserialize, Serialize};
const CONFIG_FILE: &str = "slash.ron";
@@ -7,6 +7,8 @@ const CONFIG_FILE: &str = "slash.ron";
/// Config for base system actions for the anime display
#[derive(Deserialize, Serialize, Debug)]
pub struct SlashConfig {
#[serde(skip)]
pub slash_type: SlashType,
pub slash_enabled: bool,
pub slash_brightness: u8,
pub slash_interval: u8,
@@ -20,6 +22,7 @@ impl Default for SlashConfig {
slash_brightness: 255,
slash_interval: 0,
slash_mode: SlashMode::Bounce,
slash_type: SlashType::Unsupported,
}
}
}
+70
View File
@@ -0,0 +1,70 @@
use std::sync::Arc;
use config::SlashConfig;
use rog_platform::hid_raw::HidRaw;
use rog_platform::usb_raw::USBRaw;
use rog_slash::usb::{pkt_set_mode, pkt_set_options, pkts_for_init};
use rog_slash::SlashType;
use tokio::sync::{Mutex, MutexGuard};
use crate::error::RogError;
pub mod config;
pub mod trait_impls;
#[derive(Debug, Clone)]
pub struct Slash {
hid: Option<Arc<Mutex<HidRaw>>>,
usb: Option<Arc<Mutex<USBRaw>>>,
config: Arc<Mutex<SlashConfig>>,
}
impl Slash {
pub fn new(
hid: Option<Arc<Mutex<HidRaw>>>,
usb: Option<Arc<Mutex<USBRaw>>>,
config: Arc<Mutex<SlashConfig>>,
) -> Self {
Self { hid, usb, config }
}
pub async fn lock_config(&self) -> MutexGuard<SlashConfig> {
self.config.lock().await
}
pub async fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
if let Some(hid) = &self.hid {
hid.lock().await.write_bytes(message)?;
} else if let Some(usb) = &self.usb {
usb.lock().await.write_bytes(message)?;
}
Ok(())
}
/// Initialise the device if required. Locks the internal config so be wary
/// of deadlocks.
pub async fn do_initialization(&self) -> Result<(), RogError> {
// Don't try to initialise these models as the asus drivers already did
let config = self.config.lock().await;
if !matches!(config.slash_type, SlashType::GA605 | SlashType::GU605) {
for pkt in &pkts_for_init(config.slash_type) {
self.write_bytes(pkt).await?;
}
}
// Apply config upon initialization
let option_packets = pkt_set_options(
config.slash_type,
config.slash_enabled,
config.slash_brightness,
config.slash_interval,
);
self.write_bytes(&option_packets).await?;
let mode_packets = pkt_set_mode(config.slash_type, config.slash_mode);
// self.node.write_bytes(&mode_packets[0])?;
self.write_bytes(&mode_packets[1]).await?;
Ok(())
}
}
+190
View File
@@ -0,0 +1,190 @@
use config_traits::StdConfig;
use log::{debug, error, warn};
use rog_slash::usb::{pkt_save, pkt_set_mode, pkt_set_options};
use rog_slash::{DeviceState, SlashMode};
use zbus::zvariant::OwnedObjectPath;
use zbus::{interface, Connection};
use super::Slash;
use crate::error::RogError;
use crate::Reloadable;
#[derive(Clone)]
pub struct SlashZbus(Slash);
impl SlashZbus {
pub fn new(slash: Slash) -> Self {
Self(slash)
}
pub async fn start_tasks(
mut self,
connection: &Connection,
path: OwnedObjectPath,
) -> Result<(), RogError> {
// let task = zbus.clone();
self.reload()
.await
.unwrap_or_else(|err| warn!("Controller error: {}", err));
connection
.object_server()
.at(path.clone(), self)
.await
.map_err(|e| error!("Couldn't add server at path: {path}, {e:?}"))
.ok();
Ok(())
}
}
#[interface(name = "org.asuslinux.Slash")]
impl SlashZbus {
/// Get enabled or not
#[zbus(property)]
async fn enabled(&self) -> bool {
let lock = self.0.lock_config().await;
lock.slash_enabled
}
/// Set enabled true or false
#[zbus(property)]
async fn set_enabled(&self, enabled: bool) {
let mut config = self.0.lock_config().await;
let brightness = if enabled && config.slash_brightness == 0 {
0x88
} else {
config.slash_brightness
};
self.0
.write_bytes(&pkt_set_options(
config.slash_type,
enabled,
brightness,
config.slash_interval,
))
.await
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
config.slash_enabled = enabled;
config.slash_brightness = brightness;
config.write();
}
/// Get brightness level
#[zbus(property)]
async fn brightness(&self) -> u8 {
let config = self.0.lock_config().await;
config.slash_brightness
}
/// Set brightness level
#[zbus(property)]
async fn set_brightness(&self, brightness: u8) {
let mut config = self.0.lock_config().await;
let enabled = brightness > 0;
self.0
.write_bytes(&pkt_set_options(
config.slash_type,
enabled,
brightness,
config.slash_interval,
))
.await
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
config.slash_enabled = enabled;
config.slash_brightness = brightness;
config.write();
}
#[zbus(property)]
async fn interval(&self) -> u8 {
let config = self.0.lock_config().await;
config.slash_interval
}
/// Set interval between slash animations (0-255)
#[zbus(property)]
async fn set_interval(&self, interval: u8) {
let mut config = self.0.lock_config().await;
self.0
.write_bytes(&pkt_set_options(
config.slash_type,
config.slash_enabled,
config.slash_brightness,
interval,
))
.await
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
config.slash_interval = interval;
config.write();
}
#[zbus(property)]
async fn slash_mode(&self) -> u8 {
let config = self.0.lock_config().await;
config.slash_interval
}
/// Set interval between slash animations (0-255)
#[zbus(property)]
async fn set_slash_mode(&self, slash_mode: SlashMode) {
let mut config = self.0.lock_config().await;
let command_packets = pkt_set_mode(config.slash_type, slash_mode);
// self.node.write_bytes(&command_packets[0])?;
self.0
.write_bytes(&command_packets[1])
.await
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
self.0
.write_bytes(&pkt_save(config.slash_type))
.await
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
config.slash_mode = slash_mode;
config.write();
}
/// Get the device state as stored by asusd
// #[zbus(property)]
async fn device_state(&self) -> DeviceState {
let config = self.0.lock_config().await;
DeviceState::from(&*config)
}
}
impl Reloadable for SlashZbus {
async fn reload(&mut self) -> Result<(), RogError> {
debug!("reloading slash settings");
let config = self.0.lock_config().await;
self.0
.write_bytes(&pkt_set_options(
config.slash_type,
config.slash_enabled,
config.slash_brightness,
config.slash_interval,
))
.await
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
Ok(())
}
}
+204
View File
@@ -0,0 +1,204 @@
use std::sync::Arc;
use config_traits::{StdConfig, StdConfigLoad};
use log::{debug, error, info};
use rog_anime::error::AnimeError;
use rog_anime::usb::get_anime_type;
use rog_anime::AnimeType;
use rog_aura::AuraDeviceType;
use rog_platform::hid_raw::HidRaw;
use rog_platform::keyboard_led::KeyboardBacklight;
use rog_platform::usb_raw::USBRaw;
use rog_scsi::{open_device, ScsiType};
use rog_slash::error::SlashError;
use rog_slash::SlashType;
use tokio::sync::Mutex;
use crate::aura_anime::config::AniMeConfig;
use crate::aura_anime::AniMe;
use crate::aura_laptop::config::AuraConfig;
use crate::aura_laptop::Aura;
use crate::aura_scsi::config::ScsiConfig;
use crate::aura_scsi::ScsiAura;
use crate::aura_slash::config::SlashConfig;
use crate::aura_slash::Slash;
use crate::error::RogError;
pub enum _DeviceHandle {
/// The AniMe devices require USBRaw as they are not HID devices
Usb(USBRaw),
HidRaw(HidRaw),
LedClass(KeyboardBacklight),
/// TODO
MulticolourLed,
None,
}
#[derive(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/
TufLedClass(Arc<Mutex<HidRaw>>),
/// TODO
MulticolourLed,
None,
}
impl DeviceHandle {
/// Try Slash HID. If one exists it is initialsed and returned.
pub async fn new_slash_hid(
device: Arc<Mutex<HidRaw>>,
prod_id: &str,
) -> Result<Self, RogError> {
debug!("Testing for HIDRAW Slash");
let slash_type = SlashType::from_dmi();
if matches!(slash_type, SlashType::Unsupported)
|| slash_type
.prod_id_str()
.to_lowercase()
.trim_start_matches("0x")
!= prod_id
{
log::info!("Unknown or invalid slash: {prod_id:?}, skipping");
return Err(RogError::NotFound("No slash device".to_string()));
}
info!("Found slash type {slash_type:?}: {prod_id}");
let mut config = SlashConfig::new().load();
config.slash_type = slash_type;
let slash = Slash::new(Some(device), None, Arc::new(Mutex::new(config)));
slash.do_initialization().await?;
Ok(Self::Slash(slash))
}
/// Try Slash USB. If one exists it is initialsed and returned.
pub async fn new_slash_usb() -> Result<Self, RogError> {
debug!("Testing for USB Slash");
let slash_type = SlashType::from_dmi();
if matches!(slash_type, SlashType::Unsupported) {
return Err(RogError::Slash(SlashError::NoDevice));
}
if let Ok(usb) = USBRaw::new(slash_type.prod_id()) {
info!("Found Slash USB {slash_type:?}");
let mut config = SlashConfig::new().load();
config.slash_type = slash_type;
let slash = Slash::new(
None,
Some(Arc::new(Mutex::new(usb))),
Arc::new(Mutex::new(config)),
);
slash.do_initialization().await?;
Ok(Self::Slash(slash))
} else {
Err(RogError::NotFound("No slash device found".to_string()))
}
}
/// Try AniMe Matrix HID. If one exists it is initialsed and returned.
pub async fn maybe_anime_hid(
device: Arc<Mutex<HidRaw>>,
prod_id: &str,
) -> Result<Self, RogError> {
debug!("Testing for HIDRAW AniMe");
let anime_type = AnimeType::from_dmi();
dbg!(prod_id);
if matches!(anime_type, AnimeType::Unsupported) || prod_id != "193b" {
log::info!("Unknown or invalid AniMe: {prod_id:?}, skipping");
return Err(RogError::NotFound("No anime-matrix device".to_string()));
}
info!("Found AniMe Matrix HIDRAW {anime_type:?}: {prod_id}");
let mut config = AniMeConfig::new().load();
config.anime_type = anime_type;
let mut anime = AniMe::new(Some(device), None, Arc::new(Mutex::new(config)));
anime.do_initialization().await?;
Ok(Self::AniMe(anime))
}
pub async fn maybe_anime_usb() -> Result<Self, RogError> {
debug!("Testing for USB AniMe");
let anime_type = get_anime_type();
if matches!(anime_type, AnimeType::Unsupported) {
info!("No Anime Matrix capable laptop found");
return Err(RogError::Anime(AnimeError::NoDevice));
}
if let Ok(usb) = USBRaw::new(0x193b) {
info!("Found AniMe Matrix USB {anime_type:?}");
let mut config = AniMeConfig::new().load();
config.anime_type = anime_type;
let mut anime = AniMe::new(
None,
Some(Arc::new(Mutex::new(usb))),
Arc::new(Mutex::new(config)),
);
anime.do_initialization().await?;
Ok(Self::AniMe(anime))
} else {
Err(RogError::NotFound(
"No AnimeMatrix device found".to_string(),
))
}
}
pub async fn maybe_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: Option<Arc<Mutex<HidRaw>>>,
prod_id: &str,
) -> Result<Self, RogError> {
debug!("Testing for laptop aura");
let aura_type = AuraDeviceType::from(prod_id);
if !matches!(
aura_type,
AuraDeviceType::LaptopKeyboard2021
| AuraDeviceType::LaptopKeyboardPre2021
| AuraDeviceType::LaptopKeyboardTuf
) {
log::info!("Unknown or invalid laptop aura: {prod_id:?}, skipping");
return Err(RogError::NotFound("No laptop aura device".to_string()));
}
info!("Found laptop aura type {prod_id:?}");
let backlight = KeyboardBacklight::new()
.map_err(|e| error!("Keyboard backlight error: {e:?}"))
.map_or(None, |k| {
info!("Found sysfs backlight control");
Some(Arc::new(Mutex::new(k)))
});
let mut config = AuraConfig::load_and_update_config(prod_id);
config.led_type = aura_type;
let aura = Aura {
hid: device,
backlight,
config: Arc::new(Mutex::new(config)),
};
aura.do_initialization().await?;
Ok(Self::Aura(aura))
}
}
+8 -2
View File
@@ -7,8 +7,11 @@ const CONFIG_FILE: &str = "asusd.ron";
#[derive(Deserialize, Serialize, Debug, PartialEq, PartialOrd)]
pub struct Config {
/// Save charge limit for restoring on boot/resume
// The current charge limit applied
pub charge_control_end_threshold: u8,
/// Save charge limit for restoring
#[serde(skip)]
pub base_charge_control_end_threshold: u8,
pub panel_od: bool,
pub boot_sound: bool,
pub mini_led_mode: bool,
@@ -64,6 +67,7 @@ impl Default for Config {
fn default() -> Self {
Self {
charge_control_end_threshold: 100,
base_charge_control_end_threshold: 100,
panel_od: false,
boot_sound: false,
mini_led_mode: false,
@@ -116,7 +120,7 @@ impl StdConfigLoad1<Config507> for Config {}
#[derive(Deserialize, Serialize)]
pub struct Config507 {
/// Save charge limit for restoring on boot
// The current charge limit applied
pub charge_control_end_threshold: u8,
pub panel_od: bool,
pub mini_led_mode: bool,
@@ -139,7 +143,9 @@ pub struct Config507 {
impl From<Config507> for Config {
fn from(c: Config507) -> Self {
Self {
// Restore the base charge limit
charge_control_end_threshold: c.charge_control_end_threshold,
base_charge_control_end_threshold: c.charge_control_end_threshold,
panel_od: c.panel_od,
boot_sound: false,
disable_nvidia_powerd_on_battery: c.disable_nvidia_powerd_on_battery,
-296
View File
@@ -1,296 +0,0 @@
pub mod config;
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
pub mod trait_impls;
use std::convert::TryFrom;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::sleep;
use ::zbus::export::futures_util::lock::Mutex;
use config_traits::{StdConfig, StdConfigLoad};
use log::{error, info, warn};
use rog_anime::error::AnimeError;
use rog_anime::usb::{
get_maybe_anime_type, pkt_flush, pkt_set_brightness, pkt_set_enable_display,
pkt_set_enable_powersave_anim, pkts_for_init, Brightness,
};
use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType, AnimeType};
use rog_platform::hid_raw::HidRaw;
use rog_platform::usb_raw::USBRaw;
use self::config::{AnimeConfig, AnimeConfigCached};
use crate::error::RogError;
enum Node {
Usb(USBRaw),
Hid(HidRaw),
}
impl Node {
pub fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
// TODO: map and pass on errors
match self {
Node::Usb(u) => {
u.write_bytes(message).ok();
}
Node::Hid(h) => {
h.write_bytes(message).ok();
}
}
Ok(())
}
pub fn set_builtins_enabled(&self, enabled: bool, bright: Brightness) -> Result<(), RogError> {
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))?;
self.write_bytes(&pkt_set_enable_display(enabled))?;
self.write_bytes(&pkt_set_brightness(bright))?;
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))
}
}
pub struct CtrlAnime {
// node: HidRaw,
node: Node,
anime_type: AnimeType,
cache: AnimeConfigCached,
config: AnimeConfig,
// set to force thread to exit
thread_exit: Arc<AtomicBool>,
// Set to false when the thread exits
thread_running: Arc<AtomicBool>,
}
impl CtrlAnime {
#[inline]
pub fn new() -> Result<CtrlAnime, RogError> {
let anime_type = get_maybe_anime_type()?;
if matches!(anime_type, AnimeType::Unsupported) {
info!("No Anime Matrix capable laptop found");
return Err(RogError::Anime(AnimeError::NoDevice));
}
let usb = USBRaw::new(0x193b).ok();
let hid = HidRaw::new("193b").ok();
let node = if usb.is_some() {
info!("Anime using the USB interface");
unsafe { Node::Usb(usb.unwrap_unchecked()) }
} else if hid.is_some() {
info!("Anime using the HID interface");
unsafe { Node::Hid(hid.unwrap_unchecked()) }
} else {
return Err(RogError::Anime(AnimeError::NoDevice));
};
// TODO: something better to set wakeups disabled
// if matches!(node, Node::Usb(_)) {
// if let Ok(mut enumerator) = udev::Enumerator::new() {
// enumerator.match_subsystem("usb").ok();
// enumerator.match_attribute("idProduct", "193b").ok();
// if let Ok(mut enumer) = enumerator.scan_devices() {
// if let Some(mut dev) = enumer.next() {
// dev.set_attribute_value("power/wakeup", "disabled").ok();
// }
// }
// }
// }
let mut config = AnimeConfig::new().load();
info!("Device has an AniMe Matrix display: {anime_type:?}");
let mut cache = AnimeConfigCached::default();
if let Err(e) = cache.init_from_config(&config, anime_type) {
error!("Trying to cache the Anime Config failed, will reset to default config: {e:?}");
config.rename_file_old();
config = AnimeConfig::new();
config.write();
}
let ctrl = CtrlAnime {
node,
anime_type,
cache,
config,
thread_exit: Arc::new(AtomicBool::new(false)),
thread_running: Arc::new(AtomicBool::new(false)),
};
ctrl.do_initialization()?;
Ok(ctrl)
}
// let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
/// Start an action thread. This is classed as a singleton and there should
/// be only one running - so the thread uses atomics to signal run/exit.
///
/// Because this also writes to the usb device, other write tries (display
/// only) *must* get the mutex lock and set the `thread_exit` atomic.
async fn run_thread(inner: Arc<Mutex<CtrlAnime>>, actions: Vec<ActionData>, mut once: bool) {
if actions.is_empty() {
warn!("AniMe system actions was empty");
return;
}
if let Some(lock) = inner.try_lock() {
lock.node
.write_bytes(&pkt_set_enable_powersave_anim(false))
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
}
// Loop rules:
// - Lock the mutex **only when required**. That is, the lock must be held for
// the shortest duration possible.
// - An AtomicBool used for thread exit should be checked in every loop,
// including nested
// The only reason for this outer thread is to prevent blocking while waiting
// for the next spawned thread to exit
// TODO: turn this in to async task (maybe? COuld still risk blocking main
// thread)
std::thread::Builder::new()
.name("AniMe system thread start".into())
.spawn(move || {
info!("AniMe new system thread started");
// Getting copies of these Atomics is done *in* the thread to ensure
// we don't block other threads/main
let thread_exit;
let thread_running;
let anime_type;
loop {
if let Some(lock) = inner.try_lock() {
thread_exit = lock.thread_exit.clone();
thread_running = lock.thread_running.clone();
anime_type = lock.anime_type;
break;
}
}
// First two loops are to ensure we *do* aquire a lock on the mutex
// The reason the loop is required is because the USB writes can block
// for up to 10ms. We can't fail to get the atomics.
while thread_running.load(Ordering::SeqCst) {
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
}
info!("AniMe no previous system thread running (now)");
thread_exit.store(false, Ordering::SeqCst);
thread_running.store(true, Ordering::SeqCst);
'main: loop {
for action in &actions {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
match action {
ActionData::Animation(frames) => {
rog_anime::run_animation(frames, &|frame| {
if thread_exit.load(Ordering::Acquire) {
info!("rog-anime: animation sub-loop was asked to exit");
return Ok(true); // Do safe exit
}
inner
.try_lock()
.map(|lock| {
lock.write_data_buffer(frame)
.map_err(|err| {
warn!(
"rog_anime::run_animation:callback {}",
err
);
})
.ok();
false // Don't exit yet
})
.map_or_else(
|| {
warn!("rog_anime::run_animation:callback failed");
Err(AnimeError::NoFrames)
},
Ok,
)
});
if thread_exit.load(Ordering::Acquire) {
info!("rog-anime: sub-loop exited and main loop exiting now");
break 'main;
}
}
ActionData::Image(image) => {
once = false;
if let Some(lock) = inner.try_lock() {
lock.write_data_buffer(image.as_ref().clone())
.map_err(|e| error!("{}", e))
.ok();
}
}
ActionData::Pause(duration) => sleep(*duration),
ActionData::AudioEq
| ActionData::SystemInfo
| ActionData::TimeDate
| ActionData::Matrix => {}
}
}
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
if once || actions.is_empty() {
break 'main;
}
}
// Clear the display on exit
if let Some(lock) = inner.try_lock() {
if let Ok(data) =
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()])
.map_err(|e| error!("{}", e))
{
lock.write_data_buffer(data)
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
}
lock.node
.write_bytes(&pkt_set_enable_powersave_anim(
lock.config.builtin_anims_enabled,
))
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
}
// Loop ended, set the atmonics
thread_running.store(false, Ordering::SeqCst);
info!("AniMe system thread exited");
})
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
}
/// Write only a data packet. This will modify the leds brightness using the
/// global brightness set in config.
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> {
for led in buffer.data_mut().iter_mut() {
let mut bright = *led as f32;
if bright > 254.0 {
bright = 254.0;
}
*led = bright as u8;
}
let data = AnimePacketType::try_from(buffer)?;
for row in &data {
self.node.write_bytes(row)?;
}
self.node.write_bytes(&pkt_flush())?;
Ok(())
}
fn do_initialization(&self) -> Result<(), RogError> {
let pkts = pkts_for_init();
self.node.write_bytes(&pkts[0])?;
self.node.write_bytes(&pkts[1])?;
Ok(())
}
}
-573
View File
@@ -1,573 +0,0 @@
use std::collections::HashSet;
use config_traits::{StdConfig, StdConfigLoad};
use dmi_id::DMIID;
use inotify::Inotify;
use log::{debug, info, warn};
use rog_aura::aura_detection::LedSupportData;
use rog_aura::keyboard::{LedUsbPackets, UsbPackets};
use rog_aura::usb::{LED_APPLY, LED_SET};
use rog_aura::{AuraDeviceType, AuraEffect, LedBrightness, PowerZones, LED_MSG_LEN};
use rog_platform::hid_raw::HidRaw;
use rog_platform::keyboard_led::KeyboardBacklight;
use udev::Device;
use zbus::zvariant::OwnedObjectPath;
use zbus::Connection;
use super::config::AuraConfig;
use crate::ctrl_aura::manager::{dbus_path_for_dev, dbus_path_for_tuf, start_tasks};
use crate::ctrl_aura::trait_impls::CtrlAuraZbus;
use crate::error::RogError;
use crate::CtrlTask;
#[derive(Debug)]
pub enum LEDNode {
/// Brightness and/or TUF RGB controls
KbdLed(KeyboardBacklight),
/// Raw HID handle
Rog(Option<KeyboardBacklight>, HidRaw),
}
impl LEDNode {
// TODO: move various methods upwards to this
pub fn set_brightness(&self, value: u8) -> Result<(), RogError> {
match self {
LEDNode::KbdLed(k) => k.set_brightness(value)?,
LEDNode::Rog(k, r) => {
if let Some(k) = k {
k.set_brightness(value)?;
let x = k.get_brightness()?;
if x != value {
debug!(
"Kernel brightness control didn't read back correct value, setting \
with raw hid"
);
r.write_bytes(&[0x5a, 0xba, 0xc5, 0xc4, value])?;
}
} else {
debug!("No brightness control found, trying raw write");
r.write_bytes(&[0x5a, 0xba, 0xc5, 0xc4, value])?;
}
}
}
Ok(())
}
pub fn get_brightness(&self) -> Result<u8, RogError> {
Ok(match self {
LEDNode::KbdLed(k) => k.get_brightness()?,
LEDNode::Rog(k, _) => {
if let Some(k) = k {
k.get_brightness()?
} else {
debug!("No brightness control found");
return Err(RogError::MissingFunction(
"No keyboard brightness control found".to_string(),
));
}
}
})
}
pub fn monitor_brightness(&self) -> Result<Inotify, RogError> {
Ok(match self {
LEDNode::KbdLed(k) => k.monitor_brightness()?,
LEDNode::Rog(k, _) => {
if let Some(k) = k {
k.monitor_brightness()?
} else {
debug!("No brightness control found");
return Err(RogError::MissingFunction(
"No keyboard brightness control found".to_string(),
));
}
}
})
}
pub fn has_brightness_control(&self) -> bool {
match self {
LEDNode::KbdLed(k) => k.has_brightness(),
LEDNode::Rog(k, _) => {
if let Some(k) = k {
k.has_brightness()
} else {
false
}
}
}
}
}
/// Individual controller for one Aura device
pub struct CtrlKbdLed {
pub led_type: AuraDeviceType,
pub led_node: LEDNode,
pub supported_data: LedSupportData, // TODO: is storing this really required?
pub per_key_mode_active: bool,
pub config: AuraConfig,
pub dbus_path: OwnedObjectPath,
}
impl CtrlKbdLed {
pub fn add_to_dbus_and_start(
self,
interfaces: &mut HashSet<OwnedObjectPath>,
conn: Connection,
) -> Result<(), RogError> {
let dbus_path = self.dbus_path.clone();
let dbus_path_cpy = self.dbus_path.clone();
info!(
"AuraManager starting device at: {:?}, {:?}",
dbus_path, self.led_type
);
let conn_copy = conn.clone();
let sig_ctx1 = CtrlAuraZbus::signal_context(&conn_copy)?;
let sig_ctx2 = CtrlAuraZbus::signal_context(&conn_copy)?;
let zbus = CtrlAuraZbus::new(self, sig_ctx1);
tokio::spawn(
async move { start_tasks(zbus, conn_copy.clone(), sig_ctx2, dbus_path).await },
);
interfaces.insert(dbus_path_cpy);
Ok(())
}
/// Build and init a `CtrlKbdLed` from a udev device. Maybe.
/// This will initialise the config also.
pub fn maybe_device(
device: Device,
interfaces: &mut HashSet<OwnedObjectPath>,
) -> Result<Option<Self>, RogError> {
// usb_device gives us a product and vendor ID
if let Some(usb_device) = device.parent_with_subsystem_devtype("usb", "usb_device")? {
let dbus_path = dbus_path_for_dev(&usb_device).unwrap_or_default();
if interfaces.contains(&dbus_path) {
debug!("Already a ctrl at {dbus_path:?}, ignoring this end-point");
return Ok(None);
}
// The asus_wmi driver latches MCU that controls the USB endpoints
if let Some(parent) = device.parent() {
if let Some(driver) = parent.driver() {
// There is a tree of devices added so filter by driver
if driver != "asus" {
return Ok(None);
}
} else {
return Ok(None);
}
}
// Device is something like 002, while its parent is the MCU
// Think of it like the device is an endpoint of the USB device attached
let mut prod_id = String::new();
if let Some(usb_id) = usb_device.attribute_value("idProduct") {
prod_id = usb_id.to_string_lossy().to_string();
let aura_device = AuraDeviceType::from(prod_id.as_str());
if aura_device == AuraDeviceType::Unknown {
log::debug!("Unknown or invalid device: {usb_id:?}, skipping");
return Ok(None);
}
}
let dev_node = if let Some(dev_node) = usb_device.devnode() {
dev_node
} else {
debug!("Device has no devnode, skipping");
return Ok(None);
};
info!("AuraControl found device at: {:?}", dev_node);
let dev = HidRaw::from_device(device)?;
let mut controller = Self::from_hidraw(dev, dbus_path.clone())?;
controller.config = Self::load_and_update_config(&prod_id);
interfaces.insert(dbus_path);
return Ok(Some(controller));
}
Ok(None)
}
pub fn find_all() -> Result<Vec<Self>, RogError> {
info!("Searching for all Aura devices");
let mut devices = Vec::new();
let mut interfaces = HashSet::new(); // track and ensure we use only one hidraw per prod_id
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
err
})?;
enumerator.match_subsystem("hidraw").map_err(|err| {
warn!("{}", err);
err
})?;
for end_point in enumerator.scan_devices()? {
// maybe?
if let Some(device) = Self::maybe_device(end_point, &mut interfaces)? {
devices.push(device);
}
}
// Check for a TUF laptop LED. Assume there is only ever one.
if let Ok(kbd_backlight) = KeyboardBacklight::new() {
if kbd_backlight.has_kbd_rgb_mode() {
// Extra sure double-check that this isn't a laptop with crap
// ACPI with borked return on the TUF rgb methods
let dmi = DMIID::new().unwrap_or_default();
info!("Found a TUF with product family: {}", dmi.product_family);
info!("and board name: {}", dmi.board_name);
if dmi.product_family.contains("TUF") {
info!("AuraControl found a TUF laptop keyboard");
let ctrl = CtrlKbdLed {
led_type: AuraDeviceType::LaptopTuf,
led_node: LEDNode::KbdLed(kbd_backlight),
supported_data: LedSupportData::get_data("tuf"),
per_key_mode_active: false,
config: Self::load_and_update_config("tuf"),
dbus_path: dbus_path_for_tuf(),
};
devices.push(ctrl);
}
}
} else {
let dmi = DMIID::new().unwrap_or_default();
warn!("No asus::kbd_backlight found for {} ??", dmi.product_family);
}
info!("Found {} Aura devices", devices.len());
Ok(devices)
}
/// The generated data from this function has a default config. This config
/// should be overwritten. The reason for the default config is because
/// of async issues between this and udev/hidraw
fn from_hidraw(device: HidRaw, dbus_path: OwnedObjectPath) -> Result<Self, RogError> {
let rgb_led = KeyboardBacklight::new()
.map_err(|e| {
log::error!(
"{} is missing a keyboard backlight brightness control: {e:?}",
device.prod_id()
);
})
.ok();
let prod_id = AuraDeviceType::from(device.prod_id());
if prod_id == AuraDeviceType::Unknown {
log::error!("{} is AuraDevice::Unknown", device.prod_id());
return Err(RogError::NoAuraNode);
}
// New loads data from the DB also
// let config = Self::init_config(prod_id, data);
let data = LedSupportData::get_data(device.prod_id());
let ctrl = CtrlKbdLed {
led_type: prod_id,
led_node: LEDNode::Rog(rgb_led, device),
supported_data: data.clone(),
per_key_mode_active: false,
config: AuraConfig::default(),
dbus_path,
};
Ok(ctrl)
}
/// Reload the config from disk then verify and update it if required
fn load_and_update_config(prod_id: &str) -> AuraConfig {
// New loads data from the DB also
let mut config_init = AuraConfig::new(prod_id);
// config_init.set_filename(prod_id);
let mut config_loaded = config_init.clone().load();
// update the initialised data with what we loaded from disk
for mode in &mut config_init.builtins {
// update init values from loaded values if they exist
if let Some(loaded) = config_loaded.builtins.get(mode.0) {
*mode.1 = loaded.clone();
}
}
// Then replace just incase the initialised data contains new modes added
config_loaded.builtins = config_init.builtins;
// Check the powerzones and replace, if the len is different then the support
// file was updated
if config_loaded.enabled.states.len() != config_init.enabled.states.len() {
config_loaded.enabled.states = config_init.enabled.states;
}
if let (Some(mut multizone_init), Some(multizone_loaded)) =
(config_init.multizone, config_loaded.multizone.as_mut())
{
for mode in multizone_init.iter_mut() {
// update init values from loaded values if they exist
if let Some(loaded) = multizone_loaded.get(mode.0) {
let mut new_set = Vec::new();
let data = LedSupportData::get_data(prod_id);
// only reuse a zone mode if the mode is supported
for mode in loaded {
if data.basic_modes.contains(&mode.mode) {
new_set.push(mode.clone());
}
}
*mode.1 = new_set;
}
}
*multizone_loaded = multizone_init;
}
config_loaded
}
pub(super) fn fix_ally_power(&mut self) -> Result<(), RogError> {
if self.led_type == AuraDeviceType::Ally {
if let LEDNode::Rog(_, hid_raw) = &self.led_node {
if let Some(fix) = self.config.ally_fix.as_mut() {
if !*fix {
let msg = [0x5d, 0xbd, 0x01, 0xff, 0xff, 0xff, 0xff];
hid_raw.write_bytes(&msg)?;
info!("Reset Ally power settings to base");
}
*fix = true;
}
self.config.write();
}
}
Ok(())
}
/// Set combination state for boot animation/sleep animation/all leds/keys
/// leds/side leds LED active
pub(super) fn set_power_states(&mut self) -> Result<(), RogError> {
if let LEDNode::KbdLed(platform) = &mut self.led_node {
// TODO: tuf bool array
let buf = self.config.enabled.to_bytes(self.led_type);
platform.set_kbd_rgb_state(&buf)?;
} else if let LEDNode::Rog(_, hid_raw) = &self.led_node {
if let Some(p) = self.config.enabled.states.first() {
if p.zone == PowerZones::Ally {
let msg = [0x5d, 0xd1, 0x09, 0x01, p.new_to_byte() as u8, 0x0, 0x0];
hid_raw.write_bytes(&msg)?;
return Ok(());
}
}
let bytes = self.config.enabled.to_bytes(self.led_type);
let msg = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3]];
hid_raw.write_bytes(&msg)?;
}
Ok(())
}
/// Write an effect block. This is for per-key, but can be repurposed to
/// write the raw factory mode packets - when doing this it is expected that
/// only the first `Vec` (`effect[0]`) is valid.
pub fn write_effect_block(&mut self, effect: &UsbPackets) -> Result<(), RogError> {
if self.config.brightness == LedBrightness::Off {
self.config.brightness = LedBrightness::Med;
self.config.write();
}
let pkt_type = effect[0][1];
const PER_KEY_TYPE: u8 = 0xbc;
if pkt_type != PER_KEY_TYPE {
self.per_key_mode_active = false;
if let LEDNode::Rog(_, hid_raw) = &self.led_node {
hid_raw.write_bytes(&effect[0])?;
hid_raw.write_bytes(&LED_SET)?;
// hid_raw.write_bytes(&LED_APPLY)?;
}
} else {
if !self.per_key_mode_active {
if let LEDNode::Rog(_, hid_raw) = &self.led_node {
let init = LedUsbPackets::get_init_msg();
hid_raw.write_bytes(&init)?;
}
self.per_key_mode_active = true;
}
if let LEDNode::Rog(_, hid_raw) = &self.led_node {
for row in effect.iter() {
hid_raw.write_bytes(row)?;
}
} else if let LEDNode::KbdLed(tuf) = &self.led_node {
for row in effect.iter() {
let r = row[9];
let g = row[10];
let b = row[11];
tuf.set_kbd_rgb_mode(&[0, 0, r, g, b, 0])?;
}
}
}
Ok(())
}
/// Write the AuraEffect to the device
pub fn write_effect_and_apply(&mut self, mode: &AuraEffect) -> Result<(), RogError> {
if let LEDNode::KbdLed(platform) = &self.led_node {
let buf = [
1,
mode.mode as u8,
mode.colour1.r,
mode.colour1.g,
mode.colour1.b,
mode.speed as u8,
];
platform.set_kbd_rgb_mode(&buf)?;
} else if let LEDNode::Rog(_, hid_raw) = &self.led_node {
let bytes: [u8; LED_MSG_LEN] = mode.into();
hid_raw.write_bytes(&bytes)?;
hid_raw.write_bytes(&LED_SET)?;
// Changes won't persist unless apply is set
hid_raw.write_bytes(&LED_APPLY)?;
} else {
return Err(RogError::NoAuraKeyboard);
}
self.per_key_mode_active = false;
Ok(())
}
pub(super) fn write_current_config_mode(&mut self) -> Result<(), RogError> {
if self.config.multizone_on {
let mode = self.config.current_mode;
let mut create = false;
// There is no multizone config for this mode so create one here
// using the colours of rainbow if it exists, or first available
// mode, or random
if self.config.multizone.is_none() {
create = true;
} else if let Some(multizones) = self.config.multizone.as_ref() {
if !multizones.contains_key(&mode) {
create = true;
}
}
if create {
info!("No user-set config for zone founding, attempting a default");
self.config.create_multizone_default(&self.supported_data)?;
}
if let Some(multizones) = self.config.multizone.as_mut() {
if let Some(set) = multizones.get(&mode) {
for mode in set.clone() {
self.write_effect_and_apply(&mode)?;
}
}
}
} else {
let mode = self.config.current_mode;
if let Some(effect) = self.config.builtins.get(&mode).cloned() {
self.write_effect_and_apply(&effect)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use rog_aura::aura_detection::LedSupportData;
use rog_aura::{AuraDeviceType, AuraModeNum, AuraZone, PowerZones};
use rog_platform::hid_raw::HidRaw;
use rog_platform::keyboard_led::KeyboardBacklight;
use zbus::zvariant::OwnedObjectPath;
use super::CtrlKbdLed;
use crate::ctrl_aura::config::AuraConfig;
use crate::ctrl_aura::controller::LEDNode;
#[test]
#[ignore = "Unable to run in CI as the HIDRAW device is required"]
fn create_multizone_if_no_config() {
// Checking to ensure set_mode errors when unsupported modes are tried
let config = AuraConfig::new("19b6");
let supported_basic_modes = LedSupportData {
device_name: String::new(),
product_id: String::new(),
layout_name: "ga401".to_owned(),
basic_modes: vec![AuraModeNum::Static],
basic_zones: vec![],
advanced_type: rog_aura::keyboard::AdvancedAuraType::None,
power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow],
};
let mut controller = CtrlKbdLed {
led_type: AuraDeviceType::LaptopPost2021,
led_node: LEDNode::Rog(
Some(KeyboardBacklight::default()),
HidRaw::new("19b6").unwrap(),
),
supported_data: supported_basic_modes,
per_key_mode_active: false,
config,
dbus_path: OwnedObjectPath::default(),
};
assert!(controller.config.multizone.is_none());
assert!(controller
.config
.create_multizone_default(&controller.supported_data)
.is_err());
assert!(controller.config.multizone.is_none());
controller.supported_data.basic_zones.push(AuraZone::Key1);
controller.supported_data.basic_zones.push(AuraZone::Key2);
assert!(controller
.config
.create_multizone_default(&controller.supported_data)
.is_ok());
assert!(controller.config.multizone.is_some());
let m = controller.config.multizone.unwrap();
assert!(m.contains_key(&AuraModeNum::Static));
let e = m.get(&AuraModeNum::Static).unwrap();
assert_eq!(e.len(), 2);
assert_eq!(e[0].zone, AuraZone::Key1);
assert_eq!(e[1].zone, AuraZone::Key2);
}
#[test]
#[ignore = "Unable to run in CI as the HIDRAW device is required"]
// TODO: use sim device
fn next_mode_create_multizone_if_no_config() {
// Checking to ensure set_mode errors when unsupported modes are tried
let config = AuraConfig::new("19b6");
let supported_basic_modes = LedSupportData {
device_name: String::new(),
product_id: String::new(),
layout_name: "ga401".to_owned(),
basic_modes: vec![AuraModeNum::Static],
basic_zones: vec![AuraZone::Key1, AuraZone::Key2],
advanced_type: rog_aura::keyboard::AdvancedAuraType::None,
power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow],
};
let mut controller = CtrlKbdLed {
led_type: AuraDeviceType::LaptopPost2021,
led_node: LEDNode::Rog(
Some(KeyboardBacklight::default()),
HidRaw::new("19b6").unwrap(),
),
supported_data: supported_basic_modes,
per_key_mode_active: false,
config,
dbus_path: OwnedObjectPath::default(),
};
assert!(controller.config.multizone.is_none());
controller.config.multizone_on = true;
// This is called in toggle_mode. It will error here because we have no
// keyboard node in tests.
assert_eq!(
controller
.write_current_config_mode()
.unwrap_err()
.to_string(),
"No supported Aura keyboard"
);
assert!(controller.config.multizone.is_some());
let m = controller.config.multizone.unwrap();
assert!(m.contains_key(&AuraModeNum::Static));
let e = m.get(&AuraModeNum::Static).unwrap();
assert_eq!(e.len(), 2);
assert_eq!(e[0].zone, AuraZone::Key1);
assert_eq!(e[1].zone, AuraZone::Key2);
}
}
-139
View File
@@ -1,139 +0,0 @@
// Plan:
// - Manager has udev monitor on USB looking for ROG devices
// - If a device is found, add it to watch
// - Add it to Zbus server
// - If udev sees device removed then remove the zbus path
use std::collections::HashSet;
use log::{error, info, warn};
use mio::{Events, Interest, Poll, Token};
use tokio::task::spawn_blocking;
use udev::{Device, MonitorBuilder};
use zbus::object_server::SignalContext;
use zbus::zvariant::{ObjectPath, OwnedObjectPath};
use zbus::Connection;
use crate::ctrl_aura::controller::CtrlKbdLed;
use crate::ctrl_aura::trait_impls::{CtrlAuraZbus, AURA_ZBUS_PATH};
use crate::error::RogError;
use crate::{CtrlTask, Reloadable};
pub struct AuraManager {
_connection: Connection,
}
impl AuraManager {
pub async fn new(connection: Connection) -> Result<Self, RogError> {
let conn_copy = connection.clone();
let mut interfaces = HashSet::new();
// Do the initial keyboard detection:
let all = CtrlKbdLed::find_all()?;
for ctrl in all {
let path = ctrl.dbus_path.clone();
interfaces.insert(path.clone()); // ensure we record the initial stuff
let sig_ctx = CtrlAuraZbus::signal_context(&connection)?;
let sig_ctx2 = sig_ctx.clone();
let zbus = CtrlAuraZbus::new(ctrl, sig_ctx);
start_tasks(zbus, connection.clone(), sig_ctx2, path).await?;
}
let manager = Self {
_connection: connection,
};
// detect all plugged in aura devices (eventually)
// only USB devices are detected for here
spawn_blocking(move || {
let mut monitor = MonitorBuilder::new()?.match_subsystem("hidraw")?.listen()?;
let mut poll = Poll::new()?;
let mut events = Events::with_capacity(1024);
poll.registry()
.register(&mut monitor, Token(0), Interest::READABLE)?;
loop {
if poll.poll(&mut events, None).is_err() {
continue;
}
for event in monitor.iter() {
let action = event.action().unwrap_or_default();
if let Some(parent) =
event.parent_with_subsystem_devtype("usb", "usb_device")?
{
if action == "remove" {
if let Some(path) = dbus_path_for_dev(&parent) {
if interfaces.remove(&path) {
info!("AuraManager removing: {path:?}");
let conn_copy = conn_copy.clone();
tokio::spawn(async move {
let res = conn_copy
.object_server()
.remove::<CtrlAuraZbus, _>(&path)
.await
.map_err(|e| {
error!("Failed to remove {path:?}, {e:?}");
e
})?;
info!("AuraManager removed: {path:?}, {res}");
Ok::<(), RogError>(())
});
}
}
} else if action == "add" {
if let Ok(Some(ctrl)) =
CtrlKbdLed::maybe_device(event.device(), &mut interfaces)
{
ctrl.add_to_dbus_and_start(&mut interfaces, conn_copy.clone())
.map_err(|e| {
error!("Couldn't start aura device on dbus: {e:?}")
})
.ok();
}
};
}
}
}
// Required for return type on spawn
#[allow(unreachable_code)]
Ok::<(), RogError>(())
});
Ok(manager)
}
}
pub(crate) fn dbus_path_for_dev(parent: &Device) -> Option<OwnedObjectPath> {
if let Some(filename) = super::filename_partial(parent) {
return Some(
ObjectPath::from_str_unchecked(&format!("{AURA_ZBUS_PATH}/{filename}")).into(),
);
}
None
}
pub(crate) fn dbus_path_for_tuf() -> OwnedObjectPath {
ObjectPath::from_str_unchecked(&format!("{AURA_ZBUS_PATH}/tuf")).into()
}
pub async fn start_tasks(
mut zbus: CtrlAuraZbus,
connection: Connection,
_signal_ctx: SignalContext<'static>,
path: OwnedObjectPath,
) -> Result<(), RogError> {
// let task = zbus.clone();
// let signal_ctx = signal_ctx.clone();
zbus.reload()
.await
.unwrap_or_else(|err| warn!("Controller error: {}", err));
connection
.object_server()
.at(path.clone(), zbus)
.await
.map_err(|e| error!("Couldn't add server at path: {path}, {e:?}"))
.ok();
// TODO: skip this until we keep handles to tasks so they can be killed
// task.create_tasks(signal_ctx).await
Ok(())
}
-34
View File
@@ -1,34 +0,0 @@
use log::warn;
use udev::Device;
use zbus::zvariant::{ObjectPath, OwnedObjectPath};
pub mod config;
pub mod controller;
pub mod manager;
/// Implements `CtrlTask`, `Reloadable`, `ZbusRun`
pub mod trait_impls;
/// Returns only the Device details concatenated in a form usable for
/// adding/appending to a filename
pub(super) fn filename_partial(parent: &Device) -> Option<OwnedObjectPath> {
if let Some(id_product) = parent.attribute_value("idProduct") {
let id_product = id_product.to_string_lossy();
let mut path = if let Some(devnum) = parent.attribute_value("devnum") {
let devnum = devnum.to_string_lossy();
if let Some(devpath) = parent.attribute_value("devpath") {
let devpath = devpath.to_string_lossy();
format!("{id_product}_{devnum}_{devpath}")
} else {
format!("{id_product}_{devnum}")
}
} else {
format!("{id_product}")
};
if path.contains('.') {
warn!("dbus path for {id_product} contains `.`, removing");
path.replace('.', "").clone_into(&mut path);
}
return Some(ObjectPath::from_str_unchecked(&path).into());
}
None
}
-306
View File
@@ -1,306 +0,0 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use config_traits::StdConfig;
use log::{debug, error, info, warn};
use rog_aura::keyboard::{LaptopAuraPower, UsbPackets};
use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones};
use zbus::export::futures_util::lock::{Mutex, MutexGuard};
use zbus::export::futures_util::StreamExt;
use zbus::fdo::Error as ZbErr;
use zbus::{interface, SignalContext};
use super::controller::CtrlKbdLed;
use crate::error::RogError;
use crate::CtrlTask;
pub const AURA_ZBUS_NAME: &str = "Aura";
pub const AURA_ZBUS_PATH: &str = "/org/asuslinux";
#[derive(Clone)]
pub struct CtrlAuraZbus(Arc<Mutex<CtrlKbdLed>>, SignalContext<'static>);
impl CtrlAuraZbus {
pub fn new(controller: CtrlKbdLed, signal: SignalContext<'static>) -> Self {
Self(Arc::new(Mutex::new(controller)), signal)
}
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
let bright = lock.led_node.get_brightness().unwrap_or_default();
lock.config.read();
lock.config.brightness = bright.into();
lock.config.write();
Ok(())
}
}
/// The main interface for changing, reading, or notfying
///
/// LED commands are split between Brightness, Modes, Per-Key
#[interface(name = "org.asuslinux.Aura")]
impl CtrlAuraZbus {
/// Return the device type for this Aura keyboard
#[zbus(property)]
async fn device_type(&self) -> AuraDeviceType {
let ctrl = self.0.lock().await;
ctrl.led_type
}
/// Return the current LED brightness
#[zbus(property)]
async fn brightness(&self) -> Result<LedBrightness, ZbErr> {
let ctrl = self.0.lock().await;
Ok(ctrl.led_node.get_brightness().map(|n| n.into())?)
}
/// Set the keyboard brightness level (0-3)
#[zbus(property)]
async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> {
let ctrl = self.0.lock().await;
Ok(ctrl.led_node.set_brightness(brightness.into())?)
}
/// Total levels of brightness available
#[zbus(property)]
async fn supported_brightness(&self) -> Vec<LedBrightness> {
vec![
LedBrightness::Off,
LedBrightness::Low,
LedBrightness::Med,
LedBrightness::High,
]
}
/// The total available modes
#[zbus(property)]
async fn supported_basic_modes(&self) -> Result<Vec<AuraModeNum>, ZbErr> {
let ctrl = self.0.lock().await;
Ok(ctrl.config.builtins.keys().cloned().collect())
}
#[zbus(property)]
async fn supported_basic_zones(&self) -> Result<Vec<AuraZone>, ZbErr> {
let ctrl = self.0.lock().await;
Ok(ctrl.supported_data.basic_zones.clone())
}
#[zbus(property)]
async fn supported_power_zones(&self) -> Result<Vec<PowerZones>, ZbErr> {
let ctrl = self.0.lock().await;
Ok(ctrl.supported_data.power_zones.clone())
}
/// The current mode data
#[zbus(property)]
async fn led_mode(&self) -> Result<AuraModeNum, ZbErr> {
let ctrl = self.0.lock().await;
Ok(ctrl.config.current_mode)
}
/// Set an Aura effect if the effect mode or zone is supported.
///
/// On success the aura config file is read to refresh cached values, then
/// the effect is stored and config written to disk.
#[zbus(property)]
async fn set_led_mode(&mut self, num: AuraModeNum) -> Result<(), ZbErr> {
let mut ctrl = self.0.lock().await;
ctrl.config.current_mode = num;
ctrl.write_current_config_mode()?;
if ctrl.config.brightness == LedBrightness::Off {
ctrl.config.brightness = LedBrightness::Med;
}
if ctrl.led_node.has_brightness_control() {
ctrl.led_node
.set_brightness(ctrl.config.brightness.into())?;
}
ctrl.config.write();
self.led_mode_data_invalidate(&self.1).await.ok();
Ok(())
}
/// The current mode data
#[zbus(property)]
async fn led_mode_data(&self) -> Result<AuraEffect, ZbErr> {
let ctrl = self.0.lock().await;
let mode = ctrl.config.current_mode;
match ctrl.config.builtins.get(&mode) {
Some(effect) => Ok(effect.clone()),
None => Err(ZbErr::Failed("Could not get the current effect".into())),
}
}
/// Set an Aura effect if the effect mode or zone is supported.
///
/// On success the aura config file is read to refresh cached values, then
/// the effect is stored and config written to disk.
#[zbus(property)]
async fn set_led_mode_data(&mut self, effect: AuraEffect) -> Result<(), ZbErr> {
let mut ctrl = self.0.lock().await;
if !ctrl.supported_data.basic_modes.contains(&effect.mode)
|| effect.zone != AuraZone::None
&& !ctrl.supported_data.basic_zones.contains(&effect.zone)
{
return Err(ZbErr::NotSupported(format!(
"The Aura effect is not supported: {effect:?}"
)));
}
ctrl.write_effect_and_apply(&effect)?;
if ctrl.config.brightness == LedBrightness::Off {
ctrl.config.brightness = LedBrightness::Med;
}
if ctrl.led_node.has_brightness_control() {
ctrl.led_node
.set_brightness(ctrl.config.brightness.into())?;
}
ctrl.config.set_builtin(effect);
ctrl.config.write();
self.led_mode_invalidate(&self.1).await.ok();
Ok(())
}
/// Get the data set for every mode available
async fn all_mode_data(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
let ctrl = self.0.lock().await;
ctrl.config.builtins.clone()
}
// As property doesn't work for AuraPowerDev (complexity of serialization?)
#[zbus(property)]
async fn led_power(&self) -> LaptopAuraPower {
let ctrl = self.0.lock().await;
ctrl.config.enabled.clone()
}
/// Set a variety of states, input is array of enum.
/// `enabled` sets if the sent array should be disabled or enabled
///
/// For Modern ROG devices the "enabled" flag is ignored.
#[zbus(property)]
async fn set_led_power(&mut self, options: LaptopAuraPower) -> Result<(), ZbErr> {
let mut ctrl = self.0.lock().await;
for opt in options.states {
let zone = opt.zone;
for config in ctrl.config.enabled.states.iter_mut() {
if config.zone == zone {
*config = opt;
}
}
}
ctrl.config.write();
Ok(ctrl.set_power_states().map_err(|e| {
warn!("{}", e);
e
})?)
}
/// On machine that have some form of either per-key keyboard or per-zone
/// this can be used to write custom effects over dbus. The input is a
/// nested `Vec<Vec<8>>` where `Vec<u8>` is a raw USB packet
async fn direct_addressing_raw(&self, data: UsbPackets) -> Result<(), ZbErr> {
let mut ctrl = self.0.lock().await;
ctrl.write_effect_block(&data)?;
Ok(())
}
}
impl CtrlTask for CtrlAuraZbus {
fn zbus_path() -> &'static str {
"/org/asuslinux"
}
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
let load_save =
|start: bool, mut lock: MutexGuard<'_, CtrlKbdLed>| -> Result<(), RogError> {
// If waking up
if !start {
info!("CtrlKbdLedTask reloading brightness and modes");
if lock.led_node.has_brightness_control() {
lock.led_node
.set_brightness(lock.config.brightness.into())
.map_err(|e| {
error!("CtrlKbdLedTask: {e}");
e
})?;
}
lock.write_current_config_mode().map_err(|e| {
error!("CtrlKbdLedTask: {e}");
e
})?;
} else if start {
Self::update_config(&mut lock).map_err(|e| {
error!("CtrlKbdLedTask: {e}");
e
})?;
}
Ok(())
};
let inner1 = self.0.clone();
let inner3 = self.0.clone();
self.create_sys_event_tasks(
move |sleeping| {
let inner1 = inner1.clone();
async move {
let lock = inner1.lock().await;
load_save(sleeping, lock).unwrap(); // unwrap as we want to
// bomb out of the task
}
},
move |_shutting_down| {
let inner3 = inner3.clone();
async move {
let lock = inner3.lock().await;
load_save(false, lock).unwrap(); // unwrap as we want to
// bomb out of the task
}
},
move |_lid_closed| {
// on lid change
async move {}
},
move |_power_plugged| {
// power change
async move {}
},
)
.await;
let ctrl2 = self.0.clone();
let ctrl = self.0.lock().await;
if ctrl.led_node.has_brightness_control() {
let watch = ctrl.led_node.monitor_brightness()?;
tokio::spawn(async move {
let mut buffer = [0; 32];
watch
.into_event_stream(&mut buffer)
.unwrap()
.for_each(|_| async {
if let Some(lock) = ctrl2.try_lock() {
load_save(true, lock).unwrap(); // unwrap as we want
// to
// bomb out of the
// task
}
})
.await;
});
}
Ok(())
}
}
impl crate::Reloadable for CtrlAuraZbus {
async fn reload(&mut self) -> Result<(), RogError> {
let mut ctrl = self.0.lock().await;
ctrl.fix_ally_power()?;
debug!("reloading keyboard mode");
ctrl.write_current_config_mode()?;
debug!("reloading power states");
ctrl.set_power_states().map_err(|err| warn!("{err}")).ok();
Ok(())
}
}
+3 -2
View File
@@ -10,7 +10,8 @@ use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::{find_fan_curve_node, FanCurvePU, FanCurveProfiles};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use zbus::{interface, Connection, SignalContext};
use zbus::object_server::SignalEmitter;
use zbus::{interface, Connection};
use crate::error::RogError;
use crate::{CtrlTask, CONFIG_PATH_BASE};
@@ -234,7 +235,7 @@ impl CtrlTask for CtrlFanCurveZbus {
FAN_CURVE_ZBUS_PATH
}
async fn create_tasks(&self, _signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
async fn create_tasks(&self, _signal_ctxt: SignalEmitter<'static>) -> Result<(), RogError> {
let watch_throttle_thermal_policy = self.platform.monitor_throttle_thermal_policy()?;
let platform = self.platform.clone();
let config = self.config.clone();
+68 -6
View File
@@ -10,7 +10,8 @@ use rog_platform::platform::{GpuMode, Properties, RogPlatform, ThrottlePolicy};
use rog_platform::power::AsusPower;
use zbus::export::futures_util::lock::Mutex;
use zbus::fdo::Error as FdoErr;
use zbus::{interface, Connection, SignalContext};
use zbus::object_server::SignalEmitter;
use zbus::{interface, Connection};
use crate::config::Config;
use crate::error::RogError;
@@ -44,7 +45,7 @@ macro_rules! platform_set_value {
concat_idents::concat_idents!(set = set_, $property {
$self.platform.set($new_value).map_err(|err| {
error!("RogPlatform: {} {err}", $prop_name);
FdoErr::NotSupported(format!("RogPlatform: {} {err}", $prop_name))
FdoErr::Failed(format!("RogPlatform: {} {err}", $prop_name))
})?;
});
let mut lock = $self.config.lock().await;
@@ -93,7 +94,7 @@ impl CtrlPlatform {
pub fn new(
config: Arc<Mutex<Config>>,
config_path: &Path,
signal_context: SignalContext<'static>,
signal_context: SignalEmitter<'static>,
) -> Result<Self, RogError> {
// let attrs = FirmwareAttributes::new();
let platform = RogPlatform::new()?;
@@ -180,6 +181,24 @@ impl CtrlPlatform {
Ok(())
}
async fn restore_charge_limit(&self) {
let limit = self.config.lock().await.base_charge_control_end_threshold;
if limit > 0
&& std::mem::replace(
&mut self.config.lock().await.charge_control_end_threshold,
limit,
) != limit
{
self.power
.set_charge_control_end_threshold(limit)
.map_err(|e| {
error!("Couldn't restore charge limit: {e}");
})
.ok();
self.config.lock().await.write();
}
}
async fn run_ac_or_bat_cmd(&self, power_plugged: bool) {
let prog: Vec<String> = if power_plugged {
// AC ONLINE
@@ -352,10 +371,24 @@ impl CtrlPlatform {
}
self.power.set_charge_control_end_threshold(limit)?;
self.config.lock().await.charge_control_end_threshold = limit;
self.config.lock().await.base_charge_control_end_threshold = limit;
self.config.lock().await.write();
Ok(())
}
async fn one_shot_full_charge(&self) -> Result<(), FdoErr> {
let base_limit = std::mem::replace(
&mut self.config.lock().await.charge_control_end_threshold,
100,
);
if base_limit != 100 {
self.power.set_charge_control_end_threshold(100)?;
self.config.lock().await.base_charge_control_end_threshold = base_limit;
self.config.lock().await.write();
}
Ok(())
}
#[zbus(property)]
fn gpu_mux_mode(&self) -> Result<u8, FdoErr> {
self.platform.get_gpu_mux_mode().map_err(|err| {
@@ -384,7 +417,7 @@ impl CtrlPlatform {
/// If fan-curves are supported will also activate a fan curve for profile.
async fn next_throttle_thermal_policy(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
#[zbus(signal_context)] ctxt: SignalEmitter<'_>,
) -> Result<(), FdoErr> {
let policy: ThrottlePolicy =
platform_get_value!(self, throttle_thermal_policy, "throttle_thermal_policy")
@@ -715,19 +748,24 @@ impl ReloadAndNotify for CtrlPlatform {
/// Called on config file changed externally
async fn reload_and_notify(
&mut self,
signal_context: &SignalContext<'static>,
signal_context: &SignalEmitter<'static>,
data: Self::Data,
) -> Result<(), RogError> {
let mut config = self.config.lock().await;
if *config != data {
info!("asusd.ron updated externally, reloading and updating internal copy");
let mut base_charge_control_end_threshold = None;
if self.power.has_charge_control_end_threshold() {
let limit = data.charge_control_end_threshold;
warn!("setting charge_control_end_threshold to {limit}");
self.power.set_charge_control_end_threshold(limit)?;
self.charge_control_end_threshold_changed(signal_context)
.await?;
base_charge_control_end_threshold = (config.base_charge_control_end_threshold > 0)
.then_some(config.base_charge_control_end_threshold)
.or(Some(limit));
}
if self.platform.has_throttle_thermal_policy()
@@ -776,6 +814,8 @@ impl ReloadAndNotify for CtrlPlatform {
ppt_reload_and_notify!(nv_temp_target, "nv_temp_target");
*config = data;
config.base_charge_control_end_threshold =
base_charge_control_end_threshold.unwrap_or_default();
}
Ok(())
@@ -786,6 +826,7 @@ impl crate::Reloadable for CtrlPlatform {
async fn reload(&mut self) -> Result<(), RogError> {
info!("Begin Platform settings restore");
if self.power.has_charge_control_end_threshold() {
// self.restore_charge_limit().await;
let limit = self.config.lock().await.charge_control_end_threshold;
info!("reloading charge_control_end_threshold to {limit}");
self.power.set_charge_control_end_threshold(limit)?;
@@ -878,7 +919,7 @@ impl CtrlTask for CtrlPlatform {
PLATFORM_ZBUS_PATH
}
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
async fn create_tasks(&self, signal_ctxt: SignalEmitter<'static>) -> Result<(), RogError> {
let platform1 = self.clone();
let platform2 = self.clone();
let platform3 = self.clone();
@@ -946,6 +987,23 @@ impl CtrlTask for CtrlPlatform {
})
.ok();
}
if shutting_down
&& platform2.power.has_charge_control_end_threshold()
&& lock.base_charge_control_end_threshold > 0
{
info!("RogPlatform restoring charge_control_end_threshold");
platform2
.power
.set_charge_control_end_threshold(
lock.base_charge_control_end_threshold,
)
.map_err(|err| {
warn!("CtrlCharge: charge_control_end_threshold {}", err);
err
})
.ok();
}
}
},
move |_lid_closed| {
@@ -963,6 +1021,10 @@ impl CtrlTask for CtrlPlatform {
.await;
}
platform3.run_ac_or_bat_cmd(power_plugged).await;
// In case one-shot charge was used, restore the old charge limit
if platform3.power.has_charge_control_end_threshold() && !power_plugged {
platform3.restore_charge_limit().await;
}
}
},
)
-100
View File
@@ -1,100 +0,0 @@
pub mod config;
pub mod trait_impls;
use config_traits::{StdConfig, StdConfigLoad};
use log::info;
use rog_platform::hid_raw::HidRaw;
use rog_platform::usb_raw::USBRaw;
use rog_slash::error::SlashError;
use rog_slash::usb::{get_maybe_slash_type, pkt_set_mode, pkt_set_options, pkts_for_init};
use rog_slash::{SlashMode, SlashType};
use crate::ctrl_slash::config::SlashConfig;
use crate::error::RogError;
enum Node {
Usb(USBRaw),
Hid(HidRaw),
}
impl Node {
pub fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
// TODO: map and pass on errors
match self {
Node::Usb(u) => {
u.write_bytes(message).ok();
}
Node::Hid(h) => {
h.write_bytes(message).ok();
}
}
Ok(())
}
}
pub struct CtrlSlash {
node: Node,
config: SlashConfig,
}
impl CtrlSlash {
#[inline]
pub fn new() -> Result<CtrlSlash, RogError> {
let slash_type = get_maybe_slash_type()?;
if matches!(slash_type, SlashType::Unsupported) {
info!("No Slash capable laptop found");
return Err(RogError::Slash(SlashError::NoDevice));
}
let usb = USBRaw::new(rog_slash::usb::PROD_ID).ok();
let hid = HidRaw::new(rog_slash::usb::PROD_ID_STR).ok();
let node = if usb.is_some() {
unsafe { Node::Usb(usb.unwrap_unchecked()) }
} else if hid.is_some() {
unsafe { Node::Hid(hid.unwrap_unchecked()) }
} else {
return Err(RogError::Slash(SlashError::NoDevice));
};
let ctrl = CtrlSlash {
node,
config: SlashConfig::new().load(),
};
ctrl.do_initialization()?;
Ok(ctrl)
}
fn do_initialization(&self) -> Result<(), RogError> {
let init_packets = pkts_for_init();
self.node.write_bytes(&init_packets[0])?;
self.node.write_bytes(&init_packets[1])?;
// Apply config upon initialization
let option_packets = pkt_set_options(
self.config.slash_enabled,
self.config.slash_brightness,
self.config.slash_interval,
);
self.node.write_bytes(&option_packets)?;
let mode_packets = pkt_set_mode(self.config.slash_mode);
self.node.write_bytes(&mode_packets[0])?;
self.node.write_bytes(&mode_packets[1])?;
Ok(())
}
pub fn set_options(&self, enabled: bool, brightness: u8, interval: u8) -> Result<(), RogError> {
let command_packets = pkt_set_options(enabled, brightness, interval);
self.node.write_bytes(&command_packets)?;
Ok(())
}
pub fn set_slash_mode(&self, slash_mode: SlashMode) -> Result<(), RogError> {
let command_packets = pkt_set_mode(slash_mode);
self.node.write_bytes(&command_packets[0])?;
self.node.write_bytes(&command_packets[1])?;
Ok(())
}
}
-165
View File
@@ -1,165 +0,0 @@
use std::sync::Arc;
use config_traits::StdConfig;
use log::warn;
use rog_slash::usb::{pkt_set_mode, pkt_set_options};
use rog_slash::{DeviceState, SlashMode};
use zbus::export::futures_util::lock::Mutex;
use zbus::{interface, Connection, SignalContext};
use crate::ctrl_slash::CtrlSlash;
use crate::error::RogError;
pub const SLASH_ZBUS_NAME: &str = "Slash";
pub const SLASH_ZBUS_PATH: &str = "/org/asuslinux";
#[derive(Clone)]
pub struct CtrlSlashZbus(pub Arc<Mutex<CtrlSlash>>);
/// The struct with the main dbus methods requires this trait
impl crate::ZbusRun for CtrlSlashZbus {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, SLASH_ZBUS_PATH, server).await;
}
}
#[interface(name = "org.asuslinux.Slash")]
impl CtrlSlashZbus {
/// Get enabled or not
#[zbus(property)]
async fn enabled(&self) -> bool {
let lock = self.0.lock().await;
lock.config.slash_enabled
}
/// Set enabled true or false
#[zbus(property)]
async fn set_enabled(&self, enabled: bool) {
let mut lock = self.0.lock().await;
let brightness = if enabled && lock.config.slash_brightness == 0 {
0x88
} else {
lock.config.slash_brightness
};
lock.node
.write_bytes(&pkt_set_options(
enabled,
brightness,
lock.config.slash_interval,
))
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
lock.config.slash_enabled = enabled;
lock.config.slash_brightness = brightness;
lock.config.write();
}
/// Get brightness level
#[zbus(property)]
async fn brightness(&self) -> u8 {
let lock = self.0.lock().await;
lock.config.slash_brightness
}
/// Set brightness level
#[zbus(property)]
async fn set_brightness(&self, brightness: u8) {
let mut lock = self.0.lock().await;
let enabled = brightness > 0;
lock.node
.write_bytes(&pkt_set_options(
enabled,
brightness,
lock.config.slash_interval,
))
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
lock.config.slash_enabled = enabled;
lock.config.slash_brightness = brightness;
lock.config.write();
}
#[zbus(property)]
async fn interval(&self) -> u8 {
let lock = self.0.lock().await;
lock.config.slash_interval
}
/// Set interval between slash animations (0-255)
#[zbus(property)]
async fn set_interval(&self, interval: u8) {
let mut lock = self.0.lock().await;
lock.node
.write_bytes(&pkt_set_options(
lock.config.slash_enabled,
lock.config.slash_brightness,
interval,
))
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
lock.config.slash_interval = interval;
lock.config.write();
}
#[zbus(property)]
async fn slash_mode(&self) -> u8 {
let lock = self.0.lock().await;
lock.config.slash_interval
}
/// Set interval between slash animations (0-255)
#[zbus(property)]
async fn set_slash_mode(&self, slash_mode: SlashMode) {
let mut lock = self.0.lock().await;
let command_packets = pkt_set_mode(slash_mode);
lock.node
.write_bytes(&command_packets[0])
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
lock.node
.write_bytes(&command_packets[1])
.map_err(|err| {
warn!("ctrl_slash::set_options {}", err);
})
.ok();
lock.config.slash_mode = slash_mode;
lock.config.write();
}
/// Get the device state as stored by asusd
// #[zbus(property)]
async fn device_state(&self) -> DeviceState {
let lock = self.0.lock().await;
DeviceState::from(&lock.config)
}
}
impl crate::CtrlTask for CtrlSlashZbus {
fn zbus_path() -> &'static str {
SLASH_ZBUS_PATH
}
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
Ok(())
}
}
impl crate::Reloadable for CtrlSlashZbus {
async fn reload(&mut self) -> Result<(), RogError> {
Ok(())
}
}
+4 -33
View File
@@ -4,14 +4,10 @@ use std::sync::Arc;
use ::zbus::export::futures_util::lock::Mutex;
use ::zbus::Connection;
use asusd::aura_manager::DeviceManager;
use asusd::config::Config;
use asusd::ctrl_anime::trait_impls::CtrlAnimeZbus;
use asusd::ctrl_anime::CtrlAnime;
use asusd::ctrl_aura::manager::AuraManager;
use asusd::ctrl_fancurves::CtrlFanCurveZbus;
use asusd::ctrl_platform::CtrlPlatform;
use asusd::ctrl_slash::trait_impls::CtrlSlashZbus;
use asusd::ctrl_slash::CtrlSlash;
use asusd::{print_board_info, start_tasks, CtrlTask, DBUS_NAME};
use config_traits::{StdConfig, StdConfigLoad1};
use log::{error, info};
@@ -25,11 +21,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.parse_default_env()
.target(env_logger::Target::Stdout)
.format_timestamp(None)
.filter_level(log::LevelFilter::Debug)
.init();
let is_service = match env::var_os("IS_SERVICE") {
Some(val) => val == "1",
None => false,
None => true,
};
if !is_service {
@@ -96,33 +93,7 @@ async fn start_daemon() -> Result<(), Box<dyn Error>> {
}
}
match CtrlAnime::new() {
Ok(ctrl) => {
let zbus = CtrlAnimeZbus(Arc::new(Mutex::new(ctrl)));
let sig_ctx = CtrlAnimeZbus::signal_context(&connection)?;
start_tasks(zbus, &mut connection, sig_ctx).await?;
}
Err(err) => {
info!("AniMe control: {}", err);
}
}
match CtrlSlash::new() {
Ok(ctrl) => {
let zbus = CtrlSlashZbus(Arc::new(Mutex::new(ctrl)));
// Currently, the Slash has no need for a loop watching power events, however,
// it could be cool to have the slash do some power-on/off animation
// (It has a built-in power on animation which plays when u plug in the power
// supply)
let sig_ctx = CtrlSlashZbus::signal_context(&connection)?;
start_tasks(zbus, &mut connection, sig_ctx).await?;
}
Err(err) => {
info!("AniMe control: {}", err);
}
}
let _ = AuraManager::new(connection.clone()).await?;
let _ = DeviceManager::new(connection.clone()).await?;
// Request dbus name after finishing initalizing all functions
connection.request_name(DBUS_NAME).await?;
+18 -16
View File
@@ -1,17 +1,17 @@
#![deny(unused_must_use)]
/// Configuration loading, saving
pub mod config;
/// Control of anime matrix display
pub mod ctrl_anime;
/// Keyboard LED brightness control, RGB, and LED display modes
pub mod ctrl_aura;
/// Control platform profiles + fan-curves if available
pub mod ctrl_fancurves;
/// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
pub mod ctrl_platform;
/// Control of Slash led bar
pub mod ctrl_slash;
pub mod aura_anime;
pub mod aura_laptop;
pub mod aura_manager;
pub mod aura_scsi;
pub mod aura_slash;
pub mod aura_types;
pub mod error;
use std::future::Future;
@@ -22,8 +22,10 @@ use futures_lite::stream::StreamExt;
use log::{debug, info, warn};
use logind_zbus::manager::ManagerProxy;
use tokio::time::sleep;
use zbus::object_server::{Interface, SignalEmitter};
use zbus::proxy::CacheProperties;
use zbus::zvariant::ObjectPath;
use zbus::{CacheProperties, Connection, SignalContext};
use zbus::Connection;
use crate::error::RogError;
@@ -39,7 +41,7 @@ pub static DBUS_IFACE: &str = "org.asuslinux.Daemon";
/// methods to be available:
/// - `<name>() -> SomeValue`, functionally is a getter, but is allowed to have
/// side effects.
/// - `notify_<name>(SignalContext, SomeValue)`
/// - `notify_<name>(SignalEmitter, SomeValue)`
///
/// In most cases if `SomeValue` is stored in a config then `<name>()` getter is
/// expected to update it. The getter should *never* write back to the path or
@@ -60,7 +62,7 @@ macro_rules! task_watch_item {
concat_idents::concat_idents!(fn_name = watch_, $name {
async fn fn_name(
&self,
signal_ctxt: SignalContext<'static>,
signal_ctxt: SignalEmitter<'static>,
) -> Result<(), RogError> {
use zbus::export::futures_util::StreamExt;
@@ -100,7 +102,7 @@ macro_rules! task_watch_item_notify {
concat_idents::concat_idents!(fn_name = watch_, $name {
async fn fn_name(
&self,
signal_ctxt: SignalContext<'static>,
signal_ctxt: SignalEmitter<'static>,
) -> Result<(), RogError> {
use zbus::export::futures_util::StreamExt;
@@ -143,7 +145,7 @@ pub trait ReloadAndNotify {
fn reload_and_notify(
&mut self,
signal_context: &SignalContext<'static>,
signal_context: &SignalEmitter<'static>,
data: Self::Data,
) -> impl Future<Output = Result<(), RogError>> + Send;
}
@@ -152,7 +154,7 @@ pub trait ZbusRun {
fn add_to_server(self, server: &mut Connection) -> impl Future<Output = ()> + Send;
fn add_to_server_helper(
iface: impl zbus::Interface,
iface: impl Interface,
path: &str,
server: &mut Connection,
) -> impl Future<Output = ()> + Send {
@@ -174,8 +176,8 @@ pub trait ZbusRun {
pub trait CtrlTask {
fn zbus_path() -> &'static str;
fn signal_context(connection: &Connection) -> Result<SignalContext<'static>, zbus::Error> {
SignalContext::new(connection, Self::zbus_path())
fn signal_context(connection: &Connection) -> Result<SignalEmitter<'static>, zbus::Error> {
SignalEmitter::new(connection, Self::zbus_path())
}
/// Implement to set up various tasks that may be required, using the
@@ -183,7 +185,7 @@ pub trait CtrlTask {
/// separate thread.
fn create_tasks(
&self,
signal: SignalContext<'static>,
signal: SignalEmitter<'static>,
) -> impl Future<Output = Result<(), RogError>> + Send;
// /// Create a timed repeating task
@@ -297,7 +299,7 @@ pub trait GetSupported {
pub async fn start_tasks<T>(
mut zbus: T,
connection: &mut Connection,
signal_ctx: SignalContext<'static>,
signal_ctx: SignalEmitter<'static>,
) -> Result<(), RogError>
where
T: ZbusRun + Reloadable + CtrlTask + Clone,
-3
View File
@@ -13,6 +13,3 @@ serde.workspace = true
ron.workspace = true
log.workspace = true
[dev-dependencies]
cargo-husky.workspace = true
+1
View File
@@ -8,6 +8,7 @@ ENV{DMI_FAMILY}=="*Zephyrus*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*Strix*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*Vivo*ook*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*Zenbook*", GOTO="asusd_start"
ENV{DMI_FAMILY}=="*ProArt*", GOTO="asusd_start"
# No match so
GOTO="asusd_end"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 75 KiB

-4
View File
@@ -46,11 +46,7 @@ BuildRequires: cmake
BuildRequires: rust
BuildRequires: rust-std-static
BuildRequires: pkgconfig(expat)
BuildRequires: pkgconfig(gbm)
BuildRequires: pkgconfig(dbus-1)
BuildRequires: pkgconfig(libdrm)
BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(libseat)
BuildRequires: pkgconfig(libudev)
BuildRequires: pkgconfig(xkbcommon)
BuildRequires: pkgconfig(libzstd)
+1 -3
View File
@@ -1,4 +1,4 @@
use log::{info, warn};
use log::warn;
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Clone)]
pub struct DMIID {
@@ -33,8 +33,6 @@ impl DMIID {
})?;
if let Some(device) = (result).next() {
info!("Found dmi ID info at {:?}", device.sysname());
return Ok(Self {
id_model: device
.property_value("ID_MODEL")
-6
View File
@@ -35,9 +35,3 @@ typeshare.workspace = true
zbus = { workspace = true, optional = true }
dmi_id = { path = "../dmi-id", optional = true }
[dev-dependencies]
cargo-husky.workspace = true
[package.metadata.cargo-machete]
ignored = ["serde"]
+16 -1
View File
@@ -3,6 +3,7 @@ use std::str::FromStr;
use std::thread::sleep;
use std::time::{Duration, Instant};
use dmi_id::DMIID;
use log::info;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
@@ -57,11 +58,12 @@ pub struct DeviceState {
#[typeshare]
#[cfg_attr(feature = "dbus", derive(Type), zvariant(signature = "s"))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub enum AnimeType {
GA401,
GA402,
GU604,
#[default]
Unsupported,
}
@@ -79,6 +81,19 @@ impl FromStr for AnimeType {
}
impl AnimeType {
pub fn from_dmi() -> Self {
let board_name = DMIID::new().unwrap_or_default().board_name.to_uppercase();
if board_name.contains("GA401I") || board_name.contains("GA401Q") {
AnimeType::GA401
} else if board_name.contains("GA402R") || board_name.contains("GA402X") {
AnimeType::GA402
} else if board_name.contains("GU604V") {
AnimeType::GU604
} else {
AnimeType::Unsupported
}
}
/// The width of diagonal images
pub fn width(&self) -> usize {
match self {
+7 -7
View File
@@ -247,19 +247,19 @@ impl From<AnimShutdown> for i32 {
///
/// The currently known USB device is `19b6`.
#[inline]
pub fn get_maybe_anime_type() -> Result<AnimeType, AnimeError> {
let dmi = DMIID::new().map_err(|_| AnimeError::NoDevice)?; // TODO: better error
pub fn get_anime_type() -> AnimeType {
let dmi = DMIID::new().unwrap_or_default();
let board_name = dmi.board_name;
if board_name.contains("GA401I") || board_name.contains("GA401Q") {
return Ok(AnimeType::GA401);
AnimeType::GA401
} else if board_name.contains("GA402R") || board_name.contains("GA402X") {
return Ok(AnimeType::GA402);
AnimeType::GA402
} else if board_name.contains("GU604V") {
return Ok(AnimeType::GU604);
AnimeType::GU604
} else {
AnimeType::Unsupported
}
log::warn!("AniMe Matrix device found but could be a slash");
Ok(AnimeType::Unsupported)
}
/// Get the two device initialization packets. These are required for device
-3
View File
@@ -26,6 +26,3 @@ log.workspace = true
typeshare.workspace = true
ron = { version = "*", optional = true }
[dev-dependencies]
cargo-husky.workspace = true
+28 -1
View File
@@ -8,6 +8,15 @@
advanced_type: None,
power_zones: [Keyboard],
),
(
device_name: "FA506N",
product_id: "",
layout_name: "fa506i",
basic_modes: [Static, Breathe, RainbowCycle],
basic_zones: [],
advanced_type: None,
power_zones: [Keyboard],
),
(
device_name: "FA506Q",
product_id: "",
@@ -402,7 +411,7 @@
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Pulse, Comet, Flash],
basic_zones: [],
advanced_type: PerKey,
power_zones: [Keyboard],
power_zones: [Keyboard, Lightbar, Logo, Lid],
),
(
device_name: "G814J",
@@ -521,6 +530,15 @@
advanced_type: None,
power_zones: [Keyboard],
),
(
device_name: "GA605W",
product_id: "",
layout_name: "ga401q",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Pulse],
basic_zones: [],
advanced_type: Zoned([SingleZone]),
power_zones: [Keyboard],
),
(
device_name: "GL503",
product_id: "",
@@ -638,6 +656,15 @@
advanced_type: Zoned([SingleZone]),
power_zones: [Keyboard],
),
(
device_name: "GA605W",
product_id: "",
layout_name: "ga401q",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Pulse],
basic_zones: [],
advanced_type: Zoned([SingleZone]),
power_zones: [Keyboard],
),
(
device_name: "GV301Q",
product_id: "",
+40 -64
View File
@@ -7,7 +7,7 @@ use typeshare::typeshare;
use zbus::zvariant::{OwnedValue, Type, Value};
use crate::error::Error;
use crate::LED_MSG_LEN;
use crate::AURA_LAPTOP_LED_MSG_LEN;
#[typeshare]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -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:
@@ -552,9 +502,9 @@ impl AuraEffect {
/// |---|---|-----|-----|---------|------|----------|---|-----------|
/// |5d |b3 |Zone |Mode |Colour 1 |Speed |Direction |00 |Colour 2 |
/// ```
impl From<&AuraEffect> for [u8; LED_MSG_LEN] {
impl From<&AuraEffect> for [u8; AURA_LAPTOP_LED_MSG_LEN] {
fn from(aura: &AuraEffect) -> Self {
let mut msg = [0u8; LED_MSG_LEN];
let mut msg = [0u8; AURA_LAPTOP_LED_MSG_LEN];
msg[0] = 0x5d;
msg[1] = 0xb3;
msg[2] = aura.zone as u8;
@@ -573,7 +523,7 @@ impl From<&AuraEffect> for [u8; LED_MSG_LEN] {
impl From<&AuraEffect> for Vec<u8> {
fn from(aura: &AuraEffect) -> Self {
let mut msg = vec![0u8; LED_MSG_LEN];
let mut msg = vec![0u8; AURA_LAPTOP_LED_MSG_LEN];
msg[0] = 0x5d;
msg[1] = 0xb3;
msg[2] = aura.zone as u8;
@@ -592,7 +542,9 @@ impl From<&AuraEffect> for Vec<u8> {
#[cfg(test)]
mod tests {
use crate::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, LED_MSG_LEN};
use crate::{
AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, AURA_LAPTOP_LED_MSG_LEN,
};
#[test]
fn check_led_static_packet() {
@@ -608,7 +560,7 @@ mod tests {
speed: Speed::Med,
direction: Direction::Right,
};
let ar = <[u8; LED_MSG_LEN]>::from(&st);
let ar = <[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st);
println!("{:02x?}", ar);
let check = [
@@ -636,7 +588,10 @@ mod tests {
0x5d, 0xb3, 0x01, 0x00, 0xff, 0x00, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
st.zone = AuraZone::Key2;
st.colour1 = Colour {
@@ -648,7 +603,10 @@ mod tests {
0x5d, 0xb3, 0x02, 0x00, 0xff, 0xff, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
st.zone = AuraZone::Key3;
st.colour1 = Colour {
@@ -660,7 +618,10 @@ mod tests {
0x5d, 0xb3, 0x03, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
st.zone = AuraZone::Key4;
st.colour1 = Colour {
@@ -672,7 +633,10 @@ mod tests {
0x5d, 0xb3, 0x04, 0x00, 0xff, 0x00, 0xff, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
st.zone = AuraZone::Logo;
st.colour1 = Colour {
@@ -684,7 +648,10 @@ mod tests {
0x5d, 0xb3, 0x05, 0x00, 0x2c, 0xff, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
st.zone = AuraZone::BarLeft;
st.colour1 = Colour {
@@ -696,7 +663,10 @@ mod tests {
0x5d, 0xb3, 0x06, 0x00, 0xff, 0x00, 0x00, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
st.zone = AuraZone::BarRight;
st.colour1 = Colour {
@@ -708,13 +678,19 @@ mod tests {
0x5d, 0xb3, 0x07, 0x00, 0xff, 0x00, 0xcd, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
st.mode = AuraModeNum::RainbowWave;
let capture = [
0x5d, 0xb3, 0x07, 0x03, 0xff, 0x00, 0xcd, 0xe1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0,
];
assert_eq!(<[u8; LED_MSG_LEN]>::from(&st)[..9], capture[..9]);
assert_eq!(
<[u8; AURA_LAPTOP_LED_MSG_LEN]>::from(&st)[..9],
capture[..9]
);
}
}
+2 -2
View File
@@ -12,7 +12,7 @@ pub use breathe::*;
mod static_;
pub use static_::*;
use crate::keyboard::{KeyLayout, LedCode, LedUsbPackets, UsbPackets};
use crate::keyboard::{AuraLaptopUsbPackets, KeyLayout, LedCode, LedUsbPackets};
use crate::Colour;
// static mut RNDINDEX: usize = 0;
@@ -106,7 +106,7 @@ impl AdvancedEffects {
}
}
pub fn create_packets(&self) -> UsbPackets {
pub fn create_packets(&self) -> AuraLaptopUsbPackets {
let mut usb_packets = if self.zoned {
// TODO: figure out if that single byte difference for multizone actually
// matters
+11 -11
View File
@@ -195,7 +195,7 @@ impl LedCode {
/// Represents the per-key raw USB packets
#[typeshare]
pub type UsbPackets = Vec<Vec<u8>>;
pub type AuraLaptopUsbPackets = Vec<Vec<u8>>;
/// A `UsbPackets` contains all data to change the full set of keyboard
/// key colours individually.
@@ -209,7 +209,7 @@ pub type UsbPackets = Vec<Vec<u8>>;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LedUsbPackets {
/// The packet data used to send data to the USB keyboard
usb_packets: UsbPackets,
usb_packets: AuraLaptopUsbPackets,
/// Wether or not this packet collection is zoned. The determines which
/// starting bytes are used and what the indexing is for lightbar RGB
/// colours
@@ -472,22 +472,22 @@ impl LedUsbPackets {
}
#[inline]
pub fn get(&self) -> UsbPackets {
pub fn get(&self) -> AuraLaptopUsbPackets {
self.usb_packets.clone()
}
#[inline]
pub fn get_ref(&self) -> &UsbPackets {
pub fn get_ref(&self) -> &AuraLaptopUsbPackets {
&self.usb_packets
}
#[inline]
pub fn get_mut(&mut self) -> &mut UsbPackets {
pub fn get_mut(&mut self) -> &mut AuraLaptopUsbPackets {
&mut self.usb_packets
}
}
impl From<LedUsbPackets> for UsbPackets {
impl From<LedUsbPackets> for AuraLaptopUsbPackets {
fn from(k: LedUsbPackets) -> Self {
k.usb_packets
}
@@ -643,7 +643,7 @@ impl From<&LedCode> for &str {
#[cfg(test)]
mod tests {
use crate::keyboard::{LedCode, LedUsbPackets, UsbPackets};
use crate::keyboard::{AuraLaptopUsbPackets, LedCode, LedUsbPackets};
macro_rules! colour_check_zoned {
($zone:expr, $pkt_idx_start:expr) => {
@@ -653,7 +653,7 @@ mod tests {
c[1] = 255;
c[2] = 255;
let pkt: UsbPackets = zone.into();
let pkt: AuraLaptopUsbPackets = zone.into();
assert_eq!(pkt[0][$pkt_idx_start], 0xff);
assert_eq!(pkt[0][$pkt_idx_start + 1], 0xff);
assert_eq!(pkt[0][$pkt_idx_start + 2], 0xff);
@@ -663,7 +663,7 @@ mod tests {
#[test]
fn zone_to_packet_check() {
let zone = LedUsbPackets::new_zoned(true);
let pkt: UsbPackets = zone.into();
let pkt: AuraLaptopUsbPackets = zone.into();
assert_eq!(pkt[0][0], 0x5d);
assert_eq!(pkt[0][1], 0xbc);
assert_eq!(pkt[0][2], 0x01);
@@ -686,7 +686,7 @@ mod tests {
#[test]
fn perkey_to_packet_check() {
let per_key = LedUsbPackets::new_per_key();
let pkt: UsbPackets = per_key.into();
let pkt: AuraLaptopUsbPackets = per_key.into();
assert_eq!(pkt[0][0], 0x5d);
assert_eq!(pkt[0][1], 0xbc);
assert_eq!(pkt[0][2], 0x00);
@@ -712,7 +712,7 @@ mod tests {
c[1] = 255;
c[2] = 255;
let pkt: UsbPackets = per_key.into();
let pkt: AuraLaptopUsbPackets = per_key.into();
assert_eq!(pkt[5][30], 0xff); // D, red
assert_eq!(pkt[5][31], 0xff); // D
assert_eq!(pkt[5][32], 0xff); // D
+12 -10
View File
@@ -193,14 +193,14 @@ impl LaptopAuraPower {
// TODO: use support data to setup correct zones
pub fn new(aura_type: AuraDeviceType, support_data: &LedSupportData) -> Self {
match aura_type {
AuraDeviceType::Unknown | AuraDeviceType::Ally | AuraDeviceType::LaptopPost2021 => {
AuraDeviceType::Unknown | AuraDeviceType::Ally | AuraDeviceType::LaptopKeyboard2021 => {
let mut states = Vec::new();
for zone in support_data.power_zones.iter() {
states.push(AuraPowerState::default_for(*zone))
}
Self { states }
}
AuraDeviceType::LaptopPre2021 => {
AuraDeviceType::LaptopKeyboardPre2021 => {
// The older devices are tri-state if have lightbar:
// 1. Keyboard
// 2. Lightbar
@@ -215,10 +215,11 @@ impl LaptopAuraPower {
}
}
}
AuraDeviceType::LaptopTuf => Self {
AuraDeviceType::LaptopKeyboardTuf => Self {
states: vec![AuraPowerState::default_for(PowerZones::Keyboard)],
},
AuraDeviceType::ScsiExtDisk => todo!(),
AuraDeviceType::AnimeOrSlash => todo!(),
}
}
@@ -229,8 +230,8 @@ impl LaptopAuraPower {
}
}
match aura_type {
AuraDeviceType::LaptopPost2021 | AuraDeviceType::Ally => self.new_to_bytes(),
AuraDeviceType::LaptopPre2021 => {
AuraDeviceType::LaptopKeyboard2021 | AuraDeviceType::Ally => self.new_to_bytes(),
AuraDeviceType::LaptopKeyboardPre2021 => {
if self.states.len() == 1 {
self.states
.first()
@@ -249,7 +250,7 @@ impl LaptopAuraPower {
b
}
}
AuraDeviceType::LaptopTuf => self
AuraDeviceType::LaptopKeyboardTuf => self
.states
.first()
.cloned()
@@ -260,6 +261,7 @@ impl LaptopAuraPower {
self.new_to_bytes()
}
AuraDeviceType::ScsiExtDisk => todo!("scsi disk not implemented yet"),
AuraDeviceType::AnimeOrSlash => todo!("anime/slash not implemented yet"),
}
}
}
@@ -309,7 +311,7 @@ mod test {
use crate::{AuraDeviceType, PowerZones};
fn to_binary_string_post2021(power: &LaptopAuraPower) -> String {
let bytes = power.to_bytes(AuraDeviceType::LaptopPost2021);
let bytes = power.to_bytes(AuraDeviceType::LaptopKeyboard2021);
format!(
"{:08b}, {:08b}, {:08b}, {:08b}",
bytes[0], bytes[1], bytes[2], bytes[3]
@@ -327,7 +329,7 @@ mod test {
shutdown: false,
}],
};
let bytes = power.to_bytes(AuraDeviceType::LaptopPre2021);
let bytes = power.to_bytes(AuraDeviceType::LaptopKeyboardPre2021);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x08, 0x00, 0x02, 0x00]);
@@ -340,7 +342,7 @@ mod test {
shutdown: false,
}],
};
let bytes = power.to_bytes(AuraDeviceType::LaptopPre2021);
let bytes = power.to_bytes(AuraDeviceType::LaptopKeyboardPre2021);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0x04, 0x05, 0x02, 0x00]);
@@ -369,7 +371,7 @@ mod test {
},
],
};
let bytes = power.to_bytes(AuraDeviceType::LaptopPre2021);
let bytes = power.to_bytes(AuraDeviceType::LaptopKeyboardPre2021);
println!("{:08b}, {:08b}, {:08b}", bytes[0], bytes[1], bytes[2]);
assert_eq!(bytes, [0xff, 0x1f, 0x000f, 0x00]);
}
+13 -10
View File
@@ -24,7 +24,7 @@ pub mod usb;
pub mod keyboard;
pub const LED_MSG_LEN: usize = 17;
pub const AURA_LAPTOP_LED_MSG_LEN: usize = 17;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const RED: Colour = Colour {
@@ -70,25 +70,26 @@ pub const GRADIENT: [Colour; 7] = [RED, VIOLET, BLUE, TEAL, GREEN, YELLOW, ORANG
pub enum AuraDeviceType {
/// Most new laptops
#[default]
LaptopPost2021 = 0,
LaptopPre2021 = 1,
LaptopTuf = 2,
LaptopKeyboard2021 = 0,
LaptopKeyboardPre2021 = 1,
LaptopKeyboardTuf = 2,
ScsiExtDisk = 3,
Ally = 4,
AnimeOrSlash = 5,
Unknown = 255,
}
impl AuraDeviceType {
pub fn is_old_laptop(&self) -> bool {
*self == Self::LaptopPre2021
*self == Self::LaptopKeyboardPre2021
}
pub fn is_tuf_laptop(&self) -> bool {
*self == Self::LaptopTuf
*self == Self::LaptopKeyboardTuf
}
pub fn is_new_laptop(&self) -> bool {
*self == Self::LaptopPost2021
*self == Self::LaptopKeyboard2021
}
pub fn is_ally(&self) -> bool {
@@ -103,11 +104,13 @@ impl AuraDeviceType {
impl From<&str> for AuraDeviceType {
fn from(s: &str) -> Self {
match s.to_lowercase().trim_start_matches("0x") {
"tuf" => AuraDeviceType::LaptopTuf,
"tuf" => AuraDeviceType::LaptopKeyboardTuf,
"1932" => AuraDeviceType::ScsiExtDisk,
"1866" | "18c6" | "1869" | "1854" => Self::LaptopPre2021,
"1866" | "18c6" | "1869" | "1854" => Self::LaptopKeyboardPre2021,
"1abe" | "1b4c" => Self::Ally,
_ => Self::LaptopPost2021,
"19b3" | "193b" => Self::AnimeOrSlash,
"19b6" => Self::LaptopKeyboard2021,
_ => Self::Unknown,
}
}
}
+3 -9
View File
@@ -1,10 +1,4 @@
// Only these two packets must be 17 bytes
pub const LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
pub const LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
/// Writes out the correct byte string for brightness
pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
[
0x5a, 0xba, 0xc5, 0xc4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]
}
pub const AURA_LAPTOP_LED_APPLY: [u8; 17] =
[0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
pub const AURA_LAPTOP_LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+2 -7
View File
@@ -18,9 +18,8 @@ tokio-debug = ["console-subscriber"]
[dependencies]
console-subscriber = { version = "^0.4", optional = true }
nix = { version = "^0.29.0", features = ["fs"] }
tempfile = "3.3.0"
betrayer = { version = "0.2.0" }
ksni = { version = "0.3", default-features = false, features = ["async-io"] }
image = "0.25.5"
asusd = { path = "../asusd" }
config-traits = { path = "../config-traits" }
@@ -51,7 +50,6 @@ default-features = false
features = [
"gettext",
"compat-1-2",
"backend-linuxkms",
"backend-winit-wayland",
"renderer-winit-femtovg",
# "renderer-skia-opengl",
@@ -59,6 +57,3 @@ features = [
[build-dependencies.slint-build]
git = "https://github.com/slint-ui/slint.git"
[dev-dependencies]
cargo-husky.workspace = true
-8
View File
@@ -5,7 +5,6 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Nix(nix::Error),
ConfigLoadFail,
ConfigLockFail,
XdgVars,
@@ -18,7 +17,6 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(err) => write!(f, "Failed to open: {}", err),
Error::Nix(err) => write!(f, "Error: {}", err),
Error::ConfigLoadFail => write!(f, "Failed to load user config"),
Error::ConfigLockFail => write!(f, "Failed to lock user config"),
Error::XdgVars => write!(f, "XDG environment vars appear unset"),
@@ -36,12 +34,6 @@ impl From<std::io::Error> for Error {
}
}
impl From<nix::Error> for Error {
fn from(err: nix::Error) -> Self {
Error::Nix(err)
}
}
impl From<zbus::Error> for Error {
fn from(err: zbus::Error) -> Self {
Error::Zbus(err)
+3 -73
View File
@@ -2,14 +2,8 @@
#![allow(clippy::redundant_clone, clippy::cmp_owned)]
slint::include_modules!();
// Intentionally reexport slint so that GUI consumers don't need to add to
// `Cargo.toml`
use std::fs::{remove_dir_all, File, OpenOptions};
use std::io::{Read, Write};
use std::process::exit;
use std::thread::sleep;
use std::time::Duration;
/// Intentionally reexport slint so that GUI consumers don't need to add to
/// `Cargo.toml`
pub use slint;
pub mod cli_options;
@@ -21,11 +15,7 @@ pub mod notify;
pub mod tray;
pub mod types;
pub mod ui;
use nix::sys::stat;
use nix::unistd;
use tempfile::TempDir;
// use log::{error, info, warn};
pub mod zbus;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const APP_ICON_PATH: &str = "/usr/share/icons/hicolor/512x512/apps/rog-control-center.png";
@@ -42,10 +32,6 @@ pub fn print_versions() {
println!("rog-platform v{}", rog_platform::VERSION);
}
pub const SHOWING_GUI: u8 = 1;
pub const SHOW_GUI: u8 = 2;
pub const QUIT_APP: u8 = 3;
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Page {
AppSettings,
@@ -54,59 +40,3 @@ pub enum Page {
AnimeMatrix,
FanCurves,
}
/// Either exit the process, or return with a refreshed tmp-dir
pub fn on_tmp_dir_exists() -> Result<TempDir, std::io::Error> {
let mut buf = [0u8; 2];
let path = std::env::temp_dir().join("rog-gui");
if path.read_dir()?.next().is_none() {
std::fs::remove_dir_all(path)?;
return tempfile::Builder::new()
.prefix("rog-gui")
.rand_bytes(0)
.tempdir();
}
let mut ipc_file = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(path.join("ipc.pipe"))?;
// If the app is running this ends up stacked on top of SHOWING_GUI
ipc_file.write_all(&[SHOW_GUI, 0])?;
// tiny sleep to give the app a chance to respond
sleep(Duration::from_millis(10));
ipc_file.read_exact(&mut buf).ok();
// First entry is the actual state
if buf[0] == SHOWING_GUI {
ipc_file.write_all(&[SHOWING_GUI, 0])?; // Store state again as we drained the fifo
// Early exit is not an error and we don't want to pass back a dir
#[allow(clippy::exit)]
exit(0);
} else if buf[0] == SHOW_GUI {
remove_dir_all(&path)?;
return tempfile::Builder::new()
.prefix("rog-gui")
.rand_bytes(0)
.tempdir();
}
panic!("Invalid exit or app state");
}
pub fn get_ipc_file() -> Result<File, crate::error::Error> {
let tmp_dir = std::env::temp_dir().join("rog-gui");
let fifo_path = tmp_dir.join("ipc.pipe");
if let Err(e) = unistd::mkfifo(&fifo_path, stat::Mode::S_IRWXU) {
if !matches!(e, nix::errno::Errno::EEXIST) {
Err(e)?
}
}
Ok(OpenOptions::new()
.read(true)
.write(true)
// .truncate(true)
.open(&fifo_path)?)
}
+91 -76
View File
@@ -1,6 +1,5 @@
use std::borrow::BorrowMut;
use std::env::args;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::{Arc, Mutex};
@@ -10,7 +9,7 @@ use std::time::Duration;
use config_traits::{StdConfig, StdConfigLoad1};
use dmi_id::DMIID;
use gumdrop::Options;
use log::{info, LevelFilter};
use log::{info, warn, LevelFilter};
use rog_control_center::cli_options::CliStart;
use rog_control_center::config::Config;
use rog_control_center::error::Result;
@@ -18,41 +17,71 @@ use rog_control_center::notify::start_notifications;
use rog_control_center::slint::ComponentHandle;
use rog_control_center::tray::init_tray;
use rog_control_center::ui::setup_window;
use rog_control_center::{
get_ipc_file, on_tmp_dir_exists, print_versions, MainWindow, QUIT_APP, SHOWING_GUI, SHOW_GUI,
use rog_control_center::zbus::{
AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH,
};
use rog_control_center::{print_versions, MainWindow};
use tokio::runtime::Runtime;
#[tokio::main]
async fn main() -> Result<()> {
#[cfg(feature = "tokio-debug")]
console_subscriber::init();
let mut logger = env_logger::Builder::new();
logger
.filter_level(LevelFilter::Warn)
.parse_default_env()
.target(env_logger::Target::Stdout)
.format_timestamp(None)
.init();
// Try to open a proxy and check for app state first
{
let user_con = zbus::blocking::Connection::session()?;
if let Ok(proxy) = ROGCCZbusProxyBlocking::new(&user_con) {
if let Ok(state) = proxy.state() {
info!("App is already running: {state:?}, opening the window");
// if there is a proxy connection assume the app is already running
proxy.set_state(AppState::MainWindowShouldOpen)?;
std::process::exit(0);
}
}
}
// version checks
let self_version = env!("CARGO_PKG_VERSION");
let conn = zbus::blocking::Connection::system()?;
let proxy = rog_dbus::zbus_platform::PlatformProxyBlocking::new(&conn)?;
let asusd_version = proxy.version().unwrap();
let zbus_con = zbus::blocking::Connection::system()?;
let platform_proxy = rog_dbus::zbus_platform::PlatformProxyBlocking::new(&zbus_con)?;
let asusd_version = platform_proxy.version().unwrap();
if asusd_version != self_version {
println!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}");
return Ok(());
}
// start tokio
let rt = Runtime::new().expect("Unable to create Runtime");
// Enter the runtime so that `tokio::spawn` is available immediately.
let _enter = rt.enter();
#[cfg(feature = "tokio-debug")]
console_subscriber::init();
let state_zbus = ROGCCZbus::new();
let app_state = state_zbus.clone_state();
let _conn = zbus::connection::Builder::session()?
.name(ZBUS_IFACE)?
.serve_at(ZBUS_PATH, state_zbus)?
.build()
.await
.map_err(|err| {
warn!("{}: add_to_server {}", ZBUS_PATH, err);
err
})?;
let dmi = DMIID::new().unwrap_or_default();
let board_name = dmi.board_name;
let prod_family = dmi.product_family;
info!("Running on {board_name}, product: {prod_family}");
let is_rog_ally = prod_family == "RC71L";
// tmp-dir must live to the end of program life
let _tmp_dir = match tempfile::Builder::new()
.prefix("rog-gui")
.rand_bytes(0)
.tempdir()
{
Ok(tmp) => tmp,
Err(_) => on_tmp_dir_exists().unwrap(),
};
let args: Vec<String> = args().skip(1).collect();
let cli_parsed = match CliStart::parse_args_default(&args) {
@@ -66,15 +95,7 @@ async fn main() -> Result<()> {
return Ok(());
}
let mut logger = env_logger::Builder::new();
logger
.filter_level(LevelFilter::Warn)
.parse_default_env()
.target(env_logger::Target::Stdout)
.format_timestamp(None)
.init();
let supported_properties = proxy.supported_properties().unwrap_or_default();
let supported_properties = platform_proxy.supported_properties().unwrap_or_default();
// Startup
let mut config = Config::new().load();
@@ -99,8 +120,6 @@ async fn main() -> Result<()> {
if config.startup_in_background {
config.run_in_background = true;
} else {
get_ipc_file().unwrap().write_all(&[SHOW_GUI, 0]).unwrap();
}
config.write();
@@ -108,10 +127,6 @@ async fn main() -> Result<()> {
let startup_in_background = config.startup_in_background;
let config = Arc::new(Mutex::new(config));
// start tokio
let rt = Runtime::new().expect("Unable to create Runtime");
// Enter the runtime so that `tokio::spawn` is available immediately.
let _enter = rt.enter();
start_notifications(config.clone(), &rt)?;
if enable_tray_icon {
@@ -121,7 +136,11 @@ async fn main() -> Result<()> {
thread_local! { pub static UI: std::cell::RefCell<Option<MainWindow>> = Default::default()};
// i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
let mut do_once = !startup_in_background;
if !startup_in_background {
if let Ok(mut lock) = app_state.lock() {
*lock = AppState::MainWindowShouldOpen;
}
}
if std::env::var("RUST_TRANSLATIONS").is_ok() {
// don't care about content
@@ -133,42 +152,40 @@ async fn main() -> Result<()> {
}
thread::spawn(move || {
let mut buf = [0u8; 2];
// blocks until it is read, typically the read will happen after a second
// process writes to the IPC (so there is data to actually read)
let mut state = AppState::StartingUp;
loop {
if do_once {
buf[0] = SHOW_GUI;
do_once = false;
} else {
get_ipc_file().unwrap().read_exact(&mut buf).unwrap();
// save as a var, don't hold the lock the entire time or deadlocks happen
if let Ok(lock) = app_state.lock() {
state = *lock;
}
if buf[0] == SHOW_GUI {
// There's a balancing act with read/write timing of IPC, there needs to be a
// small sleep after this to give any other process a chance to
// read the IPC before looping
get_ipc_file()
.unwrap()
.write_all(&[SHOWING_GUI, 0])
.unwrap();
if state == AppState::MainWindowShouldOpen {
if let Ok(mut lock) = app_state.lock() {
*lock = AppState::MainWindowOpen;
}
sleep(Duration::from_millis(50));
let config_copy = config.clone();
let app_state_copy = app_state.clone();
slint::invoke_from_event_loop(move || {
UI.with(|ui| {
let app_state_copy = app_state_copy.clone();
let mut ui = ui.borrow_mut();
if let Some(ui) = ui.as_mut() {
ui.window().show().unwrap();
ui.window().on_close_requested(|| {
get_ipc_file().unwrap().write_all(&[0, 0]).unwrap();
ui.window().on_close_requested(move || {
if let Ok(mut lock) = app_state_copy.lock() {
*lock = AppState::MainWindowClosed;
}
slint::CloseRequestResponse::HideWindow
});
} else {
let newui = setup_window(config_copy);
newui.window().show().unwrap();
newui.window().on_close_requested(|| {
get_ipc_file().unwrap().write_all(&[0, 0]).unwrap();
newui.window().on_close_requested(move || {
if let Ok(mut lock) = app_state_copy.lock() {
*lock = AppState::MainWindowClosed;
}
slint::CloseRequestResponse::HideWindow
});
ui.replace(newui);
@@ -176,30 +193,28 @@ async fn main() -> Result<()> {
});
})
.unwrap();
} else {
if buf[1] == QUIT_APP {
slint::quit_event_loop().unwrap();
exit(0);
}
if buf[0] != SHOWING_GUI {
if let Ok(lock) = config.lock() {
if !lock.run_in_background {
slint::quit_event_loop().unwrap();
exit(0);
}
} else if state == AppState::QuitApp {
slint::quit_event_loop().unwrap();
exit(0);
} else if state != AppState::MainWindowOpen {
if let Ok(lock) = config.lock() {
if !lock.run_in_background {
slint::quit_event_loop().unwrap();
exit(0);
}
slint::invoke_from_event_loop(move || {
UI.with(|ui| {
let mut ui = ui.take();
if let Some(ui) = ui.borrow_mut() {
ui.window().hide().unwrap();
}
});
})
.unwrap();
}
slint::invoke_from_event_loop(move || {
UI.with(|ui| {
let mut ui = ui.take();
if let Some(ui) = ui.borrow_mut() {
ui.window().hide().unwrap();
}
});
})
.unwrap();
}
sleep(Duration::from_millis(300));
}
});
+140 -125
View File
@@ -1,23 +1,20 @@
//! A seld-contained tray icon with menus. The control of app<->tray is done via
//! commands over an MPSC channel.
//! A self-contained tray icon with menus.
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::{Arc, Mutex, OnceLock};
use std::thread::sleep;
use std::time::Duration;
use betrayer::{Icon, Menu, MenuItem, TrayEvent, TrayIcon, TrayIconBuilder};
use log::{debug, error, info, warn};
use ksni::{Handle, Icon, TrayMethods};
use log::{info, warn};
use rog_platform::platform::Properties;
use supergfxctl::pci_device::{Device, GfxMode, GfxPower};
use supergfxctl::zbus_proxy::DaemonProxyBlocking as GfxProxy;
use supergfxctl::zbus_proxy::DaemonProxy as GfxProxy;
use versions::Versioning;
use crate::config::Config;
use crate::{get_ipc_file, QUIT_APP, SHOW_GUI};
use crate::zbus::{AppState, ROGCCZbusProxyBlocking};
const TRAY_LABEL: &str = "ROG Control Center";
const TRAY_ICON_PATH: &str = "/usr/share/icons/hicolor/512x512/apps/";
@@ -32,30 +29,6 @@ struct Icons {
static ICONS: OnceLock<Icons> = OnceLock::new();
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum TrayAction {
Open,
Quit,
}
fn open_app() {
if let Ok(mut ipc) = get_ipc_file().map_err(|e| {
error!("ROGTray: get_ipc_file: {}", e);
}) {
debug!("Tray told app to show self");
ipc.write_all(&[SHOW_GUI, 0]).ok();
}
}
fn quit_app() {
if let Ok(mut ipc) = get_ipc_file().map_err(|e| {
error!("ROGTray: get_ipc_file: {}", e);
}) {
debug!("Tray told app to show self");
ipc.write_all(&[QUIT_APP, 0]).ok();
}
}
fn read_icon(file: &Path) -> Icon {
let mut path = PathBuf::from(TRAY_ICON_PATH);
path.push(file);
@@ -63,34 +36,73 @@ fn read_icon(file: &Path) -> Icon {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).unwrap();
Icon::from_png_bytes(&bytes)
.unwrap_or(Icon::from_rgba(vec![255u8; 32 * 32 * 4], 32, 32).unwrap())
}
let mut img = image::load_from_memory_with_format(&bytes, image::ImageFormat::Png)
.expect("icon not found")
.to_rgba8();
for image::Rgba(pixel) in img.pixels_mut() {
// (╯°□°)╯︵ ┻━┻
*pixel = u32::from_be_bytes(*pixel).rotate_right(8).to_be_bytes();
}
fn build_menu() -> Menu<TrayAction> {
Menu::new([
MenuItem::separator(),
MenuItem::button("Open", TrayAction::Open),
MenuItem::button("Quit App", TrayAction::Quit),
])
}
fn do_action(event: TrayEvent<TrayAction>) {
if let TrayEvent::Menu(action) = event {
match action {
TrayAction::Open => open_app(),
TrayAction::Quit => {
quit_app();
exit(0);
}
}
let (width, height) = img.dimensions();
Icon {
width: width as i32,
height: height as i32,
data: img.into_raw(),
}
}
fn set_tray_icon_and_tip(
struct AsusTray {
current_title: String,
current_icon: Icon,
proxy: ROGCCZbusProxyBlocking<'static>,
}
impl ksni::Tray for AsusTray {
fn id(&self) -> String {
TRAY_LABEL.into()
}
fn icon_pixmap(&self) -> Vec<ksni::Icon> {
vec![self.current_icon.clone()]
}
fn title(&self) -> String {
self.current_title.clone()
}
fn status(&self) -> ksni::Status {
ksni::Status::Active
}
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
use ksni::menu::*;
vec![
StandardItem {
label: "Open ROGCC".into(),
icon_name: "rog-control-center".into(),
activate: Box::new(move |s: &mut AsusTray| {
s.proxy.set_state(AppState::MainWindowShouldOpen).ok();
}),
..Default::default()
}
.into(),
MenuItem::Separator,
StandardItem {
label: "Quit ROGCC".into(),
icon_name: "application-exit".into(),
activate: Box::new(|_| std::process::exit(0)),
..Default::default()
}
.into(),
]
}
}
async fn set_tray_icon_and_tip(
mode: GfxMode,
power: GfxPower,
tray: &mut TrayIcon<TrayAction>,
tray: &mut Handle<AsusTray>,
supergfx_active: bool,
) {
if let Some(icons) = ICONS.get() {
@@ -113,16 +125,16 @@ fn set_tray_icon_and_tip(
}
}
};
// *tray = TrayIconBuilder::<TrayAction>::new()
// .with_icon(icon)
// .with_tooltip(format!("ROG: gpu mode = {mode:?}, gpu power = {power:?}"))
// .with_menu(build_menu())
// .build(do_action)
// .map_err(|e| log::error!("Tray unable to be initialised: {e:?}"))
// .unwrap();
tray.set_icon(Some(icon));
tray.set_tooltip(format!("ROG: gpu mode = {mode:?}, gpu power = {power:?}"));
tray.update(|tray: &mut AsusTray| {
dbg!(power);
tray.current_icon = icon;
tray.current_title = format!(
"ROG: gpu mode = {mode:?}, gpu power =
{power:?}"
);
})
.await;
}
}
@@ -142,86 +154,89 @@ fn find_dgpu() -> Option<Device> {
/// The tray is controlled somewhat by `Arc<Mutex<SystemState>>`
pub fn init_tray(_supported_properties: Vec<Properties>, config: Arc<Mutex<Config>>) {
std::thread::spawn(move || {
tokio::spawn(async move {
let user_con = zbus::blocking::Connection::session().unwrap();
let proxy = ROGCCZbusProxyBlocking::new(&user_con).unwrap();
let rog_red = read_icon(&PathBuf::from("asus_notif_red.png"));
if let Ok(mut tray) = TrayIconBuilder::<TrayAction>::new()
.with_icon(rog_red.clone())
.with_tooltip(TRAY_LABEL)
.with_menu(build_menu())
.build(do_action)
let tray = AsusTray {
current_title: TRAY_LABEL.to_string(),
current_icon: rog_red.clone(),
proxy,
};
let mut tray = tray
.spawn_without_dbus_name()
.await
.map_err(|e| {
log::error!(
"Tray unable to be initialised: {e:?}. Do you have a system tray enabled?"
)
})
{
info!("Tray started");
let rog_blue = read_icon(&PathBuf::from("asus_notif_blue.png"));
let rog_green = read_icon(&PathBuf::from("asus_notif_green.png"));
let rog_white = read_icon(&PathBuf::from("asus_notif_white.png"));
let gpu_integrated = read_icon(&PathBuf::from("rog-control-center.png"));
ICONS.get_or_init(|| Icons {
rog_blue,
rog_red: rog_red.clone(),
rog_green,
rog_white,
gpu_integrated,
});
.unwrap();
let mut has_supergfx = false;
let conn = zbus::blocking::Connection::system().unwrap();
if let Ok(gfx_proxy) = GfxProxy::new(&conn) {
match gfx_proxy.mode() {
Ok(_) => {
has_supergfx = true;
if let Ok(version) = gfx_proxy.version() {
if let Some(version) = Versioning::new(&version) {
let curr_gfx = Versioning::new("5.2.0").unwrap();
warn!("supergfxd version = {version}");
if version < curr_gfx {
// Don't allow mode changing if too old a version
warn!("supergfxd found but is too old to use");
has_supergfx = false;
}
info!("Tray started");
let rog_blue = read_icon(&PathBuf::from("asus_notif_blue.png"));
let rog_green = read_icon(&PathBuf::from("asus_notif_green.png"));
let rog_white = read_icon(&PathBuf::from("asus_notif_white.png"));
let gpu_integrated = read_icon(&PathBuf::from("rog-control-center.png"));
ICONS.get_or_init(|| Icons {
rog_blue,
rog_red: rog_red.clone(),
rog_green,
rog_white,
gpu_integrated,
});
let mut has_supergfx = false;
let conn = zbus::Connection::system().await.unwrap();
if let Ok(gfx_proxy) = GfxProxy::new(&conn).await {
match gfx_proxy.mode().await {
Ok(_) => {
has_supergfx = true;
if let Ok(version) = gfx_proxy.version().await {
if let Some(version) = Versioning::new(&version) {
let curr_gfx = Versioning::new("5.2.0").unwrap();
warn!("supergfxd version = {version}");
if version < curr_gfx {
// Don't allow mode changing if too old a version
warn!("supergfxd found but is too old to use");
has_supergfx = false;
}
}
}
Err(e) => warn!("Couldn't get mode form supergfxd: {e:?}"),
}
Err(e) => warn!("Couldn't get mode form supergfxd: {e:?}"),
}
info!("Started ROGTray");
let mut last_power = GfxPower::Unknown;
let dev = find_dgpu();
loop {
sleep(Duration::from_millis(1000));
if let Ok(lock) = config.try_lock() {
if !lock.enable_tray_icon {
return;
}
info!("Started ROGTray");
let mut last_power = GfxPower::Unknown;
let dev = find_dgpu();
loop {
tokio::time::sleep(Duration::from_millis(1000)).await;
if let Ok(lock) = config.try_lock() {
if !lock.enable_tray_icon {
return;
}
if has_supergfx {
if let Ok(mode) = gfx_proxy.mode() {
if let Ok(power) = gfx_proxy.power() {
if last_power != power {
set_tray_icon_and_tip(mode, power, &mut tray, has_supergfx);
last_power = power;
}
}
}
} else if let Some(dev) = dev.as_ref() {
if let Ok(power) = dev.get_runtime_status() {
}
if has_supergfx {
if let Ok(mode) = gfx_proxy.mode().await {
if let Ok(power) = gfx_proxy.power().await {
if last_power != power {
set_tray_icon_and_tip(
GfxMode::Hybrid,
power,
&mut tray,
has_supergfx,
);
set_tray_icon_and_tip(mode, power, &mut tray, has_supergfx).await;
last_power = power;
}
}
}
} else if let Some(dev) = dev.as_ref() {
if let Ok(power) = dev.get_runtime_status() {
if last_power != power {
set_tray_icon_and_tip(GfxMode::Hybrid, power, &mut tray, has_supergfx)
.await;
last_power = power;
}
}
}
}
}
+8 -6
View File
@@ -148,12 +148,13 @@ impl From<LaptopAuraPower> for SlintLaptopAuraPower {
impl From<SlintDeviceType> for AuraDeviceType {
fn from(value: SlintDeviceType) -> Self {
match value {
SlintDeviceType::New => Self::LaptopPost2021,
SlintDeviceType::Old => Self::LaptopPre2021,
SlintDeviceType::Tuf => Self::LaptopTuf,
SlintDeviceType::New => Self::LaptopKeyboard2021,
SlintDeviceType::Old => Self::LaptopKeyboardPre2021,
SlintDeviceType::Tuf => Self::LaptopKeyboardTuf,
SlintDeviceType::ScsiExtDisk => Self::ScsiExtDisk,
SlintDeviceType::Unknown => Self::Unknown,
SlintDeviceType::Ally => Self::Ally,
SlintDeviceType::AnimeOrSlash => Self::AnimeOrSlash,
}
}
}
@@ -161,12 +162,13 @@ impl From<SlintDeviceType> for AuraDeviceType {
impl From<AuraDeviceType> for SlintDeviceType {
fn from(value: AuraDeviceType) -> Self {
match value {
AuraDeviceType::LaptopPost2021 => SlintDeviceType::New,
AuraDeviceType::LaptopPre2021 => SlintDeviceType::Old,
AuraDeviceType::LaptopTuf => SlintDeviceType::Tuf,
AuraDeviceType::LaptopKeyboard2021 => SlintDeviceType::New,
AuraDeviceType::LaptopKeyboardPre2021 => SlintDeviceType::Old,
AuraDeviceType::LaptopKeyboardTuf => SlintDeviceType::Tuf,
AuraDeviceType::ScsiExtDisk => SlintDeviceType::ScsiExtDisk,
AuraDeviceType::Unknown => SlintDeviceType::Unknown,
AuraDeviceType::Ally => SlintDeviceType::Ally,
AuraDeviceType::AnimeOrSlash => SlintDeviceType::AnimeOrSlash,
}
}
}
+1 -1
View File
@@ -89,7 +89,7 @@ pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
let dev_type = aura
.device_type()
.await
.unwrap_or(AuraDeviceType::LaptopPost2021);
.unwrap_or(AuraDeviceType::LaptopKeyboard2021);
log::debug!("Available LED power modes {pow3r:?}");
handle
.upgrade_in_event_loop(move |handle| {
+68
View File
@@ -0,0 +1,68 @@
use std::sync::{Arc, Mutex};
use zbus::zvariant::{OwnedValue, Type, Value};
use zbus::{interface, proxy};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Type, Value, OwnedValue)]
#[zvariant(signature = "u")]
pub enum AppState {
MainWindowOpen = 0,
/// If the app is running, open the main window
MainWindowShouldOpen = 1,
MainWindowClosed = 2,
StartingUp = 3,
QuitApp = 4,
LockFailed = 5,
}
pub struct ROGCCZbus {
state: Arc<Mutex<AppState>>,
}
impl ROGCCZbus {
pub fn new() -> Self {
Self {
state: Arc::new(Mutex::new(AppState::StartingUp)),
}
}
pub fn clone_state(&self) -> Arc<Mutex<AppState>> {
self.state.clone()
}
}
pub const ZBUS_PATH: &str = "/xyz/ljones/rogcc";
pub const ZBUS_IFACE: &str = "xyz.ljones.rogcc";
#[interface(name = "xyz.ljones.rogcc")]
impl ROGCCZbus {
/// Return the device type for this Aura keyboard
#[zbus(property)]
async fn state(&self) -> AppState {
if let Ok(lock) = self.state.try_lock() {
return *lock;
}
AppState::LockFailed
}
#[zbus(property)]
async fn set_state(&self, state: AppState) {
if let Ok(mut lock) = self.state.try_lock() {
*lock = state;
}
}
}
#[proxy(
interface = "xyz.ljones.rogcc",
default_service = "xyz.ljones.rogcc",
default_path = "/xyz/ljones/rogcc"
)]
pub trait ROGCCZbus {
/// EnableDisplay property
#[zbus(property)]
fn state(&self) -> zbus::Result<AppState>;
#[zbus(property)]
fn set_state(&self, state: AppState) -> zbus::Result<()>;
}
@@ -2,7 +2,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-08-30 09:29+0000\n"
"POT-Creation-Date: 2024-12-24 22:29+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"
@@ -12,251 +12,6 @@ msgstr ""
"Language: \n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: rog-control-center/ui/pages/anime.slint:6
msgctxt "Anime Brightness"
msgid "Off"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:7
msgctxt "Anime Brightness"
msgid "Low"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:8
msgctxt "Anime Brightness"
msgid "Med"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:9
msgctxt "Anime Brightness"
msgid "High"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:23
msgctxt "AnimePageData"
msgid "Glitch Construction"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:23
msgctxt "AnimePageData"
msgid "Static Emergence"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:25
msgctxt "AnimePageData"
msgid "Binary Banner Scroll"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:25
msgctxt "AnimePageData"
msgid "Rog Logo Glitch"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:27
msgctxt "AnimePageData"
msgid "Banner Swipe"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:27
msgctxt "AnimePageData"
msgid "Starfield"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:29
msgctxt "AnimePageData"
msgid "Glitch Out"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:29
msgctxt "AnimePageData"
msgid "See Ya"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:50
msgctxt "Anime Brightness"
msgid "Brightness"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:66
msgctxt "PageAnime"
msgid "Enable display"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:74 rog-control-center/ui/pages/anime.slint:97
msgctxt "PageAnime"
msgid "Advanced"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:89
msgctxt "PageAnime"
msgid "Use built-in animations"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:146
msgctxt "PageAnime"
msgid "Set which builtin animations are played"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:150
msgctxt "Anime built-in selection"
msgid "Boot Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:160
msgctxt "Anime built-in selection"
msgid "Running Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:170
msgctxt "Anime built-in selection"
msgid "Sleep Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:180
msgctxt "Anime built-in selection"
msgid "Shutdown Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:220
msgctxt "PageAnime"
msgid "Advanced Display Settings"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:225
msgctxt "PageAnime"
msgid "Off when lid closed"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:234
msgctxt "PageAnime"
msgid "Off when suspended"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:243
msgctxt "PageAnime"
msgid "Off when on battery"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:26
msgctxt "PageAppSettings"
msgid "Run in background after closing"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:34
msgctxt "PageAppSettings"
msgid "Start app in background (UI closed)"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:42
msgctxt "PageAppSettings"
msgid "Enable system tray icon"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:50
msgctxt "PageAppSettings"
msgid "Enable dGPU notifications"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:28
msgctxt "PageAura"
msgid "Brightness"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:39
msgctxt "PageAura"
msgid "Aura mode"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:59
msgctxt "PageAura"
msgid "Colour 1"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:85
msgctxt "PageAura"
msgid "Colour 2"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:119
msgctxt "PageAura"
msgid "Zone"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:142
msgctxt "PageAura"
msgid "Direction"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:164
msgctxt "PageAura"
msgid "Speed"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:185
msgctxt "PageAura"
msgid "Power Settings"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:270
msgctxt "PageAura"
msgid "Power Zones"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:26
msgctxt "FanTab"
msgid "This fan is not avilable on this machine"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:34
msgctxt "FanTab"
msgid "Enabled"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:43
msgctxt "FanTab"
msgid "Apply"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:51
msgctxt "FanTab"
msgid "Cancel"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:59
msgctxt "FanTab"
msgid "Factory Default (all fans)"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:72
msgctxt "PageFans"
msgid "Balanced"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:75 rog-control-center/ui/pages/fans.slint:134 rog-control-center/ui/pages/fans.slint:193
msgctxt "PageFans"
msgid "CPU"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:93 rog-control-center/ui/pages/fans.slint:152 rog-control-center/ui/pages/fans.slint:211
msgctxt "PageFans"
msgid "Mid"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:111 rog-control-center/ui/pages/fans.slint:170 rog-control-center/ui/pages/fans.slint:229
msgctxt "PageFans"
msgid "GPU"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:131
msgctxt "PageFans"
msgid "Performance"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:190
msgctxt "PageFans"
msgid "Quiet"
msgstr ""
#: rog-control-center/ui/pages/system.slint:26
msgctxt "SystemPageData"
msgid "Balanced"
@@ -412,198 +167,443 @@ msgctxt "PageSystem"
msgid "Throttle Policy on AC"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:51
msgctxt "Aura power zone"
msgid "Logo"
#: rog-control-center/ui/pages/aura.slint:28
msgctxt "PageAura"
msgid "Brightness"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:52 rog-control-center/ui/types/aura_types.slint:62
#: rog-control-center/ui/pages/aura.slint:39
msgctxt "PageAura"
msgid "Aura mode"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:59
msgctxt "PageAura"
msgid "Colour 1"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:85
msgctxt "PageAura"
msgid "Colour 2"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:119
msgctxt "PageAura"
msgid "Zone"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:142
msgctxt "PageAura"
msgid "Direction"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:164
msgctxt "PageAura"
msgid "Speed"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:185
msgctxt "PageAura"
msgid "Power Settings"
msgstr ""
#: rog-control-center/ui/pages/aura.slint:270
msgctxt "PageAura"
msgid "Power Zones"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:6
msgctxt "Anime Brightness"
msgid "Off"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:7
msgctxt "Anime Brightness"
msgid "Low"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:8
msgctxt "Anime Brightness"
msgid "Med"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:9
msgctxt "Anime Brightness"
msgid "High"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:23
msgctxt "AnimePageData"
msgid "Glitch Construction"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:23
msgctxt "AnimePageData"
msgid "Static Emergence"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:25
msgctxt "AnimePageData"
msgid "Binary Banner Scroll"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:25
msgctxt "AnimePageData"
msgid "Rog Logo Glitch"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:27
msgctxt "AnimePageData"
msgid "Banner Swipe"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:27
msgctxt "AnimePageData"
msgid "Starfield"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:29
msgctxt "AnimePageData"
msgid "Glitch Out"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:29
msgctxt "AnimePageData"
msgid "See Ya"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:50
msgctxt "Anime Brightness"
msgid "Brightness"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:66
msgctxt "PageAnime"
msgid "Enable display"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:74 rog-control-center/ui/pages/anime.slint:97
msgctxt "PageAnime"
msgid "Advanced"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:89
msgctxt "PageAnime"
msgid "Use built-in animations"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:146
msgctxt "PageAnime"
msgid "Set which builtin animations are played"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:150
msgctxt "Anime built-in selection"
msgid "Boot Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:160
msgctxt "Anime built-in selection"
msgid "Running Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:170
msgctxt "Anime built-in selection"
msgid "Sleep Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:180
msgctxt "Anime built-in selection"
msgid "Shutdown Animation"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:220
msgctxt "PageAnime"
msgid "Advanced Display Settings"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:225
msgctxt "PageAnime"
msgid "Off when lid closed"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:234
msgctxt "PageAnime"
msgid "Off when suspended"
msgstr ""
#: rog-control-center/ui/pages/anime.slint:243
msgctxt "PageAnime"
msgid "Off when on battery"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:26
msgctxt "FanTab"
msgid "This fan is not avilable on this machine"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:34
msgctxt "FanTab"
msgid "Enabled"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:43
msgctxt "FanTab"
msgid "Apply"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:51
msgctxt "FanTab"
msgid "Cancel"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:59
msgctxt "FanTab"
msgid "Factory Default (all fans)"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:72
msgctxt "PageFans"
msgid "Balanced"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:75 rog-control-center/ui/pages/fans.slint:134 rog-control-center/ui/pages/fans.slint:193
msgctxt "PageFans"
msgid "CPU"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:93 rog-control-center/ui/pages/fans.slint:152 rog-control-center/ui/pages/fans.slint:211
msgctxt "PageFans"
msgid "Mid"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:111 rog-control-center/ui/pages/fans.slint:170 rog-control-center/ui/pages/fans.slint:229
msgctxt "PageFans"
msgid "GPU"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:131
msgctxt "PageFans"
msgid "Performance"
msgstr ""
#: rog-control-center/ui/pages/fans.slint:190
msgctxt "PageFans"
msgid "Quiet"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:26
msgctxt "PageAppSettings"
msgid "Run in background after closing"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:34
msgctxt "PageAppSettings"
msgid "Start app in background (UI closed)"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:42
msgctxt "PageAppSettings"
msgid "Enable system tray icon"
msgstr ""
#: rog-control-center/ui/pages/app_settings.slint:50
msgctxt "PageAppSettings"
msgid "Enable dGPU notifications"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:52
msgctxt "Aura power zone"
msgid "Keyboard"
msgid "Logo"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:53 rog-control-center/ui/types/aura_types.slint:63
msgctxt "Aura power zone"
msgid "Lightbar"
msgid "Keyboard"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:54
#: rog-control-center/ui/types/aura_types.slint:54 rog-control-center/ui/types/aura_types.slint:64
msgctxt "Aura power zone"
msgid "Lid"
msgid "Lightbar"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:55
msgctxt "Aura power zone"
msgid "Lid"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:56
msgctxt "Aura power zone"
msgid "Rear Glow"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:56 rog-control-center/ui/types/aura_types.slint:64
#: rog-control-center/ui/types/aura_types.slint:57 rog-control-center/ui/types/aura_types.slint:65
msgctxt "Aura power zone"
msgid "Keyboard and Lightbar"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:57
#: rog-control-center/ui/types/aura_types.slint:58
msgctxt "Aura power zone"
msgid "Ally"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:67
#: rog-control-center/ui/types/aura_types.slint:68
msgctxt "Aura brightness"
msgid "Off"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:68
msgctxt "Aura brightness"
msgid "Low"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:69
msgctxt "Aura brightness"
msgid "Med"
msgid "Low"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:70
msgctxt "Aura brightness"
msgid "High"
msgid "Med"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:75 rog-control-center/ui/types/aura_types.slint:90
msgctxt "Basic aura mode"
msgid "Static"
#: rog-control-center/ui/types/aura_types.slint:71
msgctxt "Aura brightness"
msgid "High"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:76 rog-control-center/ui/types/aura_types.slint:91
msgctxt "Basic aura mode"
msgid "Breathe"
msgid "Static"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:77 rog-control-center/ui/types/aura_types.slint:92
msgctxt "Basic aura mode"
msgid "Strobe"
msgid "Breathe"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:78
#: rog-control-center/ui/types/aura_types.slint:78 rog-control-center/ui/types/aura_types.slint:93
msgctxt "Basic aura mode"
msgid "Rainbow"
msgid "Strobe"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:79
msgctxt "Basic aura mode"
msgid "Star"
msgid "Rainbow"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:80
msgctxt "Basic aura mode"
msgid "Rain"
msgid "Star"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:81
msgctxt "Basic aura mode"
msgid "Highlight"
msgid "Rain"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:82
msgctxt "Basic aura mode"
msgid "Laser"
msgid "Highlight"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:83
msgctxt "Basic aura mode"
msgid "Ripple"
msgid "Laser"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:84
msgctxt "Basic aura mode"
msgid "Nothing"
msgid "Ripple"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:85
msgctxt "Basic aura mode"
msgid "Pulse"
msgid "Nothing"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:86
msgctxt "Basic aura mode"
msgid "Comet"
msgid "Pulse"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:87
msgctxt "Basic aura mode"
msgid "Flash"
msgid "Comet"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:99
msgctxt "Aura zone"
msgid "None"
#: rog-control-center/ui/types/aura_types.slint:88
msgctxt "Basic aura mode"
msgid "Flash"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:100
msgctxt "Aura zone"
msgid "Key1"
msgid "None"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:101
msgctxt "Aura zone"
msgid "Key2"
msgid "Key1"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:102
msgctxt "Aura zone"
msgid "Key3"
msgid "Key2"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:103
msgctxt "Aura zone"
msgid "Key4"
msgid "Key3"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:104
msgctxt "Aura zone"
msgid "Logo"
msgid "Key4"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:105
msgctxt "Aura zone"
msgid "Lightbar Left"
msgid "Logo"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:106
msgctxt "Aura zone"
msgid "Lightbar Right"
msgid "Lightbar Left"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:110
msgctxt "Aura direction"
msgid "Right"
#: rog-control-center/ui/types/aura_types.slint:107
msgctxt "Aura zone"
msgid "Lightbar Right"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:111
msgctxt "Aura direction"
msgid "Left"
msgid "Right"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:112
msgctxt "Aura direction"
msgid "Up"
msgid "Left"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:113
msgctxt "Aura direction"
msgid "Down"
msgid "Up"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:117
msgctxt "Aura speed"
msgid "Low"
#: rog-control-center/ui/types/aura_types.slint:114
msgctxt "Aura direction"
msgid "Down"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:118
msgctxt "Aura speed"
msgid "Medium"
msgid "Low"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:119
msgctxt "Aura speed"
msgid "Medium"
msgstr ""
#: rog-control-center/ui/types/aura_types.slint:120
msgctxt "Aura speed"
msgid "High"
msgstr ""
@@ -5,6 +5,7 @@ export enum AuraDevType {
ScsiExtDisk,
Unknown,
Ally,
AnimeOrSlash,
}
export struct AuraEffect {
+1 -2
View File
@@ -13,10 +13,9 @@ 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" }
zbus.workspace = true
[dev-dependencies]
cargo-husky.workspace = true
+1
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
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<()>;
}
+1 -1
View File
@@ -7,7 +7,7 @@ use zbus::proxy;
default_service = "org.asuslinux.Daemon",
default_path = "/org/asuslinux"
)]
trait Anime {
pub trait Anime {
/// DeviceState method
fn device_state(&self) -> zbus::Result<AnimeDeviceState>;
+4 -4
View File
@@ -22,7 +22,7 @@
use std::collections::BTreeMap;
use rog_aura::keyboard::{LaptopAuraPower, UsbPackets};
use rog_aura::keyboard::{AuraLaptopUsbPackets, LaptopAuraPower};
use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones};
use zbus::blocking::Connection;
use zbus::{proxy, Result};
@@ -34,12 +34,12 @@ const BLOCKING_TIME: u64 = 33; // 100ms = 10 FPS, max 50ms = 20 FPS, 40ms = 25 F
default_service = "org.asuslinux.Daemon",
default_path = "/org/asuslinux/Aura"
)]
trait Aura {
pub trait Aura {
/// AllModeData method
fn all_mode_data(&self) -> zbus::Result<BTreeMap<AuraModeNum, AuraEffect>>;
/// DirectAddressingRaw method
fn direct_addressing_raw(&self, data: UsbPackets) -> zbus::Result<()>;
fn direct_addressing_raw(&self, data: AuraLaptopUsbPackets) -> zbus::Result<()>;
/// Brightness property
#[zbus(property)]
@@ -104,7 +104,7 @@ impl<'a> AuraProxyPerkey<'a> {
/// Intentionally blocks for 10ms after sending to allow the block to
/// be written to the keyboard EC. This should not be async.
#[inline]
pub fn direct_addressing_raw(&self, direct_raw: UsbPackets) -> Result<()> {
pub fn direct_addressing_raw(&self, direct_raw: AuraLaptopUsbPackets) -> Result<()> {
self.0.direct_addressing_raw(direct_raw)?;
std::thread::sleep(std::time::Duration::from_millis(BLOCKING_TIME));
Ok(())
+1 -1
View File
@@ -30,7 +30,7 @@ use zbus::proxy;
default_service = "org.asuslinux.Daemon",
default_path = "/org/asuslinux"
)]
trait FanCurves {
pub trait FanCurves {
/// Get the fan-curve data for the currently active PlatformProfile
fn fan_curve_data(&self, profile: ThrottlePolicy) -> zbus::Result<Vec<CurveData>>;
+4 -1
View File
@@ -29,7 +29,7 @@ use zbus::proxy;
default_service = "org.asuslinux.Daemon",
default_path = "/org/asuslinux"
)]
trait Platform {
pub trait Platform {
#[zbus(property)]
fn version(&self) -> zbus::Result<String>;
@@ -45,6 +45,9 @@ trait Platform {
#[zbus(property)]
fn set_charge_control_end_threshold(&self, value: u8) -> zbus::Result<()>;
// Toggle one-shot charge to 100%
fn one_shot_full_charge(&self) -> zbus::Result<()>;
/// DgpuDisable property
#[zbus(property)]
fn dgpu_disable(&self) -> zbus::Result<bool>;
+1 -1
View File
@@ -6,7 +6,7 @@ use zbus::proxy;
default_service = "org.asuslinux.Daemon",
default_path = "/org/asuslinux"
)]
trait Slash {
pub trait Slash {
/// EnableDisplay property
#[zbus(property)]
fn enabled(&self) -> zbus::Result<bool>;
-3
View File
@@ -18,6 +18,3 @@ inotify.workspace = true
typeshare.workspace = true
rusb.workspace = true
[dev-dependencies]
cargo-husky.workspace = true
+4
View File
@@ -19,6 +19,7 @@ pub enum PlatformError {
MissingLedBrightNode(String, std::io::Error),
IoPath(String, std::io::Error),
Io(std::io::Error),
InvalidValue,
NoAuraKeyboard,
NoAuraNode,
CPU(String),
@@ -38,6 +39,9 @@ impl fmt::Display for PlatformError {
PlatformError::NotSupported => write!(f, "Not supported"),
PlatformError::AttrNotFound(deets) => write!(f, "Attribute not found: {}", deets),
PlatformError::Io(deets) => write!(f, "std::io error: {}", deets),
PlatformError::InvalidValue => {
write!(f, "The input value did not match the attribute value type")
}
PlatformError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
PlatformError::MissingLedBrightNode(path, error) => write!(
f,
+183 -51
View File
@@ -1,9 +1,15 @@
use std::fs::{read_dir, File};
use std::io::Read;
use std::path::Path;
use std::fs::{read_dir, File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use zbus::zvariant::Type;
use crate::error::PlatformError;
/// The root sysfs path. This path should never change in kernel so
/// using udev to find it *should* not be required.
const BASE_DIR: &str = "/sys/class/firmware-attributes/asus-armoury/attributes/";
fn read_i32(path: &Path) -> Result<i32, PlatformError> {
@@ -39,12 +45,12 @@ pub enum AttrValue {
pub struct Attribute {
name: String,
help: String,
current_value: AttrValue,
default_value: AttrValue,
possible_values: AttrValue,
min_value: AttrValue,
max_value: AttrValue,
scalar_increment: Option<i32>,
base_path: PathBuf,
}
impl Attribute {
@@ -56,8 +62,33 @@ impl Attribute {
&self.help
}
pub fn current_value(&self) -> &AttrValue {
&self.current_value
/// Read the `current_value` directly from the attribute path
pub fn current_value(&self) -> Result<AttrValue, PlatformError> {
match read_string(&self.base_path.join("current_value")) {
Ok(val) => {
if let Ok(int) = val.parse::<i32>() {
Ok(AttrValue::Integer(int))
} else {
Ok(AttrValue::String(val))
}
}
Err(e) => Err(e),
}
}
/// Write the `current_value` directly to the attribute path
pub fn set_current_value(&self, new_value: AttrValue) -> Result<(), PlatformError> {
let path = self.base_path.join("current_value");
let value_str = match new_value {
AttrValue::Integer(val) => val.to_string(),
AttrValue::String(val) => val,
_ => return Err(PlatformError::InvalidValue),
};
let mut file = OpenOptions::new().write(true).open(&path)?;
file.write_all(value_str.as_bytes())?;
Ok(())
}
pub fn default_value(&self) -> &AttrValue {
@@ -80,27 +111,12 @@ impl Attribute {
self.scalar_increment
}
fn read_values(
/// Read all the immutable values to struct data. These should *never*
/// change, if they do then it is possibly a driver issue - although this is
/// subject to `firmware_attributes` class changes in kernel.
fn read_base_values(
base_path: &Path,
) -> (
AttrValue,
AttrValue,
AttrValue,
AttrValue,
AttrValue,
Option<i32>,
) {
let current_value = match read_string(&base_path.join("current_value")) {
Ok(val) => {
if let Ok(int) = val.parse::<i32>() {
AttrValue::Integer(int)
} else {
AttrValue::String(val)
}
}
Err(_) => AttrValue::None,
};
) -> (AttrValue, AttrValue, AttrValue, AttrValue, Option<i32>) {
let default_value = match read_string(&base_path.join("default_value")) {
Ok(val) => {
if let Ok(int) = val.parse::<i32>() {
@@ -136,7 +152,6 @@ impl Attribute {
let scalar_increment = read_i32(&base_path.join("scalar_increment")).ok();
(
current_value,
default_value,
possible_values,
min_value,
@@ -160,24 +175,18 @@ impl FirmwareAttributes {
let name = base_path.file_name().unwrap().to_string_lossy().to_string();
let help = read_string(&base_path.join("display_name")).unwrap_or_default();
let (
current_value,
default_value,
possible_values,
min_value,
max_value,
scalar_increment,
) = Attribute::read_values(&base_path);
let (default_value, possible_values, min_value, max_value, scalar_increment) =
Attribute::read_base_values(&base_path);
attrs.push(Attribute {
name,
help,
current_value,
default_value,
possible_values,
min_value,
max_value,
scalar_increment,
base_path,
});
}
}
@@ -236,31 +245,130 @@ define_attribute_getters!(
mini_led_mode
);
/// CamelCase names of the properties. Intended for use with DBUS
#[typeshare]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Type, PartialEq, PartialOrd)]
#[zvariant(signature = "s")]
pub enum FirmwareAttribute {
ApuMem,
CoresPerformance,
CoresEfficiency,
PptPl1Spl,
PptPl2Sppt,
PptApuSppt,
PptPlatformSppt,
PptFppt,
NvDynamicBoost,
NvTempTarget,
DgpuBaseTgp,
DgpuTgp,
ChargeMode,
BootSound,
McuPowersave,
PanelOverdrive,
PanelHdMode,
EgpuConnected,
EgpuEnable,
DgpuDisable,
GpuMuxMode,
MiniLedMode,
PendingReboot,
}
impl From<&str> for FirmwareAttribute {
fn from(s: &str) -> Self {
match s {
"apu_mem" => Self::ApuMem,
"cores_performance" => Self::CoresPerformance,
"cores_efficiency" => Self::CoresEfficiency,
"ppt_pl1_spl" => Self::PptPl1Spl,
"ppt_pl2_sppt" => Self::PptPl2Sppt,
"ppt_apu_sppt" => Self::PptApuSppt,
"ppt_platform_sppt" => Self::PptPlatformSppt,
"ppt_fppt" => Self::PptFppt,
"nv_dynamic_boost" => Self::NvDynamicBoost,
"nv_temp_target" => Self::NvTempTarget,
"dgpu_base_tgp" => Self::DgpuBaseTgp,
"dgpu_tgp" => Self::DgpuTgp,
"charge_mode" => Self::ChargeMode,
"boot_sound" => Self::BootSound,
"mcu_powersave" => Self::McuPowersave,
"panel_overdrive" => Self::PanelOverdrive,
"panel_hd_mode" => Self::PanelHdMode,
"egpu_connected" => Self::EgpuConnected,
"egpu_enable" => Self::EgpuEnable,
"dgpu_disable" => Self::DgpuDisable,
"gpu_mux_mode" => Self::GpuMuxMode,
"mini_led_mode" => Self::MiniLedMode,
"pending_reboot" => Self::PendingReboot,
_ => panic!("Invalid firmware attribute: {}", s),
}
}
}
impl From<FirmwareAttribute> for &'static str {
fn from(attr: FirmwareAttribute) -> Self {
match attr {
FirmwareAttribute::ApuMem => "apu_mem",
FirmwareAttribute::CoresPerformance => "cores_performance",
FirmwareAttribute::CoresEfficiency => "cores_efficiency",
FirmwareAttribute::PptPl1Spl => "ppt_pl1_spl",
FirmwareAttribute::PptPl2Sppt => "ppt_pl2_sppt",
FirmwareAttribute::PptApuSppt => "ppt_apu_sppt",
FirmwareAttribute::PptPlatformSppt => "ppt_platform_sppt",
FirmwareAttribute::PptFppt => "ppt_fppt",
FirmwareAttribute::NvDynamicBoost => "nv_dynamic_boost",
FirmwareAttribute::NvTempTarget => "nv_temp_target",
FirmwareAttribute::DgpuBaseTgp => "dgpu_base_tgp",
FirmwareAttribute::DgpuTgp => "dgpu_tgp",
FirmwareAttribute::ChargeMode => "charge_mode",
FirmwareAttribute::BootSound => "boot_sound",
FirmwareAttribute::McuPowersave => "mcu_powersave",
FirmwareAttribute::PanelOverdrive => "panel_overdrive",
FirmwareAttribute::PanelHdMode => "panel_hd_mode",
FirmwareAttribute::EgpuConnected => "egpu_connected",
FirmwareAttribute::EgpuEnable => "egpu_enable",
FirmwareAttribute::DgpuDisable => "dgpu_disable",
FirmwareAttribute::GpuMuxMode => "gpu_mux_mode",
FirmwareAttribute::MiniLedMode => "mini_led_mode",
FirmwareAttribute::PendingReboot => "pending_reboot",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore = "Can't check in docker env"]
fn find_attributes() {
let attrs = FirmwareAttributes::new();
for attr in attrs.attributes() {
dbg!(attr.name());
match attr.name() {
"nv_dynamic_boost" => {
match attr.name().into() {
FirmwareAttribute::DgpuDisable => {
assert!(!attr.help().is_empty());
assert!(matches!(attr.current_value, AttrValue::Integer(_)));
if let AttrValue::Integer(val) = attr.current_value {
assert_eq!(val, 5);
assert!(matches!(
attr.current_value().unwrap(),
AttrValue::Integer(_)
));
if let AttrValue::Integer(val) = attr.current_value().unwrap() {
assert_eq!(val, 0);
}
if let AttrValue::Integer(val) = attr.default_value {
assert_eq!(val, 25);
}
assert_eq!(attr.min_value, AttrValue::Integer(0));
assert_eq!(attr.max_value, AttrValue::Integer(25));
assert_eq!(attr.min_value, AttrValue::None);
assert_eq!(attr.max_value, AttrValue::None);
}
"boot_sound" => {
FirmwareAttribute::BootSound => {
assert!(!attr.help().is_empty());
assert!(matches!(attr.current_value, AttrValue::Integer(0)));
assert!(matches!(
attr.current_value().unwrap(),
AttrValue::Integer(0)
));
// dbg!(attr.current_value());
}
_ => {}
@@ -269,20 +377,44 @@ mod tests {
}
#[test]
#[ignore = "Can't check in docker env"]
fn test_boot_sound() {
let attrs = FirmwareAttributes::new();
let attr = attrs
.attributes()
.iter()
.find(|a| a.name() == "boot_sound")
.find(|a| a.name() == <&str>::from(FirmwareAttribute::BootSound))
.unwrap();
assert_eq!(attr.name(), "boot_sound");
assert_eq!(attr.name(), <&str>::from(FirmwareAttribute::BootSound));
assert_eq!(
attr.base_path.to_str().unwrap(),
"/sys/class/firmware-attributes/asus-armoury/attributes/boot_sound"
);
assert!(!attr.help().is_empty());
assert!(matches!(attr.current_value(), AttrValue::Integer(_)));
if let AttrValue::Integer(val) = attr.current_value() {
assert_eq!(*val, 0); // assuming value is 0
assert!(matches!(
attr.current_value().unwrap(),
AttrValue::Integer(_)
));
if let AttrValue::Integer(val) = attr.current_value().unwrap() {
assert_eq!(val, 0); // assuming value is 0
}
// Check other members if applicable
}
#[test]
#[ignore = "Can't check in docker env"]
fn test_set_boot_sound() {
let attrs = FirmwareAttributes::new();
let attr = attrs
.attributes()
.iter()
.find(|a| a.name() == <&str>::from(FirmwareAttribute::BootSound))
.unwrap();
let mut val = attr.current_value().unwrap();
if let AttrValue::Integer(val) = &mut val {
*val = 0;
}
attr.set_current_value(val).unwrap();
}
}
+22 -37
View File
@@ -17,7 +17,7 @@ pub struct HidRaw {
syspath: PathBuf,
/// The product ID. The vendor ID is not kept
prod_id: String,
device_bcd: u32,
_device_bcd: u32,
/// Retaining a handle to the file for the duration of `HidRaw`
file: RefCell<File>,
}
@@ -44,40 +44,24 @@ impl HidRaw {
PlatformError::IoPath(endpoint.devpath().to_string_lossy().to_string(), e)
})?
{
if let Some(parent_id) = usb_device.attribute_value("idProduct") {
if parent_id == id_product {
if let Some(dev_node) = endpoint.devnode() {
info!("Using device at: {:?} for hidraw control", dev_node);
return Ok(Self {
file: RefCell::new(OpenOptions::new().write(true).open(dev_node)?),
devfs_path: dev_node.to_owned(),
prod_id: id_product.to_string(),
syspath: endpoint.syspath().into(),
device_bcd: usb_device
.attribute_value("bcdDevice")
.unwrap_or_default()
.to_string_lossy()
.parse()
.unwrap_or_default(),
});
if let Some(dev_node) = endpoint.devnode() {
if let Some(this_id_product) = usb_device.attribute_value("idProduct") {
if this_id_product != id_product {
continue;
}
let dev_path = endpoint.devpath().to_string_lossy();
if dev_path.contains("virtual") {
info!(
"Using device at: {:?} for <TODO: label control> control",
dev_node
);
}
}
}
} else {
// Try to see if there is a virtual device created with uhid for testing
let dev_path = endpoint.devpath().to_string_lossy();
if dev_path.contains("virtual") && dev_path.contains(&id_product.to_uppercase()) {
if let Some(dev_node) = endpoint.devnode() {
info!(
"Using device at: {:?} for <TODO: label control> control",
dev_node
);
return Ok(Self {
file: RefCell::new(OpenOptions::new().write(true).open(dev_node)?),
devfs_path: dev_node.to_owned(),
prod_id: id_product.to_string(),
prod_id: this_id_product.to_string_lossy().into(),
syspath: endpoint.syspath().into(),
device_bcd: endpoint
_device_bcd: usb_device
.attribute_value("bcdDevice")
.unwrap_or_default()
.to_string_lossy()
@@ -95,19 +79,21 @@ impl HidRaw {
}
/// Make `HidRaw` device from a udev device
pub fn from_device(device: Device) -> Result<Self> {
if let Some(parent) = device
pub fn from_device(endpoint: Device) -> Result<Self> {
if let Some(parent) = endpoint
.parent_with_subsystem_devtype("usb", "usb_device")
.map_err(|e| PlatformError::IoPath(device.devpath().to_string_lossy().to_string(), e))?
.map_err(|e| {
PlatformError::IoPath(endpoint.devpath().to_string_lossy().to_string(), e)
})?
{
if let Some(dev_node) = device.devnode() {
if let Some(dev_node) = endpoint.devnode() {
if let Some(id_product) = parent.attribute_value("idProduct") {
return Ok(Self {
file: RefCell::new(OpenOptions::new().write(true).open(dev_node)?),
devfs_path: dev_node.to_owned(),
prod_id: id_product.to_string_lossy().into(),
syspath: device.syspath().into(),
device_bcd: device
syspath: endpoint.syspath().into(),
_device_bcd: endpoint
.attribute_value("bcdDevice")
.unwrap_or_default()
.to_string_lossy()
@@ -129,7 +115,6 @@ impl HidRaw {
/// Write an array of raw bytes to the device using the hidraw interface
pub fn write_bytes(&self, message: &[u8]) -> Result<()> {
if let Ok(mut file) = self.file.try_borrow_mut() {
// let mut file = self.file.borrow_mut();
// TODO: re-get the file if error?
file.write_all(message).map_err(|e| {
PlatformError::IoPath(self.devfs_path.to_string_lossy().to_string(), e)
+6 -1
View File
@@ -14,6 +14,7 @@ pub mod usb_raw;
use std::path::Path;
use error::{PlatformError, Result};
use log::warn;
use udev::Device;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -44,9 +45,13 @@ pub fn read_attr_bool(device: &Device, attr_name: &str) -> Result<bool> {
}
pub fn write_attr_bool(device: &mut Device, attr: &str, value: bool) -> Result<()> {
let value = if value { 1 } else { 0 };
device
.set_attribute_value(attr, value.to_string())
.map_err(|e| PlatformError::IoPath(attr.into(), e))
.map_err(|e| {
warn!("attr write error: {e:?}");
PlatformError::IoPath(attr.into(), e)
})
}
pub fn read_attr_u8(device: &Device, attr_name: &str) -> Result<u8> {
-6
View File
@@ -20,9 +20,3 @@ typeshare.workspace = true
rog_platform = { path = "../rog-platform" }
zbus = { workspace = true, optional = true }
[dev-dependencies]
cargo-husky.workspace = true
[package.metadata.cargo-machete]
ignored = ["serde"]
+25
View File
@@ -0,0 +1,25 @@
[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
typeshare.workspace = true
ron = { version = "*", optional = true }
+398
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
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
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
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
}
-6
View File
@@ -26,9 +26,3 @@ serde.workspace = true
typeshare.workspace = true
zbus = { workspace = true, optional = true }
dmi_id = { path = "../dmi-id", optional = true }
[dev-dependencies]
cargo-husky.workspace = true
[package.metadata.cargo-machete]
ignored = ["serde"]
+37 -1
View File
@@ -1,6 +1,7 @@
use std::fmt::Display;
use std::str::FromStr;
use dmi_id::DMIID;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
#[cfg(feature = "dbus")]
@@ -8,15 +9,50 @@ use zbus::zvariant::Type;
use zbus::zvariant::{OwnedValue, Value};
use crate::error::SlashError;
use crate::usb::{PROD_ID1, PROD_ID1_STR, PROD_ID2, PROD_ID2_STR};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum SlashType {
GA403,
GA605,
GU605,
#[default]
Unsupported,
}
impl SlashType {
pub const fn prod_id(&self) -> u16 {
match self {
SlashType::GA403 => PROD_ID1,
SlashType::GA605 => PROD_ID2,
SlashType::GU605 => PROD_ID1,
SlashType::Unsupported => 0,
}
}
pub const fn prod_id_str(&self) -> &str {
match self {
SlashType::GA403 => PROD_ID1_STR,
SlashType::GA605 => PROD_ID2_STR,
SlashType::GU605 => PROD_ID1_STR,
SlashType::Unsupported => "",
}
}
pub fn from_dmi() -> Self {
let board_name = DMIID::new().unwrap_or_default().board_name.to_uppercase();
if board_name.contains("GA403") {
SlashType::GA403
} else if board_name.contains("GA605") {
SlashType::GA605
} else if board_name.contains("GU605") {
SlashType::GU605
} else {
SlashType::Unsupported
}
}
}
impl FromStr for SlashType {
type Err = SlashError;
+47 -22
View File
@@ -14,11 +14,16 @@ use dmi_id::DMIID;
use crate::error::SlashError;
use crate::{SlashMode, SlashType};
const PACKET_SIZE: usize = 128;
const DEV_PAGE: u8 = 0x5e;
const PACKET_SIZE: usize = 32;
const REPORT_ID_193B: u8 = 0x5e;
const REPORT_ID_19B6: u8 = 0x5d;
pub const VENDOR_ID: u16 = 0x0b05;
pub const PROD_ID: u16 = 0x193b;
pub const PROD_ID_STR: &str = "193B";
pub const PROD_ID1: u16 = 0x193b;
pub const PROD_ID1_STR: &str = "193B";
pub const PROD_ID2: u16 = 0x19b6;
pub const PROD_ID2_STR: &str = "19B6";
pub type SlashUsbPacket = [u8; PACKET_SIZE];
@@ -28,26 +33,40 @@ pub type SlashUsbPacket = [u8; PACKET_SIZE];
///
/// The currently known USB device is `193B`.
#[inline]
pub fn get_maybe_slash_type() -> Result<SlashType, SlashError> {
let dmi = DMIID::new().map_err(|_| SlashError::NoDevice)?; // TODO: better error
pub fn get_slash_type() -> SlashType {
let dmi = DMIID::new()
.map_err(|_| SlashError::NoDevice)
.unwrap_or_default();
let board_name = dmi.board_name;
if board_name.contains("GA403") {
return Ok(SlashType::GA403);
SlashType::GA403
} else if board_name.contains("GA605") {
return Ok(SlashType::GA605);
SlashType::GA605
} else if board_name.contains("GU605") {
return Ok(SlashType::GU605);
SlashType::GU605
} else {
SlashType::Unsupported
}
}
pub const fn report_id(slash_type: SlashType) -> u8 {
match slash_type {
SlashType::GA403 => REPORT_ID_193B,
SlashType::GA605 => REPORT_ID_19B6,
SlashType::GU605 => REPORT_ID_193B,
SlashType::Unsupported => REPORT_ID_19B6,
}
Ok(SlashType::Unsupported)
}
/// Get the two device initialization packets. These are required for device
/// start after the laptop boots.
#[inline]
pub const fn pkts_for_init() -> [SlashUsbPacket; 2] {
pub fn pkts_for_init(slash_type: SlashType) -> [SlashUsbPacket; 2] {
let report_id = report_id(slash_type);
let mut pkt1 = [0; PACKET_SIZE];
pkt1[0] = DEV_PAGE;
pkt1[0] = report_id;
pkt1[1] = 0xd7;
pkt1[2] = 0x00;
pkt1[3] = 0x00;
@@ -55,7 +74,7 @@ pub const fn pkts_for_init() -> [SlashUsbPacket; 2] {
pkt1[5] = 0xac;
let mut pkt2 = [0; PACKET_SIZE];
pkt2[0] = DEV_PAGE;
pkt2[0] = report_id;
pkt2[1] = 0xd2;
pkt2[2] = 0x02;
pkt2[3] = 0x01;
@@ -66,9 +85,9 @@ pub const fn pkts_for_init() -> [SlashUsbPacket; 2] {
}
#[inline]
pub const fn pkt_save() -> SlashUsbPacket {
pub const fn pkt_save(slash_type: SlashType) -> SlashUsbPacket {
let mut pkt = [0; PACKET_SIZE];
pkt[0] = DEV_PAGE;
pkt[0] = report_id(slash_type);
pkt[1] = 0xd4;
pkt[2] = 0x00;
pkt[3] = 0x00;
@@ -79,16 +98,17 @@ pub const fn pkt_save() -> SlashUsbPacket {
}
#[inline]
pub const fn pkt_set_mode(mode: SlashMode) -> [SlashUsbPacket; 2] {
pub const fn pkt_set_mode(slash_type: SlashType, mode: SlashMode) -> [SlashUsbPacket; 2] {
let report_id = report_id(slash_type);
let mut pkt1 = [0; PACKET_SIZE];
pkt1[0] = DEV_PAGE;
pkt1[1] = 0x02;
pkt1[0] = report_id;
pkt1[1] = 0xd2;
pkt1[2] = 0x03;
pkt1[3] = 0x00;
pkt1[4] = 0x0c;
let mut pkt2 = [0; PACKET_SIZE];
pkt2[0] = DEV_PAGE;
pkt2[0] = report_id;
pkt2[1] = 0xd3;
pkt2[2] = 0x04;
pkt2[3] = 0x00;
@@ -96,7 +116,7 @@ pub const fn pkt_set_mode(mode: SlashMode) -> [SlashUsbPacket; 2] {
pkt2[5] = 0x01;
pkt2[6] = mode as u8;
pkt2[7] = 0x02;
pkt2[8] = 0x19;
pkt2[8] = 0x19; // difference, GA605 = 0x10
pkt2[9] = 0x03;
pkt2[10] = 0x13;
pkt2[11] = 0x04;
@@ -110,11 +130,16 @@ pub const fn pkt_set_mode(mode: SlashMode) -> [SlashUsbPacket; 2] {
}
#[inline]
pub const fn pkt_set_options(enabled: bool, brightness: u8, interval: u8) -> SlashUsbPacket {
pub const fn pkt_set_options(
slash_type: SlashType,
enabled: bool,
brightness: u8,
interval: u8,
) -> SlashUsbPacket {
let status_byte = if enabled { 0x01 } else { 0x00 };
let mut pkt = [0; PACKET_SIZE];
pkt[0] = DEV_PAGE;
pkt[0] = report_id(slash_type);
pkt[1] = 0xd3;
pkt[2] = 0x03;
pkt[3] = 0x01;