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

veridian_kernel/desktop/
xwayland.rs

1//! XWayland Compatibility Layer
2//!
3//! Provides the socket infrastructure and window mapping for running X11
4//! applications via XWayland on VeridianOS. Full X11 server implementation
5//! is deferred to Phase 8; this module establishes the correct architecture
6//! and provides the bridge between X11 window IDs and Wayland surface IDs.
7//!
8//! ## Architecture
9//!
10//! XWayland runs as a child process of the compositor, speaking the X11
11//! protocol over a Unix socket pair. It creates Wayland surfaces for each
12//! X11 window and forwards input events.
13//!
14//! ```text
15//! X11 Client -> XWayland Process -> Wayland Compositor
16//!                   |                     |
17//!              X11 protocol          Wayland protocol
18//!              (/tmp/.X11-unix/X0)   (kernel syscalls)
19//! ```
20//!
21//! ## Current Status
22//!
23//! This is a stub implementation that:
24//! - Creates the X11 socket directory structure
25//! - Manages X11-to-Wayland window ID mappings
26//! - Provides the XWayland server lifecycle (start/stop)
27//! - Logs operations for debugging
28//!
29//! Actual X11 protocol handling requires Phase 8.
30
31#![allow(dead_code)]
32
33use alloc::{format, string::String, vec::Vec};
34
35use crate::error::KernelError;
36
37// ---------------------------------------------------------------------------
38// Constants
39// ---------------------------------------------------------------------------
40
41/// Default X11 display number
42pub const DEFAULT_DISPLAY: u32 = 0;
43
44/// Maximum display number we will try
45pub const MAX_DISPLAY: u32 = 32;
46
47/// X11 socket directory
48pub const X11_SOCKET_DIR: &str = "/tmp/.X11-unix";
49
50/// X11 lock file directory
51pub const X11_LOCK_DIR: &str = "/tmp";
52
53/// Maximum number of X11 windows we track
54const MAX_WINDOWS: usize = 256;
55
56// ---------------------------------------------------------------------------
57// XWayland server state
58// ---------------------------------------------------------------------------
59
60/// State of the XWayland server process.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum XWaylandState {
63    /// Server has not been started
64    NotStarted,
65    /// Server is in the process of starting (socket created, waiting for ready)
66    Starting,
67    /// Server is running and accepting X11 connections
68    Running,
69    /// Server failed to start
70    Failed,
71    /// Server has been stopped
72    Stopped,
73}
74
75// ---------------------------------------------------------------------------
76// X11 window mapping
77// ---------------------------------------------------------------------------
78
79/// Maps an X11 window to a Wayland compositor surface.
80///
81/// When XWayland creates a window, it also creates a corresponding Wayland
82/// surface. This mapping lets the compositor route events and manage the
83/// window's lifecycle across both protocols.
84#[derive(Debug, Clone)]
85pub struct X11WindowMapping {
86    /// X11 window ID (XID)
87    pub x11_window_id: u32,
88    /// Corresponding Wayland surface ID in the compositor
89    pub wayland_surface_id: u32,
90    /// Whether this is an override-redirect window (popup/tooltip)
91    pub override_redirect: bool,
92    /// Window position in X11 coordinate space
93    pub x: i32,
94    pub y: i32,
95    /// Window dimensions
96    pub width: u32,
97    pub height: u32,
98    /// Whether the window is currently mapped (visible)
99    pub mapped: bool,
100    /// Window title (from _NET_WM_NAME or WM_NAME property)
101    pub title: String,
102    /// Window class (from WM_CLASS property)
103    pub window_class: String,
104    /// Whether this window wants input focus
105    pub accepts_focus: bool,
106}
107
108impl X11WindowMapping {
109    /// Create a new window mapping.
110    fn new(x11_id: u32, wayland_id: u32) -> Self {
111        Self {
112            x11_window_id: x11_id,
113            wayland_surface_id: wayland_id,
114            override_redirect: false,
115            x: 0,
116            y: 0,
117            width: 0,
118            height: 0,
119            mapped: false,
120            title: String::new(),
121            window_class: String::new(),
122            accepts_focus: true,
123        }
124    }
125}
126
127// ---------------------------------------------------------------------------
128// X11 event types (stub)
129// ---------------------------------------------------------------------------
130
131/// Simplified X11 event for the compatibility layer.
132///
133/// These are the events that XWayland would forward from X11 clients
134/// to the Wayland compositor.
135#[derive(Debug, Clone)]
136pub enum X11Event {
137    /// Window creation (CreateNotify equivalent)
138    WindowCreated {
139        window_id: u32,
140        x: i32,
141        y: i32,
142        width: u32,
143        height: u32,
144        override_redirect: bool,
145    },
146    /// Window destruction (DestroyNotify equivalent)
147    WindowDestroyed { window_id: u32 },
148    /// Window mapped (visible)
149    WindowMapped { window_id: u32 },
150    /// Window unmapped (hidden)
151    WindowUnmapped { window_id: u32 },
152    /// Window reconfigured (position/size change)
153    WindowConfigured {
154        window_id: u32,
155        x: i32,
156        y: i32,
157        width: u32,
158        height: u32,
159    },
160    /// Window title changed
161    TitleChanged { window_id: u32, title: String },
162    /// Focus request
163    FocusRequest { window_id: u32 },
164}
165
166// ---------------------------------------------------------------------------
167// XWayland server
168// ---------------------------------------------------------------------------
169
170/// XWayland compatibility server.
171///
172/// Manages the lifecycle of the XWayland process and maintains the mapping
173/// between X11 window IDs and Wayland surface IDs.
174pub struct XWaylandServer {
175    /// Current server state
176    state: XWaylandState,
177    /// X11 display number (typically 0)
178    display_number: u32,
179    /// Path to the X11 socket (e.g., /tmp/.X11-unix/X0)
180    socket_path: String,
181    /// Path to the X11 lock file (e.g., /tmp/.X0-lock)
182    lock_path: String,
183    /// Active window mappings
184    window_mappings: Vec<X11WindowMapping>,
185    /// Next X11 window ID to assign (for stub purposes)
186    next_window_id: u32,
187    /// Window manager socket file descriptor (for WM <-> XWayland comms)
188    wm_fd: Option<i32>,
189    /// PID of the XWayland process (0 = not running)
190    xwayland_pid: u64,
191}
192
193impl XWaylandServer {
194    /// Create a new XWayland server (not yet started).
195    pub fn new() -> Self {
196        Self::with_display(DEFAULT_DISPLAY)
197    }
198
199    /// Create a new XWayland server with a specific display number.
200    pub fn with_display(display: u32) -> Self {
201        Self {
202            state: XWaylandState::NotStarted,
203            display_number: display,
204            socket_path: format!("{}/X{}", X11_SOCKET_DIR, display),
205            lock_path: format!("{}/.X{}-lock", X11_LOCK_DIR, display),
206            window_mappings: Vec::new(),
207            next_window_id: 1,
208            wm_fd: None,
209            xwayland_pid: 0,
210        }
211    }
212
213    /// Start the XWayland server.
214    ///
215    /// In this stub implementation, this creates the socket directory
216    /// structure and transitions to the Running state. Actual XWayland
217    /// process spawning is deferred to Phase 8.
218    pub fn start(&mut self) -> Result<(), KernelError> {
219        if self.state == XWaylandState::Running {
220            return Err(KernelError::InvalidState {
221                expected: "not running",
222                actual: "already running",
223            });
224        }
225
226        self.state = XWaylandState::Starting;
227
228        // Create socket directory (in real implementation, this would use
229        // the VFS to create /tmp/.X11-unix/)
230        crate::println!(
231            "[XWAYLAND] Stub: creating X11 socket directory at {}",
232            X11_SOCKET_DIR
233        );
234        crate::println!(
235            "[XWAYLAND] Stub: display :{} socket at {}",
236            self.display_number,
237            self.socket_path
238        );
239        crate::println!("[XWAYLAND] Stub: lock file at {}", self.lock_path);
240
241        // In Phase 8, this would:
242        // 1. Create a socketpair for WM communication
243        // 2. Fork and exec the XWayland binary
244        // 3. Wait for the SIGUSR1 ready signal
245        // 4. Set up the X11 window manager (reparenting, decorations)
246
247        self.state = XWaylandState::Running;
248        crate::println!(
249            "[XWAYLAND] Stub server running on display :{}",
250            self.display_number
251        );
252        Ok(())
253    }
254
255    /// Stop the XWayland server.
256    pub fn stop(&mut self) -> Result<(), KernelError> {
257        if self.state != XWaylandState::Running && self.state != XWaylandState::Starting {
258            return Err(KernelError::InvalidState {
259                expected: "running or starting",
260                actual: "not running",
261            });
262        }
263
264        // In Phase 8, this would:
265        // 1. Send SIGTERM to the XWayland process
266        // 2. Wait for exit
267        // 3. Clean up sockets and lock file
268
269        // Destroy all window mappings
270        self.window_mappings.clear();
271
272        self.wm_fd = None;
273        self.xwayland_pid = 0;
274        self.state = XWaylandState::Stopped;
275
276        crate::println!(
277            "[XWAYLAND] Server stopped on display :{}",
278            self.display_number
279        );
280        Ok(())
281    }
282
283    /// Get the current server state.
284    pub fn state(&self) -> XWaylandState {
285        self.state
286    }
287
288    /// Get the display number.
289    pub fn display_number(&self) -> u32 {
290        self.display_number
291    }
292
293    /// Get the socket path.
294    pub fn socket_path(&self) -> &str {
295        &self.socket_path
296    }
297
298    /// Check if the server is running.
299    pub fn is_running(&self) -> bool {
300        self.state == XWaylandState::Running
301    }
302
303    /// Create a new X11-to-Wayland window mapping.
304    ///
305    /// Called when XWayland creates a new X11 window and its corresponding
306    /// Wayland surface.
307    pub fn create_window_mapping(&mut self, wayland_surface_id: u32) -> Result<u32, KernelError> {
308        if self.window_mappings.len() >= MAX_WINDOWS {
309            return Err(KernelError::ResourceExhausted {
310                resource: "x11_windows",
311            });
312        }
313
314        let x11_id = self.next_window_id;
315        self.next_window_id += 1;
316
317        let mapping = X11WindowMapping::new(x11_id, wayland_surface_id);
318        self.window_mappings.push(mapping);
319
320        Ok(x11_id)
321    }
322
323    /// Destroy a window mapping by X11 window ID.
324    pub fn destroy_window_mapping(&mut self, x11_window_id: u32) -> Result<(), KernelError> {
325        let idx = self
326            .window_mappings
327            .iter()
328            .position(|m| m.x11_window_id == x11_window_id)
329            .ok_or(KernelError::NotFound {
330                resource: "x11_window",
331                id: x11_window_id as u64,
332            })?;
333
334        self.window_mappings.remove(idx);
335        Ok(())
336    }
337
338    /// Get the Wayland surface ID for an X11 window.
339    pub fn get_surface_for_x11_window(&self, x11_window_id: u32) -> Option<u32> {
340        self.window_mappings
341            .iter()
342            .find(|m| m.x11_window_id == x11_window_id)
343            .map(|m| m.wayland_surface_id)
344    }
345
346    /// Get the X11 window ID for a Wayland surface.
347    pub fn get_x11_window_for_surface(&self, wayland_surface_id: u32) -> Option<u32> {
348        self.window_mappings
349            .iter()
350            .find(|m| m.wayland_surface_id == wayland_surface_id)
351            .map(|m| m.x11_window_id)
352    }
353
354    /// Get a reference to a window mapping.
355    pub fn get_window_mapping(&self, x11_window_id: u32) -> Option<&X11WindowMapping> {
356        self.window_mappings
357            .iter()
358            .find(|m| m.x11_window_id == x11_window_id)
359    }
360
361    /// Get a mutable reference to a window mapping.
362    pub fn get_window_mapping_mut(&mut self, x11_window_id: u32) -> Option<&mut X11WindowMapping> {
363        self.window_mappings
364            .iter_mut()
365            .find(|m| m.x11_window_id == x11_window_id)
366    }
367
368    /// Get all active window mappings.
369    pub fn get_all_mappings(&self) -> &[X11WindowMapping] {
370        &self.window_mappings
371    }
372
373    /// Get the number of active window mappings.
374    pub fn window_count(&self) -> usize {
375        self.window_mappings.len()
376    }
377
378    /// Handle an X11 event from the XWayland process.
379    ///
380    /// In the stub implementation, this updates internal mappings.
381    /// In Phase 8, this would translate X11 events to Wayland compositor
382    /// actions.
383    pub fn handle_x11_event(&mut self, event: &X11Event) -> Result<(), KernelError> {
384        match event {
385            X11Event::WindowCreated {
386                window_id,
387                x,
388                y,
389                width,
390                height,
391                override_redirect,
392            } => {
393                if let Some(mapping) = self.get_window_mapping_mut(*window_id) {
394                    mapping.x = *x;
395                    mapping.y = *y;
396                    mapping.width = *width;
397                    mapping.height = *height;
398                    mapping.override_redirect = *override_redirect;
399                }
400                Ok(())
401            }
402
403            X11Event::WindowDestroyed { window_id } => {
404                let _ = self.destroy_window_mapping(*window_id);
405                Ok(())
406            }
407
408            X11Event::WindowMapped { window_id } => {
409                if let Some(mapping) = self.get_window_mapping_mut(*window_id) {
410                    mapping.mapped = true;
411                }
412                Ok(())
413            }
414
415            X11Event::WindowUnmapped { window_id } => {
416                if let Some(mapping) = self.get_window_mapping_mut(*window_id) {
417                    mapping.mapped = false;
418                }
419                Ok(())
420            }
421
422            X11Event::WindowConfigured {
423                window_id,
424                x,
425                y,
426                width,
427                height,
428            } => {
429                if let Some(mapping) = self.get_window_mapping_mut(*window_id) {
430                    mapping.x = *x;
431                    mapping.y = *y;
432                    mapping.width = *width;
433                    mapping.height = *height;
434                }
435                Ok(())
436            }
437
438            X11Event::TitleChanged { window_id, title } => {
439                if let Some(mapping) = self.get_window_mapping_mut(*window_id) {
440                    mapping.title = title.clone();
441                }
442                Ok(())
443            }
444
445            X11Event::FocusRequest { window_id: _ } => {
446                // In Phase 8, this would request focus through the Wayland
447                // compositor's window manager
448                Ok(())
449            }
450        }
451    }
452
453    /// Find a free display number by checking for existing lock files.
454    ///
455    /// Returns the first available display number, or an error if all
456    /// display numbers up to MAX_DISPLAY are taken.
457    pub fn find_free_display() -> Result<u32, KernelError> {
458        // In the stub, we always return 0 since there is no actual
459        // lock file checking.
460        Ok(DEFAULT_DISPLAY)
461    }
462
463    /// Get the DISPLAY environment variable string for this server.
464    ///
465    /// Returns a string like ":0" that X11 clients use to connect.
466    pub fn display_string(&self) -> String {
467        format!(":{}", self.display_number)
468    }
469}
470
471impl Default for XWaylandServer {
472    fn default() -> Self {
473        Self::new()
474    }
475}
476
477// ---------------------------------------------------------------------------
478// Tests
479// ---------------------------------------------------------------------------
480
481#[cfg(test)]
482mod tests {
483    use super::*;
484
485    #[test]
486    fn test_xwayland_lifecycle() {
487        let mut server = XWaylandServer::new();
488        assert_eq!(server.state(), XWaylandState::NotStarted);
489        assert!(!server.is_running());
490
491        server.start().unwrap();
492        assert_eq!(server.state(), XWaylandState::Running);
493        assert!(server.is_running());
494
495        server.stop().unwrap();
496        assert_eq!(server.state(), XWaylandState::Stopped);
497        assert!(!server.is_running());
498    }
499
500    #[test]
501    fn test_window_mapping() {
502        let mut server = XWaylandServer::new();
503        server.start().unwrap();
504
505        let x11_id = server.create_window_mapping(2001).unwrap();
506        assert_eq!(server.window_count(), 1);
507        assert_eq!(server.get_surface_for_x11_window(x11_id), Some(2001));
508        assert_eq!(server.get_x11_window_for_surface(2001), Some(x11_id));
509
510        server.destroy_window_mapping(x11_id).unwrap();
511        assert_eq!(server.window_count(), 0);
512    }
513
514    #[test]
515    fn test_x11_events() {
516        let mut server = XWaylandServer::new();
517        server.start().unwrap();
518
519        let x11_id = server.create_window_mapping(2002).unwrap();
520
521        server
522            .handle_x11_event(&X11Event::WindowCreated {
523                window_id: x11_id,
524                x: 100,
525                y: 200,
526                width: 640,
527                height: 480,
528                override_redirect: false,
529            })
530            .unwrap();
531
532        let mapping = server.get_window_mapping(x11_id).unwrap();
533        assert_eq!(mapping.x, 100);
534        assert_eq!(mapping.y, 200);
535        assert_eq!(mapping.width, 640);
536        assert_eq!(mapping.height, 480);
537        assert!(!mapping.override_redirect);
538
539        server
540            .handle_x11_event(&X11Event::WindowMapped { window_id: x11_id })
541            .unwrap();
542        assert!(server.get_window_mapping(x11_id).unwrap().mapped);
543
544        server
545            .handle_x11_event(&X11Event::TitleChanged {
546                window_id: x11_id,
547                title: String::from("Test Window"),
548            })
549            .unwrap();
550        assert_eq!(
551            server.get_window_mapping(x11_id).unwrap().title,
552            "Test Window"
553        );
554    }
555
556    #[test]
557    fn test_display_string() {
558        let server = XWaylandServer::with_display(2);
559        assert_eq!(server.display_string(), ":2");
560        assert_eq!(server.socket_path(), "/tmp/.X11-unix/X2");
561    }
562}