mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Refactor: Make all Aura type devices use "device_manager"
Open the door to adding many other types of "aura" devices later.
This commit is contained in:
440
asusd/src/aura_laptop/config.rs
Normal file
440
asusd/src/aura_laptop/config.rs
Normal file
@@ -0,0 +1,440 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use log::{debug, info, warn};
|
||||
use rog_aura::aura_detection::LedSupportData;
|
||||
use rog_aura::keyboard::LaptopAuraPower;
|
||||
use rog_aura::{
|
||||
AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, Direction, LedBrightness, Speed, GRADIENT,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::RogError;
|
||||
|
||||
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
|
||||
// #[serde(default)]
|
||||
pub struct AuraConfig {
|
||||
#[serde(skip)]
|
||||
pub led_type: AuraDeviceType,
|
||||
#[serde(skip)]
|
||||
pub support_data: LedSupportData,
|
||||
pub config_name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub ally_fix: Option<bool>,
|
||||
pub brightness: LedBrightness,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub multizone: Option<BTreeMap<AuraModeNum, Vec<AuraEffect>>>,
|
||||
pub multizone_on: bool,
|
||||
pub enabled: LaptopAuraPower,
|
||||
#[serde(skip)]
|
||||
pub per_key_mode_active: bool,
|
||||
}
|
||||
|
||||
impl StdConfig for AuraConfig {
|
||||
/// Detect the keyboard type and load from default DB if data available
|
||||
fn new() -> Self {
|
||||
panic!("This should not be used");
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
if self.config_name.is_empty() {
|
||||
panic!("Config file name should not be empty");
|
||||
}
|
||||
self.config_name.to_owned()
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for AuraConfig {}
|
||||
|
||||
impl AuraConfig {
|
||||
/// Detect the keyboard type and load from default DB if data available
|
||||
pub fn new(prod_id: &str) -> Self {
|
||||
info!("Setting up AuraConfig for {prod_id:?}");
|
||||
// create a default config here
|
||||
let device_type = AuraDeviceType::from(prod_id);
|
||||
if device_type == AuraDeviceType::Unknown {
|
||||
warn!("idProduct:{prod_id:?} is unknown");
|
||||
}
|
||||
let support_data = LedSupportData::get_data(prod_id);
|
||||
let enabled = LaptopAuraPower::new(device_type, &support_data);
|
||||
let mut config = AuraConfig {
|
||||
led_type: device_type,
|
||||
support_data,
|
||||
config_name: format!("aura_{prod_id}.ron"),
|
||||
ally_fix: None,
|
||||
brightness: LedBrightness::Med,
|
||||
current_mode: AuraModeNum::Static,
|
||||
builtins: BTreeMap::new(),
|
||||
multizone: None,
|
||||
multizone_on: false,
|
||||
enabled,
|
||||
per_key_mode_active: false,
|
||||
};
|
||||
|
||||
for n in &config.support_data.basic_modes {
|
||||
debug!("creating default for {n}");
|
||||
config
|
||||
.builtins
|
||||
.insert(*n, AuraEffect::default_with_mode(*n));
|
||||
|
||||
if !config.support_data.basic_zones.is_empty() {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in config.support_data.basic_zones.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: *n,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
});
|
||||
}
|
||||
if let Some(m) = config.multizone.as_mut() {
|
||||
m.insert(*n, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*n, default);
|
||||
config.multizone = Some(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
/// Set the mode data, current mode, and if multizone enabled.
|
||||
///
|
||||
/// Multipurpose, will accept `AuraEffect` with zones and put in the correct
|
||||
/// store.
|
||||
pub fn set_builtin(&mut self, effect: AuraEffect) {
|
||||
self.current_mode = effect.mode;
|
||||
if effect.zone() == AuraZone::None {
|
||||
self.builtins.insert(*effect.mode(), effect);
|
||||
self.multizone_on = false;
|
||||
} else {
|
||||
if let Some(multi) = self.multizone.as_mut() {
|
||||
if let Some(fx) = multi.get_mut(effect.mode()) {
|
||||
for fx in fx.iter_mut() {
|
||||
if fx.zone == effect.zone {
|
||||
*fx = effect;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fx.push(effect);
|
||||
} else {
|
||||
multi.insert(*effect.mode(), vec![effect]);
|
||||
}
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*effect.mode(), vec![effect]);
|
||||
self.multizone = Some(tmp);
|
||||
}
|
||||
self.multizone_on = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect]> {
|
||||
if let Some(multi) = &self.multizone {
|
||||
return multi.get(&aura_type).map(|v| v.as_slice());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Create a default for the `current_mode` if multizone and no config
|
||||
/// exists.
|
||||
pub fn create_multizone_default(&mut self) -> Result<(), RogError> {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in self.support_data.basic_zones.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: self.current_mode,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
});
|
||||
}
|
||||
if default.is_empty() {
|
||||
return Err(RogError::AuraEffectNotSupported);
|
||||
}
|
||||
|
||||
if let Some(multizones) = self.multizone.as_mut() {
|
||||
multizones.insert(self.current_mode, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(self.current_mode, default);
|
||||
self.multizone = Some(tmp);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reload the config from disk then verify and update it if required.
|
||||
/// Always rewrites the file to disk.
|
||||
pub fn load_and_update_config(prod_id: &str) -> AuraConfig {
|
||||
// New loads data from the DB also
|
||||
let mut config_init = AuraConfig::new(prod_id);
|
||||
// config_init.set_filename(prod_id);
|
||||
let mut config_loaded = config_init.clone().load();
|
||||
// update the initialised data with what we loaded from disk
|
||||
for mode_init in &mut config_init.builtins {
|
||||
// update init values from loaded values if they exist
|
||||
if let Some(loaded) = config_loaded.builtins.get(mode_init.0) {
|
||||
*mode_init.1 = loaded.clone();
|
||||
}
|
||||
}
|
||||
// Then replace just incase the initialised data contains new modes added
|
||||
config_loaded.builtins = config_init.builtins;
|
||||
config_loaded.support_data = config_init.support_data;
|
||||
config_loaded.led_type = config_init.led_type;
|
||||
config_loaded.ally_fix = config_init.ally_fix;
|
||||
|
||||
for enabled_init in &mut config_init.enabled.states {
|
||||
for enabled in &mut config_loaded.enabled.states {
|
||||
if enabled.zone == enabled_init.zone {
|
||||
*enabled_init = *enabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
config_loaded.enabled = config_init.enabled;
|
||||
|
||||
if let (Some(mut multizone_init), Some(multizone_loaded)) =
|
||||
(config_init.multizone, config_loaded.multizone.as_mut())
|
||||
{
|
||||
for mode in multizone_init.iter_mut() {
|
||||
// update init values from loaded values if they exist
|
||||
if let Some(loaded) = multizone_loaded.get(mode.0) {
|
||||
let mut new_set = Vec::new();
|
||||
let data = LedSupportData::get_data(prod_id);
|
||||
// only reuse a zone mode if the mode is supported
|
||||
for mode in loaded {
|
||||
if data.basic_modes.contains(&mode.mode) {
|
||||
new_set.push(mode.clone());
|
||||
}
|
||||
}
|
||||
*mode.1 = new_set;
|
||||
}
|
||||
}
|
||||
*multizone_loaded = multizone_init;
|
||||
}
|
||||
|
||||
config_loaded.write();
|
||||
config_loaded
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rog_aura::keyboard::AuraPowerState;
|
||||
use rog_aura::{
|
||||
AuraEffect, AuraModeNum, AuraZone, Colour, Direction, LedBrightness, PowerZones, Speed,
|
||||
};
|
||||
|
||||
use super::AuraConfig;
|
||||
|
||||
#[test]
|
||||
fn set_multizone_4key_config() {
|
||||
std::env::set_var("BOARD_NAME", "");
|
||||
let mut config = AuraConfig::new("19b6");
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0xff,
|
||||
g: 0x00,
|
||||
b: 0xff,
|
||||
},
|
||||
zone: AuraZone::Key1,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0xff,
|
||||
},
|
||||
zone: AuraZone::Key2,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0xff,
|
||||
g: 0xff,
|
||||
b: 0x00,
|
||||
},
|
||||
zone: AuraZone::Key3,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0x00,
|
||||
},
|
||||
zone: AuraZone::Key4,
|
||||
..Default::default()
|
||||
};
|
||||
let effect_clone = effect.clone();
|
||||
config.set_builtin(effect);
|
||||
// This should replace existing
|
||||
config.set_builtin(effect_clone);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 4);
|
||||
assert_eq!(
|
||||
sta[0].colour1,
|
||||
Colour {
|
||||
r: 0xff,
|
||||
g: 0x00,
|
||||
b: 0xff
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
sta[1].colour1,
|
||||
Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0xff
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
sta[2].colour1,
|
||||
Colour {
|
||||
r: 0xff,
|
||||
g: 0xff,
|
||||
b: 0x00
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
sta[3].colour1,
|
||||
Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0x00
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_multizone_multimode_config() {
|
||||
std::env::set_var("BOARD_NAME", "");
|
||||
let mut config = AuraConfig::new("19b6");
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key1,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key2,
|
||||
mode: AuraModeNum::Breathe,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key3,
|
||||
mode: AuraModeNum::Comet,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key4,
|
||||
mode: AuraModeNum::Pulse,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Breathe).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Comet).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Pulse).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_0x1866_g531i() {
|
||||
std::env::set_var("BOARD_NAME", "G513I");
|
||||
let mut config = AuraConfig::new("1866");
|
||||
|
||||
assert_eq!(config.brightness, LedBrightness::Med);
|
||||
assert_eq!(config.builtins.len(), 5);
|
||||
assert_eq!(
|
||||
config.builtins.first_entry().unwrap().get(),
|
||||
&AuraEffect {
|
||||
mode: AuraModeNum::Static,
|
||||
zone: AuraZone::None,
|
||||
colour1: Colour { r: 166, g: 0, b: 0 },
|
||||
colour2: Colour { r: 0, g: 0, b: 0 },
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Right
|
||||
}
|
||||
);
|
||||
assert_eq!(config.enabled.states.len(), 1);
|
||||
assert_eq!(
|
||||
config.enabled.states[0],
|
||||
AuraPowerState {
|
||||
zone: PowerZones::KeyboardAndLightbar,
|
||||
boot: true,
|
||||
awake: true,
|
||||
sleep: true,
|
||||
shutdown: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_0x19b6_g634j() {
|
||||
std::env::set_var("BOARD_NAME", "G634J");
|
||||
let mut config = AuraConfig::new("19b6");
|
||||
|
||||
assert_eq!(config.brightness, LedBrightness::Med);
|
||||
assert_eq!(config.builtins.len(), 12);
|
||||
assert_eq!(
|
||||
config.builtins.first_entry().unwrap().get(),
|
||||
&AuraEffect {
|
||||
mode: AuraModeNum::Static,
|
||||
zone: AuraZone::None,
|
||||
colour1: Colour { r: 166, g: 0, b: 0 },
|
||||
colour2: Colour { r: 0, g: 0, b: 0 },
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Right
|
||||
}
|
||||
);
|
||||
assert_eq!(config.enabled.states.len(), 4);
|
||||
assert_eq!(
|
||||
config.enabled.states[0],
|
||||
AuraPowerState {
|
||||
zone: PowerZones::Keyboard,
|
||||
boot: true,
|
||||
awake: true,
|
||||
sleep: true,
|
||||
shutdown: true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
219
asusd/src/aura_laptop/mod.rs
Normal file
219
asusd/src/aura_laptop/mod.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use config::AuraConfig;
|
||||
use config_traits::StdConfig;
|
||||
use log::info;
|
||||
use rog_aura::keyboard::{AuraLaptopUsbPackets, LedUsbPackets};
|
||||
use rog_aura::usb::{AURA_LAPTOP_LED_APPLY, AURA_LAPTOP_LED_SET};
|
||||
use rog_aura::{AuraDeviceType, AuraEffect, LedBrightness, PowerZones, AURA_LAPTOP_LED_MSG_LEN};
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use rog_platform::keyboard_led::KeyboardBacklight;
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::error::RogError;
|
||||
|
||||
pub mod config;
|
||||
pub mod trait_impls;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Aura {
|
||||
pub hid: Option<Arc<Mutex<HidRaw>>>,
|
||||
pub backlight: Option<Arc<Mutex<KeyboardBacklight>>>,
|
||||
pub config: Arc<Mutex<AuraConfig>>,
|
||||
}
|
||||
|
||||
impl Aura {
|
||||
/// Initialise the device if required.
|
||||
pub async fn do_initialization(&self) -> Result<(), RogError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn lock_config(&self) -> MutexGuard<AuraConfig> {
|
||||
self.config.lock().await
|
||||
}
|
||||
|
||||
/// Will lock the internal config and update. If anything else has locked
|
||||
/// this in scope then a deadlock can occur.
|
||||
pub async fn update_config(&self) -> Result<(), RogError> {
|
||||
let mut config = self.config.lock().await;
|
||||
let bright = if let Some(bl) = self.backlight.as_ref() {
|
||||
bl.lock().await.get_brightness().unwrap_or_default()
|
||||
} else {
|
||||
config.brightness.into()
|
||||
};
|
||||
config.read();
|
||||
config.brightness = bright.into();
|
||||
config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_current_config_mode(&self, config: &mut AuraConfig) -> Result<(), RogError> {
|
||||
if config.multizone_on {
|
||||
let mode = config.current_mode;
|
||||
let mut create = false;
|
||||
// There is no multizone config for this mode so create one here
|
||||
// using the colours of rainbow if it exists, or first available
|
||||
// mode, or random
|
||||
if config.multizone.is_none() {
|
||||
create = true;
|
||||
} else if let Some(multizones) = config.multizone.as_ref() {
|
||||
if !multizones.contains_key(&mode) {
|
||||
create = true;
|
||||
}
|
||||
}
|
||||
if create {
|
||||
info!("No user-set config for zone founding, attempting a default");
|
||||
config.create_multizone_default()?;
|
||||
}
|
||||
|
||||
if let Some(multizones) = config.multizone.as_mut() {
|
||||
if let Some(set) = multizones.get(&mode) {
|
||||
for mode in set.clone() {
|
||||
self.write_effect_and_apply(config.led_type, &mode).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mode = config.current_mode;
|
||||
if let Some(effect) = config.builtins.get(&mode).cloned() {
|
||||
self.write_effect_and_apply(config.led_type, &effect)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the AuraEffect to the device. Will lock `backlight` or `hid`.
|
||||
///
|
||||
/// If per-key or software-mode is active it must be marked as disabled in
|
||||
/// config.
|
||||
pub async fn write_effect_and_apply(
|
||||
&self,
|
||||
dev_type: AuraDeviceType,
|
||||
mode: &AuraEffect,
|
||||
) -> Result<(), RogError> {
|
||||
if matches!(dev_type, AuraDeviceType::LaptopKeyboardTuf) {
|
||||
if let Some(platform) = &self.backlight {
|
||||
let buf = [
|
||||
1,
|
||||
mode.mode as u8,
|
||||
mode.colour1.r,
|
||||
mode.colour1.g,
|
||||
mode.colour1.b,
|
||||
mode.speed as u8,
|
||||
];
|
||||
platform.lock().await.set_kbd_rgb_mode(&buf)?;
|
||||
}
|
||||
} else if let Some(hid_raw) = &self.hid {
|
||||
let bytes: [u8; AURA_LAPTOP_LED_MSG_LEN] = mode.into();
|
||||
let hid_raw = hid_raw.lock().await;
|
||||
hid_raw.write_bytes(&bytes)?;
|
||||
hid_raw.write_bytes(&AURA_LAPTOP_LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
hid_raw.write_bytes(&AURA_LAPTOP_LED_APPLY)?;
|
||||
} else {
|
||||
return Err(RogError::NoAuraKeyboard);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_brightness(&self, value: u8) -> Result<(), RogError> {
|
||||
if let Some(backlight) = &self.backlight {
|
||||
backlight.lock().await.set_brightness(value)?;
|
||||
return Ok(());
|
||||
}
|
||||
Err(RogError::MissingFunction(
|
||||
"No LED backlight control available".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Set combination state for boot animation/sleep animation/all leds/keys
|
||||
/// leds/side leds LED active
|
||||
pub async fn set_power_states(&self, config: &AuraConfig) -> Result<(), RogError> {
|
||||
if matches!(config.led_type, rog_aura::AuraDeviceType::LaptopKeyboardTuf) {
|
||||
if let Some(backlight) = &self.backlight {
|
||||
// TODO: tuf bool array
|
||||
let buf = config.enabled.to_bytes(config.led_type);
|
||||
backlight.lock().await.set_kbd_rgb_state(&buf)?;
|
||||
}
|
||||
} else if let Some(hid_raw) = &self.hid {
|
||||
let hid_raw = hid_raw.lock().await;
|
||||
if let Some(p) = config.enabled.states.first() {
|
||||
if p.zone == PowerZones::Ally {
|
||||
let msg = [0x5d, 0xd1, 0x09, 0x01, p.new_to_byte() as u8, 0x0, 0x0];
|
||||
hid_raw.write_bytes(&msg)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = config.enabled.to_bytes(config.led_type);
|
||||
let msg = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3]];
|
||||
hid_raw.write_bytes(&msg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an effect block. This is for per-key, but can be repurposed to
|
||||
/// write the raw factory mode packets - when doing this it is expected that
|
||||
/// only the first `Vec` (`effect[0]`) is valid.
|
||||
pub async fn write_effect_block(
|
||||
&self,
|
||||
config: &mut AuraConfig,
|
||||
effect: &AuraLaptopUsbPackets,
|
||||
) -> Result<(), RogError> {
|
||||
if config.brightness == LedBrightness::Off {
|
||||
config.brightness = LedBrightness::Med;
|
||||
config.write();
|
||||
}
|
||||
|
||||
let pkt_type = effect[0][1];
|
||||
const PER_KEY_TYPE: u8 = 0xbc;
|
||||
|
||||
if let Some(hid_raw) = &self.hid {
|
||||
let hid_raw = hid_raw.lock().await;
|
||||
if pkt_type != PER_KEY_TYPE {
|
||||
config.per_key_mode_active = false;
|
||||
hid_raw.write_bytes(&effect[0])?;
|
||||
hid_raw.write_bytes(&AURA_LAPTOP_LED_SET)?;
|
||||
// hid_raw.write_bytes(&LED_APPLY)?;
|
||||
} else {
|
||||
if !config.per_key_mode_active {
|
||||
let init = LedUsbPackets::get_init_msg();
|
||||
hid_raw.write_bytes(&init)?;
|
||||
config.per_key_mode_active = true;
|
||||
}
|
||||
for row in effect.iter() {
|
||||
hid_raw.write_bytes(row)?;
|
||||
}
|
||||
}
|
||||
} else if matches!(config.led_type, rog_aura::AuraDeviceType::LaptopKeyboardTuf) {
|
||||
if let Some(tuf) = &self.backlight {
|
||||
for row in effect.iter() {
|
||||
let r = row[9];
|
||||
let g = row[10];
|
||||
let b = row[11];
|
||||
tuf.lock().await.set_kbd_rgb_mode(&[0, 0, r, g, b, 0])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fix_ally_power(&mut self) -> Result<(), RogError> {
|
||||
if self.config.lock().await.led_type == AuraDeviceType::Ally {
|
||||
if let Some(hid_raw) = &self.hid {
|
||||
let mut config = self.config.lock().await;
|
||||
if config.ally_fix.is_none() {
|
||||
let msg = [0x5d, 0xbd, 0x01, 0xff, 0xff, 0xff, 0xff];
|
||||
hid_raw.lock().await.write_bytes(&msg)?;
|
||||
info!("Reset Ally power settings to base");
|
||||
config.ally_fix = Some(true);
|
||||
}
|
||||
config.write();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
343
asusd/src/aura_laptop/trait_impls.rs
Normal file
343
asusd/src/aura_laptop/trait_impls.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use config_traits::StdConfig;
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_aura::keyboard::{AuraLaptopUsbPackets, LaptopAuraPower};
|
||||
use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones};
|
||||
use zbus::fdo::Error as ZbErr;
|
||||
use zbus::object_server::SignalEmitter;
|
||||
use zbus::zvariant::OwnedObjectPath;
|
||||
use zbus::{interface, Connection};
|
||||
|
||||
use super::Aura;
|
||||
use crate::error::RogError;
|
||||
use crate::{CtrlTask, Reloadable};
|
||||
|
||||
pub const AURA_ZBUS_NAME: &str = "Aura";
|
||||
pub const AURA_ZBUS_PATH: &str = "/org/asuslinux";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuraZbus(Aura);
|
||||
|
||||
impl AuraZbus {
|
||||
pub fn new(aura: Aura) -> Self {
|
||||
Self(aura)
|
||||
}
|
||||
|
||||
pub async fn start_tasks(
|
||||
mut self,
|
||||
connection: &Connection,
|
||||
// _signal_ctx: SignalEmitter<'static>,
|
||||
path: OwnedObjectPath,
|
||||
) -> Result<(), RogError> {
|
||||
// let task = zbus.clone();
|
||||
// let signal_ctx = signal_ctx.clone();
|
||||
self.reload()
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Controller error: {}", err));
|
||||
connection
|
||||
.object_server()
|
||||
.at(path.clone(), self)
|
||||
.await
|
||||
.map_err(|e| error!("Couldn't add server at path: {path}, {e:?}"))
|
||||
.ok();
|
||||
// TODO: skip this until we keep handles to tasks so they can be killed
|
||||
// task.create_tasks(signal_ctx).await
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interface for changing, reading, or notfying
|
||||
///
|
||||
/// LED commands are split between Brightness, Modes, Per-Key
|
||||
#[interface(name = "org.asuslinux.Aura")]
|
||||
impl AuraZbus {
|
||||
/// Return the device type for this Aura keyboard
|
||||
#[zbus(property)]
|
||||
async fn device_type(&self) -> AuraDeviceType {
|
||||
self.0.config.lock().await.led_type
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[zbus(property)]
|
||||
async fn brightness(&self) -> Result<LedBrightness, ZbErr> {
|
||||
if let Some(bl) = self.0.backlight.as_ref() {
|
||||
return Ok(bl.lock().await.get_brightness().map(|n| n.into())?);
|
||||
}
|
||||
Err(ZbErr::Failed("No sysfs brightness control".to_string()))
|
||||
}
|
||||
|
||||
/// Set the keyboard brightness level (0-3)
|
||||
#[zbus(property)]
|
||||
async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> {
|
||||
if let Some(bl) = self.0.backlight.as_ref() {
|
||||
return Ok(bl.lock().await.set_brightness(brightness.into())?);
|
||||
}
|
||||
Err(ZbErr::Failed("No sysfs brightness control".to_string()))
|
||||
}
|
||||
|
||||
/// Total levels of brightness available
|
||||
#[zbus(property)]
|
||||
async fn supported_brightness(&self) -> Vec<LedBrightness> {
|
||||
vec![
|
||||
LedBrightness::Off,
|
||||
LedBrightness::Low,
|
||||
LedBrightness::Med,
|
||||
LedBrightness::High,
|
||||
]
|
||||
}
|
||||
|
||||
/// The total available modes
|
||||
#[zbus(property)]
|
||||
async fn supported_basic_modes(&self) -> Result<Vec<AuraModeNum>, ZbErr> {
|
||||
let config = self.0.config.lock().await;
|
||||
Ok(config.builtins.keys().cloned().collect())
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn supported_basic_zones(&self) -> Result<Vec<AuraZone>, ZbErr> {
|
||||
let config = self.0.config.lock().await;
|
||||
Ok(config.support_data.basic_zones.clone())
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn supported_power_zones(&self) -> Result<Vec<PowerZones>, ZbErr> {
|
||||
let config = self.0.config.lock().await;
|
||||
Ok(config.support_data.power_zones.clone())
|
||||
}
|
||||
|
||||
/// The current mode data
|
||||
#[zbus(property)]
|
||||
async fn led_mode(&self) -> Result<AuraModeNum, ZbErr> {
|
||||
// entirely possible to deadlock here, so use try instead of lock()
|
||||
// let ctrl = self.0.lock().await;
|
||||
// Ok(config.current_mode)
|
||||
if let Ok(config) = self.0.config.try_lock() {
|
||||
Ok(config.current_mode)
|
||||
} else {
|
||||
Err(ZbErr::Failed("Aura control couldn't lock self".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Set an Aura effect if the effect mode or zone is supported.
|
||||
///
|
||||
/// On success the aura config file is read to refresh cached values, then
|
||||
/// the effect is stored and config written to disk.
|
||||
#[zbus(property)]
|
||||
async fn set_led_mode(&mut self, num: AuraModeNum) -> Result<(), ZbErr> {
|
||||
let mut config = self.0.config.lock().await;
|
||||
config.current_mode = num;
|
||||
self.0.write_current_config_mode(&mut config).await?;
|
||||
if config.brightness == LedBrightness::Off {
|
||||
config.brightness = LedBrightness::Med;
|
||||
}
|
||||
self.0.set_brightness(config.brightness.into()).await?;
|
||||
config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The current mode data
|
||||
#[zbus(property)]
|
||||
async fn led_mode_data(&self) -> Result<AuraEffect, ZbErr> {
|
||||
// entirely possible to deadlock here, so use try instead of lock()
|
||||
if let Ok(config) = self.0.config.try_lock() {
|
||||
let mode = config.current_mode;
|
||||
match config.builtins.get(&mode) {
|
||||
Some(effect) => Ok(effect.clone()),
|
||||
None => Err(ZbErr::Failed("Could not get the current effect".into())),
|
||||
}
|
||||
} else {
|
||||
Err(ZbErr::Failed("Aura control couldn't lock self".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Set an Aura effect if the effect mode or zone is supported.
|
||||
///
|
||||
/// On success the aura config file is read to refresh cached values, then
|
||||
/// the effect is stored and config written to disk.
|
||||
#[zbus(property)]
|
||||
async fn set_led_mode_data(&mut self, effect: AuraEffect) -> Result<(), ZbErr> {
|
||||
let mut config = self.0.config.lock().await;
|
||||
if !config.support_data.basic_modes.contains(&effect.mode)
|
||||
|| effect.zone != AuraZone::None
|
||||
&& !config.support_data.basic_zones.contains(&effect.zone)
|
||||
{
|
||||
return Err(ZbErr::NotSupported(format!(
|
||||
"The Aura effect is not supported: {effect:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
self.0
|
||||
.write_effect_and_apply(config.led_type, &effect)
|
||||
.await?;
|
||||
if config.brightness == LedBrightness::Off {
|
||||
config.brightness = LedBrightness::Med;
|
||||
}
|
||||
self.0.set_brightness(config.brightness.into()).await?;
|
||||
config.set_builtin(effect);
|
||||
config.write();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the data set for every mode available
|
||||
async fn all_mode_data(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
|
||||
let config = self.0.config.lock().await;
|
||||
config.builtins.clone()
|
||||
}
|
||||
|
||||
// As property doesn't work for AuraPowerDev (complexity of serialization?)
|
||||
#[zbus(property)]
|
||||
async fn led_power(&self) -> LaptopAuraPower {
|
||||
let config = self.0.config.lock().await;
|
||||
config.enabled.clone()
|
||||
}
|
||||
|
||||
/// Set a variety of states, input is array of enum.
|
||||
/// `enabled` sets if the sent array should be disabled or enabled
|
||||
///
|
||||
/// For Modern ROG devices the "enabled" flag is ignored.
|
||||
#[zbus(property)]
|
||||
async fn set_led_power(&mut self, options: LaptopAuraPower) -> Result<(), ZbErr> {
|
||||
let mut config = self.0.config.lock().await;
|
||||
for opt in options.states {
|
||||
let zone = opt.zone;
|
||||
for config in config.enabled.states.iter_mut() {
|
||||
if config.zone == zone {
|
||||
*config = opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
config.write();
|
||||
Ok(self.0.set_power_states(&config).await.map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?)
|
||||
}
|
||||
|
||||
/// On machine that have some form of either per-key keyboard or per-zone
|
||||
/// this can be used to write custom effects over dbus. The input is a
|
||||
/// nested `Vec<Vec<8>>` where `Vec<u8>` is a raw USB packet
|
||||
async fn direct_addressing_raw(&self, data: AuraLaptopUsbPackets) -> Result<(), ZbErr> {
|
||||
let mut config = self.0.config.lock().await;
|
||||
self.0.write_effect_block(&mut config, &data).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlTask for AuraZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
"/org/asuslinux"
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> {
|
||||
let inner1 = self.0.clone();
|
||||
let inner3 = self.0.clone();
|
||||
self.create_sys_event_tasks(
|
||||
move |sleeping| {
|
||||
let inner1 = inner1.clone();
|
||||
// unwrap as we want to bomb out of the task
|
||||
async move {
|
||||
if !sleeping {
|
||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
||||
if let Some(backlight) = &inner1.backlight {
|
||||
backlight
|
||||
.lock()
|
||||
.await
|
||||
.set_brightness(inner1.config.lock().await.brightness.into())
|
||||
.map_err(|e| {
|
||||
error!("CtrlKbdLedTask: {e}");
|
||||
e
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
let mut config = inner1.config.lock().await;
|
||||
inner1
|
||||
.write_current_config_mode(&mut config)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("CtrlKbdLedTask: {e}");
|
||||
e
|
||||
})
|
||||
.unwrap();
|
||||
} else if sleeping {
|
||||
inner1
|
||||
.update_config()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("CtrlKbdLedTask: {e}");
|
||||
e
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
move |_shutting_down| {
|
||||
let inner3 = inner3.clone();
|
||||
async move {
|
||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
||||
if let Some(backlight) = &inner3.backlight {
|
||||
// unwrap as we want to bomb out of the task
|
||||
backlight
|
||||
.lock()
|
||||
.await
|
||||
.set_brightness(inner3.config.lock().await.brightness.into())
|
||||
.map_err(|e| {
|
||||
error!("CtrlKbdLedTask: {e}");
|
||||
e
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
move |_lid_closed| {
|
||||
// on lid change
|
||||
async move {}
|
||||
},
|
||||
move |_power_plugged| {
|
||||
// power change
|
||||
async move {}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// let ctrl2 = self.0.clone();
|
||||
// let ctrl = self.0.lock().await;
|
||||
// if ctrl.led_node.has_brightness_control() {
|
||||
// let watch = ctrl.led_node.monitor_brightness()?;
|
||||
// tokio::spawn(async move {
|
||||
// let mut buffer = [0; 32];
|
||||
// watch
|
||||
// .into_event_stream(&mut buffer)
|
||||
// .unwrap()
|
||||
// .for_each(|_| async {
|
||||
// if let Some(lock) = ctrl2.try_lock() {
|
||||
// load_save(true, lock).unwrap(); // unwrap as we want
|
||||
// // to
|
||||
// // bomb out of the
|
||||
// // task
|
||||
// }
|
||||
// })
|
||||
// .await;
|
||||
// });
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Reloadable for AuraZbus {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
self.0.fix_ally_power().await?;
|
||||
debug!("reloading keyboard mode");
|
||||
let mut config = self.0.lock_config().await;
|
||||
self.0.write_current_config_mode(&mut config).await?;
|
||||
debug!("reloading power states");
|
||||
self.0
|
||||
.set_power_states(&config)
|
||||
.await
|
||||
.map_err(|err| warn!("{err}"))
|
||||
.ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user