anime: initial system config work

This commit is contained in:
Luke D Jones
2021-04-12 12:51:34 +12:00
parent 64d99a3e05
commit 8a6d364304
32 changed files with 624 additions and 226 deletions

View File

@@ -1,5 +1,5 @@
use rog_dbus::AuraDbusClient;
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout}; use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_dbus::AuraDbusClient;
use std::collections::LinkedList; use std::collections::LinkedList;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@@ -1,5 +1,5 @@
use rog_dbus::AuraDbusClient;
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout}; use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
use rog_dbus::AuraDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?; let (dbus, _) = AuraDbusClient::new()?;

View File

@@ -1,5 +1,5 @@
use rog_dbus::AuraDbusClient;
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout}; use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
use rog_dbus::AuraDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?; let (dbus, _) = AuraDbusClient::new()?;

View File

@@ -1,5 +1,5 @@
use rog_dbus::AuraDbusClient;
use rog_aura::{Key, KeyColourArray}; use rog_aura::{Key, KeyColourArray};
use rog_dbus::AuraDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?; let (dbus, _) = AuraDbusClient::new()?;

View File

@@ -1,5 +1,5 @@
use rog_dbus::AuraDbusClient;
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout}; use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
use rog_dbus::AuraDbusClient;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let (dbus, _) = AuraDbusClient::new()?; let (dbus, _) = AuraDbusClient::new()?;

View File

@@ -1,5 +1,5 @@
use gumdrop::Options; use gumdrop::Options;
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed, error::Error}; use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed};
use std::str::FromStr; use std::str::FromStr;
#[derive(Options)] #[derive(Options)]

View File

@@ -5,8 +5,8 @@ use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
use anime_cli::{AnimeActions, AnimeCommand}; use anime_cli::{AnimeActions, AnimeCommand};
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_aura::{self, AuraEffect, AuraModeNum}; use rog_aura::{self, AuraEffect, AuraModeNum};
use rog_dbus::AuraDbusClient;
use rog_types::{ use rog_types::{
gfx_vendors::GfxVendors, gfx_vendors::GfxVendors,
profile::{FanLevel, ProfileCommand, ProfileEvent}, profile::{FanLevel, ProfileCommand, ProfileEvent},

View File

@@ -147,7 +147,6 @@ impl<'a> CtrlAnime<'static> {
} }
} }
// The pattern for a zbus method is: // The pattern for a zbus method is:
// - Get config lock if required // - Get config lock if required
// - Set inner_early_return to stop the inner run loop temporarily // - Set inner_early_return to stop the inner run loop temporarily

View File

@@ -6,4 +6,4 @@ pub mod ctrl_anime;
pub mod zbus_anime; pub mod zbus_anime;
pub static DBUS_NAME: &str = "org.asuslinux.Daemon"; pub static DBUS_NAME: &str = "org.asuslinux.Daemon";

View File

@@ -101,7 +101,11 @@ impl UserConfig {
path.push("rog-user.cfg"); path.push("rog-user.cfg");
let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(&path)?; let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let json = serde_json::to_string_pretty(&self).unwrap(); let json = serde_json::to_string_pretty(&self).unwrap();
dbg!(&json); dbg!(&json);

131
daemon/src/config_anime.rs Normal file
View File

@@ -0,0 +1,131 @@
use log::{error, warn};
use rog_anime::{error::AnimeError, ActionData, AnimTime, AnimeAction, Vec2};
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::time::Duration;
pub static ANIME_CONFIG_PATH: &str = "/etc/asusd/anime.conf";
pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf";
#[derive(Deserialize, Serialize, Default)]
pub struct AnimeConfigCached {
pub system: Option<ActionData>,
pub boot: Option<ActionData>,
pub suspend: Option<ActionData>,
pub shutdown: Option<ActionData>,
}
impl AnimeConfigCached {
pub fn init_from_config(&mut self, config: &AnimeConfig) -> Result<(), AnimeError> {
if let Some(ref sys) = config.system {
self.system = Some(ActionData::from_anime_action(sys)?)
}
if let Some(ref boot) = config.boot {
self.boot = Some(ActionData::from_anime_action(boot)?)
}
if let Some(ref suspend) = config.boot {
self.suspend = Some(ActionData::from_anime_action(suspend)?)
}
if let Some(ref shutdown) = config.boot {
self.shutdown = Some(ActionData::from_anime_action(shutdown)?)
}
Ok(())
}
}
/// Config for base system actions for the anime display
#[derive(Deserialize, Serialize)]
pub struct AnimeConfig {
pub system: Option<AnimeAction>,
pub boot: Option<AnimeAction>,
pub suspend: Option<AnimeAction>,
pub shutdown: Option<AnimeAction>,
}
impl Default for AnimeConfig {
fn default() -> Self {
AnimeConfig {
system: None,
boot: None,
suspend: None,
shutdown: None,
}
}
}
impl AnimeConfig {
/// `load` will attempt to read the config, and panic if the dir is missing
pub fn load() -> Self {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&ANIME_CONFIG_PATH)
.unwrap_or_else(|_| {
panic!(
"The file {} or directory /etc/asusd/ is missing",
ANIME_CONFIG_PATH
)
}); // okay to cause panic here
let mut buf = String::new();
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
return AnimeConfig::create_default(&mut file);
} else {
if let Ok(data) = serde_json::from_str(&buf) {
return data;
}
warn!("Could not deserialise {}", ANIME_CONFIG_PATH);
panic!("Please remove {} then restart asusd", ANIME_CONFIG_PATH);
}
}
AnimeConfig::create_default(&mut file)
}
fn create_default(file: &mut File) -> Self {
// create a default config here
let config = AnimeConfig {
system: None,
boot: Some(AnimeAction::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::Time(Duration::from_secs(5)),
}),
suspend: None,
shutdown: None,
};
// Should be okay to unwrap this as is since it is a Default
let json = serde_json::to_string(&config).unwrap();
file.write_all(json.as_bytes())
.unwrap_or_else(|_| panic!("Could not write {}", ANIME_CONFIG_PATH));
config
}
pub fn read(&mut self) {
let mut file = OpenOptions::new()
.read(true)
.open(&ANIME_CONFIG_PATH)
.unwrap_or_else(|err| panic!("Error reading {}: {}", ANIME_CONFIG_PATH, err));
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {}", ANIME_CONFIG_PATH);
} else {
let x: AnimeConfig = serde_json::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", ANIME_CONFIG_PATH));
*self = x;
}
}
}
pub fn write(&self) {
let mut file = File::create(ANIME_CONFIG_PATH).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
}

View File

@@ -147,7 +147,6 @@ impl AuraConfig {
} }
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct AuraMultiZone { pub struct AuraMultiZone {
static_: [AuraEffect; 4], static_: [AuraEffect; 4],
@@ -233,4 +232,4 @@ impl Default for AuraMultiZone {
], ],
} }
} }
} }

View File

@@ -1,4 +1,3 @@
use rog_aura::AuraEffect;
use rog_types::{gfx_vendors::GfxVendors, profile::Profile}; use rog_types::{gfx_vendors::GfxVendors, profile::Profile};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@@ -66,7 +65,6 @@ impl ConfigV324 {
} }
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ConfigV341 { pub struct ConfigV341 {
pub gfx_mode: GfxVendors, pub gfx_mode: GfxVendors,
@@ -94,4 +92,4 @@ impl ConfigV341 {
power_profiles: self.power_profiles, power_profiles: self.power_profiles,
} }
} }
} }

View File

@@ -4,66 +4,49 @@ use rog_anime::{
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID, pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
VENDOR_ID, VENDOR_ID,
}, },
AnimeDataBuffer, AnimePacketType, ActionData, AnimTime, AnimeDataBuffer, AnimePacketType, ANIME_DATA_LEN,
}; };
use rog_types::supported::AnimeSupportedFunctions; use rog_types::supported::AnimeSupportedFunctions;
use rusb::{Device, DeviceHandle}; use rusb::{Device, DeviceHandle};
use std::error::Error; use std::{
use std::time::Duration; error::Error,
sync::{Arc, Mutex},
thread::sleep,
time::Instant,
};
use std::{sync::atomic::AtomicBool, time::Duration};
use zbus::dbus_interface; use zbus::dbus_interface;
use zvariant::ObjectPath; use zvariant::ObjectPath;
use crate::GetSupported; use crate::{
config_anime::{AnimeConfig, AnimeConfigCached},
error::RogError,
GetSupported,
};
impl GetSupported for CtrlAnimeDisplay { impl GetSupported for CtrlAnime {
type A = AnimeSupportedFunctions; type A = AnimeSupportedFunctions;
fn get_supported() -> Self::A { fn get_supported() -> Self::A {
AnimeSupportedFunctions(CtrlAnimeDisplay::get_device(VENDOR_ID, PROD_ID).is_ok()) AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok())
} }
} }
pub struct CtrlAnimeDisplay { pub struct CtrlAnime {
handle: DeviceHandle<rusb::GlobalContext>, handle: DeviceHandle<rusb::GlobalContext>,
cache: AnimeConfigCached,
_config: AnimeConfig,
// set to force thread to exit
thread_exit: AtomicBool,
// Set to false when the thread exits
thread_running: AtomicBool,
} }
impl crate::ZbusAdd for CtrlAnimeDisplay { impl CtrlAnime {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeDisplay {
/// Writes a data stream of length
fn write(&self, input: AnimeDataBuffer) {
self.write_data_buffer(input);
}
fn set_on_off(&self, status: bool) {
self.write_bytes(&pkt_for_set_on(status));
}
fn set_boot_on_off(&self, on: bool) {
self.write_bytes(&pkt_for_set_boot(on));
self.write_bytes(&pkt_for_apply());
}
}
impl CtrlAnimeDisplay {
#[inline] #[inline]
pub fn new() -> Result<CtrlAnimeDisplay, Box<dyn Error>> { pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
// We don't expect this ID to ever change // We don't expect this ID to ever change
let device = CtrlAnimeDisplay::get_device(0x0b05, 0x193b)?; let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
let mut device = device.open()?; let mut device = device.open()?;
device.reset()?; device.reset()?;
@@ -79,7 +62,16 @@ impl CtrlAnimeDisplay {
})?; })?;
info!("Device has an AniMe Matrix display"); info!("Device has an AniMe Matrix display");
let ctrl = CtrlAnimeDisplay { handle: device }; let mut cache = AnimeConfigCached::default();
cache.init_from_config(&config)?;
let ctrl = CtrlAnime {
handle: device,
cache,
_config: config,
thread_exit: AtomicBool::new(false),
thread_running: AtomicBool::new(false),
};
ctrl.do_initialization(); ctrl.do_initialization();
Ok(ctrl) Ok(ctrl)
@@ -95,6 +87,108 @@ impl CtrlAnimeDisplay {
Err(rusb::Error::NoDevice) Err(rusb::Error::NoDevice)
} }
// DOUBLE THREAD NEST!
fn run_thread(inner: Arc<Mutex<CtrlAnime>>, action: Option<ActionData>, mut once: bool) {
std::thread::Builder::new()
.name("AniMe system thread start".into())
.spawn(move || {
// Make the loop exit first
loop {
if let Ok(lock) = inner.try_lock() {
lock.thread_exit
.store(true, std::sync::atomic::Ordering::SeqCst);
break;
}
}
loop {
if let Ok(lock) = inner.try_lock() {
if !lock
.thread_running
.load(std::sync::atomic::Ordering::SeqCst)
{
lock.thread_exit
.store(false, std::sync::atomic::Ordering::SeqCst);
info!("AniMe system thread exited");
break;
}
}
}
std::thread::Builder::new()
.name("AniMe system actions".into())
.spawn(move || {
info!("AniMe system thread started");
'main: loop {
if let Ok(lock) = inner.try_lock() {
if !once
&& lock.thread_exit.load(std::sync::atomic::Ordering::SeqCst)
{
break 'main;
}
if let Some(ref action) = action {
match action {
ActionData::Animation(frames) => {
let mut count = 0;
let start = Instant::now();
'animation: loop {
for frame in frames.frames() {
lock.write_data_buffer(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;
}
}
}
}
ActionData::Image(image) => {
once = false;
lock.write_data_buffer(image.as_ref().clone())
}
ActionData::Pause(_) => {}
ActionData::AudioEq => {}
ActionData::SystemInfo => {}
ActionData::TimeDate => {}
ActionData::Matrix => {}
}
} else {
break 'main;
}
if once {
let data =
AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec());
lock.write_data_buffer(data);
break 'main;
}
}
}
'exit: loop {
if let Ok(lock) = inner.try_lock() {
lock.thread_exit
.store(false, std::sync::atomic::Ordering::SeqCst);
lock.thread_running
.store(false, std::sync::atomic::Ordering::SeqCst);
break 'exit;
}
}
})
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
})
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
}
fn write_bytes(&self, message: &[u8]) { fn write_bytes(&self, message: &[u8]) {
match self.handle.write_control( match self.handle.write_control(
0x21, // request_type 0x21, // request_type
@@ -126,3 +220,89 @@ impl CtrlAnimeDisplay {
self.write_bytes(&pkts[1]); self.write_bytes(&pkts[1]);
} }
} }
pub struct CtrlAnimeTask(pub Arc<Mutex<CtrlAnime>>);
impl crate::CtrlTask for CtrlAnimeTask {
fn do_task(&self) -> Result<(), RogError> {
Ok(())
}
}
pub struct CtrlAnimeReloader(pub Arc<Mutex<CtrlAnime>>);
impl crate::Reloadable for CtrlAnimeReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(lock) = self.0.try_lock() {
let action = lock.cache.boot.clone();
CtrlAnime::run_thread(self.0.clone(), action, true);
}
Ok(())
}
}
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
/// The struct with the main dbus methods requires this trait
impl crate::ZbusAdd for CtrlAnimeZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
// 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
fn write(&self, input: AnimeDataBuffer) {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit
.store(true, std::sync::atomic::Ordering::SeqCst);
lock.write_data_buffer(input);
break 'outer;
}
}
}
fn set_on_off(&self, status: bool) {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_on(status));
break 'outer;
}
}
}
fn set_boot_on_off(&self, on: bool) {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.write_bytes(&pkt_for_set_boot(on));
lock.write_bytes(&pkt_for_apply());
break 'outer;
}
}
}
fn run_main_loop(&self, on: bool) {
'outer: loop {
if let Ok(lock) = self.0.try_lock() {
lock.thread_exit
.store(on, std::sync::atomic::Ordering::SeqCst);
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
break 'outer;
}
}
}
}

View File

@@ -1,7 +1,10 @@
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}, supported::FanCpuSupportedFunctions}; use rog_types::{
profile::{FanLevel, Profile, ProfileEvent},
supported::FanCpuSupportedFunctions,
};
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
@@ -31,18 +34,18 @@ impl GetSupported for CtrlFanAndCpu {
} }
} }
pub struct DbusFanAndCpu { pub struct FanAndCpuZbus {
inner: Arc<Mutex<CtrlFanAndCpu>>, inner: Arc<Mutex<CtrlFanAndCpu>>,
} }
impl DbusFanAndCpu { impl FanAndCpuZbus {
pub fn new(inner: Arc<Mutex<CtrlFanAndCpu>>) -> Self { pub fn new(inner: Arc<Mutex<CtrlFanAndCpu>>) -> Self {
Self { inner } Self { inner }
} }
} }
#[dbus_interface(name = "org.asuslinux.Daemon")] #[dbus_interface(name = "org.asuslinux.Daemon")]
impl DbusFanAndCpu { impl FanAndCpuZbus {
/// Set profile details /// Set profile details
fn set_profile(&self, profile: String) { fn set_profile(&self, profile: String) {
if let Ok(event) = serde_json::from_str(&profile) { if let Ok(event) = serde_json::from_str(&profile) {
@@ -170,7 +173,7 @@ impl DbusFanAndCpu {
fn notify_profile(&self, profile: &str) -> zbus::Result<()> {} fn notify_profile(&self, profile: &str) -> zbus::Result<()> {}
} }
impl crate::ZbusAdd for DbusFanAndCpu { impl crate::ZbusAdd for FanAndCpuZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) { fn add_to_server(self, server: &mut zbus::ObjectServer) {
server server
.at( .at(

View File

@@ -7,7 +7,10 @@ use crate::{
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES}, laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
}; };
use log::{error, info, warn}; use log::{error, info, warn};
use rog_aura::{AuraEffect, AuraModeNum, LED_MSG_LEN, LedBrightness, usb::{LED_APPLY, LED_SET}}; use rog_aura::{
usb::{LED_APPLY, LED_SET},
AuraEffect, LedBrightness, LED_MSG_LEN,
};
use rog_types::supported::LedSupportedFunctions; use rog_types::supported::LedSupportedFunctions;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::{Read, Write}; use std::io::{Read, Write};
@@ -50,6 +53,37 @@ pub struct CtrlKbdBacklight {
config: AuraConfig, config: AuraConfig,
} }
pub struct CtrlKbdBacklightTask(pub Arc<Mutex<CtrlKbdBacklight>>);
impl crate::CtrlTask for CtrlKbdBacklightTask {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(mut lock) = self.0.try_lock() {
let mut file = OpenOptions::new()
.read(true)
.open(&lock.bright_node)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&lock.bright_node).into(), err)
}
_ => RogError::Path((&lock.bright_node).into(), err),
})?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read("buffer".into(), err))?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if lock.config.brightness != num.into() {
lock.config.read();
lock.config.brightness = num.into();
lock.config.write();
}
return Ok(());
}
return Err(RogError::ParseLed);
}
Ok(())
}
}
pub struct DbusKbdBacklight { pub struct DbusKbdBacklight {
inner: Arc<Mutex<CtrlKbdBacklight>>, inner: Arc<Mutex<CtrlKbdBacklight>>,
} }
@@ -166,73 +200,6 @@ impl DbusKbdBacklight {
fn notify_led(&self, data: &str) -> zbus::Result<()>; fn notify_led(&self, data: &str) -> zbus::Result<()>;
} }
impl crate::Reloadable for CtrlKbdBacklight {
fn reload(&mut self) -> Result<(), RogError> {
// set current mode (if any)
if self.supported_modes.standard.len() > 1 {
let current_mode = self.config.current_mode;
if self.supported_modes.standard.contains(&(current_mode)) {
let mode = self
.config
.builtins
.get(&current_mode)
.ok_or(RogError::NotSupported)?
.to_owned();
self.write_mode(&mode)?;
info!("Reloaded last used mode");
} else {
warn!(
"An unsupported mode was set: {}, reset to first mode available",
<&str>::from(&self.config.current_mode)
);
self.config.builtins.remove(&current_mode);
self.config.current_mode = AuraModeNum::Static;
// TODO: do a recursive call with a boxed dyn future later
let mode = self
.config
.builtins
.get(&current_mode)
.ok_or(RogError::NotSupported)?
.to_owned();
self.write_mode(&mode)?;
info!("Reloaded last used mode");
}
}
// Reload brightness
let bright = self.config.brightness;
self.set_brightness(bright)?;
info!("Reloaded last used brightness");
Ok(())
}
}
impl crate::CtrlTask for CtrlKbdBacklight {
fn do_task(&mut self) -> Result<(), RogError> {
let mut file = OpenOptions::new()
.read(true)
.open(&self.bright_node)
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
}
_ => RogError::Path((&self.bright_node).into(), err),
})?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)
.map_err(|err| RogError::Read("buffer".into(), err))?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if self.config.brightness != num.into() {
self.config.read();
self.config.brightness = num.into();
self.config.write();
}
return Ok(());
}
Err(RogError::ParseLed)
}
}
impl CtrlKbdBacklight { impl CtrlKbdBacklight {
#[inline] #[inline]
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> { pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {

View File

@@ -4,7 +4,7 @@ use zbus::dbus_interface;
use zvariant::ObjectPath; use zvariant::ObjectPath;
use crate::{ use crate::{
ctrl_anime::CtrlAnimeDisplay, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCpu, ctrl_anime::CtrlAnime, ctrl_charge::CtrlCharge, ctrl_fan_cpu::CtrlFanAndCpu,
ctrl_leds::CtrlKbdBacklight, ctrl_rog_bios::CtrlRogBios, GetSupported, ctrl_leds::CtrlKbdBacklight, ctrl_rog_bios::CtrlRogBios, GetSupported,
}; };
@@ -50,7 +50,7 @@ impl GetSupported for SupportedFunctions {
fn get_supported() -> Self::A { fn get_supported() -> Self::A {
SupportedFunctions { SupportedFunctions {
keyboard_led: CtrlKbdBacklight::get_supported(), keyboard_led: CtrlKbdBacklight::get_supported(),
anime_ctrl: CtrlAnimeDisplay::get_supported(), anime_ctrl: CtrlAnime::get_supported(),
charge_ctrl: CtrlCharge::get_supported(), charge_ctrl: CtrlCharge::get_supported(),
fan_cpu_ctrl: CtrlFanAndCpu::get_supported(), fan_cpu_ctrl: CtrlFanAndCpu::get_supported(),
rog_bios_ctrl: CtrlRogBios::get_supported(), rog_bios_ctrl: CtrlRogBios::get_supported(),

View File

@@ -1,11 +1,11 @@
use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight}; use daemon::ctrl_leds::{CtrlKbdBacklight, CtrlKbdBacklightTask, DbusKbdBacklight};
use daemon::{ use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported, config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
}; };
use daemon::{config_aura::AuraConfig, ctrl_charge::CtrlCharge}; use daemon::{config_anime::AnimeConfig, config_aura::AuraConfig, ctrl_charge::CtrlCharge};
use daemon::{ctrl_anime::CtrlAnimeDisplay, ctrl_gfx::gfx::CtrlGraphics}; use daemon::{ctrl_anime::*, ctrl_gfx::gfx::CtrlGraphics};
use daemon::{ use daemon::{
ctrl_fan_cpu::{CtrlFanAndCpu, DbusFanAndCpu}, ctrl_fan_cpu::{CtrlFanAndCpu, FanAndCpuZbus},
laptops::LaptopLedData, laptops::LaptopLedData,
}; };
@@ -41,27 +41,24 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
// Timing is such that: /// The actual main loop for the daemon
// - interrupt write is minimum 1ms (sometimes lower)
// - read interrupt must timeout, minimum of 1ms
// - for a single usb packet, 2ms total.
// - to maintain constant times of 1ms, per-key colours should use
// the effect endpoint so that the complete colour block is written
// as fast as 1ms per row of the matrix inside it. (10ms total time)
fn start_daemon() -> Result<(), Box<dyn Error>> { fn start_daemon() -> Result<(), Box<dyn Error>> {
let supported = SupportedFunctions::get_supported(); let supported = SupportedFunctions::get_supported();
print_board_info(); print_board_info();
println!("{}", serde_json::to_string_pretty(&supported).unwrap()); println!("{}", serde_json::to_string_pretty(&supported).unwrap());
let config = Config::load(); // Collect tasks for task thread
let enable_gfx_switching = config.gfx_managed; let mut tasks: Vec<Box<dyn CtrlTask + Send>> = Vec::new();
let config = Arc::new(Mutex::new(config)); // Start zbus server
let connection = Connection::new_system()?; let connection = Connection::new_system()?;
fdo::DBusProxy::new(&connection)? fdo::DBusProxy::new(&connection)?
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?; .request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
let mut object_server = zbus::ObjectServer::new(&connection); let mut object_server = zbus::ObjectServer::new(&connection);
let config = Config::load();
let enable_gfx_switching = config.gfx_managed;
let config = Arc::new(Mutex::new(config));
supported.add_to_server(&mut object_server); supported.add_to_server(&mut object_server);
match CtrlRogBios::new(config.clone()) { match CtrlRogBios::new(config.clone()) {
@@ -90,15 +87,52 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
} }
} }
match CtrlAnimeDisplay::new() { match CtrlFanAndCpu::new(config.clone()) {
Ok(mut ctrl) => {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
FanAndCpuZbus::new(tmp).add_to_server(&mut object_server);
}
Err(err) => {
error!("Profile control: {}", err);
}
}
match CtrlAnime::new(AnimeConfig::load()) {
Ok(ctrl) => { Ok(ctrl) => {
ctrl.add_to_server(&mut object_server); let inner = Arc::new(Mutex::new(ctrl));
let mut reload = CtrlAnimeReloader(inner.clone());
reload
.reload()
.unwrap_or_else(|err| warn!("AniMe: {}", err));
let zbus = CtrlAnimeZbus(inner.clone());
zbus.add_to_server(&mut object_server);
tasks.push(Box::new(CtrlAnimeTask(inner)));
} }
Err(err) => { Err(err) => {
error!("AniMe control: {}", err); error!("AniMe control: {}", err);
} }
} }
let laptop = LaptopLedData::get_data();
let aura_config = AuraConfig::load(&laptop);
match CtrlKbdBacklight::new(laptop, aura_config) {
Ok(ctrl) => {
let tmp = Arc::new(Mutex::new(ctrl));
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
let task = CtrlKbdBacklightTask(tmp);
tasks.push(Box::new(task));
}
Err(err) => {
error!("Keyboard control: {}", err);
}
}
// Graphics switching requires some checks on boot specifically for g-sync capable laptops
if enable_gfx_switching { if enable_gfx_switching {
match CtrlGraphics::new(config.clone()) { match CtrlGraphics::new(config.clone()) {
Ok(mut ctrl) => { Ok(mut ctrl) => {
@@ -141,48 +175,24 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
} }
} }
// Collect tasks for task thread
let mut tasks: Vec<Arc<Mutex<dyn CtrlTask + Send>>> = Vec::new();
if let Ok(mut ctrl) = CtrlFanAndCpu::new(config).map_err(|err| {
error!("Profile control: {}", err);
}) {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl));
DbusFanAndCpu::new(tmp).add_to_server(&mut object_server);
};
let laptop = LaptopLedData::get_data();
let aura_config = AuraConfig::load(&laptop);
if let Ok(ctrl) = CtrlKbdBacklight::new(laptop, aura_config).map_err(|err| {
error!("Keyboard control: {}", err);
err
}) {
let tmp = Arc::new(Mutex::new(ctrl));
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
tasks.push(tmp);
}
// TODO: implement messaging between threads to check fails // TODO: implement messaging between threads to check fails
// These tasks generally read a sys path or file to check for a
// change // Run tasks
let handle = std::thread::Builder::new() let handle = std::thread::Builder::new()
.name("asusd watch".to_string()) .name("asusd watch".to_string())
.spawn(move || loop { .spawn(move || loop {
std::thread::sleep(std::time::Duration::from_millis(100)); std::thread::sleep(std::time::Duration::from_millis(100));
for ctrl in tasks.iter() { for ctrl in tasks.iter() {
if let Ok(mut lock) = ctrl.try_lock() { ctrl.do_task()
lock.do_task() .map_err(|err| {
.map_err(|err| { warn!("do_task error: {}", err);
warn!("do_task error: {}", err); })
}) .ok();
.ok();
}
} }
}); });
// Run zbus server
object_server object_server
.with( .with(
&ObjectPath::from_str_unchecked("/org/asuslinux/Charge"), &ObjectPath::from_str_unchecked("/org/asuslinux/Charge"),
@@ -196,6 +206,7 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
}) })
.ok(); .ok();
// Loop to check errors and iterate zbus server
loop { loop {
if let Err(err) = &handle { if let Err(err) = &handle {
error!("{}", err); error!("{}", err);

View File

@@ -1,6 +1,7 @@
#![deny(unused_must_use)] #![deny(unused_must_use)]
/// Configuration loading, saving /// Configuration loading, saving
pub mod config; pub mod config;
pub mod config_anime;
pub mod config_aura; pub mod config_aura;
pub(crate) mod config_old; pub(crate) mod config_old;
/// Control of AniMe matrix display /// Control of AniMe matrix display
@@ -48,7 +49,7 @@ pub trait ZbusAdd {
} }
pub trait CtrlTask { pub trait CtrlTask {
fn do_task(&mut self) -> Result<(), RogError>; fn do_task(&self) -> Result<(), RogError>;
} }
pub trait CtrlTaskComplex { pub trait CtrlTaskComplex {

89
design-patterns.md Normal file
View File

@@ -0,0 +1,89 @@
# Daemon
## Controller pattern
There are a series of traits in the daemon for use with controller objects. Not all traits are required:
- `Reloadable`, for controllers that need the ability to reload (typically on start)
- `ZbusAdd`, for controllers that have zbus derive. These need to run on the zbus server.
- `CtrlTask`, for controllers that need to run tasks every loop.
- `GetSupported`, see if the hardware/functions this controller requires are supported.
The first 3 trait objects get owned by the daemon methods that required them, which is why an `Arc<Mutex<T>>` is required.
Generally the actual controller object will need to live in its own world as its own struct.
Then for each trait that is required a new struct is required that can have the trait implemented, and that struct would have a reference to the main controller via `Arc<Mutex<T>>`.
### Example
Main controller:
```rust
pub struct CtrlAnime {
<things the controller requires>
}
impl CtrlAnime {
<functions the controller exposes>
}
```
The task trait:
```rust
pub struct CtrlAnimeTask(Arc<Mutex<CtrlAnime>>);
impl crate::CtrlTask for CtrlAnimeTask {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(lock) = self.inner.try_lock() {
<some action>
}
Ok(())
}
}
```
The reloader trait
```rust
pub struct CtrlAnimeReloader(Arc<Mutex<CtrlAnime>>);
impl crate::Reloadable for CtrlAnimeReloader {
fn reload(&mut self) -> Result<(), RogError> {
if let Ok(lock) = self.inner.try_lock() {
<some action>
}
Ok(())
}
}
```
The Zbus requirements:
```rust
pub struct CtrlAnimeZbus(Arc<Mutex<CtrlAnime>>);
impl crate::ZbusAdd for CtrlAnimeZbus {
fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
self,
)
.map_err(|err| {
warn!("CtrlAnimeDisplay: add_to_server {}", err);
err
})
.ok();
}
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlAnimeZbus {
fn <zbus method>() {
if let Ok(lock) = self.inner.try_lock() {
<some action>
}
}
}
```
The controller can then be added to the daemon parts as required.

View File

@@ -16,7 +16,7 @@ pub enum AnimeError {
/// 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 = "dbus")] #[cfg(feature = "dbus")]
Zbus(fdo::Error) Zbus(fdo::Error),
} }
impl fmt::Display for AnimeError { impl fmt::Display for AnimeError {
@@ -68,4 +68,4 @@ impl From<AnimeError> for fdo::Error {
fn from(err: AnimeError) -> Self { fn from(err: AnimeError) -> Self {
fdo::Error::Failed(format!("{}", err)) fdo::Error::Failed(format!("{}", err))
} }
} }

View File

@@ -30,4 +30,4 @@ pub use sequencer::*;
pub mod error; pub mod error;
/// Provides const methods to create the USB HID control packets /// Provides const methods to create the USB HID control packets
pub mod usb; pub mod usb;

View File

@@ -6,7 +6,7 @@ use std::{
use glam::Vec2; use glam::Vec2;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use crate::{error::AnimeError, AnimeDataBuffer, AnimeGif, AnimeImage, AnimTime}; use crate::{error::AnimeError, AnimTime, AnimeDataBuffer, AnimeGif, AnimeImage};
/// All the possible AniMe actions that can be used. This enum is intended to be /// All the possible AniMe actions that can be used. This enum is intended to be
/// a helper for loading up `ActionData`. /// a helper for loading up `ActionData`.
@@ -40,7 +40,7 @@ pub enum AnimeAction {
/// All the possible AniMe actions that can be used. The enum is intended to be /// All the possible AniMe actions that can be used. The enum is intended to be
/// used in a array allowing the user to cycle through a series of actions. /// used in a array allowing the user to cycle through a series of actions.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub enum ActionData { pub enum ActionData {
/// Full gif sequence. Immutable. /// Full gif sequence. Immutable.
Animation(AnimeGif), Animation(AnimeGif),
@@ -58,6 +58,50 @@ pub enum ActionData {
Matrix, Matrix,
} }
impl ActionData {
pub fn from_anime_action(action: &AnimeAction) -> Result<ActionData, AnimeError> {
let a = match action {
AnimeAction::AsusAnimation {
file,
time: duration,
brightness,
} => ActionData::Animation(AnimeGif::create_diagonal_gif(
&file,
*duration,
*brightness,
)?),
AnimeAction::ImageAnimation {
file,
scale,
angle,
translation,
time: duration,
brightness,
} => ActionData::Animation(AnimeGif::create_png_gif(
&file,
*scale,
*angle,
*translation,
*duration,
*brightness,
)?),
AnimeAction::Image {
file,
scale,
angle,
translation,
brightness,
} => {
let image = AnimeImage::from_png(&file, *scale, *angle, *translation, *brightness)?;
let data = <AnimeDataBuffer>::from(&image);
ActionData::Image(Box::new(data))
}
AnimeAction::Pause(duration) => ActionData::Pause(*duration),
};
Ok(a)
}
}
/// An optimised precomputed set of actions that the user can cycle through /// An optimised precomputed set of actions that the user can cycle through
#[derive(Debug, Deserialize, Serialize, Default)] #[derive(Debug, Deserialize, Serialize, Default)]
pub struct Sequences(Vec<ActionData>); pub struct Sequences(Vec<ActionData>);

View File

@@ -24,7 +24,7 @@ pub const fn pkts_for_init() -> [[u8; PACKET_SIZE]; 2] {
let mut count = 0; let mut count = 0;
while count < INIT_STR.len() { while count < INIT_STR.len() {
packets[0][count] = INIT_STR[count]; packets[0][count] = INIT_STR[count];
count +=1; count += 1;
} }
// //
packets[1][0] = DEV_PAGE; // write it to be sure? packets[1][0] = DEV_PAGE; // write it to be sure?

View File

@@ -9,7 +9,7 @@ use std::str::FromStr;
#[cfg(feature = "dbus")] #[cfg(feature = "dbus")]
use zvariant_derive::Type; use zvariant_derive::Type;
use crate::{LED_MSG_LEN, error::Error}; use crate::{error::Error, LED_MSG_LEN};
#[cfg_attr(feature = "dbus", derive(Type))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
@@ -121,9 +121,7 @@ impl FromStr for Direction {
/// Enum of modes that convert to the actual number required by a USB HID packet /// Enum of modes that convert to the actual number required by a USB HID packet
#[cfg_attr(feature = "dbus", derive(Type))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive( #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Deserialize, Serialize)]
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Deserialize, Serialize,
)]
pub enum AuraModeNum { pub enum AuraModeNum {
Static = 0, Static = 0,
Breathe = 1, Breathe = 1,

View File

@@ -23,4 +23,4 @@ impl fmt::Display for Error {
} }
} }
impl error::Error for Error {} impl error::Error for Error {}

View File

@@ -13,4 +13,4 @@ pub mod usb;
pub mod error; pub mod error;
pub const LED_MSG_LEN: usize = 17; pub const LED_MSG_LEN: usize = 17;

View File

@@ -33,7 +33,7 @@ impl KeyColourArray {
KeyColourArray(set) KeyColourArray(set)
} }
/// Initialise and clear the keyboard for custom effects, this must be done for /// Initialise and clear the keyboard for custom effects, this must be done for
/// every time mode switches from builtin to custom /// every time mode switches from builtin to custom
#[inline] #[inline]
pub const fn get_init_msg() -> [u8; 64] { pub const fn get_init_msg() -> [u8; 64] {

View File

@@ -11,7 +11,7 @@ use crate::error::Error;
/// used in a array allowing the user to cycle through a series of actions. /// used in a array allowing the user to cycle through a series of actions.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum ActionData { pub enum ActionData {
Static Static,
} }
/// An optimised precomputed set of actions that the user can cycle through /// An optimised precomputed set of actions that the user can cycle through

View File

@@ -13,4 +13,4 @@ pub const fn aura_brightness_bytes(brightness: u8) -> [u8; 17] {
[ [
0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5A, 0xBA, 0xC5, 0xC4, brightness, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
] ]
} }

View File

@@ -11,9 +11,7 @@ pub mod zbus_rogbios;
pub mod zbus_supported; pub mod zbus_supported;
use rog_aura::AuraEffect; use rog_aura::AuraEffect;
use rog_types::{ use rog_types::gfx_vendors::{GfxRequiredUserAction, GfxVendors};
gfx_vendors::{GfxRequiredUserAction, GfxVendors},
};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use zbus::{Connection, Result, SignalReceiver}; use zbus::{Connection, Result, SignalReceiver};

View File

@@ -17,28 +17,4 @@ impl fmt::Display for GraphicsError {
} }
} }
impl Error for GraphicsError {} impl Error for GraphicsError {}
#[derive(Debug)]
pub enum AnimeError {
InvalidBitmap,
Io(std::io::Error),
}
impl fmt::Display for AnimeError {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AnimeError::InvalidBitmap => write!(f, "Bitmap is invalid"),
AnimeError::Io(e) => write!(f, "Could not open: {}", e),
}
}
}
impl Error for AnimeError {}
impl From<std::io::Error> for AnimeError {
fn from(err: std::io::Error) -> Self {
AnimeError::Io(err)
}
}