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

veridian_kernel/services/
desktop_ipc.rs

1//! Desktop IPC Protocol
2//!
3//! Provides IPC communication channels for desktop applications (window
4//! manager, terminal, compositor, notifications, clipboard, launcher).
5//!
6//! Each desktop subsystem registers a well-known endpoint so that user-space
7//! clients can discover and communicate with it by name rather than relying
8//! on dynamic ID allocation.
9
10#![allow(dead_code)]
11
12use alloc::vec::Vec;
13
14use crate::{error::KernelError, ipc::EndpointId, process::pcb::ProcessId};
15
16// ---------------------------------------------------------------------------
17// Well-known desktop service endpoints
18// ---------------------------------------------------------------------------
19
20/// Window manager IPC endpoint (manages window lifecycle, focus, stacking)
21pub const DESKTOP_WM_ENDPOINT: EndpointId = 1000;
22/// Input server endpoint (keyboard, mouse, touch event routing)
23pub const DESKTOP_INPUT_ENDPOINT: EndpointId = 1001;
24/// Compositor endpoint (surface compositing, damage, frame callbacks)
25pub const DESKTOP_COMPOSITOR_ENDPOINT: EndpointId = 1002;
26/// Notification daemon endpoint (desktop notifications)
27pub const DESKTOP_NOTIFICATION_ENDPOINT: EndpointId = 1003;
28/// Clipboard manager endpoint (copy/paste, primary selection)
29pub const DESKTOP_CLIPBOARD_ENDPOINT: EndpointId = 1004;
30/// Application launcher endpoint (app start, .desktop file queries)
31pub const DESKTOP_LAUNCHER_ENDPOINT: EndpointId = 1005;
32
33/// Legacy aliases for backward compatibility with Phase 6 code.
34pub const WINDOW_MANAGER_ENDPOINT: EndpointId = DESKTOP_WM_ENDPOINT;
35pub const INPUT_SERVER_ENDPOINT: EndpointId = DESKTOP_INPUT_ENDPOINT;
36pub const COMPOSITOR_ENDPOINT: EndpointId = DESKTOP_COMPOSITOR_ENDPOINT;
37
38// ---------------------------------------------------------------------------
39// Desktop IPC message types
40// ---------------------------------------------------------------------------
41
42/// Desktop IPC message types.
43///
44/// Grouped by subsystem with non-overlapping discriminant ranges so that a
45/// single `match` on the wire `u32` unambiguously identifies the message.
46#[repr(u32)]
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum DesktopMessageType {
49    // -- Window Manager messages (100-199) ----------------------------------
50    /// Request: create a new window
51    CreateWindow = 100,
52    /// Request: destroy an existing window
53    DestroyWindow = 101,
54    /// Request: move a window to (x, y)
55    MoveWindow = 102,
56    /// Request: resize a window to (w, h)
57    ResizeWindow = 103,
58    /// Request: give keyboard focus to a window
59    FocusWindow = 104,
60    /// Request: update a region of the window's framebuffer
61    UpdateWindowContent = 105,
62    /// Request: minimize a window
63    MinimizeWindow = 106,
64    /// Request: maximize/restore a window
65    MaximizeWindow = 107,
66    /// Request: set a window's title
67    SetWindowTitle = 108,
68    /// Request: query window geometry
69    GetWindowGeometry = 109,
70
71    // -- Input messages (200-299) -------------------------------------------
72    /// Event: key pressed
73    KeyPress = 200,
74    /// Event: key released
75    KeyRelease = 201,
76    /// Event: pointer motion
77    MouseMove = 202,
78    /// Event: pointer button press/release
79    MouseButton = 203,
80    /// Event: scroll wheel or touchpad scroll
81    ScrollEvent = 204,
82    /// Event: touch down
83    TouchDown = 205,
84    /// Event: touch up
85    TouchUp = 206,
86    /// Event: touch motion
87    TouchMotion = 207,
88
89    // -- Terminal messages (300-399) ----------------------------------------
90    /// Data: terminal stdin bytes
91    TerminalInput = 300,
92    /// Data: terminal stdout bytes
93    TerminalOutput = 301,
94    /// Request: resize terminal (cols, rows)
95    TerminalResize = 302,
96
97    // -- Compositor messages (400-499) --------------------------------------
98    /// Request: commit surface
99    SurfaceCommit = 400,
100    /// Request: attach buffer to surface
101    SurfaceAttach = 401,
102    /// Request: mark damage region
103    SurfaceDamage = 402,
104    /// Event: frame callback
105    FrameCallback = 403,
106
107    // -- Notification messages (500-599) ------------------------------------
108    /// Request: show a notification
109    NotificationShow = 500,
110    /// Request: dismiss a notification
111    NotificationDismiss = 501,
112    /// Event: notification was clicked
113    NotificationAction = 502,
114
115    // -- Clipboard messages (600-699) --------------------------------------
116    /// Request: set clipboard content
117    ClipboardSet = 600,
118    /// Request: get clipboard content
119    ClipboardGet = 601,
120    /// Event: clipboard content changed
121    ClipboardChanged = 602,
122
123    // -- Launcher messages (700-799) ----------------------------------------
124    /// Request: launch an application by name
125    LaunchApp = 700,
126    /// Request: list available applications
127    ListApps = 701,
128
129    // -- Response messages (900-999) ----------------------------------------
130    /// Generic success response
131    Success = 900,
132    /// Generic error response with reason code
133    Error = 901,
134}
135
136impl DesktopMessageType {
137    /// Convert from a raw `u32` wire value.
138    pub fn from_u32(v: u32) -> Option<Self> {
139        match v {
140            100 => Some(Self::CreateWindow),
141            101 => Some(Self::DestroyWindow),
142            102 => Some(Self::MoveWindow),
143            103 => Some(Self::ResizeWindow),
144            104 => Some(Self::FocusWindow),
145            105 => Some(Self::UpdateWindowContent),
146            106 => Some(Self::MinimizeWindow),
147            107 => Some(Self::MaximizeWindow),
148            108 => Some(Self::SetWindowTitle),
149            109 => Some(Self::GetWindowGeometry),
150
151            200 => Some(Self::KeyPress),
152            201 => Some(Self::KeyRelease),
153            202 => Some(Self::MouseMove),
154            203 => Some(Self::MouseButton),
155            204 => Some(Self::ScrollEvent),
156            205 => Some(Self::TouchDown),
157            206 => Some(Self::TouchUp),
158            207 => Some(Self::TouchMotion),
159
160            300 => Some(Self::TerminalInput),
161            301 => Some(Self::TerminalOutput),
162            302 => Some(Self::TerminalResize),
163
164            400 => Some(Self::SurfaceCommit),
165            401 => Some(Self::SurfaceAttach),
166            402 => Some(Self::SurfaceDamage),
167            403 => Some(Self::FrameCallback),
168
169            500 => Some(Self::NotificationShow),
170            501 => Some(Self::NotificationDismiss),
171            502 => Some(Self::NotificationAction),
172
173            600 => Some(Self::ClipboardSet),
174            601 => Some(Self::ClipboardGet),
175            602 => Some(Self::ClipboardChanged),
176
177            700 => Some(Self::LaunchApp),
178            701 => Some(Self::ListApps),
179
180            900 => Some(Self::Success),
181            901 => Some(Self::Error),
182            _ => None,
183        }
184    }
185}
186
187// ---------------------------------------------------------------------------
188// Wire-format message structs
189// ---------------------------------------------------------------------------
190
191/// Window creation request
192#[repr(C)]
193#[derive(Debug, Clone, Copy)]
194pub struct CreateWindowRequest {
195    pub x: i32,
196    pub y: i32,
197    pub width: u32,
198    pub height: u32,
199    pub title_len: u32,
200    // Title follows as bytes
201}
202
203/// Window creation response
204#[repr(C)]
205#[derive(Debug, Clone, Copy)]
206pub struct CreateWindowResponse {
207    pub window_id: u64,
208}
209
210/// Window update request
211#[repr(C)]
212#[derive(Debug, Clone, Copy)]
213pub struct UpdateWindowRequest {
214    pub window_id: u64,
215    pub x: u32,
216    pub y: u32,
217    pub width: u32,
218    pub height: u32,
219    pub data_len: u32,
220    // Framebuffer data follows
221}
222
223/// Window geometry query/response
224#[repr(C)]
225#[derive(Debug, Clone, Copy)]
226pub struct WindowGeometryRequest {
227    pub window_id: u64,
228}
229
230/// Keyboard event
231#[repr(C)]
232#[derive(Debug, Clone, Copy)]
233pub struct KeyEvent {
234    pub key_code: u32,
235    pub modifiers: u32,
236    pub pressed: bool,
237}
238
239/// Mouse event
240#[repr(C)]
241#[derive(Debug, Clone, Copy)]
242pub struct MouseEvent {
243    pub x: i32,
244    pub y: i32,
245    pub button: u8,
246    pub pressed: bool,
247}
248
249/// Scroll event
250#[repr(C)]
251#[derive(Debug, Clone, Copy)]
252pub struct ScrollEvent {
253    pub x: i32,
254    pub y: i32,
255    pub dx: i32,
256    pub dy: i32,
257}
258
259/// Desktop notification request
260#[repr(C)]
261#[derive(Debug, Clone, Copy)]
262pub struct NotificationRequest {
263    pub urgency: u8,
264    pub timeout_ms: u32,
265    pub title_len: u32,
266    pub body_len: u32,
267    // title bytes followed by body bytes
268}
269
270/// Clipboard header
271#[repr(C)]
272#[derive(Debug, Clone, Copy)]
273pub struct ClipboardHeader {
274    pub mime_type_len: u32,
275    pub data_len: u32,
276    // mime_type bytes followed by data bytes
277}
278
279// ---------------------------------------------------------------------------
280// Desktop IPC helper functions
281// ---------------------------------------------------------------------------
282
283/// Desktop IPC helper functions for building and parsing wire messages.
284pub mod helpers {
285    use super::*;
286
287    /// Create a window creation message
288    pub fn create_window_message(x: i32, y: i32, width: u32, height: u32, title: &str) -> Vec<u8> {
289        let mut data = Vec::new();
290
291        // Message type
292        data.extend_from_slice(&(DesktopMessageType::CreateWindow as u32).to_le_bytes());
293
294        // Request struct
295        let req = CreateWindowRequest {
296            x,
297            y,
298            width,
299            height,
300            title_len: title.len() as u32,
301        };
302
303        // SAFETY: CreateWindowRequest is #[repr(C)] so its memory layout
304        // is well-defined. We create a byte slice view over the struct
305        // for size_of::<CreateWindowRequest>() bytes. The reference &req
306        // is valid for the entire scope, so the slice is valid.
307        unsafe {
308            let req_bytes = core::slice::from_raw_parts(
309                &req as *const _ as *const u8,
310                core::mem::size_of::<CreateWindowRequest>(),
311            );
312            data.extend_from_slice(req_bytes);
313        }
314
315        // Title bytes
316        data.extend_from_slice(title.as_bytes());
317
318        data
319    }
320
321    /// Parse window creation response
322    pub fn parse_window_response(data: &[u8]) -> Result<u64, KernelError> {
323        if data.len() < core::mem::size_of::<CreateWindowResponse>() {
324            return Err(KernelError::InvalidArgument {
325                name: "response_size",
326                value: "too_small",
327            });
328        }
329
330        // SAFETY: data.len() >= size_of::<CreateWindowResponse>() was
331        // validated above. read_unaligned handles any alignment issues.
332        // CreateWindowResponse is #[repr(C)] with Copy, so reading it
333        // from the byte buffer is valid.
334        unsafe {
335            let resp = core::ptr::read_unaligned(data.as_ptr() as *const CreateWindowResponse);
336            Ok(resp.window_id)
337        }
338    }
339
340    /// Create a keyboard event message
341    pub fn keyboard_event_message(key_code: u32, modifiers: u32, pressed: bool) -> Vec<u8> {
342        let mut data = Vec::new();
343
344        // Message type
345        let msg_type = if pressed {
346            DesktopMessageType::KeyPress
347        } else {
348            DesktopMessageType::KeyRelease
349        };
350        data.extend_from_slice(&(msg_type as u32).to_le_bytes());
351
352        // Event struct
353        let event = KeyEvent {
354            key_code,
355            modifiers,
356            pressed,
357        };
358
359        // SAFETY: KeyEvent is #[repr(C)] so its memory layout is
360        // well-defined. We create a byte slice view over the struct
361        // for size_of::<KeyEvent>() bytes. The reference is valid.
362        unsafe {
363            let event_bytes = core::slice::from_raw_parts(
364                &event as *const _ as *const u8,
365                core::mem::size_of::<KeyEvent>(),
366            );
367            data.extend_from_slice(event_bytes);
368        }
369
370        data
371    }
372
373    /// Create a mouse event message
374    pub fn mouse_event_message(x: i32, y: i32, button: u8, pressed: bool) -> Vec<u8> {
375        let mut data = Vec::new();
376
377        // Message type
378        data.extend_from_slice(&(DesktopMessageType::MouseMove as u32).to_le_bytes());
379
380        // Event struct
381        let event = MouseEvent {
382            x,
383            y,
384            button,
385            pressed,
386        };
387
388        // SAFETY: MouseEvent is #[repr(C)] so its memory layout is
389        // well-defined. We create a byte slice view over the struct
390        // for size_of::<MouseEvent>() bytes. The reference is valid.
391        unsafe {
392            let event_bytes = core::slice::from_raw_parts(
393                &event as *const _ as *const u8,
394                core::mem::size_of::<MouseEvent>(),
395            );
396            data.extend_from_slice(event_bytes);
397        }
398
399        data
400    }
401}
402
403// ---------------------------------------------------------------------------
404// Desktop IPC Server
405// ---------------------------------------------------------------------------
406
407/// Desktop IPC server that manages endpoint registration and message routing.
408pub struct DesktopIpcServer {
409    /// Whether all endpoints have been registered
410    endpoints_registered: bool,
411    /// Number of endpoints successfully registered
412    registered_count: u32,
413}
414
415impl DesktopIpcServer {
416    /// Create a new server instance (endpoints not yet registered).
417    pub fn new() -> Self {
418        Self {
419            endpoints_registered: false,
420            registered_count: 0,
421        }
422    }
423
424    /// Register all well-known desktop endpoints with the IPC subsystem.
425    ///
426    /// Each endpoint is created with the kernel (PID 0) as owner. Errors
427    /// during individual endpoint creation are logged but do not prevent
428    /// the remaining endpoints from being registered.
429    pub fn register_endpoints(&mut self) -> Result<(), KernelError> {
430        let kernel_pid = ProcessId(0);
431        let endpoints: &[(EndpointId, &str)] = &[
432            (DESKTOP_WM_ENDPOINT, "window_manager"),
433            (DESKTOP_INPUT_ENDPOINT, "input_server"),
434            (DESKTOP_COMPOSITOR_ENDPOINT, "compositor"),
435            (DESKTOP_NOTIFICATION_ENDPOINT, "notifications"),
436            (DESKTOP_CLIPBOARD_ENDPOINT, "clipboard"),
437            (DESKTOP_LAUNCHER_ENDPOINT, "launcher"),
438        ];
439
440        for &(id, name) in endpoints {
441            match crate::ipc::create_endpoint(kernel_pid) {
442                Ok((_endpoint_id, _cap)) => {
443                    self.registered_count += 1;
444                    crate::println!("[DESKTOP-IPC] Registered endpoint {} (ID {})", name, id);
445                }
446                Err(e) => {
447                    crate::println!(
448                        "[DESKTOP-IPC] Warning: failed to register {} (ID {}): {:?}",
449                        name,
450                        id,
451                        e
452                    );
453                }
454            }
455        }
456
457        self.endpoints_registered = true;
458        Ok(())
459    }
460
461    /// Whether all endpoints have been registered.
462    pub fn is_registered(&self) -> bool {
463        self.endpoints_registered
464    }
465
466    /// Number of successfully registered endpoints.
467    pub fn registered_count(&self) -> u32 {
468        self.registered_count
469    }
470}
471
472impl Default for DesktopIpcServer {
473    fn default() -> Self {
474        Self::new()
475    }
476}
477
478// ---------------------------------------------------------------------------
479// Global state
480// ---------------------------------------------------------------------------
481
482/// Global desktop IPC server instance.
483static DESKTOP_IPC: spin::Mutex<Option<DesktopIpcServer>> = spin::Mutex::new(None);
484
485/// Check whether the desktop IPC system has been initialized.
486pub fn is_initialized() -> bool {
487    DESKTOP_IPC
488        .lock()
489        .as_ref()
490        .is_some_and(|s| s.is_registered())
491}
492
493/// Initialize the desktop IPC system.
494///
495/// Creates a `DesktopIpcServer` and registers all well-known endpoints.
496pub fn init() -> Result<(), KernelError> {
497    crate::println!("[DESKTOP-IPC] Initializing desktop IPC protocol...");
498
499    let mut server = DesktopIpcServer::new();
500    server.register_endpoints()?;
501
502    let count = server.registered_count();
503    *DESKTOP_IPC.lock() = Some(server);
504
505    crate::println!(
506        "[DESKTOP-IPC] Desktop IPC protocol initialized ({} endpoints)",
507        count
508    );
509    Ok(())
510}
511
512// ---------------------------------------------------------------------------
513// Tests
514// ---------------------------------------------------------------------------
515
516#[cfg(test)]
517mod tests {
518    use super::{helpers::*, *};
519
520    #[test]
521    fn test_create_window_message() {
522        let msg = create_window_message(100, 200, 800, 600, "Test Window");
523
524        // Should contain message type + struct + title
525        assert!(msg.len() > core::mem::size_of::<CreateWindowRequest>());
526    }
527
528    #[test]
529    fn test_keyboard_event() {
530        let msg = keyboard_event_message(65, 0, true); // 'A' key pressed
531
532        // Should contain message type + event struct
533        assert_eq!(msg.len(), 4 + core::mem::size_of::<KeyEvent>());
534    }
535
536    #[test]
537    fn test_message_type_round_trip() {
538        let cases: &[DesktopMessageType] = &[
539            DesktopMessageType::CreateWindow,
540            DesktopMessageType::KeyPress,
541            DesktopMessageType::TerminalInput,
542            DesktopMessageType::SurfaceCommit,
543            DesktopMessageType::NotificationShow,
544            DesktopMessageType::ClipboardSet,
545            DesktopMessageType::LaunchApp,
546            DesktopMessageType::Success,
547            DesktopMessageType::Error,
548        ];
549        for &mt in cases {
550            let v = mt as u32;
551            assert_eq!(DesktopMessageType::from_u32(v), Some(mt));
552        }
553    }
554
555    #[test]
556    fn test_message_type_unknown() {
557        assert_eq!(DesktopMessageType::from_u32(0), None);
558        assert_eq!(DesktopMessageType::from_u32(9999), None);
559    }
560
561    #[test]
562    fn test_desktop_ipc_server_new() {
563        let server = DesktopIpcServer::new();
564        assert!(!server.is_registered());
565        assert_eq!(server.registered_count(), 0);
566    }
567
568    #[test]
569    fn test_endpoint_constants() {
570        // Ensure legacy aliases match the new names
571        assert_eq!(WINDOW_MANAGER_ENDPOINT, DESKTOP_WM_ENDPOINT);
572        assert_eq!(INPUT_SERVER_ENDPOINT, DESKTOP_INPUT_ENDPOINT);
573        assert_eq!(COMPOSITOR_ENDPOINT, DESKTOP_COMPOSITOR_ENDPOINT);
574
575        // Ensure all endpoint IDs are unique
576        let ids = [
577            DESKTOP_WM_ENDPOINT,
578            DESKTOP_INPUT_ENDPOINT,
579            DESKTOP_COMPOSITOR_ENDPOINT,
580            DESKTOP_NOTIFICATION_ENDPOINT,
581            DESKTOP_CLIPBOARD_ENDPOINT,
582            DESKTOP_LAUNCHER_ENDPOINT,
583        ];
584        for i in 0..ids.len() {
585            for j in (i + 1)..ids.len() {
586                assert_ne!(ids[i], ids[j]);
587            }
588        }
589    }
590}