diff --git a/Cargo.toml b/Cargo.toml index 0afabc1..b7393bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ hidapi = "2.6.6" [features] access_hid_wrapper = [] +preset_file_saving = [] fast_controller_experimental = [] \ No newline at end of file diff --git a/src/color.rs b/src/color.rs index 580f67c..1c5f951 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,4 +1,4 @@ -#[derive(Default)] +#[derive(Default, PartialEq, Debug)] pub struct Color { pub red: u8, pub green: u8, diff --git a/src/lib.rs b/src/lib.rs index 74912e3..bb89875 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ mod color; mod hid_wrapper; mod keymap; mod rgb_controller; +#[cfg(test)] +mod tests; pub use color::Color; pub use hid_wrapper::HidWrapperError; @@ -11,6 +13,12 @@ pub use rgb_controller::effect_info::{ DirectionPolicy, EFFECTS, EffectInfo, HorizontalDirections, VerticalDirections, }; 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")] pub use hid_wrapper::HidWrapper; diff --git a/src/rgb_controller.rs b/src/rgb_controller.rs index 07882e1..93077ea 100644 --- a/src/rgb_controller.rs +++ b/src/rgb_controller.rs @@ -1,3 +1,4 @@ pub mod controller; pub mod effect_info; pub mod effects; +pub mod preset; diff --git a/src/rgb_controller/controller.rs b/src/rgb_controller/controller.rs index df1d901..3ee426f 100644 --- a/src/rgb_controller/controller.rs +++ b/src/rgb_controller/controller.rs @@ -2,8 +2,8 @@ use crate::Color; use crate::Effects; use crate::HidWrapperError; use crate::Keymap; +use crate::Preset; use crate::hid_wrapper::HidWrapper; -use crate::rgb_controller::effects::Effect; pub struct RGBController { hid_wrapper: HidWrapper, @@ -225,7 +225,7 @@ impl RGBController { speed: u8, direction: u8, keymap: Option<[[u8; 3]; 144]>, - ) -> Result { + ) -> Result { self.hid_wrapper.open_device()?; self.prepare_device()?; @@ -264,7 +264,7 @@ impl RGBController { self.hid_wrapper.close_device(); - Ok(Effect { + Ok(Preset { effect, color, full_color, @@ -280,7 +280,7 @@ impl RGBController { color: Color, brightness: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Static, color, @@ -298,7 +298,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::SingleOff, color, @@ -316,7 +316,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::SingleOn, color, @@ -334,7 +334,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Glittering, color, @@ -352,7 +352,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Rain, color, @@ -368,7 +368,7 @@ impl RGBController { &mut self, brightness: u8, speed: u8, - ) -> Result { + ) -> Result { self.set_effect( Effects::Colorful, Color::default(), @@ -386,7 +386,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Breath, color, @@ -402,7 +402,7 @@ impl RGBController { &mut self, brightness: u8, speed: u8, - ) -> Result { + ) -> Result { self.set_effect( Effects::Spectrum, Color::default(), @@ -420,7 +420,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::CentrifugalWave, color, @@ -439,7 +439,7 @@ impl RGBController { speed: u8, direction: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::VerticalWave, color, @@ -458,7 +458,7 @@ impl RGBController { speed: u8, direction: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::HorizontalWave, color, @@ -476,7 +476,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Rotating, color, @@ -494,7 +494,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Explosion, color, @@ -512,7 +512,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Launch, color, @@ -530,7 +530,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Ripples, color, @@ -548,7 +548,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Snake, color, @@ -566,7 +566,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Pulse, color, @@ -584,7 +584,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Tilt, color, @@ -602,7 +602,7 @@ impl RGBController { brightness: u8, speed: u8, full_color: Option, - ) -> Result { + ) -> Result { self.set_effect( Effects::Shuttle, color, @@ -618,7 +618,7 @@ impl RGBController { &mut self, brightness: u8, keymap: Keymap, - ) -> Result { + ) -> Result { self.set_effect( Effects::StaticPerKey, Color::default(), diff --git a/src/rgb_controller/effect_info.rs b/src/rgb_controller/effect_info.rs index 2164011..30f9d60 100644 --- a/src/rgb_controller/effect_info.rs +++ b/src/rgb_controller/effect_info.rs @@ -1,15 +1,17 @@ use crate::Effects; +#[derive(Clone)] pub struct EffectInfo<'a> { - name: &'a str, - effect: Effects, - can_set_color: bool, - can_set_speed: bool, - can_set_direction: DirectionPolicy, - can_set_full_color: bool, - requires_keymap: bool, + pub name: &'a str, + pub effect: Effects, + pub can_set_color: bool, + pub can_set_speed: bool, + pub can_set_direction: DirectionPolicy, + pub can_set_full_color: bool, + pub requires_keymap: bool, } +#[derive(PartialEq, Clone)] pub enum DirectionPolicy { None, Horizontal01, diff --git a/src/rgb_controller/effects.rs b/src/rgb_controller/effects.rs index 9b8ed7a..feecd94 100644 --- a/src/rgb_controller/effects.rs +++ b/src/rgb_controller/effects.rs @@ -4,10 +4,10 @@ use Effects::{ VerticalWave, }; -use crate::Color; - /// Effects of the keyboard +#[derive(Clone, Default, PartialEq, Debug)] pub enum Effects { + #[default] Static, SingleOn, 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]>, -} diff --git a/src/rgb_controller/preset.rs b/src/rgb_controller/preset.rs new file mode 100644 index 0000000..5084db7 --- /dev/null +++ b/src/rgb_controller/preset.rs @@ -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(), + ) + } +} diff --git a/src/save_preset.rs b/src/save_preset.rs new file mode 100644 index 0000000..524f785 --- /dev/null +++ b/src/save_preset.rs @@ -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, SavePresetError> { + let effect_info: EffectInfo = EFFECTS[usize::from(self.effect.to_u8() - 1)].clone(); + let mut buffer: Vec = 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 { + let mut buffer: Vec = vec![]; + match _file.read_to_end(&mut buffer) { + Ok(_) => { + let mut buffer: VecDeque = buffer.into(); + + Self::_decode_from_buffer(&mut buffer) + } + Err(err) => Err(LoadPresetError::OpenFileError(err)), + } + } + + pub(crate) fn _decode_from_buffer(buffer: &mut VecDeque) -> Result { + 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) + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..2fad9fd --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "preset_file_saving")] +mod preset_saving; diff --git a/src/tests/preset_saving.rs b/src/tests/preset_saving.rs new file mode 100644 index 0000000..f7ad297 --- /dev/null +++ b/src/tests/preset_saving.rs @@ -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 = 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 = 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 = 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 = 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"); + + } +}