diff --git a/CHANGELOG.md b/CHANGELOG.md index cd203023..9a1fb412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased ] -### Changed (v4.4.0) + +### Added (v4.4.0) +- Support for per-key config has been added to `asusd-user`. At the moment it is + basic with only two effects done. Please see the manual for more information. +### Changed - Create new rog-platform crate to manage all i/o in a universal way + kbd-led handling (requires kernel patches, TUF specific) + platform handling (asus-nb-wmi) diff --git a/Cargo.lock b/Cargo.lock index a38ed0c8..3daabf7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,6 +579,7 @@ version = "1.3.0" dependencies = [ "dirs 4.0.0", "rog_anime", + "rog_aura", "rog_dbus", "rog_platform", "serde", @@ -2028,6 +2029,7 @@ version = "1.3.1" dependencies = [ "serde", "serde_derive", + "serde_json", "toml", "zvariant", ] diff --git a/MANUAL.md b/MANUAL.md index 385ea06f..ecc887c6 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -131,6 +131,62 @@ As of now only AniMe is active in this with configuration in `~/.config/rog/`. O The main config is `~/.config/rog/rog-user.cfg` +#### Config options: Aura (per-key support only) + +`~/.config/rog/rog-user.cfg` contains a setting `"active_aura": ""` where `` is the name of the Aura config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "aura-default"` + +An Aura config itself is a file with contents: + +```json +{ + [ + { + "key": "F", + "action": { + "Breathe": { + "colour1": [ + 255, + 127, + 0 + ], + "colour2": [ + 127, + 0, + 255 + ], + "speed": "Med" + } + } + }, + { + "key": "Esc", + "action": { + "Static": [ + 0, + 0, + 255 + ] + } + ] +} +``` + +At the moment there are only two effects available as shown in the example. More will come in the future +but this may take me some time. + +**Aura layouts**: `asusd-user` does its best to find a suitable layout to use based on `/sys/class/dmi/id/board_name`. +It looks at each of the files in `/usr/share/rog-gui/layouts/` and matches against the toml block looking like: +```toml +matches = [ + 'GX502', + 'GU502', +] +``` + +My laptop is a `GX502GW`, so `GX502` is a match. Note that these layouts are the physical representation of +the keyboard and are used in the GUI also. The config that tells if per-key is supported is located in +`/etc/asusd/asusd-ledmodes.toml` + #### Config options: AniMe `~/.config/rog/rog-user.cfg` contains a setting `"active_anime": ""` where `` is the name of the AniMe config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "anime-doom"` diff --git a/daemon-user/Cargo.toml b/daemon-user/Cargo.toml index ec095781..90faa4ba 100644 --- a/daemon-user/Cargo.toml +++ b/daemon-user/Cargo.toml @@ -20,6 +20,7 @@ serde_json = "^1.0" serde_derive = "^1.0" rog_anime = { path = "../rog-anime" } +rog_aura = { path = "../rog-aura" } rog_dbus = { path = "../rog-dbus" } rog_platform = { path = "../rog-platform" } diff --git a/daemon-user/src/ctrl_anime.rs b/daemon-user/src/ctrl_anime.rs index 8e5daa1c..9c233c2c 100644 --- a/daemon-user/src/ctrl_anime.rs +++ b/daemon-user/src/ctrl_anime.rs @@ -15,6 +15,7 @@ use zbus::dbus_interface; use zvariant::ObjectPath; use zvariant_derive::Type; +use crate::user_config::ConfigLoadSave; use crate::{error::Error, user_config::UserAnimeConfig}; #[derive(Debug, Clone, Deserialize, Serialize, Type)] diff --git a/daemon-user/src/daemon.rs b/daemon-user/src/daemon.rs index 6a65588c..c83c25c0 100644 --- a/daemon-user/src/daemon.rs +++ b/daemon-user/src/daemon.rs @@ -1,4 +1,5 @@ use rog_anime::usb::get_anime_type; +use rog_aura::layouts::KeyLayout; use rog_dbus::RogDbusClientBlocking; use rog_user::{ ctrl_anime::{CtrlAnime, CtrlAnimeInner}, @@ -6,12 +7,18 @@ use rog_user::{ DBUS_NAME, }; use smol::Executor; -use std::sync::Arc; use std::sync::Mutex; +use std::{fs::OpenOptions, io::Read, path::PathBuf, sync::Arc}; use zbus::Connection; use std::sync::atomic::AtomicBool; +#[cfg(not(feature = "local_data"))] +const DATA_DIR: &str = "/usr/share/rog-gui/"; +#[cfg(feature = "local_data")] +const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR"); +const BOARD_NAME: &str = "/sys/class/dmi/id/board_name"; + fn main() -> Result<(), Box> { println!(" user daemon v{}", rog_user::VERSION); println!(" rog-anime v{}", rog_anime::VERSION); @@ -22,49 +29,83 @@ fn main() -> Result<(), Box> { let supported = client.proxies().supported().supported_functions()?; let mut config = UserConfig::new(); - config.load_config()?; + config.load()?; let executor = Executor::new(); let early_return = Arc::new(AtomicBool::new(false)); // Set up the anime data and run loop/thread if supported.anime_ctrl.0 { - let anime_type = get_anime_type()?; - let anime_config = UserAnimeConfig::load_config(config.active_anime)?; - let anime = anime_config.create_anime(anime_type)?; - let anime_config = Arc::new(Mutex::new(anime_config)); + if let Some(cfg) = config.active_anime { + let anime_type = get_anime_type()?; + let anime_config = UserAnimeConfig::load(cfg)?; + let anime = anime_config.create(anime_type)?; + let anime_config = Arc::new(Mutex::new(anime_config)); - executor - .spawn(async move { - // Create server - let mut connection = Connection::session().await.unwrap(); - connection.request_name(DBUS_NAME).await.unwrap(); + executor + .spawn(async move { + // Create server + let mut connection = Connection::session().await.unwrap(); + connection.request_name(DBUS_NAME).await.unwrap(); - // Inner behind mutex required for thread safety - let inner = Arc::new(Mutex::new( - CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(), - )); - // Need new client object for dbus control part - let (client, _) = RogDbusClientBlocking::new().unwrap(); - let anime_control = - CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap(); - anime_control.add_to_server(&mut connection).await; - loop { - if let Ok(inner) = inner.clone().try_lock() { - inner.run().ok(); + // Inner behind mutex required for thread safety + let inner = Arc::new(Mutex::new( + CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(), + )); + // Need new client object for dbus control part + let (client, _) = RogDbusClientBlocking::new().unwrap(); + let anime_control = + CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap(); + anime_control.add_to_server(&mut connection).await; + loop { + if let Ok(inner) = inner.clone().try_lock() { + inner.run().ok(); + } } - } - }) - .detach(); + }) + .detach(); + } } - // if supported.keyboard_led.per_key_led_mode { - // executor - // .spawn(async move { - // // - // }) - // .detach(); - // } + if supported.keyboard_led.per_key_led_mode { + if let Some(cfg) = config.active_aura { + let mut aura_config = UserAuraConfig::load(cfg)?; + + // Find and load a matching layout for laptop + let mut file = OpenOptions::new() + .read(true) + .open(PathBuf::from(BOARD_NAME)) + .map_err(|e| { + println!("{BOARD_NAME}, {e}"); + e + })?; + let mut board_name = String::new(); + file.read_to_string(&mut board_name)?; + + let layout = KeyLayout::find_layout(board_name.as_str(), PathBuf::from(DATA_DIR)) + .map_err(|e| { + println!("{BOARD_NAME}, {e}"); + }) + .unwrap_or(KeyLayout::ga401_layout()); + + executor + .spawn(async move { + // Create server + let (client, _) = RogDbusClientBlocking::new().unwrap(); + // let connection = Connection::session().await.unwrap(); + // connection.request_name(DBUS_NAME).await.unwrap(); + + loop { + aura_config.aura.next_state(&layout); + let packets = aura_config.aura.create_packets(); + + client.proxies().led().per_key_raw(packets).unwrap(); + std::thread::sleep(std::time::Duration::from_millis(60)); + } + }) + .detach(); + } + } loop { smol::block_on(executor.tick()); diff --git a/daemon-user/src/user_config.rs b/daemon-user/src/user_config.rs index 5f424096..6a756baf 100644 --- a/daemon-user/src/user_config.rs +++ b/daemon-user/src/user_config.rs @@ -5,28 +5,21 @@ use std::{ }; use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences, Vec2}; +use rog_aura::{keys::Key, Colour, Speed}; +use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; use crate::error::Error; -#[derive(Debug, Deserialize, Serialize)] -pub struct UserAnimeConfig { - pub name: String, - pub anime: Vec, -} +pub trait ConfigLoadSave { + fn name(&self) -> String; -impl UserAnimeConfig { - pub fn create_anime(&self, anime_type: AnimeType) -> Result { - let mut seq = Sequences::new(anime_type); + fn default_with_name(name: String) -> T; - for (idx, action) in self.anime.iter().enumerate() { - seq.insert(idx, action)?; - } - - Ok(seq) - } - - pub fn write(&self) -> Result<(), Error> { + fn write(&self) -> Result<(), Error> + where + Self: serde::Serialize, + { let mut path = if let Some(dir) = dirs::config_dir() { dir } else { @@ -37,7 +30,7 @@ impl UserAnimeConfig { if !path.exists() { create_dir(path.clone())?; } - let name = self.name.clone(); + let name = self.name().clone(); path.push(name + ".cfg"); let mut file = OpenOptions::new() @@ -51,7 +44,10 @@ impl UserAnimeConfig { Ok(()) } - pub fn load_config(name: String) -> Result { + fn load(name: String) -> Result + where + T: DeserializeOwned + serde::Serialize, + { let mut path = if let Some(dir) = dirs::config_dir() { dir } else { @@ -75,14 +71,11 @@ impl UserAnimeConfig { if let Ok(read_len) = file.read_to_string(&mut buf) { if read_len == 0 { - let default = UserAnimeConfig { - name, - ..Default::default() - }; + let default = Self::default_with_name(name); let json = serde_json::to_string_pretty(&default).unwrap(); file.write_all(json.as_bytes())?; return Ok(default); - } else if let Ok(data) = serde_json::from_str::(&buf) { + } else if let Ok(data) = serde_json::from_str::(&buf) { return Ok(data); } } @@ -90,6 +83,37 @@ impl UserAnimeConfig { } } +#[derive(Debug, Deserialize, Serialize)] +pub struct UserAnimeConfig { + pub name: String, + pub anime: Vec, +} + +impl UserAnimeConfig { + pub fn create(&self, anime_type: AnimeType) -> Result { + let mut seq = Sequences::new(anime_type); + + for (idx, action) in self.anime.iter().enumerate() { + seq.insert(idx, action)?; + } + + Ok(seq) + } +} + +impl ConfigLoadSave for UserAnimeConfig { + fn name(&self) -> String { + self.name.clone() + } + + fn default_with_name(name: String) -> Self { + UserAnimeConfig { + name, + ..Default::default() + } + } +} + impl Default for UserAnimeConfig { fn default() -> Self { Self { @@ -151,20 +175,83 @@ impl Default for UserAnimeConfig { } } +#[derive(Debug, Deserialize, Serialize)] +pub struct UserAuraConfig { + pub name: String, + pub aura: rog_aura::Sequences, +} + +impl ConfigLoadSave for UserAuraConfig { + fn name(&self) -> String { + self.name.clone() + } + + fn default_with_name(name: String) -> Self { + UserAuraConfig { + name, + ..Default::default() + } + } +} + +impl Default for UserAuraConfig { + fn default() -> Self { + let mut seq = rog_aura::Sequences::new(); + let mut key = rog_aura::ActionData::new_breathe( + Key::W, + Colour(255, 0, 20), + Colour(20, 255, 0), + Speed::Low, + ); + + seq.push(key.clone()); + key.set_key(Key::A); + seq.push(key.clone()); + key.set_key(Key::S); + seq.push(key.clone()); + key.set_key(Key::D); + seq.push(key); + + let key = rog_aura::ActionData::new_breathe( + Key::F, + Colour(255, 0, 0), + Colour(255, 0, 0), + Speed::High, + ); + seq.push(key); + + let mut key = rog_aura::ActionData::new_static(Key::RCtrl, Colour(0, 0, 255)); + seq.push(key.clone()); + key.set_key(Key::LCtrl); + seq.push(key.clone()); + key.set_key(Key::Esc); + seq.push(key); + + Self { + name: "default".to_string(), + aura: seq, + } + } +} + #[derive(Debug, Default, Deserialize, Serialize)] +#[serde(default)] pub struct UserConfig { /// Name of active anime config file in the user config directory - pub active_anime: String, + pub active_anime: Option, + /// Name of active aura config file in the user config directory + pub active_aura: Option, } impl UserConfig { pub fn new() -> Self { Self { - active_anime: "anime-default".to_string(), + active_anime: Some("anime-default".to_string()), + active_aura: Some("aura-default".to_string()), } } - pub fn load_config(&mut self) -> Result<(), Error> { + pub fn load(&mut self) -> Result<(), Error> { let mut path = if let Some(dir) = dirs::config_dir() { dir } else { @@ -192,6 +279,7 @@ impl UserConfig { file.write_all(json.as_bytes())?; } else if let Ok(data) = serde_json::from_str::(&buf) { self.active_anime = data.active_anime; + self.active_aura = data.active_aura; return Ok(()); } } diff --git a/rog-aura/Cargo.toml b/rog-aura/Cargo.toml index 42727e7b..256b03f0 100644 --- a/rog-aura/Cargo.toml +++ b/rog-aura/Cargo.toml @@ -20,4 +20,7 @@ dbus = ["zvariant"] serde = "^1.0" serde_derive = "^1.0" toml = { version = "^0.5", optional = true } -zvariant = { version = "^3.0", optional = true } \ No newline at end of file +zvariant = { version = "^3.0", optional = true } + +[dev-dependencies] +serde_json = "^1.0" \ No newline at end of file diff --git a/rog-aura/src/layouts/mod.rs b/rog-aura/src/layouts/mod.rs index 5a05c4f9..62276669 100644 --- a/rog-aura/src/layouts/mod.rs +++ b/rog-aura/src/layouts/mod.rs @@ -10,7 +10,12 @@ pub mod gx502; use crate::{error::Error, keys::Key}; use serde::{Deserialize, Serialize}; -use std::{fs::OpenOptions, io::Read, path::Path, slice::Iter}; +use std::{ + fs::{self, OpenOptions}, + io::Read, + path::{Path, PathBuf}, + slice::Iter, +}; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct KeyLayout { @@ -53,6 +58,26 @@ impl KeyLayout { pub fn rows_ref(&self) -> &[KeyRow] { &self.rows } + + /// Find a layout matching the provided board name in the provided dir + pub fn find_layout(board_name: &str, mut data_path: PathBuf) -> Result { + let mut layout = KeyLayout::ga401_layout(); // default + + data_path.push("layouts"); + let path = data_path.as_path(); + for p in fs::read_dir(path).map_err(|e| { + println!("{:?}, {e}", path); + e + })? { + let tmp = KeyLayout::from_file(&p?.path()).unwrap(); + if tmp.matches(board_name) { + layout = tmp; + break; + } + } + + Ok(layout) + } } #[derive(Debug, Deserialize, Serialize, Clone)] diff --git a/rog-aura/src/sequencer.rs b/rog-aura/src/sequencer.rs index 8114e654..e239c1b5 100644 --- a/rog-aura/src/sequencer.rs +++ b/rog-aura/src/sequencer.rs @@ -204,6 +204,9 @@ mod tests { colour: Default::default(), }); + let s = serde_json::to_string_pretty(&seq).unwrap(); + println!("{s}"); + seq.next_state(&layout); let packets = seq.create_packets(); diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index ddca1909..907f0a09 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -60,8 +60,6 @@ fn main() -> Result<(), Box> { let mut board_name = String::new(); file.read_to_string(&mut board_name)?; - let mut layout = KeyLayout::ga401_layout(); // default - let mut path = PathBuf::from(DATA_DIR); #[cfg(feature = "mocking")] { board_name = "gl504".to_string(); @@ -69,18 +67,12 @@ fn main() -> Result<(), Box> { path.push("rog-aura"); path.push("data"); } - path.push("layouts"); - let path = path.as_path(); - for p in fs::read_dir(path).map_err(|e| { - println!("{:?}, {e}", path); - e - })? { - let tmp = KeyLayout::from_file(&p?.path()).unwrap(); - if tmp.matches(board_name.as_str()) { - layout = tmp; - break; - } - } + + let layout = KeyLayout::find_layout(board_name.as_str(), PathBuf::from(DATA_DIR)) + .map_err(|e| { + println!("{BOARD_NAME}, {e}"); + }) + .unwrap_or(KeyLayout::ga401_layout()); // Cheap method to alert to notifications rather than spinning a thread for each // This is quite different when done in a retained mode app