mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
370 lines
12 KiB
Rust
370 lines
12 KiB
Rust
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: u64,
|
|
/// Used only for `TimeType::Timer`, milliseonds to fade the image out for
|
|
fade_out: u64,
|
|
}
|
|
|
|
impl From<Timer> for AnimTime {
|
|
fn from(time: Timer) -> Self {
|
|
match time.type_of {
|
|
TimeType::Timer => {
|
|
if time.fade_in != 0 && time.fade_out != 0 {
|
|
let fade_in = Duration::from_millis(time.fade_in);
|
|
let fade_out = Duration::from_millis(time.fade_out);
|
|
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(())
|
|
}
|
|
}
|