git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitaba735bf34087d17c805daa40e5033de10d7064d
parent7983b99d4d
authorLucas Galante <[email protected]>
date2026-06-19 02:31
Add custom cce-window-management-v1 protocol and server backend

 build.rs                              |   1 +
 protocol/cce-window-management-v1.xml |  75 +++++++++
 src/lib.rs                            |   2 +
 src/server/cce_window_management.rs   | 298 ++++++++++++++++++++++++++++++++++
 src/server/server.rs                  |   3 +
 wrapper.h                             |   1 +
 6 files changed, 380 insertions(+)

diff --git a/build.rs b/build.rs
index 5d8e1fe..d800e46 100644
--- a/build.rs
+++ b/build.rs
@@ -87,6 +87,7 @@ fn main() {
         ("river-libinput-config-v1.xml", "protocol/river-libinput-config-v1.xml"),
         ("river-xkb-config-v1.xml", "protocol/river-xkb-config-v1.xml"),
         ("clear-inspector-v1.xml", "protocol/clear-inspector-v1.xml"),
+        ("cce-window-management-v1.xml", "protocol/cce-window-management-v1.xml"),
     ];
 
     let mut generated_c_files = Vec::new();
diff --git a/protocol/cce-window-management-v1.xml b/protocol/cce-window-management-v1.xml
new file mode 100644
index 0000000..b05c51f
--- /dev/null
+++ b/protocol/cce-window-management-v1.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="cce_window_management_v1">
+  <copyright>
+    Copyright © 2026 Antigravity
+  </copyright>
+
+  <interface name="zcce_window_manager_v1" version="1">
+    <description summary="cce window management interface">
+      This global interface allows clients to obtain custom window management
+      controls for their surfaces.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the window manager object"/>
+    </request>
+
+    <request name="get_cce_toplevel">
+      <description summary="get a cce toplevel for a surface">
+        Create a zcce_toplevel_v1 object for a given wl_surface.
+      </description>
+      <arg name="id" type="new_id" interface="zcce_toplevel_v1"/>
+      <arg name="surface" type="object" interface="wl_surface"/>
+    </request>
+  </interface>
+
+  <interface name="zcce_toplevel_v1" version="1">
+    <description summary="toplevel window management controls">
+      An interface to control and listen to CCE-specific window management states
+      for a client surface.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the cce toplevel object"/>
+    </request>
+
+    <request name="set_floating">
+      <description summary="request floating mode">
+        Explicitly request the compositor to place the window in floating mode.
+      </description>
+    </request>
+
+    <request name="unset_floating">
+      <description summary="unset floating mode">
+        Request the compositor to return the window to normal tiling mode.
+      </description>
+    </request>
+
+    <request name="set_maximized">
+      <description summary="request maximized state"/>
+    </request>
+
+    <request name="unset_maximized">
+      <description summary="unset maximized state"/>
+    </request>
+
+    <request name="set_fullscreen">
+      <description summary="request fullscreen state"/>
+    </request>
+
+    <request name="unset_fullscreen">
+      <description summary="unset fullscreen state"/>
+    </request>
+
+    <request name="set_minimized">
+      <description summary="request minimized state"/>
+    </request>
+
+    <event name="floating_state">
+      <description summary="floating status event">
+        Sent by the compositor to inform the client of its current floating status.
+      </description>
+      <arg name="state" type="uint" summary="1 if floating, 0 otherwise"/>
+    </event>
+  </interface>
+</protocol>
diff --git a/src/lib.rs b/src/lib.rs
index f2ad07b..1cf30a3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -93,6 +93,8 @@ pub mod pointer_binding;
 pub mod keyboard_group;
 #[path = "server/inspector.rs"]
 pub mod inspector;
+#[path = "server/cce_window_management.rs"]
+pub mod cce_window_management;
 
 #[path = "server/run_server.rs"]
 pub mod run_server;
diff --git a/src/server/cce_window_management.rs b/src/server/cce_window_management.rs
new file mode 100644
index 0000000..6623f45
--- /dev/null
+++ b/src/server/cce_window_management.rs
@@ -0,0 +1,298 @@
+use crate::ffi;
+use crate::server::Server;
+use crate::window::Window;
+use crate::slotmap::Key as SlotMapKey;
+
+pub struct CceWindowManagement {
+    pub server: *mut Server,
+    pub global: *mut ffi::wl_global,
+    pub toplevels: Vec<*mut ffi::wl_resource>,
+}
+
+impl CceWindowManagement {
+    pub fn new() -> Self {
+        Self {
+            server: std::ptr::null_mut(),
+            global: std::ptr::null_mut(),
+            toplevels: Vec::new(),
+        }
+    }
+
+    pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
+        self.server = server;
+        self.toplevels = Vec::new();
+
+        self.global = ffi::wl_global_create(
+            (*server).wl_server,
+            &ffi::zcce_window_manager_v1_interface,
+            1,
+            self as *mut CceWindowManagement as *mut _,
+            Some(bind_wm),
+        );
+
+        if self.global.is_null() {
+            return Err("Failed to create zcce_window_manager_v1 global");
+        }
+
+        log::info!("zcce_window_manager_v1 protocol global initialized successfully");
+        Ok(())
+    }
+
+    pub unsafe fn deinit(&mut self) {
+        if !self.global.is_null() {
+            ffi::wl_global_destroy(self.global);
+            self.global = std::ptr::null_mut();
+        }
+    }
+}
+
+unsafe extern "C" fn bind_wm(
+    client: *mut ffi::wl_client,
+    data: *mut std::ffi::c_void,
+    version: u32,
+    id: u32,
+) {
+    let wm = data as *mut CceWindowManagement;
+    if wm.is_null() {
+        return;
+    }
+
+    let resource = ffi::wl_resource_create(client, &ffi::zcce_window_manager_v1_interface, version as i32, id);
+    if resource.is_null() {
+        ffi::wl_client_post_no_memory(client);
+        return;
+    }
+
+    ffi::wl_resource_set_implementation(
+        resource,
+        &CCE_WM_INTERFACE as *const _ as *const _,
+        wm as *mut _,
+        None,
+    );
+}
+
+unsafe extern "C" fn wm_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+    ffi::wl_resource_destroy(resource);
+}
+
+struct CceToplevelData {
+    server: *mut Server,
+    window_key: SlotMapKey,
+    _resource: *mut ffi::wl_resource,
+}
+
+unsafe extern "C" fn wm_get_cce_toplevel(
+    client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+    id: u32,
+    surface_resource: *mut ffi::wl_resource,
+) {
+    let wm = ffi::wl_resource_get_user_data(resource) as *mut CceWindowManagement;
+    if wm.is_null() {
+        return;
+    }
+
+    let surface = ffi::wlr_surface_from_resource(surface_resource);
+    if surface.is_null() {
+        log::error!("wm_get_cce_toplevel: surface is null");
+        return;
+    }
+
+    // Find the Window corresponding to the surface
+    let mut target_window = std::ptr::null_mut();
+    for &window in (*(*wm).server).wm.windows.iter() {
+        if !window.is_null() && (*window).root_surface() == surface {
+            target_window = window;
+            break;
+        }
+    }
+
+    if target_window.is_null() {
+        log::error!("wm_get_cce_toplevel: no Window structure found for surface");
+        return;
+    }
+
+    let version = ffi::wl_resource_get_version(resource);
+    let toplevel_res = ffi::wl_resource_create(client, &ffi::zcce_toplevel_v1_interface, version, id);
+    if toplevel_res.is_null() {
+        ffi::wl_client_post_no_memory(client);
+        return;
+    }
+
+    let data = Box::into_raw(Box::new(CceToplevelData {
+        server: (*wm).server,
+        window_key: (*target_window).ref_key,
+        _resource: toplevel_res,
+    }));
+
+    ffi::wl_resource_set_implementation(
+        toplevel_res,
+        &CCE_TOPLEVEL_INTERFACE as *const _ as *const _,
+        data as *mut _,
+        Some(handle_destroy_toplevel_resource),
+    );
+
+    (*wm).toplevels.push(toplevel_res);
+
+    // Initial event: send current floating state
+    let state = if (*target_window).tiling_mode == crate::tiling::TilingMode::Floating { 1 } else { 0 };
+    // Event floating_state has index 0
+    ffi::wl_resource_post_event(toplevel_res, 0, state as u32);
+}
+
+unsafe extern "C" fn handle_destroy_toplevel_resource(resource: *mut ffi::wl_resource) {
+    let data_ptr = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if !data_ptr.is_null() {
+        let _ = Box::from_raw(data_ptr);
+    }
+}
+
+static CCE_WM_INTERFACE: ffi::zcce_window_manager_v1_interface = ffi::zcce_window_manager_v1_interface {
+    destroy: Some(wm_destroy),
+    get_cce_toplevel: Some(wm_get_cce_toplevel),
+};
+
+unsafe extern "C" fn toplevel_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+    ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn toplevel_set_floating(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if data.is_null() {
+        return;
+    }
+    let server = (*data).server;
+    let window_key = (*data).window_key;
+    if let Some(window) = resolve_window(server, window_key) {
+        (*window).tiling_mode = crate::tiling::TilingMode::Floating;
+        (*window).mode_locked = true;
+        (*server).wm.dirty_windowing();
+
+        // Send floating_state(1)
+        ffi::wl_resource_post_event(resource, 0, 1u32);
+    }
+}
+
+unsafe extern "C" fn toplevel_unset_floating(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if data.is_null() {
+        return;
+    }
+    let server = (*data).server;
+    let window_key = (*data).window_key;
+    if let Some(window) = resolve_window(server, window_key) {
+        (*window).mode_locked = false;
+        (*server).wm.dirty_windowing();
+
+        // Send floating_state(0)
+        ffi::wl_resource_post_event(resource, 0, 0u32);
+    }
+}
+
+unsafe extern "C" fn toplevel_set_maximized(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if data.is_null() {
+        return;
+    }
+    let server = (*data).server;
+    let window_key = (*data).window_key;
+    if let Some(window) = resolve_window(server, window_key) {
+        (*window).tiling_mode = crate::tiling::TilingMode::Cascade;
+        (*window).mode_locked = true;
+        (*window).wm_scheduled.maximize_requested = crate::window::MaximizeRequest::Maximize;
+        (*server).wm.dirty_windowing();
+    }
+}
+
+unsafe extern "C" fn toplevel_unset_maximized(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if data.is_null() {
+        return;
+    }
+    let server = (*data).server;
+    let window_key = (*data).window_key;
+    if let Some(window) = resolve_window(server, window_key) {
+        (*window).mode_locked = false;
+        (*window).wm_scheduled.maximize_requested = crate::window::MaximizeRequest::Unmaximize;
+        (*server).wm.dirty_windowing();
+    }
+}
+
+unsafe extern "C" fn toplevel_set_fullscreen(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if data.is_null() {
+        return;
+    }
+    let server = (*data).server;
+    let window_key = (*data).window_key;
+    if let Some(window) = resolve_window(server, window_key) {
+        (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Fullscreen(std::ptr::null_mut());
+        (*server).wm.dirty_windowing();
+    }
+}
+
+unsafe extern "C" fn toplevel_unset_fullscreen(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if data.is_null() {
+        return;
+    }
+    let server = (*data).server;
+    let window_key = (*data).window_key;
+    if let Some(window) = resolve_window(server, window_key) {
+        (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Exit;
+        (*server).wm.dirty_windowing();
+    }
+}
+
+unsafe extern "C" fn toplevel_set_minimized(
+    _client: *mut ffi::wl_client,
+    resource: *mut ffi::wl_resource,
+) {
+    let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
+    if data.is_null() {
+        return;
+    }
+    let server = (*data).server;
+    let window_key = (*data).window_key;
+    if let Some(window) = resolve_window(server, window_key) {
+        (*window).wm_scheduled.minimize_requested = true;
+        (*server).wm.dirty_windowing();
+    }
+}
+
+static CCE_TOPLEVEL_INTERFACE: ffi::zcce_toplevel_v1_interface = ffi::zcce_toplevel_v1_interface {
+    destroy: Some(toplevel_destroy),
+    set_floating: Some(toplevel_set_floating),
+    unset_floating: Some(toplevel_unset_floating),
+    set_maximized: Some(toplevel_set_maximized),
+    unset_maximized: Some(toplevel_unset_maximized),
+    set_fullscreen: Some(toplevel_set_fullscreen),
+    unset_fullscreen: Some(toplevel_unset_fullscreen),
+    set_minimized: Some(toplevel_set_minimized),
+};
+
+unsafe fn resolve_window(server: *mut Server, key: SlotMapKey) -> Option<*mut Window> {
+    if server.is_null() {
+        return None;
+    }
+    let windows_map = &(*server).wm.windows;
+    windows_map.get(key).copied().filter(|&w| !w.is_null())
+}
diff --git a/src/server/server.rs b/src/server/server.rs
index 4f8c1bf..4df2c82 100644
--- a/src/server/server.rs
+++ b/src/server/server.rs
@@ -271,6 +271,7 @@ pub struct Server {
     pub idle_inhibit_manager: IdleInhibitManager,
     pub lock_manager: LockManager,
     pub inspector: crate::inspector::Inspector,
+    pub cce_window_management: crate::cce_window_management::CceWindowManagement,
 
     // Event listeners
     pub renderer_lost: ffi::wl_listener,
@@ -693,6 +694,7 @@ impl Server {
             self.idle_inhibit_manager.init(server_ptr).map_err(|_| "Failed to init idle_inhibit_manager")?;
             self.lock_manager.init(server_ptr).map_err(|_| "Failed to init lock_manager")?;
             self.inspector.init(server_ptr).map_err(|_| "Failed to init inspector")?;
+            self.cce_window_management.init(server_ptr).map_err(|_| "Failed to init cce_window_management")?;
 
             // Setup listeners
             let r_lost = &mut self.renderer_lost as *mut ffi::wl_listener as *mut WlListener;
@@ -822,6 +824,7 @@ impl Default for Server {
             std::ptr::write(&mut (*server.as_mut_ptr()).wm.status_sender, None);
             std::ptr::write(&mut (*server.as_mut_ptr()).layer_shell.surfaces, crate::slotmap::SlotMap::new());
             std::ptr::write(&mut (*server.as_mut_ptr()).inspector, crate::inspector::Inspector::new());
+            std::ptr::write(&mut (*server.as_mut_ptr()).cce_window_management, crate::cce_window_management::CceWindowManagement::new());
             server.assume_init()
         }
     }
diff --git a/wrapper.h b/wrapper.h
index 0275a08..9f9faac 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -98,6 +98,7 @@
 #include "river-libinput-config-v1-protocol.h"
 #include "river-xkb-config-v1-protocol.h"
 #include "clear-inspector-v1-protocol.h"
+#include "cce-window-management-v1-protocol.h"
 #include "virtual-keyboard-unstable-v1-protocol.h"
 #include <wlr/backend/libinput.h>