mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Advanced Aura feature
Groundwork for 'advanced' aura modes Add single zone + Doom light flash Fix mocking for ROGCC Better prepare & change to mapping of keyboard layouts to models and functions Refactor and begin using new key layout stuff Enable first arg to rogcc to set layout in mocking feature mode Complete refactor of key layouts, and to RON serde
This commit is contained in:
554
rog-aura/src/layouts.rs
Normal file
554
rog-aura/src/layouts.rs
Normal file
@@ -0,0 +1,554 @@
|
||||
//! A series of pre-defined layouts. These were mostly used to generate an
|
||||
//! editable config.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::slice::Iter;
|
||||
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::advanced::LedCode;
|
||||
use crate::aura_detection::LaptopLedData;
|
||||
use crate::error::Error;
|
||||
use crate::{AdvancedAuraType, AuraModeNum, AuraZone};
|
||||
|
||||
/// The `key_type` plays a role in effects (eventually). You could for example
|
||||
/// add a `ShapeType::Spacing` to pad out an effect, such as a laserbeam across
|
||||
/// a row so that it doesn't appear to *jump* across a gap
|
||||
///
|
||||
/// w=1.0, h=1.0 should be considered the size of a typical key like 'A'
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub enum KeyShape {
|
||||
Led {
|
||||
width: f32,
|
||||
height: f32,
|
||||
pad_left: f32,
|
||||
pad_right: f32,
|
||||
pad_top: f32,
|
||||
pad_bottom: f32,
|
||||
},
|
||||
Blank {
|
||||
width: f32,
|
||||
height: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl KeyShape {
|
||||
pub fn new_led(
|
||||
width: f32,
|
||||
height: f32,
|
||||
pad_left: f32,
|
||||
pad_right: f32,
|
||||
pad_top: f32,
|
||||
pad_bottom: f32,
|
||||
) -> Self {
|
||||
Self::Led {
|
||||
width,
|
||||
height,
|
||||
pad_left,
|
||||
pad_right,
|
||||
pad_top,
|
||||
pad_bottom,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_blank(width: f32, height: f32) -> Self {
|
||||
Self::Blank { width, height }
|
||||
}
|
||||
|
||||
/// Scale the shape up/down. Intended for use in UI on a clone
|
||||
pub fn scale(&mut self, scale: f32) {
|
||||
match self {
|
||||
KeyShape::Led {
|
||||
width,
|
||||
height,
|
||||
pad_left,
|
||||
pad_right,
|
||||
pad_top,
|
||||
pad_bottom,
|
||||
} => {
|
||||
*width *= scale;
|
||||
*height *= scale;
|
||||
*pad_left *= scale;
|
||||
*pad_right *= scale;
|
||||
*pad_top *= scale;
|
||||
*pad_bottom *= scale;
|
||||
}
|
||||
KeyShape::Blank { width, height } => {
|
||||
*width *= scale;
|
||||
*height *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The first `Key` will determine the row height.
|
||||
///
|
||||
/// Every row is considered to start a x=0, with the first row being y=0,
|
||||
/// and following rows starting after the previous row_y+pad_top and
|
||||
/// row_x+pad_left
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct KeyRow {
|
||||
pad_left: f32,
|
||||
pad_top: f32,
|
||||
/// The `Key` is what provides an RGB index location in the final USB
|
||||
/// packets
|
||||
row: Vec<(LedCode, String)>,
|
||||
/// The final data structure merged key_shapes and rows
|
||||
#[serde(skip)]
|
||||
built_row: Vec<(LedCode, KeyShape)>,
|
||||
}
|
||||
|
||||
impl KeyRow {
|
||||
pub fn new(pad_left: f32, pad_top: f32, row: Vec<(LedCode, String)>) -> Self {
|
||||
Self {
|
||||
pad_left,
|
||||
pad_top,
|
||||
row,
|
||||
built_row: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row(&self) -> Iter<'_, (LedCode, KeyShape)> {
|
||||
self.built_row.iter()
|
||||
}
|
||||
|
||||
pub fn row_ref(&self) -> &[(LedCode, KeyShape)] {
|
||||
&self.built_row
|
||||
}
|
||||
|
||||
/// Find and return the heightest height of this row
|
||||
pub fn height(&self) -> f32 {
|
||||
if self.built_row.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let mut h = 0.0;
|
||||
for k in &self.built_row {
|
||||
let height = match &k.1 {
|
||||
KeyShape::Led {
|
||||
height,
|
||||
pad_top,
|
||||
pad_bottom,
|
||||
..
|
||||
} => height + pad_top + pad_bottom,
|
||||
KeyShape::Blank { height, .. } => *height,
|
||||
};
|
||||
|
||||
if h < height {
|
||||
h = height
|
||||
}
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
/// Return the total row width
|
||||
pub fn width(&self) -> f32 {
|
||||
if self.built_row.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let mut w = 0.0;
|
||||
for k in &self.built_row {
|
||||
match &k.1 {
|
||||
KeyShape::Led {
|
||||
width,
|
||||
pad_left,
|
||||
pad_right,
|
||||
..
|
||||
} => w += width + pad_left + pad_right,
|
||||
KeyShape::Blank { width, .. } => w += width,
|
||||
}
|
||||
}
|
||||
w
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct KeyLayout {
|
||||
/// Localization of this keyboard layout
|
||||
locale: String,
|
||||
/// The shapes of keys used
|
||||
key_shapes: HashMap<String, KeyShape>,
|
||||
/// The rows of keys of this layout
|
||||
key_rows: Vec<KeyRow>,
|
||||
/// Should be copied from the `LaptopLedData` as laptops may have the same
|
||||
/// layout, but different EC features
|
||||
#[serde(skip)]
|
||||
basic_modes: Vec<AuraModeNum>,
|
||||
/// Should be copied from the `LaptopLedData` as laptops may have the same
|
||||
/// layout, but different EC features
|
||||
#[serde(skip)]
|
||||
basic_zones: Vec<AuraZone>,
|
||||
/// Paired with the key selection in UI. Determines if individual keys are
|
||||
/// selectable, zones, or single zone.
|
||||
///
|
||||
/// Should be copied from the `LaptopLedData` as laptops may have the same
|
||||
/// layout, but different EC features.
|
||||
#[serde(skip)]
|
||||
advanced_type: AdvancedAuraType,
|
||||
}
|
||||
|
||||
impl KeyLayout {
|
||||
pub fn from_file(path: &Path) -> Result<Self, Error> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(|e| Error::IoPath(path.to_string_lossy().to_string(), e))?;
|
||||
let mut buf = String::new();
|
||||
let read_len = file
|
||||
.read_to_string(&mut buf)
|
||||
.map_err(|e| Error::IoPath(path.to_string_lossy().to_string(), e))?;
|
||||
if read_len == 0 {
|
||||
Err(Error::IoPath(
|
||||
path.to_string_lossy().to_string(),
|
||||
std::io::ErrorKind::InvalidData.into(),
|
||||
))
|
||||
} else {
|
||||
let mut data = ron::from_str::<Self>(&buf)?;
|
||||
|
||||
let mut unused = HashSet::new();
|
||||
for k in data.key_shapes.keys() {
|
||||
unused.insert(k);
|
||||
}
|
||||
|
||||
let rows = &mut data.key_rows;
|
||||
for row in rows {
|
||||
for k in &mut row.row {
|
||||
if let Some(shape) = data.key_shapes.get(&k.1) {
|
||||
row.built_row.push((k.0, shape.clone()));
|
||||
unused.remove(&k.1);
|
||||
} else {
|
||||
warn!("Key {:?} was missing matching shape {}", k.0, k.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !unused.is_empty() {
|
||||
warn!("The layout {path:?} had unused shapes {unused:?}",);
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rows(&self) -> Iter<KeyRow> {
|
||||
self.key_rows.iter()
|
||||
}
|
||||
|
||||
pub fn rows_ref(&self) -> &[KeyRow] {
|
||||
&self.key_rows
|
||||
}
|
||||
|
||||
pub fn basic_modes(&self) -> &[AuraModeNum] {
|
||||
&self.basic_modes
|
||||
}
|
||||
|
||||
pub fn basic_zones(&self) -> &[AuraZone] {
|
||||
&self.basic_zones
|
||||
}
|
||||
|
||||
pub fn advanced_type(&self) -> &AdvancedAuraType {
|
||||
&self.advanced_type
|
||||
}
|
||||
|
||||
pub fn max_height(&self) -> f32 {
|
||||
let mut height = 0.0;
|
||||
for r in &self.key_rows {
|
||||
height += r.height();
|
||||
}
|
||||
height
|
||||
}
|
||||
|
||||
pub fn max_width(&self) -> f32 {
|
||||
let mut width = 0.0;
|
||||
for r in &self.key_rows {
|
||||
let tmp = r.width();
|
||||
if width < tmp {
|
||||
width = tmp
|
||||
}
|
||||
}
|
||||
width
|
||||
}
|
||||
|
||||
/// Find a layout matching the name in `LaptopLedData` in the provided dir
|
||||
pub fn find_layout(led_data: LaptopLedData, mut data_path: PathBuf) -> Result<Self, Error> {
|
||||
// TODO: locales
|
||||
let layout_file = format!("{}_US.ron", led_data.layout_name);
|
||||
data_path.push("layouts");
|
||||
data_path.push(layout_file);
|
||||
let path = data_path.as_path();
|
||||
|
||||
let mut tmp = KeyLayout::from_file(path)?;
|
||||
tmp.basic_modes = led_data.basic_modes;
|
||||
tmp.basic_zones = led_data.basic_zones;
|
||||
tmp.advanced_type = led_data.advanced_type;
|
||||
|
||||
Ok(tmp)
|
||||
}
|
||||
|
||||
pub fn layout_files(mut data_path: PathBuf) -> Result<Vec<PathBuf>, Error> {
|
||||
data_path.push("layouts");
|
||||
let path = data_path.as_path();
|
||||
let mut files = Vec::new();
|
||||
fs::read_dir(path)
|
||||
.map_err(|e| {
|
||||
println!("{:?}, {e}", path);
|
||||
e
|
||||
})
|
||||
.unwrap()
|
||||
.for_each(|p| {
|
||||
if let Ok(p) = p {
|
||||
files.push(p.path());
|
||||
}
|
||||
});
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyLayout {
|
||||
pub fn default_layout() -> Self {
|
||||
Self {
|
||||
locale: "US".to_owned(),
|
||||
basic_modes: vec![
|
||||
AuraModeNum::Static,
|
||||
AuraModeNum::Breathe,
|
||||
AuraModeNum::Pulse,
|
||||
],
|
||||
basic_zones: vec![AuraZone::None],
|
||||
advanced_type: AdvancedAuraType::None,
|
||||
key_shapes: HashMap::from([(
|
||||
"regular".to_owned(),
|
||||
KeyShape::new_led(1.0, 1.0, 0.1, 0.1, 0.1, 0.1),
|
||||
)]),
|
||||
key_rows: vec![
|
||||
KeyRow::new(
|
||||
0.1,
|
||||
0.1,
|
||||
vec![
|
||||
(LedCode::Esc, "regular".to_owned()),
|
||||
(LedCode::F1, "regular".to_owned()),
|
||||
(LedCode::F2, "regular".to_owned()),
|
||||
(LedCode::F3, "regular".to_owned()),
|
||||
(LedCode::F4, "regular".to_owned()),
|
||||
// not sure which key to put here
|
||||
(LedCode::F5, "regular".to_owned()),
|
||||
(LedCode::F6, "regular".to_owned()),
|
||||
(LedCode::F7, "regular".to_owned()),
|
||||
(LedCode::F8, "regular".to_owned()),
|
||||
(LedCode::F9, "regular".to_owned()),
|
||||
(LedCode::F10, "regular".to_owned()),
|
||||
(LedCode::F11, "regular".to_owned()),
|
||||
(LedCode::F12, "regular".to_owned()),
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.1,
|
||||
0.1,
|
||||
vec![
|
||||
(LedCode::Tilde, "regular".to_owned()),
|
||||
(LedCode::N1, "regular".to_owned()),
|
||||
(LedCode::N2, "regular".to_owned()),
|
||||
(LedCode::N3, "regular".to_owned()),
|
||||
(LedCode::N4, "regular".to_owned()),
|
||||
(LedCode::N5, "regular".to_owned()),
|
||||
(LedCode::N6, "regular".to_owned()),
|
||||
(LedCode::N7, "regular".to_owned()),
|
||||
(LedCode::N8, "regular".to_owned()),
|
||||
(LedCode::N9, "regular".to_owned()),
|
||||
(LedCode::N0, "regular".to_owned()),
|
||||
(LedCode::Hyphen, "regular".to_owned()),
|
||||
(LedCode::Equals, "regular".to_owned()),
|
||||
(LedCode::Backspace, "regular".to_owned()),
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.1,
|
||||
0.1,
|
||||
vec![
|
||||
(LedCode::Tab, "regular".to_owned()),
|
||||
(LedCode::Q, "regular".to_owned()),
|
||||
(LedCode::W, "regular".to_owned()),
|
||||
(LedCode::E, "regular".to_owned()),
|
||||
(LedCode::R, "regular".to_owned()),
|
||||
(LedCode::T, "regular".to_owned()),
|
||||
(LedCode::Y, "regular".to_owned()),
|
||||
(LedCode::U, "regular".to_owned()),
|
||||
(LedCode::I, "regular".to_owned()),
|
||||
(LedCode::O, "regular".to_owned()),
|
||||
(LedCode::P, "regular".to_owned()),
|
||||
(LedCode::LBracket, "regular".to_owned()),
|
||||
(LedCode::RBracket, "regular".to_owned()),
|
||||
(LedCode::BackSlash, "regular".to_owned()),
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.1,
|
||||
0.1,
|
||||
vec![
|
||||
(LedCode::Caps, "regular".to_owned()),
|
||||
(LedCode::A, "regular".to_owned()),
|
||||
(LedCode::S, "regular".to_owned()),
|
||||
(LedCode::D, "regular".to_owned()),
|
||||
(LedCode::F, "regular".to_owned()),
|
||||
(LedCode::G, "regular".to_owned()),
|
||||
(LedCode::H, "regular".to_owned()),
|
||||
(LedCode::J, "regular".to_owned()),
|
||||
(LedCode::K, "regular".to_owned()),
|
||||
(LedCode::L, "regular".to_owned()),
|
||||
(LedCode::SemiColon, "regular".to_owned()),
|
||||
(LedCode::Quote, "regular".to_owned()),
|
||||
(LedCode::Return, "regular".to_owned()),
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.1,
|
||||
0.1,
|
||||
vec![
|
||||
(LedCode::LShift, "regular".to_owned()),
|
||||
(LedCode::Z, "regular".to_owned()),
|
||||
(LedCode::X, "regular".to_owned()),
|
||||
(LedCode::C, "regular".to_owned()),
|
||||
(LedCode::V, "regular".to_owned()),
|
||||
(LedCode::B, "regular".to_owned()),
|
||||
(LedCode::N, "regular".to_owned()),
|
||||
(LedCode::M, "regular".to_owned()),
|
||||
(LedCode::Comma, "regular".to_owned()),
|
||||
(LedCode::Period, "regular".to_owned()),
|
||||
(LedCode::FwdSlash, "regular".to_owned()),
|
||||
(LedCode::Rshift, "regular".to_owned()),
|
||||
],
|
||||
),
|
||||
KeyRow::new(
|
||||
0.1,
|
||||
0.1,
|
||||
vec![
|
||||
(LedCode::LCtrl, "regular".to_owned()),
|
||||
(LedCode::LFn, "regular".to_owned()),
|
||||
(LedCode::Meta, "regular".to_owned()),
|
||||
(LedCode::LAlt, "regular".to_owned()),
|
||||
(LedCode::Spacebar, "regular".to_owned()),
|
||||
(LedCode::RAlt, "regular".to_owned()),
|
||||
(LedCode::PrtSc, "regular".to_owned()),
|
||||
(LedCode::RCtrl, "regular".to_owned()),
|
||||
],
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::aura_detection::LedSupportFile;
|
||||
use crate::layouts::KeyLayout;
|
||||
|
||||
#[test]
|
||||
fn check_parse_all() {
|
||||
const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
let mut data_path = PathBuf::from(DATA_DIR);
|
||||
|
||||
data_path.push("data");
|
||||
data_path.push("layouts");
|
||||
let path = data_path.as_path();
|
||||
let mut buf = String::new();
|
||||
for p in fs::read_dir(path)
|
||||
.map_err(|e| {
|
||||
println!("{:?}, {e}", path);
|
||||
e
|
||||
})
|
||||
.unwrap()
|
||||
{
|
||||
let path = p.unwrap().path();
|
||||
let mut file = OpenOptions::new().read(true).open(&path).unwrap();
|
||||
file.read_to_string(&mut buf).unwrap();
|
||||
|
||||
let data: KeyLayout = ron::from_str(&buf).unwrap();
|
||||
|
||||
let mut unused = HashSet::new();
|
||||
for k in data.key_shapes.keys() {
|
||||
unused.insert(k);
|
||||
}
|
||||
|
||||
let rows = &data.key_rows;
|
||||
for row in rows {
|
||||
for k in &row.row {
|
||||
if data.key_shapes.get(&k.1).is_some() {
|
||||
unused.remove(&k.1);
|
||||
} else {
|
||||
panic!("Key {:?} was missing matching shape {}", k.0, k.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !unused.is_empty() {
|
||||
panic!("The layout {path:?} had unused shapes {unused:?}",);
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
// println!(
|
||||
// "RON: {}",
|
||||
// ron::ser::to_string_pretty(&tmp,
|
||||
// PrettyConfig::new().depth_limit(4)).unwrap() );
|
||||
|
||||
// let mut data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
// data.push("data/aura-support2.json");
|
||||
|
||||
// let mut file =
|
||||
// OpenOptions::new().write(true).create(true).truncate(true).open(&
|
||||
// data).unwrap(); file.write_all(json.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_layout_file_links() {
|
||||
const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
let mut data_path = PathBuf::from(DATA_DIR);
|
||||
data_path.push("data");
|
||||
data_path.push("aura_support.ron");
|
||||
|
||||
let mut buf = String::new();
|
||||
let mut file = OpenOptions::new().read(true).open(&data_path).unwrap();
|
||||
file.read_to_string(&mut buf).unwrap();
|
||||
let data: LedSupportFile = ron::from_str(&buf).unwrap();
|
||||
|
||||
data_path.pop();
|
||||
data_path.push("layouts");
|
||||
data_path.push("loop_prep");
|
||||
|
||||
for config in data.get().iter().rev() {
|
||||
buf.clear();
|
||||
|
||||
let layout_file = format!("{}_US.ron", config.layout_name);
|
||||
data_path.pop();
|
||||
data_path.push(&layout_file);
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&data_path)
|
||||
.map_err(|e| {
|
||||
panic!(
|
||||
"Error checking {data_path:?} for {} : {e:?}",
|
||||
config.board_name
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
if let Err(e) = file.read_to_string(&mut buf) {
|
||||
panic!(
|
||||
"Error checking {data_path:?} for {} : {e:?}",
|
||||
config.board_name
|
||||
)
|
||||
}
|
||||
if let Err(e) = ron::from_str::<KeyLayout>(&buf) {
|
||||
panic!("Error checking {data_path:?} : {e:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user