mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Animatrix: Default to GA402 style if Unknown, use default-workspace.
Also rename daemon crates to the bin names they use to be less confusing. Signed-off-by: Luke D. Jones <luke@ljones.dev>
This commit is contained in:
@@ -52,7 +52,7 @@ test:
|
|||||||
<<: *rust_cache
|
<<: *rust_cache
|
||||||
script:
|
script:
|
||||||
- mkdir -p .git/hooks > /dev/null
|
- mkdir -p .git/hooks > /dev/null
|
||||||
- cargo test
|
- cargo test --all
|
||||||
|
|
||||||
release:
|
release:
|
||||||
only:
|
only:
|
||||||
@@ -79,6 +79,6 @@ pages:
|
|||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- public
|
- public
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
GIT_SUBMODULE_STRATEGY: normal
|
GIT_SUBMODULE_STRATEGY: normal
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
- Move FX506HC to FX506H in arua DB to catch full series of this range
|
- Move FX506HC to FX506H in arua DB to catch full series of this range
|
||||||
- Move FX506LH to FX506L in arua DB to catch full series of this range
|
- Move FX506LH to FX506L in arua DB to catch full series of this range
|
||||||
- Rmeove notification handle tracking limit, fixes KDE issue with profile notif
|
- Remove notification handle tracking limit, fixes KDE issue with profile notif
|
||||||
|
- Rename daemon and daemon-user crates to asusd and asusd-user to not be confusing in workspace naming
|
||||||
### BREAKING
|
### BREAKING
|
||||||
- All Anime related DBUS methods/notifs are changed
|
- All Anime related DBUS methods/notifs are changed
|
||||||
|
|
||||||
|
|||||||
92
Cargo.lock
generated
92
Cargo.lock
generated
@@ -177,8 +177,8 @@ checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c"
|
|||||||
name = "asusctl"
|
name = "asusctl"
|
||||||
version = "4.6.2"
|
version = "4.6.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"asusd",
|
||||||
"cargo-husky",
|
"cargo-husky",
|
||||||
"daemon",
|
|
||||||
"gif",
|
"gif",
|
||||||
"glam",
|
"glam",
|
||||||
"gumdrop",
|
"gumdrop",
|
||||||
@@ -192,6 +192,50 @@ dependencies = [
|
|||||||
"toml 0.5.11",
|
"toml 0.5.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asusd"
|
||||||
|
version = "4.6.2"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"cargo-husky",
|
||||||
|
"concat-idents",
|
||||||
|
"config-traits",
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"logind-zbus",
|
||||||
|
"rog_anime",
|
||||||
|
"rog_aura",
|
||||||
|
"rog_dbus",
|
||||||
|
"rog_platform",
|
||||||
|
"rog_profiles",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"sysfs-class",
|
||||||
|
"systemd-zbus",
|
||||||
|
"tokio",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asusd-user"
|
||||||
|
version = "4.6.2"
|
||||||
|
dependencies = [
|
||||||
|
"cargo-husky",
|
||||||
|
"config-traits",
|
||||||
|
"dirs",
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"rog_anime",
|
||||||
|
"rog_aura",
|
||||||
|
"rog_dbus",
|
||||||
|
"rog_platform",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"smol",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -773,50 +817,6 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "daemon"
|
|
||||||
version = "4.6.2"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"cargo-husky",
|
|
||||||
"concat-idents",
|
|
||||||
"config-traits",
|
|
||||||
"env_logger",
|
|
||||||
"log",
|
|
||||||
"logind-zbus",
|
|
||||||
"rog_anime",
|
|
||||||
"rog_aura",
|
|
||||||
"rog_dbus",
|
|
||||||
"rog_platform",
|
|
||||||
"rog_profiles",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"sysfs-class",
|
|
||||||
"systemd-zbus",
|
|
||||||
"tokio",
|
|
||||||
"zbus",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "daemon-user"
|
|
||||||
version = "4.6.2"
|
|
||||||
dependencies = [
|
|
||||||
"cargo-husky",
|
|
||||||
"config-traits",
|
|
||||||
"dirs",
|
|
||||||
"env_logger",
|
|
||||||
"log",
|
|
||||||
"rog_anime",
|
|
||||||
"rog_aura",
|
|
||||||
"rog_dbus",
|
|
||||||
"rog_platform",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"smol",
|
|
||||||
"zbus",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@@ -2560,8 +2560,8 @@ checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
|||||||
name = "rog-control-center"
|
name = "rog-control-center"
|
||||||
version = "4.6.2"
|
version = "4.6.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"asusd",
|
||||||
"cargo-husky",
|
"cargo-husky",
|
||||||
"daemon",
|
|
||||||
"dirs",
|
"dirs",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["asusctl", "config-traits", "daemon", "daemon-user", "rog-platform", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles", "rog-control-center", "simulators"]
|
members = ["asusctl", "asusd", "asusd-user", "config-traits", "rog-platform", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles", "rog-control-center", "simulators"]
|
||||||
|
default-members = ["asusctl", "asusd", "asusd-user", "rog-control-center"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "4.6.2"
|
version = "4.6.2"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ rog_aura = { path = "../rog-aura" }
|
|||||||
rog_dbus = { path = "../rog-dbus" }
|
rog_dbus = { path = "../rog-dbus" }
|
||||||
rog_profiles = { path = "../rog-profiles" }
|
rog_profiles = { path = "../rog-profiles" }
|
||||||
rog_platform = { path = "../rog-platform" }
|
rog_platform = { path = "../rog-platform" }
|
||||||
daemon = { path = "../daemon" }
|
asusd = { path = "../asusd" }
|
||||||
|
|
||||||
gumdrop.workspace = true
|
gumdrop.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ fn print_error_help(err: &dyn std::error::Error, supported: Option<&SupportedFun
|
|||||||
fn print_versions() {
|
fn print_versions() {
|
||||||
println!("App and daemon versions:");
|
println!("App and daemon versions:");
|
||||||
println!(" asusctl v{}", env!("CARGO_PKG_VERSION"));
|
println!(" asusctl v{}", env!("CARGO_PKG_VERSION"));
|
||||||
println!(" asusd v{}", daemon::VERSION);
|
println!(" asusd v{}", asusd::VERSION);
|
||||||
println!("\nComponent crate versions:");
|
println!("\nComponent crate versions:");
|
||||||
println!(" rog-anime v{}", rog_anime::VERSION);
|
println!(" rog-anime v{}", rog_anime::VERSION);
|
||||||
println!(" rog-aura v{}", rog_aura::VERSION);
|
println!(" rog-aura v{}", rog_aura::VERSION);
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "daemon-user"
|
|
||||||
license = "MPL-2.0"
|
|
||||||
version.workspace = true
|
|
||||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
|
||||||
edition = "2021"
|
|
||||||
description = "Usermode daemon for user settings, anime, per-key lighting"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "rog_user"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "asusd-user"
|
|
||||||
path = "src/daemon.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
dirs.workspace = true
|
|
||||||
smol.workspace = true
|
|
||||||
|
|
||||||
# serialisation
|
|
||||||
serde.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
serde_derive.workspace = true
|
|
||||||
|
|
||||||
rog_anime = { path = "../rog-anime" }
|
|
||||||
rog_aura = { path = "../rog-aura" }
|
|
||||||
rog_dbus = { path = "../rog-dbus" }
|
|
||||||
rog_platform = { path = "../rog-platform" }
|
|
||||||
config-traits = { path = "../config-traits" }
|
|
||||||
|
|
||||||
zbus.workspace = true
|
|
||||||
|
|
||||||
# cli and logging
|
|
||||||
log.workspace = true
|
|
||||||
env_logger.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
cargo-husky.workspace = true
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# daemon-user
|
|
||||||
|
|
||||||
This crate is for the binary of `asusd-user` and its helper lib.
|
|
||||||
|
|
||||||
The purpose of `asusd-user` is to run in userland and provide the user + third-party apps an interface for such things as creating AniMe sequences (and more in future, see todo list).
|
|
||||||
|
|
||||||
`asusd-user` should try to be as simple as possible while allowing a decent degree of control.
|
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
- [ ] CLI for basic settings/interaction
|
|
||||||
- [ ] RGB keyboard per-key programs
|
|
||||||
- [ ] User profiles (fan, cpu etc). These would be replacing the system-daemon profiles only when the user is active, otherwise system-daemon defaults to system settings.
|
|
||||||
- [ ] Audio EQ visualiser - for use with anime + keyboard lighting
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use config_traits::{StdConfig, StdConfigLoad};
|
|
||||||
use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences as AnimeSequences, Vec2};
|
|
||||||
use rog_aura::advanced::LedCode;
|
|
||||||
use rog_aura::effects::{AdvancedEffects as AuraSequences, Breathe, DoomFlicker, Effect, Static};
|
|
||||||
use rog_aura::{Colour, Speed};
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
const ROOT_CONF_DIR: &str = "rog";
|
|
||||||
|
|
||||||
fn root_conf_dir() -> PathBuf {
|
|
||||||
let mut dir = dirs::config_dir().unwrap_or_else(|| PathBuf::from("/tmp"));
|
|
||||||
dir.push(ROOT_CONF_DIR);
|
|
||||||
dir
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ConfigAnime {
|
|
||||||
pub name: String,
|
|
||||||
pub anime: Vec<ActionLoader>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigAnime {
|
|
||||||
pub fn create(&self, anime_type: AnimeType) -> Result<AnimeSequences, Error> {
|
|
||||||
let mut seq = AnimeSequences::new(anime_type);
|
|
||||||
|
|
||||||
for (idx, action) in self.anime.iter().enumerate() {
|
|
||||||
seq.insert(idx, action)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_name(mut self, name: String) -> Self {
|
|
||||||
self.name = name;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ConfigAnime {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
name: "anime-default".to_owned(),
|
|
||||||
anime: vec![
|
|
||||||
ActionLoader::AsusImage {
|
|
||||||
file: "/usr/share/asusd/anime/custom/diagonal-template.png".into(),
|
|
||||||
brightness: 1.0,
|
|
||||||
time: AnimTime::Fade(Fade::new(
|
|
||||||
Duration::from_secs(2),
|
|
||||||
None,
|
|
||||||
Duration::from_secs(2),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
ActionLoader::AsusAnimation {
|
|
||||||
file: "/usr/share/asusd/anime/asus/rog/Sunset.gif".into(),
|
|
||||||
brightness: 0.5,
|
|
||||||
time: AnimTime::Fade(Fade::new(
|
|
||||||
Duration::from_secs(6),
|
|
||||||
None,
|
|
||||||
Duration::from_secs(3),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
ActionLoader::ImageAnimation {
|
|
||||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
|
||||||
scale: 0.9,
|
|
||||||
angle: 0.65,
|
|
||||||
translation: Vec2::default(),
|
|
||||||
brightness: 0.5,
|
|
||||||
time: AnimTime::Fade(Fade::new(
|
|
||||||
Duration::from_secs(2),
|
|
||||||
Some(Duration::from_secs(2)),
|
|
||||||
Duration::from_secs(2),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
ActionLoader::Image {
|
|
||||||
file: "/usr/share/asusd/anime/custom/rust.png".into(),
|
|
||||||
scale: 1.0,
|
|
||||||
angle: 0.0,
|
|
||||||
translation: Vec2::default(),
|
|
||||||
time: AnimTime::Fade(Fade::new(
|
|
||||||
Duration::from_secs(2),
|
|
||||||
Some(Duration::from_secs(1)),
|
|
||||||
Duration::from_secs(2),
|
|
||||||
)),
|
|
||||||
brightness: 0.6,
|
|
||||||
},
|
|
||||||
ActionLoader::Pause(Duration::from_secs(1)),
|
|
||||||
ActionLoader::ImageAnimation {
|
|
||||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
|
||||||
scale: 0.9,
|
|
||||||
angle: 0.0,
|
|
||||||
translation: Vec2::new(3.0, 2.0),
|
|
||||||
brightness: 0.5,
|
|
||||||
time: AnimTime::Count(2),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for ConfigAnime {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
format!("{}.ron", self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
root_conf_dir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad for ConfigAnime {}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ConfigAura {
|
|
||||||
pub name: String,
|
|
||||||
pub aura: AuraSequences,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigAura {
|
|
||||||
pub fn set_name(mut self, name: String) -> Self {
|
|
||||||
self.name = name;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ConfigAura {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut seq = AuraSequences::new(false);
|
|
||||||
let mut key = Effect::Breathe(Breathe::new(
|
|
||||||
LedCode::W,
|
|
||||||
Colour(255, 0, 20),
|
|
||||||
Colour(20, 255, 0),
|
|
||||||
Speed::Low,
|
|
||||||
));
|
|
||||||
|
|
||||||
seq.push(key.clone());
|
|
||||||
key.set_led(LedCode::A);
|
|
||||||
seq.push(key.clone());
|
|
||||||
key.set_led(LedCode::S);
|
|
||||||
seq.push(key.clone());
|
|
||||||
key.set_led(LedCode::D);
|
|
||||||
seq.push(key);
|
|
||||||
|
|
||||||
let key = Effect::Breathe(Breathe::new(
|
|
||||||
LedCode::F,
|
|
||||||
Colour(255, 0, 0),
|
|
||||||
Colour(255, 0, 0),
|
|
||||||
Speed::High,
|
|
||||||
));
|
|
||||||
seq.push(key);
|
|
||||||
|
|
||||||
let mut key = Effect::Static(Static::new(LedCode::RCtrl, Colour(0, 0, 255)));
|
|
||||||
seq.push(key.clone());
|
|
||||||
key.set_led(LedCode::LCtrl);
|
|
||||||
seq.push(key.clone());
|
|
||||||
key.set_led(LedCode::Esc);
|
|
||||||
seq.push(key);
|
|
||||||
|
|
||||||
let key = Effect::DoomFlicker(DoomFlicker::new(LedCode::N9, Colour(0, 0, 255), 80, 40));
|
|
||||||
seq.push(key);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
name: "aura-default".to_owned(),
|
|
||||||
aura: seq,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for ConfigAura {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
format!("{}.ron", self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
root_conf_dir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad for ConfigAura {}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
|
||||||
#[serde(default)]
|
|
||||||
pub struct ConfigBase {
|
|
||||||
/// Name of active anime config file in the user config directory
|
|
||||||
pub active_anime: Option<String>,
|
|
||||||
/// Name of active aura config file in the user config directory
|
|
||||||
pub active_aura: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for ConfigBase {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
active_anime: Some("anime-default".to_owned()),
|
|
||||||
active_aura: Some("aura-default".to_owned()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
"rog-user.ron".to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
root_conf_dir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad for ConfigBase {}
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
use std::path::Path;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::thread::sleep;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use config_traits::StdConfig;
|
|
||||||
use rog_anime::error::AnimeError;
|
|
||||||
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
|
|
||||||
use rog_dbus::RogDbusClientBlocking;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use zbus::dbus_interface;
|
|
||||||
use zbus::zvariant::{ObjectPath, Type};
|
|
||||||
|
|
||||||
use crate::config::ConfigAnime;
|
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
|
|
||||||
pub struct Timer {
|
|
||||||
type_of: TimeType,
|
|
||||||
/// If time type is Timer then this is milliseonds, otherwise it is
|
|
||||||
/// animation loop count
|
|
||||||
count: u64,
|
|
||||||
/// Used only for `TimeType::Timer`, milliseonds to fade the image in for
|
|
||||||
fade_in: Option<u64>,
|
|
||||||
/// Used only for `TimeType::Timer`, milliseonds to fade the image out for
|
|
||||||
fade_out: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Timer> for AnimTime {
|
|
||||||
fn from(time: Timer) -> Self {
|
|
||||||
match time.type_of {
|
|
||||||
TimeType::Timer => {
|
|
||||||
if time.fade_in.is_some() || time.fade_out.is_some() {
|
|
||||||
let fade_in = time
|
|
||||||
.fade_in
|
|
||||||
.map_or(Duration::from_secs(0), Duration::from_millis);
|
|
||||||
let fade_out = time
|
|
||||||
.fade_out
|
|
||||||
.map_or(Duration::from_secs(0), Duration::from_millis);
|
|
||||||
let show_for = if time.count != 0 {
|
|
||||||
Some(Duration::from_millis(time.count))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
AnimTime::Fade(Fade::new(fade_in, show_for, fade_out))
|
|
||||||
} else {
|
|
||||||
AnimTime::Time(Duration::from_millis(time.count))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TimeType::Count => AnimTime::Count(time.count as u32),
|
|
||||||
TimeType::Infinite => AnimTime::Infinite,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
|
|
||||||
pub enum TimeType {
|
|
||||||
Timer,
|
|
||||||
Count,
|
|
||||||
Infinite,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The inner object exists to allow the zbus proxy to share it with a runner
|
|
||||||
/// thread and a zbus server behind `Arc<Mutex<T>>`
|
|
||||||
pub struct CtrlAnimeInner<'a> {
|
|
||||||
sequences: Sequences,
|
|
||||||
client: RogDbusClientBlocking<'a>,
|
|
||||||
do_early_return: Arc<AtomicBool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CtrlAnimeInner<'static> {
|
|
||||||
pub fn new(
|
|
||||||
sequences: Sequences,
|
|
||||||
client: RogDbusClientBlocking<'static>,
|
|
||||||
do_early_return: Arc<AtomicBool>,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
Ok(Self {
|
|
||||||
sequences,
|
|
||||||
client,
|
|
||||||
do_early_return,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// To be called on each main loop iteration to pump out commands to the
|
|
||||||
/// anime
|
|
||||||
pub fn run(&'a self) -> Result<(), Error> {
|
|
||||||
if self.do_early_return.load(Ordering::SeqCst) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
for action in self.sequences.iter() {
|
|
||||||
match action {
|
|
||||||
ActionData::Animation(frames) => {
|
|
||||||
rog_anime::run_animation(frames, &|output| {
|
|
||||||
if self.do_early_return.load(Ordering::Acquire) {
|
|
||||||
return Ok(true); // Do safe exit
|
|
||||||
}
|
|
||||||
self.client
|
|
||||||
.proxies()
|
|
||||||
.anime()
|
|
||||||
.write(output)
|
|
||||||
.map_err(|e| AnimeError::Dbus(format!("{}", e)))
|
|
||||||
.map(|_| false)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ActionData::Image(image) => {
|
|
||||||
self.client
|
|
||||||
.proxies()
|
|
||||||
.anime()
|
|
||||||
.write(image.as_ref().clone())
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
ActionData::Pause(duration) => {
|
|
||||||
let start = Instant::now();
|
|
||||||
'pause: loop {
|
|
||||||
if self.do_early_return.load(Ordering::SeqCst) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if Instant::now().duration_since(start) > *duration {
|
|
||||||
break 'pause;
|
|
||||||
}
|
|
||||||
sleep(Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ActionData::AudioEq
|
|
||||||
| ActionData::SystemInfo
|
|
||||||
| ActionData::TimeDate
|
|
||||||
| ActionData::Matrix => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CtrlAnime<'a> {
|
|
||||||
config: Arc<Mutex<ConfigAnime>>,
|
|
||||||
client: RogDbusClientBlocking<'a>,
|
|
||||||
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
|
|
||||||
/// Must be the same Atomic as in CtrlAnimeInner
|
|
||||||
inner_early_return: Arc<AtomicBool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CtrlAnime<'static> {
|
|
||||||
pub fn new(
|
|
||||||
config: Arc<Mutex<ConfigAnime>>,
|
|
||||||
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
|
|
||||||
client: RogDbusClientBlocking<'static>,
|
|
||||||
inner_early_return: Arc<AtomicBool>,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
Ok(CtrlAnime {
|
|
||||||
config,
|
|
||||||
client,
|
|
||||||
inner,
|
|
||||||
inner_early_return,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_to_server(self, server: &mut zbus::Connection) {
|
|
||||||
server
|
|
||||||
.object_server()
|
|
||||||
.at(
|
|
||||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
println!("CtrlAnime: add_to_server {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The pattern for a zbus method is:
|
|
||||||
// - Get config lock if required
|
|
||||||
// - Set inner_early_return to stop the inner run loop temporarily
|
|
||||||
// - Do actions
|
|
||||||
// - Write config if required
|
|
||||||
// - Unset inner_early_return
|
|
||||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
||||||
impl CtrlAnime<'static> {
|
|
||||||
pub fn insert_asus_gif(
|
|
||||||
&mut self,
|
|
||||||
index: u32,
|
|
||||||
file: &str,
|
|
||||||
time: Timer,
|
|
||||||
brightness: f32,
|
|
||||||
) -> zbus::fdo::Result<String> {
|
|
||||||
if let Ok(mut config) = self.config.try_lock() {
|
|
||||||
let time: AnimTime = time.into();
|
|
||||||
let file = Path::new(&file);
|
|
||||||
let action = ActionLoader::AsusAnimation {
|
|
||||||
file: file.into(),
|
|
||||||
brightness,
|
|
||||||
time,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Must make the inner run loop return early
|
|
||||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
if let Ok(mut controller) = self.inner.lock() {
|
|
||||||
controller
|
|
||||||
.sequences
|
|
||||||
.insert(index as usize, &action)
|
|
||||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
|
||||||
}
|
|
||||||
config.anime.push(action);
|
|
||||||
config.write();
|
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&*config).expect("Parse config to JSON failed");
|
|
||||||
|
|
||||||
// Release the inner run loop again
|
|
||||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
|
||||||
return Ok(json);
|
|
||||||
}
|
|
||||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn insert_image_gif(
|
|
||||||
&mut self,
|
|
||||||
index: u32,
|
|
||||||
file: &str,
|
|
||||||
scale: f32,
|
|
||||||
angle: f32,
|
|
||||||
xy: (f32, f32),
|
|
||||||
time: Timer,
|
|
||||||
brightness: f32,
|
|
||||||
) -> zbus::fdo::Result<String> {
|
|
||||||
if let Ok(mut config) = self.config.try_lock() {
|
|
||||||
let time: AnimTime = time.into();
|
|
||||||
let file = Path::new(&file);
|
|
||||||
let translation = Vec2::new(xy.0, xy.1);
|
|
||||||
let action = ActionLoader::ImageAnimation {
|
|
||||||
file: file.into(),
|
|
||||||
scale,
|
|
||||||
angle,
|
|
||||||
translation,
|
|
||||||
brightness,
|
|
||||||
time,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Must make the inner run loop return early
|
|
||||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
if let Ok(mut controller) = self.inner.lock() {
|
|
||||||
controller
|
|
||||||
.sequences
|
|
||||||
.insert(index as usize, &action)
|
|
||||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
|
||||||
}
|
|
||||||
config.anime.push(action);
|
|
||||||
config.write();
|
|
||||||
|
|
||||||
let json =
|
|
||||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
|
||||||
|
|
||||||
// Release the inner run loop again
|
|
||||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
|
||||||
return Ok(json);
|
|
||||||
}
|
|
||||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn insert_image(
|
|
||||||
&mut self,
|
|
||||||
index: u32,
|
|
||||||
file: &str,
|
|
||||||
scale: f32,
|
|
||||||
angle: f32,
|
|
||||||
xy: (f32, f32),
|
|
||||||
time: Timer,
|
|
||||||
brightness: f32,
|
|
||||||
) -> zbus::fdo::Result<String> {
|
|
||||||
if let Ok(mut config) = self.config.try_lock() {
|
|
||||||
let file = Path::new(&file);
|
|
||||||
let time = time.into();
|
|
||||||
let action = ActionLoader::Image {
|
|
||||||
file: file.into(),
|
|
||||||
scale,
|
|
||||||
angle,
|
|
||||||
translation: Vec2::new(xy.0, xy.1),
|
|
||||||
brightness,
|
|
||||||
time,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Must make the inner run loop return early
|
|
||||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
if let Ok(mut controller) = self.inner.lock() {
|
|
||||||
controller
|
|
||||||
.sequences
|
|
||||||
.insert(index as usize, &action)
|
|
||||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
|
||||||
}
|
|
||||||
config.anime.push(action);
|
|
||||||
config.write();
|
|
||||||
|
|
||||||
let json =
|
|
||||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
|
||||||
|
|
||||||
// Release the inner run loop again
|
|
||||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
|
||||||
return Ok(json);
|
|
||||||
}
|
|
||||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_pause(&mut self, index: u32, millis: u64) -> zbus::fdo::Result<String> {
|
|
||||||
if let Ok(mut config) = self.config.try_lock() {
|
|
||||||
let action = ActionLoader::Pause(Duration::from_millis(millis));
|
|
||||||
// Must make the inner run loop return early
|
|
||||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
if let Ok(mut controller) = self.inner.lock() {
|
|
||||||
controller
|
|
||||||
.sequences
|
|
||||||
.insert(index as usize, &action)
|
|
||||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
|
||||||
}
|
|
||||||
config.anime.push(action);
|
|
||||||
config.write();
|
|
||||||
|
|
||||||
let json =
|
|
||||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
|
||||||
|
|
||||||
// Release the inner run loop again
|
|
||||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
|
||||||
return Ok(json);
|
|
||||||
}
|
|
||||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_item(&mut self, index: u32) -> zbus::fdo::Result<String> {
|
|
||||||
if let Ok(mut config) = self.config.try_lock() {
|
|
||||||
// Must make the inner run loop return early
|
|
||||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
if let Ok(mut controller) = self.inner.lock() {
|
|
||||||
controller.sequences.remove_item(index as usize);
|
|
||||||
}
|
|
||||||
if (index as usize) < config.anime.len() {
|
|
||||||
config.anime.remove(index as usize);
|
|
||||||
}
|
|
||||||
config.write();
|
|
||||||
|
|
||||||
let json =
|
|
||||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
|
||||||
|
|
||||||
// Release the inner run loop again
|
|
||||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
|
||||||
return Ok(json);
|
|
||||||
}
|
|
||||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_state(&mut self, on: bool) -> zbus::fdo::Result<()> {
|
|
||||||
// Operations here need to be in specific order
|
|
||||||
if on {
|
|
||||||
self.client.proxies().anime().set_enable_display(on).ok();
|
|
||||||
// Let the inner loop run
|
|
||||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
|
||||||
} else {
|
|
||||||
// Must make the inner run loop return early
|
|
||||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
|
||||||
self.client.proxies().anime().set_enable_display(on).ok();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use config_traits::{StdConfig, StdConfigLoad};
|
|
||||||
use rog_anime::usb::get_anime_type;
|
|
||||||
use rog_aura::aura_detection::LaptopLedData;
|
|
||||||
use rog_aura::layouts::KeyLayout;
|
|
||||||
use rog_dbus::RogDbusClientBlocking;
|
|
||||||
use rog_user::config::*;
|
|
||||||
use rog_user::ctrl_anime::{CtrlAnime, CtrlAnimeInner};
|
|
||||||
use rog_user::DBUS_NAME;
|
|
||||||
use smol::Executor;
|
|
||||||
use zbus::Connection;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "local_data"))]
|
|
||||||
const DATA_DIR: &str = "/usr/share/rog-gui/";
|
|
||||||
#[cfg(feature = "local_data")]
|
|
||||||
const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
|
||||||
const BOARD_NAME: &str = "/sys/class/dmi/id/board_name";
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let mut logger = env_logger::Builder::new();
|
|
||||||
logger
|
|
||||||
.parse_default_env()
|
|
||||||
.target(env_logger::Target::Stdout)
|
|
||||||
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
|
|
||||||
.init();
|
|
||||||
|
|
||||||
println!(" user daemon v{}", rog_user::VERSION);
|
|
||||||
println!(" rog-anime v{}", rog_anime::VERSION);
|
|
||||||
println!(" rog-dbus v{}", rog_dbus::VERSION);
|
|
||||||
println!("rog-platform v{}", rog_platform::VERSION);
|
|
||||||
|
|
||||||
let (client, _) = RogDbusClientBlocking::new()?;
|
|
||||||
let supported = client.proxies().supported().supported_functions()?;
|
|
||||||
|
|
||||||
let config = ConfigBase::new().load();
|
|
||||||
|
|
||||||
let executor = Executor::new();
|
|
||||||
|
|
||||||
let early_return = Arc::new(AtomicBool::new(false));
|
|
||||||
// Set up the anime data and run loop/thread
|
|
||||||
if supported.anime_ctrl.0 {
|
|
||||||
if let Some(cfg) = config.active_anime {
|
|
||||||
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));
|
|
||||||
|
|
||||||
executor
|
|
||||||
.spawn(async move {
|
|
||||||
// Create server
|
|
||||||
let mut connection = Connection::session().await.unwrap();
|
|
||||||
connection.request_name(DBUS_NAME).await.unwrap();
|
|
||||||
|
|
||||||
// Inner behind mutex required for thread safety
|
|
||||||
let inner = Arc::new(Mutex::new(
|
|
||||||
CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(),
|
|
||||||
));
|
|
||||||
// Need new client object for dbus control part
|
|
||||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
|
||||||
let anime_control =
|
|
||||||
CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap();
|
|
||||||
anime_control.add_to_server(&mut connection).await;
|
|
||||||
loop {
|
|
||||||
if let Ok(inner) = inner.clone().try_lock() {
|
|
||||||
inner.run().ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if supported.keyboard_led.per_key_led_mode {
|
|
||||||
if let Some(cfg) = config.active_aura {
|
|
||||||
let mut aura_config = ConfigAura::new().set_name(cfg).load();
|
|
||||||
// let baord_name = std::fs::read_to_string(BOARD_NAME)?;
|
|
||||||
|
|
||||||
let led_support = LaptopLedData::get_data();
|
|
||||||
|
|
||||||
let layout = KeyLayout::find_layout(led_support, PathBuf::from(DATA_DIR))
|
|
||||||
.map_err(|e| {
|
|
||||||
println!("{BOARD_NAME}, {e}");
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|_| KeyLayout::default_layout());
|
|
||||||
|
|
||||||
executor
|
|
||||||
.spawn(async move {
|
|
||||||
// Create server
|
|
||||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
|
||||||
// let connection = Connection::session().await.unwrap();
|
|
||||||
// connection.request_name(DBUS_NAME).await.unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
aura_config.aura.next_state(&layout);
|
|
||||||
let packets = aura_config.aura.create_packets();
|
|
||||||
|
|
||||||
client
|
|
||||||
.proxies()
|
|
||||||
.led()
|
|
||||||
.direct_addressing_raw(packets)
|
|
||||||
.unwrap();
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(33));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
|
|
||||||
loop {
|
|
||||||
smol::block_on(executor.tick());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
use rog_anime::error::AnimeError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Io(std::io::Error),
|
|
||||||
ConfigLoadFail,
|
|
||||||
ConfigLockFail,
|
|
||||||
XdgVars,
|
|
||||||
Anime(AnimeError),
|
|
||||||
}
|
|
||||||
|
|
||||||
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::Io(err) => write!(f, "Failed to open: {}", 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"),
|
|
||||||
Error::Anime(err) => write!(f, "Anime error: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for Error {
|
|
||||||
fn from(err: std::io::Error) -> Self {
|
|
||||||
Error::Io(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AnimeError> for Error {
|
|
||||||
fn from(err: AnimeError) -> Self {
|
|
||||||
Error::Anime(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Error> for zbus::fdo::Error {
|
|
||||||
fn from(err: Error) -> Self {
|
|
||||||
zbus::fdo::Error::Failed(format!("Anime zbus error: {}", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
pub mod config;
|
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
|
|
||||||
pub mod ctrl_anime;
|
|
||||||
|
|
||||||
pub mod zbus_anime;
|
|
||||||
|
|
||||||
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
|
|
||||||
|
|
||||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
//! # `DBus` interface proxy for: `org.asuslinux.Daemon`
|
|
||||||
//!
|
|
||||||
//! This code was generated by `zbus-xmlgen` `1.0.0` from `DBus` introspection
|
|
||||||
//! data. Source: `Interface '/org/asuslinux/Anime' from service
|
|
||||||
//! 'org.asuslinux.Daemon' on session bus`.
|
|
||||||
//!
|
|
||||||
//! You may prefer to adapt it, instead of using it verbatim.
|
|
||||||
//!
|
|
||||||
//! More information can be found in the
|
|
||||||
//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
|
|
||||||
//! section of the zbus documentation.
|
|
||||||
//!
|
|
||||||
//! This `DBus` object implements
|
|
||||||
//! [standard `DBus` interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
|
|
||||||
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
|
|
||||||
//!
|
|
||||||
//! * [`zbus::fdo::PeerProxy`]
|
|
||||||
//! * [`zbus::fdo::IntrospectableProxy`]
|
|
||||||
//! * [`zbus::fdo::PropertiesProxy`]
|
|
||||||
//!
|
|
||||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
|
||||||
#![allow(clippy::too_many_arguments)]
|
|
||||||
|
|
||||||
use zbus::dbus_proxy;
|
|
||||||
|
|
||||||
#[dbus_proxy(interface = "org.asuslinux.Daemon")]
|
|
||||||
trait Daemon {
|
|
||||||
/// InsertAsusGif method
|
|
||||||
fn insert_asus_gif(
|
|
||||||
&self,
|
|
||||||
index: u32,
|
|
||||||
file: &str,
|
|
||||||
time: u32,
|
|
||||||
count: u32,
|
|
||||||
brightness: f64,
|
|
||||||
) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
/// InsertImage method
|
|
||||||
fn insert_image(
|
|
||||||
&self,
|
|
||||||
index: u32,
|
|
||||||
file: &str,
|
|
||||||
scale: f64,
|
|
||||||
angle: f64,
|
|
||||||
xy: &(f64, f64),
|
|
||||||
brightness: f64,
|
|
||||||
) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
/// InsertImageGif method
|
|
||||||
fn insert_image_gif(
|
|
||||||
&self,
|
|
||||||
index: u32,
|
|
||||||
file: &str,
|
|
||||||
scale: f64,
|
|
||||||
angle: f64,
|
|
||||||
xy: &(f64, f64),
|
|
||||||
time: u32,
|
|
||||||
count: u32,
|
|
||||||
brightness: f64,
|
|
||||||
) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
/// InsertPause method
|
|
||||||
fn insert_pause(&self, index: u32, millis: u64) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
/// RemoveItem method
|
|
||||||
fn remove_item(&self, index: u32) -> zbus::Result<String>;
|
|
||||||
|
|
||||||
/// SetState method
|
|
||||||
fn set_state(&self, on: bool) -> zbus::Result<()>;
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "daemon"
|
|
||||||
license = "MPL-2.0"
|
|
||||||
version.workspace = true
|
|
||||||
readme = "README.md"
|
|
||||||
authors = ["Luke <luke@ljones.dev>"]
|
|
||||||
repository = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
|
||||||
homepage = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
|
||||||
description = "A daemon app for ASUS GX502 and similar laptops to control missing features"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "daemon"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "asusd"
|
|
||||||
path = "src/daemon.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
config-traits = { path = "../config-traits" }
|
|
||||||
rog_anime = { path = "../rog-anime", features = ["dbus"] }
|
|
||||||
rog_aura = { path = "../rog-aura", features = ["dbus"] }
|
|
||||||
rog_platform = { path = "../rog-platform" }
|
|
||||||
rog_profiles = { path = "../rog-profiles" }
|
|
||||||
rog_dbus = { path = "../rog-dbus" }
|
|
||||||
|
|
||||||
async-trait.workspace = true
|
|
||||||
tokio.workspace = true
|
|
||||||
|
|
||||||
# cli and logging
|
|
||||||
log.workspace = true
|
|
||||||
env_logger.workspace = true
|
|
||||||
|
|
||||||
zbus.workspace = true
|
|
||||||
logind-zbus.workspace = true
|
|
||||||
|
|
||||||
# serialisation
|
|
||||||
serde.workspace = true
|
|
||||||
serde_derive.workspace = true
|
|
||||||
|
|
||||||
# Device control
|
|
||||||
sysfs-class.workspace = true # used for backlight control and baord ID
|
|
||||||
|
|
||||||
concat-idents.workspace = true
|
|
||||||
|
|
||||||
systemd-zbus = "*"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
cargo-husky.workspace = true
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
use config_traits::{StdConfig, StdConfigLoad2};
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
const CONFIG_FILE: &str = "asusd.ron";
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default, Debug)]
|
|
||||||
pub struct Config {
|
|
||||||
/// Save charge limit for restoring on boot
|
|
||||||
pub bat_charge_limit: u8,
|
|
||||||
pub panel_od: bool,
|
|
||||||
pub disable_nvidia_powerd_on_battery: bool,
|
|
||||||
pub ac_command: String,
|
|
||||||
pub bat_command: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for Config {
|
|
||||||
fn new() -> Self {
|
|
||||||
Config {
|
|
||||||
bat_charge_limit: 100,
|
|
||||||
panel_od: false,
|
|
||||||
disable_nvidia_powerd_on_battery: true,
|
|
||||||
ac_command: String::new(),
|
|
||||||
bat_command: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
CONFIG_FILE.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad2<Config455, Config458> for Config {}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default)]
|
|
||||||
#[serde(default)]
|
|
||||||
pub struct Config455 {
|
|
||||||
/// Save charge limit for restoring on boot
|
|
||||||
pub bat_charge_limit: u8,
|
|
||||||
pub panel_od: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Config455> for Config {
|
|
||||||
fn from(c: Config455) -> Self {
|
|
||||||
Self {
|
|
||||||
bat_charge_limit: c.bat_charge_limit,
|
|
||||||
panel_od: c.panel_od,
|
|
||||||
disable_nvidia_powerd_on_battery: true,
|
|
||||||
ac_command: String::new(),
|
|
||||||
bat_command: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default)]
|
|
||||||
pub struct Config458 {
|
|
||||||
/// Save charge limit for restoring on boot
|
|
||||||
pub bat_charge_limit: u8,
|
|
||||||
pub panel_od: bool,
|
|
||||||
pub ac_command: String,
|
|
||||||
pub bat_command: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Config458> for Config {
|
|
||||||
fn from(c: Config458) -> Self {
|
|
||||||
Self {
|
|
||||||
bat_charge_limit: c.bat_charge_limit,
|
|
||||||
panel_od: c.panel_od,
|
|
||||||
disable_nvidia_powerd_on_battery: true,
|
|
||||||
ac_command: c.ac_command,
|
|
||||||
bat_command: c.bat_command,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use config_traits::{StdConfig, StdConfigLoad2};
|
|
||||||
use rog_anime::error::AnimeError;
|
|
||||||
use rog_anime::usb::Brightness;
|
|
||||||
use rog_anime::{ActionData, ActionLoader, AnimTime, Animations, AnimeType, Fade, Vec2};
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
const CONFIG_FILE: &str = "anime.ron";
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct AnimeConfigV460 {
|
|
||||||
pub system: Vec<ActionLoader>,
|
|
||||||
pub boot: Vec<ActionLoader>,
|
|
||||||
pub wake: Vec<ActionLoader>,
|
|
||||||
pub sleep: Vec<ActionLoader>,
|
|
||||||
pub shutdown: Vec<ActionLoader>,
|
|
||||||
pub brightness: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AnimeConfigV460> for AnimeConfig {
|
|
||||||
fn from(c: AnimeConfigV460) -> AnimeConfig {
|
|
||||||
AnimeConfig {
|
|
||||||
system: c.system,
|
|
||||||
boot: c.boot,
|
|
||||||
wake: c.wake,
|
|
||||||
sleep: c.sleep,
|
|
||||||
shutdown: c.shutdown,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct AnimeConfigV5 {
|
|
||||||
pub system: Vec<ActionLoader>,
|
|
||||||
pub boot: Vec<ActionLoader>,
|
|
||||||
pub wake: Vec<ActionLoader>,
|
|
||||||
pub sleep: Vec<ActionLoader>,
|
|
||||||
pub shutdown: Vec<ActionLoader>,
|
|
||||||
pub brightness: f32,
|
|
||||||
pub awake_enabled: bool,
|
|
||||||
pub boot_anim_enabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AnimeConfigV5> for AnimeConfig {
|
|
||||||
fn from(c: AnimeConfigV5) -> AnimeConfig {
|
|
||||||
AnimeConfig {
|
|
||||||
system: c.system,
|
|
||||||
boot: c.boot,
|
|
||||||
wake: c.wake,
|
|
||||||
sleep: c.sleep,
|
|
||||||
shutdown: c.shutdown,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default)]
|
|
||||||
pub struct AnimeConfigCached {
|
|
||||||
pub system: Vec<ActionData>,
|
|
||||||
pub boot: Vec<ActionData>,
|
|
||||||
pub wake: Vec<ActionData>,
|
|
||||||
pub sleep: Vec<ActionData>,
|
|
||||||
pub shutdown: Vec<ActionData>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnimeConfigCached {
|
|
||||||
pub fn init_from_config(
|
|
||||||
&mut self,
|
|
||||||
config: &AnimeConfig,
|
|
||||||
anime_type: AnimeType,
|
|
||||||
) -> Result<(), AnimeError> {
|
|
||||||
let mut sys = Vec::with_capacity(config.system.len());
|
|
||||||
for ani in &config.system {
|
|
||||||
sys.push(ActionData::from_anime_action(anime_type, ani)?);
|
|
||||||
}
|
|
||||||
self.system = sys;
|
|
||||||
|
|
||||||
let mut boot = Vec::with_capacity(config.boot.len());
|
|
||||||
for ani in &config.boot {
|
|
||||||
boot.push(ActionData::from_anime_action(anime_type, ani)?);
|
|
||||||
}
|
|
||||||
self.boot = boot;
|
|
||||||
|
|
||||||
let mut wake = Vec::with_capacity(config.wake.len());
|
|
||||||
for ani in &config.wake {
|
|
||||||
wake.push(ActionData::from_anime_action(anime_type, ani)?);
|
|
||||||
}
|
|
||||||
self.wake = wake;
|
|
||||||
|
|
||||||
let mut sleep = Vec::with_capacity(config.sleep.len());
|
|
||||||
for ani in &config.sleep {
|
|
||||||
sleep.push(ActionData::from_anime_action(anime_type, ani)?);
|
|
||||||
}
|
|
||||||
self.sleep = sleep;
|
|
||||||
|
|
||||||
let mut shutdown = Vec::with_capacity(config.shutdown.len());
|
|
||||||
for ani in &config.shutdown {
|
|
||||||
shutdown.push(ActionData::from_anime_action(anime_type, ani)?);
|
|
||||||
}
|
|
||||||
self.shutdown = shutdown;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Config for base system actions for the anime display
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct AnimeConfig {
|
|
||||||
pub system: Vec<ActionLoader>,
|
|
||||||
pub boot: Vec<ActionLoader>,
|
|
||||||
pub wake: Vec<ActionLoader>,
|
|
||||||
pub sleep: Vec<ActionLoader>,
|
|
||||||
pub shutdown: Vec<ActionLoader>,
|
|
||||||
pub brightness: f32,
|
|
||||||
pub display_enabled: bool,
|
|
||||||
pub display_brightness: Brightness,
|
|
||||||
pub builtin_anims_enabled: bool,
|
|
||||||
pub builtin_anims: Animations,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AnimeConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
AnimeConfig {
|
|
||||||
system: Vec::new(),
|
|
||||||
boot: Vec::new(),
|
|
||||||
wake: Vec::new(),
|
|
||||||
sleep: Vec::new(),
|
|
||||||
shutdown: Vec::new(),
|
|
||||||
brightness: 1.0,
|
|
||||||
display_enabled: true,
|
|
||||||
display_brightness: Brightness::Med,
|
|
||||||
builtin_anims_enabled: true,
|
|
||||||
builtin_anims: Animations::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for AnimeConfig {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self::create_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
CONFIG_FILE.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad2<AnimeConfigV460, AnimeConfigV5> for AnimeConfig {}
|
|
||||||
|
|
||||||
impl AnimeConfig {
|
|
||||||
// fn clamp_config_brightness(mut config: &mut AnimeConfig) {
|
|
||||||
// if config.brightness < 0.0 || config.brightness > 1.0 {
|
|
||||||
// warn!(
|
|
||||||
// "Clamped brightness to [0.0 ; 1.0], was {}",
|
|
||||||
// config.brightness
|
|
||||||
// );
|
|
||||||
// config.brightness = f32::max(0.0, f32::min(1.0, config.brightness));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn create_default() -> Self {
|
|
||||||
// create a default config here
|
|
||||||
AnimeConfig {
|
|
||||||
system: vec![],
|
|
||||||
boot: vec![ActionLoader::ImageAnimation {
|
|
||||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
|
||||||
scale: 0.9,
|
|
||||||
angle: 0.65,
|
|
||||||
translation: Vec2::default(),
|
|
||||||
brightness: 1.0,
|
|
||||||
time: AnimTime::Fade(Fade::new(
|
|
||||||
Duration::from_secs(2),
|
|
||||||
Some(Duration::from_secs(2)),
|
|
||||||
Duration::from_secs(2),
|
|
||||||
)),
|
|
||||||
}],
|
|
||||||
wake: vec![ActionLoader::ImageAnimation {
|
|
||||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
|
||||||
scale: 0.9,
|
|
||||||
angle: 0.65,
|
|
||||||
translation: Vec2::default(),
|
|
||||||
brightness: 1.0,
|
|
||||||
time: AnimTime::Fade(Fade::new(
|
|
||||||
Duration::from_secs(2),
|
|
||||||
Some(Duration::from_secs(2)),
|
|
||||||
Duration::from_secs(2),
|
|
||||||
)),
|
|
||||||
}],
|
|
||||||
sleep: vec![ActionLoader::ImageAnimation {
|
|
||||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
|
||||||
scale: 0.9,
|
|
||||||
angle: 0.0,
|
|
||||||
translation: Vec2::new(3.0, 2.0),
|
|
||||||
brightness: 1.0,
|
|
||||||
time: AnimTime::Infinite,
|
|
||||||
}],
|
|
||||||
shutdown: vec![ActionLoader::ImageAnimation {
|
|
||||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
|
||||||
scale: 0.9,
|
|
||||||
angle: 0.0,
|
|
||||||
translation: Vec2::new(3.0, 2.0),
|
|
||||||
brightness: 1.0,
|
|
||||||
time: AnimTime::Infinite,
|
|
||||||
}],
|
|
||||||
brightness: 1.0,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
pub mod config;
|
|
||||||
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
|
|
||||||
pub mod trait_impls;
|
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread::sleep;
|
|
||||||
|
|
||||||
use ::zbus::export::futures_util::lock::Mutex;
|
|
||||||
use log::{error, info, warn};
|
|
||||||
use rog_anime::error::AnimeError;
|
|
||||||
use rog_anime::usb::{get_anime_type, pkt_flush, pkt_set_enable_powersave_anim, pkts_for_init};
|
|
||||||
use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType, AnimeType};
|
|
||||||
use rog_platform::hid_raw::HidRaw;
|
|
||||||
use rog_platform::supported::AnimeSupportedFunctions;
|
|
||||||
use rog_platform::usb_raw::USBRaw;
|
|
||||||
|
|
||||||
use self::config::{AnimeConfig, AnimeConfigCached};
|
|
||||||
use crate::error::RogError;
|
|
||||||
use crate::GetSupported;
|
|
||||||
|
|
||||||
impl GetSupported for CtrlAnime {
|
|
||||||
type A = AnimeSupportedFunctions;
|
|
||||||
|
|
||||||
fn get_supported() -> Self::A {
|
|
||||||
AnimeSupportedFunctions(HidRaw::new("193b").is_ok())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CtrlAnime {
|
|
||||||
// node: HidRaw,
|
|
||||||
node: USBRaw,
|
|
||||||
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(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
|
|
||||||
// let node = HidRaw::new("193b")?;
|
|
||||||
let node = USBRaw::new(0x193b)?;
|
|
||||||
let anime_type = get_anime_type().unwrap_or(AnimeType::GA401);
|
|
||||||
|
|
||||||
info!("Device has an AniMe Matrix display: {anime_type:?}");
|
|
||||||
let mut cache = AnimeConfigCached::default();
|
|
||||||
cache.init_from_config(&config, anime_type)?;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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 * self.config.brightness;
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use config_traits::StdConfig;
|
|
||||||
use log::warn;
|
|
||||||
use rog_anime::usb::{
|
|
||||||
pkt_set_brightness, pkt_set_builtin_animations, pkt_set_enable_display,
|
|
||||||
pkt_set_enable_powersave_anim, AnimAwake, AnimBooting, AnimShutdown, AnimSleeping, Brightness,
|
|
||||||
};
|
|
||||||
use rog_anime::{AnimeDataBuffer, DeviceState};
|
|
||||||
use zbus::export::futures_util::lock::Mutex;
|
|
||||||
use zbus::{dbus_interface, Connection, SignalContext};
|
|
||||||
|
|
||||||
use super::CtrlAnime;
|
|
||||||
use crate::error::RogError;
|
|
||||||
|
|
||||||
pub(super) const ZBUS_PATH: &str = "/org/asuslinux/Anime";
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
|
|
||||||
|
|
||||||
/// The struct with the main dbus methods requires this trait
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::ZbusRun for CtrlAnimeZbus {
|
|
||||||
async fn add_to_server(self, server: &mut Connection) {
|
|
||||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// None of these calls can be guarnateed to succeed unless we loop until okay
|
|
||||||
// If the try_lock *does* succeed then any other thread trying to lock will not
|
|
||||||
// grab it until we finish.
|
|
||||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
||||||
impl CtrlAnimeZbus {
|
|
||||||
/// 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<()> {
|
|
||||||
let lock = self.0.lock().await;
|
|
||||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
|
||||||
lock.write_data_buffer(input).map_err(|err| {
|
|
||||||
warn!("rog_anime::run_animation:callback {}", err);
|
|
||||||
err
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the global AniMe brightness
|
|
||||||
async fn set_image_brightness(&self, bright: f32) {
|
|
||||||
let mut lock = self.0.lock().await;
|
|
||||||
let mut bright = bright;
|
|
||||||
if bright < 0.0 {
|
|
||||||
bright = 0.0;
|
|
||||||
} else if bright > 1.0 {
|
|
||||||
bright = 1.0;
|
|
||||||
}
|
|
||||||
lock.config.brightness = bright;
|
|
||||||
lock.config.write();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set base brightness level
|
|
||||||
// TODO: enum for brightness
|
|
||||||
async fn set_brightness(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
brightness: Brightness,
|
|
||||||
) {
|
|
||||||
let mut lock = self.0.lock().await;
|
|
||||||
lock.node
|
|
||||||
.write_bytes(&pkt_set_brightness(brightness))
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("rog_anime::run_animation:callback {}", err);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
lock.config.display_brightness = brightness;
|
|
||||||
lock.config.write();
|
|
||||||
|
|
||||||
Self::notify_device_state(
|
|
||||||
&ctxt,
|
|
||||||
DeviceState {
|
|
||||||
display_enabled: lock.config.display_enabled,
|
|
||||||
display_brightness: lock.config.display_brightness,
|
|
||||||
builtin_anims_enabled: lock.config.builtin_anims_enabled,
|
|
||||||
builtin_anims: lock.config.builtin_anims,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable the builtin animations or not
|
|
||||||
async fn set_builtins_enabled(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
enabled: bool,
|
|
||||||
) {
|
|
||||||
let mut lock = self.0.lock().await;
|
|
||||||
lock.node
|
|
||||||
.write_bytes(&pkt_set_enable_powersave_anim(enabled))
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("rog_anime::run_animation:callback {}", err);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
lock.config.builtin_anims_enabled = enabled;
|
|
||||||
lock.config.write();
|
|
||||||
if enabled {
|
|
||||||
lock.thread_exit.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::notify_device_state(
|
|
||||||
&ctxt,
|
|
||||||
DeviceState {
|
|
||||||
display_enabled: lock.config.display_enabled,
|
|
||||||
display_brightness: lock.config.display_brightness,
|
|
||||||
builtin_anims_enabled: lock.config.builtin_anims_enabled,
|
|
||||||
builtin_anims: lock.config.builtin_anims,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set which builtin animation is used for each stage
|
|
||||||
async fn set_builtin_animations(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
boot: AnimBooting,
|
|
||||||
awake: AnimAwake,
|
|
||||||
sleep: AnimSleeping,
|
|
||||||
shutdown: AnimShutdown,
|
|
||||||
) {
|
|
||||||
let mut lock = self.0.lock().await;
|
|
||||||
lock.node
|
|
||||||
.write_bytes(&pkt_set_enable_powersave_anim(true))
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("rog_anime::run_animation:callback {}", err);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
lock.node
|
|
||||||
.write_bytes(&pkt_set_builtin_animations(boot, awake, sleep, shutdown))
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("rog_anime::run_animation:callback {}", err);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
lock.config.builtin_anims.boot = boot;
|
|
||||||
lock.config.builtin_anims.sleep = sleep;
|
|
||||||
lock.config.builtin_anims.awake = awake;
|
|
||||||
lock.config.builtin_anims.shutdown = shutdown;
|
|
||||||
lock.config.write();
|
|
||||||
|
|
||||||
Self::notify_device_state(
|
|
||||||
&ctxt,
|
|
||||||
DeviceState {
|
|
||||||
display_enabled: lock.config.display_enabled,
|
|
||||||
display_brightness: lock.config.display_brightness,
|
|
||||||
builtin_anims_enabled: lock.config.builtin_anims_enabled,
|
|
||||||
builtin_anims: lock.config.builtin_anims,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set whether the AniMe is enabled at all
|
|
||||||
async fn set_enable_display(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
enabled: bool,
|
|
||||||
) {
|
|
||||||
let mut lock = self.0.lock().await;
|
|
||||||
lock.node
|
|
||||||
.write_bytes(&pkt_set_enable_display(enabled))
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("rog_anime::run_animation:callback {}", err);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
lock.config.display_enabled = enabled;
|
|
||||||
lock.config.write();
|
|
||||||
|
|
||||||
Self::notify_device_state(
|
|
||||||
&ctxt,
|
|
||||||
DeviceState {
|
|
||||||
display_enabled: lock.config.display_enabled,
|
|
||||||
display_brightness: lock.config.display_brightness,
|
|
||||||
builtin_anims_enabled: lock.config.builtin_anims_enabled,
|
|
||||||
builtin_anims: lock.config.builtin_anims,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
let lock = self.0.lock().await;
|
|
||||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
|
||||||
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the device state as stored by asusd
|
|
||||||
// #[dbus_interface(property)]
|
|
||||||
async fn device_state(&self) -> DeviceState {
|
|
||||||
let lock = self.0.lock().await;
|
|
||||||
DeviceState {
|
|
||||||
display_enabled: lock.config.display_enabled,
|
|
||||||
display_brightness: lock.config.display_brightness,
|
|
||||||
builtin_anims_enabled: lock.config.builtin_anims_enabled,
|
|
||||||
builtin_anims: lock.config.builtin_anims,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notify listeners of the status of AniMe LED power and factory
|
|
||||||
/// system-status animations
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_device_state(ctxt: &SignalContext<'_>, data: DeviceState) -> zbus::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::CtrlTask for CtrlAnimeZbus {
|
|
||||||
fn zbus_path() -> &'static str {
|
|
||||||
ZBUS_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
|
||||||
let inner1 = self.0.clone();
|
|
||||||
let inner2 = self.0.clone();
|
|
||||||
let inner3 = self.0.clone();
|
|
||||||
let inner4 = self.0.clone();
|
|
||||||
self.create_sys_event_tasks(
|
|
||||||
move || {
|
|
||||||
// on_sleep
|
|
||||||
let inner1 = inner1.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner1.lock().await;
|
|
||||||
CtrlAnime::run_thread(inner1.clone(), lock.cache.sleep.clone(), true).await;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || {
|
|
||||||
// on_wake
|
|
||||||
let inner2 = inner2.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner2.lock().await;
|
|
||||||
CtrlAnime::run_thread(inner2.clone(), lock.cache.wake.clone(), true).await;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || {
|
|
||||||
// on_shutdown
|
|
||||||
let inner3 = inner3.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner3.lock().await;
|
|
||||||
CtrlAnime::run_thread(inner3.clone(), lock.cache.shutdown.clone(), true).await;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || {
|
|
||||||
// on_boot
|
|
||||||
let inner4 = inner4.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner4.lock().await;
|
|
||||||
CtrlAnime::run_thread(inner4.clone(), lock.cache.boot.clone(), true).await;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::Reloadable for CtrlAnimeZbus {
|
|
||||||
async fn reload(&mut self) -> Result<(), RogError> {
|
|
||||||
if let Some(lock) = self.0.try_lock() {
|
|
||||||
let anim = &lock.config.builtin_anims;
|
|
||||||
lock.node
|
|
||||||
.write_bytes(&pkt_set_enable_display(lock.config.display_enabled))?;
|
|
||||||
lock.node.write_bytes(&pkt_set_enable_powersave_anim(
|
|
||||||
lock.config.builtin_anims_enabled,
|
|
||||||
))?;
|
|
||||||
lock.node.write_bytes(&pkt_set_builtin_animations(
|
|
||||||
anim.boot,
|
|
||||||
anim.awake,
|
|
||||||
anim.sleep,
|
|
||||||
anim.shutdown,
|
|
||||||
))?;
|
|
||||||
|
|
||||||
if lock.config.builtin_anims_enabled && !lock.cache.boot.is_empty() {
|
|
||||||
lock.node
|
|
||||||
.write_bytes(&pkt_set_enable_powersave_anim(false))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
let action = lock.cache.boot.clone();
|
|
||||||
CtrlAnime::run_thread(self.0.clone(), action, true).await;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
use std::collections::{BTreeMap, HashSet};
|
|
||||||
|
|
||||||
use config_traits::{StdConfig, StdConfigLoad};
|
|
||||||
use rog_aura::aura_detection::LaptopLedData;
|
|
||||||
use rog_aura::usb::{AuraDevRog1, AuraDevRog2, AuraDevTuf, AuraDevice, AuraPowerDev};
|
|
||||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Direction, LedBrightness, Speed, GRADIENT};
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
const CONFIG_FILE: &str = "aura.ron";
|
|
||||||
|
|
||||||
/// Enable/disable LED control in various states such as
|
|
||||||
/// when the device is awake, suspended, shutting down or
|
|
||||||
/// booting.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub enum AuraPowerConfig {
|
|
||||||
AuraDevTuf(HashSet<AuraDevTuf>),
|
|
||||||
AuraDevRog1(HashSet<AuraDevRog1>),
|
|
||||||
AuraDevRog2(HashSet<AuraDevRog2>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuraPowerConfig {
|
|
||||||
/// Invalid for TUF laptops
|
|
||||||
pub fn to_bytes(control: &Self) -> [u8; 4] {
|
|
||||||
match control {
|
|
||||||
AuraPowerConfig::AuraDevTuf(_) => [0, 0, 0, 0],
|
|
||||||
AuraPowerConfig::AuraDevRog1(c) => {
|
|
||||||
let c: Vec<AuraDevRog1> = c.iter().copied().collect();
|
|
||||||
AuraDevRog1::to_bytes(&c)
|
|
||||||
}
|
|
||||||
AuraPowerConfig::AuraDevRog2(c) => {
|
|
||||||
let c: Vec<AuraDevRog2> = c.iter().copied().collect();
|
|
||||||
AuraDevRog2::to_bytes(&c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_tuf_bool_array(control: &Self) -> Option<[bool; 5]> {
|
|
||||||
if let Self::AuraDevTuf(c) = control {
|
|
||||||
return Some([
|
|
||||||
true,
|
|
||||||
c.contains(&AuraDevTuf::Boot),
|
|
||||||
c.contains(&AuraDevTuf::Awake),
|
|
||||||
c.contains(&AuraDevTuf::Sleep),
|
|
||||||
c.contains(&AuraDevTuf::Keyboard),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Self::AuraDevRog1(c) = control {
|
|
||||||
return Some([
|
|
||||||
true,
|
|
||||||
c.contains(&AuraDevRog1::Boot),
|
|
||||||
c.contains(&AuraDevRog1::Awake),
|
|
||||||
c.contains(&AuraDevRog1::Sleep),
|
|
||||||
c.contains(&AuraDevRog1::Keyboard),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_tuf(&mut self, power: AuraDevTuf, on: bool) {
|
|
||||||
if let Self::AuraDevTuf(p) = self {
|
|
||||||
if on {
|
|
||||||
p.insert(power);
|
|
||||||
} else {
|
|
||||||
p.remove(&power);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_0x1866(&mut self, power: AuraDevRog1, on: bool) {
|
|
||||||
if let Self::AuraDevRog1(p) = self {
|
|
||||||
if on {
|
|
||||||
p.insert(power);
|
|
||||||
} else {
|
|
||||||
p.remove(&power);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_0x19b6(&mut self, power: AuraDevRog2, on: bool) {
|
|
||||||
if let Self::AuraDevRog2(p) = self {
|
|
||||||
if on {
|
|
||||||
p.insert(power);
|
|
||||||
} else {
|
|
||||||
p.remove(&power);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&AuraPowerConfig> for AuraPowerDev {
|
|
||||||
fn from(config: &AuraPowerConfig) -> Self {
|
|
||||||
match config {
|
|
||||||
AuraPowerConfig::AuraDevTuf(d) => AuraPowerDev {
|
|
||||||
tuf: d.iter().copied().collect(),
|
|
||||||
x1866: vec![],
|
|
||||||
x19b6: vec![],
|
|
||||||
},
|
|
||||||
AuraPowerConfig::AuraDevRog1(d) => AuraPowerDev {
|
|
||||||
tuf: vec![],
|
|
||||||
x1866: d.iter().copied().collect(),
|
|
||||||
x19b6: vec![],
|
|
||||||
},
|
|
||||||
AuraPowerConfig::AuraDevRog2(d) => AuraPowerDev {
|
|
||||||
tuf: vec![],
|
|
||||||
x1866: vec![],
|
|
||||||
x19b6: d.iter().copied().collect(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
||||||
// #[serde(default)]
|
|
||||||
pub struct AuraConfig {
|
|
||||||
pub brightness: LedBrightness,
|
|
||||||
pub current_mode: AuraModeNum,
|
|
||||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
|
||||||
pub multizone: Option<BTreeMap<AuraModeNum, Vec<AuraEffect>>>,
|
|
||||||
pub multizone_on: bool,
|
|
||||||
pub enabled: AuraPowerConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for AuraConfig {
|
|
||||||
fn new() -> Self {
|
|
||||||
// Self::create_default(AuraDevice::X19b6, &LaptopLedData::get_data())
|
|
||||||
panic!("AuraConfig::new() should not be used, use AuraConfig::create_default() instead");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
CONFIG_FILE.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad for AuraConfig {}
|
|
||||||
|
|
||||||
impl AuraConfig {
|
|
||||||
pub fn create_default(prod_id: AuraDevice, support_data: &LaptopLedData) -> Self {
|
|
||||||
// create a default config here
|
|
||||||
let enabled = if prod_id == AuraDevice::X19b6 {
|
|
||||||
AuraPowerConfig::AuraDevRog2(HashSet::from([
|
|
||||||
AuraDevRog2::BootLogo,
|
|
||||||
AuraDevRog2::BootKeyb,
|
|
||||||
AuraDevRog2::SleepLogo,
|
|
||||||
AuraDevRog2::SleepKeyb,
|
|
||||||
AuraDevRog2::AwakeLogo,
|
|
||||||
AuraDevRog2::AwakeKeyb,
|
|
||||||
AuraDevRog2::ShutdownLogo,
|
|
||||||
AuraDevRog2::ShutdownKeyb,
|
|
||||||
AuraDevRog2::BootBar,
|
|
||||||
AuraDevRog2::AwakeBar,
|
|
||||||
AuraDevRog2::SleepBar,
|
|
||||||
AuraDevRog2::ShutdownBar,
|
|
||||||
AuraDevRog2::BootRearGlow,
|
|
||||||
AuraDevRog2::AwakeRearGlow,
|
|
||||||
AuraDevRog2::SleepRearGlow,
|
|
||||||
AuraDevRog2::ShutdownRearGlow,
|
|
||||||
]))
|
|
||||||
} else if prod_id == AuraDevice::Tuf {
|
|
||||||
AuraPowerConfig::AuraDevTuf(HashSet::from([
|
|
||||||
AuraDevTuf::Awake,
|
|
||||||
AuraDevTuf::Boot,
|
|
||||||
AuraDevTuf::Sleep,
|
|
||||||
AuraDevTuf::Keyboard,
|
|
||||||
]))
|
|
||||||
} else {
|
|
||||||
AuraPowerConfig::AuraDevRog1(HashSet::from([
|
|
||||||
AuraDevRog1::Awake,
|
|
||||||
AuraDevRog1::Boot,
|
|
||||||
AuraDevRog1::Sleep,
|
|
||||||
AuraDevRog1::Keyboard,
|
|
||||||
AuraDevRog1::Lightbar,
|
|
||||||
]))
|
|
||||||
};
|
|
||||||
let mut config = AuraConfig {
|
|
||||||
brightness: LedBrightness::Med,
|
|
||||||
current_mode: AuraModeNum::Static,
|
|
||||||
builtins: BTreeMap::new(),
|
|
||||||
multizone: None,
|
|
||||||
multizone_on: false,
|
|
||||||
enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
for n in &support_data.basic_modes {
|
|
||||||
config
|
|
||||||
.builtins
|
|
||||||
.insert(*n, AuraEffect::default_with_mode(*n));
|
|
||||||
|
|
||||||
if !support_data.basic_zones.is_empty() {
|
|
||||||
let mut default = vec![];
|
|
||||||
for (i, tmp) in support_data.basic_zones.iter().enumerate() {
|
|
||||||
default.push(AuraEffect {
|
|
||||||
mode: *n,
|
|
||||||
zone: *tmp,
|
|
||||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
|
||||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
|
||||||
speed: Speed::Med,
|
|
||||||
direction: Direction::Left,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(m) = config.multizone.as_mut() {
|
|
||||||
m.insert(*n, default);
|
|
||||||
} else {
|
|
||||||
let mut tmp = BTreeMap::new();
|
|
||||||
tmp.insert(*n, default);
|
|
||||||
config.multizone = Some(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the mode data, current mode, and if multizone enabled.
|
|
||||||
///
|
|
||||||
/// Multipurpose, will accept `AuraEffect` with zones and put in the correct
|
|
||||||
/// store.
|
|
||||||
pub fn set_builtin(&mut self, effect: AuraEffect) {
|
|
||||||
self.current_mode = effect.mode;
|
|
||||||
if effect.zone() == AuraZone::None {
|
|
||||||
self.builtins.insert(*effect.mode(), effect);
|
|
||||||
self.multizone_on = false;
|
|
||||||
} else {
|
|
||||||
if let Some(multi) = self.multizone.as_mut() {
|
|
||||||
if let Some(fx) = multi.get_mut(effect.mode()) {
|
|
||||||
for fx in fx.iter_mut() {
|
|
||||||
if fx.zone == effect.zone {
|
|
||||||
*fx = effect;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fx.push(effect);
|
|
||||||
} else {
|
|
||||||
multi.insert(*effect.mode(), vec![effect]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut tmp = BTreeMap::new();
|
|
||||||
tmp.insert(*effect.mode(), vec![effect]);
|
|
||||||
self.multizone = Some(tmp);
|
|
||||||
}
|
|
||||||
self.multizone_on = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect]> {
|
|
||||||
if let Some(multi) = &self.multizone {
|
|
||||||
return multi.get(&aura_type).map(|v| v.as_slice());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use rog_aura::aura_detection::LaptopLedData;
|
|
||||||
use rog_aura::usb::AuraDevice;
|
|
||||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
|
||||||
|
|
||||||
use super::AuraConfig;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_multizone_4key_config() {
|
|
||||||
let mut config = AuraConfig::create_default(AuraDevice::X19b6, &LaptopLedData::default());
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
colour1: Colour(0xff, 0x00, 0xff),
|
|
||||||
zone: AuraZone::Key1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
config.set_builtin(effect);
|
|
||||||
|
|
||||||
assert!(config.multizone.is_some());
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
colour1: Colour(0x00, 0xff, 0xff),
|
|
||||||
zone: AuraZone::Key2,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
config.set_builtin(effect);
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
colour1: Colour(0xff, 0xff, 0x00),
|
|
||||||
zone: AuraZone::Key3,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
config.set_builtin(effect);
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
colour1: Colour(0x00, 0xff, 0x00),
|
|
||||||
zone: AuraZone::Key4,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let effect_clone = effect.clone();
|
|
||||||
config.set_builtin(effect);
|
|
||||||
// This should replace existing
|
|
||||||
config.set_builtin(effect_clone);
|
|
||||||
|
|
||||||
let res = config.multizone.unwrap();
|
|
||||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
|
||||||
assert_eq!(sta.len(), 4);
|
|
||||||
assert_eq!(sta[0].colour1, Colour(0xff, 0x00, 0xff));
|
|
||||||
assert_eq!(sta[1].colour1, Colour(0x00, 0xff, 0xff));
|
|
||||||
assert_eq!(sta[2].colour1, Colour(0xff, 0xff, 0x00));
|
|
||||||
assert_eq!(sta[3].colour1, Colour(0x00, 0xff, 0x00));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_multizone_multimode_config() {
|
|
||||||
let mut config = AuraConfig::create_default(AuraDevice::X19b6, &LaptopLedData::default());
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
zone: AuraZone::Key1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
config.set_builtin(effect);
|
|
||||||
|
|
||||||
assert!(config.multizone.is_some());
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
zone: AuraZone::Key2,
|
|
||||||
mode: AuraModeNum::Breathe,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
config.set_builtin(effect);
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
zone: AuraZone::Key3,
|
|
||||||
mode: AuraModeNum::Comet,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
config.set_builtin(effect);
|
|
||||||
|
|
||||||
let effect = AuraEffect {
|
|
||||||
zone: AuraZone::Key4,
|
|
||||||
mode: AuraModeNum::Pulse,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
config.set_builtin(effect);
|
|
||||||
|
|
||||||
let res = config.multizone.unwrap();
|
|
||||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
|
||||||
assert_eq!(sta.len(), 1);
|
|
||||||
|
|
||||||
let sta = res.get(&AuraModeNum::Breathe).unwrap();
|
|
||||||
assert_eq!(sta.len(), 1);
|
|
||||||
|
|
||||||
let sta = res.get(&AuraModeNum::Comet).unwrap();
|
|
||||||
assert_eq!(sta.len(), 1);
|
|
||||||
|
|
||||||
let sta = res.get(&AuraModeNum::Pulse).unwrap();
|
|
||||||
assert_eq!(sta.len(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,558 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use config_traits::{StdConfig, StdConfigLoad};
|
|
||||||
use log::{info, warn};
|
|
||||||
use rog_aura::advanced::{LedUsbPackets, UsbPackets};
|
|
||||||
use rog_aura::aura_detection::{LaptopLedData, ASUS_KEYBOARD_DEVICES};
|
|
||||||
use rog_aura::usb::{AuraDevice, LED_APPLY, LED_SET};
|
|
||||||
use rog_aura::{AuraEffect, AuraZone, Direction, LedBrightness, Speed, GRADIENT, LED_MSG_LEN};
|
|
||||||
use rog_platform::hid_raw::HidRaw;
|
|
||||||
use rog_platform::keyboard_led::KeyboardLed;
|
|
||||||
use rog_platform::supported::LedSupportedFunctions;
|
|
||||||
|
|
||||||
use super::config::{AuraConfig, AuraPowerConfig};
|
|
||||||
use crate::error::RogError;
|
|
||||||
use crate::GetSupported;
|
|
||||||
|
|
||||||
impl GetSupported for CtrlKbdLed {
|
|
||||||
type A = LedSupportedFunctions;
|
|
||||||
|
|
||||||
fn get_supported() -> Self::A {
|
|
||||||
// let mode = <&str>::from(&<AuraModes>::from(*mode));
|
|
||||||
let laptop = LaptopLedData::get_data();
|
|
||||||
let stock_led_modes = laptop.basic_modes;
|
|
||||||
let multizone_led_mode = laptop.basic_zones;
|
|
||||||
let advanced_type = laptop.advanced_type;
|
|
||||||
|
|
||||||
let mut prod_id = AuraDevice::Unknown;
|
|
||||||
for prod in ASUS_KEYBOARD_DEVICES {
|
|
||||||
if HidRaw::new(prod.into()).is_ok() {
|
|
||||||
prod_id = prod;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let rgb = KeyboardLed::new();
|
|
||||||
if let Ok(p) = rgb.as_ref() {
|
|
||||||
if p.has_kbd_rgb_mode() {
|
|
||||||
prod_id = AuraDevice::Tuf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LedSupportedFunctions {
|
|
||||||
dev_id: prod_id,
|
|
||||||
brightness: rgb.is_ok(),
|
|
||||||
basic_modes: stock_led_modes,
|
|
||||||
basic_zones: multizone_led_mode,
|
|
||||||
advanced_type: advanced_type.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd)]
|
|
||||||
pub enum LEDNode {
|
|
||||||
KbdLed(KeyboardLed),
|
|
||||||
Rog(HidRaw),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CtrlKbdLed {
|
|
||||||
// TODO: config stores the keyboard type as an AuraPower, use or update this
|
|
||||||
pub led_prod: AuraDevice,
|
|
||||||
pub led_node: LEDNode,
|
|
||||||
pub kd_brightness: KeyboardLed,
|
|
||||||
pub supported_modes: LaptopLedData,
|
|
||||||
pub flip_effect_write: bool,
|
|
||||||
pub per_key_mode_active: bool,
|
|
||||||
pub config: AuraConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CtrlKbdLed {
|
|
||||||
pub fn new(supported_modes: LaptopLedData) -> Result<Self, RogError> {
|
|
||||||
let mut led_prod = AuraDevice::Unknown;
|
|
||||||
let mut usb_node = None;
|
|
||||||
for prod in ASUS_KEYBOARD_DEVICES {
|
|
||||||
match HidRaw::new(prod.into()) {
|
|
||||||
Ok(node) => {
|
|
||||||
led_prod = prod;
|
|
||||||
usb_node = Some(node);
|
|
||||||
info!(
|
|
||||||
"Looked for keyboard controller 0x{}: Found",
|
|
||||||
<&str>::from(prod)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(err) => info!(
|
|
||||||
"Looked for keyboard controller 0x{}: {err}",
|
|
||||||
<&str>::from(prod)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let rgb_led = KeyboardLed::new()?;
|
|
||||||
|
|
||||||
if usb_node.is_none() && !rgb_led.has_kbd_rgb_mode() {
|
|
||||||
let dmi = sysfs_class::DmiId::default();
|
|
||||||
if let Ok(prod_family) = dmi.product_family() {
|
|
||||||
if prod_family.contains("TUF") {
|
|
||||||
warn!(
|
|
||||||
"kbd_rgb_mode was not found in the /sys/. You require a minimum 6.1 \
|
|
||||||
kernel and a supported TUF laptop"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(RogError::NoAuraKeyboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
let led_node = if let Some(rog) = usb_node {
|
|
||||||
info!("Found ROG USB keyboard");
|
|
||||||
LEDNode::Rog(rog)
|
|
||||||
} else if rgb_led.has_kbd_rgb_mode() {
|
|
||||||
info!("Found TUF keyboard");
|
|
||||||
LEDNode::KbdLed(rgb_led.clone())
|
|
||||||
} else {
|
|
||||||
LEDNode::None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut config_init = AuraConfig::create_default(led_prod, &supported_modes);
|
|
||||||
let mut config_loaded = config_init.clone().load();
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config_loaded.builtins = config_init.builtins;
|
|
||||||
|
|
||||||
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();
|
|
||||||
// only reuse a zone mode if the mode is supported
|
|
||||||
for mode in loaded {
|
|
||||||
if supported_modes.basic_modes.contains(&mode.mode) {
|
|
||||||
new_set.push(mode.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*mode.1 = new_set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*multizone_loaded = multizone_init;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ctrl = CtrlKbdLed {
|
|
||||||
led_prod,
|
|
||||||
led_node, // on TUF this is the same as rgb_led / kd_brightness
|
|
||||||
kd_brightness: rgb_led, // If was none then we already returned above
|
|
||||||
supported_modes,
|
|
||||||
flip_effect_write: false,
|
|
||||||
per_key_mode_active: false,
|
|
||||||
config: config_loaded,
|
|
||||||
};
|
|
||||||
Ok(ctrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn get_brightness(&self) -> Result<u8, RogError> {
|
|
||||||
self.kd_brightness
|
|
||||||
.get_brightness()
|
|
||||||
.map_err(RogError::Platform)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
|
|
||||||
self.kd_brightness
|
|
||||||
.set_brightness(brightness as u8)
|
|
||||||
.map_err(RogError::Platform)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_brightness(&mut self) -> Result<(), RogError> {
|
|
||||||
let mut bright = (self.config.brightness as u32) + 1;
|
|
||||||
if bright > 3 {
|
|
||||||
bright = 0;
|
|
||||||
}
|
|
||||||
self.config.brightness = <LedBrightness>::from(bright);
|
|
||||||
self.config.write();
|
|
||||||
self.set_brightness(self.config.brightness)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prev_brightness(&mut self) -> Result<(), RogError> {
|
|
||||||
let mut bright = self.config.brightness as u32;
|
|
||||||
if bright == 0 {
|
|
||||||
bright = 3;
|
|
||||||
} else {
|
|
||||||
bright -= 1;
|
|
||||||
}
|
|
||||||
self.config.brightness = <LedBrightness>::from(bright);
|
|
||||||
self.config.write();
|
|
||||||
self.set_brightness(self.config.brightness)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
if let Some(pwr) = AuraPowerConfig::to_tuf_bool_array(&self.config.enabled) {
|
|
||||||
let buf = [1, pwr[1] as u8, pwr[2] as u8, pwr[3] as u8, pwr[4] as u8];
|
|
||||||
platform.set_kbd_rgb_state(&buf)?;
|
|
||||||
}
|
|
||||||
} else if let LEDNode::Rog(hid_raw) = &self.led_node {
|
|
||||||
let bytes = AuraPowerConfig::to_bytes(&self.config.enabled);
|
|
||||||
let message = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3]];
|
|
||||||
|
|
||||||
hid_raw.write_bytes(&message)?;
|
|
||||||
hid_raw.write_bytes(&LED_SET)?;
|
|
||||||
// Changes won't persist unless apply is set
|
|
||||||
hid_raw.write_bytes(&LED_APPLY)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
pub(crate) fn set_effect(&mut self, effect: AuraEffect) -> Result<(), RogError> {
|
|
||||||
if !self.supported_modes.basic_modes.contains(&effect.mode)
|
|
||||||
|| effect.zone != AuraZone::None
|
|
||||||
&& !self.supported_modes.basic_zones.contains(&effect.zone)
|
|
||||||
{
|
|
||||||
return Err(RogError::AuraEffectNotSupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.write_mode(&effect)?;
|
|
||||||
self.config.read(); // refresh config if successful
|
|
||||||
self.config.set_builtin(effect);
|
|
||||||
if self.config.brightness == LedBrightness::Off {
|
|
||||||
self.config.brightness = LedBrightness::Med;
|
|
||||||
}
|
|
||||||
self.config.write();
|
|
||||||
self.set_brightness(self.config.brightness)?;
|
|
||||||
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])?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.flip_effect_write = !self.flip_effect_write;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
|
|
||||||
let current = self.config.current_mode;
|
|
||||||
if let Some(idx) = self
|
|
||||||
.supported_modes
|
|
||||||
.basic_modes
|
|
||||||
.iter()
|
|
||||||
.position(|v| *v == current)
|
|
||||||
{
|
|
||||||
let mut idx = idx;
|
|
||||||
// goes past end of array
|
|
||||||
if reverse {
|
|
||||||
if idx == 0 {
|
|
||||||
idx = self.supported_modes.basic_modes.len() - 1;
|
|
||||||
} else {
|
|
||||||
idx -= 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
idx += 1;
|
|
||||||
if idx == self.supported_modes.basic_modes.len() {
|
|
||||||
idx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let next = self.supported_modes.basic_modes[idx];
|
|
||||||
|
|
||||||
self.config.read();
|
|
||||||
// if self.config.builtins.contains_key(&next) {
|
|
||||||
self.config.current_mode = next;
|
|
||||||
self.write_current_config_mode()?;
|
|
||||||
// }
|
|
||||||
self.config.write();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_mode(&mut self, mode: &AuraEffect) -> Result<(), RogError> {
|
|
||||||
if let LEDNode::KbdLed(platform) = &self.led_node {
|
|
||||||
let buf = [
|
|
||||||
1,
|
|
||||||
mode.mode as u8,
|
|
||||||
mode.colour1.0,
|
|
||||||
mode.colour1.1,
|
|
||||||
mode.colour1.2,
|
|
||||||
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.create_multizone_default()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(multizones) = self.config.multizone.as_mut() {
|
|
||||||
if let Some(set) = multizones.get(&mode) {
|
|
||||||
for mode in set.clone() {
|
|
||||||
self.write_mode(&mode)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mode = self.config.current_mode;
|
|
||||||
if let Some(effect) = self.config.builtins.get(&mode).cloned() {
|
|
||||||
self.write_mode(&effect)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a default for the `current_mode` if multizone and no config
|
|
||||||
/// exists.
|
|
||||||
fn create_multizone_default(&mut self) -> Result<(), RogError> {
|
|
||||||
let mut default = vec![];
|
|
||||||
for (i, tmp) in self.supported_modes.basic_zones.iter().enumerate() {
|
|
||||||
default.push(AuraEffect {
|
|
||||||
mode: self.config.current_mode,
|
|
||||||
zone: *tmp,
|
|
||||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
|
||||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
|
||||||
speed: Speed::Med,
|
|
||||||
direction: Direction::Left,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if default.is_empty() {
|
|
||||||
return Err(RogError::AuraEffectNotSupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(multizones) = self.config.multizone.as_mut() {
|
|
||||||
multizones.insert(self.config.current_mode, default);
|
|
||||||
} else {
|
|
||||||
let mut tmp = BTreeMap::new();
|
|
||||||
tmp.insert(self.config.current_mode, default);
|
|
||||||
self.config.multizone = Some(tmp);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use rog_aura::aura_detection::LaptopLedData;
|
|
||||||
use rog_aura::usb::AuraDevice;
|
|
||||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
|
||||||
use rog_platform::keyboard_led::KeyboardLed;
|
|
||||||
|
|
||||||
use super::CtrlKbdLed;
|
|
||||||
use crate::ctrl_aura::config::AuraConfig;
|
|
||||||
use crate::ctrl_aura::controller::LEDNode;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
// #[ignore = "Must be manually run due to detection stage"]
|
|
||||||
fn check_set_mode_errors() {
|
|
||||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
|
||||||
let config = AuraConfig::create_default(AuraDevice::X19b6, &LaptopLedData::default());
|
|
||||||
let supported_modes = LaptopLedData {
|
|
||||||
board_name: String::new(),
|
|
||||||
layout_name: "ga401".to_owned(),
|
|
||||||
basic_modes: vec![AuraModeNum::Static],
|
|
||||||
basic_zones: vec![],
|
|
||||||
advanced_type: rog_aura::AdvancedAuraType::None,
|
|
||||||
};
|
|
||||||
let mut controller = CtrlKbdLed {
|
|
||||||
led_prod: AuraDevice::X19b6,
|
|
||||||
led_node: LEDNode::None,
|
|
||||||
kd_brightness: KeyboardLed::default(),
|
|
||||||
supported_modes,
|
|
||||||
flip_effect_write: false,
|
|
||||||
per_key_mode_active: false,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut effect = AuraEffect {
|
|
||||||
colour1: Colour(0xff, 0x00, 0xff),
|
|
||||||
zone: AuraZone::None,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// This error comes from write_bytes because we don't have a keyboard node
|
|
||||||
// stored
|
|
||||||
assert_eq!(
|
|
||||||
controller
|
|
||||||
.set_effect(effect.clone())
|
|
||||||
.unwrap_err()
|
|
||||||
.to_string(),
|
|
||||||
"No supported Aura keyboard"
|
|
||||||
);
|
|
||||||
|
|
||||||
effect.mode = AuraModeNum::Laser;
|
|
||||||
assert_eq!(
|
|
||||||
controller
|
|
||||||
.set_effect(effect.clone())
|
|
||||||
.unwrap_err()
|
|
||||||
.to_string(),
|
|
||||||
"Aura effect not supported"
|
|
||||||
);
|
|
||||||
|
|
||||||
effect.mode = AuraModeNum::Static;
|
|
||||||
effect.zone = AuraZone::Key2;
|
|
||||||
assert_eq!(
|
|
||||||
controller
|
|
||||||
.set_effect(effect.clone())
|
|
||||||
.unwrap_err()
|
|
||||||
.to_string(),
|
|
||||||
"Aura effect not supported"
|
|
||||||
);
|
|
||||||
|
|
||||||
controller.supported_modes.basic_zones.push(AuraZone::Key2);
|
|
||||||
assert_eq!(
|
|
||||||
controller.set_effect(effect).unwrap_err().to_string(),
|
|
||||||
"No supported Aura keyboard"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn create_multizone_if_no_config() {
|
|
||||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
|
||||||
let config = AuraConfig::create_default(AuraDevice::X19b6, &LaptopLedData::default());
|
|
||||||
let supported_modes = LaptopLedData {
|
|
||||||
board_name: String::new(),
|
|
||||||
layout_name: "ga401".to_owned(),
|
|
||||||
basic_modes: vec![AuraModeNum::Static],
|
|
||||||
basic_zones: vec![],
|
|
||||||
advanced_type: rog_aura::AdvancedAuraType::None,
|
|
||||||
};
|
|
||||||
let mut controller = CtrlKbdLed {
|
|
||||||
led_prod: AuraDevice::X19b6,
|
|
||||||
led_node: LEDNode::None,
|
|
||||||
kd_brightness: KeyboardLed::default(),
|
|
||||||
supported_modes,
|
|
||||||
flip_effect_write: false,
|
|
||||||
per_key_mode_active: false,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(controller.config.multizone.is_none());
|
|
||||||
assert!(controller.create_multizone_default().is_err());
|
|
||||||
assert!(controller.config.multizone.is_none());
|
|
||||||
|
|
||||||
controller.supported_modes.basic_zones.push(AuraZone::Key1);
|
|
||||||
controller.supported_modes.basic_zones.push(AuraZone::Key2);
|
|
||||||
assert!(controller.create_multizone_default().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]
|
|
||||||
fn next_mode_create_multizone_if_no_config() {
|
|
||||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
|
||||||
let config = AuraConfig::create_default(AuraDevice::X19b6, &LaptopLedData::default());
|
|
||||||
let supported_modes = LaptopLedData {
|
|
||||||
board_name: String::new(),
|
|
||||||
layout_name: "ga401".to_owned(),
|
|
||||||
basic_modes: vec![AuraModeNum::Static],
|
|
||||||
basic_zones: vec![AuraZone::Key1, AuraZone::Key2],
|
|
||||||
advanced_type: rog_aura::AdvancedAuraType::None,
|
|
||||||
};
|
|
||||||
let mut controller = CtrlKbdLed {
|
|
||||||
led_prod: AuraDevice::X19b6,
|
|
||||||
led_node: LEDNode::None,
|
|
||||||
kd_brightness: KeyboardLed::default(),
|
|
||||||
supported_modes,
|
|
||||||
flip_effect_write: false,
|
|
||||||
per_key_mode_active: false,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
pub mod config;
|
|
||||||
pub mod controller;
|
|
||||||
/// Implements `CtrlTask`, `Reloadable`, `ZbusRun`
|
|
||||||
pub mod trait_impls;
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use config_traits::StdConfig;
|
|
||||||
use log::{error, info, warn};
|
|
||||||
use rog_aura::advanced::UsbPackets;
|
|
||||||
use rog_aura::usb::AuraPowerDev;
|
|
||||||
use rog_aura::{AuraEffect, AuraModeNum, LedBrightness};
|
|
||||||
use zbus::export::futures_util::lock::{Mutex, MutexGuard};
|
|
||||||
use zbus::export::futures_util::StreamExt;
|
|
||||||
use zbus::{dbus_interface, Connection, SignalContext};
|
|
||||||
|
|
||||||
use super::controller::CtrlKbdLed;
|
|
||||||
use crate::error::RogError;
|
|
||||||
use crate::CtrlTask;
|
|
||||||
|
|
||||||
pub(super) const ZBUS_PATH: &str = "/org/asuslinux/Aura";
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CtrlKbdLedZbus(pub Arc<Mutex<CtrlKbdLed>>);
|
|
||||||
|
|
||||||
impl CtrlKbdLedZbus {
|
|
||||||
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
|
|
||||||
let bright = lock.kd_brightness.get_brightness()?;
|
|
||||||
lock.config.read();
|
|
||||||
lock.config.brightness = (bright as u32).into();
|
|
||||||
lock.config.write();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::ZbusRun for CtrlKbdLedZbus {
|
|
||||||
async fn add_to_server(self, server: &mut Connection) {
|
|
||||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main interface for changing, reading, or notfying signals
|
|
||||||
///
|
|
||||||
/// LED commands are split between Brightness, Modes, Per-Key
|
|
||||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
||||||
impl CtrlKbdLedZbus {
|
|
||||||
/// Set the keyboard brightness level (0-3)
|
|
||||||
async fn set_brightness(&mut self, brightness: LedBrightness) {
|
|
||||||
let ctrl = self.0.lock().await;
|
|
||||||
ctrl.set_brightness(brightness)
|
|
||||||
.map_err(|err| warn!("{}", err))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a variety of states, input is array of enum.
|
|
||||||
/// `enabled` sets if the sent array should be disabled or enabled
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// pub struct AuraPowerDev {
|
|
||||||
/// pub x1866: Vec<AuraDev1866>,
|
|
||||||
/// pub x19b6: Vec<AuraDev19b6>,
|
|
||||||
/// }
|
|
||||||
/// pub enum AuraDev1866 {
|
|
||||||
/// Awake,
|
|
||||||
/// Keyboard,
|
|
||||||
/// Lightbar,
|
|
||||||
/// Boot,
|
|
||||||
/// Sleep,
|
|
||||||
/// }
|
|
||||||
/// enum AuraDev19b6 {
|
|
||||||
/// BootLogo,
|
|
||||||
/// BootKeyb,
|
|
||||||
/// AwakeLogo,
|
|
||||||
/// AwakeKeyb,
|
|
||||||
/// SleepLogo,
|
|
||||||
/// SleepKeyb,
|
|
||||||
/// ShutdownLogo,
|
|
||||||
/// ShutdownKeyb,
|
|
||||||
/// AwakeBar,
|
|
||||||
/// BootBar,
|
|
||||||
/// SleepBar,
|
|
||||||
/// ShutdownBar,
|
|
||||||
/// BootRearBar,
|
|
||||||
/// AwakeRearBar,
|
|
||||||
/// SleepRearBar,
|
|
||||||
/// ShutdownRearBar,
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
async fn set_leds_power(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
options: AuraPowerDev,
|
|
||||||
enabled: bool,
|
|
||||||
) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
for p in options.tuf {
|
|
||||||
ctrl.config.enabled.set_tuf(p, enabled);
|
|
||||||
}
|
|
||||||
for p in options.x1866 {
|
|
||||||
ctrl.config.enabled.set_0x1866(p, enabled);
|
|
||||||
}
|
|
||||||
for p in options.x19b6 {
|
|
||||||
ctrl.config.enabled.set_0x19b6(p, enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl.config.write();
|
|
||||||
|
|
||||||
ctrl.set_power_states().map_err(|e| {
|
|
||||||
warn!("{}", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Self::notify_power_states(&ctxt, &AuraPowerDev::from(&ctrl.config.enabled))
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| warn!("{}", err));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_led_mode(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
effect: AuraEffect,
|
|
||||||
) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
|
|
||||||
ctrl.set_effect(effect).map_err(|e| {
|
|
||||||
warn!("{}", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
|
||||||
Self::notify_led(&ctxt, mode.clone())
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| warn!("{}", err));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn next_led_mode(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
|
|
||||||
ctrl.toggle_mode(false).map_err(|e| {
|
|
||||||
warn!("{}", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
|
||||||
Self::notify_led(&ctxt, mode.clone())
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| warn!("{}", err));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn prev_led_mode(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
|
|
||||||
ctrl.toggle_mode(true).map_err(|e| {
|
|
||||||
warn!("{}", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
|
||||||
Self::notify_led(&ctxt, mode.clone())
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| warn!("{}", err));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn next_led_brightness(&self) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.next_brightness().map_err(|e| {
|
|
||||||
warn!("{}", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn prev_led_brightness(&self) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.prev_brightness().map_err(|e| {
|
|
||||||
warn!("{}", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// As property doesn't work for AuraPowerDev (complexity of serialization?)
|
|
||||||
// #[dbus_interface(property)]
|
|
||||||
async fn leds_enabled(&self) -> AuraPowerDev {
|
|
||||||
let ctrl = self.0.lock().await;
|
|
||||||
AuraPowerDev::from(&ctrl.config.enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the current mode data
|
|
||||||
async fn led_mode(&self) -> AuraModeNum {
|
|
||||||
let ctrl = self.0.lock().await;
|
|
||||||
ctrl.config.current_mode
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a list of available modes
|
|
||||||
async fn led_modes(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
|
|
||||||
let ctrl = self.0.lock().await;
|
|
||||||
ctrl.config.builtins.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.write_effect_block(&data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the current LED brightness
|
|
||||||
#[dbus_interface(property)]
|
|
||||||
async fn led_brightness(&self) -> i8 {
|
|
||||||
let ctrl = self.0.lock().await;
|
|
||||||
ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_led(signal_ctxt: &SignalContext<'_>, data: AuraEffect) -> zbus::Result<()>;
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_power_states(
|
|
||||||
signal_ctxt: &SignalContext<'_>,
|
|
||||||
data: &AuraPowerDev,
|
|
||||||
) -> zbus::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl CtrlTask for CtrlKbdLedZbus {
|
|
||||||
fn zbus_path() -> &'static str {
|
|
||||||
ZBUS_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
|
||||||
let load_save = |start: bool, mut lock: MutexGuard<'_, CtrlKbdLed>| {
|
|
||||||
// If waking up
|
|
||||||
if !start {
|
|
||||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
|
||||||
lock.set_brightness(lock.config.brightness)
|
|
||||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
|
||||||
.ok();
|
|
||||||
lock.write_current_config_mode()
|
|
||||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
|
||||||
.ok();
|
|
||||||
} else if start {
|
|
||||||
info!("CtrlKbdLedTask saving last brightness");
|
|
||||||
Self::update_config(&mut lock)
|
|
||||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let inner1 = self.0.clone();
|
|
||||||
let inner2 = self.0.clone();
|
|
||||||
let inner3 = self.0.clone();
|
|
||||||
let inner4 = self.0.clone();
|
|
||||||
self.create_sys_event_tasks(
|
|
||||||
// Loop so that we do aquire the lock but also don't block other
|
|
||||||
// threads (prevents potential deadlocks)
|
|
||||||
move || {
|
|
||||||
let inner1 = inner1.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner1.lock().await;
|
|
||||||
load_save(true, lock);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || {
|
|
||||||
let inner2 = inner2.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner2.lock().await;
|
|
||||||
load_save(false, lock);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || {
|
|
||||||
let inner3 = inner3.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner3.lock().await;
|
|
||||||
load_save(false, lock);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || {
|
|
||||||
let inner4 = inner4.clone();
|
|
||||||
async move {
|
|
||||||
let lock = inner4.lock().await;
|
|
||||||
load_save(false, lock);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let ctrl2 = self.0.clone();
|
|
||||||
let ctrl = self.0.lock().await;
|
|
||||||
let watch = ctrl.kd_brightness.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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::Reloadable for CtrlKbdLedZbus {
|
|
||||||
async fn reload(&mut self) -> Result<(), RogError> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.write_current_config_mode()?;
|
|
||||||
ctrl.set_power_states().map_err(|err| warn!("{err}")).ok();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,382 +0,0 @@
|
|||||||
use std::fs::OpenOptions;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use config_traits::StdConfig;
|
|
||||||
use log::{info, warn};
|
|
||||||
use rog_platform::platform::{AsusPlatform, GpuMode};
|
|
||||||
use rog_platform::supported::RogBiosSupportedFunctions;
|
|
||||||
use zbus::export::futures_util::lock::Mutex;
|
|
||||||
use zbus::{dbus_interface, Connection, SignalContext};
|
|
||||||
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::error::RogError;
|
|
||||||
use crate::{task_watch_item, CtrlTask, GetSupported};
|
|
||||||
|
|
||||||
const ZBUS_PATH: &str = "/org/asuslinux/Platform";
|
|
||||||
const ASUS_POST_LOGO_SOUND: &str =
|
|
||||||
"/sys/firmware/efi/efivars/AsusPostLogoSound-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CtrlPlatform {
|
|
||||||
platform: AsusPlatform,
|
|
||||||
config: Arc<Mutex<Config>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetSupported for CtrlPlatform {
|
|
||||||
type A = RogBiosSupportedFunctions;
|
|
||||||
|
|
||||||
fn get_supported() -> Self::A {
|
|
||||||
let mut panel_overdrive = false;
|
|
||||||
let mut dgpu_disable = false;
|
|
||||||
let mut egpu_enable = false;
|
|
||||||
let mut gpu_mux = false;
|
|
||||||
|
|
||||||
if let Ok(platform) = AsusPlatform::new() {
|
|
||||||
panel_overdrive = platform.has_panel_od();
|
|
||||||
dgpu_disable = platform.has_dgpu_disable();
|
|
||||||
egpu_enable = platform.has_egpu_enable();
|
|
||||||
gpu_mux = platform.has_gpu_mux_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
RogBiosSupportedFunctions {
|
|
||||||
post_sound: Path::new(ASUS_POST_LOGO_SOUND).exists(),
|
|
||||||
gpu_mux,
|
|
||||||
panel_overdrive,
|
|
||||||
dgpu_disable,
|
|
||||||
egpu_enable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CtrlPlatform {
|
|
||||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
|
||||||
let platform = AsusPlatform::new()?;
|
|
||||||
|
|
||||||
if !platform.has_gpu_mux_mode() {
|
|
||||||
info!("G-Sync Switchable Graphics or GPU MUX not detected");
|
|
||||||
info!("Standard graphics switching will still work.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if Path::new(ASUS_POST_LOGO_SOUND).exists() {
|
|
||||||
CtrlPlatform::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
|
|
||||||
} else {
|
|
||||||
info!("Switch for POST boot sound not detected");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CtrlPlatform { platform, config })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_path_mutable(path: &str) -> Result<(), RogError> {
|
|
||||||
let output = Command::new("/usr/bin/chattr")
|
|
||||||
.arg("-i")
|
|
||||||
.arg(path)
|
|
||||||
.output()
|
|
||||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
|
||||||
info!("Set {} writeable: status: {}", path, output.status);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_gfx_mode(&self, mode: GpuMode) -> Result<(), RogError> {
|
|
||||||
self.platform.set_gpu_mux_mode(mode.to_mux_attr())?;
|
|
||||||
// self.update_initramfs(enable)?;
|
|
||||||
if mode == GpuMode::Discrete {
|
|
||||||
info!("Set system-level graphics mode: Dedicated Nvidia");
|
|
||||||
} else {
|
|
||||||
info!("Set system-level graphics mode: Optimus");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_boot_sound() -> Result<i8, RogError> {
|
|
||||||
let data = std::fs::read(ASUS_POST_LOGO_SOUND)
|
|
||||||
.map_err(|err| RogError::Read(ASUS_POST_LOGO_SOUND.into(), err))?;
|
|
||||||
|
|
||||||
let idx = data.len() - 1;
|
|
||||||
Ok(data[idx] as i8)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_boot_sound(on: bool) -> Result<(), RogError> {
|
|
||||||
let path = ASUS_POST_LOGO_SOUND;
|
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.open(path)
|
|
||||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
|
||||||
|
|
||||||
let mut data = Vec::new();
|
|
||||||
#[allow(clippy::verbose_file_reads)]
|
|
||||||
file.read_to_end(&mut data)
|
|
||||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
|
||||||
|
|
||||||
let idx = data.len() - 1;
|
|
||||||
if on {
|
|
||||||
data[idx] = 1;
|
|
||||||
info!("Set boot POST sound on");
|
|
||||||
} else {
|
|
||||||
data[idx] = 0;
|
|
||||||
info!("Set boot POST sound off");
|
|
||||||
}
|
|
||||||
file.write_all(&data)
|
|
||||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_panel_overdrive(&self, enable: bool) -> Result<(), RogError> {
|
|
||||||
self.platform.set_panel_od(enable).map_err(|err| {
|
|
||||||
warn!("CtrlRogBios: set_panel_overdrive {}", err);
|
|
||||||
err
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
||||||
impl CtrlPlatform {
|
|
||||||
async fn set_gpu_mux_mode(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
mode: GpuMode,
|
|
||||||
) {
|
|
||||||
self.set_gfx_mode(mode)
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlRogBios: set_gpu_mux_mode {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
Self::notify_gpu_mux_mode(&ctxt, mode).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gpu_mux_mode(&self) -> GpuMode {
|
|
||||||
match self.platform.get_gpu_mux_mode() {
|
|
||||||
Ok(m) => GpuMode::from_mux(m as u8),
|
|
||||||
Err(e) => {
|
|
||||||
warn!("CtrlRogBios: get_gfx_mode {}", e);
|
|
||||||
GpuMode::Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_gpu_mux_mode(
|
|
||||||
signal_ctxt: &SignalContext<'_>,
|
|
||||||
mode: GpuMode,
|
|
||||||
) -> zbus::Result<()> {
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_post_boot_sound(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
on: bool,
|
|
||||||
) {
|
|
||||||
Self::set_boot_sound(on)
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlRogBios: set_post_boot_sound {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
Self::notify_post_boot_sound(&ctxt, on).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn post_boot_sound(&self) -> i8 {
|
|
||||||
Self::get_boot_sound()
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlRogBios: get_boot_sound {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.unwrap_or(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_post_boot_sound(ctxt: &SignalContext<'_>, on: bool) -> zbus::Result<()> {}
|
|
||||||
|
|
||||||
async fn set_panel_od(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
overdrive: bool,
|
|
||||||
) {
|
|
||||||
match self.platform.set_panel_od(overdrive) {
|
|
||||||
Ok(_) => {
|
|
||||||
if let Some(mut lock) = self.config.try_lock() {
|
|
||||||
lock.panel_od = overdrive;
|
|
||||||
lock.write();
|
|
||||||
}
|
|
||||||
Self::notify_panel_od(&ctxt, overdrive).await.ok();
|
|
||||||
}
|
|
||||||
Err(err) => warn!("CtrlRogBios: set_panel_overdrive {}", err),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the `panel_od` value from platform. Updates the stored value in
|
|
||||||
/// internal config also.
|
|
||||||
fn panel_od(&self) -> bool {
|
|
||||||
let od = self
|
|
||||||
.platform
|
|
||||||
.get_panel_od()
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlRogBios: get_panel_od {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.unwrap_or(false);
|
|
||||||
if let Some(mut lock) = self.config.try_lock() {
|
|
||||||
lock.panel_od = od;
|
|
||||||
lock.write();
|
|
||||||
}
|
|
||||||
od
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_panel_od(signal_ctxt: &SignalContext<'_>, overdrive: bool) -> zbus::Result<()> {
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_dgpu_disable(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
disable: bool,
|
|
||||||
) {
|
|
||||||
match self.platform.set_dgpu_disable(disable) {
|
|
||||||
Ok(_) => {
|
|
||||||
Self::notify_dgpu_disable(&ctxt, disable).await.ok();
|
|
||||||
}
|
|
||||||
Err(err) => warn!("CtrlRogBios: set_dgpu_disable {}", err),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dgpu_disable(&self) -> bool {
|
|
||||||
self.platform
|
|
||||||
.get_dgpu_disable()
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlRogBios: get_dgpu_disable {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_dgpu_disable(
|
|
||||||
signal_ctxt: &SignalContext<'_>,
|
|
||||||
disable: bool,
|
|
||||||
) -> zbus::Result<()> {
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_egpu_enable(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
enable: bool,
|
|
||||||
) {
|
|
||||||
match self.platform.set_egpu_enable(enable) {
|
|
||||||
Ok(_) => {
|
|
||||||
Self::notify_egpu_enable(&ctxt, enable).await.ok();
|
|
||||||
}
|
|
||||||
Err(err) => warn!("CtrlRogBios: set_egpu_enable {}", err),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn egpu_enable(&self) -> bool {
|
|
||||||
self.platform
|
|
||||||
.get_egpu_enable()
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlRogBios: get_egpu_enable {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_egpu_enable(signal_ctxt: &SignalContext<'_>, enable: bool) -> zbus::Result<()> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::ZbusRun for CtrlPlatform {
|
|
||||||
async fn add_to_server(self, server: &mut Connection) {
|
|
||||||
Self::add_to_server_helper(self, "/org/asuslinux/Platform", server).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::Reloadable for CtrlPlatform {
|
|
||||||
async fn reload(&mut self) -> Result<(), RogError> {
|
|
||||||
if self.platform.has_panel_od() {
|
|
||||||
let p = if let Some(lock) = self.config.try_lock() {
|
|
||||||
lock.panel_od
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
self.set_panel_overdrive(p)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CtrlPlatform {
|
|
||||||
task_watch_item!(panel_od platform);
|
|
||||||
|
|
||||||
task_watch_item!(dgpu_disable platform);
|
|
||||||
|
|
||||||
task_watch_item!(egpu_enable platform);
|
|
||||||
// NOTE: see note further below
|
|
||||||
// task_watch_item!(gpu_mux_mode platform);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl CtrlTask for CtrlPlatform {
|
|
||||||
fn zbus_path() -> &'static str {
|
|
||||||
ZBUS_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
|
||||||
let platform1 = self.clone();
|
|
||||||
let platform2 = self.clone();
|
|
||||||
self.create_sys_event_tasks(
|
|
||||||
move || async { {} },
|
|
||||||
move || {
|
|
||||||
let platform1 = platform1.clone();
|
|
||||||
async move {
|
|
||||||
info!("CtrlRogBios reloading panel_od");
|
|
||||||
let lock = platform1.config.lock().await;
|
|
||||||
if platform1.platform.has_panel_od() {
|
|
||||||
platform1
|
|
||||||
.set_panel_overdrive(lock.panel_od)
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlCharge: set_limit {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || async { {} },
|
|
||||||
move || {
|
|
||||||
let platform2 = platform2.clone();
|
|
||||||
async move {
|
|
||||||
info!("CtrlRogBios reloading panel_od");
|
|
||||||
let lock = platform2.config.lock().await;
|
|
||||||
if platform2.platform.has_panel_od() {
|
|
||||||
platform2
|
|
||||||
.set_panel_overdrive(lock.panel_od)
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlCharge: set_limit {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self.watch_panel_od(signal_ctxt.clone()).await?;
|
|
||||||
self.watch_dgpu_disable(signal_ctxt.clone()).await?;
|
|
||||||
self.watch_egpu_enable(signal_ctxt.clone()).await?;
|
|
||||||
// NOTE: Can't have this as a watch because on a write to it, it reverts back to
|
|
||||||
// booted-with value as it does not actually change until reboot.
|
|
||||||
// self.watch_gpu_mux_mode(signal_ctxt.clone()).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
use std::process::Command;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use config_traits::StdConfig;
|
|
||||||
use log::{error, info, warn};
|
|
||||||
use rog_platform::power::AsusPower;
|
|
||||||
use rog_platform::supported::ChargeSupportedFunctions;
|
|
||||||
use systemd_zbus::{ManagerProxy as SystemdProxy, Mode, UnitFileState};
|
|
||||||
use tokio::time::sleep;
|
|
||||||
use zbus::export::futures_util::lock::Mutex;
|
|
||||||
use zbus::{dbus_interface, Connection, SignalContext};
|
|
||||||
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::error::RogError;
|
|
||||||
use crate::{task_watch_item, CtrlTask, GetSupported};
|
|
||||||
|
|
||||||
const ZBUS_PATH: &str = "/org/asuslinux/Power";
|
|
||||||
const NVIDIA_POWERD: &str = "nvidia-powerd.service";
|
|
||||||
|
|
||||||
impl GetSupported for CtrlPower {
|
|
||||||
type A = ChargeSupportedFunctions;
|
|
||||||
|
|
||||||
fn get_supported() -> Self::A {
|
|
||||||
ChargeSupportedFunctions {
|
|
||||||
charge_level_set: if let Ok(power) = AsusPower::new() {
|
|
||||||
power.has_charge_control_end_threshold()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CtrlPower {
|
|
||||||
power: AsusPower,
|
|
||||||
config: Arc<Mutex<Config>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
||||||
impl CtrlPower {
|
|
||||||
async fn set_charge_control_end_threshold(
|
|
||||||
&mut self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
limit: u8,
|
|
||||||
) -> zbus::fdo::Result<()> {
|
|
||||||
if !(20..=100).contains(&limit) {
|
|
||||||
return Err(RogError::ChargeLimit(limit))?;
|
|
||||||
}
|
|
||||||
self.set(limit)
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlCharge: set_limit {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
Self::notify_charge_control_end_threshold(&ctxt, limit)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn charge_control_end_threshold(&self) -> u8 {
|
|
||||||
loop {
|
|
||||||
if let Some(mut config) = self.config.try_lock() {
|
|
||||||
let limit = self
|
|
||||||
.power
|
|
||||||
.get_charge_control_end_threshold()
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlCharge: get_charge_control_end_threshold {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.unwrap_or(100);
|
|
||||||
|
|
||||||
config.read();
|
|
||||||
config.bat_charge_limit = limit;
|
|
||||||
config.write();
|
|
||||||
|
|
||||||
return config.bat_charge_limit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mains_online(&self) -> bool {
|
|
||||||
if self.power.has_online() {
|
|
||||||
if let Ok(v) = self.power.get_online() {
|
|
||||||
return v == 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_charge_control_end_threshold(
|
|
||||||
ctxt: &SignalContext<'_>,
|
|
||||||
limit: u8,
|
|
||||||
) -> zbus::Result<()>;
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_mains_online(ctxt: &SignalContext<'_>, on: bool) -> zbus::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::ZbusRun for CtrlPower {
|
|
||||||
async fn add_to_server(self, server: &mut Connection) {
|
|
||||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::Reloadable for CtrlPower {
|
|
||||||
async fn reload(&mut self) -> Result<(), RogError> {
|
|
||||||
if let Some(mut config) = self.config.try_lock() {
|
|
||||||
config.read();
|
|
||||||
self.set(config.bat_charge_limit)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CtrlPower {
|
|
||||||
task_watch_item!(charge_control_end_threshold power);
|
|
||||||
|
|
||||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
|
||||||
Ok(CtrlPower {
|
|
||||||
power: AsusPower::new()?,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set(&self, limit: u8) -> Result<(), RogError> {
|
|
||||||
if !(20..=100).contains(&limit) {
|
|
||||||
return Err(RogError::ChargeLimit(limit));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.power.set_charge_control_end_threshold(limit)?;
|
|
||||||
|
|
||||||
info!("Battery charge limit: {}", limit);
|
|
||||||
|
|
||||||
if let Some(mut config) = self.config.try_lock() {
|
|
||||||
config.read();
|
|
||||||
config.bat_charge_limit = limit;
|
|
||||||
config.write();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl CtrlTask for CtrlPower {
|
|
||||||
fn zbus_path() -> &'static str {
|
|
||||||
ZBUS_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
|
||||||
let conn = zbus::Connection::system().await?;
|
|
||||||
let sysd1 = SystemdProxy::new(&conn).await?;
|
|
||||||
let sysd2 = sysd1.clone();
|
|
||||||
let sysd3 = sysd1.clone();
|
|
||||||
|
|
||||||
let power1 = self.clone();
|
|
||||||
let power2 = self.clone();
|
|
||||||
self.create_sys_event_tasks(
|
|
||||||
move || async {},
|
|
||||||
move || {
|
|
||||||
let power = power1.clone();
|
|
||||||
let sysd = sysd1.clone();
|
|
||||||
async move {
|
|
||||||
info!("CtrlCharge reloading charge limit");
|
|
||||||
let lock = power.config.lock().await;
|
|
||||||
power
|
|
||||||
.set(lock.bat_charge_limit)
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlCharge: set_limit {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if lock.disable_nvidia_powerd_on_battery {
|
|
||||||
if let Ok(value) = power.power.get_online() {
|
|
||||||
do_nvidia_powerd_action(&sysd, value == 1).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move || async {},
|
|
||||||
move || {
|
|
||||||
let power = power2.clone();
|
|
||||||
let sysd = sysd2.clone();
|
|
||||||
async move {
|
|
||||||
info!("CtrlCharge reloading charge limit");
|
|
||||||
let lock = power.config.lock().await;
|
|
||||||
power
|
|
||||||
.set(lock.bat_charge_limit)
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("CtrlCharge: set_limit {}", err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if lock.disable_nvidia_powerd_on_battery {
|
|
||||||
if let Ok(value) = power.power.get_online() {
|
|
||||||
do_nvidia_powerd_action(&sysd, value == 1).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let config = self.config.clone();
|
|
||||||
self.watch_charge_control_end_threshold(signal_ctxt.clone())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let ctrl = self.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut online = 10;
|
|
||||||
loop {
|
|
||||||
if let Ok(value) = ctrl.power.get_online() {
|
|
||||||
if online != value {
|
|
||||||
online = value;
|
|
||||||
let mut config = config.lock().await;
|
|
||||||
config.read();
|
|
||||||
|
|
||||||
if config.disable_nvidia_powerd_on_battery {
|
|
||||||
do_nvidia_powerd_action(&sysd3, value == 1).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::notify_mains_online(&signal_ctxt, value == 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut prog: Vec<&str> = Vec::new();
|
|
||||||
if value == 1 {
|
|
||||||
// AC ONLINE
|
|
||||||
prog = config.ac_command.split_whitespace().collect();
|
|
||||||
} else if value == 0 {
|
|
||||||
// BATTERY
|
|
||||||
prog = config.bat_command.split_whitespace().collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if prog.len() > 1 {
|
|
||||||
let mut cmd = Command::new(prog[0]);
|
|
||||||
for arg in prog.iter().skip(1) {
|
|
||||||
cmd.arg(*arg);
|
|
||||||
}
|
|
||||||
if let Err(e) = cmd.spawn() {
|
|
||||||
if value == 1 {
|
|
||||||
error!("AC power command error: {e}");
|
|
||||||
} else {
|
|
||||||
error!("Battery power command error: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The inotify doesn't pick up events when the kernel changes internal value
|
|
||||||
// so we need to watch it with a thread and sleep unfortunately
|
|
||||||
sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn do_nvidia_powerd_action(proxy: &SystemdProxy<'_>, ac_on: bool) {
|
|
||||||
if let Ok(res) = proxy.get_unit_file_state(NVIDIA_POWERD).await {
|
|
||||||
if res == UnitFileState::Enabled {
|
|
||||||
if ac_on {
|
|
||||||
proxy
|
|
||||||
.start_unit(NVIDIA_POWERD, Mode::Replace)
|
|
||||||
.await
|
|
||||||
.map_err(|e| error!("Error stopping {NVIDIA_POWERD}, {e:?}"))
|
|
||||||
.ok();
|
|
||||||
} else {
|
|
||||||
proxy
|
|
||||||
.stop_unit(NVIDIA_POWERD, Mode::Replace)
|
|
||||||
.await
|
|
||||||
.map_err(|e| error!("Error stopping {NVIDIA_POWERD}, {e:?}"))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use config_traits::{StdConfig, StdConfigLoad};
|
|
||||||
use rog_profiles::fan_curve_set::FanCurveSet;
|
|
||||||
use rog_profiles::Profile;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::CONFIG_PATH_BASE;
|
|
||||||
|
|
||||||
const CONFIG_FILE: &str = "profile.ron";
|
|
||||||
const CONFIG_FAN_FILE: &str = "fan_curves.ron";
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct ProfileConfig {
|
|
||||||
/// For restore on boot
|
|
||||||
pub active_profile: Profile,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for ProfileConfig {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
active_profile: Profile::Balanced,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
PathBuf::from(CONFIG_PATH_BASE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
CONFIG_FILE.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad for ProfileConfig {}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
|
||||||
pub struct FanCurveConfig {
|
|
||||||
pub balanced: FanCurveSet,
|
|
||||||
pub performance: FanCurveSet,
|
|
||||||
pub quiet: FanCurveSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfig for FanCurveConfig {
|
|
||||||
/// Create a new config. The defaults are zeroed so the device must be read
|
|
||||||
/// to get the actual device defaults.
|
|
||||||
fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_dir() -> std::path::PathBuf {
|
|
||||||
PathBuf::from(CONFIG_PATH_BASE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
CONFIG_FAN_FILE.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdConfigLoad for FanCurveConfig {}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
use config_traits::{StdConfig, StdConfigLoad};
|
|
||||||
use log::{info, warn};
|
|
||||||
use rog_platform::platform::AsusPlatform;
|
|
||||||
use rog_platform::supported::PlatformProfileFunctions;
|
|
||||||
use rog_profiles::error::ProfileError;
|
|
||||||
use rog_profiles::{FanCurveProfiles, Profile};
|
|
||||||
|
|
||||||
use super::config::{FanCurveConfig, ProfileConfig};
|
|
||||||
use crate::error::RogError;
|
|
||||||
use crate::GetSupported;
|
|
||||||
|
|
||||||
// TODO: macro wrapper for warn/info/error log macros to add module name
|
|
||||||
const MOD_NAME: &str = "CtrlPlatformProfile";
|
|
||||||
|
|
||||||
pub struct FanCurves {
|
|
||||||
config_file: FanCurveConfig,
|
|
||||||
profiles: FanCurveProfiles,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FanCurves {
|
|
||||||
pub fn update_profiles_from_config(&mut self) {
|
|
||||||
self.profiles.balanced = self.config_file.balanced.clone();
|
|
||||||
self.profiles.performance = self.config_file.performance.clone();
|
|
||||||
self.profiles.quiet = self.config_file.quiet.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_config_from_profiles(&mut self) {
|
|
||||||
self.config_file.balanced = self.profiles.balanced.clone();
|
|
||||||
self.config_file.performance = self.profiles.performance.clone();
|
|
||||||
self.config_file.quiet = self.profiles.quiet.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn profiles(&self) -> &FanCurveProfiles {
|
|
||||||
&self.profiles
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn profiles_mut(&mut self) -> &mut FanCurveProfiles {
|
|
||||||
&mut self.profiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CtrlPlatformProfile {
|
|
||||||
pub profile_config: ProfileConfig,
|
|
||||||
pub fan_curves: Option<FanCurves>,
|
|
||||||
pub platform: AsusPlatform,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetSupported for CtrlPlatformProfile {
|
|
||||||
type A = PlatformProfileFunctions;
|
|
||||||
|
|
||||||
fn get_supported() -> Self::A {
|
|
||||||
if !Profile::is_platform_profile_supported() {
|
|
||||||
warn!(
|
|
||||||
"platform_profile kernel interface not found, your laptop does not support this, \
|
|
||||||
or the interface is missing."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = FanCurveProfiles::is_supported();
|
|
||||||
let mut fan_curve_supported = res.is_err();
|
|
||||||
if let Ok(r) = res {
|
|
||||||
fan_curve_supported = r;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !fan_curve_supported {
|
|
||||||
info!(
|
|
||||||
"fan curves kernel interface not found, your laptop does not support this, or the \
|
|
||||||
interface is missing."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformProfileFunctions {
|
|
||||||
platform_profile: Profile::is_platform_profile_supported(),
|
|
||||||
fan_curves: fan_curve_supported,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CtrlPlatformProfile {
|
|
||||||
pub fn new(config: ProfileConfig) -> Result<Self, RogError> {
|
|
||||||
let platform = AsusPlatform::new()?;
|
|
||||||
if platform.has_platform_profile() || platform.has_throttle_thermal_policy() {
|
|
||||||
info!("{MOD_NAME}: Device has profile control available");
|
|
||||||
|
|
||||||
let mut controller = CtrlPlatformProfile {
|
|
||||||
profile_config: config,
|
|
||||||
fan_curves: None,
|
|
||||||
platform,
|
|
||||||
};
|
|
||||||
if FanCurveProfiles::get_device().is_ok() {
|
|
||||||
info!("{MOD_NAME}: Device has fan curves available");
|
|
||||||
let fan_config = FanCurveConfig::new();
|
|
||||||
// Only do defaults if the config doesn't already exist
|
|
||||||
if !fan_config.file_path().exists() {
|
|
||||||
info!("{MOD_NAME}: Fetching default fan curves");
|
|
||||||
controller.fan_curves = Some(FanCurves {
|
|
||||||
config_file: fan_config,
|
|
||||||
profiles: FanCurveProfiles::default(),
|
|
||||||
});
|
|
||||||
for _ in [Profile::Balanced, Profile::Performance, Profile::Quiet] {
|
|
||||||
// For each profile we need to switch to it before we
|
|
||||||
// can read the existing values from hardware. The ACPI method used
|
|
||||||
// for this is what limits us.
|
|
||||||
controller.set_next_profile()?;
|
|
||||||
// Make sure to set the baseline to default
|
|
||||||
controller.set_active_curve_to_defaults()?;
|
|
||||||
let active = Profile::get_active_profile().unwrap_or(Profile::Balanced);
|
|
||||||
|
|
||||||
if let Some(curves) = controller.fan_curves.as_ref() {
|
|
||||||
info!(
|
|
||||||
"{MOD_NAME}: {active:?}: {}",
|
|
||||||
String::from(curves.profiles().get_fan_curves_for(active))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(curves) = controller.fan_curves.as_ref() {
|
|
||||||
curves.config_file.write();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info!("{MOD_NAME}: Fan curves previously stored, loading...");
|
|
||||||
let mut fan_curves = FanCurves {
|
|
||||||
config_file: fan_config.load(),
|
|
||||||
profiles: FanCurveProfiles::default(),
|
|
||||||
};
|
|
||||||
fan_curves.update_profiles_from_config();
|
|
||||||
controller.fan_curves = Some(fan_curves);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ProfileError::NotSupported.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_config(&mut self) {
|
|
||||||
self.profile_config.write();
|
|
||||||
if let Some(fans) = self.fan_curves.as_mut() {
|
|
||||||
fans.update_config_from_profiles();
|
|
||||||
fans.config_file.write(); // config write
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle to next profile in list. This will first read the config, switch,
|
|
||||||
/// then write out
|
|
||||||
pub(super) fn set_next_profile(&mut self) -> Result<(), RogError> {
|
|
||||||
// Read first just incase the user has modified the config before calling this
|
|
||||||
match self.profile_config.active_profile {
|
|
||||||
Profile::Balanced => {
|
|
||||||
Profile::set_profile(Profile::Performance)?;
|
|
||||||
self.profile_config.active_profile = Profile::Performance;
|
|
||||||
}
|
|
||||||
Profile::Performance => {
|
|
||||||
Profile::set_profile(Profile::Quiet)?;
|
|
||||||
self.profile_config.active_profile = Profile::Quiet;
|
|
||||||
}
|
|
||||||
Profile::Quiet => {
|
|
||||||
Profile::set_profile(Profile::Balanced)?;
|
|
||||||
self.profile_config.active_profile = Profile::Balanced;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.write_profile_curve_to_platform()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the curve for the active profile active
|
|
||||||
pub(super) fn write_profile_curve_to_platform(&mut self) -> Result<(), RogError> {
|
|
||||||
if let Some(curves) = &mut self.fan_curves {
|
|
||||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
|
||||||
curves.profiles_mut().write_profile_curve_to_platform(
|
|
||||||
self.profile_config.active_profile,
|
|
||||||
&mut device,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_active_curve_to_defaults(&mut self) -> Result<(), RogError> {
|
|
||||||
if let Some(curves) = self.fan_curves.as_mut() {
|
|
||||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
|
||||||
curves.profiles_mut().set_active_curve_to_defaults(
|
|
||||||
self.profile_config.active_profile,
|
|
||||||
&mut device,
|
|
||||||
)?;
|
|
||||||
curves.update_config_from_profiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
pub mod config;
|
|
||||||
pub mod controller;
|
|
||||||
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
|
|
||||||
pub mod trait_impls;
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use config_traits::StdConfig;
|
|
||||||
use log::{error, info, warn};
|
|
||||||
use rog_profiles::fan_curve_set::{CurveData, FanCurveSet};
|
|
||||||
use rog_profiles::{FanCurveProfiles, Profile};
|
|
||||||
use zbus::export::futures_util::lock::Mutex;
|
|
||||||
use zbus::export::futures_util::StreamExt;
|
|
||||||
use zbus::fdo::Error;
|
|
||||||
use zbus::{dbus_interface, Connection, SignalContext};
|
|
||||||
|
|
||||||
use super::controller::CtrlPlatformProfile;
|
|
||||||
use crate::error::RogError;
|
|
||||||
use crate::CtrlTask;
|
|
||||||
|
|
||||||
const MOD_NAME: &str = "ProfileZbus";
|
|
||||||
|
|
||||||
const ZBUS_PATH: &str = "/org/asuslinux/Profile";
|
|
||||||
const UNSUPPORTED_MSG: &str =
|
|
||||||
"Fan curves are not supported on this laptop or you require a patched kernel";
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ProfileZbus(pub Arc<Mutex<CtrlPlatformProfile>>);
|
|
||||||
|
|
||||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
||||||
impl ProfileZbus {
|
|
||||||
/// Fetch profile names
|
|
||||||
fn profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
|
||||||
if let Ok(profiles) = Profile::get_profile_names() {
|
|
||||||
return Ok(profiles);
|
|
||||||
}
|
|
||||||
Err(Error::Failed(
|
|
||||||
"Failed to get all profile details".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle to next platform_profile. Names provided by `Profiles`.
|
|
||||||
/// If fan-curves are supported will also activate a fan curve for profile.
|
|
||||||
async fn next_profile(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>) {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.set_next_profile()
|
|
||||||
.unwrap_or_else(|err| warn!("{MOD_NAME}: {}", err));
|
|
||||||
ctrl.save_config();
|
|
||||||
|
|
||||||
Self::notify_profile(&ctxt, ctrl.profile_config.active_profile)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch the active profile name
|
|
||||||
async fn active_profile(&mut self) -> zbus::fdo::Result<Profile> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
Ok(ctrl.profile_config.active_profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set this platform_profile name as active
|
|
||||||
async fn set_active_profile(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
|
||||||
profile: Profile,
|
|
||||||
) {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
// Read first just incase the user has modified the config before calling this
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
Profile::set_profile(profile)
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: set_profile, {}", e))
|
|
||||||
.ok();
|
|
||||||
ctrl.profile_config.active_profile = profile;
|
|
||||||
ctrl.write_profile_curve_to_platform()
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: write_profile_curve_to_platform, {}", e))
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
ctrl.save_config();
|
|
||||||
|
|
||||||
Self::notify_profile(&ctxt, ctrl.profile_config.active_profile)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of profiles that have fan-curves enabled.
|
|
||||||
async fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
if let Some(curves) = &mut ctrl.fan_curves {
|
|
||||||
return Ok(curves.profiles().get_enabled_curve_profiles());
|
|
||||||
}
|
|
||||||
Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a profile fan curve enabled status. Will also activate a fan curve
|
|
||||||
/// if in the same profile mode
|
|
||||||
async fn set_fan_curve_enabled(
|
|
||||||
&mut self,
|
|
||||||
profile: Profile,
|
|
||||||
enabled: bool,
|
|
||||||
) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
if let Some(curves) = &mut ctrl.fan_curves {
|
|
||||||
curves
|
|
||||||
.profiles_mut()
|
|
||||||
.set_profile_curve_enabled(profile, enabled);
|
|
||||||
|
|
||||||
ctrl.write_profile_curve_to_platform()
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: write_profile_curve_to_platform, {}", e))
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
ctrl.save_config();
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the fan-curve data for the currently active Profile
|
|
||||||
async fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<FanCurveSet> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
if let Some(curves) = &mut ctrl.fan_curves {
|
|
||||||
let curve = curves.profiles().get_fan_curves_for(profile);
|
|
||||||
return Ok(curve.clone());
|
|
||||||
}
|
|
||||||
Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the fan curve for the specified profile.
|
|
||||||
/// Will also activate the fan curve if the user is in the same mode.
|
|
||||||
async fn set_fan_curve(&self, profile: Profile, curve: CurveData) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
if let Some(curves) = &mut ctrl.fan_curves {
|
|
||||||
curves
|
|
||||||
.profiles_mut()
|
|
||||||
.save_fan_curve(curve, profile)
|
|
||||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
|
||||||
} else {
|
|
||||||
return Err(Error::Failed(UNSUPPORTED_MSG.to_owned()));
|
|
||||||
}
|
|
||||||
ctrl.write_profile_curve_to_platform()
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: Profile::set_profile, {}", e))
|
|
||||||
.ok();
|
|
||||||
ctrl.save_config();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the stored (self) and device curve to the defaults of the
|
|
||||||
/// platform.
|
|
||||||
///
|
|
||||||
/// Each platform_profile has a different default and the defualt can be
|
|
||||||
/// read only for the currently active profile.
|
|
||||||
async fn set_active_curve_to_defaults(&self) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
ctrl.set_active_curve_to_defaults()
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: Profile::set_active_curve_to_defaults, {}", e))
|
|
||||||
.ok();
|
|
||||||
ctrl.save_config();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the stored (self) and device curve to the defaults of the
|
|
||||||
/// platform.
|
|
||||||
///
|
|
||||||
/// Each platform_profile has a different default and the defualt can be
|
|
||||||
/// read only for the currently active profile.
|
|
||||||
async fn reset_profile_curves(&self, profile: Profile) -> zbus::fdo::Result<()> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
ctrl.profile_config.read();
|
|
||||||
let active = Profile::get_active_profile().unwrap_or(Profile::Balanced);
|
|
||||||
|
|
||||||
Profile::set_profile(profile)
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: set_profile, {}", e))
|
|
||||||
.ok();
|
|
||||||
ctrl.set_active_curve_to_defaults()
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: Profile::set_active_curve_to_defaults, {}", e))
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Profile::set_profile(active)
|
|
||||||
.map_err(|e| warn!("{MOD_NAME}: set_profile, {}", e))
|
|
||||||
.ok();
|
|
||||||
ctrl.save_config();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
|
||||||
async fn notify_profile(signal_ctxt: &SignalContext<'_>, profile: Profile) -> zbus::Result<()> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::ZbusRun for ProfileZbus {
|
|
||||||
async fn add_to_server(self, server: &mut Connection) {
|
|
||||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl CtrlTask for ProfileZbus {
|
|
||||||
fn zbus_path() -> &'static str {
|
|
||||||
ZBUS_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
|
||||||
let ctrl = self.0.clone();
|
|
||||||
let sig_ctx = signal_ctxt.clone();
|
|
||||||
let watch = self
|
|
||||||
.0
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.platform
|
|
||||||
.monitor_throttle_thermal_policy()?;
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut buffer = [0; 32];
|
|
||||||
if let Ok(stream) = watch.into_event_stream(&mut buffer) {
|
|
||||||
stream
|
|
||||||
.for_each(|_| async {
|
|
||||||
let mut lock = ctrl.lock().await;
|
|
||||||
if let Ok(profile) =
|
|
||||||
lock.platform.get_throttle_thermal_policy().map_err(|e| {
|
|
||||||
error!("{MOD_NAME}: get_throttle_thermal_policy error: {e}");
|
|
||||||
})
|
|
||||||
{
|
|
||||||
let new_profile = Profile::from_throttle_thermal_policy(profile);
|
|
||||||
if new_profile != lock.profile_config.active_profile {
|
|
||||||
info!("{MOD_NAME}: platform_profile changed to {new_profile}");
|
|
||||||
lock.profile_config.active_profile = new_profile;
|
|
||||||
lock.write_profile_curve_to_platform().unwrap();
|
|
||||||
lock.save_config();
|
|
||||||
Profile::set_profile(lock.profile_config.active_profile)
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Profile::set_profile() error: {e}");
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
Self::notify_profile(&sig_ctx, lock.profile_config.active_profile)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let ctrl = self.0.clone();
|
|
||||||
let watch = self.0.lock().await.platform.monitor_platform_profile()?;
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut buffer = [0; 32];
|
|
||||||
if let Ok(stream) = watch.into_event_stream(&mut buffer) {
|
|
||||||
stream
|
|
||||||
.for_each(|_| async {
|
|
||||||
let mut lock = ctrl.lock().await;
|
|
||||||
if let Ok(profile) = lock.platform.get_platform_profile().map_err(|e| {
|
|
||||||
error!("get_platform_profile error: {e}");
|
|
||||||
}) {
|
|
||||||
if let Ok(new_profile) = Profile::from_str(&profile).map_err(|e| {
|
|
||||||
error!("Profile::from_str(&profile) error: {e}");
|
|
||||||
}) {
|
|
||||||
if new_profile != lock.profile_config.active_profile {
|
|
||||||
info!("{MOD_NAME}: platform_profile changed to {new_profile}");
|
|
||||||
lock.profile_config.active_profile = new_profile;
|
|
||||||
lock.write_profile_curve_to_platform().unwrap();
|
|
||||||
lock.save_config();
|
|
||||||
Profile::set_profile(lock.profile_config.active_profile)
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Profile::set_profile() error: {e}");
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
Self::notify_profile(
|
|
||||||
&signal_ctxt,
|
|
||||||
lock.profile_config.active_profile,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::Reloadable for ProfileZbus {
|
|
||||||
/// Fetch the active profile and use that to set all related components up
|
|
||||||
async fn reload(&mut self) -> Result<(), RogError> {
|
|
||||||
let mut ctrl = self.0.lock().await;
|
|
||||||
let active = ctrl.profile_config.active_profile;
|
|
||||||
if let Some(curves) = &mut ctrl.fan_curves {
|
|
||||||
if let Ok(mut device) = FanCurveProfiles::get_device() {
|
|
||||||
// There is a possibility that the curve was default zeroed, so this call
|
|
||||||
// initialises the data from system read and we need to save it
|
|
||||||
// after
|
|
||||||
curves
|
|
||||||
.profiles_mut()
|
|
||||||
.write_profile_curve_to_platform(active, &mut device)?;
|
|
||||||
ctrl.profile_config.write();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
use async_trait::async_trait;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use zbus::zvariant::Type;
|
|
||||||
use zbus::{dbus_interface, Connection};
|
|
||||||
|
|
||||||
use crate::ctrl_anime::CtrlAnime;
|
|
||||||
use crate::ctrl_aura::controller::CtrlKbdLed;
|
|
||||||
use crate::ctrl_platform::CtrlPlatform;
|
|
||||||
use crate::ctrl_power::CtrlPower;
|
|
||||||
use crate::ctrl_profiles::controller::CtrlPlatformProfile;
|
|
||||||
use crate::GetSupported;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Type)]
|
|
||||||
pub struct SupportedFunctions(rog_platform::supported::SupportedFunctions);
|
|
||||||
|
|
||||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
||||||
impl SupportedFunctions {
|
|
||||||
pub fn supported_functions(&self) -> &rog_platform::supported::SupportedFunctions {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl crate::ZbusRun for SupportedFunctions {
|
|
||||||
async fn add_to_server(self, server: &mut Connection) {
|
|
||||||
Self::add_to_server_helper(self, "/org/asuslinux/Supported", server).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetSupported for SupportedFunctions {
|
|
||||||
type A = SupportedFunctions;
|
|
||||||
|
|
||||||
fn get_supported() -> Self::A {
|
|
||||||
Self(rog_platform::supported::SupportedFunctions {
|
|
||||||
anime_ctrl: CtrlAnime::get_supported(),
|
|
||||||
keyboard_led: CtrlKbdLed::get_supported(),
|
|
||||||
charge_ctrl: CtrlPower::get_supported(),
|
|
||||||
platform_profile: CtrlPlatformProfile::get_supported(),
|
|
||||||
rog_bios_ctrl: CtrlPlatform::get_supported(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use ::zbus::export::futures_util::lock::Mutex;
|
|
||||||
use ::zbus::Connection;
|
|
||||||
use config_traits::{StdConfig, StdConfigLoad, StdConfigLoad2};
|
|
||||||
use daemon::config::Config;
|
|
||||||
use daemon::ctrl_anime::config::AnimeConfig;
|
|
||||||
use daemon::ctrl_anime::trait_impls::CtrlAnimeZbus;
|
|
||||||
use daemon::ctrl_anime::CtrlAnime;
|
|
||||||
use daemon::ctrl_aura::controller::CtrlKbdLed;
|
|
||||||
use daemon::ctrl_aura::trait_impls::CtrlKbdLedZbus;
|
|
||||||
use daemon::ctrl_platform::CtrlPlatform;
|
|
||||||
use daemon::ctrl_power::CtrlPower;
|
|
||||||
use daemon::ctrl_profiles::config::ProfileConfig;
|
|
||||||
use daemon::ctrl_profiles::controller::CtrlPlatformProfile;
|
|
||||||
use daemon::ctrl_profiles::trait_impls::ProfileZbus;
|
|
||||||
use daemon::ctrl_supported::SupportedFunctions;
|
|
||||||
use daemon::{print_board_info, CtrlTask, GetSupported, Reloadable, ZbusRun};
|
|
||||||
use log::{error, info, warn};
|
|
||||||
use rog_aura::aura_detection::LaptopLedData;
|
|
||||||
use rog_dbus::DBUS_NAME;
|
|
||||||
use rog_profiles::Profile;
|
|
||||||
use tokio::time::sleep;
|
|
||||||
use zbus::SignalContext;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let mut logger = env_logger::Builder::new();
|
|
||||||
logger
|
|
||||||
.parse_default_env()
|
|
||||||
.target(env_logger::Target::Stdout)
|
|
||||||
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
|
|
||||||
.init();
|
|
||||||
|
|
||||||
let is_service = match env::var_os("IS_SERVICE") {
|
|
||||||
Some(val) => val == "1",
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_service {
|
|
||||||
println!("asusd schould be only run from the right systemd service");
|
|
||||||
println!(
|
|
||||||
"do not run in your terminal, if you need an logs please use journalctl -b -u asusd"
|
|
||||||
);
|
|
||||||
println!("asusd will now exit");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(" daemon v{}", daemon::VERSION);
|
|
||||||
info!(" rog-anime v{}", rog_anime::VERSION);
|
|
||||||
info!(" rog-aura v{}", rog_aura::VERSION);
|
|
||||||
info!(" rog-dbus v{}", rog_dbus::VERSION);
|
|
||||||
info!(" rog-profiles v{}", rog_profiles::VERSION);
|
|
||||||
info!("rog-platform v{}", rog_platform::VERSION);
|
|
||||||
|
|
||||||
start_daemon().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The actual main loop for the daemon
|
|
||||||
async fn start_daemon() -> Result<(), Box<dyn Error>> {
|
|
||||||
let supported = SupportedFunctions::get_supported();
|
|
||||||
print_board_info();
|
|
||||||
println!("{}", supported.supported_functions());
|
|
||||||
|
|
||||||
// Start zbus server
|
|
||||||
let mut connection = Connection::system().await?;
|
|
||||||
|
|
||||||
let config = Config::new().load();
|
|
||||||
let config = Arc::new(Mutex::new(config));
|
|
||||||
|
|
||||||
supported.add_to_server(&mut connection).await;
|
|
||||||
|
|
||||||
match CtrlPlatform::new(config.clone()) {
|
|
||||||
Ok(ctrl) => {
|
|
||||||
let sig_ctx = CtrlPlatform::signal_context(&connection)?;
|
|
||||||
start_tasks(ctrl, &mut connection, sig_ctx).await?;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("CtrlPlatform: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match CtrlPower::new(config.clone()) {
|
|
||||||
Ok(ctrl) => {
|
|
||||||
let sig_ctx = CtrlPower::signal_context(&connection)?;
|
|
||||||
start_tasks(ctrl, &mut connection, sig_ctx).await?;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("CtrlPower: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if Profile::is_platform_profile_supported() {
|
|
||||||
let profile_config = ProfileConfig::new().load();
|
|
||||||
match CtrlPlatformProfile::new(profile_config) {
|
|
||||||
Ok(ctrl) => {
|
|
||||||
let zbus = ProfileZbus(Arc::new(Mutex::new(ctrl)));
|
|
||||||
let sig_ctx = ProfileZbus::signal_context(&connection)?;
|
|
||||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("Profile control: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("platform_profile support not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
match CtrlAnime::new(AnimeConfig::new().load()) {
|
|
||||||
Ok(ctrl) => {
|
|
||||||
let zbus = CtrlAnimeZbus(Arc::new(Mutex::new(ctrl)));
|
|
||||||
let sig_ctx = CtrlAnimeZbus::signal_context(&connection)?;
|
|
||||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
info!("AniMe control: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let laptop = LaptopLedData::get_data();
|
|
||||||
// CtrlKbdLed deviates from the config pattern above due to requiring a keyboard
|
|
||||||
// detection first
|
|
||||||
match CtrlKbdLed::new(laptop) {
|
|
||||||
Ok(ctrl) => {
|
|
||||||
let zbus = CtrlKbdLedZbus(Arc::new(Mutex::new(ctrl)));
|
|
||||||
let sig_ctx = CtrlKbdLedZbus::signal_context(&connection)?;
|
|
||||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("Keyboard control: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request dbus name after finishing initalizing all functions
|
|
||||||
connection.request_name(DBUS_NAME).await?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// This is just a blocker to idle and ensure the reator reacts
|
|
||||||
sleep(Duration::from_millis(1000)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn start_tasks<T>(
|
|
||||||
mut zbus: T,
|
|
||||||
connection: &mut Connection,
|
|
||||||
signal_ctx: SignalContext<'static>,
|
|
||||||
) -> Result<(), Box<dyn Error>>
|
|
||||||
where
|
|
||||||
T: ZbusRun + Reloadable + CtrlTask + Clone,
|
|
||||||
{
|
|
||||||
let task = zbus.clone();
|
|
||||||
|
|
||||||
zbus.reload()
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| warn!("Controller error: {}", err));
|
|
||||||
zbus.add_to_server(connection).await;
|
|
||||||
|
|
||||||
task.create_tasks(signal_ctx).await.ok();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
use std::convert::From;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use config_traits::ron;
|
|
||||||
use rog_anime::error::AnimeError;
|
|
||||||
use rog_platform::error::PlatformError;
|
|
||||||
use rog_profiles::error::ProfileError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RogError {
|
|
||||||
ParseVendor,
|
|
||||||
ParseLed,
|
|
||||||
MissingProfile(String),
|
|
||||||
Udev(String, std::io::Error),
|
|
||||||
Path(String, std::io::Error),
|
|
||||||
Read(String, std::io::Error),
|
|
||||||
Write(String, std::io::Error),
|
|
||||||
NotSupported,
|
|
||||||
NotFound(String),
|
|
||||||
DoTask(String),
|
|
||||||
MissingFunction(String),
|
|
||||||
MissingLedBrightNode(String, std::io::Error),
|
|
||||||
ReloadFail(String),
|
|
||||||
Profiles(ProfileError),
|
|
||||||
Initramfs(String),
|
|
||||||
Modprobe(String),
|
|
||||||
Io(std::io::Error),
|
|
||||||
Zbus(zbus::Error),
|
|
||||||
ChargeLimit(u8),
|
|
||||||
AuraEffectNotSupported,
|
|
||||||
NoAuraKeyboard,
|
|
||||||
NoAuraNode,
|
|
||||||
Anime(AnimeError),
|
|
||||||
Platform(PlatformError),
|
|
||||||
SystemdUnitAction(String),
|
|
||||||
SystemdUnitWaitTimeout(String),
|
|
||||||
Command(String, std::io::Error),
|
|
||||||
ParseRon(ron::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for RogError {
|
|
||||||
// This trait requires `fmt` with this exact signature.
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
RogError::ParseVendor => write!(f, "Parse gfx vendor error"),
|
|
||||||
RogError::ParseLed => write!(f, "Parse LED error"),
|
|
||||||
RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile),
|
|
||||||
RogError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
|
|
||||||
RogError::Path(path, error) => write!(f, "Path {}: {}", path, error),
|
|
||||||
RogError::Read(path, error) => write!(f, "Read {}: {}", path, error),
|
|
||||||
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
|
|
||||||
RogError::NotSupported => write!(f, "Not supported"),
|
|
||||||
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
|
|
||||||
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
|
|
||||||
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
|
|
||||||
RogError::MissingLedBrightNode(path, error) => write!(
|
|
||||||
f,
|
|
||||||
"Led node at {} is missing, please check you have the required patch or dkms \
|
|
||||||
module installed: {}",
|
|
||||||
path, error
|
|
||||||
),
|
|
||||||
RogError::ReloadFail(deets) => write!(f, "Reload error: {}", deets),
|
|
||||||
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
|
|
||||||
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
|
|
||||||
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
|
|
||||||
RogError::Io(detail) => write!(f, "std::io error: {}", detail),
|
|
||||||
RogError::Zbus(detail) => write!(f, "Zbus error: {}", detail),
|
|
||||||
RogError::ChargeLimit(value) => {
|
|
||||||
write!(f, "Invalid charging limit, not in range 20-100%: {}", value)
|
|
||||||
}
|
|
||||||
RogError::AuraEffectNotSupported => write!(f, "Aura effect not supported"),
|
|
||||||
RogError::NoAuraKeyboard => write!(f, "No supported Aura keyboard"),
|
|
||||||
RogError::NoAuraNode => write!(f, "No Aura keyboard node found"),
|
|
||||||
RogError::Anime(deets) => write!(f, "AniMe Matrix error: {}", deets),
|
|
||||||
RogError::Platform(deets) => write!(f, "Asus Platform error: {}", deets),
|
|
||||||
RogError::SystemdUnitAction(action) => {
|
|
||||||
write!(f, "systemd unit action {} failed", action)
|
|
||||||
}
|
|
||||||
RogError::SystemdUnitWaitTimeout(state) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Timed out waiting for systemd unit change {} state",
|
|
||||||
state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
RogError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
|
||||||
RogError::ParseRon(error) => write!(f, "Parse config error: {}", error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for RogError {}
|
|
||||||
|
|
||||||
impl From<ProfileError> for RogError {
|
|
||||||
fn from(err: ProfileError) -> Self {
|
|
||||||
RogError::Profiles(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AnimeError> for RogError {
|
|
||||||
fn from(err: AnimeError) -> Self {
|
|
||||||
RogError::Anime(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PlatformError> for RogError {
|
|
||||||
fn from(err: PlatformError) -> Self {
|
|
||||||
RogError::Platform(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<zbus::Error> for RogError {
|
|
||||||
fn from(err: zbus::Error) -> Self {
|
|
||||||
RogError::Zbus(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for RogError {
|
|
||||||
fn from(err: std::io::Error) -> Self {
|
|
||||||
RogError::Io(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ron::Error> for RogError {
|
|
||||||
fn from(err: ron::Error) -> Self {
|
|
||||||
RogError::ParseRon(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RogError> for zbus::fdo::Error {
|
|
||||||
#[inline]
|
|
||||||
fn from(err: RogError) -> Self {
|
|
||||||
zbus::fdo::Error::Failed(format!("{}", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
#![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 ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
|
|
||||||
pub mod ctrl_platform;
|
|
||||||
/// Control of battery charge level
|
|
||||||
pub mod ctrl_power;
|
|
||||||
/// Control platform profiles + fan-curves if available
|
|
||||||
pub mod ctrl_profiles;
|
|
||||||
|
|
||||||
/// Fetch all supported functions for the laptop
|
|
||||||
pub mod ctrl_supported;
|
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
|
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use log::{debug, info, warn};
|
|
||||||
use logind_zbus::manager::ManagerProxy;
|
|
||||||
use zbus::export::futures_util::StreamExt;
|
|
||||||
use zbus::zvariant::ObjectPath;
|
|
||||||
use zbus::{Connection, SignalContext};
|
|
||||||
|
|
||||||
use crate::error::RogError;
|
|
||||||
|
|
||||||
const CONFIG_PATH_BASE: &str = "/etc/asusd/";
|
|
||||||
|
|
||||||
/// This macro adds a function which spawns an `inotify` task on the passed in
|
|
||||||
/// `Executor`.
|
|
||||||
///
|
|
||||||
/// The generated function is `watch_<name>()`. Self requires the following
|
|
||||||
/// methods to be available:
|
|
||||||
/// - `<name>() -> SomeValue`, functionally is a getter, but is allowed to have
|
|
||||||
/// side effects.
|
|
||||||
/// - `notify_<name>(SignalContext, 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
|
|
||||||
/// attribute that is being watched or an infinite loop will occur.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// impl CtrlRogBios {
|
|
||||||
/// task_watch_item!(panel_od platform);
|
|
||||||
/// task_watch_item!(gpu_mux_mode platform);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! task_watch_item {
|
|
||||||
($name:ident $self_inner:ident) => {
|
|
||||||
concat_idents::concat_idents!(fn_name = watch_, $name {
|
|
||||||
async fn fn_name(
|
|
||||||
&self,
|
|
||||||
signal_ctxt: SignalContext<'static>,
|
|
||||||
) -> Result<(), RogError> {
|
|
||||||
use zbus::export::futures_util::StreamExt;
|
|
||||||
|
|
||||||
let ctrl = self.clone();
|
|
||||||
concat_idents::concat_idents!(watch_fn = monitor_, $name {
|
|
||||||
match self.$self_inner.watch_fn() {
|
|
||||||
Ok(watch) => {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut buffer = [0; 32];
|
|
||||||
watch.into_event_stream(&mut buffer).unwrap().for_each(|_| async {
|
|
||||||
let value = ctrl.$name();
|
|
||||||
concat_idents::concat_idents!(notif_fn = notify_, $name {
|
|
||||||
Self::notif_fn(&signal_ctxt, value).await.ok();
|
|
||||||
});
|
|
||||||
}).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
pub fn print_board_info() {
|
|
||||||
let dmi = sysfs_class::DmiId::default();
|
|
||||||
let board_name = dmi.board_name().expect("Could not get board_name");
|
|
||||||
let prod_family = dmi.product_family().expect("Could not get product_family");
|
|
||||||
|
|
||||||
info!("Product family: {}", prod_family.trim());
|
|
||||||
info!("Board name: {}", board_name.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Reloadable {
|
|
||||||
async fn reload(&mut self) -> Result<(), RogError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait ZbusRun {
|
|
||||||
async fn add_to_server(self, server: &mut Connection);
|
|
||||||
|
|
||||||
async fn add_to_server_helper(
|
|
||||||
iface: impl zbus::Interface,
|
|
||||||
path: &str,
|
|
||||||
server: &mut Connection,
|
|
||||||
) {
|
|
||||||
server
|
|
||||||
.object_server()
|
|
||||||
.at(&ObjectPath::from_str_unchecked(path), iface)
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("{}: add_to_server {}", path, err);
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up a task to run on the async executor
|
|
||||||
#[async_trait]
|
|
||||||
pub trait CtrlTask {
|
|
||||||
fn zbus_path() -> &'static str;
|
|
||||||
|
|
||||||
fn signal_context(connection: &Connection) -> Result<SignalContext<'static>, zbus::Error> {
|
|
||||||
SignalContext::new(connection, Self::zbus_path())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement to set up various tasks that may be required, using the
|
|
||||||
/// `Executor`. No blocking loops are allowed, or they must be run on a
|
|
||||||
/// separate thread.
|
|
||||||
async fn create_tasks(&self, signal: SignalContext<'static>) -> Result<(), RogError>;
|
|
||||||
|
|
||||||
// /// Create a timed repeating task
|
|
||||||
// async fn repeating_task(&self, millis: u64, mut task: impl FnMut() + Send +
|
|
||||||
// 'static) { use std::time::Duration;
|
|
||||||
// use tokio::time;
|
|
||||||
// let mut timer = time::interval(Duration::from_millis(millis));
|
|
||||||
// tokio::spawn(async move {
|
|
||||||
// timer.tick().await;
|
|
||||||
// task();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Free helper method to create tasks to run on: sleep, wake, shutdown,
|
|
||||||
/// boot
|
|
||||||
///
|
|
||||||
/// The closures can potentially block, so execution time should be the
|
|
||||||
/// minimal possible such as save a variable.
|
|
||||||
async fn create_sys_event_tasks<
|
|
||||||
Fut1,
|
|
||||||
Fut2,
|
|
||||||
Fut3,
|
|
||||||
Fut4,
|
|
||||||
F1: Send + 'static,
|
|
||||||
F2: Send + 'static,
|
|
||||||
F3: Send + 'static,
|
|
||||||
F4: Send + 'static,
|
|
||||||
>(
|
|
||||||
&self,
|
|
||||||
mut on_sleep: F1,
|
|
||||||
mut on_wake: F2,
|
|
||||||
mut on_shutdown: F3,
|
|
||||||
mut on_boot: F4,
|
|
||||||
) where
|
|
||||||
F1: FnMut() -> Fut1,
|
|
||||||
F2: FnMut() -> Fut2,
|
|
||||||
F3: FnMut() -> Fut3,
|
|
||||||
F4: FnMut() -> Fut4,
|
|
||||||
Fut1: Future<Output = ()> + Send,
|
|
||||||
Fut2: Future<Output = ()> + Send,
|
|
||||||
Fut3: Future<Output = ()> + Send,
|
|
||||||
Fut4: Future<Output = ()> + Send,
|
|
||||||
{
|
|
||||||
let connection = Connection::system()
|
|
||||||
.await
|
|
||||||
.expect("Controller could not create dbus connection");
|
|
||||||
|
|
||||||
let manager = ManagerProxy::new(&connection)
|
|
||||||
.await
|
|
||||||
.expect("Controller could not create ManagerProxy");
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Ok(mut notif) = manager.receive_prepare_for_sleep().await {
|
|
||||||
while let Some(event) = notif.next().await {
|
|
||||||
if let Ok(args) = event.args() {
|
|
||||||
if args.start {
|
|
||||||
debug!("Doing on_sleep()");
|
|
||||||
on_sleep().await;
|
|
||||||
} else if !args.start() {
|
|
||||||
debug!("Doing on_wake()");
|
|
||||||
on_wake().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let manager = ManagerProxy::new(&connection)
|
|
||||||
.await
|
|
||||||
.expect("Controller could not create ManagerProxy");
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Ok(mut notif) = manager.receive_prepare_for_shutdown().await {
|
|
||||||
while let Some(event) = notif.next().await {
|
|
||||||
if let Ok(args) = event.args() {
|
|
||||||
if args.start {
|
|
||||||
debug!("Doing on_shutdown()");
|
|
||||||
on_shutdown().await;
|
|
||||||
} else if !args.start() {
|
|
||||||
debug!("Doing on_boot()");
|
|
||||||
on_boot().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait GetSupported {
|
|
||||||
type A;
|
|
||||||
|
|
||||||
fn get_supported() -> Self::A;
|
|
||||||
}
|
|
||||||
@@ -57,9 +57,8 @@ impl AnimeType {
|
|||||||
/// The width of diagonal images
|
/// The width of diagonal images
|
||||||
pub fn width(&self) -> usize {
|
pub fn width(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
AnimeType::GA401 | AnimeType::GA402 => 74,
|
|
||||||
AnimeType::GU604 => 70,
|
AnimeType::GU604 => 70,
|
||||||
AnimeType::Unknown => 0,
|
_ => 74,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +66,8 @@ impl AnimeType {
|
|||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
AnimeType::GA401 => 36,
|
AnimeType::GA401 => 36,
|
||||||
AnimeType::GA402 => 39,
|
|
||||||
AnimeType::GU604 => 43,
|
AnimeType::GU604 => 43,
|
||||||
AnimeType::Unknown => 0,
|
_ => 39,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,9 +75,8 @@ impl AnimeType {
|
|||||||
pub fn data_length(&self) -> usize {
|
pub fn data_length(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
AnimeType::GA401 => PANE_LEN * 2,
|
AnimeType::GA401 => PANE_LEN * 2,
|
||||||
AnimeType::GA402 => PANE_LEN * 3,
|
|
||||||
AnimeType::GU604 => PANE_LEN * 3,
|
AnimeType::GU604 => PANE_LEN * 3,
|
||||||
AnimeType::Unknown => 0,
|
_ => PANE_LEN * 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,9 +140,8 @@ impl AnimeDiagonal {
|
|||||||
pub fn into_data_buffer(&self, anime_type: AnimeType) -> Result<AnimeDataBuffer> {
|
pub fn into_data_buffer(&self, anime_type: AnimeType) -> Result<AnimeDataBuffer> {
|
||||||
match anime_type {
|
match anime_type {
|
||||||
AnimeType::GA401 => self.to_ga401_packets(),
|
AnimeType::GA401 => self.to_ga401_packets(),
|
||||||
AnimeType::GA402 => self.to_ga402_packets(),
|
|
||||||
AnimeType::GU604 => self.to_gu604_packets(),
|
AnimeType::GU604 => self.to_gu604_packets(),
|
||||||
AnimeType::Unknown => self.to_unknown_packets(),
|
_ => self.to_ga402_packets(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,9 +376,4 @@ impl AnimeDiagonal {
|
|||||||
|
|
||||||
AnimeDataBuffer::from_vec(crate::AnimeType::GA402, buf)
|
AnimeDataBuffer::from_vec(crate::AnimeType::GA402, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_unknown_packets(&self) -> Result<AnimeDataBuffer> {
|
|
||||||
let buf = vec![0u8; AnimeType::Unknown.data_length()];
|
|
||||||
AnimeDataBuffer::from_vec(crate::AnimeType::Unknown, buf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,9 +119,8 @@ impl AnimeImage {
|
|||||||
fn scale_x(anime_type: AnimeType) -> f32 {
|
fn scale_x(anime_type: AnimeType) -> f32 {
|
||||||
match anime_type {
|
match anime_type {
|
||||||
AnimeType::GA401 => 0.8,
|
AnimeType::GA401 => 0.8,
|
||||||
AnimeType::GA402 => 0.77,
|
|
||||||
AnimeType::GU604 => 0.78,
|
AnimeType::GU604 => 0.78,
|
||||||
AnimeType::Unknown => 0.0,
|
_ => 0.77,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,9 +136,8 @@ impl AnimeImage {
|
|||||||
fn scale_y(anime_type: AnimeType) -> f32 {
|
fn scale_y(anime_type: AnimeType) -> f32 {
|
||||||
match anime_type {
|
match anime_type {
|
||||||
AnimeType::GA401 => 0.3,
|
AnimeType::GA401 => 0.3,
|
||||||
AnimeType::GA402 => 0.283,
|
|
||||||
AnimeType::GU604 => 0.28,
|
AnimeType::GU604 => 0.28,
|
||||||
AnimeType::Unknown => 0.0,
|
_ => 0.283,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,14 +170,6 @@ impl AnimeImage {
|
|||||||
}
|
}
|
||||||
(y + 1) / 2 - 3
|
(y + 1) / 2 - 3
|
||||||
}
|
}
|
||||||
AnimeType::GA402 => {
|
|
||||||
// first 11 rows start at zero
|
|
||||||
if y <= 11 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// and then their offset grows by one every two rows
|
|
||||||
(y + 1) / 2 - 5
|
|
||||||
}
|
|
||||||
AnimeType::GU604 => {
|
AnimeType::GU604 => {
|
||||||
// first 9 rows start at zero
|
// first 9 rows start at zero
|
||||||
if y <= 9 {
|
if y <= 9 {
|
||||||
@@ -188,7 +178,14 @@ impl AnimeImage {
|
|||||||
// and then their offset grows by one every two rows
|
// and then their offset grows by one every two rows
|
||||||
(y - 9) / 2
|
(y - 9) / 2
|
||||||
}
|
}
|
||||||
AnimeType::Unknown => 0,
|
_ => {
|
||||||
|
// first 11 rows start at zero
|
||||||
|
if y <= 11 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// and then their offset grows by one every two rows
|
||||||
|
(y + 1) / 2 - 5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,19 +214,18 @@ impl AnimeImage {
|
|||||||
}
|
}
|
||||||
36 - (y + 1) / 2
|
36 - (y + 1) / 2
|
||||||
}
|
}
|
||||||
AnimeType::GA402 => {
|
|
||||||
if y <= 11 {
|
|
||||||
return 34;
|
|
||||||
}
|
|
||||||
39 - y / 2
|
|
||||||
}
|
|
||||||
AnimeType::GU604 => {
|
AnimeType::GU604 => {
|
||||||
if y <= 9 {
|
if y <= 9 {
|
||||||
return 38 + y % 2;
|
return 38 + y % 2;
|
||||||
}
|
}
|
||||||
38 - Self::first_x(anime_type, y) + y % 2
|
38 - Self::first_x(anime_type, y) + y % 2
|
||||||
}
|
}
|
||||||
AnimeType::Unknown => 0,
|
_ => {
|
||||||
|
if y <= 11 {
|
||||||
|
return 34;
|
||||||
|
}
|
||||||
|
39 - y / 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,9 +234,9 @@ impl AnimeImage {
|
|||||||
match anime_type {
|
match anime_type {
|
||||||
// 33.0 = Longest row LED count (physical) plus half-pixel offset
|
// 33.0 = Longest row LED count (physical) plus half-pixel offset
|
||||||
AnimeType::GA401 => (33.0 + 0.5) * Self::scale_x(anime_type),
|
AnimeType::GA401 => (33.0 + 0.5) * Self::scale_x(anime_type),
|
||||||
AnimeType::GA402 => (35.0 + 0.5) * Self::scale_x(anime_type),
|
|
||||||
AnimeType::GU604 => (38.0 + 0.5) * Self::scale_x(anime_type),
|
AnimeType::GU604 => (38.0 + 0.5) * Self::scale_x(anime_type),
|
||||||
AnimeType::Unknown => 0.0,
|
_ => (35.0 + 0.5) * Self::scale_x(anime_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,9 +244,8 @@ impl AnimeImage {
|
|||||||
fn height(anime_type: AnimeType) -> u32 {
|
fn height(anime_type: AnimeType) -> u32 {
|
||||||
match anime_type {
|
match anime_type {
|
||||||
AnimeType::GA401 => 55,
|
AnimeType::GA401 => 55,
|
||||||
AnimeType::GA402 => 61,
|
|
||||||
AnimeType::GU604 => 62,
|
AnimeType::GU604 => 62,
|
||||||
AnimeType::Unknown => 0,
|
_ => 61,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,10 +254,9 @@ impl AnimeImage {
|
|||||||
match anime_type {
|
match anime_type {
|
||||||
// 54.0 = End column LED count (physical) plus one dead pixel
|
// 54.0 = End column LED count (physical) plus one dead pixel
|
||||||
AnimeType::GA401 => (54.0 + 1.0) * Self::scale_y(anime_type),
|
AnimeType::GA401 => (54.0 + 1.0) * Self::scale_y(anime_type),
|
||||||
// GA402 may not have dead pixels and require only the physical LED count
|
|
||||||
AnimeType::GA402 => 61.0 * Self::scale_y(anime_type),
|
|
||||||
AnimeType::GU604 => 62.0 * Self::scale_y(anime_type),
|
AnimeType::GU604 => 62.0 * Self::scale_y(anime_type),
|
||||||
AnimeType::Unknown => 0.0,
|
// GA402 may not have dead pixels and require only the physical LED count
|
||||||
|
_ => 61.0 * Self::scale_y(anime_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,10 +268,9 @@ impl AnimeImage {
|
|||||||
1 | 3 => 35, // Some rows are padded
|
1 | 3 => 35, // Some rows are padded
|
||||||
_ => 36 - y / 2,
|
_ => 36 - y / 2,
|
||||||
},
|
},
|
||||||
// GA402 does not have padding, equivalent to width
|
|
||||||
AnimeType::GA402 => AnimeImage::width(anime_type, y),
|
|
||||||
AnimeType::GU604 => AnimeImage::width(anime_type, y),
|
AnimeType::GU604 => AnimeImage::width(anime_type, y),
|
||||||
AnimeType::Unknown => 0,
|
// GA402 does not have padding, equivalent to width
|
||||||
|
_ => AnimeImage::width(anime_type, y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,14 +84,6 @@ impl EffectState for DoomFlicker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LightFlash {
|
|
||||||
pub count: i32,
|
|
||||||
pub max_light: i32,
|
|
||||||
pub min_light: i32,
|
|
||||||
pub max_time: i32,
|
|
||||||
pub min_time: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct DoomLightFlash {
|
pub struct DoomLightFlash {
|
||||||
led: LedCode,
|
led: LedCode,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ eframe = { git = "https://github.com/emilk/egui", rev = "b8e798777de519de3a18787
|
|||||||
libappindicator = "0.8" # Tray icon
|
libappindicator = "0.8" # Tray icon
|
||||||
gtk = "0.16"
|
gtk = "0.16"
|
||||||
|
|
||||||
daemon = { path = "../daemon" }
|
asusd = { path = "../asusd" }
|
||||||
rog_anime = { path = "../rog-anime" }
|
rog_anime = { path = "../rog-anime" }
|
||||||
rog_dbus = { path = "../rog-dbus" }
|
rog_dbus = { path = "../rog-dbus" }
|
||||||
rog_aura = { path = "../rog-aura" }
|
rog_aura = { path = "../rog-aura" }
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|||||||
pub fn print_versions() {
|
pub fn print_versions() {
|
||||||
println!("App and daemon versions:");
|
println!("App and daemon versions:");
|
||||||
println!(" rog-gui v{}", VERSION);
|
println!(" rog-gui v{}", VERSION);
|
||||||
println!(" asusd v{}", daemon::VERSION);
|
println!(" asusd v{}", asusd::VERSION);
|
||||||
println!("\nComponent crate versions:");
|
println!("\nComponent crate versions:");
|
||||||
println!(" rog-anime v{}", rog_anime::VERSION);
|
println!(" rog-anime v{}", rog_anime::VERSION);
|
||||||
println!(" rog-aura v{}", rog_aura::VERSION);
|
println!(" rog-aura v{}", rog_aura::VERSION);
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ impl HidRaw {
|
|||||||
} else {
|
} else {
|
||||||
// Try to see if there is a virtual device created with uhid for testing
|
// Try to see if there is a virtual device created with uhid for testing
|
||||||
let dev_path = device.devpath().to_string_lossy();
|
let dev_path = device.devpath().to_string_lossy();
|
||||||
dbg!(&dev_path);
|
|
||||||
if dev_path.contains("virtual") && dev_path.contains(&id_product.to_uppercase()) {
|
if dev_path.contains("virtual") && dev_path.contains(&id_product.to_uppercase()) {
|
||||||
if let Some(dev_node) = device.devnode() {
|
if let Some(dev_node) = device.devnode() {
|
||||||
info!("Using device at: {:?} for asdfgsadfgh control", dev_node);
|
info!("Using device at: {:?} for asdfgsadfgh control", dev_node);
|
||||||
|
|||||||
@@ -10,23 +10,22 @@ use sdl2::pixels::Color;
|
|||||||
use sdl2::rect::Rect;
|
use sdl2::rect::Rect;
|
||||||
use uhid_virt::{Bus, CreateParams, UHIDDevice};
|
use uhid_virt::{Bus, CreateParams, UHIDDevice};
|
||||||
|
|
||||||
|
mod animatrix;
|
||||||
|
use animatrix::*;
|
||||||
|
|
||||||
pub struct VirtAnimeMatrix {
|
pub struct VirtAnimeMatrix {
|
||||||
device: UHIDDevice<std::fs::File>,
|
device: UHIDDevice<std::fs::File>,
|
||||||
buffer: [u8; 640],
|
buffer: [u8; 640],
|
||||||
time: Instant,
|
time: Instant,
|
||||||
}
|
animatrix: AniMatrix,
|
||||||
|
|
||||||
impl Default for VirtAnimeMatrix {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtAnimeMatrix {
|
impl VirtAnimeMatrix {
|
||||||
pub fn new() -> Self {
|
pub fn new(model: Model) -> Self {
|
||||||
VirtAnimeMatrix {
|
VirtAnimeMatrix {
|
||||||
time: Instant::now(),
|
time: Instant::now(),
|
||||||
buffer: [0; 640],
|
buffer: [0; 640],
|
||||||
|
animatrix: AniMatrix::new(model),
|
||||||
device: UHIDDevice::create(CreateParams {
|
device: UHIDDevice::create(CreateParams {
|
||||||
name: String::from("ROG_Virtual Anime Matrix"),
|
name: String::from("ROG_Virtual Anime Matrix"),
|
||||||
phys: String::from(""),
|
phys: String::from(""),
|
||||||
@@ -146,33 +145,35 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
let mut canvas = window.into_canvas().build().unwrap();
|
let mut canvas = window.into_canvas().build().unwrap();
|
||||||
|
|
||||||
let mut dev = VirtAnimeMatrix::new();
|
let mut dev = VirtAnimeMatrix::new(Model::GA402);
|
||||||
|
|
||||||
|
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||||
|
canvas.clear();
|
||||||
let mut event_pump = sdl_context.event_pump().unwrap();
|
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||||
'running: loop {
|
'running: loop {
|
||||||
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
|
||||||
canvas.clear();
|
|
||||||
|
|
||||||
dev.read(); // it's blocking, and damned hard to sync with arc/mutex
|
dev.read(); // it's blocking, and damned hard to sync with arc/mutex
|
||||||
let one = dev.buffer[0..7] != USB_PREFIX2;
|
// let one = dev.buffer[0..7] != USB_PREFIX2;
|
||||||
|
let index = dev.buffer[3];
|
||||||
|
println!("{:02x}", index);
|
||||||
|
|
||||||
for (mut i, b) in dev.buffer.iter().skip(7).enumerate() {
|
let w = dev.animatrix.led_shape().horizontal * 6;
|
||||||
if !one {
|
let h = dev.animatrix.led_shape().vertical * 6;
|
||||||
i += 640;
|
for (y_count, row) in dev.animatrix.rows().iter().enumerate() {
|
||||||
};
|
if row.0 == index {
|
||||||
if *b == 0 {
|
let start = row.1;
|
||||||
continue;
|
let end = start + row.2;
|
||||||
|
for (x_count, b) in dev.buffer[start..end].iter().enumerate() {
|
||||||
|
print!("{b},");
|
||||||
|
canvas.set_draw_color(Color::RGB(*b as u8, *b as u8, *b as u8));
|
||||||
|
|
||||||
|
let x = x_count as i32 * w - if y_count % 2 == 0 { 0 } else { w / 2 };
|
||||||
|
let y = y_count as i32 * h;
|
||||||
|
canvas
|
||||||
|
.fill_rect(Rect::new(x, y, w as u32, h as u32))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
canvas.set_draw_color(Color::RGB(*b as u8, *b as u8, *b as u8));
|
|
||||||
let y = (i / 33) % 55;
|
|
||||||
let x = (i % 33) as i32 * 20;
|
|
||||||
// if i % 2 == 0 {
|
|
||||||
// x /= 2;
|
|
||||||
// }
|
|
||||||
dbg!(i, i / 33);
|
|
||||||
canvas
|
|
||||||
.fill_rect(Rect::new(x, y as i32 * 20, 20, 20))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for event in event_pump.poll_iter() {
|
for event in event_pump.poll_iter() {
|
||||||
|
|||||||
Reference in New Issue
Block a user