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

veridian_kernel/desktop/wayland/
shell.rs

1//! XDG Shell Protocol
2//!
3//! Implements xdg_wm_base, xdg_surface, and xdg_toplevel -- the standard
4//! desktop shell interface for managing windows, popups, and positioners.
5//!
6//! Many constants, struct fields, and methods define the complete Wayland XDG
7//! shell protocol surface area. Items that are not yet wired into the
8//! compositor dispatch path are retained for protocol completeness and will be
9//! connected as more of the XDG shell protocol is implemented.
10#![allow(dead_code)]
11
12use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
13
14use spin::RwLock;
15
16use super::protocol::{self, Argument, WaylandMessage};
17use crate::error::KernelError;
18
19// ---------------------------------------------------------------------------
20// XDG constants
21// ---------------------------------------------------------------------------
22
23// xdg_wm_base opcodes (requests)
24/// destroy
25pub const XDG_WM_BASE_DESTROY: u16 = 0;
26/// create_positioner
27pub const XDG_WM_BASE_CREATE_POSITIONER: u16 = 1;
28/// get_xdg_surface
29pub const XDG_WM_BASE_GET_XDG_SURFACE: u16 = 2;
30/// pong
31pub const XDG_WM_BASE_PONG: u16 = 3;
32
33// xdg_wm_base event opcodes (server -> client)
34/// ping
35pub const XDG_WM_BASE_PING: u16 = 0;
36
37// xdg_surface opcodes (requests)
38/// destroy
39pub const XDG_SURFACE_DESTROY: u16 = 0;
40/// get_toplevel
41pub const XDG_SURFACE_GET_TOPLEVEL: u16 = 1;
42/// get_popup
43pub const XDG_SURFACE_GET_POPUP: u16 = 2;
44/// set_window_geometry
45pub const XDG_SURFACE_SET_WINDOW_GEOMETRY: u16 = 3;
46/// ack_configure
47pub const XDG_SURFACE_ACK_CONFIGURE: u16 = 4;
48
49// xdg_surface event opcodes
50/// configure
51pub const XDG_SURFACE_CONFIGURE: u16 = 0;
52
53// xdg_toplevel opcodes (requests)
54/// destroy
55pub const XDG_TOPLEVEL_DESTROY: u16 = 0;
56/// set_parent
57pub const XDG_TOPLEVEL_SET_PARENT: u16 = 1;
58/// set_title
59pub const XDG_TOPLEVEL_SET_TITLE: u16 = 2;
60/// set_app_id
61pub const XDG_TOPLEVEL_SET_APP_ID: u16 = 3;
62/// move (interactive)
63pub const XDG_TOPLEVEL_MOVE: u16 = 5;
64/// resize (interactive)
65pub const XDG_TOPLEVEL_RESIZE: u16 = 6;
66/// set_max_size
67pub const XDG_TOPLEVEL_SET_MAX_SIZE: u16 = 7;
68/// set_min_size
69pub const XDG_TOPLEVEL_SET_MIN_SIZE: u16 = 8;
70/// set_maximized
71pub const XDG_TOPLEVEL_SET_MAXIMIZED: u16 = 9;
72/// unset_maximized
73pub const XDG_TOPLEVEL_UNSET_MAXIMIZED: u16 = 10;
74/// set_fullscreen
75pub const XDG_TOPLEVEL_SET_FULLSCREEN: u16 = 11;
76/// unset_fullscreen
77pub const XDG_TOPLEVEL_UNSET_FULLSCREEN: u16 = 12;
78/// set_minimized
79pub const XDG_TOPLEVEL_SET_MINIMIZED: u16 = 13;
80
81// xdg_toplevel event opcodes
82/// configure
83pub const XDG_TOPLEVEL_CONFIGURE: u16 = 0;
84/// close
85pub const XDG_TOPLEVEL_CLOSE: u16 = 1;
86
87// xdg_toplevel state enum values (used in configure event's states array)
88/// maximized state
89pub const XDG_TOPLEVEL_STATE_MAXIMIZED: u32 = 1;
90/// fullscreen state
91pub const XDG_TOPLEVEL_STATE_FULLSCREEN: u32 = 2;
92/// resizing state
93pub const XDG_TOPLEVEL_STATE_RESIZING: u32 = 3;
94/// activated (focused) state
95pub const XDG_TOPLEVEL_STATE_ACTIVATED: u32 = 4;
96
97// ---------------------------------------------------------------------------
98// XDG Decoration constants (zxdg_decoration_manager_v1)
99// ---------------------------------------------------------------------------
100
101/// Wayland global interface name for decoration manager
102pub const ZXDG_DECORATION_MANAGER_V1: &str = "zxdg_decoration_manager_v1";
103
104/// Protocol version
105pub const ZXDG_DECORATION_MANAGER_V1_VERSION: u32 = 1;
106
107// Manager request opcodes
108/// destroy
109pub const ZXDG_DECORATION_MANAGER_V1_DESTROY: u16 = 0;
110/// get_toplevel_decoration(id: new_id, toplevel: object)
111pub const ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION: u16 = 1;
112
113// Toplevel decoration request opcodes
114/// destroy
115pub const ZXDG_TOPLEVEL_DECORATION_V1_DESTROY: u16 = 0;
116/// set_mode(mode: uint)
117pub const ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE: u16 = 1;
118/// unset_mode
119pub const ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE: u16 = 2;
120
121// Toplevel decoration event opcodes
122/// configure(mode: uint)
123pub const ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE: u16 = 0;
124
125// Decoration mode constants
126/// Decorations are drawn by the client
127pub const ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: u32 = 1;
128/// Decorations are drawn by the compositor (server)
129pub const ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: u32 = 2;
130
131/// Server-side vs client-side decoration preference.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum DecorationMode {
134    /// Client draws its own title bar, borders, etc.
135    ClientSide,
136    /// Compositor draws title bar, borders, etc.
137    ServerSide,
138}
139
140impl DecorationMode {
141    /// Parse from the wire `u32` value.
142    pub fn from_u32(v: u32) -> Option<Self> {
143        match v {
144            ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE => Some(Self::ClientSide),
145            ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE => Some(Self::ServerSide),
146            _ => None,
147        }
148    }
149
150    /// Convert to the wire `u32` value.
151    pub fn to_u32(self) -> u32 {
152        match self {
153            Self::ClientSide => ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE,
154            Self::ServerSide => ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE,
155        }
156    }
157}
158
159/// Per-toplevel decoration state negotiated between client and compositor.
160pub struct ToplevelDecoration {
161    /// Decoration object ID
162    pub id: u32,
163    /// Associated toplevel ID
164    pub toplevel_id: u32,
165    /// The mode that the compositor decided on
166    pub mode: DecorationMode,
167}
168
169/// Negotiate decoration mode for a toplevel.
170///
171/// VeridianOS always prefers server-side decorations so that the compositor
172/// draws title bars, close buttons, and window borders uniformly.
173pub fn negotiate_decoration(_client_preference: Option<DecorationMode>) -> DecorationMode {
174    // The compositor always chooses SSD for a consistent desktop appearance.
175    DecorationMode::ServerSide
176}
177
178// ---------------------------------------------------------------------------
179// Window state
180// ---------------------------------------------------------------------------
181
182/// Window state for an xdg_toplevel.
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum WindowState {
185    Normal,
186    Maximized,
187    Fullscreen,
188    Minimized,
189}
190
191// ---------------------------------------------------------------------------
192// XDG Surface
193// ---------------------------------------------------------------------------
194
195/// An xdg_surface wraps a wl_surface with desktop shell semantics.
196pub struct XdgSurface {
197    /// xdg_surface object ID
198    pub id: u32,
199    /// Underlying wl_surface ID
200    pub surface_id: u32,
201    /// Whether the client has ack'd the latest configure
202    pub configured: bool,
203    /// Last sent configure serial
204    pub configure_serial: u32,
205    /// Window geometry (client-set visible bounds)
206    pub geometry: Option<(i32, i32, u32, u32)>,
207    /// Associated toplevel (if any)
208    pub toplevel: Option<XdgToplevel>,
209}
210
211impl XdgSurface {
212    pub fn new(id: u32, surface_id: u32) -> Self {
213        Self {
214            id,
215            surface_id,
216            configured: false,
217            configure_serial: 0,
218            geometry: None,
219            toplevel: None,
220        }
221    }
222
223    /// Handle ack_configure from the client.
224    pub fn ack_configure(&mut self, serial: u32) -> bool {
225        if serial == self.configure_serial {
226            self.configured = true;
227            true
228        } else {
229            false
230        }
231    }
232
233    /// Set window geometry.
234    pub fn set_geometry(&mut self, x: i32, y: i32, width: u32, height: u32) {
235        self.geometry = Some((x, y, width, height));
236    }
237}
238
239// ---------------------------------------------------------------------------
240// XDG Toplevel
241// ---------------------------------------------------------------------------
242
243/// An xdg_toplevel represents a standard desktop window.
244pub struct XdgToplevel {
245    /// Toplevel object ID
246    pub id: u32,
247    /// Parent xdg_surface ID
248    pub xdg_surface_id: u32,
249    /// Window title (set by client)
250    pub title: String,
251    /// Application identifier
252    pub app_id: String,
253    /// Current window state
254    pub state: WindowState,
255    /// Whether this toplevel is activated (focused)
256    pub activated: bool,
257    /// Minimum size constraint (0,0 = no constraint)
258    pub min_size: (u32, u32),
259    /// Maximum size constraint (0,0 = no constraint)
260    pub max_size: (u32, u32),
261}
262
263impl XdgToplevel {
264    pub fn new(id: u32, xdg_surface_id: u32) -> Self {
265        Self {
266            id,
267            xdg_surface_id,
268            title: String::new(),
269            app_id: String::new(),
270            state: WindowState::Normal,
271            activated: false,
272            min_size: (0, 0),
273            max_size: (0, 0),
274        }
275    }
276
277    pub fn set_title(&mut self, title: String) {
278        self.title = title;
279    }
280
281    pub fn set_app_id(&mut self, app_id: String) {
282        self.app_id = app_id;
283    }
284
285    pub fn set_maximized(&mut self) {
286        self.state = WindowState::Maximized;
287    }
288
289    pub fn set_fullscreen(&mut self) {
290        self.state = WindowState::Fullscreen;
291    }
292
293    pub fn set_minimized(&mut self) {
294        self.state = WindowState::Minimized;
295    }
296
297    pub fn set_normal(&mut self) {
298        self.state = WindowState::Normal;
299    }
300}
301
302// ---------------------------------------------------------------------------
303// XDG Shell Manager (xdg_wm_base)
304// ---------------------------------------------------------------------------
305
306/// Manages the xdg_wm_base protocol state: ping/pong, xdg_surface and
307/// xdg_toplevel lifecycle.
308pub struct XdgShell {
309    /// All xdg_surfaces keyed by their object ID
310    xdg_surfaces: BTreeMap<u32, XdgSurface>,
311    /// Next configure serial
312    next_serial: u32,
313    /// Pending ping serial (None = no outstanding ping)
314    pending_ping: Option<u32>,
315    /// Whether the client responded to the last ping
316    client_alive: bool,
317}
318
319impl XdgShell {
320    pub fn new() -> Self {
321        Self {
322            xdg_surfaces: BTreeMap::new(),
323            next_serial: 1,
324            pending_ping: None,
325            client_alive: true,
326        }
327    }
328
329    /// Allocate the next serial number.
330    fn next_serial(&mut self) -> u32 {
331        let s = self.next_serial;
332        self.next_serial += 1;
333        s
334    }
335
336    // -- xdg_wm_base requests -----------------------------------------------
337
338    /// Handle xdg_wm_base.pong from the client.
339    pub fn handle_pong(&mut self, serial: u32) -> bool {
340        if self.pending_ping == Some(serial) {
341            self.pending_ping = None;
342            self.client_alive = true;
343            true
344        } else {
345            false
346        }
347    }
348
349    /// Build a ping event to send to the client.
350    pub fn build_ping(&mut self, wm_base_id: u32) -> Vec<u8> {
351        let serial = self.next_serial();
352        self.pending_ping = Some(serial);
353        protocol::serialize_message(&WaylandMessage::new(
354            wm_base_id,
355            XDG_WM_BASE_PING,
356            vec![Argument::Uint(serial)],
357        ))
358    }
359
360    // -- xdg_surface lifecycle ----------------------------------------------
361
362    /// Create a new xdg_surface wrapping a wl_surface.
363    pub fn create_xdg_surface(
364        &mut self,
365        xdg_surface_id: u32,
366        surface_id: u32,
367    ) -> Result<(), KernelError> {
368        let xdg = XdgSurface::new(xdg_surface_id, surface_id);
369        self.xdg_surfaces.insert(xdg_surface_id, xdg);
370        Ok(())
371    }
372
373    /// Get a reference to an xdg_surface.
374    pub fn get_xdg_surface(&self, id: u32) -> Option<&XdgSurface> {
375        self.xdg_surfaces.get(&id)
376    }
377
378    /// Get a mutable reference to an xdg_surface.
379    pub fn get_xdg_surface_mut(&mut self, id: u32) -> Option<&mut XdgSurface> {
380        self.xdg_surfaces.get_mut(&id)
381    }
382
383    /// Destroy an xdg_surface.
384    pub fn destroy_xdg_surface(&mut self, id: u32) -> bool {
385        self.xdg_surfaces.remove(&id).is_some()
386    }
387
388    /// Find and mutate a toplevel by its object ID.
389    ///
390    /// Scans all xdg_surfaces for a toplevel matching `toplevel_id` and calls
391    /// `f` on it if found.
392    pub fn with_toplevel_mut<R, F: FnOnce(&mut XdgToplevel) -> R>(
393        &mut self,
394        toplevel_id: u32,
395        f: F,
396    ) -> Option<R> {
397        for xdg in self.xdg_surfaces.values_mut() {
398            if let Some(ref mut tl) = xdg.toplevel {
399                if tl.id == toplevel_id {
400                    return Some(f(tl));
401                }
402            }
403        }
404        None
405    }
406
407    // -- xdg_toplevel lifecycle ---------------------------------------------
408
409    /// Create a toplevel role on an xdg_surface.
410    pub fn create_toplevel(
411        &mut self,
412        xdg_surface_id: u32,
413        toplevel_id: u32,
414    ) -> Result<(), KernelError> {
415        let xdg = self
416            .xdg_surfaces
417            .get_mut(&xdg_surface_id)
418            .ok_or(KernelError::NotFound {
419                resource: "xdg_surface",
420                id: xdg_surface_id as u64,
421            })?;
422
423        if xdg.toplevel.is_some() {
424            return Err(KernelError::AlreadyExists {
425                resource: "xdg_toplevel",
426                id: toplevel_id as u64,
427            });
428        }
429
430        xdg.toplevel = Some(XdgToplevel::new(toplevel_id, xdg_surface_id));
431        Ok(())
432    }
433
434    // -- Configure events ---------------------------------------------------
435
436    /// Build an xdg_toplevel.configure event followed by xdg_surface.configure.
437    ///
438    /// This is the initial configure sequence that must be sent before the
439    /// client can attach buffers.
440    pub fn build_initial_configure(
441        &mut self,
442        xdg_surface_id: u32,
443        width: u32,
444        height: u32,
445    ) -> Vec<u8> {
446        let serial = self.next_serial();
447
448        if let Some(xdg) = self.xdg_surfaces.get_mut(&xdg_surface_id) {
449            xdg.configure_serial = serial;
450        }
451
452        let mut events = Vec::new();
453
454        // xdg_toplevel.configure(width, height, states)
455        if let Some(xdg) = self.xdg_surfaces.get(&xdg_surface_id) {
456            if let Some(ref toplevel) = xdg.toplevel {
457                let mut states_data = Vec::new();
458                if toplevel.activated {
459                    states_data.extend_from_slice(&XDG_TOPLEVEL_STATE_ACTIVATED.to_ne_bytes());
460                }
461                match toplevel.state {
462                    WindowState::Maximized => {
463                        states_data.extend_from_slice(&XDG_TOPLEVEL_STATE_MAXIMIZED.to_ne_bytes());
464                    }
465                    WindowState::Fullscreen => {
466                        states_data.extend_from_slice(&XDG_TOPLEVEL_STATE_FULLSCREEN.to_ne_bytes());
467                    }
468                    _ => {}
469                }
470
471                let toplevel_configure = WaylandMessage::new(
472                    toplevel.id,
473                    XDG_TOPLEVEL_CONFIGURE,
474                    vec![
475                        Argument::Int(width as i32),
476                        Argument::Int(height as i32),
477                        Argument::Array(states_data),
478                    ],
479                );
480                events.extend_from_slice(&protocol::serialize_message(&toplevel_configure));
481            }
482        }
483
484        // xdg_surface.configure(serial)
485        let surface_configure = WaylandMessage::new(
486            xdg_surface_id,
487            XDG_SURFACE_CONFIGURE,
488            vec![Argument::Uint(serial)],
489        );
490        events.extend_from_slice(&protocol::serialize_message(&surface_configure));
491
492        events
493    }
494
495    /// Build an xdg_toplevel.close event.
496    pub fn build_close_event(&self, xdg_surface_id: u32) -> Option<Vec<u8>> {
497        let xdg = self.xdg_surfaces.get(&xdg_surface_id)?;
498        let toplevel = xdg.toplevel.as_ref()?;
499
500        Some(protocol::serialize_message(&WaylandMessage::new(
501            toplevel.id,
502            XDG_TOPLEVEL_CLOSE,
503            vec![],
504        )))
505    }
506}
507
508impl Default for XdgShell {
509    fn default() -> Self {
510        Self::new()
511    }
512}
513
514// ---------------------------------------------------------------------------
515// Global XDG Shell instance
516// ---------------------------------------------------------------------------
517
518static XDG_SHELL: RwLock<Option<XdgShell>> = RwLock::new(None);
519
520/// Initialize the XDG shell manager.
521pub fn init_xdg_shell() {
522    let mut shell = XDG_SHELL.write();
523    if shell.is_none() {
524        *shell = Some(XdgShell::new());
525    }
526}
527
528/// Execute a closure with read access to the XDG shell.
529pub fn with_xdg_shell<R, F: FnOnce(&XdgShell) -> R>(f: F) -> Option<R> {
530    let guard = XDG_SHELL.read();
531    guard.as_ref().map(f)
532}
533
534/// Execute a closure with mutable access to the XDG shell.
535pub fn with_xdg_shell_mut<R, F: FnOnce(&mut XdgShell) -> R>(f: F) -> Option<R> {
536    let mut guard = XDG_SHELL.write();
537    guard.as_mut().map(f)
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543
544    #[test]
545    fn test_xdg_toplevel_title() {
546        let mut tl = XdgToplevel::new(1, 1);
547        tl.set_title(String::from("Hello"));
548        assert_eq!(tl.title, "Hello");
549    }
550
551    #[test]
552    fn test_xdg_toplevel_state() {
553        let mut tl = XdgToplevel::new(1, 1);
554        assert_eq!(tl.state, WindowState::Normal);
555        tl.set_maximized();
556        assert_eq!(tl.state, WindowState::Maximized);
557        tl.set_fullscreen();
558        assert_eq!(tl.state, WindowState::Fullscreen);
559    }
560
561    #[test]
562    fn test_xdg_shell_ping_pong() {
563        let mut shell = XdgShell::new();
564        let _ping_event = shell.build_ping(10);
565        let serial = shell.pending_ping.unwrap();
566        assert!(shell.handle_pong(serial));
567        assert!(shell.pending_ping.is_none());
568    }
569
570    #[test]
571    fn test_xdg_surface_configure() {
572        let mut shell = XdgShell::new();
573        shell.create_xdg_surface(5, 3).unwrap();
574        shell.create_toplevel(5, 6).unwrap();
575
576        let events = shell.build_initial_configure(5, 800, 600);
577        assert!(!events.is_empty());
578
579        // ack_configure with correct serial
580        let serial = shell.get_xdg_surface(5).unwrap().configure_serial;
581        assert!(shell.get_xdg_surface_mut(5).unwrap().ack_configure(serial));
582    }
583
584    #[test]
585    fn test_xdg_surface_ack_wrong_serial() {
586        let mut shell = XdgShell::new();
587        shell.create_xdg_surface(5, 3).unwrap();
588        // Wrong serial
589        assert!(!shell.get_xdg_surface_mut(5).unwrap().ack_configure(999));
590    }
591
592    #[test]
593    fn test_close_event() {
594        let mut shell = XdgShell::new();
595        shell.create_xdg_surface(5, 3).unwrap();
596        shell.create_toplevel(5, 6).unwrap();
597        let close = shell.build_close_event(5);
598        assert!(close.is_some());
599    }
600}