⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/desktop/desktop_ext/
shortcuts.rs

1//! Global Keyboard Shortcuts
2//!
3//! Configurable key bindings with modifier masks.
4
5#[cfg(feature = "alloc")]
6use alloc::{collections::BTreeMap, vec::Vec};
7
8/// Modifier key bitmask.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub struct ModifierMask(pub u8);
11
12impl ModifierMask {
13    pub const NONE: Self = Self(0);
14    pub const CTRL: Self = Self(1 << 0);
15    pub const ALT: Self = Self(1 << 1);
16    pub const SUPER: Self = Self(1 << 2);
17    pub const SHIFT: Self = Self(1 << 3);
18
19    /// Check if a modifier is set.
20    pub fn has(self, modifier: Self) -> bool {
21        (self.0 & modifier.0) == modifier.0
22    }
23
24    /// Combine two modifier masks.
25    pub fn combine(self, other: Self) -> Self {
26        Self(self.0 | other.0)
27    }
28
29    /// Remove a modifier.
30    pub fn remove(self, modifier: Self) -> Self {
31        Self(self.0 & !modifier.0)
32    }
33
34    /// Check if this mask is empty (no modifiers).
35    pub fn is_empty(self) -> bool {
36        self.0 == 0
37    }
38}
39
40/// Key code (PS/2 scancode or virtual key code).
41pub type KeyCode = u8;
42
43/// Actions that can be triggered by keyboard shortcuts.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ShortcutAction {
46    /// Launch the application launcher.
47    LaunchLauncher,
48    /// Launch terminal.
49    LaunchTerminal,
50    /// Launch file manager.
51    LaunchFileManager,
52    /// Close the focused window.
53    CloseWindow,
54    /// Minimize the focused window.
55    MinimizeWindow,
56    /// Maximize/restore the focused window.
57    MaximizeWindow,
58    /// Toggle fullscreen on focused window.
59    ToggleFullscreen,
60    /// Switch to workspace N (0-based).
61    SwitchWorkspace(u8),
62    /// Move window to workspace N (0-based).
63    MoveToWorkspace(u8),
64    /// Switch to next window (Alt+Tab).
65    SwitchNextWindow,
66    /// Switch to previous window (Alt+Shift+Tab).
67    SwitchPrevWindow,
68    /// Take a screenshot.
69    Screenshot,
70    /// Take a screenshot of the focused window.
71    ScreenshotWindow,
72    /// Lock the screen.
73    LockScreen,
74    /// Log out.
75    Logout,
76    /// Snap window left.
77    SnapLeft,
78    /// Snap window right.
79    SnapRight,
80    /// Copy (Ctrl+C).
81    Copy,
82    /// Paste (Ctrl+V).
83    Paste,
84    /// Cut (Ctrl+X).
85    Cut,
86    /// Undo (Ctrl+Z).
87    Undo,
88    /// Redo (Ctrl+Shift+Z or Ctrl+Y).
89    Redo,
90    /// Custom action identified by ID.
91    Custom(u16),
92}
93
94/// Priority for shortcut matching (higher wins).
95#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
96pub enum ShortcutPriority {
97    /// System-level shortcuts (cannot be overridden).
98    System = 3,
99    /// Desktop environment shortcuts.
100    Desktop = 2,
101    /// Application shortcuts.
102    Application = 1,
103    /// User-defined shortcuts.
104    #[default]
105    User = 0,
106}
107
108/// A keyboard shortcut binding.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct KeyBinding {
111    /// Required modifier keys.
112    pub modifiers: ModifierMask,
113    /// The key code.
114    pub key: KeyCode,
115    /// Action to perform.
116    pub action: ShortcutAction,
117    /// Priority for conflict resolution.
118    pub priority: ShortcutPriority,
119    /// Whether this binding is currently enabled.
120    pub enabled: bool,
121}
122
123impl KeyBinding {
124    /// Create a new key binding.
125    pub fn new(modifiers: ModifierMask, key: KeyCode, action: ShortcutAction) -> Self {
126        Self {
127            modifiers,
128            key,
129            action,
130            priority: ShortcutPriority::User,
131            enabled: true,
132        }
133    }
134
135    /// Create a new key binding with priority.
136    pub fn with_priority(mut self, priority: ShortcutPriority) -> Self {
137        self.priority = priority;
138        self
139    }
140
141    /// Check if this binding matches the given modifiers and key.
142    pub fn matches(&self, modifiers: ModifierMask, key: KeyCode) -> bool {
143        self.enabled && self.modifiers == modifiers && self.key == key
144    }
145}
146
147/// Maximum number of registered shortcuts.
148const MAX_SHORTCUTS: usize = 128;
149
150/// Keyboard shortcut manager.
151#[derive(Debug)]
152#[cfg(feature = "alloc")]
153pub struct ShortcutManager {
154    /// Registered bindings.
155    bindings: Vec<KeyBinding>,
156    /// Whether shortcut processing is globally enabled.
157    enabled: bool,
158    /// Binding IDs for removal (index into bindings).
159    next_id: u32,
160    /// Map from binding ID to index.
161    id_map: BTreeMap<u32, usize>,
162}
163
164#[cfg(feature = "alloc")]
165impl Default for ShortcutManager {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171#[cfg(feature = "alloc")]
172impl ShortcutManager {
173    /// Create a new shortcut manager with default system bindings.
174    pub fn new() -> Self {
175        let mut mgr = Self {
176            bindings: Vec::new(),
177            enabled: true,
178            next_id: 0,
179            id_map: BTreeMap::new(),
180        };
181        mgr.register_defaults();
182        mgr
183    }
184
185    /// Register default system shortcuts.
186    fn register_defaults(&mut self) {
187        // Alt+Tab: switch window
188        self.register(
189            KeyBinding::new(ModifierMask::ALT, 0x0F, ShortcutAction::SwitchNextWindow)
190                .with_priority(ShortcutPriority::System),
191        );
192        // Ctrl+Alt+L: lock screen
193        self.register(
194            KeyBinding::new(
195                ModifierMask(ModifierMask::CTRL.0 | ModifierMask::ALT.0),
196                0x26,
197                ShortcutAction::LockScreen,
198            )
199            .with_priority(ShortcutPriority::System),
200        );
201        // Super: launcher
202        self.register(
203            KeyBinding::new(ModifierMask::SUPER, 0xDB, ShortcutAction::LaunchLauncher)
204                .with_priority(ShortcutPriority::Desktop),
205        );
206        // Ctrl+C: copy
207        self.register(
208            KeyBinding::new(ModifierMask::CTRL, 0x2E, ShortcutAction::Copy)
209                .with_priority(ShortcutPriority::Application),
210        );
211        // Ctrl+V: paste
212        self.register(
213            KeyBinding::new(ModifierMask::CTRL, 0x2F, ShortcutAction::Paste)
214                .with_priority(ShortcutPriority::Application),
215        );
216        // Ctrl+X: cut
217        self.register(
218            KeyBinding::new(ModifierMask::CTRL, 0x2D, ShortcutAction::Cut)
219                .with_priority(ShortcutPriority::Application),
220        );
221        // Alt+F4: close window
222        self.register(
223            KeyBinding::new(ModifierMask::ALT, 0x3E, ShortcutAction::CloseWindow)
224                .with_priority(ShortcutPriority::Desktop),
225        );
226        // Print Screen (scancode 0x37 with E0 prefix): screenshot
227        self.register(
228            KeyBinding::new(ModifierMask::NONE, 0xB7, ShortcutAction::Screenshot)
229                .with_priority(ShortcutPriority::System),
230        );
231    }
232
233    /// Register a new shortcut binding. Returns binding ID.
234    pub fn register(&mut self, binding: KeyBinding) -> u32 {
235        let id = self.next_id;
236        self.next_id += 1;
237
238        if self.bindings.len() < MAX_SHORTCUTS {
239            self.id_map.insert(id, self.bindings.len());
240            self.bindings.push(binding);
241        }
242
243        id
244    }
245
246    /// Remove a shortcut by ID.
247    pub fn unregister(&mut self, id: u32) -> bool {
248        if let Some(&index) = self.id_map.get(&id) {
249            if index < self.bindings.len() {
250                self.bindings.remove(index);
251                self.id_map.remove(&id);
252                // Rebuild ID map (indices shifted).
253                let mut new_map = BTreeMap::new();
254                for (&k, &v) in &self.id_map {
255                    if v > index {
256                        new_map.insert(k, v - 1);
257                    } else {
258                        new_map.insert(k, v);
259                    }
260                }
261                self.id_map = new_map;
262                return true;
263            }
264        }
265        false
266    }
267
268    /// Process a key event and return the matching action (if any).
269    /// Returns the highest-priority matching action.
270    pub fn process_key(&self, modifiers: ModifierMask, key: KeyCode) -> Option<ShortcutAction> {
271        if !self.enabled {
272            return None;
273        }
274
275        self.bindings
276            .iter()
277            .filter(|b| b.matches(modifiers, key))
278            .max_by_key(|b| b.priority)
279            .map(|b| b.action)
280    }
281
282    /// Enable or disable all shortcut processing.
283    pub fn set_enabled(&mut self, enabled: bool) {
284        self.enabled = enabled;
285    }
286
287    /// Check if shortcuts are enabled.
288    pub fn is_enabled(&self) -> bool {
289        self.enabled
290    }
291
292    /// Get the number of registered bindings.
293    pub fn binding_count(&self) -> usize {
294        self.bindings.len()
295    }
296
297    /// Get all bindings for a given action.
298    pub fn bindings_for_action(&self, action: ShortcutAction) -> Vec<&KeyBinding> {
299        self.bindings
300            .iter()
301            .filter(|b| b.action == action)
302            .collect()
303    }
304
305    /// Enable or disable a specific binding by ID.
306    pub fn set_binding_enabled(&mut self, id: u32, enabled: bool) -> bool {
307        if let Some(&index) = self.id_map.get(&id) {
308            if let Some(binding) = self.bindings.get_mut(index) {
309                binding.enabled = enabled;
310                return true;
311            }
312        }
313        false
314    }
315}