rog-aura: add basic per-key support

This commit is contained in:
Luke D. Jones
2022-08-25 21:45:36 +12:00
parent f378c54815
commit 40987ecd5d
11 changed files with 292 additions and 76 deletions

View File

@@ -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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased ] ## [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 - Create new rog-platform crate to manage all i/o in a universal way
+ kbd-led handling (requires kernel patches, TUF specific) + kbd-led handling (requires kernel patches, TUF specific)
+ platform handling (asus-nb-wmi) + platform handling (asus-nb-wmi)

2
Cargo.lock generated
View File

@@ -579,6 +579,7 @@ version = "1.3.0"
dependencies = [ dependencies = [
"dirs 4.0.0", "dirs 4.0.0",
"rog_anime", "rog_anime",
"rog_aura",
"rog_dbus", "rog_dbus",
"rog_platform", "rog_platform",
"serde", "serde",
@@ -2028,6 +2029,7 @@ version = "1.3.1"
dependencies = [ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json",
"toml", "toml",
"zvariant", "zvariant",
] ]

View File

@@ -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` 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": "<FILENAME>"` where `<FILENAME>` 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 options: AniMe
`~/.config/rog/rog-user.cfg` contains a setting `"active_anime": "<FILENAME>"` where `<FILENAME>` 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"` `~/.config/rog/rog-user.cfg` contains a setting `"active_anime": "<FILENAME>"` where `<FILENAME>` 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"`

View File

@@ -20,6 +20,7 @@ serde_json = "^1.0"
serde_derive = "^1.0" serde_derive = "^1.0"
rog_anime = { path = "../rog-anime" } rog_anime = { path = "../rog-anime" }
rog_aura = { path = "../rog-aura" }
rog_dbus = { path = "../rog-dbus" } rog_dbus = { path = "../rog-dbus" }
rog_platform = { path = "../rog-platform" } rog_platform = { path = "../rog-platform" }

View File

@@ -15,6 +15,7 @@ use zbus::dbus_interface;
use zvariant::ObjectPath; use zvariant::ObjectPath;
use zvariant_derive::Type; use zvariant_derive::Type;
use crate::user_config::ConfigLoadSave;
use crate::{error::Error, user_config::UserAnimeConfig}; use crate::{error::Error, user_config::UserAnimeConfig};
#[derive(Debug, Clone, Deserialize, Serialize, Type)] #[derive(Debug, Clone, Deserialize, Serialize, Type)]

View File

@@ -1,4 +1,5 @@
use rog_anime::usb::get_anime_type; use rog_anime::usb::get_anime_type;
use rog_aura::layouts::KeyLayout;
use rog_dbus::RogDbusClientBlocking; use rog_dbus::RogDbusClientBlocking;
use rog_user::{ use rog_user::{
ctrl_anime::{CtrlAnime, CtrlAnimeInner}, ctrl_anime::{CtrlAnime, CtrlAnimeInner},
@@ -6,12 +7,18 @@ use rog_user::{
DBUS_NAME, DBUS_NAME,
}; };
use smol::Executor; use smol::Executor;
use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use std::{fs::OpenOptions, io::Read, path::PathBuf, sync::Arc};
use zbus::Connection; use zbus::Connection;
use std::sync::atomic::AtomicBool; 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<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" user daemon v{}", rog_user::VERSION); println!(" user daemon v{}", rog_user::VERSION);
println!(" rog-anime v{}", rog_anime::VERSION); println!(" rog-anime v{}", rog_anime::VERSION);
@@ -22,49 +29,83 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let supported = client.proxies().supported().supported_functions()?; let supported = client.proxies().supported().supported_functions()?;
let mut config = UserConfig::new(); let mut config = UserConfig::new();
config.load_config()?; config.load()?;
let executor = Executor::new(); let executor = Executor::new();
let early_return = Arc::new(AtomicBool::new(false)); let early_return = Arc::new(AtomicBool::new(false));
// Set up the anime data and run loop/thread // Set up the anime data and run loop/thread
if supported.anime_ctrl.0 { if supported.anime_ctrl.0 {
let anime_type = get_anime_type()?; if let Some(cfg) = config.active_anime {
let anime_config = UserAnimeConfig::load_config(config.active_anime)?; let anime_type = get_anime_type()?;
let anime = anime_config.create_anime(anime_type)?; let anime_config = UserAnimeConfig::load(cfg)?;
let anime_config = Arc::new(Mutex::new(anime_config)); let anime = anime_config.create(anime_type)?;
let anime_config = Arc::new(Mutex::new(anime_config));
executor executor
.spawn(async move { .spawn(async move {
// Create server // Create server
let mut connection = Connection::session().await.unwrap(); let mut connection = Connection::session().await.unwrap();
connection.request_name(DBUS_NAME).await.unwrap(); connection.request_name(DBUS_NAME).await.unwrap();
// Inner behind mutex required for thread safety // Inner behind mutex required for thread safety
let inner = Arc::new(Mutex::new( let inner = Arc::new(Mutex::new(
CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(), CtrlAnimeInner::new(anime, client, early_return.clone()).unwrap(),
)); ));
// Need new client object for dbus control part // Need new client object for dbus control part
let (client, _) = RogDbusClientBlocking::new().unwrap(); let (client, _) = RogDbusClientBlocking::new().unwrap();
let anime_control = let anime_control =
CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap(); CtrlAnime::new(anime_config, inner.clone(), client, early_return).unwrap();
anime_control.add_to_server(&mut connection).await; anime_control.add_to_server(&mut connection).await;
loop { loop {
if let Ok(inner) = inner.clone().try_lock() { if let Ok(inner) = inner.clone().try_lock() {
inner.run().ok(); inner.run().ok();
}
} }
} })
}) .detach();
.detach(); }
} }
// if supported.keyboard_led.per_key_led_mode { if supported.keyboard_led.per_key_led_mode {
// executor if let Some(cfg) = config.active_aura {
// .spawn(async move { let mut aura_config = UserAuraConfig::load(cfg)?;
// //
// }) // Find and load a matching layout for laptop
// .detach(); 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 { loop {
smol::block_on(executor.tick()); smol::block_on(executor.tick());

View File

@@ -5,28 +5,21 @@ use std::{
}; };
use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences, Vec2}; 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 serde_derive::{Deserialize, Serialize};
use crate::error::Error; use crate::error::Error;
#[derive(Debug, Deserialize, Serialize)] pub trait ConfigLoadSave<T> {
pub struct UserAnimeConfig { fn name(&self) -> String;
pub name: String,
pub anime: Vec<ActionLoader>,
}
impl UserAnimeConfig { fn default_with_name(name: String) -> T;
pub fn create_anime(&self, anime_type: AnimeType) -> Result<Sequences, Error> {
let mut seq = Sequences::new(anime_type);
for (idx, action) in self.anime.iter().enumerate() { fn write(&self) -> Result<(), Error>
seq.insert(idx, action)?; where
} Self: serde::Serialize,
{
Ok(seq)
}
pub fn write(&self) -> Result<(), Error> {
let mut path = if let Some(dir) = dirs::config_dir() { let mut path = if let Some(dir) = dirs::config_dir() {
dir dir
} else { } else {
@@ -37,7 +30,7 @@ impl UserAnimeConfig {
if !path.exists() { if !path.exists() {
create_dir(path.clone())?; create_dir(path.clone())?;
} }
let name = self.name.clone(); let name = self.name().clone();
path.push(name + ".cfg"); path.push(name + ".cfg");
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
@@ -51,7 +44,10 @@ impl UserAnimeConfig {
Ok(()) Ok(())
} }
pub fn load_config(name: String) -> Result<UserAnimeConfig, Error> { fn load(name: String) -> Result<T, Error>
where
T: DeserializeOwned + serde::Serialize,
{
let mut path = if let Some(dir) = dirs::config_dir() { let mut path = if let Some(dir) = dirs::config_dir() {
dir dir
} else { } else {
@@ -75,14 +71,11 @@ impl UserAnimeConfig {
if let Ok(read_len) = file.read_to_string(&mut buf) { if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 { if read_len == 0 {
let default = UserAnimeConfig { let default = Self::default_with_name(name);
name,
..Default::default()
};
let json = serde_json::to_string_pretty(&default).unwrap(); let json = serde_json::to_string_pretty(&default).unwrap();
file.write_all(json.as_bytes())?; file.write_all(json.as_bytes())?;
return Ok(default); return Ok(default);
} else if let Ok(data) = serde_json::from_str::<UserAnimeConfig>(&buf) { } else if let Ok(data) = serde_json::from_str::<T>(&buf) {
return Ok(data); return Ok(data);
} }
} }
@@ -90,6 +83,37 @@ impl UserAnimeConfig {
} }
} }
#[derive(Debug, Deserialize, Serialize)]
pub struct UserAnimeConfig {
pub name: String,
pub anime: Vec<ActionLoader>,
}
impl UserAnimeConfig {
pub fn create(&self, anime_type: AnimeType) -> Result<Sequences, Error> {
let mut seq = Sequences::new(anime_type);
for (idx, action) in self.anime.iter().enumerate() {
seq.insert(idx, action)?;
}
Ok(seq)
}
}
impl ConfigLoadSave<UserAnimeConfig> for UserAnimeConfig {
fn name(&self) -> String {
self.name.clone()
}
fn default_with_name(name: String) -> Self {
UserAnimeConfig {
name,
..Default::default()
}
}
}
impl Default for UserAnimeConfig { impl Default for UserAnimeConfig {
fn default() -> Self { fn default() -> Self {
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<UserAuraConfig> 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)] #[derive(Debug, Default, Deserialize, Serialize)]
#[serde(default)]
pub struct UserConfig { pub struct UserConfig {
/// Name of active anime config file in the user config directory /// Name of active anime config file in the user config directory
pub active_anime: String, pub active_anime: Option<String>,
/// Name of active aura config file in the user config directory
pub active_aura: Option<String>,
} }
impl UserConfig { impl UserConfig {
pub fn new() -> Self { pub fn new() -> Self {
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() { let mut path = if let Some(dir) = dirs::config_dir() {
dir dir
} else { } else {
@@ -192,6 +279,7 @@ impl UserConfig {
file.write_all(json.as_bytes())?; file.write_all(json.as_bytes())?;
} else if let Ok(data) = serde_json::from_str::<UserConfig>(&buf) { } else if let Ok(data) = serde_json::from_str::<UserConfig>(&buf) {
self.active_anime = data.active_anime; self.active_anime = data.active_anime;
self.active_aura = data.active_aura;
return Ok(()); return Ok(());
} }
} }

View File

@@ -20,4 +20,7 @@ dbus = ["zvariant"]
serde = "^1.0" serde = "^1.0"
serde_derive = "^1.0" serde_derive = "^1.0"
toml = { version = "^0.5", optional = true } toml = { version = "^0.5", optional = true }
zvariant = { version = "^3.0", optional = true } zvariant = { version = "^3.0", optional = true }
[dev-dependencies]
serde_json = "^1.0"

View File

@@ -10,7 +10,12 @@ pub mod gx502;
use crate::{error::Error, keys::Key}; use crate::{error::Error, keys::Key};
use serde::{Deserialize, Serialize}; 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)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct KeyLayout { pub struct KeyLayout {
@@ -53,6 +58,26 @@ impl KeyLayout {
pub fn rows_ref(&self) -> &[KeyRow] { pub fn rows_ref(&self) -> &[KeyRow] {
&self.rows &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<Self, Error> {
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)] #[derive(Debug, Deserialize, Serialize, Clone)]

View File

@@ -204,6 +204,9 @@ mod tests {
colour: Default::default(), colour: Default::default(),
}); });
let s = serde_json::to_string_pretty(&seq).unwrap();
println!("{s}");
seq.next_state(&layout); seq.next_state(&layout);
let packets = seq.create_packets(); let packets = seq.create_packets();

View File

@@ -60,8 +60,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut board_name = String::new(); let mut board_name = String::new();
file.read_to_string(&mut board_name)?; file.read_to_string(&mut board_name)?;
let mut layout = KeyLayout::ga401_layout(); // default
let mut path = PathBuf::from(DATA_DIR);
#[cfg(feature = "mocking")] #[cfg(feature = "mocking")]
{ {
board_name = "gl504".to_string(); board_name = "gl504".to_string();
@@ -69,18 +67,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
path.push("rog-aura"); path.push("rog-aura");
path.push("data"); path.push("data");
} }
path.push("layouts");
let path = path.as_path(); let layout = KeyLayout::find_layout(board_name.as_str(), PathBuf::from(DATA_DIR))
for p in fs::read_dir(path).map_err(|e| { .map_err(|e| {
println!("{:?}, {e}", path); println!("{BOARD_NAME}, {e}");
e })
})? { .unwrap_or(KeyLayout::ga401_layout());
let tmp = KeyLayout::from_file(&p?.path()).unwrap();
if tmp.matches(board_name.as_str()) {
layout = tmp;
break;
}
}
// Cheap method to alert to notifications rather than spinning a thread for each // Cheap method to alert to notifications rather than spinning a thread for each
// This is quite different when done in a retained mode app // This is quite different when done in a retained mode app