mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
anime: add zbus methods
This commit is contained in:
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Revert zbus to 1.9.1
|
- Revert zbus to 1.9.1
|
||||||
- Use enum to show power states, and catch missing pci path for nvidia.
|
- Use enum to show power states, and catch missing pci path for nvidia.
|
||||||
- Partial user-daemon for anime/per-key done, asusd-user. Includes asusd-user systemd unit.
|
- Partial user-daemon for anime/per-key done, asusd-user. Includes asusd-user systemd unit.
|
||||||
|
- user-daemon provides dbus emthods to insert anime actions, remove from index, set leds on/off
|
||||||
|
|
||||||
# [3.3.0] - 2021-04-3
|
# [3.3.0] - 2021-04-3
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -233,9 +233,13 @@ dependencies = [
|
|||||||
"dirs 3.0.1",
|
"dirs 3.0.1",
|
||||||
"rog_anime",
|
"rog_anime",
|
||||||
"rog_dbus",
|
"rog_dbus",
|
||||||
|
"rog_types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"zbus",
|
||||||
|
"zvariant",
|
||||||
|
"zvariant_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -909,6 +913,7 @@ dependencies = [
|
|||||||
"png_pong",
|
"png_pong",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
"zbus",
|
||||||
"zvariant",
|
"zvariant",
|
||||||
"zvariant_derive",
|
"zvariant_derive",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use notify_rust::{Hint, Notification, NotificationHandle};
|
|||||||
use rog_dbus::{DbusProxies, Signals};
|
use rog_dbus::{DbusProxies, Signals};
|
||||||
use rog_types::profile::Profile;
|
use rog_types::profile::Profile;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@@ -18,9 +19,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let mut last_chrg_notif: Option<NotificationHandle> = None;
|
let mut last_chrg_notif: Option<NotificationHandle> = None;
|
||||||
|
|
||||||
let recv = proxies.setup_recv(conn);
|
let recv = proxies.setup_recv(conn);
|
||||||
|
let mut err_count = 0;
|
||||||
loop {
|
loop {
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
sleep(Duration::from_millis(100));
|
||||||
recv.next_signal().unwrap();
|
if let Err(err) = recv.next_signal() {
|
||||||
|
if err_count < 3 {
|
||||||
|
err_count += 1;
|
||||||
|
println!("{}", err);
|
||||||
|
}
|
||||||
|
if err_count == 3 {
|
||||||
|
err_count += 1;
|
||||||
|
println!("Max error count reached. Spooling silently.");
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(2000));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
err_count = 0;
|
||||||
|
|
||||||
if let Ok(mut lock) = signals.gfx_vendor.lock() {
|
if let Ok(mut lock) = signals.gfx_vendor.lock() {
|
||||||
if let Some(vendor) = lock.take() {
|
if let Some(vendor) = lock.take() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{env, path::Path, thread::sleep};
|
use std::{env, path::Path, thread::sleep};
|
||||||
|
|
||||||
use rog_anime::{Action, Sequences};
|
use rog_anime::{ActionData, AnimeAction, Sequences};
|
||||||
use rog_dbus::AuraDbusClient;
|
use rog_dbus::AuraDbusClient;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -15,11 +15,19 @@ fn main() {
|
|||||||
let path = Path::new(&args[1]);
|
let path = Path::new(&args[1]);
|
||||||
let brightness = args[2].parse::<f32>().unwrap();
|
let brightness = args[2].parse::<f32>().unwrap();
|
||||||
let mut seq = Sequences::new();
|
let mut seq = Sequences::new();
|
||||||
seq.add_asus_gif(path, rog_anime::AnimTime::Infinite, brightness).unwrap();
|
seq.insert(
|
||||||
|
0,
|
||||||
|
&AnimeAction::AsusAnimation {
|
||||||
|
file: path.into(),
|
||||||
|
time: rog_anime::AnimTime::Infinite,
|
||||||
|
brightness,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
for action in seq.iter() {
|
for action in seq.iter() {
|
||||||
if let Action::Animation(frames) = action {
|
if let ActionData::Animation(frames) = action {
|
||||||
for frame in frames.frames() {
|
for frame in frames.frames() {
|
||||||
client
|
client
|
||||||
.proxies()
|
.proxies()
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
use std::{
|
|
||||||
env,
|
|
||||||
path::Path,
|
|
||||||
thread::sleep,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use glam::Vec2;
|
|
||||||
use rog_anime::{Action, AnimTime, Sequences};
|
|
||||||
use rog_dbus::AuraDbusClient;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let (client, _) = AuraDbusClient::new().unwrap();
|
|
||||||
|
|
||||||
let args: Vec<String> = env::args().into_iter().collect();
|
|
||||||
if args.len() < 7 {
|
|
||||||
println!(
|
|
||||||
"Usage: <filepath> <scale> <angle> <x pos> <y pos> <brightness> <duration> <filepath>"
|
|
||||||
);
|
|
||||||
println!("e.g, asusctl/examples/file.gif 0.9 0.4 0.0 0.0 0.8 0");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut seq = Sequences::new();
|
|
||||||
seq.add_image_gif(
|
|
||||||
Path::new(&args[1]),
|
|
||||||
args[2].parse::<f32>().unwrap(),
|
|
||||||
args[3].parse::<f32>().unwrap(),
|
|
||||||
Vec2::new(
|
|
||||||
args[4].parse::<f32>().unwrap(),
|
|
||||||
args[5].parse::<f32>().unwrap(),
|
|
||||||
),
|
|
||||||
if let Ok(time) = args[7].parse::<u64>() {
|
|
||||||
if time != 0 {
|
|
||||||
AnimTime::Time(Duration::from_secs(time))
|
|
||||||
} else {
|
|
||||||
AnimTime::Infinite
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AnimTime::Infinite
|
|
||||||
},
|
|
||||||
args[6].parse::<f32>().unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if args.len() == 9 {
|
|
||||||
seq.add_image_gif(
|
|
||||||
Path::new(&args[8]),
|
|
||||||
args[2].parse::<f32>().unwrap(),
|
|
||||||
args[3].parse::<f32>().unwrap(),
|
|
||||||
Vec2::new(
|
|
||||||
args[4].parse::<f32>().unwrap(),
|
|
||||||
args[5].parse::<f32>().unwrap(),
|
|
||||||
),
|
|
||||||
if let Ok(time) = args[7].parse::<u64>() {
|
|
||||||
if time != 0 {
|
|
||||||
AnimTime::Time(Duration::from_secs(time))
|
|
||||||
} else {
|
|
||||||
AnimTime::Infinite
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AnimTime::Infinite
|
|
||||||
},
|
|
||||||
args[6].parse::<f32>().unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
for action in seq.iter() {
|
|
||||||
if let Action::Animation(frames) = action {
|
|
||||||
let mut count = 0;
|
|
||||||
let start = Instant::now();
|
|
||||||
'outer: loop {
|
|
||||||
for frame in frames.frames() {
|
|
||||||
client
|
|
||||||
.proxies()
|
|
||||||
.anime()
|
|
||||||
.write(frame.frame().clone())
|
|
||||||
.unwrap();
|
|
||||||
if let AnimTime::Time(time) = frames.duration() {
|
|
||||||
if Instant::now().duration_since(start) > time {
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
} else if let AnimTime::Cycles(times) = frames.duration() {
|
|
||||||
if count == times {
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sleep(frame.delay());
|
|
||||||
}
|
|
||||||
count +=1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,10 +3,6 @@ mod aura_cli;
|
|||||||
|
|
||||||
use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
|
use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
|
||||||
use anime_cli::{AniMeActions, AniMeCommand};
|
use anime_cli::{AniMeActions, AniMeCommand};
|
||||||
use daemon::{
|
|
||||||
ctrl_fan_cpu::FanCpuSupportedFunctions, ctrl_leds::LedSupportedFunctions,
|
|
||||||
ctrl_rog_bios::RogBiosSupportedFunctions, ctrl_supported::SupportedFunctions,
|
|
||||||
};
|
|
||||||
use gumdrop::{Opt, Options};
|
use gumdrop::{Opt, Options};
|
||||||
use rog_anime::{AniMeDataBuffer, AniMeImage, Vec2, ANIME_DATA_LEN};
|
use rog_anime::{AniMeDataBuffer, AniMeImage, Vec2, ANIME_DATA_LEN};
|
||||||
use rog_dbus::AuraDbusClient;
|
use rog_dbus::AuraDbusClient;
|
||||||
@@ -14,6 +10,10 @@ use rog_types::{
|
|||||||
aura_modes::{self, AuraEffect, AuraModeNum},
|
aura_modes::{self, AuraEffect, AuraModeNum},
|
||||||
gfx_vendors::GfxVendors,
|
gfx_vendors::GfxVendors,
|
||||||
profile::{FanLevel, ProfileCommand, ProfileEvent},
|
profile::{FanLevel, ProfileCommand, ProfileEvent},
|
||||||
|
supported::{
|
||||||
|
FanCpuSupportedFunctions, LedSupportedFunctions, RogBiosSupportedFunctions,
|
||||||
|
SupportedFunctions,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use std::{env::args, path::Path};
|
use std::{env::args, path::Path};
|
||||||
use yansi_term::Colour::Green;
|
use yansi_term::Colour::Green;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "asusd-user"
|
name = "asusd-user"
|
||||||
path = "src/main.rs"
|
path = "src/daemon.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# serialisation
|
# serialisation
|
||||||
@@ -21,5 +21,10 @@ serde_derive = "^1.0"
|
|||||||
|
|
||||||
rog_anime = { path = "../rog-anime" }
|
rog_anime = { path = "../rog-anime" }
|
||||||
rog_dbus = { path = "../rog-dbus" }
|
rog_dbus = { path = "../rog-dbus" }
|
||||||
|
rog_types = { path = "../rog-types" }
|
||||||
|
|
||||||
dirs = "3.0.1"
|
dirs = "3.0.1"
|
||||||
|
|
||||||
|
zbus = "^1.9.1"
|
||||||
|
zvariant = "^2.6"
|
||||||
|
zvariant_derive = "^2.6"
|
||||||
342
daemon-user/src/ctrl_anime.rs
Normal file
342
daemon-user/src/ctrl_anime.rs
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
use rog_anime::{ActionData, AnimTime, AnimeAction, Sequences, Vec2};
|
||||||
|
use rog_dbus::AuraDbusClient;
|
||||||
|
//use crate::dbus::DbusEvents;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{
|
||||||
|
path::Path,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Mutex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{sync::Arc, thread::sleep, time::Instant};
|
||||||
|
use zbus::dbus_interface;
|
||||||
|
use zvariant::ObjectPath;
|
||||||
|
use zvariant_derive::Type;
|
||||||
|
|
||||||
|
use crate::{error::Error, user_config::UserConfig};
|
||||||
|
|
||||||
|
#[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: AuraDbusClient<'a>,
|
||||||
|
do_early_return: &'a AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CtrlAnimeInner<'static> {
|
||||||
|
pub fn new(
|
||||||
|
sequences: Sequences,
|
||||||
|
client: AuraDbusClient<'static>,
|
||||||
|
do_early_return: &'static 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(&self) -> Result<(), Error> {
|
||||||
|
if self.do_early_return.load(Ordering::SeqCst) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for action in self.sequences.iter() {
|
||||||
|
match action {
|
||||||
|
ActionData::Animation(frames) => {
|
||||||
|
let mut count = 0;
|
||||||
|
let start = Instant::now();
|
||||||
|
'animation: loop {
|
||||||
|
for frame in frames.frames() {
|
||||||
|
if self.do_early_return.load(Ordering::SeqCst) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.client
|
||||||
|
.proxies()
|
||||||
|
.anime()
|
||||||
|
.write(frame.frame().clone())
|
||||||
|
.unwrap();
|
||||||
|
if let AnimTime::Time(time) = frames.duration() {
|
||||||
|
if Instant::now().duration_since(start) > time {
|
||||||
|
break 'animation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(frame.delay());
|
||||||
|
}
|
||||||
|
if let AnimTime::Cycles(times) = frames.duration() {
|
||||||
|
count += 1;
|
||||||
|
if count >= times {
|
||||||
|
break 'animation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActionData::Image(image) => {
|
||||||
|
self.client
|
||||||
|
.proxies()
|
||||||
|
.anime()
|
||||||
|
.write(image.as_ref().clone())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
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<UserConfig>>,
|
||||||
|
client: AuraDbusClient<'a>,
|
||||||
|
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
|
||||||
|
/// Must be the same Atomic as in CtrlAnimeInner
|
||||||
|
inner_early_return: &'a AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CtrlAnime<'static> {
|
||||||
|
pub fn new(
|
||||||
|
config: Arc<Mutex<UserConfig>>,
|
||||||
|
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
|
||||||
|
client: AuraDbusClient<'static>,
|
||||||
|
inner_early_return: &'static AtomicBool,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
Ok(CtrlAnime {
|
||||||
|
config,
|
||||||
|
inner,
|
||||||
|
client,
|
||||||
|
inner_early_return,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||||
|
server
|
||||||
|
.at(
|
||||||
|
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
.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: String,
|
||||||
|
time: TimeType,
|
||||||
|
count: u32,
|
||||||
|
brightness: f32,
|
||||||
|
) -> zbus::fdo::Result<String> {
|
||||||
|
if let Ok(mut config) = self.config.try_lock() {
|
||||||
|
let time: AnimTime = match time {
|
||||||
|
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
|
||||||
|
TimeType::Count => AnimTime::Cycles(count),
|
||||||
|
TimeType::Infinite => AnimTime::Infinite,
|
||||||
|
};
|
||||||
|
let file = Path::new(&file);
|
||||||
|
let action = AnimeAction::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)?;
|
||||||
|
}
|
||||||
|
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: String,
|
||||||
|
scale: f32,
|
||||||
|
angle: f32,
|
||||||
|
xy: (f32, f32),
|
||||||
|
time: TimeType,
|
||||||
|
count: u32,
|
||||||
|
brightness: f32,
|
||||||
|
) -> zbus::fdo::Result<String> {
|
||||||
|
if let Ok(mut config) = self.config.try_lock() {
|
||||||
|
let time: AnimTime = match time {
|
||||||
|
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
|
||||||
|
TimeType::Count => AnimTime::Cycles(count),
|
||||||
|
TimeType::Infinite => AnimTime::Infinite,
|
||||||
|
};
|
||||||
|
let file = Path::new(&file);
|
||||||
|
let translation = Vec2::new(xy.0, xy.1);
|
||||||
|
let action = AnimeAction::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)?;
|
||||||
|
}
|
||||||
|
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_image(
|
||||||
|
&mut self,
|
||||||
|
index: u32,
|
||||||
|
file: String,
|
||||||
|
scale: f32,
|
||||||
|
angle: f32,
|
||||||
|
xy: (f32, f32),
|
||||||
|
brightness: f32,
|
||||||
|
) -> zbus::fdo::Result<String> {
|
||||||
|
if let Ok(mut config) = self.config.try_lock() {
|
||||||
|
let file = Path::new(&file);
|
||||||
|
let action = AnimeAction::Image {
|
||||||
|
file: file.into(),
|
||||||
|
scale,
|
||||||
|
angle,
|
||||||
|
translation: Vec2::new(xy.0, xy.1),
|
||||||
|
brightness,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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)?;
|
||||||
|
}
|
||||||
|
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 = AnimeAction::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)?;
|
||||||
|
}
|
||||||
|
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().toggle_on(on)?;
|
||||||
|
// 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().toggle_on(on)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
67
daemon-user/src/daemon.rs
Normal file
67
daemon-user/src/daemon.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use rog_dbus::AuraDbusClient;
|
||||||
|
use rog_types::supported::SupportedFunctions;
|
||||||
|
use rog_user::{
|
||||||
|
ctrl_anime::{CtrlAnime, CtrlAnimeInner},
|
||||||
|
user_config::*,
|
||||||
|
DBUS_NAME,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::thread;
|
||||||
|
use zbus::{fdo, Connection};
|
||||||
|
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
/// The anime loop needs an atomic to make it exit early if required
|
||||||
|
static ANIME_INNER_EARLY_RETURN: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!(" rog-dbus version {}", rog_dbus::VERSION);
|
||||||
|
|
||||||
|
let (client, _) = AuraDbusClient::new().unwrap();
|
||||||
|
let supported = client.proxies().supported().get_supported_functions()?;
|
||||||
|
let supported = serde_json::from_str::<SupportedFunctions>(&&supported).unwrap();
|
||||||
|
|
||||||
|
let mut config = UserConfig::new();
|
||||||
|
config.load_config()?;
|
||||||
|
let anime = config.create_anime()?;
|
||||||
|
|
||||||
|
let config = Arc::new(Mutex::new(config));
|
||||||
|
|
||||||
|
// Create server
|
||||||
|
let connection = Connection::new_session()?;
|
||||||
|
fdo::DBusProxy::new(&connection)?
|
||||||
|
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
|
||||||
|
let mut server = zbus::ObjectServer::new(&connection);
|
||||||
|
|
||||||
|
// Set up the anime data and run loop/thread
|
||||||
|
if supported.anime_ctrl.0 {
|
||||||
|
// Inner behind mutex required for thread safety
|
||||||
|
let inner = Arc::new(Mutex::new(CtrlAnimeInner::new(
|
||||||
|
anime,
|
||||||
|
client,
|
||||||
|
&ANIME_INNER_EARLY_RETURN,
|
||||||
|
)?));
|
||||||
|
// Need new client object for dbus control part
|
||||||
|
let (client, _) = AuraDbusClient::new().unwrap();
|
||||||
|
let anime_control =
|
||||||
|
CtrlAnime::new(config, inner.clone(), client, &ANIME_INNER_EARLY_RETURN)?;
|
||||||
|
anime_control.add_to_server(&mut server);
|
||||||
|
// Thread using inner
|
||||||
|
let _anime_thread = thread::Builder::new()
|
||||||
|
.name("Anime User".into())
|
||||||
|
.spawn(move || loop {
|
||||||
|
if let Ok(inner) = inner.try_lock() {
|
||||||
|
inner.run().unwrap();
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if supported.keyboard_led.per_key_led_mode {}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Err(err) = server.try_handle_next() {
|
||||||
|
println!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use rog_anime::error::AnimeError;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
ConfigLoadFail,
|
ConfigLoadFail,
|
||||||
|
ConfigLockFail,
|
||||||
XdgVars,
|
XdgVars,
|
||||||
Anime(AnimeError),
|
Anime(AnimeError),
|
||||||
}
|
}
|
||||||
@@ -16,6 +17,7 @@ impl fmt::Display for Error {
|
|||||||
match self {
|
match self {
|
||||||
Error::Io(err) => write!(f, "Failed to open: {}", err),
|
Error::Io(err) => write!(f, "Failed to open: {}", err),
|
||||||
Error::ConfigLoadFail => write!(f, "Failed to load user config"),
|
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::XdgVars => write!(f, "XDG environment vars appear unset"),
|
||||||
Error::Anime(err) => write!(f, "Anime error: {}", err),
|
Error::Anime(err) => write!(f, "Anime error: {}", err),
|
||||||
}
|
}
|
||||||
@@ -35,3 +37,9 @@ impl From<AnimeError> for Error {
|
|||||||
Error::Anime(err)
|
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,3 +1,9 @@
|
|||||||
pub mod user_config;
|
pub mod user_config;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
|
pub mod ctrl_anime;
|
||||||
|
|
||||||
|
pub mod zbus_anime;
|
||||||
|
|
||||||
|
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
use rog_anime::{Action, AnimTime};
|
|
||||||
use rog_dbus::AuraDbusClient;
|
|
||||||
use rog_user::user_config::*;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
thread::sleep,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
println!(" rog-dbus version {}", rog_dbus::VERSION);
|
|
||||||
|
|
||||||
let (client, _) = AuraDbusClient::new().unwrap();
|
|
||||||
|
|
||||||
let mut config = UserConfig::new();
|
|
||||||
config.load_config()?;
|
|
||||||
let anime = config.create_anime()?;
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - find user config dir with xdg
|
|
||||||
// - load user config
|
|
||||||
// - start anime
|
|
||||||
// A way to reload when the config changes
|
|
||||||
|
|
||||||
loop {
|
|
||||||
for action in anime.iter() {
|
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
match action {
|
|
||||||
Action::Animation(frames) => {
|
|
||||||
let mut count = 0;
|
|
||||||
'animation: loop {
|
|
||||||
for frame in frames.frames() {
|
|
||||||
client.proxies().anime().write(frame.frame().clone())?;
|
|
||||||
if let AnimTime::Time(time) = frames.duration() {
|
|
||||||
if Instant::now().duration_since(start) > time {
|
|
||||||
break 'animation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sleep(frame.delay());
|
|
||||||
}
|
|
||||||
if let AnimTime::Cycles(times) = frames.duration() {
|
|
||||||
count += 1;
|
|
||||||
if count >= times {
|
|
||||||
break 'animation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Image(image) => {
|
|
||||||
client.proxies().anime().write(image.as_ref().clone())?;
|
|
||||||
}
|
|
||||||
Action::Pause(duration) => 'pause: loop {
|
|
||||||
if Instant::now().duration_since(start) > *duration {
|
|
||||||
break 'pause;
|
|
||||||
}
|
|
||||||
sleep(Duration::from_millis(10));
|
|
||||||
},
|
|
||||||
Action::AudioEq => {}
|
|
||||||
Action::SystemInfo => {}
|
|
||||||
Action::TimeDate => {}
|
|
||||||
Action::Matrix => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,17 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::{create_dir, OpenOptions},
|
fs::{create_dir, OpenOptions},
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
path::PathBuf,
|
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rog_anime::{AnimTime, Sequences, Vec2};
|
use rog_anime::{AnimTime, AnimeAction, Sequences, Vec2};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
pub struct UserConfig {
|
pub struct UserConfig {
|
||||||
anime: Vec<AnimeAction>,
|
pub anime: Vec<AnimeAction>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub enum AnimeAction {
|
|
||||||
/// Full gif sequence. Immutable.
|
|
||||||
AsusAnimation {
|
|
||||||
file: PathBuf,
|
|
||||||
time: AnimTime,
|
|
||||||
brightness: f32,
|
|
||||||
},
|
|
||||||
/// Basic image, can have properties changed
|
|
||||||
ImageAnimation {
|
|
||||||
file: PathBuf,
|
|
||||||
scale: f32,
|
|
||||||
angle: f32,
|
|
||||||
translation: Vec2,
|
|
||||||
time: AnimTime,
|
|
||||||
brightness: f32,
|
|
||||||
},
|
|
||||||
Image {
|
|
||||||
file: PathBuf,
|
|
||||||
scale: f32,
|
|
||||||
angle: f32,
|
|
||||||
translation: Vec2,
|
|
||||||
brightness: f32,
|
|
||||||
},
|
|
||||||
/// A pause to be used between sequences
|
|
||||||
Pause(Duration),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserConfig {
|
impl UserConfig {
|
||||||
@@ -114,38 +85,35 @@ impl UserConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
//Err(Error::ConfigLoadFail)
|
}
|
||||||
|
|
||||||
|
pub fn write(&self) -> Result<(), Error> {
|
||||||
|
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||||
|
dir
|
||||||
|
} else {
|
||||||
|
return Err(Error::XdgVars);
|
||||||
|
};
|
||||||
|
|
||||||
|
path.push("rog");
|
||||||
|
if !path.exists() {
|
||||||
|
create_dir(path.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push("rog-user.cfg");
|
||||||
|
|
||||||
|
let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(&path)?;
|
||||||
|
|
||||||
|
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||||
|
dbg!(&json);
|
||||||
|
file.write_all(json.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_anime(&self) -> Result<Sequences, Error> {
|
pub fn create_anime(&self) -> Result<Sequences, Error> {
|
||||||
let mut seq = Sequences::new();
|
let mut seq = Sequences::new();
|
||||||
|
|
||||||
for anime in self.anime.iter() {
|
for (idx, action) in self.anime.iter().enumerate() {
|
||||||
match anime {
|
seq.insert(idx, action)?;
|
||||||
AnimeAction::AsusAnimation {
|
|
||||||
file,
|
|
||||||
time: duration,
|
|
||||||
brightness,
|
|
||||||
} => seq.add_asus_gif(&file, *duration, *brightness)?,
|
|
||||||
AnimeAction::ImageAnimation {
|
|
||||||
file,
|
|
||||||
scale,
|
|
||||||
angle,
|
|
||||||
translation,
|
|
||||||
time: duration,
|
|
||||||
brightness,
|
|
||||||
} => {
|
|
||||||
seq.add_image_gif(&file, *scale, *angle, *translation, *duration, *brightness)?
|
|
||||||
}
|
|
||||||
AnimeAction::Image {
|
|
||||||
file,
|
|
||||||
scale,
|
|
||||||
angle,
|
|
||||||
translation,
|
|
||||||
brightness,
|
|
||||||
} => seq.add_png(&file, *scale, *angle, *translation, *brightness)?,
|
|
||||||
AnimeAction::Pause(duration) => seq.add_pause(*duration)?,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(seq)
|
Ok(seq)
|
||||||
|
|||||||
68
daemon-user/src/zbus_anime.rs
Normal file
68
daemon-user/src/zbus_anime.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
//! # 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.
|
||||||
|
|
||||||
|
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<()>;
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ const ON_OFF: u8 = 0x04;
|
|||||||
|
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use rog_anime::{AniMeDataBuffer, AniMePacketType};
|
use rog_anime::{AniMeDataBuffer, AniMePacketType};
|
||||||
|
use rog_types::supported::AnimeSupportedFunctions;
|
||||||
use rusb::{Device, DeviceHandle};
|
use rusb::{Device, DeviceHandle};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -23,10 +24,6 @@ use zvariant::ObjectPath;
|
|||||||
|
|
||||||
use crate::GetSupported;
|
use crate::GetSupported;
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct AnimeSupportedFunctions(pub bool);
|
|
||||||
|
|
||||||
impl GetSupported for CtrlAnimeDisplay {
|
impl GetSupported for CtrlAnimeDisplay {
|
||||||
type A = AnimeSupportedFunctions;
|
type A = AnimeSupportedFunctions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{config::Config, error::RogError, GetSupported};
|
use crate::{config::Config, error::RogError, GetSupported};
|
||||||
//use crate::dbus::DbusEvents;
|
//use crate::dbus::DbusEvents;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use rog_types::supported::ChargeSupportedFunctions;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -12,11 +12,6 @@ use zvariant::ObjectPath;
|
|||||||
|
|
||||||
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct ChargeSupportedFunctions {
|
|
||||||
pub charge_level_set: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetSupported for CtrlCharge {
|
impl GetSupported for CtrlCharge {
|
||||||
type A = ChargeSupportedFunctions;
|
type A = ChargeSupportedFunctions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use crate::error::RogError;
|
use crate::error::RogError;
|
||||||
use crate::{config::Config, GetSupported};
|
use crate::{config::Config, GetSupported};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use rog_types::profile::{FanLevel, Profile, ProfileEvent};
|
use rog_types::{profile::{FanLevel, Profile, ProfileEvent}, supported::FanCpuSupportedFunctions};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -20,13 +19,6 @@ pub struct CtrlFanAndCpu {
|
|||||||
config: Arc<Mutex<Config>>,
|
config: Arc<Mutex<Config>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct FanCpuSupportedFunctions {
|
|
||||||
pub stock_fan_modes: bool,
|
|
||||||
pub min_max_freq: bool,
|
|
||||||
pub fan_curve_set: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetSupported for CtrlFanAndCpu {
|
impl GetSupported for CtrlFanAndCpu {
|
||||||
type A = FanCpuSupportedFunctions;
|
type A = FanCpuSupportedFunctions;
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ use crate::{
|
|||||||
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
|
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
|
||||||
};
|
};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use rog_types::{
|
use rog_types::{LED_MSG_LEN, aura_modes::{AuraEffect, AuraModeNum, LedBrightness}, supported::LedSupportedFunctions};
|
||||||
aura_modes::{AuraEffect, AuraModeNum, LedBrightness},
|
|
||||||
LED_MSG_LEN,
|
|
||||||
};
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -24,15 +21,6 @@ use zvariant::ObjectPath;
|
|||||||
|
|
||||||
use crate::GetSupported;
|
use crate::GetSupported;
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct LedSupportedFunctions {
|
|
||||||
pub brightness_set: bool,
|
|
||||||
pub stock_led_modes: Option<Vec<AuraModeNum>>,
|
|
||||||
pub multizone_led_mode: bool,
|
|
||||||
pub per_key_led_mode: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetSupported for CtrlKbdBacklight {
|
impl GetSupported for CtrlKbdBacklight {
|
||||||
type A = LedSupportedFunctions;
|
type A = LedSupportedFunctions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{config::Config, error::RogError, GetSupported};
|
use crate::{config::Config, error::RogError, GetSupported};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use rog_types::supported::RogBiosSupportedFunctions;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
@@ -23,12 +23,6 @@ pub struct CtrlRogBios {
|
|||||||
_config: Arc<Mutex<Config>>,
|
_config: Arc<Mutex<Config>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct RogBiosSupportedFunctions {
|
|
||||||
pub post_sound_toggle: bool,
|
|
||||||
pub dedicated_gfx_toggle: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetSupported for CtrlRogBios {
|
impl GetSupported for CtrlRogBios {
|
||||||
type A = RogBiosSupportedFunctions;
|
type A = RogBiosSupportedFunctions;
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ use zbus::dbus_interface;
|
|||||||
use zvariant::ObjectPath;
|
use zvariant::ObjectPath;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ctrl_anime::{AnimeSupportedFunctions, CtrlAnimeDisplay},
|
ctrl_anime::CtrlAnimeDisplay, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCpu,
|
||||||
ctrl_charge::{ChargeSupportedFunctions, CtrlCharge},
|
ctrl_leds::CtrlKbdBacklight, ctrl_rog_bios::CtrlRogBios, GetSupported,
|
||||||
ctrl_fan_cpu::{CtrlFanAndCpu, FanCpuSupportedFunctions},
|
};
|
||||||
ctrl_leds::{CtrlKbdBacklight, LedSupportedFunctions},
|
|
||||||
ctrl_rog_bios::{CtrlRogBios, RogBiosSupportedFunctions},
|
use rog_types::supported::{
|
||||||
GetSupported,
|
AnimeSupportedFunctions, ChargeSupportedFunctions, FanCpuSupportedFunctions,
|
||||||
|
LedSupportedFunctions, RogBiosSupportedFunctions,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ gif = "^0.11.2"
|
|||||||
|
|
||||||
serde = "^1.0"
|
serde = "^1.0"
|
||||||
serde_derive = "^1.0"
|
serde_derive = "^1.0"
|
||||||
|
zbus = "^1.9.1"
|
||||||
zvariant = "^2.5"
|
zvariant = "^2.5"
|
||||||
zvariant_derive = "^2.5"
|
zvariant_derive = "^2.5"
|
||||||
|
|
||||||
glam = { version = "*", features = ["serde"] }
|
glam = { version = "*", features = ["serde"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["zbus"]
|
default = ["z"]
|
||||||
zbus = []
|
z = []
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
#[cfg(feature = "zbus")]
|
#[cfg(feature = "z")]
|
||||||
use zvariant_derive::Type;
|
use zvariant_derive::Type;
|
||||||
|
|
||||||
/// The first 7 bytes of a USB packet are accounted for by `USB_PREFIX1` and `USB_PREFIX2`
|
/// The first 7 bytes of a USB packet are accounted for by `USB_PREFIX1` and `USB_PREFIX2`
|
||||||
@@ -16,7 +16,7 @@ const USB_PREFIX2: [u8; 7] = [0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02];
|
|||||||
|
|
||||||
/// The minimal serializable data that can be transferred over wire types.
|
/// The minimal serializable data that can be transferred over wire types.
|
||||||
/// Other data structures in `rog_anime` will convert to this.
|
/// Other data structures in `rog_anime` will convert to this.
|
||||||
#[cfg_attr(feature = "zbus", derive(Type))]
|
#[cfg_attr(feature = "z", derive(Type))]
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct AniMeDataBuffer(Vec<u8>);
|
pub struct AniMeDataBuffer(Vec<u8>);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ use png_pong::decode::Error as PngError;
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
#[cfg(feature = "z")]
|
||||||
|
use zbus::fdo;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AnimeError {
|
pub enum AnimeError {
|
||||||
NoFrames,
|
NoFrames,
|
||||||
@@ -12,6 +15,8 @@ pub enum AnimeError {
|
|||||||
Format,
|
Format,
|
||||||
/// The input was incorrect size, expected size is `IncorrectSize(width, height)`
|
/// The input was incorrect size, expected size is `IncorrectSize(width, height)`
|
||||||
IncorrectSize(u32, u32),
|
IncorrectSize(u32, u32),
|
||||||
|
#[cfg(feature = "z")]
|
||||||
|
Zbus(fdo::Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for AnimeError {
|
impl fmt::Display for AnimeError {
|
||||||
@@ -28,6 +33,8 @@ impl fmt::Display for AnimeError {
|
|||||||
"The input image size is incorrect, expected {}x{}",
|
"The input image size is incorrect, expected {}x{}",
|
||||||
width, height
|
width, height
|
||||||
),
|
),
|
||||||
|
#[cfg(feature = "z")]
|
||||||
|
AnimeError::Zbus(e) => write!(f, "ZBUS error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,3 +58,10 @@ impl From<DecodingError> for AnimeError {
|
|||||||
AnimeError::Gif(err)
|
AnimeError::Gif(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "z")]
|
||||||
|
impl From<AnimeError> for fdo::Error {
|
||||||
|
fn from(err: AnimeError) -> Self {
|
||||||
|
fdo::Error::Failed(format!("{}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ impl AniMeFrame {
|
|||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
|
||||||
pub enum AnimTime {
|
pub enum AnimTime {
|
||||||
|
/// Time in milliseconds for animation to run
|
||||||
Time(Duration),
|
Time(Duration),
|
||||||
Cycles(u32),
|
Cycles(u32),
|
||||||
Infinite,
|
Infinite,
|
||||||
@@ -35,7 +36,6 @@ impl Default for AnimTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A gif animation. This is a collection of frames from the gif, and a duration
|
/// A gif animation. This is a collection of frames from the gif, and a duration
|
||||||
/// that the animation should be shown for.
|
/// that the animation should be shown for.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
|||||||
@@ -1,13 +1,44 @@
|
|||||||
use std::{path::Path, time::Duration};
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{AniMeDataBuffer, AniMeGif, AniMeImage, AnimTime, error::AnimeError};
|
use crate::{error::AnimeError, AniMeDataBuffer, AniMeGif, AniMeImage, AnimTime};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub enum AnimeAction {
|
||||||
|
/// Full gif sequence. Immutable.
|
||||||
|
AsusAnimation {
|
||||||
|
file: PathBuf,
|
||||||
|
time: AnimTime,
|
||||||
|
brightness: f32,
|
||||||
|
},
|
||||||
|
/// Basic image, can have properties changed
|
||||||
|
ImageAnimation {
|
||||||
|
file: PathBuf,
|
||||||
|
scale: f32,
|
||||||
|
angle: f32,
|
||||||
|
translation: Vec2,
|
||||||
|
time: AnimTime,
|
||||||
|
brightness: f32,
|
||||||
|
},
|
||||||
|
Image {
|
||||||
|
file: PathBuf,
|
||||||
|
scale: f32,
|
||||||
|
angle: f32,
|
||||||
|
translation: Vec2,
|
||||||
|
brightness: f32,
|
||||||
|
},
|
||||||
|
/// A pause to be used between sequences
|
||||||
|
Pause(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub enum Action {
|
pub enum ActionData {
|
||||||
/// Full gif sequence. Immutable.
|
/// Full gif sequence. Immutable.
|
||||||
Animation(AniMeGif),
|
Animation(AniMeGif),
|
||||||
/// Basic image, can have properties changed and image updated via those properties
|
/// Basic image, can have properties changed and image updated via those properties
|
||||||
@@ -26,40 +57,92 @@ pub enum Action {
|
|||||||
|
|
||||||
/// An optimised precomputed set of actions
|
/// An optimised precomputed set of actions
|
||||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||||
pub struct Sequences(Vec<Action>);
|
pub struct Sequences(Vec<ActionData>);
|
||||||
|
|
||||||
impl Sequences {
|
impl Sequences {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Vec::new())
|
Self(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_asus_gif(
|
pub fn insert(&mut self, index: usize, action: &AnimeAction) -> Result<(), AnimeError> {
|
||||||
|
match action {
|
||||||
|
AnimeAction::AsusAnimation {
|
||||||
|
file,
|
||||||
|
time: duration,
|
||||||
|
brightness,
|
||||||
|
} => self.insert_asus_gif(index, &file, *duration, *brightness)?,
|
||||||
|
AnimeAction::ImageAnimation {
|
||||||
|
file,
|
||||||
|
scale,
|
||||||
|
angle,
|
||||||
|
translation,
|
||||||
|
time: duration,
|
||||||
|
brightness,
|
||||||
|
} => self.insert_image_gif(
|
||||||
|
index,
|
||||||
|
&file,
|
||||||
|
*scale,
|
||||||
|
*angle,
|
||||||
|
*translation,
|
||||||
|
*duration,
|
||||||
|
*brightness,
|
||||||
|
)?,
|
||||||
|
AnimeAction::Image {
|
||||||
|
file,
|
||||||
|
scale,
|
||||||
|
angle,
|
||||||
|
translation,
|
||||||
|
brightness,
|
||||||
|
} => self.insert_png(index, &file, *scale, *angle, *translation, *brightness)?,
|
||||||
|
AnimeAction::Pause(duration) => self.insert_pause(index, *duration),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_item(&mut self, index: usize) -> Option<ActionData> {
|
||||||
|
if index < self.0.len() {
|
||||||
|
return Some(self.0.remove(index));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_asus_gif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
mut index: usize,
|
||||||
file: &Path,
|
file: &Path,
|
||||||
duration: AnimTime,
|
duration: AnimTime,
|
||||||
brightness: f32,
|
brightness: f32,
|
||||||
) -> Result<(), AnimeError> {
|
) -> Result<(), AnimeError> {
|
||||||
|
if index > self.0.len() {
|
||||||
|
index = self.0.len() - 1;
|
||||||
|
}
|
||||||
let frames = AniMeGif::create_diagonal_gif(file, duration, brightness)?;
|
let frames = AniMeGif::create_diagonal_gif(file, duration, brightness)?;
|
||||||
self.0.push(Action::Animation(frames));
|
self.0.insert(index, ActionData::Animation(frames));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_png(
|
fn insert_png(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
mut index: usize,
|
||||||
file: &Path,
|
file: &Path,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
angle: f32,
|
angle: f32,
|
||||||
translation: Vec2,
|
translation: Vec2,
|
||||||
brightness: f32,
|
brightness: f32,
|
||||||
) -> Result<(), AnimeError> {
|
) -> Result<(), AnimeError> {
|
||||||
|
if index > self.0.len() {
|
||||||
|
index = self.0.len() - 1;
|
||||||
|
}
|
||||||
let image = AniMeImage::from_png(file, scale, angle, translation, brightness)?;
|
let image = AniMeImage::from_png(file, scale, angle, translation, brightness)?;
|
||||||
let data = <AniMeDataBuffer>::from(&image);
|
let data = <AniMeDataBuffer>::from(&image);
|
||||||
self.0.push(Action::Image(Box::new(data)));
|
self.0.insert(index, ActionData::Image(Box::new(data)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_image_gif(
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn insert_image_gif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
mut index: usize,
|
||||||
file: &Path,
|
file: &Path,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
angle: f32,
|
angle: f32,
|
||||||
@@ -67,15 +150,20 @@ impl Sequences {
|
|||||||
duration: AnimTime,
|
duration: AnimTime,
|
||||||
brightness: f32,
|
brightness: f32,
|
||||||
) -> Result<(), AnimeError> {
|
) -> Result<(), AnimeError> {
|
||||||
|
if index > self.0.len() {
|
||||||
|
index = self.0.len() - 1;
|
||||||
|
}
|
||||||
let frames =
|
let frames =
|
||||||
AniMeGif::create_png_gif(file, scale, angle, translation, duration, brightness)?;
|
AniMeGif::create_png_gif(file, scale, angle, translation, duration, brightness)?;
|
||||||
self.0.push(Action::Animation(frames));
|
self.0.insert(index, ActionData::Animation(frames));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_pause(&mut self, duration: Duration) -> Result<(), AnimeError> {
|
fn insert_pause(&mut self, mut index: usize, duration: Duration) {
|
||||||
self.0.push(Action::Pause(duration));
|
if index > self.0.len() {
|
||||||
Ok(())
|
index = self.0.len() - 1;
|
||||||
|
}
|
||||||
|
self.0.insert(index, ActionData::Pause(duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> ActionIterator {
|
pub fn iter(&self) -> ActionIterator {
|
||||||
@@ -92,9 +180,9 @@ pub struct ActionIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for ActionIterator<'a> {
|
impl<'a> Iterator for ActionIterator<'a> {
|
||||||
type Item = &'a Action;
|
type Item = &'a ActionData;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<&'a Action> {
|
fn next(&mut self) -> Option<&'a ActionData> {
|
||||||
if self.next_idx == self.actions.0.len() {
|
if self.next_idx == self.actions.0.len() {
|
||||||
self.next_idx = 0;
|
self.next_idx = 0;
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ gumdrop = "^0.8"
|
|||||||
rog_fan_curve = { version = "^0.1", features = ["serde"] }
|
rog_fan_curve = { version = "^0.1", features = ["serde"] }
|
||||||
serde = "^1.0"
|
serde = "^1.0"
|
||||||
serde_derive = "^1.0"
|
serde_derive = "^1.0"
|
||||||
zvariant = "^2.5"
|
zvariant = "^2.6"
|
||||||
zvariant_derive = "^2.5"
|
zvariant_derive = "^2.6"
|
||||||
@@ -16,6 +16,8 @@ pub mod aura_perkey;
|
|||||||
|
|
||||||
pub mod gfx_vendors;
|
pub mod gfx_vendors;
|
||||||
|
|
||||||
|
pub mod supported;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|||||||
41
rog-types/src/supported.rs
Normal file
41
rog-types/src/supported.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::aura_modes::AuraModeNum;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SupportedFunctions {
|
||||||
|
pub anime_ctrl: AnimeSupportedFunctions,
|
||||||
|
pub charge_ctrl: ChargeSupportedFunctions,
|
||||||
|
pub fan_cpu_ctrl: FanCpuSupportedFunctions,
|
||||||
|
pub keyboard_led: LedSupportedFunctions,
|
||||||
|
pub rog_bios_ctrl: RogBiosSupportedFunctions,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct AnimeSupportedFunctions(pub bool);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ChargeSupportedFunctions {
|
||||||
|
pub charge_level_set: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct FanCpuSupportedFunctions {
|
||||||
|
pub stock_fan_modes: bool,
|
||||||
|
pub min_max_freq: bool,
|
||||||
|
pub fan_curve_set: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct LedSupportedFunctions {
|
||||||
|
pub brightness_set: bool,
|
||||||
|
pub stock_led_modes: Option<Vec<AuraModeNum>>,
|
||||||
|
pub multizone_led_mode: bool,
|
||||||
|
pub per_key_led_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RogBiosSupportedFunctions {
|
||||||
|
pub post_sound_toggle: bool,
|
||||||
|
pub dedicated_gfx_toggle: bool,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user