mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
rog-aura: add basic per-key support
This commit is contained in:
@@ -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)
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
56
MANUAL.md
56
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": "<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/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"`
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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<dyn std::error::Error>> {
|
||||
println!(" user daemon v{}", rog_user::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 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());
|
||||
|
||||
@@ -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<ActionLoader>,
|
||||
}
|
||||
pub trait ConfigLoadSave<T> {
|
||||
fn name(&self) -> String;
|
||||
|
||||
impl UserAnimeConfig {
|
||||
pub fn create_anime(&self, anime_type: AnimeType) -> Result<Sequences, Error> {
|
||||
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<UserAnimeConfig, Error> {
|
||||
fn load(name: String) -> Result<T, Error>
|
||||
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::<UserAnimeConfig>(&buf) {
|
||||
} else if let Ok(data) = serde_json::from_str::<T>(&buf) {
|
||||
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 {
|
||||
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<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)]
|
||||
#[serde(default)]
|
||||
pub struct UserConfig {
|
||||
/// 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 {
|
||||
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::<UserConfig>(&buf) {
|
||||
self.active_anime = data.active_anime;
|
||||
self.active_aura = data.active_aura;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
zvariant = { version = "^3.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "^1.0"
|
||||
@@ -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<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)]
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -60,8 +60,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user