Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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>