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

veridian_kernel/desktop/
window_manager.rs

1//! Window Manager with Event Loop
2//!
3//! Manages windows, input events, and coordinates desktop applications.
4//! Provides window placement heuristics, snap/tile support, and virtual
5//! workspaces.
6//!
7//! Public API methods define the complete window management interface.
8//! Some methods (e.g., tiling layouts, opacity control) are not yet wired
9//! into the desktop renderer but are retained as part of the WM API surface.
10#![allow(dead_code)]
11
12use alloc::{collections::BTreeMap, vec::Vec};
13
14use spin::RwLock;
15
16use crate::{error::KernelError, sync::once_lock::GlobalState};
17
18/// Window ID type
19pub type WindowId = u32;
20
21/// Workspace identifier
22pub type WorkspaceId = u8;
23
24/// Maximum number of workspaces
25pub const MAX_WORKSPACES: usize = 4;
26
27/// Window state
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum WindowState {
30    Normal,
31    Minimized,
32    Maximized,
33    Fullscreen,
34    Hidden,
35}
36
37/// Window placement heuristic
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum PlacementHeuristic {
40    /// Cascade from top-left corner
41    Cascade,
42    /// Center on screen
43    Center,
44    /// Smart placement avoiding overlap
45    Smart,
46    /// User-specified position
47    Manual { x: i32, y: i32 },
48}
49
50/// Snap zone for window tiling
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum SnapZone {
53    None,
54    Left,
55    Right,
56    Top,
57    Bottom,
58    TopLeft,
59    TopRight,
60    BottomLeft,
61    BottomRight,
62    Maximize,
63}
64
65/// Tile layout mode for arranging all visible windows
66#[derive(Debug, Clone, Copy)]
67pub enum TileLayout {
68    /// Equal horizontal split (side by side columns)
69    EqualColumns,
70    /// Master-stack: largest window on left (60%), remainder stacked right
71    /// (40%)
72    MasterStack,
73    /// Grid arrangement (auto rows x cols)
74    Grid,
75}
76
77/// Window structure
78#[derive(Debug, Clone)]
79pub struct Window {
80    pub id: WindowId,
81    pub x: i32,
82    pub y: i32,
83    pub width: u32,
84    pub height: u32,
85    pub title: [u8; 64],
86    pub title_len: usize,
87    pub state: WindowState,
88    pub visible: bool,
89    pub focused: bool,
90    pub owner_pid: u64,
91    /// Window opacity (0 = transparent, 255 = fully opaque)
92    pub opacity: u8,
93    /// Saved geometry before snap/maximize (x, y, w, h)
94    pub saved_geometry: Option<(i32, i32, u32, u32)>,
95    /// Current snap zone
96    pub snap_zone: SnapZone,
97    /// Workspace this window belongs to
98    pub workspace: WorkspaceId,
99}
100
101impl Window {
102    /// Create a new window
103    pub fn new(id: WindowId, x: i32, y: i32, width: u32, height: u32, owner_pid: u64) -> Self {
104        Self {
105            id,
106            x,
107            y,
108            width,
109            height,
110            title: [0; 64],
111            title_len: 0,
112            state: WindowState::Normal,
113            visible: true,
114            focused: false,
115            owner_pid,
116            opacity: 255,
117            saved_geometry: None,
118            snap_zone: SnapZone::None,
119            workspace: 0,
120        }
121    }
122
123    /// Set window title
124    pub fn set_title(&mut self, title: &str) {
125        let bytes = title.as_bytes();
126        let len = bytes.len().min(64);
127        self.title[..len].copy_from_slice(&bytes[..len]);
128        self.title_len = len;
129    }
130
131    /// Get window title as string slice
132    pub fn title_str(&self) -> &str {
133        core::str::from_utf8(&self.title[..self.title_len]).unwrap_or("")
134    }
135}
136
137/// Virtual workspace containing a set of windows
138pub struct Workspace {
139    pub id: WorkspaceId,
140    pub name: [u8; 32],
141    pub name_len: usize,
142    pub windows: Vec<WindowId>,
143}
144
145impl Workspace {
146    /// Create a new workspace with a numeric name
147    fn new(id: WorkspaceId) -> Self {
148        let mut name = [0u8; 32];
149        let digit = b'1' + id;
150        name[0] = digit;
151        Self {
152            id,
153            name,
154            name_len: 1,
155            windows: Vec::new(),
156        }
157    }
158
159    /// Get workspace name as string slice
160    pub fn name_str(&self) -> &str {
161        core::str::from_utf8(&self.name[..self.name_len]).unwrap_or("")
162    }
163}
164
165/// Input event types
166#[derive(Debug, Clone, Copy)]
167pub enum InputEvent {
168    KeyPress {
169        scancode: u8,
170        character: char,
171    },
172    KeyRelease {
173        scancode: u8,
174    },
175    MouseMove {
176        x: i32,
177        y: i32,
178    },
179    MouseButton {
180        button: u8,
181        pressed: bool,
182        x: i32,
183        y: i32,
184    },
185    MouseScroll {
186        delta_x: i16,
187        delta_y: i16,
188    },
189}
190
191/// Window event
192#[derive(Debug, Clone)]
193pub struct WindowEvent {
194    pub window_id: WindowId,
195    pub event: InputEvent,
196}
197
198/// Window Manager
199pub struct WindowManager {
200    /// All windows indexed by ID
201    windows: RwLock<BTreeMap<WindowId, Window>>,
202
203    /// Window Z-order (bottom to top)
204    z_order: RwLock<Vec<WindowId>>,
205
206    /// Currently focused window
207    focused_window: RwLock<Option<WindowId>>,
208
209    /// Event queue
210    event_queue: RwLock<Vec<WindowEvent>>,
211
212    /// Next window ID
213    next_window_id: RwLock<WindowId>,
214
215    /// Mouse cursor position
216    mouse_x: RwLock<i32>,
217    mouse_y: RwLock<i32>,
218
219    // --- WM-1: Placement heuristics ---
220    /// Current placement heuristic for new windows
221    placement_heuristic: RwLock<PlacementHeuristic>,
222
223    /// Next cascade offset position (x, y)
224    cascade_offset: RwLock<(i32, i32)>,
225
226    /// Screen dimensions
227    screen_width: RwLock<u32>,
228    screen_height: RwLock<u32>,
229
230    // --- WM-4: Virtual workspaces ---
231    /// Virtual workspaces
232    workspaces: RwLock<Vec<Workspace>>,
233
234    /// Currently active workspace
235    active_workspace: RwLock<WorkspaceId>,
236}
237
238impl WindowManager {
239    /// Create a new window manager
240    pub fn new() -> Self {
241        let mut workspaces = Vec::with_capacity(MAX_WORKSPACES);
242        for i in 0..MAX_WORKSPACES {
243            workspaces.push(Workspace::new(i as WorkspaceId));
244        }
245
246        Self {
247            windows: RwLock::new(BTreeMap::new()),
248            z_order: RwLock::new(Vec::new()),
249            focused_window: RwLock::new(None),
250            event_queue: RwLock::new(Vec::new()),
251            next_window_id: RwLock::new(1),
252            mouse_x: RwLock::new(0),
253            mouse_y: RwLock::new(0),
254            placement_heuristic: RwLock::new(PlacementHeuristic::Cascade),
255            cascade_offset: RwLock::new((32, 32)),
256            screen_width: RwLock::new(1280),
257            screen_height: RwLock::new(800),
258            workspaces: RwLock::new(workspaces),
259            active_workspace: RwLock::new(0),
260        }
261    }
262
263    /// Create a new window
264    pub fn create_window(
265        &self,
266        x: i32,
267        y: i32,
268        width: u32,
269        height: u32,
270        owner_pid: u64,
271    ) -> Result<WindowId, KernelError> {
272        let id = {
273            let mut next_id = self.next_window_id.write();
274            let id = *next_id;
275            *next_id += 1;
276            id
277        };
278
279        let window = Window::new(id, x, y, width, height, owner_pid);
280
281        self.windows.write().insert(id, window);
282        self.z_order.write().push(id);
283
284        // Add to active workspace
285        let active = *self.active_workspace.read();
286        {
287            let mut workspaces = self.workspaces.write();
288            if let Some(ws) = workspaces.get_mut(active as usize) {
289                ws.windows.push(id);
290            }
291        }
292
293        println!("[WM] Created window {} for PID {}", id, owner_pid);
294
295        Ok(id)
296    }
297
298    /// Destroy a window
299    pub fn destroy_window(&self, window_id: WindowId) -> Result<(), KernelError> {
300        self.windows.write().remove(&window_id);
301        self.z_order.write().retain(|&id| id != window_id);
302
303        // Remove from all workspaces
304        {
305            let mut workspaces = self.workspaces.write();
306            for ws in workspaces.iter_mut() {
307                ws.windows.retain(|&id| id != window_id);
308            }
309        }
310
311        if *self.focused_window.read() == Some(window_id) {
312            *self.focused_window.write() = None;
313        }
314
315        println!("[WM] Destroyed window {}", window_id);
316
317        Ok(())
318    }
319
320    /// Move a window
321    pub fn move_window(&self, window_id: WindowId, x: i32, y: i32) -> Result<(), KernelError> {
322        if let Some(window) = self.windows.write().get_mut(&window_id) {
323            window.x = x;
324            window.y = y;
325            Ok(())
326        } else {
327            Err(KernelError::NotFound {
328                resource: "window",
329                id: window_id as u64,
330            })
331        }
332    }
333
334    /// Resize a window
335    pub fn resize_window(
336        &self,
337        window_id: WindowId,
338        width: u32,
339        height: u32,
340    ) -> Result<(), KernelError> {
341        if let Some(window) = self.windows.write().get_mut(&window_id) {
342            window.width = width;
343            window.height = height;
344            Ok(())
345        } else {
346            Err(KernelError::NotFound {
347                resource: "window",
348                id: window_id as u64,
349            })
350        }
351    }
352
353    /// Focus a window
354    pub fn focus_window(&self, window_id: WindowId) -> Result<(), KernelError> {
355        // Unfocus previous window
356        if let Some(prev_id) = *self.focused_window.read() {
357            if let Some(prev_window) = self.windows.write().get_mut(&prev_id) {
358                prev_window.focused = false;
359            }
360        }
361
362        // Focus new window
363        if let Some(window) = self.windows.write().get_mut(&window_id) {
364            window.focused = true;
365            *self.focused_window.write() = Some(window_id);
366
367            // Bring to front
368            let mut z_order = self.z_order.write();
369            z_order.retain(|&id| id != window_id);
370            z_order.push(window_id);
371
372            println!("[WM] Focused window {}", window_id);
373            Ok(())
374        } else {
375            Err(KernelError::NotFound {
376                resource: "window",
377                id: window_id as u64,
378            })
379        }
380    }
381
382    /// Get window at position
383    pub fn window_at_position(&self, x: i32, y: i32) -> Option<WindowId> {
384        let windows = self.windows.read();
385        let z_order = self.z_order.read();
386
387        // Search from top to bottom
388        for &window_id in z_order.iter().rev() {
389            if let Some(window) = windows.get(&window_id) {
390                if window.visible
391                    && x >= window.x
392                    && x < window.x + window.width as i32
393                    && y >= window.y
394                    && y < window.y + window.height as i32
395                {
396                    return Some(window_id);
397                }
398            }
399        }
400
401        None
402    }
403
404    /// Process input event
405    pub fn process_input(&self, event: InputEvent) {
406        match event {
407            InputEvent::MouseMove { x, y } => {
408                *self.mouse_x.write() = x;
409                *self.mouse_y.write() = y;
410
411                // Send to focused window
412                if let Some(window_id) = *self.focused_window.read() {
413                    self.queue_event(WindowEvent { window_id, event });
414                }
415            }
416            InputEvent::MouseButton {
417                button: _button,
418                pressed,
419                x,
420                y,
421            } => {
422                if pressed {
423                    // Click - focus window at position
424                    if let Some(window_id) = self.window_at_position(x, y) {
425                        if let Err(_e) = self.focus_window(window_id) {
426                            crate::println!(
427                                "[WM] Warning: failed to focus window {}: {:?}",
428                                window_id,
429                                _e
430                            );
431                        }
432
433                        // Send click event to window
434                        self.queue_event(WindowEvent { window_id, event });
435                    }
436                } else {
437                    // Release - send to focused window
438                    if let Some(window_id) = *self.focused_window.read() {
439                        self.queue_event(WindowEvent { window_id, event });
440                    }
441                }
442            }
443            InputEvent::KeyPress { .. }
444            | InputEvent::KeyRelease { .. }
445            | InputEvent::MouseScroll { .. } => {
446                // Send keyboard events to focused window
447                if let Some(window_id) = *self.focused_window.read() {
448                    self.queue_event(WindowEvent { window_id, event });
449                }
450            }
451        }
452    }
453
454    /// Queue an event for delivery
455    pub fn queue_event(&self, event: WindowEvent) {
456        self.event_queue.write().push(event);
457    }
458
459    /// Get pending events for a window
460    pub fn get_events(&self, window_id: WindowId) -> Vec<InputEvent> {
461        let mut queue = self.event_queue.write();
462        let mut events = Vec::new();
463
464        // Extract events for this window
465        let mut i = 0;
466        while i < queue.len() {
467            if queue[i].window_id == window_id {
468                events.push(queue.remove(i).event);
469            } else {
470                i += 1;
471            }
472        }
473
474        events
475    }
476
477    /// Set a window's title.
478    pub fn set_window_title(&self, window_id: WindowId, title: &str) {
479        if let Some(window) = self.windows.write().get_mut(&window_id) {
480            window.set_title(title);
481        }
482    }
483
484    /// Get a clone of a window by ID.
485    pub fn get_window(&self, window_id: WindowId) -> Option<Window> {
486        self.windows.read().get(&window_id).cloned()
487    }
488
489    /// Get the currently focused window ID.
490    pub fn get_focused_window_id(&self) -> Option<WindowId> {
491        *self.focused_window.read()
492    }
493
494    /// Get all windows
495    pub fn get_all_windows(&self) -> Vec<Window> {
496        self.windows.read().values().cloned().collect()
497    }
498
499    /// Get all visible windows on the active workspace, ordered by z-order
500    /// (bottom to top -- last element is the topmost window).
501    pub fn get_visible_windows(&self) -> Vec<Window> {
502        let active = *self.active_workspace.read();
503        let windows = self.windows.read();
504        let z_order = self.z_order.read();
505        z_order
506            .iter()
507            .filter_map(|id| windows.get(id))
508            .filter(|w| w.visible && w.workspace == active && w.state != WindowState::Minimized)
509            .cloned()
510            .collect()
511    }
512
513    // -----------------------------------------------------------------------
514    // WM-1: Placement heuristics and snap/tile
515    // -----------------------------------------------------------------------
516
517    /// Set the screen dimensions (called when display is configured)
518    pub fn set_screen_size(&self, width: u32, height: u32) {
519        *self.screen_width.write() = width;
520        *self.screen_height.write() = height;
521    }
522
523    /// Set the window placement heuristic
524    pub fn set_placement_heuristic(&self, heuristic: PlacementHeuristic) {
525        *self.placement_heuristic.write() = heuristic;
526    }
527
528    /// Compute the placement position for a window based on the current
529    /// heuristic.
530    ///
531    /// The window must already be inserted into `self.windows` so its
532    /// dimensions can be read. Returns `(x, y)` for the top-left corner.
533    pub fn place_window(&self, window_id: WindowId) -> (i32, i32) {
534        let (win_w, win_h) = {
535            let windows = self.windows.read();
536            match windows.get(&window_id) {
537                Some(w) => (w.width, w.height),
538                None => return (0, 0),
539            }
540        };
541
542        let scr_w = *self.screen_width.read();
543        let scr_h = *self.screen_height.read();
544        let heuristic = *self.placement_heuristic.read();
545
546        match heuristic {
547            PlacementHeuristic::Cascade => {
548                let mut offset = self.cascade_offset.write();
549                let x = offset.0;
550                let y = offset.1;
551
552                // Advance cascade position
553                offset.0 += 32;
554                offset.1 += 32;
555
556                // Wrap around if we go off-screen
557                if offset.0 + win_w as i32 > scr_w as i32 || offset.1 + win_h as i32 > scr_h as i32
558                {
559                    offset.0 = 32;
560                    offset.1 = 32;
561                }
562
563                (x, y)
564            }
565            PlacementHeuristic::Center => {
566                let x = (scr_w as i32 - win_w as i32) / 2;
567                let y = (scr_h as i32 - win_h as i32) / 2;
568                (x.max(0), y.max(0))
569            }
570            PlacementHeuristic::Smart => self.smart_place(win_w, win_h, scr_w, scr_h),
571            PlacementHeuristic::Manual { x, y } => (x, y),
572        }
573    }
574
575    /// Smart placement: find position with minimal overlap with existing
576    /// windows
577    fn smart_place(&self, win_w: u32, win_h: u32, scr_w: u32, scr_h: u32) -> (i32, i32) {
578        let windows = self.windows.read();
579        let visible: Vec<&Window> = windows
580            .values()
581            .filter(|w| w.visible && w.state != WindowState::Minimized)
582            .collect();
583
584        if visible.is_empty() {
585            let x = (scr_w as i32 - win_w as i32) / 2;
586            let y = (scr_h as i32 - win_h as i32) / 2;
587            return (x.max(0), y.max(0));
588        }
589
590        let step = 64i32;
591        let mut best_x = 0i32;
592        let mut best_y = 0i32;
593        let mut best_overlap = i64::MAX;
594
595        let max_x = (scr_w as i32 - win_w as i32).max(0);
596        let max_y = (scr_h as i32 - win_h as i32).max(0);
597
598        let mut cy = 0i32;
599        while cy <= max_y {
600            let mut cx = 0i32;
601            while cx <= max_x {
602                let mut total_overlap: i64 = 0;
603
604                for w in &visible {
605                    let ox1 = cx.max(w.x);
606                    let oy1 = cy.max(w.y);
607                    let ox2 = (cx + win_w as i32).min(w.x + w.width as i32);
608                    let oy2 = (cy + win_h as i32).min(w.y + w.height as i32);
609
610                    if ox1 < ox2 && oy1 < oy2 {
611                        total_overlap += (ox2 - ox1) as i64 * (oy2 - oy1) as i64;
612                    }
613                }
614
615                if total_overlap < best_overlap {
616                    best_overlap = total_overlap;
617                    best_x = cx;
618                    best_y = cy;
619
620                    if total_overlap == 0 {
621                        return (best_x, best_y);
622                    }
623                }
624
625                cx += step;
626            }
627            cy += step;
628        }
629
630        (best_x, best_y)
631    }
632
633    /// Snap a window to a screen zone (half, quarter, or maximize).
634    ///
635    /// Saves the window's current geometry so it can be restored later.
636    pub fn snap_window(&self, window_id: WindowId, zone: SnapZone) {
637        let scr_w = *self.screen_width.read();
638        let scr_h = *self.screen_height.read();
639        let panel_h: u32 = 32;
640        let usable_h = scr_h.saturating_sub(panel_h);
641
642        let half_w = scr_w / 2;
643        let half_h = usable_h / 2;
644
645        let mut windows = self.windows.write();
646        let window = match windows.get_mut(&window_id) {
647            Some(w) => w,
648            None => return,
649        };
650
651        // Save geometry before snapping (only if not already snapped)
652        if window.snap_zone == SnapZone::None {
653            window.saved_geometry = Some((window.x, window.y, window.width, window.height));
654        }
655
656        match zone {
657            SnapZone::None => {
658                if let Some((sx, sy, sw, sh)) = window.saved_geometry.take() {
659                    window.x = sx;
660                    window.y = sy;
661                    window.width = sw;
662                    window.height = sh;
663                }
664                window.state = WindowState::Normal;
665            }
666            SnapZone::Left => {
667                window.x = 0;
668                window.y = 0;
669                window.width = half_w;
670                window.height = usable_h;
671            }
672            SnapZone::Right => {
673                window.x = half_w as i32;
674                window.y = 0;
675                window.width = scr_w - half_w;
676                window.height = usable_h;
677            }
678            SnapZone::Top => {
679                window.x = 0;
680                window.y = 0;
681                window.width = scr_w;
682                window.height = half_h;
683            }
684            SnapZone::Bottom => {
685                window.x = 0;
686                window.y = half_h as i32;
687                window.width = scr_w;
688                window.height = usable_h - half_h;
689            }
690            SnapZone::TopLeft => {
691                window.x = 0;
692                window.y = 0;
693                window.width = half_w;
694                window.height = half_h;
695            }
696            SnapZone::TopRight => {
697                window.x = half_w as i32;
698                window.y = 0;
699                window.width = scr_w - half_w;
700                window.height = half_h;
701            }
702            SnapZone::BottomLeft => {
703                window.x = 0;
704                window.y = half_h as i32;
705                window.width = half_w;
706                window.height = usable_h - half_h;
707            }
708            SnapZone::BottomRight => {
709                window.x = half_w as i32;
710                window.y = half_h as i32;
711                window.width = scr_w - half_w;
712                window.height = usable_h - half_h;
713            }
714            SnapZone::Maximize => {
715                window.x = 0;
716                window.y = 0;
717                window.width = scr_w;
718                window.height = usable_h;
719                window.state = WindowState::Maximized;
720            }
721        }
722
723        window.snap_zone = zone;
724    }
725
726    /// Detect which snap zone a screen coordinate falls in.
727    ///
728    /// Returns `SnapZone::None` if the position is not within the edge
729    /// threshold (8 pixels).
730    pub fn detect_snap_zone(x: i32, y: i32, screen_w: u32, screen_h: u32) -> SnapZone {
731        const EDGE_THRESHOLD: i32 = 8;
732        let sw = screen_w as i32;
733        let sh = screen_h as i32;
734
735        let near_left = x < EDGE_THRESHOLD;
736        let near_right = x >= sw - EDGE_THRESHOLD;
737        let near_top = y < EDGE_THRESHOLD;
738        let near_bottom = y >= sh - EDGE_THRESHOLD;
739
740        match (near_left, near_right, near_top, near_bottom) {
741            (true, false, true, false) => SnapZone::TopLeft,
742            (true, false, false, true) => SnapZone::BottomLeft,
743            (false, true, true, false) => SnapZone::TopRight,
744            (false, true, false, true) => SnapZone::BottomRight,
745            (true, false, false, false) => SnapZone::Left,
746            (false, true, false, false) => SnapZone::Right,
747            (false, false, true, false) => SnapZone::Top,
748            (false, false, false, true) => SnapZone::Bottom,
749            _ => SnapZone::None,
750        }
751    }
752
753    /// Tile all visible windows on the active workspace using the given layout.
754    pub fn tile_windows(&self, layout: TileLayout) {
755        let scr_w = *self.screen_width.read();
756        let scr_h = *self.screen_height.read();
757        let panel_h: u32 = 32;
758        let usable_h = scr_h.saturating_sub(panel_h);
759
760        let active_ws = *self.active_workspace.read();
761        let mut windows = self.windows.write();
762
763        let visible_ids: Vec<WindowId> = windows
764            .values()
765            .filter(|w| {
766                w.visible
767                    && w.workspace == active_ws
768                    && w.state != WindowState::Minimized
769                    && w.state != WindowState::Hidden
770            })
771            .map(|w| w.id)
772            .collect();
773
774        let count = visible_ids.len();
775        if count == 0 {
776            return;
777        }
778
779        match layout {
780            TileLayout::EqualColumns => {
781                let col_width = scr_w / count as u32;
782                for (i, &wid) in visible_ids.iter().enumerate() {
783                    if let Some(w) = windows.get_mut(&wid) {
784                        w.x = (i as u32 * col_width) as i32;
785                        w.y = 0;
786                        w.width = col_width;
787                        w.height = usable_h;
788                        w.snap_zone = SnapZone::None;
789                    }
790                }
791            }
792            TileLayout::MasterStack => {
793                if count == 1 {
794                    if let Some(w) = windows.get_mut(&visible_ids[0]) {
795                        w.x = 0;
796                        w.y = 0;
797                        w.width = scr_w;
798                        w.height = usable_h;
799                        w.snap_zone = SnapZone::None;
800                    }
801                } else {
802                    let master_w = (scr_w * 60) / 100;
803                    let stack_w = scr_w - master_w;
804                    let stack_count = (count - 1) as u32;
805                    let stack_h = usable_h / stack_count;
806
807                    if let Some(w) = windows.get_mut(&visible_ids[0]) {
808                        w.x = 0;
809                        w.y = 0;
810                        w.width = master_w;
811                        w.height = usable_h;
812                        w.snap_zone = SnapZone::None;
813                    }
814
815                    for (i, &wid) in visible_ids.iter().skip(1).enumerate() {
816                        if let Some(w) = windows.get_mut(&wid) {
817                            w.x = master_w as i32;
818                            w.y = (i as u32 * stack_h) as i32;
819                            w.width = stack_w;
820                            w.height = stack_h;
821                            w.snap_zone = SnapZone::None;
822                        }
823                    }
824                }
825            }
826            TileLayout::Grid => {
827                let cols = {
828                    let mut c = 1u32;
829                    while c * c < count as u32 {
830                        c += 1;
831                    }
832                    c
833                };
834                let rows = (count as u32).div_ceil(cols);
835                let cell_w = scr_w / cols;
836                let cell_h = usable_h / rows;
837
838                for (i, &wid) in visible_ids.iter().enumerate() {
839                    let col = (i as u32) % cols;
840                    let row = (i as u32) / cols;
841                    if let Some(w) = windows.get_mut(&wid) {
842                        w.x = (col * cell_w) as i32;
843                        w.y = (row * cell_h) as i32;
844                        w.width = cell_w;
845                        w.height = cell_h;
846                        w.snap_zone = SnapZone::None;
847                    }
848                }
849            }
850        }
851    }
852
853    /// Set window opacity (0 = transparent, 255 = opaque)
854    pub fn set_window_opacity(&self, window_id: WindowId, opacity: u8) {
855        if let Some(window) = self.windows.write().get_mut(&window_id) {
856            window.opacity = opacity;
857        }
858    }
859
860    // -----------------------------------------------------------------------
861    // WM-4: Virtual workspaces
862    // -----------------------------------------------------------------------
863
864    /// Switch to a different workspace.
865    pub fn switch_workspace(&self, workspace_id: WorkspaceId) {
866        if workspace_id as usize >= MAX_WORKSPACES {
867            return;
868        }
869
870        let current = *self.active_workspace.read();
871        if current == workspace_id {
872            return;
873        }
874
875        let mut windows = self.windows.write();
876
877        for window in windows.values_mut() {
878            if window.workspace == current {
879                window.visible = false;
880            }
881        }
882
883        for window in windows.values_mut() {
884            if window.workspace == workspace_id && window.state != WindowState::Minimized {
885                window.visible = true;
886            }
887        }
888
889        *self.active_workspace.write() = workspace_id;
890        *self.focused_window.write() = None;
891
892        crate::println!("[WM] Switched to workspace {}", workspace_id + 1);
893    }
894
895    /// Move a window to a different workspace.
896    pub fn move_window_to_workspace(&self, window_id: WindowId, workspace_id: WorkspaceId) {
897        if workspace_id as usize >= MAX_WORKSPACES {
898            return;
899        }
900
901        let active = *self.active_workspace.read();
902
903        {
904            let mut workspaces = self.workspaces.write();
905            for ws in workspaces.iter_mut() {
906                ws.windows.retain(|&id| id != window_id);
907            }
908            if let Some(ws) = workspaces.get_mut(workspace_id as usize) {
909                ws.windows.push(window_id);
910            }
911        }
912
913        let mut windows = self.windows.write();
914        if let Some(window) = windows.get_mut(&window_id) {
915            window.workspace = workspace_id;
916
917            if workspace_id != active {
918                window.visible = false;
919                if *self.focused_window.read() == Some(window_id) {
920                    *self.focused_window.write() = None;
921                }
922            } else {
923                window.visible = true;
924            }
925        }
926
927        crate::println!(
928            "[WM] Moved window {} to workspace {}",
929            window_id,
930            workspace_id + 1
931        );
932    }
933
934    /// Get the currently active workspace ID.
935    pub fn get_active_workspace(&self) -> WorkspaceId {
936        *self.active_workspace.read()
937    }
938
939    /// Get the list of window IDs on a given workspace.
940    pub fn get_workspace_windows(&self, workspace_id: WorkspaceId) -> Vec<WindowId> {
941        if workspace_id as usize >= MAX_WORKSPACES {
942            return Vec::new();
943        }
944        let workspaces = self.workspaces.read();
945        match workspaces.get(workspace_id as usize) {
946            Some(ws) => ws.windows.clone(),
947            None => Vec::new(),
948        }
949    }
950
951    /// Event loop iteration
952    pub fn event_loop_iteration(&self) {
953        // Process any pending hardware events
954        // This would integrate with keyboard/mouse drivers
955
956        // For now, this is a stub showing the structure
957    }
958}
959
960impl Default for WindowManager {
961    fn default() -> Self {
962        Self::new()
963    }
964}
965
966/// Global window manager
967static WINDOW_MANAGER: GlobalState<WindowManager> = GlobalState::new();
968
969/// Initialize window manager
970pub fn init() -> Result<(), KernelError> {
971    let wm = WindowManager::new();
972    WINDOW_MANAGER
973        .init(wm)
974        .map_err(|_| KernelError::InvalidState {
975            expected: "uninitialized",
976            actual: "initialized",
977        })?;
978
979    println!("[WM] Window manager initialized");
980    Ok(())
981}
982
983/// Execute a function with the window manager
984pub fn with_window_manager<R, F: FnOnce(&WindowManager) -> R>(f: F) -> Option<R> {
985    WINDOW_MANAGER.with(f)
986}
987
988/// Get the global window manager (deprecated - use with_window_manager instead)
989pub fn get_window_manager() -> Result<(), KernelError> {
990    WINDOW_MANAGER
991        .with(|_| ())
992        .ok_or(KernelError::InvalidState {
993            expected: "initialized",
994            actual: "uninitialized",
995        })
996}
997
998#[cfg(test)]
999mod tests {
1000    use super::*;
1001
1002    #[test]
1003    fn test_window_creation() {
1004        let wm = WindowManager::new();
1005        let id = wm.create_window(0, 0, 640, 480, 1).unwrap();
1006        assert_eq!(id, 1);
1007    }
1008
1009    #[test]
1010    fn test_window_focus() {
1011        let wm = WindowManager::new();
1012        let id1 = wm.create_window(0, 0, 640, 480, 1).unwrap();
1013        let id2 = wm.create_window(100, 100, 640, 480, 1).unwrap();
1014
1015        wm.focus_window(id1).unwrap();
1016        assert_eq!(*wm.focused_window.read(), Some(id1));
1017
1018        wm.focus_window(id2).unwrap();
1019        assert_eq!(*wm.focused_window.read(), Some(id2));
1020    }
1021
1022    #[test]
1023    fn test_window_at_position() {
1024        let wm = WindowManager::new();
1025        let id = wm.create_window(100, 100, 200, 150, 1).unwrap();
1026
1027        assert_eq!(wm.window_at_position(150, 150), Some(id));
1028        assert_eq!(wm.window_at_position(50, 50), None);
1029    }
1030
1031    #[test]
1032    fn test_snap_zone_detection() {
1033        assert_eq!(
1034            WindowManager::detect_snap_zone(2, 400, 1280, 800),
1035            SnapZone::Left
1036        );
1037        assert_eq!(
1038            WindowManager::detect_snap_zone(1275, 400, 1280, 800),
1039            SnapZone::Right
1040        );
1041        assert_eq!(
1042            WindowManager::detect_snap_zone(2, 2, 1280, 800),
1043            SnapZone::TopLeft
1044        );
1045        assert_eq!(
1046            WindowManager::detect_snap_zone(640, 400, 1280, 800),
1047            SnapZone::None
1048        );
1049    }
1050}