diff --git a/asusctl/examples/aura-rgb-ball.rs b/asusctl/examples/aura-rgb-ball.rs new file mode 100644 index 00000000..bfefc083 --- /dev/null +++ b/asusctl/examples/aura-rgb-ball.rs @@ -0,0 +1,113 @@ +//! Very bad rushed example. The better way to do this would be to have +//! the balles move on their own square grid, then translate that to the +//! key layout via shape by pitch etc. +use rog_aura::{ + layouts::{KeyLayout, KeyRow}, + KeyColourArray, +}; +use rog_dbus::RogDbusClientBlocking; +use std::collections::LinkedList; + +#[derive(Debug, Clone)] +struct Ball { + position: (f32, f32), + direction: (f32, f32), + trail: LinkedList<(f32, f32)>, +} +impl Ball { + fn new(x: f32, y: f32, trail_len: u32) -> Self { + let mut trail = LinkedList::new(); + for _ in 1..=trail_len { + trail.push_back((x, y)); + } + + Ball { + position: (x, y), + direction: (1.0, 1.0), + trail, + } + } + + #[allow(clippy::if_same_then_else)] + fn update(&mut self, key_map: &[KeyRow]) { + self.position.0 += self.direction.0; + self.position.1 += self.direction.1; + + if self.position.1.abs() as usize >= key_map.len() { + self.direction.1 *= -1.0; + self.position.1 += self.direction.1; + self.direction.0 *= -1.0; + self.position.0 += self.direction.0; + } + if self.position.0.abs() as usize >= key_map[self.position.1.abs() as usize].row_ref().len() + { + self.direction.1 *= -1.0; + self.position.1 += self.direction.1; + } + if self.position.0 as usize >= key_map[self.position.1.abs() as usize].row_ref().len() { + self.direction.0 *= -1.0; + self.position.0 += self.direction.0; + } + + let pos = self.position; + + if pos.1 == key_map[pos.1.abs() as usize].row_ref().len() as f32 - 1.0 || pos.1 <= 0.0 { + self.direction.0 *= -1.0; + } else if key_map[(pos.1) as usize].row_ref()[(pos.0) as usize].is_placeholder() { + self.direction.0 *= -1.0; + } + + if pos.0 == key_map.len() as f32 - 1.0 || pos.0 <= 0.0 { + self.direction.1 *= -1.0; + } else if key_map[(pos.1) as usize].row_ref()[(pos.0) as usize].is_placeholder() { + self.direction.1 *= -1.0; + } + + self.trail.pop_front(); + self.trail.push_back(self.position); + } +} + +fn main() -> Result<(), Box> { + let (dbus, _) = RogDbusClientBlocking::new()?; + + let mut colours = KeyColourArray::new(); + let layout = KeyLayout::gx502_layout(); + + let mut balls = [Ball::new(2.0, 1.0, 12), Ball::new(5.0, 2.0, 12)]; + // let mut balls = [Ball::new(2, 1, 12)]; + + loop { + for (n, ball) in balls.iter_mut().enumerate() { + ball.update(layout.rows_ref()); + for (i, pos) in ball.trail.iter().enumerate() { + if let Some(c) = colours + .rgb_for_key(layout.rows_ref()[pos.1.abs() as usize].row_ref()[pos.0 as usize]) + { + c[0] = 0; + c[1] = 0; + c[2] = 0; + if n == 0 { + c[0] = i as u8 * (255 / ball.trail.len() as u8); + } else if n == 1 { + c[1] = i as u8 * (255 / ball.trail.len() as u8); + } else if n == 2 { + c[2] = i as u8 * (255 / ball.trail.len() as u8); + } + }; + } + + if let Some(c) = colours.rgb_for_key( + layout.rows_ref()[ball.position.1.abs() as usize].row_ref() + [ball.position.0 as usize], + ) { + c[0] = 255; + c[1] = 255; + c[2] = 255; + }; + } + dbus.proxies().led().per_key_raw(colours.get())?; + + std::thread::sleep(std::time::Duration::from_millis(150)); + } +} diff --git a/asusctl/examples/aura-rgb-iterate-keys.rs b/asusctl/examples/aura-rgb-iterate-keys.rs new file mode 100644 index 00000000..eaea65b9 --- /dev/null +++ b/asusctl/examples/aura-rgb-iterate-keys.rs @@ -0,0 +1,34 @@ +//! Using a combination of key-colour array plus a key layout to generate outputs. + +use rog_aura::{layouts::KeyLayout, KeyColourArray}; +use rog_dbus::RogDbusClientBlocking; + +fn main() -> Result<(), Box> { + let (client, _) = RogDbusClientBlocking::new().unwrap(); + let layout = KeyLayout::gx502_layout(); + loop { + let mut key_colours = KeyColourArray::new(); + for row in layout.rows() { + for (k, key) in row.row().enumerate() { + if k != 0 { + if let Some(prev) = row.row().nth(k - 1) { + if let Some(c) = key_colours.rgb_for_key(*prev) { + c[0] = 0; + }; + } + } + + if key.is_placeholder() { + continue; + } + + if let Some(c) = key_colours.rgb_for_key(*key) { + c[0] = 255; + }; + + client.proxies().led().per_key_raw(key_colours.get())?; + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + } +} diff --git a/daemon/src/ctrl_aura/controller.rs b/daemon/src/ctrl_aura/controller.rs index 32b71cd4..e30b33f8 100644 --- a/daemon/src/ctrl_aura/controller.rs +++ b/daemon/src/ctrl_aura/controller.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use log::{error, info, warn}; use rog_aura::{ usb::{AuraDevice, LED_APPLY, LED_SET}, - AuraEffect, LedBrightness, LED_MSG_LEN, + AuraEffect, KeyColourArray, LedBrightness, PerKeyRaw, LED_MSG_LEN, }; use rog_aura::{AuraZone, Direction, Speed, GRADIENT}; use rog_platform::{hid_raw::HidRaw, keyboard_led::KeyboardLed, supported::LedSupportedFunctions}; @@ -70,6 +70,7 @@ pub struct CtrlKbdLed { pub kd_brightness: KeyboardLed, pub supported_modes: LaptopLedData, pub flip_effect_write: bool, + pub per_key_mode_active: bool, pub config: AuraConfig, } @@ -215,6 +216,7 @@ impl CtrlKbdLed { kd_brightness: rgb_led, // If was none then we already returned above supported_modes, flip_effect_write: false, + per_key_mode_active: false, config, }; Ok(ctrl) @@ -294,17 +296,24 @@ impl CtrlKbdLed { } /// Write an effect block. This is for per-key - fn _write_effect(&mut self, effect: &[Vec]) -> Result<(), RogError> { - if let LEDNode::Rog(hid_raw) = &self.led_node { - if self.flip_effect_write { - for row in effect.iter().rev() { - hid_raw.write_bytes(row)?; - } - } else { - for row in effect.iter() { - hid_raw.write_bytes(row)?; - } + pub fn write_per_key_block(&mut self, effect: &PerKeyRaw) -> Result<(), RogError> { + if !self.per_key_mode_active { + if let LEDNode::Rog(hid_raw) = &self.led_node { + let init = KeyColourArray::get_init_msg(); + hid_raw.write_bytes(&init)?; } + self.per_key_mode_active = true; + } + if let LEDNode::Rog(hid_raw) = &self.led_node { + // if self.flip_effect_write { + // for row in effect.iter().rev() { + // hid_raw.write_bytes(row)?; + // } + // } else { + for row in effect.iter() { + hid_raw.write_bytes(row)?; + } + // } } self.flip_effect_write = !self.flip_effect_write; Ok(()) @@ -345,7 +354,7 @@ impl CtrlKbdLed { Ok(()) } - fn write_mode(&self, mode: &AuraEffect) -> Result<(), RogError> { + fn write_mode(&mut self, mode: &AuraEffect) -> Result<(), RogError> { if let LEDNode::KbdLed(platform) = &self.led_node { let buf = [ 1, @@ -365,6 +374,7 @@ impl CtrlKbdLed { } else { return Err(RogError::NoAuraKeyboard); } + self.per_key_mode_active = false; Ok(()) } @@ -387,17 +397,17 @@ impl CtrlKbdLed { self.create_multizone_default()?; } - if let Some(multizones) = self.config.multizone.as_ref() { + if let Some(multizones) = self.config.multizone.as_mut() { if let Some(set) = multizones.get(&mode) { - for mode in set { - self.write_mode(mode)?; + for mode in set.clone() { + self.write_mode(&mode)?; } } } } else { let mode = self.config.current_mode; - if let Some(effect) = self.config.builtins.get(&mode) { - self.write_mode(effect)?; + if let Some(effect) = self.config.builtins.get(&mode).cloned() { + self.write_mode(&effect)?; } } @@ -462,6 +472,7 @@ mod tests { kd_brightness: KeyboardLed::default(), supported_modes, flip_effect_write: false, + per_key_mode_active: false, config, }; @@ -524,6 +535,7 @@ mod tests { kd_brightness: KeyboardLed::default(), supported_modes, flip_effect_write: false, + per_key_mode_active: false, config, }; @@ -561,6 +573,7 @@ mod tests { kd_brightness: KeyboardLed::default(), supported_modes, flip_effect_write: false, + per_key_mode_active: false, config, }; diff --git a/daemon/src/ctrl_aura/zbus.rs b/daemon/src/ctrl_aura/zbus.rs index 38313c80..8941ea84 100644 --- a/daemon/src/ctrl_aura/zbus.rs +++ b/daemon/src/ctrl_aura/zbus.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use async_trait::async_trait; use log::warn; -use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, LedBrightness}; +use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, LedBrightness, PerKeyRaw}; use zbus::{dbus_interface, Connection, SignalContext}; use super::controller::CtrlKbdLedZbus; @@ -210,6 +210,13 @@ impl CtrlKbdLedZbus { } } + async fn per_key_raw(&self, data: PerKeyRaw) -> zbus::fdo::Result<()> { + if let Ok(mut ctrl) = self.0.try_lock() { + ctrl.write_per_key_block(&data)?; + } + Ok(()) + } + /// Return the current LED brightness #[dbus_interface(property)] async fn led_brightness(&self) -> i8 { diff --git a/rog-aura/src/keys.rs b/rog-aura/src/keys.rs index 94efc92d..1fbf12c9 100644 --- a/rog-aura/src/keys.rs +++ b/rog-aura/src/keys.rs @@ -1,10 +1,11 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, PartialEq, Copy, Clone, Serialize, Deserialize)] pub enum Key { VolUp, VolDown, MicMute, + #[default] Rog, Fan, Esc, @@ -151,6 +152,13 @@ pub enum Key { RowEndSpacer, } +impl Key { + pub fn is_placeholder(&self) -> bool { + let shape = KeyShape::from(self); + shape.is_blank() || shape.is_spacer() + } +} + /// Types of shapes of LED on keyboards. The shape is used for visual representations /// /// A post fix of Spacer *must be ignored by per-key effects diff --git a/rog-aura/src/layouts/mod.rs b/rog-aura/src/layouts/mod.rs index 687931cf..5a05c4f9 100644 --- a/rog-aura/src/layouts/mod.rs +++ b/rog-aura/src/layouts/mod.rs @@ -49,6 +49,10 @@ impl KeyLayout { pub fn rows(&self) -> Iter { self.rows.iter() } + + pub fn rows_ref(&self) -> &[KeyRow] { + &self.rows + } } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -66,6 +70,10 @@ impl KeyRow { self.row.iter() } + pub fn row_ref(&self) -> &[Key] { + &self.row + } + pub fn height(&self) -> f32 { self.height } diff --git a/rog-aura/src/layouts/old_gx502.rs b/rog-aura/src/layouts/old_gx502.rs new file mode 100644 index 00000000..c142a1b1 --- /dev/null +++ b/rog-aura/src/layouts/old_gx502.rs @@ -0,0 +1,168 @@ + +#[allow(clippy::upper_case_acronyms)] +pub struct GX502Layout(Vec<[Key; 17]>); + +impl KeyLayout for GX502Layout { + fn get_rows(&self) -> &Vec<[Key; 17]> { + &self.0 + } +} + +impl Default for GX502Layout { + fn default() -> Self { + GX502Layout(vec![ + [ + Key::NormalSpacer, + Key::FuncSpacer, + Key::VolDown, + Key::VolUp, + Key::MicMute, + Key::Rog, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + ], + [ + Key::Esc, + Key::NormalBlank, + Key::F1, + Key::F2, + Key::F3, + Key::F4, + Key::NormalBlank, // not sure which key to put here + Key::F5, + Key::F6, + Key::F7, + Key::F8, + Key::NormalBlank, + Key::F9, + Key::F10, + Key::F11, + Key::F12, + Key::Del, + ], + [ + Key::Tilde, + Key::N1, + Key::N2, + Key::N3, + Key::N4, + Key::N5, + Key::N6, + Key::N7, + Key::N8, + Key::N9, + Key::N0, + Key::Hyphen, + Key::Equals, + Key::BkSpc3_1, + Key::BkSpc3_2, + Key::BkSpc3_3, + Key::Home, + ], + [ + Key::Tab, + Key::Q, + Key::W, + Key::E, + Key::R, + Key::T, + Key::Y, + Key::U, + Key::I, + Key::O, + Key::P, + Key::LBracket, + Key::RBracket, + Key::BackSlash, + Key::BackSlash, + Key::BackSlash, + Key::PgUp, + ], + [ + Key::Caps, + Key::A, + Key::S, + Key::D, + Key::F, + Key::G, + Key::H, + Key::J, + Key::K, + Key::L, + Key::SemiColon, + Key::Quote, + Key::Quote, + Key::Return3_1, + Key::Return3_2, + Key::Return3_3, + Key::PgDn, + ], + [ + Key::LShift, + Key::LShift, + Key::Z, + Key::X, + Key::C, + Key::V, + Key::B, + Key::N, + Key::M, + Key::Comma, + Key::Period, + Key::FwdSlash, + Key::FwdSlash, + Key::Rshift3_1, + Key::Rshift3_2, + Key::Rshift3_3, + Key::End, + ], + [ + Key::LCtrl, + Key::LFn, + Key::Meta, + Key::LAlt, + Key::Space5_1, + Key::Space5_2, + Key::Space5_3, + Key::Space5_4, + Key::Space5_5, + Key::RAlt, + Key::PrtSc, + Key::RCtrl, + Key::RCtrl, + Key::Left, + Key::Up, + Key::Right, + Key::RFn, + ], + [ + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::NormalBlank, + Key::Left, + Key::Down, + Key::Right, + Key::NormalBlank, + ], + ]) + } +} \ No newline at end of file diff --git a/rog-aura/src/per_key_rgb.rs b/rog-aura/src/per_key_rgb.rs index 2dd3ca6d..b580994b 100644 --- a/rog-aura/src/per_key_rgb.rs +++ b/rog-aura/src/per_key_rgb.rs @@ -1,4 +1,10 @@ use crate::keys::Key; +use serde_derive::{Deserialize, Serialize}; +#[cfg(feature = "dbus")] +use zvariant::Type; + +/// Represents the per-key raw USB packets +pub type PerKeyRaw = Vec>; /// A `KeyColourArray` contains all data to change the full set of keyboard /// key colours individually. @@ -7,16 +13,19 @@ use crate::keys::Key; /// to the keyboard EC. One row controls one group of keys, these keys are not /// necessarily all on the same row of the keyboard, with some splitting between /// two rows. -#[derive(Clone)] -pub struct KeyColourArray([[u8; 64]; 11]); +#[cfg_attr(feature = "dbus", derive(Type))] +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct KeyColourArray(PerKeyRaw); + impl Default for KeyColourArray { fn default() -> Self { Self::new() } } + impl KeyColourArray { pub fn new() -> Self { - let mut set = [[0u8; 64]; 11]; + let mut set = vec![vec![0u8; 64]; 11]; for (count, row) in set.iter_mut().enumerate() { row[0] = 0x5d; // Report ID row[1] = 0xbc; // Mode = custom??, 0xb3 is builtin @@ -211,15 +220,31 @@ impl KeyColourArray { Key::Fan | Key::Space | Key::BkSpc => return None, }; - Some(&mut self.0[row][col..2]) + Some(&mut self.0[row][col..=col + 2]) } #[inline] - pub fn get(&self) -> &[[u8; 64]; 11] { + pub fn get(&self) -> PerKeyRaw { + self.0.clone() + } + + #[inline] + pub fn get_ref(&self) -> &PerKeyRaw { &self.0 } + + #[inline] + pub fn get_mut(&mut self) -> &mut PerKeyRaw { + &mut self.0 + } } pub trait KeyLayout { fn get_rows(&self) -> &Vec<[Key; 17]>; } + +impl From for PerKeyRaw { + fn from(k: KeyColourArray) -> Self { + k.0 + } +} diff --git a/rog-aura/src/sequencer.rs b/rog-aura/src/sequencer.rs index f3ba5016..32dcd36a 100644 --- a/rog-aura/src/sequencer.rs +++ b/rog-aura/src/sequencer.rs @@ -1,17 +1,43 @@ use serde_derive::{Deserialize, Serialize}; -use crate::error::Error; +use crate::{keys::Key, Colour, KeyColourArray, PerKeyRaw, Speed}; -/// 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. -#[derive(Debug, Deserialize, Serialize)] -pub enum ActionData { - Static, +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct PerKey { + pub key: Key, + pub action: ActionData, + /// The end resulting colour after stepping through effect + #[serde(skip)] + colour: Colour, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum ActionData { + Static(Colour), + Breathe { + /// The starting colour + colour1: Colour, + /// The secondary starting colour + colour2: Colour, + /// The speed at which to cycle between the colours + speed: Speed, + /// Temporary data to help keep state + #[serde(skip)] + colour1_actual: Colour, + /// Temporary data to help keep state + #[serde(skip)] + colour2_actual: Colour, + }, +} + +impl Default for ActionData { + fn default() -> Self { + Self::Static(Colour::default()) + } } -/// An optimised precomputed set of actions that the user can cycle through #[derive(Debug, Deserialize, Serialize, Default)] -pub struct Sequences(Vec); +pub struct Sequences(Vec); impl Sequences { #[inline] @@ -19,51 +45,76 @@ impl Sequences { Self(Vec::new()) } - /// Use a base `AnimeAction` to generate the precomputed data and insert in to - /// the run buffer #[inline] - pub fn insert(&mut self, _index: usize) -> Result<(), Error> { - Ok(()) + pub fn push(&mut self, action: PerKey) { + self.0.push(action); + } + + #[inline] + pub fn insert(&mut self, index: usize, action: PerKey) { + self.0.insert(index, action); } /// Remove an item at this position from the run buffer. If the `index` supplied /// is not in range then `None` is returned, otherwise the `ActionData` at that location /// is yeeted and returned. #[inline] - pub fn remove_item(&mut self, index: usize) -> Option { + pub fn remove_item(&mut self, index: usize) -> Option { if index < self.0.len() { return Some(self.0.remove(index)); } None } - pub fn iter(&self) -> ActionIterator { - ActionIterator { - actions: self, - next_idx: 0, + pub fn next_state(&mut self) { + for effect in self.0.iter_mut() { + match effect.action { + ActionData::Static(c) => effect.colour = c, + ActionData::Breathe { + colour1, + colour2, + speed, + colour1_actual, + colour2_actual, + } => { + effect.colour = colour1; + } + } } } + + pub fn create_packets(&self) -> PerKeyRaw { + let mut keys = KeyColourArray::new(); + for effect in self.0.iter() { + if let Some(rgb) = keys.rgb_for_key(effect.key) { + rgb[0] = effect.colour.0; + rgb[1] = effect.colour.1; + rgb[2] = effect.colour.2; + } + } + keys.into() + } } -/// Iteractor helper for iterating over all the actions in `Sequences` -pub struct ActionIterator<'a> { - actions: &'a Sequences, - next_idx: usize, -} +#[cfg(test)] +mod tests { + use crate::{keys::Key, Colour, PerKey, Sequences}; -impl<'a> Iterator for ActionIterator<'a> { - type Item = &'a ActionData; + #[test] + fn single_key_next_state_then_create() { + let mut seq = Sequences::new(); + seq.0.push(PerKey { + key: Key::F, + action: crate::ActionData::Static(Colour(255, 127, 0)), + colour: Default::default(), + }); - #[inline] - fn next(&mut self) -> Option<&'a ActionData> { - if self.next_idx == self.actions.0.len() { - self.next_idx = 0; - return None; - } + seq.next_state(); + let packets = seq.create_packets(); - let current = self.next_idx; - self.next_idx += 1; - - Some(&self.actions.0[current]) + assert_eq!(packets[0][0], 0x5d); + assert_eq!(packets[5][33], 255); + assert_eq!(packets[5][34], 127); + assert_eq!(packets[5][35], 0); } } diff --git a/rog-dbus/src/zbus_led.rs b/rog-dbus/src/zbus_led.rs index d7815e3c..455ce708 100644 --- a/rog-dbus/src/zbus_led.rs +++ b/rog-dbus/src/zbus_led.rs @@ -24,7 +24,7 @@ use std::collections::BTreeMap; use zbus::{blocking::Connection, Result}; use zbus_macros::dbus_proxy; -use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, KeyColourArray, LedBrightness}; +use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum, LedBrightness, PerKeyRaw}; const BLOCKING_TIME: u64 = 40; // 100ms = 10 FPS, max 50ms = 20 FPS, 40ms = 25 FPS @@ -53,6 +53,8 @@ trait Led { fn set_leds_power(&self, options: AuraPowerDev, enabled: bool) -> zbus::Result<()>; + fn per_key_raw(&self, data: PerKeyRaw) -> zbus::fdo::Result<()>; + /// NotifyLed signal #[dbus_proxy(signal)] fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>; @@ -93,30 +95,9 @@ impl<'a> LedProxyPerkey<'a> { /// Intentionally blocks for 10ms after sending to allow the block to /// be written to the keyboard EC. This should not be async. #[inline] - pub fn set_per_key(&self, key_colour_array: &KeyColourArray) -> Result<()> { - let group = key_colour_array.get(); - let mut vecs = Vec::with_capacity(group.len()); - for v in group { - vecs.push(v.to_vec()); - } - // TODO: let mode = AuraModes::PerKey(vecs); - // self.set_led_mode(&mode)?; - + pub fn set_per_key(&self, per_key_raw: PerKeyRaw) -> Result<()> { + self.0.per_key_raw(per_key_raw)?; std::thread::sleep(std::time::Duration::from_millis(BLOCKING_TIME)); - - // if self.stop.load(Ordering::Relaxed) { - // println!("Keyboard backlight was changed, exiting"); - // std::process::exit(1) - // } - Ok(()) - } - - /// This method must always be called before the very first write to initialise - /// the keyboard LED EC in the correct mode - #[inline] - pub fn init_effect(&self) -> Result<()> { - // TODO: let mode = AuraModes::PerKey(vec![vec![]]); - // self.0.set_led_mode(&serde_json::to_string(&mode).unwrap()) Ok(()) } }