git.lucas.co / cce-compositor
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 }