From 1ab3ddf2735f12bcba25a8bff8f6f8c4cb50720e Mon Sep 17 00:00:00 2001 From: vextrixes <113197359+vextrixes@users.noreply.github.com> Date: Sun, 31 May 2026 12:20:48 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 133 ++++++ Cargo.toml | 7 + LICENSE.md | 24 ++ README.md | 1 + src/hid_wrapper.rs | 7 + src/hid_wrapper/constants.rs | 4 + src/hid_wrapper/error.rs | 40 ++ src/hid_wrapper/wrapper.rs | 108 +++++ src/lib.rs | 6 + src/rgb_controller.rs | 3 + src/rgb_controller/controller.rs | 695 ++++++++++++++++++++++++++++++ src/rgb_controller/effect_info.rs | 182 ++++++++ src/rgb_controller/effects.rs | 57 +++ 13 files changed, 1267 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 src/hid_wrapper.rs create mode 100644 src/hid_wrapper/constants.rs create mode 100644 src/hid_wrapper/error.rs create mode 100644 src/hid_wrapper/wrapper.rs create mode 100644 src/lib.rs create mode 100644 src/rgb_controller.rs create mode 100644 src/rgb_controller/controller.rs create mode 100644 src/rgb_controller/effect_info.rs create mode 100644 src/rgb_controller/effects.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..518123a --- /dev/null +++ b/.gitignore @@ -0,0 +1,133 @@ +pkg/ +*.tar + +### VisualStudioCode template +.vscode/* +#!.vscode/settings.json +#!.vscode/tasks.json +#!.vscode/launch.json +#!.vscode/extensions.json +#!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### Rust template +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4671017 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "gk1000_controller" +version = "1.0.0" +edition = "2024" + +[dependencies] +hidapi = "2.6.5" diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..43bc8ba --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# gk1000 \ No newline at end of file diff --git a/src/hid_wrapper.rs b/src/hid_wrapper.rs new file mode 100644 index 0000000..b843145 --- /dev/null +++ b/src/hid_wrapper.rs @@ -0,0 +1,7 @@ +extern crate hidapi; +mod constants; +pub mod error; +pub mod wrapper; + +pub use error::HidWrapperError; +pub use wrapper::HidWrapper; diff --git a/src/hid_wrapper/constants.rs b/src/hid_wrapper/constants.rs new file mode 100644 index 0000000..6108996 --- /dev/null +++ b/src/hid_wrapper/constants.rs @@ -0,0 +1,4 @@ +pub const VENDOR_ID: u16 = 0x0c45; +pub const PRODUCT_ID: u16 = 0x8006; +pub const MAX_SEND_REPORT_LEN: usize = 65; +pub const GET_REPORT_REPORT: [u8; MAX_SEND_REPORT_LEN] = [0u8; MAX_SEND_REPORT_LEN]; diff --git a/src/hid_wrapper/error.rs b/src/hid_wrapper/error.rs new file mode 100644 index 0000000..9b2cf4f --- /dev/null +++ b/src/hid_wrapper/error.rs @@ -0,0 +1,40 @@ +use hidapi::HidError; +use std::error::Error; +use std::fmt::Display; + +#[derive(Debug)] +pub enum HidWrapperError { + #[allow(dead_code)] + HidApiError(HidError), + NoHidDeviceError, +} + +impl Display for HidWrapperError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HidWrapperError::HidApiError(err) => { + write!(f, "Hid Api Error: {err}") + } + HidWrapperError::NoHidDeviceError => { + write!(f, "No Hid Device Found") + } + } + } +} + +impl Error for HidWrapperError {} + +impl From for HidWrapperError { + fn from(error: HidError) -> Self { + match error { + HidError::HidApiError { ref message } => { + if message == "No HID devices with requested VID/PID found in the system." { + HidWrapperError::NoHidDeviceError + } else { + HidWrapperError::HidApiError(error) + } + } + _ => HidWrapperError::HidApiError(error), + } + } +} diff --git a/src/hid_wrapper/wrapper.rs b/src/hid_wrapper/wrapper.rs new file mode 100644 index 0000000..e3b9402 --- /dev/null +++ b/src/hid_wrapper/wrapper.rs @@ -0,0 +1,108 @@ +use crate::hid_wrapper::constants::{ + GET_REPORT_REPORT, MAX_SEND_REPORT_LEN, PRODUCT_ID, VENDOR_ID, +}; +use crate::hid_wrapper::error::HidWrapperError; +use hidapi::{HidApi, HidDevice}; + +pub struct HidWrapper { + hid_api: HidApi, + hid_device: Option, +} + +impl Default for HidWrapper { + /// Makes a new instance of HidWrapper (No shit Sherlock) + /// + /// # Panics + /// If ```HidWrapperError::HidApiError``` is returned from new() + fn default() -> Self { + match Self::new() { + Ok(hid_wrapper) => hid_wrapper, + Err(err) => panic!("hid wrapper Default() failed to create new HidWrapper: {err}"), + } + } +} + +impl HidWrapper { + /// Makes a new instance of HidWrapper (No shit Sherlock) + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + pub fn new() -> Result { + Ok(HidWrapper { + hid_api: match HidApi::new() { + Ok(hidapi) => hidapi, + Err(err) => return Err(HidWrapperError::from(err)), + }, + hid_device: None, + }) + } + + pub fn is_device_connected(&mut self) -> Result { + match self.hid_api.reset_devices() { + Ok(_) => {} + Err(err) => return Err(HidWrapperError::from(err)), + }; + match self.hid_api.add_devices(VENDOR_ID, PRODUCT_ID) { + Ok(_) => {} + Err(err) => return Err(HidWrapperError::from(err)), + }; + Ok(self.hid_api.device_list().count() >= 1) + } + + /// Opens a device + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + pub fn open_device(&mut self) -> Result<(), HidWrapperError> { + self.hid_device = match self.hid_api.open(VENDOR_ID, PRODUCT_ID) { + Ok(hid_device) => Some(hid_device), + Err(err) => return Err(HidWrapperError::from(err)), + }; + Ok(()) + } + + /// Drops the device + /// + pub fn close_device(&mut self) { + self.hid_device = None; + } + + /// Sends a set report (65 bytes len) + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + pub fn send_report(&mut self, data: &[u8]) -> Result<(), HidWrapperError> { + match &self.hid_device { + Some(hid_device) => { + let mut out = [0; MAX_SEND_REPORT_LEN]; + out[1..1 + data.len()].copy_from_slice(data); + match hid_device.send_feature_report(&out) { + Ok(_) => Ok(()), + Err(err) => Err(HidWrapperError::from(err)), + } + } + None => Err(HidWrapperError::NoHidDeviceError), + } + } + + /// Sends a get report (64 bytes len) + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + pub fn get_report(&mut self) -> Result { + match &self.hid_device { + Some(hid_device) => { + let mut request = GET_REPORT_REPORT; + request[1] = 64; + match hid_device.get_feature_report(&mut request) { + Ok(usize) => Ok(usize), + Err(err) => Err(HidWrapperError::from(err)), + } + } + None => Err(HidWrapperError::NoHidDeviceError), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cbde4cf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +mod hid_wrapper; +mod rgb_controller; + +pub use hid_wrapper::HidWrapperError; +pub use rgb_controller::controller::RGBController; +pub use rgb_controller::effects::Effects; diff --git a/src/rgb_controller.rs b/src/rgb_controller.rs new file mode 100644 index 0000000..07882e1 --- /dev/null +++ b/src/rgb_controller.rs @@ -0,0 +1,3 @@ +pub mod controller; +pub mod effect_info; +pub mod effects; diff --git a/src/rgb_controller/controller.rs b/src/rgb_controller/controller.rs new file mode 100644 index 0000000..8500fac --- /dev/null +++ b/src/rgb_controller/controller.rs @@ -0,0 +1,695 @@ +use crate::Effects; +use crate::HidWrapperError; +use crate::hid_wrapper::HidWrapper; + +pub struct RGBController { + hid_wrapper: HidWrapper, +} + +impl Default for RGBController { + /// Makes a new instance of HidWrapper (No shit Sherlock) + /// + /// # Panics + /// if ```HidWrapperError::HidApiError``` is returned from new() + fn default() -> Self { + match Self::new() { + Ok(hid_wrapper) => hid_wrapper, + Err(err) => panic!("RGBController Default() failed to create new RGBController: {err}"), + } + } +} + +impl RGBController { + /// Makes a new instance of HidWrapper (No shit Sherlock) + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + pub fn new() -> Result { + Ok(RGBController { + hid_wrapper: HidWrapper::new()?, + }) + } + + pub fn is_device_connected(&mut self) -> Result { + self.hid_wrapper.is_device_connected() + } + + /// Starts the RGB conversation + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + fn prepare_device(&mut self) -> Result<(), HidWrapperError> { + self.hid_wrapper.send_report(&[0x04, 0xab])?; + self.hid_wrapper.get_report()?; + + self.hid_wrapper.send_report(&[ + 0x26, 0x36, 0xf2, 0x27, 0xb7, 0x2a, 0x56, 0x77, 0x98, 0x59, 0x9c, 0x6d, 0x9f, 0xc6, + 0xb, 0x44, 0xec, 0xa8, 0x47, 0x21, 0xe2, 0x65, 0x76, 0xa3, 0x70, 0xbe, 0xa2, 0xbb, + 0x95, 0x47, 0x5e, 0xbe, 0x23, 0x1b, 0xad, 0xb8, 0xc3, 0xcd, 0x1f, 0x4, 0x39, 0xa2, + 0x3d, 0x19, 0xe9, 0xfd, 0x27, 0xa7, 0x9a, 0x1a, 0x7c, 0xe3, 0x7b, 0xff, 0x7a, 0x1e, + 0x63, 0xb0, 0x67, 0x9d, 0x5d, 0xa3, 0x2f, 0xa5, + ])?; + + self.hid_wrapper.send_report(&[0x04, 0x02])?; + self.hid_wrapper.get_report()?; + + self.hid_wrapper + .send_report(&[0x04, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12])?; + self.hid_wrapper.get_report()?; + Ok(()) + } + + /// Some middle bytes IDK what to name ts + /// Is a required part of the RGB conversation + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + fn mid(&mut self) -> Result<(), HidWrapperError> { + //TODO: Rename func and possibly change packets depending on mode + self.hid_wrapper.send_report(&[ + 0x1, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x2, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x3, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x4, 0x5a, 0xff, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + ])?; + + self.hid_wrapper.send_report(&[ + 0x5, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x6, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x7, 0x5a, 0xff, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x8, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + ])?; + + self.hid_wrapper.send_report(&[ + 0x9, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0xa, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0xb, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0xc, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + ])?; + + self.hid_wrapper.send_report(&[ + 0xd, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0xe, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0xf, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x10, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + ])?; + + self.hid_wrapper.send_report(&[ + 0x11, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x12, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x13, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x1, 0x10, 0xc, 0x0, 0x0, 0x0, 0xaa, 0x55, + 0x80, 0xff, 0xff, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x55, + ])?; + + Ok(()) + } + + /// Sets colors for each key (only if StaticPerKey effect is set) base on a 2d array + /// Is a required part of the RGB conversation + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + fn custom_keys(&mut self, keymap: &[[u8; 3]; 144]) -> Result<(), HidWrapperError> { + for i in 0..9 { + //Magic num 9 == len of keymap / 16 + self.hid_wrapper.send_report(&[ + 0x80, + keymap[i * 16][0], + keymap[i * 16][1], + keymap[i * 16][2], + 0x80, + keymap[i * 16 + 1][0], + keymap[i * 16 + 1][1], + keymap[i * 16 + 1][2], + 0x80, + keymap[i * 16 + 2][0], + keymap[i * 16 + 2][1], + keymap[i * 16 + 2][2], + 0x80, + keymap[i * 16 + 3][0], + keymap[i * 16 + 3][1], + keymap[i * 16 + 3][2], + 0x80, + keymap[i * 16 + 4][0], + keymap[i * 16 + 4][1], + keymap[i * 16 + 4][2], + 0x80, + keymap[i * 16 + 5][0], + keymap[i * 16 + 5][1], + keymap[i * 16 + 5][2], + 0x80, + keymap[i * 16 + 6][0], + keymap[i * 16 + 6][1], + keymap[i * 16 + 6][2], + 0x80, + keymap[i * 16 + 7][0], + keymap[i * 16 + 7][1], + keymap[i * 16 + 7][2], + 0x80, + keymap[i * 16 + 8][0], + keymap[i * 16 + 8][1], + keymap[i * 16 + 8][2], + 0x80, + keymap[i * 16 + 9][0], + keymap[i * 16 + 9][1], + keymap[i * 16 + 9][2], + 0x80, + keymap[i * 16 + 10][0], + keymap[i * 16 + 10][1], + keymap[i * 16 + 10][2], + 0x80, + keymap[i * 16 + 11][0], + keymap[i * 16 + 11][1], + keymap[i * 16 + 11][2], + 0x80, + keymap[i * 16 + 12][0], + keymap[i * 16 + 12][1], + keymap[i * 16 + 12][2], + 0x80, + keymap[i * 16 + 13][0], + keymap[i * 16 + 13][1], + keymap[i * 16 + 13][2], + 0x80, + keymap[i * 16 + 14][0], + keymap[i * 16 + 14][1], + keymap[i * 16 + 14][2], + 0x80, + keymap[i * 16 + 15][0], + keymap[i * 16 + 15][1], + keymap[i * 16 + 15][2], + ])?; + } + Ok(()) + } + + /// Applies an effect to the device + /// Is a required part of the RGB conversation + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + fn apply_to_device(&mut self) -> Result<(), HidWrapperError> { + self.hid_wrapper.send_report(&[0x04, 0x02])?; + self.hid_wrapper.get_report()?; + + self.hid_wrapper.send_report(&[0x04, 0xf0])?; + + Ok(()) + } + + /// Sets an effect + /// + /// # Errors + /// ```HidWrapperError::HidApiError``` + /// ```HidWrapperError::NoHidDeviceError``` + #[allow(clippy::too_many_arguments)] + pub fn set_effect( + &mut self, + effect: Effects, + red: u8, + green: u8, + blue: u8, + full_color: bool, + brightness: u8, + speed: u8, + direction: u8, + keymap: Option<[[u8; 3]; 144]>, + ) -> Result<(), HidWrapperError> { + self.hid_wrapper.open_device()?; + + self.prepare_device()?; + + self.mid()?; + + self.hid_wrapper.send_report(&[])?; + self.hid_wrapper.send_report(&[])?; + self.hid_wrapper.send_report(&[])?; + + match keymap { + Some(map) => self.custom_keys(&map)?, + None => self.custom_keys(&[[0, 255, 0]; 144])?, + } + + self.hid_wrapper.send_report(&[ + effect.to_u8(), + red, + green, + blue, + 0, + 0, + 0, + 0, + u8::from(full_color), + brightness.clamp(0, 16), + speed.clamp(1, 16), + direction.clamp(0, 3), + 0, + 0, + 0xaa, + 0x55, + ])?; + + self.apply_to_device()?; + + self.hid_wrapper.close_device(); + + Ok(()) + } + pub fn set_static_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Static, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + 1, + 0, + None, + ) + } + + pub fn set_single_off_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::SingleOff, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_single_on_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::SingleOn, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_glittering_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Glittering, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_rain_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Rain, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_colorful_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Colorful, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_breath_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Breath, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_spectrum_effect( + &mut self, + brightness: u8, + speed: u8, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Spectrum, + 0, + 0, + 0, + false, + brightness, + speed, + 0, + None, + ) + } + + pub fn set_centrifugal_wave_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::CentrifugalWave, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn set_vertical_wave_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + direction: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::VerticalWave, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + direction.clamp(2, 3), + None, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn set_horizontal_wave_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + direction: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::HorizontalWave, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + direction.clamp(0, 1), + None, + ) + } + + pub fn set_rotating_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Rotating, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_explosion_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Explosion, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_launch_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Launch, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_ripples_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Ripples, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_snake_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Snake, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_pulse_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Pulse, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_tilt_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Tilt, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_shuttle_effect( + &mut self, + red: u8, + green: u8, + blue: u8, + brightness: u8, + speed: u8, + full_color: Option, + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::Shuttle, + red, + green, + blue, + full_color.unwrap_or(false), + brightness, + speed, + 0, + None, + ) + } + + pub fn set_static_per_key_effect( + &mut self, + brightness: u8, + keymap: [[u8; 3]; 144], + ) -> Result<(), HidWrapperError> { + self.set_effect( + Effects::StaticPerKey, + 0, + 0, + 0, + false, + brightness, + 1, + 0, + Some(keymap), + ) + } +} diff --git a/src/rgb_controller/effect_info.rs b/src/rgb_controller/effect_info.rs new file mode 100644 index 0000000..dd807b0 --- /dev/null +++ b/src/rgb_controller/effect_info.rs @@ -0,0 +1,182 @@ +use crate::Effects; + +#[allow(dead_code)] +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, +} + +#[allow(dead_code)] +pub enum DirectionPolicy { + None, + Horizontal01, + Vertical23, +} + +#[allow(dead_code)] +pub const EFFECTS: [EffectInfo; 20] = [ + EffectInfo { + name: "Static", + effect: Effects::Static, + can_set_color: true, + can_set_speed: false, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "SingleOn", + effect: Effects::SingleOn, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "SingleOff", + effect: Effects::SingleOff, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Glittering", + effect: Effects::Glittering, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Rain", + effect: Effects::Rain, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Colorful", + effect: Effects::Colorful, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Breath", + effect: Effects::Breath, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Spectrum", + effect: Effects::Spectrum, + can_set_color: false, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: false, + }, + EffectInfo { + name: "CentrifugalWave", + effect: Effects::CentrifugalWave, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "VerticalWave", + effect: Effects::VerticalWave, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::Vertical23, + can_set_full_color: true, + }, + EffectInfo { + name: "HorizontalWave", + effect: Effects::HorizontalWave, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::Horizontal01, + can_set_full_color: true, + }, + EffectInfo { + name: "Rotating", + effect: Effects::Rotating, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Explosion", + effect: Effects::Explosion, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Launch", + effect: Effects::Launch, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Ripples", + effect: Effects::Ripples, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Snake", + effect: Effects::Snake, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Pulse", + effect: Effects::Pulse, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Tilt", + effect: Effects::Tilt, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "Shuttle", + effect: Effects::Shuttle, + can_set_color: true, + can_set_speed: true, + can_set_direction: DirectionPolicy::None, + can_set_full_color: true, + }, + EffectInfo { + name: "StaticPerKey", + effect: Effects::StaticPerKey, + can_set_color: false, + can_set_speed: false, + can_set_direction: DirectionPolicy::None, + can_set_full_color: false, + }, +]; diff --git a/src/rgb_controller/effects.rs b/src/rgb_controller/effects.rs new file mode 100644 index 0000000..b6f45de --- /dev/null +++ b/src/rgb_controller/effects.rs @@ -0,0 +1,57 @@ +use Effects::{ + Breath, CentrifugalWave, Colorful, Explosion, Glittering, HorizontalWave, Launch, Pulse, Rain, + Ripples, Rotating, Shuttle, SingleOff, SingleOn, Snake, Spectrum, Static, StaticPerKey, Tilt, + VerticalWave, +}; + +/// Effects of the keyboard +pub enum Effects { + Static, + SingleOn, + SingleOff, + Glittering, + Rain, + Colorful, + Breath, + Spectrum, + CentrifugalWave, + VerticalWave, + HorizontalWave, + Rotating, + Explosion, + Launch, + Ripples, + Snake, + Pulse, + Tilt, + Shuttle, + StaticPerKey, +} + +impl Effects { + /// Returns an u8 of the effect used for communication with the device + pub fn to_u8(&self) -> u8 { + match self { + Static => 1, + SingleOn => 2, + SingleOff => 3, + Glittering => 4, + Rain => 5, + Colorful => 6, + Breath => 7, + Spectrum => 8, + CentrifugalWave => 9, + VerticalWave => 10, + HorizontalWave => 11, + Rotating => 12, + Explosion => 13, + Launch => 14, + Ripples => 15, + Snake => 16, + Pulse => 17, + Tilt => 18, + Shuttle => 19, + StaticPerKey => 20, + } + } +}