1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::path::PathBuf;
4
5pub use bincode;
7pub use serde;
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!(
55 f,
56 "{} (VID: {:04X}, PID: {:04X}, Type: {})",
57 self.name, self.vendor_id, self.product_id, self.device_type
58 )
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
64pub struct KeyCombo {
65 pub keys: Vec<u16>, pub modifiers: Vec<u16>, }
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
74pub struct HotkeyBinding {
75 pub modifiers: Vec<String>,
79
80 pub key: String,
84
85 pub profile_name: String,
87
88 pub device_id: Option<String>,
92
93 pub layer_id: Option<usize>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
104pub struct AutoSwitchRule {
105 pub app_id: String,
111
112 pub profile_name: String,
114
115 pub device_id: Option<String>,
119
120 pub layer_id: Option<usize>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
128pub enum Action {
129 KeyPress(u16),
131 KeyRelease(u16),
133 Delay(u32),
135 Execute(String),
137 Type(String),
139 MousePress(u16),
141 MouseRelease(u16),
143 MouseMove(i32, i32),
145 MouseScroll(i32),
147 AnalogMove { axis_code: u16, normalized: f32 },
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 {
166 axis_code,
167 normalized,
168 } => {
169 let axis_name = match axis_code {
170 61000 => "X",
171 61001 => "Y",
172 61002 => "Z",
173 61003 => "RX",
174 61004 => "RY",
175 61005 => "RZ",
176 _ => "UNKNOWN",
177 };
178 write!(f, "Analog({}, {}={:.2})", axis_name, axis_code, normalized)
179 }
180 }
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
186pub struct MacroSettings {
187 pub latency_offset_ms: u32,
188 pub jitter_pct: f32,
189 pub capture_mouse: bool,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
194pub struct MacroEntry {
195 pub name: String,
196 pub trigger: KeyCombo,
197 pub actions: Vec<Action>,
198 pub device_id: Option<String>, pub enabled: bool,
200 #[serde(default)]
201 pub humanize: bool,
202 #[serde(default)]
203 pub capture_mouse: bool,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
208pub struct RemapProfileInfo {
209 pub name: String,
211 pub description: Option<String>,
213 pub remap_count: usize,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
219pub struct RemapEntry {
220 pub from_key: String,
222 pub to_key: String,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
232pub struct DeviceCapabilities {
233 pub has_analog_stick: bool,
235
236 pub has_hat_switch: bool,
238
239 pub joystick_button_count: usize,
241
242 pub led_zones: Vec<String>,
244}
245
246#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
250#[serde(rename_all = "lowercase")]
251pub enum LayerMode {
252 #[default]
257 Hold,
258
259 Toggle,
264}
265
266impl fmt::Display for LayerMode {
267 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268 match self {
269 LayerMode::Hold => write!(f, "hold"),
270 LayerMode::Toggle => write!(f, "toggle"),
271 }
272 }
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
280pub struct CommonLayerConfig {
281 #[serde(default)]
283 pub layer_id: usize,
284
285 #[serde(default)]
287 pub name: String,
288
289 #[serde(default)]
291 pub mode: LayerMode,
292
293 #[serde(default = "default_layer_color")]
295 pub led_color: (u8, u8, u8),
296
297 #[serde(default)]
299 pub led_zone: Option<LedZone>,
300}
301
302fn default_layer_color() -> (u8, u8, u8) {
304 (0, 0, 255) }
306
307#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
312pub struct LayerConfigInfo {
313 pub layer_id: usize,
315
316 pub name: String,
318
319 pub mode: LayerMode,
321
322 pub remap_count: usize,
324
325 #[serde(default = "default_layer_color")]
327 pub led_color: (u8, u8, u8),
328
329 #[serde(default)]
331 pub led_zone: Option<LedZone>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
340pub struct AnalogCalibrationConfig {
341 pub deadzone: f32,
343
344 pub deadzone_shape: String,
346
347 pub sensitivity: String,
349
350 pub sensitivity_multiplier: f32,
352
353 pub range_min: i32,
355
356 pub range_max: i32,
358
359 pub invert_x: bool,
361
362 pub invert_y: bool,
364
365 #[serde(default = "default_exponent")]
367 pub exponent: f32,
368
369 #[serde(default)]
371 pub analog_mode: AnalogMode,
372
373 #[serde(default)]
375 pub camera_output_mode: Option<CameraOutputMode>,
376}
377
378fn default_exponent() -> f32 {
379 2.0
380}
381
382impl Default for AnalogCalibrationConfig {
383 fn default() -> Self {
384 Self {
385 deadzone: 0.15,
386 deadzone_shape: "circular".to_string(),
387 sensitivity: "linear".to_string(),
388 sensitivity_multiplier: 1.0,
389 range_min: -32768,
390 range_max: 32767,
391 invert_x: false,
392 invert_y: false,
393 exponent: 2.0,
394 analog_mode: AnalogMode::Disabled,
395 camera_output_mode: None,
396 }
397 }
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
402pub enum Request {
403 GetDevices,
405
406 SetMacro {
408 device_path: String,
409 macro_entry: MacroEntry,
410 },
411
412 ListMacros,
414
415 DeleteMacro { name: String },
417
418 ReloadConfig,
420
421 LedSet {
423 device_path: String,
424 color: (u8, u8, u8), },
426
427 RecordMacro {
429 device_path: String,
430 name: String,
431 capture_mouse: bool,
432 },
433
434 StopRecording,
436
437 TestMacro { name: String },
439
440 GetStatus,
442
443 SaveProfile { name: String },
445
446 LoadProfile { name: String },
448
449 ListProfiles,
451
452 DeleteProfile { name: String },
454
455 GenerateToken { client_id: String },
457
458 Authenticate { token: String },
460
461 ExecuteMacro { name: String },
463
464 GrabDevice { device_path: String },
466
467 UngrabDevice { device_path: String },
469
470 GetDeviceProfiles {
472 device_id: String, },
474
475 ActivateProfile {
477 device_id: String, profile_name: String,
479 },
480
481 DeactivateProfile {
483 device_id: String, },
485
486 GetActiveProfile {
488 device_id: String, },
490
491 GetActiveRemaps { device_path: String },
493
494 ListRemapProfiles { device_path: String },
496
497 ActivateRemapProfile {
499 device_path: String,
500 profile_name: String,
501 },
502
503 DeactivateRemapProfile { device_path: String },
505
506 GetDeviceCapabilities { device_path: String },
508
509 GetActiveLayer { device_id: String },
511
512 SetLayerConfig {
514 device_id: String,
515 layer_id: usize,
516 config: LayerConfigInfo,
517 },
518
519 ActivateLayer {
521 device_id: String,
522 layer_id: usize,
523 mode: LayerMode,
524 },
525
526 ListLayers { device_id: String },
528
529 SetAnalogSensitivity {
531 device_id: String,
532 sensitivity: f32, },
534
535 GetAnalogSensitivity { device_id: String },
537
538 SetAnalogResponseCurve {
540 device_id: String,
541 curve: String, },
543
544 GetAnalogResponseCurve { device_id: String },
546
547 SetAnalogDeadzone {
549 device_id: String,
550 percentage: u8, },
552
553 GetAnalogDeadzone { device_id: String },
555
556 SetAnalogDeadzoneXY {
558 device_id: String,
559 x_percentage: u8, y_percentage: u8, },
562
563 GetAnalogDeadzoneXY { device_id: String },
565
566 SetAnalogOuterDeadzoneXY {
568 device_id: String,
569 x_percentage: u8, y_percentage: u8, },
572
573 GetAnalogOuterDeadzoneXY { device_id: String },
575
576 SetAnalogDpadMode {
578 device_id: String,
579 mode: String, },
581
582 GetAnalogDpadMode { device_id: String },
584
585 SetLedColor {
587 device_id: String,
588 zone: LedZone,
589 red: u8,
590 green: u8,
591 blue: u8,
592 },
593
594 GetLedColor { device_id: String, zone: LedZone },
596
597 GetAllLedColors { device_id: String },
599
600 SetLedBrightness {
602 device_id: String,
603 zone: Option<LedZone>, brightness: u8, },
606
607 GetLedBrightness {
609 device_id: String,
610 zone: Option<LedZone>,
611 },
612
613 SetLedPattern {
615 device_id: String,
616 pattern: LedPattern,
617 },
618
619 GetLedPattern { device_id: String },
621
622 FocusChanged {
624 app_id: String, window_title: Option<String>, },
627
628 RegisterHotkey {
630 device_id: String,
631 binding: HotkeyBinding,
632 },
633
634 ListHotkeys { device_id: String },
636
637 RemoveHotkey {
639 device_id: String,
640 key: String,
641 modifiers: Vec<String>,
642 },
643
644 SetAutoSwitchRules { rules: Vec<AutoSwitchRule> },
646
647 GetAutoSwitchRules,
649
650 GetAnalogCalibration { device_id: String, layer_id: usize },
652
653 SetAnalogCalibration {
655 device_id: String,
656 layer_id: usize,
657 calibration: AnalogCalibrationConfig,
658 },
659
660 SubscribeAnalogInput { device_id: String },
662
663 UnsubscribeAnalogInput { device_id: String },
665
666 SetMacroSettings(MacroSettings),
668
669 GetMacroSettings,
671}
672
673#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
678#[serde(rename_all = "lowercase")]
679pub enum AnalogMode {
680 #[default]
682 Disabled,
683 Dpad,
685 Gamepad,
687 Camera,
689 Mouse,
691 Wasd,
693}
694
695impl fmt::Display for AnalogMode {
696 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
697 match self {
698 AnalogMode::Disabled => write!(f, "Disabled"),
699 AnalogMode::Dpad => write!(f, "D-pad (Arrows)"),
700 AnalogMode::Gamepad => write!(f, "Gamepad"),
701 AnalogMode::Camera => write!(f, "Camera"),
702 AnalogMode::Mouse => write!(f, "Mouse"),
703 AnalogMode::Wasd => write!(f, "WASD"),
704 }
705 }
706}
707
708impl AnalogMode {
709 pub const ALL: [AnalogMode; 6] = [
711 AnalogMode::Disabled,
712 AnalogMode::Dpad,
713 AnalogMode::Gamepad,
714 AnalogMode::Wasd,
715 AnalogMode::Mouse,
716 AnalogMode::Camera,
717 ];
718}
719
720#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
725#[serde(rename_all = "lowercase")]
726pub enum CameraOutputMode {
727 #[default]
729 Scroll,
730 Keys,
732}
733
734impl fmt::Display for CameraOutputMode {
735 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736 match self {
737 CameraOutputMode::Scroll => write!(f, "Scroll"),
738 CameraOutputMode::Keys => write!(f, "Key Repeat"),
739 }
740 }
741}
742
743impl CameraOutputMode {
744 pub const ALL: [CameraOutputMode; 2] = [CameraOutputMode::Scroll, CameraOutputMode::Keys];
746}
747
748#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
750pub enum LedPattern {
751 Static,
753 Breathing,
755 Rainbow,
757 RainbowWave,
759}
760
761#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
763pub enum LedZone {
764 Side,
766 Logo,
768 Keys,
770 Thumbstick,
772 All,
774 Global,
776}
777
778#[derive(Debug, Clone, Serialize, Deserialize)]
780pub struct StatusInfo {
781 pub version: String,
782 pub uptime_seconds: u64,
783 pub devices_count: usize,
784 pub macros_count: usize,
785}
786
787#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
789pub enum Response {
790 Devices(Vec<DeviceInfo>),
792
793 Macros(Vec<MacroEntry>),
795
796 Ack,
798
799 Status {
801 version: String,
802 uptime_seconds: u64,
803 devices_count: usize,
804 macros_count: usize,
805 },
806
807 RecordingStarted { device_path: String, name: String },
809
810 RecordingStopped { macro_entry: MacroEntry },
812
813 Profiles(Vec<String>),
815
816 ProfileLoaded { name: String, macros_count: usize },
818
819 ProfileSaved { name: String, macros_count: usize },
821
822 Error(String),
824
825 Token(String),
827
828 Authenticated,
830
831 DeviceProfiles {
833 device_id: String,
834 profiles: Vec<String>,
835 },
836
837 ProfileActivated {
839 device_id: String,
840 profile_name: String,
841 },
842
843 ProfileDeactivated { device_id: String },
845
846 ActiveProfile {
848 device_id: String,
849 profile_name: Option<String>,
850 },
851
852 ActiveRemaps {
854 device_path: String,
855 profile_name: Option<String>,
856 remaps: Vec<RemapEntry>,
857 },
858
859 RemapProfiles {
861 device_path: String,
862 profiles: Vec<RemapProfileInfo>,
863 },
864
865 RemapProfileActivated {
867 device_path: String,
868 profile_name: String,
869 },
870
871 RemapProfileDeactivated { device_path: String },
873
874 DeviceCapabilities {
876 device_path: String,
877 capabilities: DeviceCapabilities,
878 },
879
880 ActiveLayer {
882 device_id: String,
883 layer_id: usize,
884 layer_name: String,
885 },
886
887 LayerConfigured { device_id: String, layer_id: usize },
889
890 LayerList {
892 device_id: String,
893 layers: Vec<LayerConfigInfo>,
894 },
895
896 AnalogSensitivitySet { device_id: String, sensitivity: f32 },
898
899 AnalogSensitivity { device_id: String, sensitivity: f32 },
901
902 AnalogResponseCurveSet { device_id: String, curve: String },
904
905 AnalogResponseCurve { device_id: String, curve: String },
907
908 AnalogDeadzoneSet { device_id: String, percentage: u8 },
910
911 AnalogDeadzone { device_id: String, percentage: u8 },
913
914 AnalogDeadzoneXYSet {
916 device_id: String,
917 x_percentage: u8,
918 y_percentage: u8,
919 },
920
921 AnalogDeadzoneXY {
923 device_id: String,
924 x_percentage: u8,
925 y_percentage: u8,
926 },
927
928 AnalogOuterDeadzoneXYSet {
930 device_id: String,
931 x_percentage: u8,
932 y_percentage: u8,
933 },
934
935 AnalogOuterDeadzoneXY {
937 device_id: String,
938 x_percentage: u8,
939 y_percentage: u8,
940 },
941
942 AnalogDpadModeSet { device_id: String, mode: String },
944
945 AnalogDpadMode { device_id: String, mode: String },
947
948 LedColorSet {
950 device_id: String,
951 zone: LedZone,
952 color: (u8, u8, u8),
953 },
954
955 LedColor {
957 device_id: String,
958 zone: LedZone,
959 color: Option<(u8, u8, u8)>,
960 },
961
962 AllLedColors {
964 device_id: String,
965 colors: std::collections::HashMap<LedZone, (u8, u8, u8)>,
966 },
967
968 LedBrightnessSet {
970 device_id: String,
971 zone: Option<LedZone>,
972 brightness: u8,
973 },
974
975 LedBrightness {
977 device_id: String,
978 zone: Option<LedZone>,
979 brightness: u8,
980 },
981
982 LedPatternSet {
984 device_id: String,
985 pattern: LedPattern,
986 },
987
988 LedPattern {
990 device_id: String,
991 pattern: LedPattern,
992 },
993
994 FocusChangedAck { app_id: String },
996
997 HotkeyRegistered {
999 device_id: String,
1000 key: String,
1001 modifiers: Vec<String>,
1002 },
1003
1004 HotkeyList {
1006 device_id: String,
1007 bindings: Vec<HotkeyBinding>,
1008 },
1009
1010 HotkeyRemoved {
1012 device_id: String,
1013 key: String,
1014 modifiers: Vec<String>,
1015 },
1016
1017 AutoSwitchRulesAck,
1019
1020 AutoSwitchRules { rules: Vec<AutoSwitchRule> },
1022
1023 AnalogCalibration {
1025 device_id: String,
1026 layer_id: usize,
1027 calibration: Option<AnalogCalibrationConfig>,
1028 },
1029
1030 AnalogCalibrationAck,
1032
1033 AnalogInputUpdate {
1035 device_id: String,
1036 axis_x: f32, axis_y: f32, },
1039
1040 AnalogInputSubscribed,
1042
1043 MacroSettings(MacroSettings),
1045}
1046
1047#[derive(Debug, Clone, Serialize, Deserialize)]
1049pub struct Profile {
1050 pub name: String,
1051 pub macros: std::collections::HashMap<String, MacroEntry>,
1052}
1053
1054pub fn serialize<T: Serialize>(msg: &T) -> Vec<u8> {
1056 bincode::serialize(msg).unwrap_or_else(|e| {
1057 tracing::error!("Failed to serialize message: {:?}", e);
1058 Vec::new()
1059 })
1060}
1061
1062pub fn deserialize<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, bincode::Error> {
1063 bincode::deserialize(bytes)
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068 use super::*;
1069
1070 #[test]
1071 fn test_macro_settings_serialization() {
1072 let settings = MacroSettings {
1073 latency_offset_ms: 10,
1074 jitter_pct: 0.05,
1075 capture_mouse: false,
1076 };
1077
1078 let serialized = serialize(&settings);
1079 let deserialized: MacroSettings = deserialize(&serialized).unwrap();
1080 assert_eq!(deserialized, settings);
1081 }
1082
1083 #[test]
1084 fn test_ipc_serialization() {
1085 let request = Request::GetDevices;
1086 let serialized = serialize(&request);
1087 let deserialized: Request = deserialize(&serialized).unwrap();
1088 assert!(matches!(deserialized, Request::GetDevices));
1089 }
1090
1091 #[test]
1092 fn test_macro_entry_serialization() {
1093 let macro_entry = MacroEntry {
1094 name: "Test Macro".to_string(),
1095 trigger: KeyCombo {
1096 keys: vec![30, 40], modifiers: vec![29], },
1099 actions: vec![
1100 Action::KeyPress(30),
1101 Action::Delay(100),
1102 Action::KeyRelease(30),
1103 ],
1104 device_id: Some("test_device".to_string()),
1105 enabled: true,
1106 humanize: false,
1107 capture_mouse: false,
1108 };
1109
1110 let serialized = serialize(¯o_entry);
1111 let deserialized: MacroEntry = deserialize(&serialized).unwrap();
1112 assert_eq!(deserialized.name, "Test Macro");
1113 assert_eq!(deserialized.trigger.keys, vec![30, 40]);
1114 }
1115
1116 #[test]
1117 fn test_profile_ipc_serialization() {
1118 let request = Request::GetDeviceProfiles {
1119 device_id: "1532:0220".to_string(),
1120 };
1121 let serialized = serialize(&request);
1122 let deserialized: Request = deserialize(&serialized).unwrap();
1123 assert!(matches!(deserialized, Request::GetDeviceProfiles { .. }));
1124
1125 let response = Response::DeviceProfiles {
1126 device_id: "1532:0220".to_string(),
1127 profiles: vec!["gaming".to_string(), "work".to_string()],
1128 };
1129 let serialized = serialize(&response);
1130 let deserialized: Response = deserialize(&serialized).unwrap();
1131 assert!(matches!(deserialized, Response::DeviceProfiles { .. }));
1132 }
1133
1134 #[test]
1135 fn test_analog_deadzone_ipc_serialization() {
1136 let request = Request::SetAnalogDeadzone {
1138 device_id: "1532:0220".to_string(),
1139 percentage: 50,
1140 };
1141 let serialized = serialize(&request);
1142 let deserialized: Request = deserialize(&serialized).unwrap();
1143 assert!(matches!(deserialized, Request::SetAnalogDeadzone { .. }));
1144
1145 let request = Request::GetAnalogDeadzone {
1147 device_id: "1532:0220".to_string(),
1148 };
1149 let serialized = serialize(&request);
1150 let deserialized: Request = deserialize(&serialized).unwrap();
1151 assert!(matches!(deserialized, Request::GetAnalogDeadzone { .. }));
1152
1153 let response = Response::AnalogDeadzoneSet {
1155 device_id: "1532:0220".to_string(),
1156 percentage: 50,
1157 };
1158 let serialized = serialize(&response);
1159 let deserialized: Response = deserialize(&serialized).unwrap();
1160 assert_eq!(deserialized, response);
1161
1162 let response = Response::AnalogDeadzone {
1164 device_id: "1532:0220".to_string(),
1165 percentage: 43,
1166 };
1167 let serialized = serialize(&response);
1168 let deserialized: Response = deserialize(&serialized).unwrap();
1169 assert_eq!(deserialized, response);
1170 }
1171
1172 #[test]
1173 fn test_mouse_action_serialization() {
1174 let actions = vec![
1176 Action::MousePress(0x110), Action::MouseRelease(0x110),
1178 Action::MouseMove(10, 20),
1179 Action::MouseScroll(5),
1180 ];
1181
1182 for action in &actions {
1183 let serialized = serialize(action);
1184 let deserialized: Action = deserialize(&serialized).unwrap();
1185 assert_eq!(action, &deserialized);
1186 }
1187
1188 let macro_entry = MacroEntry {
1190 name: "Mixed Macro".to_string(),
1191 trigger: KeyCombo {
1192 keys: vec![30],
1193 modifiers: vec![],
1194 },
1195 actions: vec![
1196 Action::KeyPress(30),
1197 Action::MousePress(0x110),
1198 Action::Delay(50),
1199 Action::MouseRelease(0x110),
1200 Action::MouseMove(100, 200),
1201 Action::MouseScroll(3),
1202 Action::KeyRelease(30),
1203 ],
1204 device_id: Some("1532:0220".to_string()),
1205 enabled: true,
1206 humanize: false,
1207 capture_mouse: false,
1208 };
1209
1210 let serialized = serialize(¯o_entry);
1211 let deserialized: MacroEntry = deserialize(&serialized).unwrap();
1212 assert_eq!(deserialized.name, "Mixed Macro");
1213 assert_eq!(deserialized.actions.len(), 7);
1214
1215 assert!(matches!(deserialized.actions[0], Action::KeyPress(30)));
1217 assert!(matches!(deserialized.actions[1], Action::MousePress(0x110)));
1218 assert!(matches!(deserialized.actions[2], Action::Delay(50)));
1219 assert!(matches!(
1220 deserialized.actions[3],
1221 Action::MouseRelease(0x110)
1222 ));
1223 assert!(matches!(
1224 deserialized.actions[4],
1225 Action::MouseMove(100, 200)
1226 ));
1227 assert!(matches!(deserialized.actions[5], Action::MouseScroll(3)));
1228 assert!(matches!(deserialized.actions[6], Action::KeyRelease(30)));
1229 }
1230
1231 #[test]
1232 fn test_device_capabilities_serialization() {
1233 let caps = DeviceCapabilities {
1234 has_analog_stick: true,
1235 has_hat_switch: true,
1236 joystick_button_count: 26,
1237 led_zones: vec!["logo".to_string(), "keys".to_string()],
1238 };
1239 let serialized = serialize(&caps);
1240 let deserialized: DeviceCapabilities = deserialize(&serialized).unwrap();
1241 assert!(deserialized.has_analog_stick);
1242 assert!(deserialized.has_hat_switch);
1243 assert_eq!(deserialized.joystick_button_count, 26);
1244 assert_eq!(deserialized.led_zones.len(), 2);
1245 }
1246
1247 #[test]
1248 fn test_get_device_capabilities_request() {
1249 let request = Request::GetDeviceCapabilities {
1250 device_path: "/dev/input/event0".to_string(),
1251 };
1252 let serialized = serialize(&request);
1253 let deserialized: Request = deserialize(&serialized).unwrap();
1254 assert!(matches!(
1255 deserialized,
1256 Request::GetDeviceCapabilities { .. }
1257 ));
1258 if let Request::GetDeviceCapabilities { device_path } = deserialized {
1259 assert_eq!(device_path, "/dev/input/event0");
1260 }
1261 }
1262
1263 #[test]
1264 fn test_device_capabilities_response() {
1265 let response = Response::DeviceCapabilities {
1266 device_path: "/dev/input/event0".to_string(),
1267 capabilities: DeviceCapabilities {
1268 has_analog_stick: true,
1269 has_hat_switch: true,
1270 joystick_button_count: 26,
1271 led_zones: vec![],
1272 },
1273 };
1274 let serialized = serialize(&response);
1275 let deserialized: Response = deserialize(&serialized).unwrap();
1276 assert!(matches!(deserialized, Response::DeviceCapabilities { .. }));
1277 }
1278
1279 #[test]
1280 fn test_layer_mode_serialization() {
1281 let hold_mode = LayerMode::Hold;
1283 let serialized = serialize(&hold_mode);
1284 let deserialized: LayerMode = deserialize(&serialized).unwrap();
1285 assert_eq!(deserialized, LayerMode::Hold);
1286
1287 let toggle_mode = LayerMode::Toggle;
1289 let serialized = serialize(&toggle_mode);
1290 let deserialized: LayerMode = deserialize(&serialized).unwrap();
1291 assert_eq!(deserialized, LayerMode::Toggle);
1292 }
1293
1294 #[test]
1295 fn test_layer_config_info_serialization() {
1296 let config = LayerConfigInfo {
1297 layer_id: 1,
1298 name: "Gaming".to_string(),
1299 mode: LayerMode::Toggle,
1300 remap_count: 5,
1301 led_color: (0, 0, 255), led_zone: None,
1303 };
1304
1305 let serialized = serialize(&config);
1306 let deserialized: LayerConfigInfo = deserialize(&serialized).unwrap();
1307
1308 assert_eq!(deserialized.layer_id, 1);
1309 assert_eq!(deserialized.name, "Gaming");
1310 assert_eq!(deserialized.mode, LayerMode::Toggle);
1311 assert_eq!(deserialized.remap_count, 5);
1312 }
1313
1314 #[test]
1315 fn test_get_active_layer_request() {
1316 let request = Request::GetActiveLayer {
1317 device_id: "1532:0220".to_string(),
1318 };
1319
1320 let serialized = serialize(&request);
1321 let deserialized: Request = deserialize(&serialized).unwrap();
1322
1323 assert!(matches!(deserialized, Request::GetActiveLayer { .. }));
1324 if let Request::GetActiveLayer { device_id } = deserialized {
1325 assert_eq!(device_id, "1532:0220");
1326 }
1327 }
1328
1329 #[test]
1330 fn test_active_layer_response() {
1331 let response = Response::ActiveLayer {
1332 device_id: "1532:0220".to_string(),
1333 layer_id: 2,
1334 layer_name: "Gaming".to_string(),
1335 };
1336
1337 let serialized = serialize(&response);
1338 let deserialized: Response = deserialize(&serialized).unwrap();
1339
1340 assert!(matches!(deserialized, Response::ActiveLayer { .. }));
1341 if let Response::ActiveLayer {
1342 device_id,
1343 layer_id,
1344 layer_name,
1345 } = deserialized
1346 {
1347 assert_eq!(device_id, "1532:0220");
1348 assert_eq!(layer_id, 2);
1349 assert_eq!(layer_name, "Gaming");
1350 }
1351 }
1352
1353 #[test]
1354 fn test_set_layer_config_request() {
1355 let request = Request::SetLayerConfig {
1356 device_id: "1532:0220".to_string(),
1357 layer_id: 1,
1358 config: LayerConfigInfo {
1359 layer_id: 1,
1360 name: "Work".to_string(),
1361 mode: LayerMode::Hold,
1362 remap_count: 0,
1363 led_color: (0, 0, 255),
1364 led_zone: None,
1365 },
1366 };
1367
1368 let serialized = serialize(&request);
1369 let deserialized: Request = deserialize(&serialized).unwrap();
1370
1371 assert!(matches!(deserialized, Request::SetLayerConfig { .. }));
1372 if let Request::SetLayerConfig {
1373 device_id,
1374 layer_id,
1375 config,
1376 } = deserialized
1377 {
1378 assert_eq!(device_id, "1532:0220");
1379 assert_eq!(layer_id, 1);
1380 assert_eq!(config.name, "Work");
1381 assert_eq!(config.mode, LayerMode::Hold);
1382 }
1383 }
1384
1385 #[test]
1386 fn test_activate_layer_request() {
1387 let request = Request::ActivateLayer {
1388 device_id: "1532:0220".to_string(),
1389 layer_id: 2,
1390 mode: LayerMode::Toggle,
1391 };
1392
1393 let serialized = serialize(&request);
1394 let deserialized: Request = deserialize(&serialized).unwrap();
1395
1396 assert!(matches!(deserialized, Request::ActivateLayer { .. }));
1397 if let Request::ActivateLayer {
1398 device_id,
1399 layer_id,
1400 mode,
1401 } = deserialized
1402 {
1403 assert_eq!(device_id, "1532:0220");
1404 assert_eq!(layer_id, 2);
1405 assert_eq!(mode, LayerMode::Toggle);
1406 }
1407 }
1408
1409 #[test]
1410 fn test_list_layers_request() {
1411 let request = Request::ListLayers {
1412 device_id: "1532:0220".to_string(),
1413 };
1414
1415 let serialized = serialize(&request);
1416 let deserialized: Request = deserialize(&serialized).unwrap();
1417
1418 assert!(matches!(deserialized, Request::ListLayers { .. }));
1419 if let Request::ListLayers { device_id } = deserialized {
1420 assert_eq!(device_id, "1532:0220");
1421 }
1422 }
1423
1424 #[test]
1425 fn test_layer_list_response() {
1426 let response = Response::LayerList {
1427 device_id: "1532:0220".to_string(),
1428 layers: vec![
1429 LayerConfigInfo {
1430 layer_id: 0,
1431 name: "Base".to_string(),
1432 mode: LayerMode::Hold,
1433 remap_count: 10,
1434 led_color: (0, 255, 0),
1435 led_zone: None,
1436 },
1437 LayerConfigInfo {
1438 layer_id: 1,
1439 name: "Gaming".to_string(),
1440 mode: LayerMode::Toggle,
1441 remap_count: 5,
1442 led_color: (255, 0, 0),
1443 led_zone: Some(LedZone::Side),
1444 },
1445 ],
1446 };
1447
1448 let serialized = serialize(&response);
1449 let deserialized: Response = deserialize(&serialized).unwrap();
1450
1451 assert!(matches!(deserialized, Response::LayerList { .. }));
1452 if let Response::LayerList { device_id, layers } = deserialized {
1453 assert_eq!(device_id, "1532:0220");
1454 assert_eq!(layers.len(), 2);
1455 assert_eq!(layers[0].name, "Base");
1456 assert_eq!(layers[1].name, "Gaming");
1457 }
1458 }
1459
1460 #[test]
1461 fn test_layer_configured_response() {
1462 let response = Response::LayerConfigured {
1463 device_id: "1532:0220".to_string(),
1464 layer_id: 1,
1465 };
1466
1467 let serialized = serialize(&response);
1468 let deserialized: Response = deserialize(&serialized).unwrap();
1469
1470 assert!(matches!(deserialized, Response::LayerConfigured { .. }));
1471 if let Response::LayerConfigured {
1472 device_id,
1473 layer_id,
1474 } = deserialized
1475 {
1476 assert_eq!(device_id, "1532:0220");
1477 assert_eq!(layer_id, 1);
1478 }
1479 }
1480
1481 #[test]
1482 fn test_focus_changed_request_serialization() {
1483 let request = Request::FocusChanged {
1485 app_id: "org.alacritty".to_string(),
1486 window_title: Some("Alacritty: ~/Projects".to_string()),
1487 };
1488
1489 let serialized = serialize(&request);
1490 let deserialized: Request = deserialize(&serialized).unwrap();
1491
1492 assert!(matches!(deserialized, Request::FocusChanged { .. }));
1493 if let Request::FocusChanged {
1494 app_id,
1495 window_title,
1496 } = deserialized
1497 {
1498 assert_eq!(app_id, "org.alacritty");
1499 assert_eq!(window_title, Some("Alacritty: ~/Projects".to_string()));
1500 }
1501
1502 let request = Request::FocusChanged {
1504 app_id: "org.mozilla.firefox".to_string(),
1505 window_title: None,
1506 };
1507
1508 let serialized = serialize(&request);
1509 let deserialized: Request = deserialize(&serialized).unwrap();
1510
1511 assert!(matches!(deserialized, Request::FocusChanged { .. }));
1512 if let Request::FocusChanged {
1513 app_id,
1514 window_title,
1515 } = deserialized
1516 {
1517 assert_eq!(app_id, "org.mozilla.firefox");
1518 assert_eq!(window_title, None);
1519 }
1520
1521 let request = Request::FocusChanged {
1523 app_id: "firefox".to_string(),
1524 window_title: Some("Mozilla Firefox".to_string()),
1525 };
1526
1527 let serialized = serialize(&request);
1528 let deserialized: Request = deserialize(&serialized).unwrap();
1529
1530 assert!(matches!(deserialized, Request::FocusChanged { .. }));
1531 if let Request::FocusChanged {
1532 app_id,
1533 window_title,
1534 } = deserialized
1535 {
1536 assert_eq!(app_id, "firefox");
1537 assert_eq!(window_title, Some("Mozilla Firefox".to_string()));
1538 }
1539 }
1540
1541 #[test]
1542 fn test_focus_changed_ack_response_serialization() {
1543 let response = Response::FocusChangedAck {
1544 app_id: "org.alacritty".to_string(),
1545 };
1546
1547 let serialized = serialize(&response);
1548 let deserialized: Response = deserialize(&serialized).unwrap();
1549
1550 assert!(matches!(deserialized, Response::FocusChangedAck { .. }));
1551 if let Response::FocusChangedAck { ref app_id } = deserialized {
1552 assert_eq!(app_id, "org.alacritty");
1553 }
1554
1555 assert_eq!(deserialized, response);
1557 }
1558
1559 #[test]
1560 fn test_analog_calibration_config_with_mode() {
1561 let config = AnalogCalibrationConfig {
1562 deadzone: 0.2,
1563 deadzone_shape: "circular".to_string(),
1564 sensitivity: "linear".to_string(),
1565 sensitivity_multiplier: 1.5,
1566 range_min: -32768,
1567 range_max: 32767,
1568 invert_x: false,
1569 invert_y: true,
1570 exponent: 2.0,
1571 analog_mode: AnalogMode::Wasd,
1572 camera_output_mode: None,
1573 };
1574
1575 let serialized = serialize(&config);
1576 let deserialized: AnalogCalibrationConfig = deserialize(&serialized).unwrap();
1577
1578 assert_eq!(deserialized.analog_mode, AnalogMode::Wasd);
1579 assert_eq!(deserialized.camera_output_mode, None);
1580
1581 let camera_config = AnalogCalibrationConfig {
1583 analog_mode: AnalogMode::Camera,
1584 camera_output_mode: Some(CameraOutputMode::Keys),
1585 ..config
1586 };
1587
1588 let serialized = serialize(&camera_config);
1589 let deserialized: AnalogCalibrationConfig = deserialize(&serialized).unwrap();
1590
1591 assert_eq!(deserialized.analog_mode, AnalogMode::Camera);
1592 assert_eq!(
1593 deserialized.camera_output_mode,
1594 Some(CameraOutputMode::Keys)
1595 );
1596 }
1597}