Skip to main content

ai_agent/utils/
modifiers.rs

1// Source: ~/claudecode/openclaudecode/src/utils/modifiers.ts
2
3use serde::{Deserialize, Serialize};
4
5/// Modifier key types.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ModifierKey {
8    Shift,
9    Command,
10    Control,
11    Option,
12}
13
14impl ModifierKey {
15    fn as_str(&self) -> &'static str {
16        match self {
17            Self::Shift => "shift",
18            Self::Command => "command",
19            Self::Control => "control",
20            Self::Option => "option",
21        }
22    }
23}
24
25/// A keyboard modifier.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum Modifier {
28    Shift,
29    Ctrl,
30    Alt,
31    Meta,
32}
33
34impl Modifier {
35    pub fn as_str(&self) -> &'static str {
36        match self {
37            Self::Shift => "shift",
38            Self::Ctrl => "ctrl",
39            Self::Alt => "alt",
40            Self::Meta => "meta",
41        }
42    }
43}
44
45/// A keyboard shortcut consisting of optional modifiers and a key.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Shortcut {
48    pub modifiers: Vec<Modifier>,
49    pub key: String,
50}
51
52impl Shortcut {
53    pub fn new(key: impl Into<String>, modifiers: Vec<Modifier>) -> Self {
54        Self {
55            modifiers,
56            key: key.into(),
57        }
58    }
59
60    pub fn display(&self) -> String {
61        let mut parts = Vec::new();
62        for m in &self.modifiers {
63            parts.push(match m {
64                Modifier::Shift => "⇧",
65                Modifier::Ctrl => "⌃",
66                Modifier::Alt => "⌥",
67                Modifier::Meta => "⌘",
68            });
69        }
70        parts.push(&self.key);
71        parts.join("")
72    }
73}
74
75static PREWARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
76
77/// Pre-warm the native module by loading it in advance.
78/// Call this early to avoid delay on first use.
79/// Note: This is a no-op on non-macOS platforms.
80pub fn prewarm_modifiers() {
81    if PREWARMED.load(std::sync::atomic::Ordering::SeqCst) {
82        return;
83    }
84
85    // Only on macOS
86    if !cfg!(target_os = "macos") {
87        return;
88    }
89
90    PREWARMED.store(true, std::sync::atomic::Ordering::SeqCst);
91
92    // In production, this would load the native module
93    // For now, this is a no-op as we don't have the native bindings
94}
95
96/// Check if a specific modifier key is currently pressed.
97/// Note: This is a no-op on non-macOS platforms.
98pub fn is_modifier_pressed(_modifier: ModifierKey) -> bool {
99    // Only on macOS
100    if !cfg!(target_os = "macos") {
101        return false;
102    }
103
104    // In production, this would call the native module
105    // For now, always return false as we don't have the native bindings
106    false
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_prewarm_does_not_panic() {
115        prewarm_modifiers();
116        prewarm_modifiers(); // Should be idempotent
117    }
118
119    #[test]
120    fn test_is_modifier_pressed() {
121        // On non-macOS this always returns false
122        // On macOS without native module it also returns false
123        let _ = is_modifier_pressed(ModifierKey::Shift);
124    }
125}