1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::path::PathBuf;
4
5pub use serde;
7pub use bincode;
8pub use tokio;
9pub use tracing;
10
11pub mod ipc_client;
13
14#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
16pub enum DeviceType {
17 Keyboard,
19 Mouse,
21 Gamepad,
23 Keypad,
25 Other,
27}
28
29impl fmt::Display for DeviceType {
30 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31 match self {
32 DeviceType::Keyboard => write!(f, "Keyboard"),
33 DeviceType::Mouse => write!(f, "Mouse"),
34 DeviceType::Gamepad => write!(f, "Gamepad"),
35 DeviceType::Keypad => write!(f, "Keypad"),
36 DeviceType::Other => write!(f, "Other"),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
43pub struct DeviceInfo {
44 pub name: String,
45 pub path: PathBuf,
46 pub vendor_id: u16,
47 pub product_id: u16,
48 pub phys: String,
49 pub device_type: DeviceType,
50}
51
52impl fmt::Display for DeviceInfo {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 write!(f, "{} (VID: {:04X}, PID: {:04X}, Type: {})",
55 self.name, self.vendor_id, self.product_id, self.device_type)
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
61pub struct KeyCombo {
62 pub keys: Vec<u16>, pub modifiers: Vec<u16>, }
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
71pub struct HotkeyBinding {
72 pub modifiers: Vec<String>,
76
77 pub key: String,
81
82 pub profile_name: String,
84
85 pub device_id: Option<String>,
89
90 pub layer_id: Option<usize>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
101pub struct AutoSwitchRule {
102 pub app_id: String,
108
109 pub profile_name: String,
111
112 pub device_id: Option<String>,
116
117 pub layer_id: Option<usize>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
125pub enum Action {
126 KeyPress(u16),
128 KeyRelease(u16),
130 Delay(u32),
132 Execute(String),
134 Type(String),
136 MousePress(u16),
138 MouseRelease(u16),
140 MouseMove(i32, i32),
142 MouseScroll(i32),
144 AnalogMove {
148 axis_code: u16,
149 normalized: f32,
150 },
151}
152
153impl fmt::Display for Action {
154 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155 match self {
156 Action::KeyPress(code) => write!(f, "KeyPress({})", code),
157 Action::KeyRelease(code) => write!(f, "KeyRelease({})", code),
158 Action::Delay(ms) => write!(f, "Delay({}ms)", ms),
159 Action::Execute(cmd) => write!(f, "Execute({})", cmd),
160 Action::Type(text) => write!(f, "Type({})", text),
161 Action::MousePress(btn) => write!(f, "MousePress({})", btn),
162 Action::MouseRelease(btn) => write!(f, "MouseRelease({})", btn),
163 Action::MouseMove(x, y) => write!(f, "MouseMove({}, {})", x, y),
164 Action::MouseScroll(amount) => write!(f, "MouseScroll({})", amount),
165 Action::AnalogMove { axis_code, normalized } => {
166 let axis_name = match axis_code {
167 61000 => "X",
168 61001 => "Y",
169 61002 => "Z",
170 61003 => "RX",
171 61004 => "RY",
172 61005 => "RZ",
173 _ => "UNKNOWN",
174 };
175 write!(f, "Analog({}, {}={:.2})", axis_name, axis_code, normalized)
176 }
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
183pub struct MacroSettings {
184 pub latency_offset_ms: u32,
185 pub jitter_pct: f32,
186 pub capture_mouse: bool,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
191pub struct MacroEntry {
192 pub name: String,
193 pub trigger: KeyCombo,
194 pub actions: Vec<Action>,
195 pub device_id: Option<String>, pub enabled: bool,
197 #[serde(default)]
198 pub humanize: bool,
199 #[serde(default)]
200 pub capture_mouse: bool,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
205pub struct RemapProfileInfo {
206 pub name: String,
208 pub description: Option<String>,
210 pub remap_count: usize,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
216pub struct RemapEntry {
217 pub from_key: String,
219 pub to_key: String,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
229pub struct DeviceCapabilities {
230 pub has_analog_stick: bool,
232
233 pub has_hat_switch: bool,
235
236 pub joystick_button_count: usize,
238
239 pub led_zones: Vec<String>,
241}
242
243#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
247#[serde(rename_all = "lowercase")]
248pub enum LayerMode {
249 Hold,
254
255 Toggle,
260}
261
262impl Default for LayerMode {
263 fn default() -> Self {
264 LayerMode::Hold
265 }
266}
267
268impl fmt::Display for LayerMode {
269 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270 match self {
271 LayerMode::Hold => write!(f, "hold"),
272 LayerMode::Toggle => write!(f, "toggle"),
273 }
274 }
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
282pub struct CommonLayerConfig {
283 #[serde(default)]
285 pub layer_id: usize,
286
287 #[serde(default)]
289 pub name: String,
290
291 #[serde(default)]
293 pub mode: LayerMode,
294
295 #[serde(default = "default_layer_color")]
297 pub led_color: (u8, u8, u8),
298
299 #[serde(default)]
301 pub led_zone: Option<LedZone>,
302}
303
304fn default_layer_color() -> (u8, u8, u8) {
306 (0, 0, 255) }
308
309#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
314pub struct LayerConfigInfo {
315 pub layer_id: usize,
317
318 pub name: String,
320
321 pub mode: LayerMode,
323
324 pub remap_count: usize,
326
327 #[serde(default = "default_layer_color")]
329 pub led_color: (u8, u8, u8),
330
331 #[serde(default)]
333 pub led_zone: Option<LedZone>,
334}
335
336impl Default for DeviceCapabilities {
337 fn default() -> Self {
338 Self {
339 has_analog_stick: false,
340 has_hat_switch: false,
341 joystick_button_count: 0,
342 led_zones: Vec::new(),
343 }
344 }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
353pub struct AnalogCalibrationConfig {
354 pub deadzone: f32,
356
357 pub deadzone_shape: String,
359
360 pub sensitivity: String,
362
363 pub sensitivity_multiplier: f32,
365
366 pub range_min: i32,
368
369 pub range_max: i32,
371
372 pub invert_x: bool,
374
375 pub invert_y: bool,
377
378 #[serde(default = "default_exponent")]
380 pub exponent: f32,
381
382 #[serde(default)]
384 pub analog_mode: AnalogMode,
385
386 #[serde(default)]
388 pub camera_output_mode: Option<CameraOutputMode>,
389}
390
391fn default_exponent() -> f32 {
392 2.0
393}
394
395impl Default for AnalogCalibrationConfig {
396 fn default() -> Self {
397 Self {
398 deadzone: 0.15,
399 deadzone_shape: "circular".to_string(),
400 sensitivity: "linear".to_string(),
401 sensitivity_multiplier: 1.0,
402 range_min: -32768,
403 range_max: 32767,
404 invert_x: false,
405 invert_y: false,
406 exponent: 2.0,
407 analog_mode: AnalogMode::Disabled,
408 camera_output_mode: None,
409 }
410 }
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub enum Request {
416 GetDevices,
418
419 SetMacro {
421 device_path: String,
422 macro_entry: MacroEntry,
423 },
424
425 ListMacros,
427
428 DeleteMacro {
430 name: String,
431 },
432
433 ReloadConfig,
435
436 LedSet {
438 device_path: String,
439 color: (u8, u8, u8), },
441
442 RecordMacro {
444 device_path: String,
445 name: String,
446 capture_mouse: bool,
447 },
448
449 StopRecording,
451
452 TestMacro {
454 name: String,
455 },
456
457 GetStatus,
459
460 SaveProfile {
462 name: String,
463 },
464
465 LoadProfile {
467 name: String,
468 },
469
470 ListProfiles,
472
473 DeleteProfile {
475 name: String,
476 },
477
478 GenerateToken {
480 client_id: String,
481 },
482
483 Authenticate {
485 token: String,
486 },
487
488 ExecuteMacro {
490 name: String,
491 },
492
493 GrabDevice {
495 device_path: String,
496 },
497
498 UngrabDevice {
500 device_path: String,
501 },
502
503 GetDeviceProfiles {
505 device_id: String, },
507
508 ActivateProfile {
510 device_id: String, profile_name: String,
512 },
513
514 DeactivateProfile {
516 device_id: String, },
518
519 GetActiveProfile {
521 device_id: String, },
523
524 GetActiveRemaps {
526 device_path: String,
527 },
528
529 ListRemapProfiles {
531 device_path: String,
532 },
533
534 ActivateRemapProfile {
536 device_path: String,
537 profile_name: String,
538 },
539
540 DeactivateRemapProfile {
542 device_path: String,
543 },
544
545 GetDeviceCapabilities {
547 device_path: String,
548 },
549
550 GetActiveLayer {
552 device_id: String,
553 },
554
555 SetLayerConfig {
557 device_id: String,
558 layer_id: usize,
559 config: LayerConfigInfo,
560 },
561
562 ActivateLayer {
564 device_id: String,
565 layer_id: usize,
566 mode: LayerMode,
567 },
568
569 ListLayers {
571 device_id: String,
572 },
573
574 SetAnalogSensitivity {
576 device_id: String,
577 sensitivity: f32, },
579
580 GetAnalogSensitivity {
582 device_id: String,
583 },
584
585 SetAnalogResponseCurve {
587 device_id: String,
588 curve: String, },
590
591 GetAnalogResponseCurve {
593 device_id: String,
594 },
595
596 SetAnalogDeadzone {
598 device_id: String,
599 percentage: u8, },
601
602 GetAnalogDeadzone {
604 device_id: String,
605 },
606
607 SetAnalogDeadzoneXY {
609 device_id: String,
610 x_percentage: u8, y_percentage: u8, },
613
614 GetAnalogDeadzoneXY {
616 device_id: String,
617 },
618
619 SetAnalogOuterDeadzoneXY {
621 device_id: String,
622 x_percentage: u8, y_percentage: u8, },
625
626 GetAnalogOuterDeadzoneXY {
628 device_id: String,
629 },
630
631 SetAnalogDpadMode {
633 device_id: String,
634 mode: String, },
636
637 GetAnalogDpadMode {
639 device_id: String,
640 },
641
642 SetLedColor {
644 device_id: String,
645 zone: LedZone,
646 red: u8,
647 green: u8,
648 blue: u8,
649 },
650
651 GetLedColor {
653 device_id: String,
654 zone: LedZone,
655 },
656
657 GetAllLedColors {
659 device_id: String,
660 },
661
662 SetLedBrightness {
664 device_id: String,
665 zone: Option<LedZone>, brightness: u8, },
668
669 GetLedBrightness {
671 device_id: String,
672 zone: Option<LedZone>,
673 },
674
675 SetLedPattern {
677 device_id: String,
678 pattern: LedPattern,
679 },
680
681 GetLedPattern {
683 device_id: String,
684 },
685
686 FocusChanged {
688 app_id: String, window_title: Option<String>, },
691
692 RegisterHotkey {
694 device_id: String,
695 binding: HotkeyBinding,
696 },
697
698 ListHotkeys {
700 device_id: String,
701 },
702
703 RemoveHotkey {
705 device_id: String,
706 key: String,
707 modifiers: Vec<String>,
708 },
709
710 SetAutoSwitchRules {
712 rules: Vec<AutoSwitchRule>,
713 },
714
715 GetAutoSwitchRules,
717
718 GetAnalogCalibration {
720 device_id: String,
721 layer_id: usize,
722 },
723
724 SetAnalogCalibration {
726 device_id: String,
727 layer_id: usize,
728 calibration: AnalogCalibrationConfig,
729 },
730
731 SubscribeAnalogInput {
733 device_id: String,
734 },
735
736 UnsubscribeAnalogInput {
738 device_id: String,
739 },
740
741 SetMacroSettings(MacroSettings),
743
744 GetMacroSettings,
746}
747
748#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
753#[serde(rename_all = "lowercase")]
754pub enum AnalogMode {
755 Disabled,
757 Dpad,
759 Gamepad,
761 Camera,
763 Mouse,
765 Wasd,
767}
768
769impl fmt::Display for AnalogMode {
770 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771 match self {
772 AnalogMode::Disabled => write!(f, "Disabled"),
773 AnalogMode::Dpad => write!(f, "D-pad (Arrows)"),
774 AnalogMode::Gamepad => write!(f, "Gamepad"),
775 AnalogMode::Camera => write!(f, "Camera"),
776 AnalogMode::Mouse => write!(f, "Mouse"),
777 AnalogMode::Wasd => write!(f, "WASD"),
778 }
779 }
780}
781
782impl Default for AnalogMode {
783 fn default() -> Self {
784 AnalogMode::Disabled
785 }
786}
787
788impl AnalogMode {
789 pub const ALL: [AnalogMode; 6] = [
791 AnalogMode::Disabled,
792 AnalogMode::Dpad,
793 AnalogMode::Gamepad,
794 AnalogMode::Wasd,
795 AnalogMode::Mouse,
796 AnalogMode::Camera,
797 ];
798}
799
800#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
805#[serde(rename_all = "lowercase")]
806pub enum CameraOutputMode {
807 Scroll,
809 Keys,
811}
812
813impl fmt::Display for CameraOutputMode {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 match self {
816 CameraOutputMode::Scroll => write!(f, "Scroll"),
817 CameraOutputMode::Keys => write!(f, "Key Repeat"),
818 }
819 }
820}
821
822impl Default for CameraOutputMode {
823 fn default() -> Self {
824 CameraOutputMode::Scroll
825 }
826}
827
828impl CameraOutputMode {
829 pub const ALL: [CameraOutputMode; 2] = [
831 CameraOutputMode::Scroll,
832 CameraOutputMode::Keys,
833 ];
834}
835
836#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
838pub enum LedPattern {
839 Static,
841 Breathing,
843 Rainbow,
845 RainbowWave,
847}
848
849#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
851pub enum LedZone {
852 Side,
854 Logo,
856 Keys,
858 Thumbstick,
860 All,
862 Global,
864}
865
866#[derive(Debug, Clone, Serialize, Deserialize)]
868pub struct StatusInfo {
869 pub version: String,
870 pub uptime_seconds: u64,
871 pub devices_count: usize,
872 pub macros_count: usize,
873}
874
875#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
877pub enum Response {
878 Devices(Vec<DeviceInfo>),
880
881 Macros(Vec<MacroEntry>),
883
884 Ack,
886
887 Status {
889 version: String,
890 uptime_seconds: u64,
891 devices_count: usize,
892 macros_count: usize,
893 },
894
895 RecordingStarted {
897 device_path: String,
898 name: String,
899 },
900
901 RecordingStopped {
903 macro_entry: MacroEntry,
904 },
905
906 Profiles(Vec<String>),
908
909 ProfileLoaded {
911 name: String,
912 macros_count: usize,
913 },
914
915 ProfileSaved {
917 name: String,
918 macros_count: usize,
919 },
920
921 Error(String),
923
924 Token(String),
926
927 Authenticated,
929
930 DeviceProfiles {
932 device_id: String,
933 profiles: Vec<String>,
934 },
935
936 ProfileActivated {
938 device_id: String,
939 profile_name: String,
940 },
941
942 ProfileDeactivated {
944 device_id: String,
945 },
946
947 ActiveProfile {
949 device_id: String,
950 profile_name: Option<String>,
951 },
952
953 ActiveRemaps {
955 device_path: String,
956 profile_name: Option<String>,
957 remaps: Vec<RemapEntry>,
958 },
959
960 RemapProfiles {
962 device_path: String,
963 profiles: Vec<RemapProfileInfo>,
964 },
965
966 RemapProfileActivated {
968 device_path: String,
969 profile_name: String,
970 },
971
972 RemapProfileDeactivated {
974 device_path: String,
975 },
976
977 DeviceCapabilities {
979 device_path: String,
980 capabilities: DeviceCapabilities,
981 },
982
983 ActiveLayer {
985 device_id: String,
986 layer_id: usize,
987 layer_name: String,
988 },
989
990 LayerConfigured {
992 device_id: String,
993 layer_id: usize,
994 },
995
996 LayerList {
998 device_id: String,
999 layers: Vec<LayerConfigInfo>,
1000 },
1001
1002 AnalogSensitivitySet {
1004 device_id: String,
1005 sensitivity: f32,
1006 },
1007
1008 AnalogSensitivity {
1010 device_id: String,
1011 sensitivity: f32,
1012 },
1013
1014 AnalogResponseCurveSet {
1016 device_id: String,
1017 curve: String,
1018 },
1019
1020 AnalogResponseCurve {
1022 device_id: String,
1023 curve: String,
1024 },
1025
1026 AnalogDeadzoneSet {
1028 device_id: String,
1029 percentage: u8,
1030 },
1031
1032 AnalogDeadzone {
1034 device_id: String,
1035 percentage: u8,
1036 },
1037
1038 AnalogDeadzoneXYSet {
1040 device_id: String,
1041 x_percentage: u8,
1042 y_percentage: u8,
1043 },
1044
1045 AnalogDeadzoneXY {
1047 device_id: String,
1048 x_percentage: u8,
1049 y_percentage: u8,
1050 },
1051
1052 AnalogOuterDeadzoneXYSet {
1054 device_id: String,
1055 x_percentage: u8,
1056 y_percentage: u8,
1057 },
1058
1059 AnalogOuterDeadzoneXY {
1061 device_id: String,
1062 x_percentage: u8,
1063 y_percentage: u8,
1064 },
1065
1066 AnalogDpadModeSet {
1068 device_id: String,
1069 mode: String,
1070 },
1071
1072 AnalogDpadMode {
1074 device_id: String,
1075 mode: String,
1076 },
1077
1078 LedColorSet {
1080 device_id: String,
1081 zone: LedZone,
1082 color: (u8, u8, u8),
1083 },
1084
1085 LedColor {
1087 device_id: String,
1088 zone: LedZone,
1089 color: Option<(u8, u8, u8)>,
1090 },
1091
1092 AllLedColors {
1094 device_id: String,
1095 colors: std::collections::HashMap<LedZone, (u8, u8, u8)>,
1096 },
1097
1098 LedBrightnessSet {
1100 device_id: String,
1101 zone: Option<LedZone>,
1102 brightness: u8,
1103 },
1104
1105 LedBrightness {
1107 device_id: String,
1108 zone: Option<LedZone>,
1109 brightness: u8,
1110 },
1111
1112 LedPatternSet {
1114 device_id: String,
1115 pattern: LedPattern,
1116 },
1117
1118 LedPattern {
1120 device_id: String,
1121 pattern: LedPattern,
1122 },
1123
1124 FocusChangedAck {
1126 app_id: String,
1127 },
1128
1129 HotkeyRegistered {
1131 device_id: String,
1132 key: String,
1133 modifiers: Vec<String>,
1134 },
1135
1136 HotkeyList {
1138 device_id: String,
1139 bindings: Vec<HotkeyBinding>,
1140 },
1141
1142 HotkeyRemoved {
1144 device_id: String,
1145 key: String,
1146 modifiers: Vec<String>,
1147 },
1148
1149 AutoSwitchRulesAck,
1151
1152 AutoSwitchRules {
1154 rules: Vec<AutoSwitchRule>,
1155 },
1156
1157 AnalogCalibration {
1159 device_id: String,
1160 layer_id: usize,
1161 calibration: Option<AnalogCalibrationConfig>,
1162 },
1163
1164 AnalogCalibrationAck,
1166
1167 AnalogInputUpdate {
1169 device_id: String,
1170 axis_x: f32, axis_y: f32, },
1173
1174 AnalogInputSubscribed,
1176
1177 MacroSettings(MacroSettings),
1179}
1180
1181#[derive(Debug, Clone, Serialize, Deserialize)]
1183pub struct Profile {
1184 pub name: String,
1185 pub macros: std::collections::HashMap<String, MacroEntry>,
1186}
1187
1188pub fn serialize<T: Serialize>(msg: &T) -> Vec<u8> {
1190 bincode::serialize(msg).unwrap_or_else(|e| {
1191 tracing::error!("Failed to serialize message: {:?}", e);
1192 Vec::new()
1193 })
1194}
1195
1196pub fn deserialize<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, bincode::Error> {
1197 bincode::deserialize(bytes)
1198}
1199
1200#[cfg(test)]
1201mod tests {
1202 use super::*;
1203
1204 #[test]
1205 fn test_macro_settings_serialization() {
1206 let settings = MacroSettings {
1207 latency_offset_ms: 10,
1208 jitter_pct: 0.05,
1209 capture_mouse: false,
1210 };
1211
1212 let serialized = serialize(&settings);
1213 let deserialized: MacroSettings = deserialize(&serialized).unwrap();
1214 assert_eq!(deserialized, settings);
1215 }
1216
1217 #[test]
1218 fn test_ipc_serialization() {
1219 let request = Request::GetDevices;
1220 let serialized = serialize(&request);
1221 let deserialized: Request = deserialize(&serialized).unwrap();
1222 assert!(matches!(deserialized, Request::GetDevices));
1223 }
1224
1225 #[test]
1226 fn test_macro_entry_serialization() {
1227 let macro_entry = MacroEntry {
1228 name: "Test Macro".to_string(),
1229 trigger: KeyCombo {
1230 keys: vec![30, 40], modifiers: vec![29], },
1233 actions: vec![
1234 Action::KeyPress(30),
1235 Action::Delay(100),
1236 Action::KeyRelease(30),
1237 ],
1238 device_id: Some("test_device".to_string()),
1239 enabled: true,
1240 humanize: false,
1241 capture_mouse: false,
1242 };
1243
1244 let serialized = serialize(¯o_entry);
1245 let deserialized: MacroEntry = deserialize(&serialized).unwrap();
1246 assert_eq!(deserialized.name, "Test Macro");
1247 assert_eq!(deserialized.trigger.keys, vec![30, 40]);
1248 }
1249
1250 #[test]
1251 fn test_profile_ipc_serialization() {
1252 let request = Request::GetDeviceProfiles {
1253 device_id: "1532:0220".to_string(),
1254 };
1255 let serialized = serialize(&request);
1256 let deserialized: Request = deserialize(&serialized).unwrap();
1257 assert!(matches!(deserialized, Request::GetDeviceProfiles { .. }));
1258
1259 let response = Response::DeviceProfiles {
1260 device_id: "1532:0220".to_string(),
1261 profiles: vec!["gaming".to_string(), "work".to_string()],
1262 };
1263 let serialized = serialize(&response);
1264 let deserialized: Response = deserialize(&serialized).unwrap();
1265 assert!(matches!(deserialized, Response::DeviceProfiles { .. }));
1266 }
1267
1268 #[test]
1269 fn test_analog_deadzone_ipc_serialization() {
1270 let request = Request::SetAnalogDeadzone {
1272 device_id: "1532:0220".to_string(),
1273 percentage: 50,
1274 };
1275 let serialized = serialize(&request);
1276 let deserialized: Request = deserialize(&serialized).unwrap();
1277 assert!(matches!(deserialized, Request::SetAnalogDeadzone { .. }));
1278
1279 let request = Request::GetAnalogDeadzone {
1281 device_id: "1532:0220".to_string(),
1282 };
1283 let serialized = serialize(&request);
1284 let deserialized: Request = deserialize(&serialized).unwrap();
1285 assert!(matches!(deserialized, Request::GetAnalogDeadzone { .. }));
1286
1287 let response = Response::AnalogDeadzoneSet {
1289 device_id: "1532:0220".to_string(),
1290 percentage: 50,
1291 };
1292 let serialized = serialize(&response);
1293 let deserialized: Response = deserialize(&serialized).unwrap();
1294 assert_eq!(deserialized, response);
1295
1296 let response = Response::AnalogDeadzone {
1298 device_id: "1532:0220".to_string(),
1299 percentage: 43,
1300 };
1301 let serialized = serialize(&response);
1302 let deserialized: Response = deserialize(&serialized).unwrap();
1303 assert_eq!(deserialized, response);
1304 }
1305
1306 #[test]
1307 fn test_mouse_action_serialization() {
1308 let actions = vec![
1310 Action::MousePress(0x110), Action::MouseRelease(0x110),
1312 Action::MouseMove(10, 20),
1313 Action::MouseScroll(5),
1314 ];
1315
1316 for action in &actions {
1317 let serialized = serialize(action);
1318 let deserialized: Action = deserialize(&serialized).unwrap();
1319 assert_eq!(action, &deserialized);
1320 }
1321
1322 let macro_entry = MacroEntry {
1324 name: "Mixed Macro".to_string(),
1325 trigger: KeyCombo {
1326 keys: vec![30],
1327 modifiers: vec![],
1328 },
1329 actions: vec![
1330 Action::KeyPress(30),
1331 Action::MousePress(0x110),
1332 Action::Delay(50),
1333 Action::MouseRelease(0x110),
1334 Action::MouseMove(100, 200),
1335 Action::MouseScroll(3),
1336 Action::KeyRelease(30),
1337 ],
1338 device_id: Some("1532:0220".to_string()),
1339 enabled: true,
1340 humanize: false,
1341 capture_mouse: false,
1342 };
1343
1344 let serialized = serialize(¯o_entry);
1345 let deserialized: MacroEntry = deserialize(&serialized).unwrap();
1346 assert_eq!(deserialized.name, "Mixed Macro");
1347 assert_eq!(deserialized.actions.len(), 7);
1348
1349 assert!(matches!(deserialized.actions[0], Action::KeyPress(30)));
1351 assert!(matches!(deserialized.actions[1], Action::MousePress(0x110)));
1352 assert!(matches!(deserialized.actions[2], Action::Delay(50)));
1353 assert!(matches!(deserialized.actions[3], Action::MouseRelease(0x110)));
1354 assert!(matches!(deserialized.actions[4], Action::MouseMove(100, 200)));
1355 assert!(matches!(deserialized.actions[5], Action::MouseScroll(3)));
1356 assert!(matches!(deserialized.actions[6], Action::KeyRelease(30)));
1357 }
1358
1359 #[test]
1360 fn test_device_capabilities_serialization() {
1361 let caps = DeviceCapabilities {
1362 has_analog_stick: true,
1363 has_hat_switch: true,
1364 joystick_button_count: 26,
1365 led_zones: vec!["logo".to_string(), "keys".to_string()],
1366 };
1367 let serialized = serialize(&caps);
1368 let deserialized: DeviceCapabilities = deserialize(&serialized).unwrap();
1369 assert_eq!(deserialized.has_analog_stick, true);
1370 assert_eq!(deserialized.has_hat_switch, true);
1371 assert_eq!(deserialized.joystick_button_count, 26);
1372 assert_eq!(deserialized.led_zones.len(), 2);
1373 }
1374
1375 #[test]
1376 fn test_get_device_capabilities_request() {
1377 let request = Request::GetDeviceCapabilities {
1378 device_path: "/dev/input/event0".to_string(),
1379 };
1380 let serialized = serialize(&request);
1381 let deserialized: Request = deserialize(&serialized).unwrap();
1382 assert!(matches!(deserialized, Request::GetDeviceCapabilities { .. }));
1383 if let Request::GetDeviceCapabilities { device_path } = deserialized {
1384 assert_eq!(device_path, "/dev/input/event0");
1385 }
1386 }
1387
1388 #[test]
1389 fn test_device_capabilities_response() {
1390 let response = Response::DeviceCapabilities {
1391 device_path: "/dev/input/event0".to_string(),
1392 capabilities: DeviceCapabilities {
1393 has_analog_stick: true,
1394 has_hat_switch: true,
1395 joystick_button_count: 26,
1396 led_zones: vec![],
1397 },
1398 };
1399 let serialized = serialize(&response);
1400 let deserialized: Response = deserialize(&serialized).unwrap();
1401 assert!(matches!(deserialized, Response::DeviceCapabilities { .. }));
1402 }
1403
1404 #[test]
1405 fn test_layer_mode_serialization() {
1406 let hold_mode = LayerMode::Hold;
1408 let serialized = serialize(&hold_mode);
1409 let deserialized: LayerMode = deserialize(&serialized).unwrap();
1410 assert_eq!(deserialized, LayerMode::Hold);
1411
1412 let toggle_mode = LayerMode::Toggle;
1414 let serialized = serialize(&toggle_mode);
1415 let deserialized: LayerMode = deserialize(&serialized).unwrap();
1416 assert_eq!(deserialized, LayerMode::Toggle);
1417 }
1418
1419 #[test]
1420 fn test_layer_config_info_serialization() {
1421 let config = LayerConfigInfo {
1422 layer_id: 1,
1423 name: "Gaming".to_string(),
1424 mode: LayerMode::Toggle,
1425 remap_count: 5,
1426 led_color: (0, 0, 255), led_zone: None,
1428 };
1429
1430 let serialized = serialize(&config);
1431 let deserialized: LayerConfigInfo = deserialize(&serialized).unwrap();
1432
1433 assert_eq!(deserialized.layer_id, 1);
1434 assert_eq!(deserialized.name, "Gaming");
1435 assert_eq!(deserialized.mode, LayerMode::Toggle);
1436 assert_eq!(deserialized.remap_count, 5);
1437 }
1438
1439 #[test]
1440 fn test_get_active_layer_request() {
1441 let request = Request::GetActiveLayer {
1442 device_id: "1532:0220".to_string(),
1443 };
1444
1445 let serialized = serialize(&request);
1446 let deserialized: Request = deserialize(&serialized).unwrap();
1447
1448 assert!(matches!(deserialized, Request::GetActiveLayer { .. }));
1449 if let Request::GetActiveLayer { device_id } = deserialized {
1450 assert_eq!(device_id, "1532:0220");
1451 }
1452 }
1453
1454 #[test]
1455 fn test_active_layer_response() {
1456 let response = Response::ActiveLayer {
1457 device_id: "1532:0220".to_string(),
1458 layer_id: 2,
1459 layer_name: "Gaming".to_string(),
1460 };
1461
1462 let serialized = serialize(&response);
1463 let deserialized: Response = deserialize(&serialized).unwrap();
1464
1465 assert!(matches!(deserialized, Response::ActiveLayer { .. }));
1466 if let Response::ActiveLayer { device_id, layer_id, layer_name } = deserialized {
1467 assert_eq!(device_id, "1532:0220");
1468 assert_eq!(layer_id, 2);
1469 assert_eq!(layer_name, "Gaming");
1470 }
1471 }
1472
1473 #[test]
1474 fn test_set_layer_config_request() {
1475 let request = Request::SetLayerConfig {
1476 device_id: "1532:0220".to_string(),
1477 layer_id: 1,
1478 config: LayerConfigInfo {
1479 layer_id: 1,
1480 name: "Work".to_string(),
1481 mode: LayerMode::Hold,
1482 remap_count: 0,
1483 led_color: (0, 0, 255),
1484 led_zone: None,
1485 },
1486 };
1487
1488 let serialized = serialize(&request);
1489 let deserialized: Request = deserialize(&serialized).unwrap();
1490
1491 assert!(matches!(deserialized, Request::SetLayerConfig { .. }));
1492 if let Request::SetLayerConfig { device_id, layer_id, config } = deserialized {
1493 assert_eq!(device_id, "1532:0220");
1494 assert_eq!(layer_id, 1);
1495 assert_eq!(config.name, "Work");
1496 assert_eq!(config.mode, LayerMode::Hold);
1497 }
1498 }
1499
1500 #[test]
1501 fn test_activate_layer_request() {
1502 let request = Request::ActivateLayer {
1503 device_id: "1532:0220".to_string(),
1504 layer_id: 2,
1505 mode: LayerMode::Toggle,
1506 };
1507
1508 let serialized = serialize(&request);
1509 let deserialized: Request = deserialize(&serialized).unwrap();
1510
1511 assert!(matches!(deserialized, Request::ActivateLayer { .. }));
1512 if let Request::ActivateLayer { device_id, layer_id, mode } = deserialized {
1513 assert_eq!(device_id, "1532:0220");
1514 assert_eq!(layer_id, 2);
1515 assert_eq!(mode, LayerMode::Toggle);
1516 }
1517 }
1518
1519 #[test]
1520 fn test_list_layers_request() {
1521 let request = Request::ListLayers {
1522 device_id: "1532:0220".to_string(),
1523 };
1524
1525 let serialized = serialize(&request);
1526 let deserialized: Request = deserialize(&serialized).unwrap();
1527
1528 assert!(matches!(deserialized, Request::ListLayers { .. }));
1529 if let Request::ListLayers { device_id } = deserialized {
1530 assert_eq!(device_id, "1532:0220");
1531 }
1532 }
1533
1534 #[test]
1535 fn test_layer_list_response() {
1536 let response = Response::LayerList {
1537 device_id: "1532:0220".to_string(),
1538 layers: vec![
1539 LayerConfigInfo {
1540 layer_id: 0,
1541 name: "Base".to_string(),
1542 mode: LayerMode::Hold,
1543 remap_count: 10,
1544 led_color: (0, 255, 0),
1545 led_zone: None,
1546 },
1547 LayerConfigInfo {
1548 layer_id: 1,
1549 name: "Gaming".to_string(),
1550 mode: LayerMode::Toggle,
1551 remap_count: 5,
1552 led_color: (255, 0, 0),
1553 led_zone: Some(LedZone::Side),
1554 },
1555 ],
1556 };
1557
1558 let serialized = serialize(&response);
1559 let deserialized: Response = deserialize(&serialized).unwrap();
1560
1561 assert!(matches!(deserialized, Response::LayerList { .. }));
1562 if let Response::LayerList { device_id, layers } = deserialized {
1563 assert_eq!(device_id, "1532:0220");
1564 assert_eq!(layers.len(), 2);
1565 assert_eq!(layers[0].name, "Base");
1566 assert_eq!(layers[1].name, "Gaming");
1567 }
1568 }
1569
1570 #[test]
1571 fn test_layer_configured_response() {
1572 let response = Response::LayerConfigured {
1573 device_id: "1532:0220".to_string(),
1574 layer_id: 1,
1575 };
1576
1577 let serialized = serialize(&response);
1578 let deserialized: Response = deserialize(&serialized).unwrap();
1579
1580 assert!(matches!(deserialized, Response::LayerConfigured { .. }));
1581 if let Response::LayerConfigured { device_id, layer_id } = deserialized {
1582 assert_eq!(device_id, "1532:0220");
1583 assert_eq!(layer_id, 1);
1584 }
1585 }
1586
1587 #[test]
1588 fn test_focus_changed_request_serialization() {
1589 let request = Request::FocusChanged {
1591 app_id: "org.alacritty".to_string(),
1592 window_title: Some("Alacritty: ~/Projects".to_string()),
1593 };
1594
1595 let serialized = serialize(&request);
1596 let deserialized: Request = deserialize(&serialized).unwrap();
1597
1598 assert!(matches!(deserialized, Request::FocusChanged { .. }));
1599 if let Request::FocusChanged { app_id, window_title } = deserialized {
1600 assert_eq!(app_id, "org.alacritty");
1601 assert_eq!(window_title, Some("Alacritty: ~/Projects".to_string()));
1602 }
1603
1604 let request = Request::FocusChanged {
1606 app_id: "org.mozilla.firefox".to_string(),
1607 window_title: None,
1608 };
1609
1610 let serialized = serialize(&request);
1611 let deserialized: Request = deserialize(&serialized).unwrap();
1612
1613 assert!(matches!(deserialized, Request::FocusChanged { .. }));
1614 if let Request::FocusChanged { app_id, window_title } = deserialized {
1615 assert_eq!(app_id, "org.mozilla.firefox");
1616 assert_eq!(window_title, None);
1617 }
1618
1619 let request = Request::FocusChanged {
1621 app_id: "firefox".to_string(),
1622 window_title: Some("Mozilla Firefox".to_string()),
1623 };
1624
1625 let serialized = serialize(&request);
1626 let deserialized: Request = deserialize(&serialized).unwrap();
1627
1628 assert!(matches!(deserialized, Request::FocusChanged { .. }));
1629 if let Request::FocusChanged { app_id, window_title } = deserialized {
1630 assert_eq!(app_id, "firefox");
1631 assert_eq!(window_title, Some("Mozilla Firefox".to_string()));
1632 }
1633 }
1634
1635 #[test]
1636 fn test_focus_changed_ack_response_serialization() {
1637 let response = Response::FocusChangedAck {
1638 app_id: "org.alacritty".to_string(),
1639 };
1640
1641 let serialized = serialize(&response);
1642 let deserialized: Response = deserialize(&serialized).unwrap();
1643
1644 assert!(matches!(deserialized, Response::FocusChangedAck { .. }));
1645 if let Response::FocusChangedAck { ref app_id } = deserialized {
1646 assert_eq!(app_id, "org.alacritty");
1647 }
1648
1649 assert_eq!(deserialized, response);
1651 }
1652
1653 #[test]
1654 fn test_analog_calibration_config_with_mode() {
1655 let config = AnalogCalibrationConfig {
1656 deadzone: 0.2,
1657 deadzone_shape: "circular".to_string(),
1658 sensitivity: "linear".to_string(),
1659 sensitivity_multiplier: 1.5,
1660 range_min: -32768,
1661 range_max: 32767,
1662 invert_x: false,
1663 invert_y: true,
1664 exponent: 2.0,
1665 analog_mode: AnalogMode::Wasd,
1666 camera_output_mode: None,
1667 };
1668
1669 let serialized = serialize(&config);
1670 let deserialized: AnalogCalibrationConfig = deserialize(&serialized).unwrap();
1671
1672 assert_eq!(deserialized.analog_mode, AnalogMode::Wasd);
1673 assert_eq!(deserialized.camera_output_mode, None);
1674
1675 let camera_config = AnalogCalibrationConfig {
1677 analog_mode: AnalogMode::Camera,
1678 camera_output_mode: Some(CameraOutputMode::Keys),
1679 ..config
1680 };
1681
1682 let serialized = serialize(&camera_config);
1683 let deserialized: AnalogCalibrationConfig = deserialize(&serialized).unwrap();
1684
1685 assert_eq!(deserialized.analog_mode, AnalogMode::Camera);
1686 assert_eq!(deserialized.camera_output_mode, Some(CameraOutputMode::Keys));
1687 }
1688}