Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/cce_window_management.rs (15.1K)
1 use crate::ffi;
2 use crate::server::Server;
3 use crate::window::Window;
4 use crate::slotmap::Key as SlotMapKey;
5
6 pub struct CceWindowManagement {
7 pub server: *mut Server,
8 pub toplevels: Vec<*mut ffi::wl_resource>,
9 }
10
11 impl CceWindowManagement {
12 pub fn new() -> Self {
13 Self {
14 server: std::ptr::null_mut(),
15 toplevels: Vec::new(),
16 }
17 }
18
19 pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
20 self.server = server;
21 self.toplevels = Vec::new();
22 log::info!("CceWindowManagement initialized successfully");
23 Ok(())
24 }
25 }
26
27 /// Grid patch event opcode on zcce_toplevel_v1 (floating_state is 0).
28 const ZCCE_TOPLEVEL_V1_GRID_PATCH: u32 = 1;
29
30 /// Toplevel interface version that carries the grid requests/events.
31 const GRID_SINCE: i32 = 4;
32
33 fn to_fixed(v: f64) -> i32 {
34 (v * 256.0).round() as i32
35 }
36
37 impl CceWindowManagement {
38 /// Post a grid_patch event to the toplevel resource of `window_key`, if
39 /// the client bound a new-enough version. Returns whether it was sent.
40 pub unsafe fn send_grid_patch(
41 &self,
42 window_key: SlotMapKey,
43 serial: u32,
44 patch: crate::policy::api::GridPatch,
45 ) -> bool {
46 for &res in &self.toplevels {
47 if res.is_null() {
48 continue;
49 }
50 let data = ffi::wl_resource_get_user_data(res) as *mut CceToplevelData;
51 if data.is_null() || (*data).window_key != window_key {
52 continue;
53 }
54 if ffi::wl_resource_get_version(res) < GRID_SINCE {
55 return false;
56 }
57 ffi::wl_resource_post_event(
58 res,
59 ZCCE_TOPLEVEL_V1_GRID_PATCH,
60 serial,
61 to_fixed(patch.x),
62 to_fixed(patch.y),
63 to_fixed(patch.w),
64 to_fixed(patch.h),
65 to_fixed(patch.scale),
66 );
67 return true;
68 }
69 false
70 }
71 }
72
73 struct CceToplevelData {
74 server: *mut Server,
75 window_key: SlotMapKey,
76 _resource: *mut ffi::wl_resource,
77 }
78
79 pub unsafe extern "C" fn cce_wm_get_cce_toplevel(
80 client: *mut ffi::wl_client,
81 resource: *mut ffi::wl_resource,
82 id: u32,
83 surface_resource: *mut ffi::wl_resource,
84 ) {
85 let wm = ffi::wl_resource_get_user_data(resource) as *mut crate::window_manager::WindowManager;
86 if wm.is_null() {
87 return;
88 }
89 let server = (*wm).server;
90 if server.is_null() {
91 return;
92 }
93 let cce_wm = &mut (*server).cce_window_management as *mut CceWindowManagement;
94
95 let surface = ffi::wlr_surface_from_resource(surface_resource);
96 if surface.is_null() {
97 log::error!("cce_wm_get_cce_toplevel: surface is null");
98 return;
99 }
100
101 // Find the Window corresponding to the surface
102 let mut target_window = std::ptr::null_mut();
103 for &window in (*server).wm.windows.iter() {
104 if !window.is_null() && (*window).root_surface() == surface {
105 target_window = window;
106 break;
107 }
108 }
109
110 if target_window.is_null() {
111 log::error!("cce_wm_get_cce_toplevel: no Window structure found for surface");
112 return;
113 }
114
115 let version = ffi::wl_resource_get_version(resource);
116 let toplevel_res = ffi::wl_resource_create(client, &ffi::zcce_toplevel_v1_interface, version, id);
117 if toplevel_res.is_null() {
118 ffi::wl_client_post_no_memory(client);
119 return;
120 }
121
122 let data = Box::into_raw(Box::new(CceToplevelData {
123 server,
124 window_key: (*target_window).ref_key,
125 _resource: toplevel_res,
126 }));
127
128 ffi::wl_resource_set_implementation(
129 toplevel_res,
130 &CCE_TOPLEVEL_INTERFACE as *const _ as *const _,
131 data as *mut _,
132 Some(handle_destroy_toplevel_resource),
133 );
134
135 (*cce_wm).toplevels.push(toplevel_res);
136
137 // Initial event: send current floating state
138 let state = if (*target_window).tiling_mode == crate::tiling::TilingMode::Floating { 1 } else { 0 };
139 // Event floating_state has index 0
140 ffi::wl_resource_post_event(toplevel_res, 0, state as u32);
141 }
142
143 unsafe extern "C" fn handle_destroy_toplevel_resource(resource: *mut ffi::wl_resource) {
144 let data_ptr = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
145 if !data_ptr.is_null() {
146 // Drop the tracking entry BEFORE the resource dies:
147 // send_grid_patch iterates `toplevels`, and a stale pointer there
148 // is a use-after-free on the next patch send — this crashed the
149 // session the first time the grid client was RESTARTED (the old
150 // entry lingered, the new instance's first patch dereferenced it).
151 let server = (*data_ptr).server;
152 if !server.is_null() {
153 (*server)
154 .cce_window_management
155 .toplevels
156 .retain(|&r| r != resource);
157 }
158 let _ = Box::from_raw(data_ptr);
159 }
160 }
161
162 unsafe extern "C" fn toplevel_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
163 ffi::wl_resource_destroy(resource);
164 }
165
166 unsafe extern "C" fn toplevel_set_floating(
167 _client: *mut ffi::wl_client,
168 resource: *mut ffi::wl_resource,
169 ) {
170 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
171 if data.is_null() {
172 return;
173 }
174 let server = (*data).server;
175 let window_key = (*data).window_key;
176 if let Some(window) = resolve_window(server, window_key) {
177 (*window).tiling_mode = crate::tiling::TilingMode::Floating;
178 (*window).mode_locked = true;
179 if let Some(seat) = (*server).wm.first_seat() {
180 if (*seat).focused == crate::seat::Focus::Window(window) {
181 (*server).wm.raise_window(window);
182 }
183 }
184 (*server).wm.dirty_windowing();
185
186 // Send floating_state(1)
187 ffi::wl_resource_post_event(resource, 0, 1u32);
188 }
189 }
190
191 unsafe extern "C" fn toplevel_unset_floating(
192 _client: *mut ffi::wl_client,
193 resource: *mut ffi::wl_resource,
194 ) {
195 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
196 if data.is_null() {
197 return;
198 }
199 let server = (*data).server;
200 let window_key = (*data).window_key;
201 if let Some(window) = resolve_window(server, window_key) {
202 (*window).mode_locked = false;
203 (*server).wm.dirty_windowing();
204
205 // Send floating_state(0)
206 ffi::wl_resource_post_event(resource, 0, 0u32);
207 }
208 }
209
210 unsafe extern "C" fn toplevel_set_maximized(
211 _client: *mut ffi::wl_client,
212 resource: *mut ffi::wl_resource,
213 ) {
214 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
215 if data.is_null() {
216 return;
217 }
218 let server = (*data).server;
219 let window_key = (*data).window_key;
220 if let Some(window) = resolve_window(server, window_key) {
221 (*window).tiling_mode = crate::tiling::TilingMode::Tiled;
222 (*window).mode_locked = true;
223 (*window).wm_scheduled.maximize_requested = crate::window::MaximizeRequest::Maximize;
224 (*server).wm.dirty_windowing();
225 }
226 }
227
228 unsafe extern "C" fn toplevel_unset_maximized(
229 _client: *mut ffi::wl_client,
230 resource: *mut ffi::wl_resource,
231 ) {
232 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
233 if data.is_null() {
234 return;
235 }
236 let server = (*data).server;
237 let window_key = (*data).window_key;
238 if let Some(window) = resolve_window(server, window_key) {
239 (*window).mode_locked = false;
240 (*window).wm_scheduled.maximize_requested = crate::window::MaximizeRequest::Unmaximize;
241 (*server).wm.dirty_windowing();
242 }
243 }
244
245 unsafe extern "C" fn toplevel_set_fullscreen(
246 _client: *mut ffi::wl_client,
247 resource: *mut ffi::wl_resource,
248 ) {
249 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
250 if data.is_null() {
251 return;
252 }
253 let server = (*data).server;
254 let window_key = (*data).window_key;
255 if let Some(window) = resolve_window(server, window_key) {
256 (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Fullscreen(std::ptr::null_mut());
257 (*server).wm.dirty_windowing();
258 (*server).wm.apply_client_fullscreen(window, true);
259 }
260 }
261
262 unsafe extern "C" fn toplevel_unset_fullscreen(
263 _client: *mut ffi::wl_client,
264 resource: *mut ffi::wl_resource,
265 ) {
266 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
267 if data.is_null() {
268 return;
269 }
270 let server = (*data).server;
271 let window_key = (*data).window_key;
272 if let Some(window) = resolve_window(server, window_key) {
273 (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Exit;
274 (*server).wm.dirty_windowing();
275 (*server).wm.apply_client_fullscreen(window, false);
276 }
277 }
278
279 unsafe extern "C" fn toplevel_set_minimized(
280 _client: *mut ffi::wl_client,
281 resource: *mut ffi::wl_resource,
282 ) {
283 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
284 if data.is_null() {
285 return;
286 }
287 let server = (*data).server;
288 let window_key = (*data).window_key;
289 if let Some(window) = resolve_window(server, window_key) {
290 (*window).wm_scheduled.minimize_requested = true;
291 (*server).wm.dirty_windowing();
292 }
293 }
294
295 unsafe extern "C" fn toplevel_set_popup(
296 _client: *mut ffi::wl_client,
297 resource: *mut ffi::wl_resource,
298 ) {
299 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
300 if data.is_null() {
301 return;
302 }
303 let server = (*data).server;
304 let window_key = (*data).window_key;
305 if let Some(window) = resolve_window(server, window_key) {
306 (*window).tiling_mode = crate::tiling::TilingMode::Popup;
307 (*window).mode_locked = true;
308 (*server).wm.dirty_windowing();
309 }
310 }
311
312 unsafe extern "C" fn toplevel_unset_popup(
313 _client: *mut ffi::wl_client,
314 resource: *mut ffi::wl_resource,
315 ) {
316 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
317 if data.is_null() {
318 return;
319 }
320 let server = (*data).server;
321 let window_key = (*data).window_key;
322 if let Some(window) = resolve_window(server, window_key) {
323 (*window).mode_locked = false;
324 (*server).wm.dirty_windowing();
325 }
326 }
327
328 unsafe extern "C" fn toplevel_set_utility(
329 _client: *mut ffi::wl_client,
330 resource: *mut ffi::wl_resource,
331 ) {
332 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
333 if data.is_null() {
334 return;
335 }
336 let server = (*data).server;
337 let window_key = (*data).window_key;
338 if let Some(window) = resolve_window(server, window_key) {
339 // mode_locked is the "explicit beats heuristic" latch (same as
340 // set_popup): get_mode_for_window's app_id guessing stands down.
341 (*window).tiling_mode = crate::tiling::TilingMode::Utility;
342 (*window).mode_locked = true;
343 (*server).wm.dirty_windowing();
344 }
345 }
346
347 unsafe extern "C" fn toplevel_unset_utility(
348 _client: *mut ffi::wl_client,
349 resource: *mut ffi::wl_resource,
350 ) {
351 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
352 if data.is_null() {
353 return;
354 }
355 let server = (*data).server;
356 let window_key = (*data).window_key;
357 if let Some(window) = resolve_window(server, window_key) {
358 // Unlike unset_popup, the raw field must be reset too: every Utility
359 // gate (resize rejection, save exclusion, the un-tile exemptions)
360 // reads `tiling_mode` directly, so leaving it at Utility with the
361 // lock cleared would keep the window un-resizable and unsaved while
362 // resolving Floating.
363 (*window).tiling_mode = crate::tiling::TilingMode::Floating;
364 (*window).mode_locked = false;
365 (*server).wm.dirty_windowing();
366 }
367 }
368
369 unsafe extern "C" fn toplevel_set_grid(
370 _client: *mut ffi::wl_client,
371 resource: *mut ffi::wl_resource,
372 ) {
373 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
374 if data.is_null() {
375 return;
376 }
377 let server = (*data).server;
378 let window_key = (*data).window_key;
379 if let Some(window) = resolve_window(server, window_key) {
380 log::info!("[Grid] set_grid declared");
381 (*window).grid_declared = true;
382 (*server).wm.dirty_windowing();
383 }
384 }
385
386 unsafe extern "C" fn toplevel_ack_grid_patch(
387 _client: *mut ffi::wl_client,
388 resource: *mut ffi::wl_resource,
389 serial: u32,
390 ) {
391 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
392 if data.is_null() {
393 return;
394 }
395 let server = (*data).server;
396 let window_key = (*data).window_key;
397 if let Some(window) = resolve_window(server, window_key) {
398 // Only the latest outstanding patch can be acked; a stale serial
399 // (superseded patch) is ignored — the client should already be
400 // rendering the newer one.
401 if let Some((pending_serial, patch)) = (*window).grid_patch_pending {
402 log::info!("[Grid] ack #{serial} (pending #{pending_serial})");
403 if pending_serial == serial {
404 (*window).grid_patch_pending = None;
405 (*window).grid_patch_acked = Some((serial, patch));
406 }
407 }
408 }
409 }
410
411 unsafe extern "C" fn toplevel_set_popover_region(
412 _client: *mut ffi::wl_client,
413 resource: *mut ffi::wl_resource,
414 x: i32,
415 y: i32,
416 width: i32,
417 height: i32,
418 ) {
419 let data = ffi::wl_resource_get_user_data(resource) as *mut CceToplevelData;
420 if data.is_null() {
421 return;
422 }
423 let server = (*data).server;
424 let window_key = (*data).window_key;
425 if let Some(window) = resolve_window(server, window_key) {
426 // Surface-local LOGICAL px; the consumers scale it themselves. No
427 // dirty_windowing: this changes no geometry, and the commit that
428 // drew the popover already schedules the frame that will redraw
429 // the ring through draw_borders.
430 (*window).popover_region = if width > 0 && height > 0 {
431 Some(ffi::wlr_box { x, y, width, height })
432 } else {
433 None
434 };
435 }
436 }
437
438 static CCE_TOPLEVEL_INTERFACE: ffi::zcce_toplevel_v1_interface = ffi::zcce_toplevel_v1_interface {
439 destroy: Some(toplevel_destroy),
440 set_floating: Some(toplevel_set_floating),
441 unset_floating: Some(toplevel_unset_floating),
442 set_maximized: Some(toplevel_set_maximized),
443 unset_maximized: Some(toplevel_unset_maximized),
444 set_fullscreen: Some(toplevel_set_fullscreen),
445 unset_fullscreen: Some(toplevel_unset_fullscreen),
446 set_minimized: Some(toplevel_set_minimized),
447 set_popup: Some(toplevel_set_popup),
448 unset_popup: Some(toplevel_unset_popup),
449 set_utility: Some(toplevel_set_utility),
450 unset_utility: Some(toplevel_unset_utility),
451 set_grid: Some(toplevel_set_grid),
452 ack_grid_patch: Some(toplevel_ack_grid_patch),
453 set_popover_region: Some(toplevel_set_popover_region),
454 };
455
456 unsafe fn resolve_window(server: *mut Server, key: SlotMapKey) -> Option<*mut Window> {
457 if server.is_null() {
458 return None;
459 }
460 let windows_map = &(*server).wm.windows;
461 windows_map.get(key).copied().filter(|&w| !w.is_null())
462 }