mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +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).
|
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
2
Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
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`
|
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"`
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user