Merge pull request #1 from vextrixes/preset-saving

Preset saving
This commit is contained in:
vextrixes authored and GitHub committed 2026-07-08 01:11:18 +02:00
commit 1509425ca8
11 files changed
+484 -43

No files matched your search

+1
View File
@@ -12,4 +12,5 @@ hidapi = "2.6.6"
[features] [features]
access_hid_wrapper = [] access_hid_wrapper = []
preset_file_saving = []
fast_controller_experimental = [] fast_controller_experimental = []
+1 -1
View File
@@ -1,4 +1,4 @@
#[derive(Default)] #[derive(Default, PartialEq, Debug)]
pub struct Color { pub struct Color {
pub red: u8, pub red: u8,
pub green: u8, pub green: u8,
+8
View File
@@ -2,6 +2,8 @@ mod color;
mod hid_wrapper; mod hid_wrapper;
mod keymap; mod keymap;
mod rgb_controller; mod rgb_controller;
#[cfg(test)]
mod tests;
pub use color::Color; pub use color::Color;
pub use hid_wrapper::HidWrapperError; pub use hid_wrapper::HidWrapperError;
@@ -11,6 +13,12 @@ pub use rgb_controller::effect_info::{
DirectionPolicy, EFFECTS, EffectInfo, HorizontalDirections, VerticalDirections, DirectionPolicy, EFFECTS, EffectInfo, HorizontalDirections, VerticalDirections,
}; };
pub use rgb_controller::effects::Effects; pub use rgb_controller::effects::Effects;
pub use rgb_controller::preset::Preset;
#[cfg(feature = "preset_file_saving")]
mod save_preset;
#[cfg(feature = "preset_file_saving")]
pub use save_preset::{LoadPresetError, SavePresetError};
#[cfg(feature = "access_hid_wrapper")] #[cfg(feature = "access_hid_wrapper")]
pub use hid_wrapper::HidWrapper; pub use hid_wrapper::HidWrapper;
+1
View File
@@ -1,3 +1,4 @@
pub mod controller; pub mod controller;
pub mod effect_info; pub mod effect_info;
pub mod effects; pub mod effects;
pub mod preset;
+23 -23
View File
@@ -2,8 +2,8 @@ use crate::Color;
use crate::Effects; use crate::Effects;
use crate::HidWrapperError; use crate::HidWrapperError;
use crate::Keymap; use crate::Keymap;
use crate::Preset;
use crate::hid_wrapper::HidWrapper; use crate::hid_wrapper::HidWrapper;
use crate::rgb_controller::effects::Effect;
pub struct RGBController { pub struct RGBController {
hid_wrapper: HidWrapper, hid_wrapper: HidWrapper,
@@ -225,7 +225,7 @@ impl RGBController {
speed: u8, speed: u8,
direction: u8, direction: u8,
keymap: Option<[[u8; 3]; 144]>, keymap: Option<[[u8; 3]; 144]>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.hid_wrapper.open_device()?; self.hid_wrapper.open_device()?;
self.prepare_device()?; self.prepare_device()?;
@@ -264,7 +264,7 @@ impl RGBController {
self.hid_wrapper.close_device(); self.hid_wrapper.close_device();
Ok(Effect { Ok(Preset {
effect, effect,
color, color,
full_color, full_color,
@@ -280,7 +280,7 @@ impl RGBController {
color: Color, color: Color,
brightness: u8, brightness: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Static, Effects::Static,
color, color,
@@ -298,7 +298,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::SingleOff, Effects::SingleOff,
color, color,
@@ -316,7 +316,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::SingleOn, Effects::SingleOn,
color, color,
@@ -334,7 +334,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Glittering, Effects::Glittering,
color, color,
@@ -352,7 +352,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Rain, Effects::Rain,
color, color,
@@ -368,7 +368,7 @@ impl RGBController {
&mut self, &mut self,
brightness: u8, brightness: u8,
speed: u8, speed: u8,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Colorful, Effects::Colorful,
Color::default(), Color::default(),
@@ -386,7 +386,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Breath, Effects::Breath,
color, color,
@@ -402,7 +402,7 @@ impl RGBController {
&mut self, &mut self,
brightness: u8, brightness: u8,
speed: u8, speed: u8,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Spectrum, Effects::Spectrum,
Color::default(), Color::default(),
@@ -420,7 +420,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::CentrifugalWave, Effects::CentrifugalWave,
color, color,
@@ -439,7 +439,7 @@ impl RGBController {
speed: u8, speed: u8,
direction: u8, direction: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::VerticalWave, Effects::VerticalWave,
color, color,
@@ -458,7 +458,7 @@ impl RGBController {
speed: u8, speed: u8,
direction: u8, direction: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::HorizontalWave, Effects::HorizontalWave,
color, color,
@@ -476,7 +476,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Rotating, Effects::Rotating,
color, color,
@@ -494,7 +494,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Explosion, Effects::Explosion,
color, color,
@@ -512,7 +512,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Launch, Effects::Launch,
color, color,
@@ -530,7 +530,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Ripples, Effects::Ripples,
color, color,
@@ -548,7 +548,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Snake, Effects::Snake,
color, color,
@@ -566,7 +566,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Pulse, Effects::Pulse,
color, color,
@@ -584,7 +584,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Tilt, Effects::Tilt,
color, color,
@@ -602,7 +602,7 @@ impl RGBController {
brightness: u8, brightness: u8,
speed: u8, speed: u8,
full_color: Option<bool>, full_color: Option<bool>,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::Shuttle, Effects::Shuttle,
color, color,
@@ -618,7 +618,7 @@ impl RGBController {
&mut self, &mut self,
brightness: u8, brightness: u8,
keymap: Keymap, keymap: Keymap,
) -> Result<Effect, HidWrapperError> { ) -> Result<Preset, HidWrapperError> {
self.set_effect( self.set_effect(
Effects::StaticPerKey, Effects::StaticPerKey,
Color::default(), Color::default(),
+9 -7
View File
@@ -1,15 +1,17 @@
use crate::Effects; use crate::Effects;
#[derive(Clone)]
pub struct EffectInfo<'a> { pub struct EffectInfo<'a> {
name: &'a str, pub name: &'a str,
effect: Effects, pub effect: Effects,
can_set_color: bool, pub can_set_color: bool,
can_set_speed: bool, pub can_set_speed: bool,
can_set_direction: DirectionPolicy, pub can_set_direction: DirectionPolicy,
can_set_full_color: bool, pub can_set_full_color: bool,
requires_keymap: bool, pub requires_keymap: bool,
} }
#[derive(PartialEq, Clone)]
pub enum DirectionPolicy { pub enum DirectionPolicy {
None, None,
Horizontal01, Horizontal01,
+2 -12
View File
@@ -4,10 +4,10 @@ use Effects::{
VerticalWave, VerticalWave,
}; };
use crate::Color;
/// Effects of the keyboard /// Effects of the keyboard
#[derive(Clone, Default, PartialEq, Debug)]
pub enum Effects { pub enum Effects {
#[default]
Static, Static,
SingleOn, SingleOn,
SingleOff, SingleOff,
@@ -83,13 +83,3 @@ impl Effects {
} }
} }
} }
pub struct Effect {
pub effect: Effects,
pub color: Color,
pub full_color: bool,
pub brightness: u8,
pub speed: u8,
pub direction: u8,
pub keymap: Option<[[u8; 3]; 144]>,
}
+53
View File
@@ -0,0 +1,53 @@
use crate::Color;
use crate::Effects;
#[derive(PartialEq, Debug)]
pub struct Preset {
pub effect: Effects,
pub color: Color,
pub full_color: bool,
pub brightness: u8,
pub speed: u8,
pub direction: u8,
pub keymap: Option<[[u8; 3]; 144]>,
}
impl Preset {
pub fn new(
effect: Effects,
color: Color,
full_color: bool,
brightness: u8,
speed: u8,
direction: u8,
keymap: Option<[[u8; 3]; 144]>,
) -> Self {
Self {
effect,
color,
full_color,
brightness: brightness.clamp(0, 16),
speed: speed.clamp(1, 16),
direction: direction.clamp(0, 3),
keymap,
}
}
pub fn is_valid(&self) -> bool {
self.brightness <= 16 && self.direction <= 3 && 1 <= self.speed && self.speed <= 16
}
}
impl Default for Preset {
fn default() -> Self {
Self::new(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
}
}
+148
View File
@@ -0,0 +1,148 @@
use crate::{DirectionPolicy, EFFECTS, EffectInfo, Effects, Preset};
use std::{
collections::VecDeque,
fs::File,
io::{Read, Write},
};
#[derive(Debug)]
pub enum SavePresetError {
InvalidPreset,
FileError(std::io::Error),
NoKeymapGiven,
}
#[derive(Debug)]
pub enum LoadPresetError {
InvalidOrCorruptedFile,
OpenFileError(std::io::Error),
}
const MAGIC_NUMBER_1: u8 = 0b10100100;
const MAGIC_NUMBER_2: u8 = 0b00010000;
const VERSION: u8 = 1;
impl Preset {
pub fn save_to_file(&self, _file: &mut File) -> Result<(), SavePresetError> {
if !self.is_valid() {
return Err(SavePresetError::InvalidPreset);
}
match _file.write_all(&self._encode_to_buffer()?) {
Err(err) => Err(SavePresetError::FileError(err)),
_ => Ok(()),
}
}
pub(crate) fn _encode_to_buffer(&self) -> Result<Vec<u8>, SavePresetError> {
let effect_info: EffectInfo = EFFECTS[usize::from(self.effect.to_u8() - 1)].clone();
let mut buffer: Vec<u8> = vec![MAGIC_NUMBER_1, MAGIC_NUMBER_2, VERSION];
buffer
.push(self.effect.to_u8() + (u8::from(self.full_color) * 128) + (self.direction * 32)); //Effect id, full_color, and direction in a single byte
if !self.full_color && effect_info.can_set_color {
buffer.extend(&self.color.to_array());
}
if effect_info.can_set_speed {
buffer.push(self.speed);
}
buffer.push(self.brightness);
if effect_info.requires_keymap {
for key_color in self.keymap.ok_or(SavePresetError::NoKeymapGiven)?.iter() {
buffer.extend(key_color);
}
}
Ok(buffer)
}
pub fn load_from_file(_file: &mut File) -> Result<Self, LoadPresetError> {
let mut buffer: Vec<u8> = vec![];
match _file.read_to_end(&mut buffer) {
Ok(_) => {
let mut buffer: VecDeque<u8> = buffer.into();
Self::_decode_from_buffer(&mut buffer)
}
Err(err) => Err(LoadPresetError::OpenFileError(err)),
}
}
pub(crate) fn _decode_from_buffer(buffer: &mut VecDeque<u8>) -> Result<Self, LoadPresetError> {
let mut save: Preset = Default::default();
if buffer.pop_front() != Some(MAGIC_NUMBER_1) || buffer.pop_front() != Some(MAGIC_NUMBER_2)
{
return Err(LoadPresetError::InvalidOrCorruptedFile);
}
if buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?
!= VERSION
{
return Err(LoadPresetError::InvalidOrCorruptedFile);
//todo!("TOO BAD, Invalid VERSION")
}
let effect_byte = buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?;
save.full_color = (effect_byte & 0b1000_0000) != 0;
let direction: u8 = (effect_byte >> 5) & 0b11;
let effect_id: u8 = effect_byte & 0b0001_1111;
save.effect = Effects::from_u8(effect_id).ok_or(LoadPresetError::InvalidOrCorruptedFile)?;
let effect_info: EffectInfo = EFFECTS[usize::from(save.effect.to_u8() - 1)].clone();
if !save.full_color && effect_info.can_set_color {
save.color.red = buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?;
save.color.green = buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?;
save.color.blue = buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?;
}
if effect_info.can_set_direction != DirectionPolicy::None {
save.direction = direction;
}
if effect_info.can_set_speed {
save.speed = buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?;
}
save.brightness = buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?;
if effect_info.requires_keymap {
let mut keymap_buffer: [[u8; 3]; 144] = [[0; 3]; 144];
#[allow(clippy::needless_range_loop)]
for i in 0..keymap_buffer.len() {
keymap_buffer[i] = [
buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?,
buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?,
buffer
.pop_front()
.ok_or(LoadPresetError::InvalidOrCorruptedFile)?,
];
}
save.keymap = Some(keymap_buffer);
}
Ok(save)
}
}
+2
View File
@@ -0,0 +1,2 @@
#[cfg(feature = "preset_file_saving")]
mod preset_saving;
+236
View File
@@ -0,0 +1,236 @@
#[allow(unused_imports)]
mod tests {
use std::collections::VecDeque;
use crate::{Keymap, Preset};
#[test]
fn test_encode_decode() {
let preset = Preset::new(
crate::Effects::Static,
crate::Color {
red: 0,
green: 0,
blue: 0,
},
true,
6,
1,
0,
None,
);
let mut buffer: VecDeque<u8> = preset._encode_to_buffer().expect("error encoding").into();
assert_eq!(
preset,
Preset::_decode_from_buffer(&mut buffer).expect("error decoding")
);
let preset = Preset::new(
crate::Effects::VerticalWave,
crate::Color {
red: 0,
green: 0,
blue: 0,
},
true,
2,
9,
2,
None,
);
let mut buffer: VecDeque<u8> = preset._encode_to_buffer().expect("error encoding").into();
assert_eq!(
preset,
Preset::_decode_from_buffer(&mut buffer).expect("error decoding")
);
let preset = Preset::new(
crate::Effects::HorizontalWave,
crate::Color {
red: 67,
green: 69,
blue: 255,
},
false,
2,
9,
1,
None,
);
let mut buffer: VecDeque<u8> = preset._encode_to_buffer().expect("error encoding").into();
assert_eq!(
preset,
Preset::_decode_from_buffer(&mut buffer).expect("error decoding")
);
let mut keymap: Keymap = Default::default();
keymap.set_all_keys(crate::Color {
red: 255,
green: 9,
blue: 7,
});
keymap.set_key(
crate::KeymapKeys::C,
crate::Color {
red: 111,
green: 222,
blue: 133,
},
);
keymap.set_key(
crate::KeymapKeys::NumPlus,
crate::Color {
red: 112,
green: 223,
blue: 134,
},
);
let preset = Preset::new(
crate::Effects::StaticPerKey,
crate::Color {
red: 0,
green: 0,
blue: 0,
},
false,
16,
1,
0,
Some(keymap.map()),
);
let mut buffer: VecDeque<u8> = preset._encode_to_buffer().expect("error encoding").into();
assert_eq!(
preset,
Preset::_decode_from_buffer(&mut buffer).expect("error decoding")
)
}
#[cfg(target_os = "linux")]
#[test]
fn test_save_load() {
use std::fs::{File};
use std::fs;
let preset = Preset::new(
crate::Effects::Static,
crate::Color {
red: 0,
green: 0,
blue: 0,
},
true,
6,
1,
0,
None,
);
preset
.save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file"))
.expect("failed to save");
assert_eq!(
preset,
Preset::load_from_file(
&mut File::open("/tmp/test_file.gk1k").expect("failed to open file")
)
.expect("error loading from file")
);
let preset = Preset::new(
crate::Effects::VerticalWave,
crate::Color {
red: 0,
green: 0,
blue: 0,
},
true,
2,
9,
2,
None,
);
preset
.save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file"))
.expect("failed to save");
assert_eq!(
preset,
Preset::load_from_file(
&mut File::open("/tmp/test_file.gk1k").expect("failed to open file")
)
.expect("error loading from file")
);
let preset = Preset::new(
crate::Effects::HorizontalWave,
crate::Color {
red: 67,
green: 69,
blue: 255,
},
false,
2,
9,
1,
None,
);
preset
.save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file"))
.expect("failed to save");
assert_eq!(
preset,
Preset::load_from_file(
&mut File::open("/tmp/test_file.gk1k").expect("failed to open file")
)
.expect("error loading from file")
);
let mut keymap: Keymap = Default::default();
keymap.set_all_keys(crate::Color {
red: 255,
green: 9,
blue: 7,
});
keymap.set_key(
crate::KeymapKeys::C,
crate::Color {
red: 111,
green: 222,
blue: 133,
},
);
keymap.set_key(
crate::KeymapKeys::NumPlus,
crate::Color {
red: 112,
green: 223,
blue: 134,
},
);
let preset = Preset::new(
crate::Effects::StaticPerKey,
crate::Color {
red: 0,
green: 0,
blue: 0,
},
false,
16,
1,
0,
Some(keymap.map()),
);
preset
.save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file"))
.expect("failed to save");
assert_eq!(
preset,
Preset::load_from_file(
&mut File::open("/tmp/test_file.gk1k").expect("failed to open file")
)
.expect("error loading from file")
);
#[allow(unused_must_use)]
fs::remove_file("/tmp/test_file.gk1k");
}
}