1use std::{error::Error, fmt, str::FromStr};
43
44use crate::protocol::KeyModifiers;
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub enum Key {
58 ArrowUp,
61 ArrowDown,
63 ArrowLeft,
65 ArrowRight,
67 Home,
69 End,
71 PageUp,
73 PageDown,
75
76 Enter,
79 Tab,
81 Space,
83 Backspace,
85 Delete,
87 Insert,
89 Escape,
91
92 Shift,
95 Control,
97 Alt,
99 Super,
101
102 F1,
105 F2,
107 F3,
109 F4,
111 F5,
113 F6,
115 F7,
117 F8,
119 F9,
121 F10,
123 F11,
125 F12,
127
128 CapsLock,
131 NumLock,
133 ScrollLock,
135 PrintScreen,
137 Pause,
139 ContextMenu,
141 Copy,
143 Cut,
145 Paste,
147 Undo,
149 Redo,
151
152 Char(char),
155
156 Named(String),
162}
163
164impl Key {
165 pub fn wire_name(&self) -> String {
169 match self {
170 Self::ArrowUp => "ArrowUp".into(),
171 Self::ArrowDown => "ArrowDown".into(),
172 Self::ArrowLeft => "ArrowLeft".into(),
173 Self::ArrowRight => "ArrowRight".into(),
174 Self::Home => "Home".into(),
175 Self::End => "End".into(),
176 Self::PageUp => "PageUp".into(),
177 Self::PageDown => "PageDown".into(),
178 Self::Enter => "Enter".into(),
179 Self::Tab => "Tab".into(),
180 Self::Space => "Space".into(),
181 Self::Backspace => "Backspace".into(),
182 Self::Delete => "Delete".into(),
183 Self::Insert => "Insert".into(),
184 Self::Escape => "Escape".into(),
185 Self::Shift => "Shift".into(),
186 Self::Control => "Control".into(),
187 Self::Alt => "Alt".into(),
188 Self::Super => "Super".into(),
189 Self::F1 => "F1".into(),
190 Self::F2 => "F2".into(),
191 Self::F3 => "F3".into(),
192 Self::F4 => "F4".into(),
193 Self::F5 => "F5".into(),
194 Self::F6 => "F6".into(),
195 Self::F7 => "F7".into(),
196 Self::F8 => "F8".into(),
197 Self::F9 => "F9".into(),
198 Self::F10 => "F10".into(),
199 Self::F11 => "F11".into(),
200 Self::F12 => "F12".into(),
201 Self::CapsLock => "CapsLock".into(),
202 Self::NumLock => "NumLock".into(),
203 Self::ScrollLock => "ScrollLock".into(),
204 Self::PrintScreen => "PrintScreen".into(),
205 Self::Pause => "Pause".into(),
206 Self::ContextMenu => "ContextMenu".into(),
207 Self::Copy => "Copy".into(),
208 Self::Cut => "Cut".into(),
209 Self::Paste => "Paste".into(),
210 Self::Undo => "Undo".into(),
211 Self::Redo => "Redo".into(),
212 Self::Char(c) => c.to_string(),
213 Self::Named(name) => name.clone(),
214 }
215 }
216}
217
218impl fmt::Display for Key {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 write!(f, "{}", self.wire_name())
221 }
222}
223
224fn parse_key_normalized(s: &str) -> Key {
227 match s {
228 "arrowup" | "up" | "uparrow" => Key::ArrowUp,
230 "arrowdown" | "down" | "downarrow" => Key::ArrowDown,
231 "arrowleft" | "left" | "leftarrow" => Key::ArrowLeft,
232 "arrowright" | "right" | "rightarrow" => Key::ArrowRight,
233 "home" => Key::Home,
234 "end" => Key::End,
235 "pageup" | "pgup" => Key::PageUp,
236 "pagedown" | "pgdown" | "pgdn" => Key::PageDown,
237
238 "enter" | "return" => Key::Enter,
240 "tab" => Key::Tab,
241 "space" => Key::Space,
242 "backspace" | "bs" => Key::Backspace,
243 "delete" | "del" => Key::Delete,
244 "insert" | "ins" => Key::Insert,
245 "escape" | "esc" => Key::Escape,
246
247 "shift" => Key::Shift,
249 "control" | "ctrl" => Key::Control,
250 "alt" | "option" | "opt" => Key::Alt,
251 "super" | "logo" | "meta" | "command" | "cmd" | "win" => Key::Super,
252
253 "f1" => Key::F1,
255 "f2" => Key::F2,
256 "f3" => Key::F3,
257 "f4" => Key::F4,
258 "f5" => Key::F5,
259 "f6" => Key::F6,
260 "f7" => Key::F7,
261 "f8" => Key::F8,
262 "f9" => Key::F9,
263 "f10" => Key::F10,
264 "f11" => Key::F11,
265 "f12" => Key::F12,
266
267 "capslock" | "caps" => Key::CapsLock,
269 "numlock" | "num" => Key::NumLock,
270 "scrolllock" => Key::ScrollLock,
271 "printscreen" | "prtsc" | "print" => Key::PrintScreen,
272 "pause" | "break" => Key::Pause,
273 "contextmenu" | "menu" => Key::ContextMenu,
274 "copy" => Key::Copy,
275 "cut" => Key::Cut,
276 "paste" => Key::Paste,
277 "undo" => Key::Undo,
278 "redo" => Key::Redo,
279
280 s if s.len() == 1 => Key::Char(s.chars().next().unwrap()),
283
284 _ => Key::Named(s.to_string()),
292 }
293}
294
295impl From<&str> for Key {
296 fn from(s: &str) -> Self {
297 let trimmed = s.trim();
298 if trimmed.len() == 1 {
300 return Key::Char(trimmed.chars().next().unwrap());
301 }
302 parse_key_normalized(&normalize(trimmed))
303 }
304}
305
306impl From<String> for Key {
307 fn from(s: String) -> Self {
308 Key::from(s.as_str())
309 }
310}
311
312impl From<char> for Key {
313 fn from(c: char) -> Self {
314 Key::Char(c)
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
328pub struct KeyPress {
329 pub key: Key,
331 pub modifiers: KeyModifiers,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct ParseKeyPressError {
338 modifier: String,
339}
340
341impl ParseKeyPressError {
342 pub fn modifier(&self) -> &str {
344 &self.modifier
345 }
346}
347
348impl fmt::Display for ParseKeyPressError {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 write!(f, "unknown key modifier {:?}", self.modifier)
351 }
352}
353
354impl Error for ParseKeyPressError {}
355
356impl KeyPress {
357 pub fn new(key: Key, modifiers: KeyModifiers) -> Self {
359 Self { key, modifiers }
360 }
361
362 pub fn from_wire(payload: &serde_json::Value) -> Option<Self> {
369 if let Some(combo) = payload.get("combo").and_then(|v| v.as_str()) {
371 return combo.parse().ok();
372 }
373
374 let key_str = payload.get("key").and_then(|v| v.as_str())?;
375
376 if let Some(mods) = payload.get("modifiers") {
378 let get_bool = |key| mods.get(key).and_then(|v| v.as_bool()).unwrap_or(false);
379 let modifiers = KeyModifiers {
380 shift: get_bool("shift"),
381 ctrl: get_bool("ctrl") || get_bool("command"),
382 alt: get_bool("alt"),
383 logo: get_bool("logo"),
384 command: get_bool("command"),
385 };
386 return Some(Self {
387 key: Key::from(key_str),
388 modifiers,
389 });
390 }
391
392 key_str.parse().ok()
395 }
396}
397
398impl FromStr for KeyPress {
399 type Err = ParseKeyPressError;
400
401 fn from_str(s: &str) -> Result<Self, Self::Err> {
402 let parts: Vec<&str> = s.split('+').collect();
404
405 if parts.len() == 1 {
406 return Ok(Self {
408 key: Key::from(parts[0].trim()),
409 modifiers: KeyModifiers::default(),
410 });
411 }
412
413 let mut modifiers = KeyModifiers::default();
414 for part in &parts[..parts.len() - 1] {
415 let trimmed = part.trim();
416 let normalized = normalize(trimmed);
417 match normalized.as_str() {
418 "ctrl" | "control" => modifiers.ctrl = true,
419 "shift" => modifiers.shift = true,
420 "alt" | "option" | "opt" => modifiers.alt = true,
421 "logo" | "super" | "win" | "meta" => modifiers.logo = true,
422 "command" | "cmd" => modifiers.command = true,
427 "" => {}
428 _ => {
429 return Err(ParseKeyPressError {
430 modifier: trimmed.to_string(),
431 });
432 }
433 }
434 }
435
436 let key = Key::from(parts.last().unwrap().trim());
437 Ok(Self { key, modifiers })
438 }
439}
440
441impl From<&str> for KeyPress {
442 fn from(s: &str) -> Self {
443 s.parse().unwrap_or_else(|_| Self {
444 key: Key::from(s.trim()),
445 modifiers: KeyModifiers::default(),
446 })
447 }
448}
449
450impl From<String> for KeyPress {
451 fn from(s: String) -> Self {
452 KeyPress::from(s.as_str())
453 }
454}
455
456impl From<Key> for KeyPress {
457 fn from(key: Key) -> Self {
458 Self {
459 key,
460 modifiers: KeyModifiers::default(),
461 }
462 }
463}
464
465impl From<(Key, KeyModifiers)> for KeyPress {
466 fn from((key, modifiers): (Key, KeyModifiers)) -> Self {
467 Self { key, modifiers }
468 }
469}
470
471impl fmt::Display for KeyPress {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 let mut parts = Vec::new();
474 if self.modifiers.ctrl {
475 parts.push("Ctrl".to_string());
476 }
477 if self.modifiers.shift {
478 parts.push("Shift".to_string());
479 }
480 if self.modifiers.alt {
481 parts.push("Alt".to_string());
482 }
483 if self.modifiers.logo {
484 parts.push("Super".to_string());
485 }
486 parts.push(self.key.wire_name());
487 write!(f, "{}", parts.join("+"))
488 }
489}
490
491#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
497pub enum MouseButton {
498 #[default]
499 Left,
501 Right,
503 Middle,
505 Back,
507 Forward,
509}
510
511impl MouseButton {
512 pub fn wire_name(&self) -> &'static str {
514 match self {
515 Self::Left => "left",
516 Self::Right => "right",
517 Self::Middle => "middle",
518 Self::Back => "back",
519 Self::Forward => "forward",
520 }
521 }
522
523 pub fn from_wire(s: &str) -> Option<Self> {
525 match normalize(s).as_str() {
526 "left" => Some(Self::Left),
527 "right" => Some(Self::Right),
528 "middle" | "center" => Some(Self::Middle),
529 "back" => Some(Self::Back),
530 "forward" => Some(Self::Forward),
531 _ => None,
532 }
533 }
534}
535
536impl From<&str> for MouseButton {
537 fn from(s: &str) -> Self {
538 Self::from_wire(s).unwrap_or(Self::Left)
539 }
540}
541
542impl fmt::Display for MouseButton {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 f.write_str(self.wire_name())
545 }
546}
547
548#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
554pub enum PointerKind {
555 #[default]
556 Mouse,
558 Touch,
560 Pen,
562}
563
564impl PointerKind {
565 pub fn wire_name(&self) -> &'static str {
567 match self {
568 Self::Mouse => "mouse",
569 Self::Touch => "touch",
570 Self::Pen => "pen",
571 }
572 }
573
574 pub fn from_wire(s: &str) -> Option<Self> {
576 match normalize(s).as_str() {
577 "mouse" => Some(Self::Mouse),
578 "touch" => Some(Self::Touch),
579 "pen" => Some(Self::Pen),
580 _ => None,
581 }
582 }
583}
584
585impl From<&str> for PointerKind {
586 fn from(s: &str) -> Self {
587 Self::from_wire(s).unwrap_or(Self::Mouse)
588 }
589}
590
591impl fmt::Display for PointerKind {
592 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
593 f.write_str(self.wire_name())
594 }
595}
596
597#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
605pub enum InteractAction {
606 Click,
608 TypeText,
610 Submit,
612 Toggle,
614 Select,
616 Slide,
618 Paste,
620 Scroll,
622 Sort,
624 PaneFocusCycle,
626 Press,
628 Release,
630 TypeKey,
632 MoveTo,
634 CanvasPress,
636 CanvasRelease,
638 CanvasMove,
640}
641
642impl InteractAction {
643 pub fn wire_name(&self) -> &'static str {
645 match self {
646 Self::Click => "click",
647 Self::TypeText => "type_text",
648 Self::Submit => "submit",
649 Self::Toggle => "toggle",
650 Self::Select => "select",
651 Self::Slide => "slide",
652 Self::Paste => "paste",
653 Self::Scroll => "scroll",
654 Self::Sort => "sort",
655 Self::PaneFocusCycle => "pane_focus_cycle",
656 Self::Press => "press",
657 Self::Release => "release",
658 Self::TypeKey => "type_key",
659 Self::MoveTo => "move_to",
660 Self::CanvasPress => "canvas_press",
661 Self::CanvasRelease => "canvas_release",
662 Self::CanvasMove => "canvas_move",
663 }
664 }
665
666 pub fn from_wire(s: &str) -> Option<Self> {
668 Some(match normalize(s).as_str() {
669 "click" => Self::Click,
670 "typetext" | "type" => Self::TypeText,
671 "submit" => Self::Submit,
672 "toggle" => Self::Toggle,
673 "select" => Self::Select,
674 "slide" => Self::Slide,
675 "paste" => Self::Paste,
676 "scroll" => Self::Scroll,
677 "sort" => Self::Sort,
678 "panefocuscycle" => Self::PaneFocusCycle,
679 "press" => Self::Press,
680 "release" => Self::Release,
681 "typekey" => Self::TypeKey,
682 "moveto" | "move" => Self::MoveTo,
683 "canvaspress" => Self::CanvasPress,
684 "canvasrelease" => Self::CanvasRelease,
685 "canvasmove" => Self::CanvasMove,
686 _ => return None,
687 })
688 }
689}
690
691impl fmt::Display for InteractAction {
692 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
693 f.write_str(self.wire_name())
694 }
695}
696
697#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
703pub enum EffectKind {
704 FileOpen,
706 FileOpenMultiple,
708 FileSave,
710 DirectorySelect,
712 DirectorySelectMultiple,
714 ClipboardRead,
716 ClipboardWrite,
718 ClipboardReadHtml,
720 ClipboardWriteHtml,
722 ClipboardClear,
724 ClipboardReadPrimary,
726 ClipboardWritePrimary,
728 Notification,
730}
731
732impl EffectKind {
733 pub fn wire_name(&self) -> &'static str {
735 match self {
736 Self::FileOpen => "file_open",
737 Self::FileOpenMultiple => "file_open_multiple",
738 Self::FileSave => "file_save",
739 Self::DirectorySelect => "directory_select",
740 Self::DirectorySelectMultiple => "directory_select_multiple",
741 Self::ClipboardRead => "clipboard_read",
742 Self::ClipboardWrite => "clipboard_write",
743 Self::ClipboardReadHtml => "clipboard_read_html",
744 Self::ClipboardWriteHtml => "clipboard_write_html",
745 Self::ClipboardClear => "clipboard_clear",
746 Self::ClipboardReadPrimary => "clipboard_read_primary",
747 Self::ClipboardWritePrimary => "clipboard_write_primary",
748 Self::Notification => "notification",
749 }
750 }
751}
752
753impl EffectKind {
754 pub fn from_wire(s: &str) -> Option<Self> {
756 Some(match normalize(s).as_str() {
757 "fileopen" => Self::FileOpen,
758 "fileopenmultiple" => Self::FileOpenMultiple,
759 "filesave" => Self::FileSave,
760 "directoryselect" => Self::DirectorySelect,
761 "directoryselectmultiple" => Self::DirectorySelectMultiple,
762 "clipboardread" => Self::ClipboardRead,
763 "clipboardwrite" => Self::ClipboardWrite,
764 "clipboardreadhtml" => Self::ClipboardReadHtml,
765 "clipboardwritehtml" => Self::ClipboardWriteHtml,
766 "clipboardclear" => Self::ClipboardClear,
767 "clipboardreadprimary" => Self::ClipboardReadPrimary,
768 "clipboardwriteprimary" => Self::ClipboardWritePrimary,
769 "notification" => Self::Notification,
770 _ => return None,
771 })
772 }
773}
774
775impl fmt::Display for EffectKind {
776 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
777 f.write_str(self.wire_name())
778 }
779}
780
781pub fn normalize(input: &str) -> String {
791 input
792 .chars()
793 .filter(|c| !c.is_whitespace() && *c != '_' && *c != '-')
794 .flat_map(char::to_lowercase)
795 .collect()
796}
797
798#[cfg(test)]
803mod tests {
804 use super::*;
805
806 #[test]
807 fn normalize_strips_and_lowercases() {
808 assert_eq!(normalize("LeftArrow"), "leftarrow");
809 assert_eq!(normalize("left_arrow"), "leftarrow");
810 assert_eq!(normalize("left-arrow"), "leftarrow");
811 assert_eq!(normalize("Left Arrow"), "leftarrow");
812 assert_eq!(normalize("PAGE_UP"), "pageup");
813 assert_eq!(normalize("Ctrl"), "ctrl");
814 }
815
816 #[test]
817 fn key_from_str_named_keys() {
818 assert_eq!(Key::from("Enter"), Key::Enter);
819 assert_eq!(Key::from("enter"), Key::Enter);
820 assert_eq!(Key::from("return"), Key::Enter);
821 assert_eq!(Key::from("ESCAPE"), Key::Escape);
822 assert_eq!(Key::from("esc"), Key::Escape);
823 assert_eq!(Key::from("Tab"), Key::Tab);
824 assert_eq!(Key::from("Backspace"), Key::Backspace);
825 assert_eq!(Key::from("bs"), Key::Backspace);
826 assert_eq!(Key::from("Delete"), Key::Delete);
827 assert_eq!(Key::from("del"), Key::Delete);
828 assert_eq!(Key::from("Space"), Key::Space);
829 }
830
831 #[test]
832 fn key_from_str_arrows() {
833 assert_eq!(Key::from("ArrowLeft"), Key::ArrowLeft);
834 assert_eq!(Key::from("left_arrow"), Key::ArrowLeft);
835 assert_eq!(Key::from("left"), Key::ArrowLeft);
836 assert_eq!(Key::from("Left"), Key::ArrowLeft);
837 assert_eq!(Key::from("LeftArrow"), Key::ArrowLeft);
838 assert_eq!(Key::from("ArrowUp"), Key::ArrowUp);
839 assert_eq!(Key::from("up"), Key::ArrowUp);
840 }
841
842 #[test]
843 fn key_from_str_page_nav() {
844 assert_eq!(Key::from("PageUp"), Key::PageUp);
845 assert_eq!(Key::from("page_up"), Key::PageUp);
846 assert_eq!(Key::from("pgup"), Key::PageUp);
847 assert_eq!(Key::from("PageDown"), Key::PageDown);
848 assert_eq!(Key::from("pgdn"), Key::PageDown);
849 }
850
851 #[test]
852 fn key_from_str_function_keys() {
853 assert_eq!(Key::from("F1"), Key::F1);
854 assert_eq!(Key::from("f12"), Key::F12);
855 }
856
857 #[test]
858 fn key_from_str_single_char() {
859 assert_eq!(Key::from("a"), Key::Char('a'));
860 assert_eq!(Key::from("1"), Key::Char('1'));
861 }
862
863 #[test]
864 fn key_from_str_unknown_falls_to_named() {
865 assert_eq!(Key::from("MediaPlay"), Key::Named("mediaplay".into()));
866 }
867
868 #[test]
869 fn keypress_from_str_simple() {
870 let kp = KeyPress::from("Enter");
871 assert_eq!(kp.key, Key::Enter);
872 assert_eq!(kp.modifiers, KeyModifiers::default());
873 }
874
875 #[test]
876 fn keypress_from_str_with_modifier() {
877 let kp = KeyPress::from("Ctrl+s");
878 assert_eq!(kp.key, Key::Char('s'));
879 assert!(kp.modifiers.ctrl);
880 assert!(!kp.modifiers.shift);
881 }
882
883 #[test]
884 fn keypress_from_str_multiple_modifiers() {
885 let kp = KeyPress::from("Ctrl+Shift+Enter");
886 assert_eq!(kp.key, Key::Enter);
887 assert!(kp.modifiers.ctrl);
888 assert!(kp.modifiers.shift);
889 }
890
891 #[test]
892 fn keypress_from_str_spaces_around_plus() {
893 let kp = KeyPress::from("Ctrl + Left_Arrow");
894 assert_eq!(kp.key, Key::ArrowLeft);
895 assert!(kp.modifiers.ctrl);
896 }
897
898 #[test]
899 fn keypress_from_str_modifier_aliases() {
900 let kp = KeyPress::from("Command+s");
903 assert!(kp.modifiers.command);
904 assert!(!kp.modifiers.ctrl);
905 assert!(!kp.modifiers.logo);
906
907 let kp = KeyPress::from("Option+a");
908 assert!(kp.modifiers.alt);
909
910 let kp = KeyPress::from("Win+e");
912 assert!(kp.modifiers.logo);
913
914 let kp = KeyPress::from("Super+e");
915 assert!(kp.modifiers.logo);
916
917 let kp = KeyPress::from("Ctrl+s");
919 assert!(kp.modifiers.ctrl);
920 assert!(!kp.modifiers.command);
921 }
922
923 #[test]
924 fn keypress_from_str_malformed() {
925 let kp = KeyPress::from("");
927 assert_eq!(kp.key, Key::Named(String::new()));
928 assert_eq!(kp.modifiers, KeyModifiers::default());
929
930 let kp = KeyPress::from("+");
933 assert_eq!(kp.key, Key::Named(String::new()));
934 assert_eq!(kp.modifiers, KeyModifiers::default());
935
936 let kp = KeyPress::from("Ctrl+");
939 assert_eq!(kp.key, Key::Named(String::new()));
940 assert!(kp.modifiers.ctrl);
941
942 let err = "Foo+s".parse::<KeyPress>().unwrap_err();
943 assert_eq!(err.modifier(), "Foo");
944
945 let kp = KeyPress::from("+s");
948 assert_eq!(kp.key, Key::Char('s'));
949 assert_eq!(kp.modifiers, KeyModifiers::default());
950 }
951
952 #[test]
953 fn keypress_from_str_unknown_modifier_is_literal_key() {
954 let kp = KeyPress::from("Crtl+s");
955 assert_eq!(kp.key, Key::Named("crtl+s".to_string()));
956 assert_eq!(kp.modifiers, KeyModifiers::default());
957 }
958
959 #[test]
960 fn keypress_from_wire_rejects_unknown_modifier_combo() {
961 let payload = serde_json::json!({"combo": "Crtl+s"});
962 assert_eq!(KeyPress::from_wire(&payload), None);
963 }
964
965 #[test]
966 fn keypress_from_wire_combo() {
967 let payload = serde_json::json!({"combo": "Shift+Enter"});
968 let kp = KeyPress::from_wire(&payload).unwrap();
969 assert_eq!(kp.key, Key::Enter);
970 assert!(kp.modifiers.shift);
971 }
972
973 #[test]
974 fn keypress_from_wire_explicit() {
975 let payload = serde_json::json!({"key": "s", "modifiers": {"ctrl": true}});
976 let kp = KeyPress::from_wire(&payload).unwrap();
977 assert_eq!(kp.key, Key::Char('s'));
978 assert!(kp.modifiers.ctrl);
979 }
980
981 #[test]
982 fn keypress_from_wire_command_alias() {
983 let payload = serde_json::json!({"key": "s", "modifiers": {"command": true}});
984 let kp = KeyPress::from_wire(&payload).unwrap();
985 assert!(kp.modifiers.ctrl);
986 }
987
988 #[test]
989 fn mouse_button_from_str() {
990 assert_eq!(MouseButton::from("left"), MouseButton::Left);
991 assert_eq!(MouseButton::from("Right"), MouseButton::Right);
992 assert_eq!(MouseButton::from("MIDDLE"), MouseButton::Middle);
993 assert_eq!(MouseButton::from("center"), MouseButton::Middle);
994 assert_eq!(MouseButton::from("unknown"), MouseButton::Left);
995 }
996
997 #[test]
998 fn interact_action_from_wire() {
999 assert_eq!(
1000 InteractAction::from_wire("click"),
1001 Some(InteractAction::Click)
1002 );
1003 assert_eq!(
1004 InteractAction::from_wire("type_text"),
1005 Some(InteractAction::TypeText)
1006 );
1007 assert_eq!(
1008 InteractAction::from_wire("canvas_press"),
1009 Some(InteractAction::CanvasPress)
1010 );
1011 assert_eq!(InteractAction::from_wire("unknown"), None);
1012 }
1013
1014 #[test]
1015 fn effect_kind_from_wire() {
1016 assert_eq!(
1017 EffectKind::from_wire("file_open"),
1018 Some(EffectKind::FileOpen)
1019 );
1020 assert_eq!(
1021 EffectKind::from_wire("clipboard_read"),
1022 Some(EffectKind::ClipboardRead)
1023 );
1024 assert_eq!(
1025 EffectKind::from_wire("FileOpen"),
1026 Some(EffectKind::FileOpen)
1027 );
1028 assert_eq!(EffectKind::from_wire("nonsense"), None);
1029 }
1030}