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

src/server/xdg_toplevel.rs (59.3K)

   1 // SPDX-FileCopyrightText: © 2020 The River Developers
   2 // SPDX-License-Identifier: GPL-3.0-only
   3 
   4 use crate::ffi;
   5 use crate::server::{Server, WlListener, WlList, wl_listener_remove, wl_signal_add};
   6 use crate::window::Window;
   7 
   8 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
   9 pub enum ConfigureState {
  10     Idle,
  11     Inflight(u32),
  12     Acked,
  13     Committed,
  14     TimedOut(u32),
  15     TimedOutAcked,
  16 }
  17 
  18 pub struct XdgToplevel {
  19     pub window: *mut Window,
  20     pub wlr_toplevel: *mut ffi::wlr_xdg_toplevel,
  21     pub decoration: *mut XdgDecoration,
  22     pub geometry: ffi::wlr_box,
  23     /// Surface extent as of the last commit — with `geometry`, the mapping
  24     /// change detector for the deferred pointer refresh (see `handle_commit`).
  25     pub last_surface_size: (i32, i32),
  26     pub configure_state: ConfigureState,
  27     /// Has the client ever answered a configure? Until it has,
  28     /// `geometry` is the size the CLIENT asked for, not a response to one
  29     /// — see `Window::render_start`, which refuses to adopt a wish over a
  30     /// restored size.
  31     pub acked_once: bool,
  32 
  33     pub destroy: ffi::wl_listener,
  34     pub ack_configure: ffi::wl_listener,
  35     pub map: ffi::wl_listener,
  36     pub unmap: ffi::wl_listener,
  37     pub commit: ffi::wl_listener,
  38     pub new_popup: ffi::wl_listener,
  39     pub request_show_window_menu: ffi::wl_listener,
  40     pub request_fullscreen: ffi::wl_listener,
  41     pub request_maximize: ffi::wl_listener,
  42     pub request_minimize: ffi::wl_listener,
  43     pub request_move: ffi::wl_listener,
  44     pub request_resize: ffi::wl_listener,
  45     pub set_parent: ffi::wl_listener,
  46     pub set_title: ffi::wl_listener,
  47     pub set_app_id: ffi::wl_listener,
  48 }
  49 
  50 pub struct XdgDecoration {
  51     pub wlr_decoration: *mut ffi::wlr_xdg_toplevel_decoration_v1,
  52     pub destroy: ffi::wl_listener,
  53     pub request_mode: ffi::wl_listener,
  54 }
  55 
  56 impl XdgToplevel {
  57     pub unsafe fn create(
  58         wlr_toplevel: *mut ffi::wlr_xdg_toplevel,
  59         server: *mut Server,
  60     ) -> Result<(), &'static str> {
  61         log::debug!("new xdg_toplevel");
  62 
  63         let window = Window::create(crate::window::WindowImpl::Toplevel(std::ptr::null_mut()), server)?;
  64 
  65         let toplevel = Box::new(XdgToplevel {
  66             window,
  67             wlr_toplevel,
  68             decoration: std::ptr::null_mut(),
  69             geometry: std::mem::zeroed(),
  70             last_surface_size: (0, 0),
  71             configure_state: ConfigureState::Idle,
  72             acked_once: false,
  73 
  74             destroy: std::mem::zeroed(),
  75             ack_configure: std::mem::zeroed(),
  76             map: std::mem::zeroed(),
  77             unmap: std::mem::zeroed(),
  78             commit: std::mem::zeroed(),
  79             new_popup: std::mem::zeroed(),
  80             request_show_window_menu: std::mem::zeroed(),
  81             request_fullscreen: std::mem::zeroed(),
  82             request_maximize: std::mem::zeroed(),
  83             request_minimize: std::mem::zeroed(),
  84             request_move: std::mem::zeroed(),
  85             request_resize: std::mem::zeroed(),
  86             set_parent: std::mem::zeroed(),
  87             set_title: std::mem::zeroed(),
  88             set_app_id: std::mem::zeroed(),
  89         });
  90 
  91         let raw = Box::into_raw(toplevel);
  92         (*window).set_impl(crate::window::WindowImpl::Toplevel(raw));
  93 
  94         let base = ffi::river_wlr_xdg_toplevel_get_base(wlr_toplevel);
  95         let surface = ffi::river_wlr_xdg_surface_get_surface(base);
  96 
  97         let unmap_listener = &mut (*raw).unmap as *mut ffi::wl_listener as *mut WlListener;
  98         (*unmap_listener).notify = Some(handle_unmap);
  99         wl_signal_add(ffi::river_wlr_surface_get_unmap_signal(surface), &mut (*raw).unmap);
 100 
 101         let surfaces_tree = (*window).surfaces.tree;
 102         let capture_tree = &mut (*(*window).capture_scene).tree as *mut ffi::wlr_scene_tree;
 103 
 104         let scene_xdg = ffi::wlr_scene_xdg_surface_create(surfaces_tree, base);
 105         if scene_xdg.is_null() {
 106             let _ = Box::from_raw(raw);
 107             return Err("wlr_scene_xdg_surface_create failed");
 108         }
 109         let capture_xdg = ffi::wlr_scene_xdg_surface_create(capture_tree, base);
 110         if capture_xdg.is_null() {
 111             // Already added unmap listener, but let's at least try to free raw
 112             let _ = Box::from_raw(raw);
 113             return Err("wlr_scene_xdg_surface_create for capture tree failed");
 114         }
 115 
 116         ffi::river_wlr_xdg_surface_set_data(base, raw as *mut _);
 117         ffi::river_wlr_surface_set_data(surface, (*window).tree as *mut ffi::wlr_scene_node as *mut _);
 118 
 119         let destroy_listener = &mut (*raw).destroy as *mut ffi::wl_listener as *mut WlListener;
 120         (*destroy_listener).notify = Some(handle_destroy);
 121         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_destroy_signal(wlr_toplevel), &mut (*raw).destroy);
 122 
 123         let ack_listener = &mut (*raw).ack_configure as *mut ffi::wl_listener as *mut WlListener;
 124         (*ack_listener).notify = Some(handle_ack_configure);
 125         wl_signal_add(ffi::river_wlr_xdg_surface_get_ack_configure_signal(base), &mut (*raw).ack_configure);
 126 
 127         let map_listener = &mut (*raw).map as *mut ffi::wl_listener as *mut WlListener;
 128         (*map_listener).notify = Some(handle_map);
 129         wl_signal_add(ffi::river_wlr_surface_get_map_signal(surface), &mut (*raw).map);
 130 
 131         let commit_listener = &mut (*raw).commit as *mut ffi::wl_listener as *mut WlListener;
 132         (*commit_listener).notify = Some(handle_commit);
 133         wl_signal_add(ffi::river_wlr_surface_get_commit_signal(surface), &mut (*raw).commit);
 134 
 135         let popup_listener = &mut (*raw).new_popup as *mut ffi::wl_listener as *mut WlListener;
 136         (*popup_listener).notify = Some(handle_new_popup);
 137         wl_signal_add(ffi::river_wlr_xdg_surface_get_new_popup_signal(base), &mut (*raw).new_popup);
 138 
 139         let menu_listener = &mut (*raw).request_show_window_menu as *mut ffi::wl_listener as *mut WlListener;
 140         (*menu_listener).notify = Some(handle_request_show_window_menu);
 141         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_request_show_window_menu_signal(wlr_toplevel), &mut (*raw).request_show_window_menu);
 142 
 143         let fs_listener = &mut (*raw).request_fullscreen as *mut ffi::wl_listener as *mut WlListener;
 144         (*fs_listener).notify = Some(handle_request_fullscreen);
 145         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_request_fullscreen_signal(wlr_toplevel), &mut (*raw).request_fullscreen);
 146 
 147         let max_listener = &mut (*raw).request_maximize as *mut ffi::wl_listener as *mut WlListener;
 148         (*max_listener).notify = Some(handle_request_maximize);
 149         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_request_maximize_signal(wlr_toplevel), &mut (*raw).request_maximize);
 150 
 151         let min_listener = &mut (*raw).request_minimize as *mut ffi::wl_listener as *mut WlListener;
 152         (*min_listener).notify = Some(handle_request_minimize);
 153         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_request_minimize_signal(wlr_toplevel), &mut (*raw).request_minimize);
 154 
 155         let move_listener = &mut (*raw).request_move as *mut ffi::wl_listener as *mut WlListener;
 156         (*move_listener).notify = Some(handle_request_move);
 157         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_request_move_signal(wlr_toplevel), &mut (*raw).request_move);
 158 
 159         let resize_listener = &mut (*raw).request_resize as *mut ffi::wl_listener as *mut WlListener;
 160         (*resize_listener).notify = Some(handle_request_resize);
 161         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_request_resize_signal(wlr_toplevel), &mut (*raw).request_resize);
 162 
 163         let parent_listener = &mut (*raw).set_parent as *mut ffi::wl_listener as *mut WlListener;
 164         (*parent_listener).notify = Some(handle_set_parent);
 165         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_set_parent_signal(wlr_toplevel), &mut (*raw).set_parent);
 166 
 167         let title_listener = &mut (*raw).set_title as *mut ffi::wl_listener as *mut WlListener;
 168         (*title_listener).notify = Some(handle_set_title);
 169         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_set_title_signal(wlr_toplevel), &mut (*raw).set_title);
 170 
 171         let app_listener = &mut (*raw).set_app_id as *mut ffi::wl_listener as *mut WlListener;
 172         (*app_listener).notify = Some(handle_set_app_id);
 173         wl_signal_add(ffi::river_wlr_xdg_toplevel_get_set_app_id_signal(wlr_toplevel), &mut (*raw).set_app_id);
 174 
 175         Ok(())
 176     }
 177 
 178     pub unsafe fn destroy_popups(&self) {
 179         let base = ffi::river_wlr_xdg_toplevel_get_base(self.wlr_toplevel);
 180         let list_head = ffi::river_wlr_xdg_surface_get_popups(base) as *mut WlList;
 181         let mut curr = (*list_head).next;
 182         while curr != list_head {
 183             let next = (*curr).next;
 184             let popup = crate::container_of!(curr, ffi::wlr_xdg_popup, link);
 185             ffi::wl_resource_destroy((*popup).resource);
 186             curr = next;
 187         }
 188     }
 189 
 190     pub unsafe fn configure(&mut self) -> bool {
 191         match self.configure_state {
 192             ConfigureState::Idle
 193             | ConfigureState::Inflight(..)
 194             | ConfigureState::Acked
 195             | ConfigureState::Committed
 196             | ConfigureState::TimedOut(..)
 197             | ConfigureState::TimedOutAcked => {}
 198         }
 199 
 200         let scheduled = &(*self.window).configure_scheduled;
 201         let sent = &(*self.window).configure_sent;
 202 
 203         if !self.needs_configure() {
 204             match self.configure_state {
 205                 ConfigureState::Idle => return false,
 206                 ConfigureState::TimedOut(serial) => {
 207                     self.configure_state = ConfigureState::Inflight(serial);
 208                     return true;
 209                 }
 210                 ConfigureState::TimedOutAcked => {
 211                     self.configure_state = ConfigureState::Acked;
 212                     return true;
 213                 }
 214                 ConfigureState::Inflight(..) | ConfigureState::Acked | ConfigureState::Committed => {
 215                     return false;
 216                 }
 217             }
 218         }
 219 
 220         // Absorb a size-only ECHO: the scheduled size merely restates what the
 221         // client has already committed (its current geometry) and nothing else
 222         // changed. Sending it anyway hands a self-sizing client a stale size
 223         // one commit later — and when the client's content width flaps (the
 224         // cpu module's text crossing 10%), that stale echo re-triggers a
 225         // resize on both sides and the pair ping-pongs at frame rate (the
 226         // status-bar jitter: ~3300 alternating 95/104 configures in 5min).
 227         // Agree with reality instead and send nothing. A configure whose size
 228         // DIFFERS from the committed geometry — a real compositor-driven
 229         // resize — always goes through.
 230         {
 231             let echo_w = scheduled.width.or(sent.width);
 232             let echo_h = scheduled.height.or(sent.height);
 233             // Bounds are part of the echo, not a separate signal, when they
 234             // merely track the echoed size: the arrange schedules a status
 235             // window's bounds equal to its own box, so a self-resize ALWAYS
 236             // carries a matching bounds delta — requiring bounds equality
 237             // here would keep the absorb permanently disabled for exactly
 238             // the windows that loop. A bounds change that differs from the
 239             // echoed size (a real available-area change) still forces a
 240             // configure.
 241             let bounds_ok = (scheduled.bounds.width == sent.bounds.width
 242                 && scheduled.bounds.height == sent.bounds.height)
 243                 || (echo_w == Some(scheduled.bounds.width as u32)
 244                     && echo_h == Some(scheduled.bounds.height as u32));
 245             let non_size_equal = scheduled.activated == sent.activated
 246                 && scheduled.ssd == sent.ssd
 247                 && scheduled.tiled == sent.tiled
 248                 && scheduled.capabilities == sent.capabilities
 249                 && scheduled.maximized == sent.maximized
 250                 && scheduled.inform_fullscreen == sent.inform_fullscreen
 251                 && scheduled.resizing == sent.resizing;
 252             // Idle AND Committed: after any completed configure round-trip
 253             // the state machine RESTS in Committed (Acked → Committed on
 254             // commit; only the timeout path returns to Idle), so gating on
 255             // Idle alone leaves this absorb dead in steady state — the exact
 256             // moment the echo loop runs. Inflight/Acked stay excluded: a
 257             // real configure is mid-flight and the scheduled size may need
 258             // to supersede it.
 259             let size_is_echo = self.geometry.width > 0
 260                 && self.geometry.height > 0
 261                 && echo_w == Some(self.geometry.width as u32)
 262                 && echo_h == Some(self.geometry.height as u32);
 263             // Timeout recovery states absorb too: a hot echo loop drives the
 264             // machine into TimedOut/TimedOutAcked, and an absorb that disarms
 265             // there switches itself off at exactly the moment it exists for
 266             // (observed live: a title-flapping window module sustained a
 267             // 372↔456 storm at state=TimedOutAcked). Only Inflight/Acked stay
 268             // excluded — a real configure is mid-flight there. Absorbing
 269             // leaves the timeout recovery untouched: a late ack or the next
 270             // commit still walks the state back to Idle.
 271             if size_is_echo
 272                 && non_size_equal
 273                 && bounds_ok
 274                 && !matches!(
 275                     self.configure_state,
 276                     ConfigureState::Inflight(..) | ConfigureState::Acked
 277                 )
 278             {
 279                 let absorbed_bounds = scheduled.bounds;
 280                 (*self.window).configure_sent.width = echo_w;
 281                 (*self.window).configure_sent.height = echo_h;
 282                 (*self.window).configure_sent.bounds = absorbed_bounds;
 283                 (*self.window).configure_scheduled.width = None;
 284                 (*self.window).configure_scheduled.height = None;
 285                 return false;
 286             }
 287             if size_is_echo && log::log_enabled!(log::Level::Debug) {
 288                 // The size restates committed geometry yet the absorb
 289                 // declined — name the blocker (the state, or which non-size
 290                 // field), so a live echo loop is diagnosable from the log.
 291                 log::debug!(
 292                     "XdgToplevel::configure: echo NOT absorbed: state={:?} non_size_equal={} \
 293                      (bounds {}x{}/{}x{} act {}/{} ssd {}/{} tiled {:?}/{:?} caps {:?}/{:?} max {}/{} fs {}/{} rsz {}/{})",
 294                     self.configure_state, non_size_equal,
 295                     scheduled.bounds.width, scheduled.bounds.height, sent.bounds.width, sent.bounds.height,
 296                     scheduled.activated, sent.activated,
 297                     scheduled.ssd, sent.ssd,
 298                     scheduled.tiled, sent.tiled,
 299                     scheduled.capabilities, sent.capabilities,
 300                     scheduled.maximized, sent.maximized,
 301                     scheduled.inform_fullscreen, sent.inform_fullscreen,
 302                     scheduled.resizing, sent.resizing,
 303                 );
 304             }
 305         }
 306 
 307         ffi::wlr_xdg_toplevel_set_activated(self.wlr_toplevel, scheduled.activated);
 308         ffi::wlr_xdg_toplevel_set_tiled(
 309             self.wlr_toplevel,
 310             scheduled.tiled,
 311         );
 312         ffi::wlr_xdg_toplevel_set_wm_capabilities(
 313             self.wlr_toplevel,
 314             scheduled.capabilities,
 315         );
 316         ffi::wlr_xdg_toplevel_set_maximized(self.wlr_toplevel, scheduled.maximized);
 317         ffi::wlr_xdg_toplevel_set_fullscreen(self.wlr_toplevel, scheduled.inform_fullscreen);
 318         ffi::wlr_xdg_toplevel_set_resizing(self.wlr_toplevel, scheduled.resizing);
 319         
 320         if !self.decoration.is_null() {
 321             let mode = ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
 322             ffi::wlr_xdg_toplevel_decoration_v1_set_mode((*self.decoration).wlr_decoration, mode);
 323         }
 324 
 325         if scheduled.bounds.width != sent.bounds.width || scheduled.bounds.height != sent.bounds.height {
 326             ffi::wlr_xdg_toplevel_set_bounds(self.wlr_toplevel, scheduled.bounds.width as i32, scheduled.bounds.height as i32);
 327         }
 328 
 329         let width = if let Some(w) = scheduled.width {
 330             w
 331         } else if let Some(w) = (*self.window).configure_sent.width {
 332             w
 333         } else {
 334             self.geometry.width as u32
 335         };
 336 
 337         let height = if let Some(h) = scheduled.height {
 338             h
 339         } else if let Some(h) = (*self.window).configure_sent.height {
 340             h
 341         } else {
 342             self.geometry.height as u32
 343         };
 344 
 345         if log::log_enabled!(log::Level::Debug) {
 346             log::debug!(
 347                 "XdgToplevel::configure: sending size {}x{} (scheduled={:?}, sent={:?}, geometry={:?}) to client '{}'",
 348                 width, height, scheduled.width, sent.width, (self.geometry.width, self.geometry.height), (*self.window).get_title_string().unwrap_or_else(|| "None".to_string())
 349             );
 350         }
 351 
 352         let configure_serial = ffi::wlr_xdg_toplevel_set_size(self.wlr_toplevel, width as i32, height as i32);
 353 
 354         (*self.window).configure_sent = (*self.window).configure_scheduled.clone();
 355         (*self.window).configure_sent.width = Some(width);
 356         (*self.window).configure_sent.height = Some(height);
 357         (*self.window).configure_scheduled.width = None;
 358         (*self.window).configure_scheduled.height = None;
 359 
 360         if width != 0 && height != 0 &&
 361            width == self.geometry.width as u32 && height == self.geometry.height as u32 &&
 362            matches!(self.configure_state, ConfigureState::Idle) {
 363             return false;
 364         }
 365 
 366         self.configure_state = ConfigureState::Inflight(configure_serial);
 367         true
 368     }
 369 
 370     pub unsafe fn needs_configure(&self) -> bool {
 371         let scheduled = &(*self.window).configure_scheduled;
 372         let sent = &(*self.window).configure_sent;
 373 
 374         if scheduled.width.is_some() && scheduled.width != sent.width {
 375             return true;
 376         }
 377         if scheduled.height.is_some() && scheduled.height != sent.height {
 378             return true;
 379         }
 380         if scheduled.bounds.width != sent.bounds.width || scheduled.bounds.height != sent.bounds.height {
 381             return true;
 382         }
 383         if scheduled.activated != sent.activated {
 384             return true;
 385         }
 386         if scheduled.ssd != sent.ssd {
 387             return true;
 388         }
 389         if scheduled.tiled != sent.tiled {
 390             return true;
 391         }
 392         if scheduled.capabilities != sent.capabilities {
 393             return true;
 394         }
 395         if scheduled.maximized != sent.maximized {
 396             return true;
 397         }
 398         if scheduled.inform_fullscreen != sent.inform_fullscreen {
 399             return true;
 400         }
 401         if scheduled.resizing != sent.resizing {
 402             return true;
 403         }
 404 
 405         false
 406     }
 407 }
 408 
 409 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
 410     let toplevel = crate::container_of!(listener, XdgToplevel, destroy);
 411 
 412     if !(*toplevel).decoration.is_null() {
 413         XdgDecoration::deinit((*toplevel).decoration);
 414     }
 415 
 416     wl_listener_remove(&mut (*toplevel).destroy);
 417     wl_listener_remove(&mut (*toplevel).ack_configure);
 418     wl_listener_remove(&mut (*toplevel).map);
 419     wl_listener_remove(&mut (*toplevel).unmap);
 420     wl_listener_remove(&mut (*toplevel).commit);
 421     wl_listener_remove(&mut (*toplevel).new_popup);
 422     wl_listener_remove(&mut (*toplevel).request_show_window_menu);
 423     wl_listener_remove(&mut (*toplevel).request_fullscreen);
 424     wl_listener_remove(&mut (*toplevel).request_maximize);
 425     wl_listener_remove(&mut (*toplevel).request_minimize);
 426     wl_listener_remove(&mut (*toplevel).request_move);
 427     wl_listener_remove(&mut (*toplevel).request_resize);
 428     wl_listener_remove(&mut (*toplevel).set_parent);
 429     wl_listener_remove(&mut (*toplevel).set_title);
 430     wl_listener_remove(&mut (*toplevel).set_app_id);
 431 
 432     let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
 433     ffi::river_wlr_xdg_surface_set_data(base, std::ptr::null_mut());
 434     let surface = ffi::river_wlr_xdg_surface_get_surface(base);
 435     ffi::river_wlr_surface_set_data(surface, std::ptr::null_mut());
 436 
 437     let window = (*toplevel).window;
 438     (*window).impl_destroying();
 439     match (*window).state {
 440         crate::window::WindowState::Init | crate::window::WindowState::Closing => {}
 441         crate::window::WindowState::Ready | crate::window::WindowState::Initialized | crate::window::WindowState::Mapped => {
 442             (*window).set_closing();
 443             (*(*window).server).wm.dirty_windowing();
 444         }
 445     }
 446 
 447     let _ = Box::from_raw(toplevel);
 448 }
 449 
 450 unsafe extern "C" fn handle_unmap(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
 451     let toplevel = crate::container_of!(listener, XdgToplevel, unmap);
 452     (*(*toplevel).window).unmap();
 453 }
 454 
 455 unsafe extern "C" fn handle_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
 456     let toplevel = crate::container_of!(listener, XdgToplevel, map);
 457     if let Err(e) = (*(*toplevel).window).map() {
 458         log::error!("Window map failed: {}", e);
 459         let client = ffi::wl_resource_get_client((*(*toplevel).wlr_toplevel).resource);
 460         ffi::wl_client_post_no_memory(client);
 461         return;
 462     }
 463 
 464     let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
 465     let mut new_geometry = std::mem::zeroed();
 466     ffi::river_wlr_xdg_surface_get_geometry(base, &mut new_geometry);
 467     (*toplevel).geometry = new_geometry;
 468 
 469     // A fullscreen state the client set before its first commit never raises
 470     // request_fullscreen (wlroots only records it); honour it now that the
 471     // window is mapped. A fresh floating spawn has no box yet — the arrange
 472     // pass leaves it 0x0 so the acked-commit path adopts the natural size —
 473     // but that next commit will be the fullscreen one, so seed the box with
 474     // the size the window mapped at, or leaving fullscreen restores an
 475     // output-sized window.
 476     if ffi::river_wlr_xdg_toplevel_get_requested_fullscreen((*toplevel).wlr_toplevel) {
 477         let window = (*toplevel).window;
 478         if (*window).box_geom.width <= 0 && (*window).box_geom.height <= 0 && new_geometry.width > 0 && new_geometry.height > 0 {
 479             (*window).box_geom.width = new_geometry.width;
 480             (*window).box_geom.height = new_geometry.height;
 481         }
 482         (*(*window).server).wm.apply_client_fullscreen(window, true);
 483     }
 484     // Status segments and Utility windows are SELF-sizing: their bounds
 485     // track their own box, so the committed geometry is adopted as the box.
 486     let is_self_sized = matches!(
 487         (*(*toplevel).window).tiling_mode,
 488         crate::tiling::TilingMode::Status | crate::tiling::TilingMode::Utility
 489     ) || (*(*toplevel).window).get_app_id_string().map_or(false, |id| id.starts_with("cce-status"));
 490     if is_self_sized {
 491         (*(*toplevel).window).box_geom.width = new_geometry.width;
 492         (*(*toplevel).window).box_geom.height = new_geometry.height;
 493         // A view-centered modal that is ALSO self-sizing was centered at map
 494         // against a size it had not committed yet; redo it now that it has.
 495         (*(*toplevel).window).take_pending_view_center();
 496         (*(*(*toplevel).window).server).wm.dirty_windowing();
 497     } else if (*(*toplevel).window).pending_view_center
 498         && new_geometry.width > 0
 499         && new_geometry.height > 0
 500     {
 501         // A view-centered FLOATING window whose first size just arrived (the
 502         // file chooser): adopt the geometry and redo the centering, or the
 503         // map-time 400x400-floor placement stands for a 900x500 dialog.
 504         (*(*toplevel).window).box_geom.width = new_geometry.width;
 505         (*(*toplevel).window).box_geom.height = new_geometry.height;
 506         (*(*toplevel).window).take_pending_view_center();
 507         (*(*(*toplevel).window).server).wm.dirty_windowing();
 508     }
 509 }
 510 
 511 unsafe extern "C" fn handle_new_popup(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
 512     let toplevel = crate::container_of!(listener, XdgToplevel, new_popup);
 513     let wlr_xdg_popup = data as *mut ffi::wlr_xdg_popup;
 514 
 515     let window = (*toplevel).window;
 516     let capture_node = &mut (*(*window).capture_scene).tree as *mut ffi::wlr_scene_tree;
 517     if let Err(e) = crate::xdg_popup::XdgPopup::create(
 518         wlr_xdg_popup,
 519         (*window).popup_tree,
 520         capture_node,
 521     ) {
 522         log::error!("Failed to create popup: {}", e);
 523         ffi::wl_resource_post_no_memory((*wlr_xdg_popup).resource);
 524         return;
 525     }
 526     // Opening a menu changes nothing the reorder pass's order hash can see,
 527     // so it schedules no transaction: without this raise a tiled window's
 528     // menu would stay under the floating plane until some unrelated restack
 529     // came along. The pass re-applies it from then on.
 530     (*(*window).server).wm.raise_focused_popups(window);
 531 }
 532 
 533 unsafe extern "C" fn handle_ack_configure(
 534     listener: *mut ffi::wl_listener,
 535     data: *mut std::ffi::c_void,
 536 ) {
 537     let toplevel = crate::container_of!(listener, XdgToplevel, ack_configure);
 538     let acked_configure = data as *mut ffi::wlr_xdg_surface_configure;
 539     let serial = (*acked_configure).serial;
 540 
 541     // Any ack, matching serial or not, proves the client is answering
 542     // configures: from here its geometry is a response, and `render_start`
 543     // may adopt it again.
 544     (*toplevel).acked_once = true;
 545 
 546     match (*toplevel).configure_state {
 547         ConfigureState::Inflight(s) => {
 548             if serial == s {
 549                 (*toplevel).configure_state = ConfigureState::Acked;
 550             }
 551         }
 552         ConfigureState::TimedOut(s) => {
 553             if serial == s {
 554                 (*toplevel).configure_state = ConfigureState::TimedOutAcked;
 555             }
 556         }
 557         _ => {}
 558     }
 559 
 560     let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
 561     let mut new_geometry = std::mem::zeroed();
 562     ffi::river_wlr_xdg_surface_get_geometry(base, &mut new_geometry);
 563     (*toplevel).geometry = new_geometry;
 564 
 565     // Status segments and Utility windows are SELF-sizing: their bounds
 566     // track their own box, so the committed geometry is adopted as the box.
 567     let is_self_sized = matches!(
 568         (*(*toplevel).window).tiling_mode,
 569         crate::tiling::TilingMode::Status | crate::tiling::TilingMode::Utility
 570     ) || (*(*toplevel).window).get_app_id_string().map_or(false, |id| id.starts_with("cce-status"));
 571     if is_self_sized {
 572         (*(*toplevel).window).box_geom.width = new_geometry.width;
 573         (*(*toplevel).window).box_geom.height = new_geometry.height;
 574         // A view-centered modal that is ALSO self-sizing was centered at map
 575         // against a size it had not committed yet; redo it now that it has.
 576         (*(*toplevel).window).take_pending_view_center();
 577         (*(*(*toplevel).window).server).wm.dirty_windowing();
 578     } else if (*(*toplevel).window).pending_view_center
 579         && new_geometry.width > 0
 580         && new_geometry.height > 0
 581     {
 582         // A view-centered FLOATING window whose first size just arrived (the
 583         // file chooser): adopt the geometry and redo the centering, or the
 584         // map-time 400x400-floor placement stands for a 900x500 dialog.
 585         (*(*toplevel).window).box_geom.width = new_geometry.width;
 586         (*(*toplevel).window).box_geom.height = new_geometry.height;
 587         (*(*toplevel).window).take_pending_view_center();
 588         (*(*(*toplevel).window).server).wm.dirty_windowing();
 589     }
 590 }
 591 
 592 unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
 593     let toplevel = crate::container_of!(listener, XdgToplevel, commit);
 594     let window = (*toplevel).window;
 595     // Grid-patch latch: the first commit after ack_grid_patch carries the
 596     // buffer rendered for that patch — anchor to it from this commit on.
 597     // Latching here (not at ack time) means an in-flight older buffer is
 598     // never shown at the new anchor.
 599     if let Some((serial, patch)) = (*window).grid_patch_acked.take() {
 600         log::info!("[Grid] latched patch #{serial} on commit");
 601         (*window).grid_patch_current = Some(patch);
 602         // The commit swaps the BUFFER in the scene immediately, but the
 603         // anchor (position/scale/box) otherwise waits for the next arrange
 604         // pass — up to a frame of the new buffer drawn at the OLD patch's
 605         // anchor, the occasional one-frame cell flicker at a patch swap.
 606         // Apply the new anchor inline, mirroring the arrange Grid arm
 607         // exactly (virtual_to_screen's truncating cast included); the next
 608         // arrange re-affirms the same values.
 609         {
 610             let wm = &(*(*window).server).wm;
 611             let zoom = crate::policy::background::sanitized_zoom(wm.desk_zoom);
 612             let (mut ox, mut oy) = (0i32, 0i32);
 613             let outputs_list = &(*(*window).server).om.outputs as *const ffi::wl_list
 614                 as *mut WlList;
 615             let mut curr_out = (*outputs_list).next;
 616             while curr_out != outputs_list {
 617                 let output = crate::container_of!(curr_out, crate::output::Output, link);
 618                 if (*output).sent.state == crate::output::OutputStateValue::Enabled {
 619                     let b = (*output).sent.box_layout();
 620                     ox = b.x;
 621                     oy = b.y;
 622                     break;
 623                 }
 624                 curr_out = (*curr_out).next;
 625             }
 626             if patch.scale > 0.0 {
 627                 // Rounded like every other virtual->screen placement (the
 628                 // arrange pass and the fallback lattice); truncation here put
 629                 // the latched patch a pixel off the lattice it replaces.
 630                 let (lcam, _, _) = wm.layout_camera();
 631                 let sx = ox + ((patch.x - lcam.pan_x) * zoom).round() as i32;
 632                 let sy = oy + ((patch.y - lcam.pan_y) * zoom).round() as i32;
 633                 (*window).rendering_requested.x = sx;
 634                 (*window).rendering_requested.y = sy;
 635                 (*window).scale = zoom / patch.scale;
 636                 (*window).box_geom.x = sx;
 637                 (*window).box_geom.y = sy;
 638                 (*window).box_geom.width = (patch.w * patch.scale).round() as i32;
 639                 (*window).box_geom.height = (patch.h * patch.scale).round() as i32;
 640             }
 641         }
 642         // The grid sits in the optimized-blur capture set (backdrop layers):
 643         // new patch content invalidates the shared blurred-backdrop cache,
 644         // which nothing else re-bakes when the latch lands after the
 645         // viewport has settled — translucent windows keep showing a blur of
 646         // the pre-latch desktop.
 647         ffi::river_scene_mark_optimized_blur_dirty((*(*window).server).scene.wlr_scene);
 648         (*(*window).server).wm.dirty_windowing();
 649     }
 650     let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
 651     let old_geometry = (*toplevel).geometry;
 652     let mut new_geometry = std::mem::zeroed();
 653     ffi::river_wlr_xdg_surface_get_geometry(base, &mut new_geometry);
 654     (*toplevel).geometry = new_geometry;
 655 
 656     // A commit that changes the window-geometry box or the surface extent
 657     // changes the surface↔frame mapping under a STATIONARY cursor (the scene
 658     // helper re-anchors the subtree by -geometry; a grown buffer adds
 659     // hoverable area): pointer focus and surface-local coords go stale with
 660     // no motion to fix them, and the next click is dispatched against the old
 661     // mapping or dropped — the cce-ui overflow-rim popovers exposed this.
 662     // Deferred to idle: wlroots' own scene commit listeners re-anchor AFTER
 663     // this handler, so an inline refresh would query the stale scene.
 664     {
 665         let surface = ffi::river_wlr_xdg_surface_get_surface(base);
 666         let surf_size = (
 667             ffi::river_wlr_surface_get_width(surface),
 668             ffi::river_wlr_surface_get_height(surface),
 669         );
 670         let mapping_changed = old_geometry.x != new_geometry.x
 671             || old_geometry.y != new_geometry.y
 672             || old_geometry.width != new_geometry.width
 673             || old_geometry.height != new_geometry.height
 674             || surf_size != (*toplevel).last_surface_size;
 675         (*toplevel).last_surface_size = surf_size;
 676         if mapping_changed
 677             && matches!((*window).state, crate::window::WindowState::Mapped)
 678         {
 679             (*(*window).server).input_manager.schedule_pointer_refresh();
 680         }
 681     }
 682 
 683     let app_id = (*window).get_app_id_string().unwrap_or_default();
 684     let mut ignore_transparent = (*(*window).server).wm.layout.window_backdrop_blur_ignore_transparent;
 685     if app_id.starts_with("cce-status") {
 686         ignore_transparent = (*(*window).server).wm.layout.status_backdrop_blur_ignore_transparent;
 687     }
 688     let scale = (*window).scale;
 689     let is_status = (*window).tiling_mode == crate::tiling::TilingMode::Status ||
 690                     app_id.starts_with("cce-status");
 691     let is_decorated = (*(*window).server).wm.is_decorated_app(&app_id);
 692     // Status segments are SELF-sizing (their bounds track their own box), so
 693     // the geometry of the commit being handled is the truth. `rendering_sent`
 694     // is a render-start snapshot that lags a contract commit by a render pass
 695     // — sizing the blur from it left a menu-sized blur ghost hanging below
 696     // the strip until the next commit re-ran this path.
 697     let (actual_w, actual_h) = if is_status && (*toplevel).geometry.width > 0 && (*toplevel).geometry.height > 0 {
 698         ((*toplevel).geometry.width as u32, (*toplevel).geometry.height as u32)
 699     } else {
 700         (
 701             if (*window).rendering_sent.width > 0 { (*window).rendering_sent.width } else { (*toplevel).geometry.width as u32 },
 702             if (*window).rendering_sent.height > 0 { (*window).rendering_sent.height } else { (*toplevel).geometry.height as u32 },
 703         )
 704     };
 705     let geom_w = (actual_w as f64 * scale) as i32;
 706     let geom_h = (actual_h as f64 * scale) as i32;
 707     // Must mirror Window::set_rendering_state's radius exactly: both paths drive the same
 708     // blur node, so if they disagree the corners flip between rounded and square depending
 709     // on which one ran last.
 710     let radius = if (*window).is_fullscreen() {
 711         0
 712     } else if (*window).rendering_requested.circular {
 713         let w = (*window).rendering_sent.width as i32;
 714         let h = (*window).rendering_sent.height as i32;
 715         w.min(h) / 2
 716     } else if is_status {
 717         // Same status exemption as Window::set_rendering_state (part of the
 718         // mirror): status segments draw their own module-box corners.
 719         0
 720     } else if (*window).wm_requested.ssd || is_decorated {
 721         (*(*window).server).wm.layout.root_plate_corner_radius
 722     } else {
 723         0
 724     };
 725     // Same span widening as Window::set_rendering_state (part of the mirror).
 726     let radius = if (*window).rendering_requested.circular {
 727         radius
 728     } else {
 729         crate::window::widen_corner_radius(radius, actual_w as i32, actual_h as i32)
 730     };
 731     // Rounded corners do NOT require live blur: the corner shape is applied by the
 732     // standard blur node's sampler (wlr_scene_blur_set_corner_radius) in both modes;
 733     // the optimized node only re-bakes the shared offscreen cache
 734     // (fx_render_pass_add_optimized_blur -> read_to_buffer) and never paints on
 735     // screen. The old `radius > 0` opt-out silently disabled the optimization for
 736     // every (rounded) window, forcing full-backdrop dual-kawase blur per frame per
 737     // translucent window — the DE-wide hover-lag / constant-GPU-load root cause.
 738     let use_optimized = if is_status {
 739         false
 740     } else {
 741         (*(*window).server).wm.layout.scenefx_optimized_blur
 742     };
 743     let blur_enabled = (*window).rendering_requested.blur && ((*window).wm_requested.ssd || is_decorated || is_status);
 744     ffi::river_scene_node_enable_blur(
 745         (*window).tree as *mut ffi::wlr_scene_node,
 746         blur_enabled,
 747         use_optimized,
 748         ignore_transparent,
 749         0,
 750         0,
 751         geom_w,
 752         geom_h,
 753         // geom_w/h are already scaled to device px; the radius must match.
 754         (radius as f64 * scale) as i32,
 755     );
 756 
 757     let capture_node = &mut (*(*window).capture_scene).tree as *mut ffi::wlr_scene_tree as *mut ffi::wlr_scene_node;
 758     let mut geom = std::mem::zeroed();
 759     ffi::river_wlr_xdg_surface_get_geometry(base, &mut geom);
 760     ffi::wlr_scene_subsurface_tree_set_clip(capture_node, &geom);
 761 
 762     let mut min_w = 0;
 763     let mut min_h = 0;
 764     let mut max_w = 0;
 765     let mut max_h = 0;
 766     ffi::river_wlr_xdg_toplevel_get_requested_min_max_size((*toplevel).wlr_toplevel, &mut min_w, &mut min_h, &mut max_w, &mut max_h);
 767 
 768     (*window).set_dimensions_hint(crate::window::DimensionsHint {
 769         min_width: min_w as u32,
 770         min_height: min_h as u32,
 771         max_width: max_w as u32,
 772         max_height: max_h as u32,
 773     });
 774 
 775     if ffi::river_wlr_xdg_surface_get_initial_commit(base) {
 776         assert!((*window).state != crate::window::WindowState::Ready);
 777         if (*window).get_app_id_string().map_or(false, |id| id.starts_with("cce-status")) {
 778             log::debug!("[LinkDbg] initial commit -> ready app={:?} was_state={:?} linked={}",
 779                 (*window).get_app_id_string(), (*window).state, (*window).is_linked());
 780         }
 781         (*window).state = crate::window::WindowState::Ready;
 782         let mut new_geometry = std::mem::zeroed();
 783         ffi::river_wlr_xdg_surface_get_geometry(base, &mut new_geometry);
 784         (*toplevel).geometry = new_geometry;
 785 
 786         let is_self_sized = matches!(
 787             (*window).tiling_mode,
 788             crate::tiling::TilingMode::Status | crate::tiling::TilingMode::Utility
 789         ) || (*window).get_app_id_string().map_or(false, |id| id.starts_with("cce-status"));
 790         if is_self_sized {
 791             (*window).box_geom.width = new_geometry.width;
 792             (*window).box_geom.height = new_geometry.height;
 793         }
 794 
 795         (*(*window).server).wm.dirty_windowing();
 796         return;
 797     }
 798 
 799     if (*window).state != crate::window::WindowState::Mapped && (*window).state != crate::window::WindowState::Ready {
 800         return;
 801     }
 802 
 803     // A self-sizing overlay (cce-cloud) repaints at a new size on its own, with no
 804     // configure round trip. The size-change branches below can't catch it: they
 805     // compare against (*toplevel).geometry, which already holds the new value by
 806     // the time they run, so size_changed is never true. Track the live geometry
 807     // here instead and move the border with it, in the same commit that puts the
 808     // new buffer on screen — waiting for the WM cycle (which round-trips out to
 809     // the external window-manager client) leaves the border a size behind.
 810     // Utility windows self-size the same way (the arrange pass only ever
 811     // sends them the "you choose" 0x0, so every size change originates in a
 812     // client commit like this one).
 813     if matches!(
 814         (*window).tiling_mode,
 815         crate::tiling::TilingMode::Overlay | crate::tiling::TilingMode::Utility
 816     ) {
 817         let mut live = std::mem::zeroed();
 818         ffi::river_wlr_xdg_surface_get_geometry(base, &mut live);
 819         if live.width > 0 && live.height > 0
 820             && (live.width != (*window).box_geom.width || live.height != (*window).box_geom.height)
 821         {
 822             (*window).box_geom.width = live.width;
 823             (*window).box_geom.height = live.height;
 824             // render_finish would otherwise reset box_geom from the render-start
 825             // snapshot (rendering_sent) and snap the border back to the old size.
 826             (*window).self_resized = true;
 827             (*window).draw_borders();
 828             (*window).set_dimensions(live.width as u32, live.height as u32);
 829             (*(*window).server).wm.dirty_windowing();
 830         }
 831     }
 832 
 833     // Status segments self-size the same way when an in-surface menu grows or
 834     // contracts the surface (no configure round trip). Track the live
 835     // geometry in the same commit: box_geom feeds the expanded-state order
 836     // hash that restacks the segment into the popups layer, so a lagging
 837     // box_geom left the freshly opened menu stacked UNDER its sibling
 838     // segments (their text drew sharp over the menu's blur) for a beat.
 839     // Unlike the overlay branch, no set_dimensions — the WM deliberately
 840     // leaves status segment sizes to the client.
 841     if is_status {
 842         let mut live = std::mem::zeroed();
 843         ffi::river_wlr_xdg_surface_get_geometry(base, &mut live);
 844         if live.width > 0 && live.height > 0
 845             && (live.width != (*window).box_geom.width || live.height != (*window).box_geom.height)
 846         {
 847             (*window).box_geom.width = live.width;
 848             (*window).box_geom.height = live.height;
 849             (*window).self_resized = true;
 850             (*(*window).server).wm.dirty_windowing();
 851         }
 852     }
 853 
 854     match (*toplevel).configure_state {
 855         ConfigureState::Idle | ConfigureState::Committed | ConfigureState::TimedOut(..) => {
 856             // Nothing to do: client-initiated size/position changes CANNOT
 857             // be detected here. The top of handle_commit already refreshed
 858             // (*toplevel).geometry from this commit, so any comparison
 859             // against it never fires (the branch that used to live here was
 860             // dead for that reason). Self-sizing overlays are handled by the
 861             // live-geometry sync above; clients resizing through a configure
 862             // round trip land in the Acked arm.
 863         }
 864         ConfigureState::Inflight(..) => {
 865             (*window).send_frame_done();
 866         }
 867         ConfigureState::Acked | ConfigureState::TimedOutAcked => {
 868             let mut new_geometry = std::mem::zeroed();
 869             ffi::river_wlr_xdg_surface_get_geometry(base, &mut new_geometry);
 870             (*toplevel).geometry = new_geometry;
 871 
 872             (*window).rendering_scheduled.width = new_geometry.width as u32;
 873             (*window).rendering_scheduled.height = new_geometry.height as u32;
 874 
 875             let is_status = (*window).tiling_mode == crate::tiling::TilingMode::Status ||
 876                             (*window).get_app_id_string().map_or(false, |id| id.starts_with("cce-status"));
 877             // Overlay included so its scheduled size tracks the client's own; the
 878             // border itself is handled by the live-geometry sync above.
 879             let is_overlay = (*window).tiling_mode == crate::tiling::TilingMode::Overlay;
 880             let is_utility = (*window).tiling_mode == crate::tiling::TilingMode::Utility;
 881             if matches!((*window).tiling_mode, crate::tiling::TilingMode::Floating | crate::tiling::TilingMode::Popup) || is_status || is_overlay || is_utility {
 882                 (*window).set_dimensions(new_geometry.width as u32, new_geometry.height as u32);
 883                 if is_status {
 884                     // Only a size that actually moved re-arranges. A status
 885                     // segment acks a configure and commits at its old size
 886                     // for every content refresh; each of those used to run
 887                     // a full manage/arrange/render transaction.
 888                     let (w, h) = (new_geometry.width, new_geometry.height);
 889                     if w != (*window).box_geom.width || h != (*window).box_geom.height {
 890                         (*window).box_geom.width = w;
 891                         (*window).box_geom.height = h;
 892                         (*(*window).server).wm.dirty_windowing();
 893                     }
 894                 }
 895             }
 896 
 897             let (dec_w, dec_h) = (*window).get_decorations_size();
 898             if dec_w != (*window).last_decor_w || dec_h != (*window).last_decor_h {
 899                 (*window).last_decor_w = dec_w;
 900                 (*window).last_decor_h = dec_h;
 901                 (*(*window).server).wm.dirty_windowing();
 902             }
 903 
 904             if let Some(sent_w) = (*window).configure_sent.width {
 905                 if !(*window).wm_requested.ssd && dec_w > 0 && new_geometry.width as u32 == sent_w.saturating_sub(dec_w as u32) {
 906                     if !(*window).csd_buffer_size_bug {
 907                         (*window).csd_buffer_size_bug = true;
 908                         log::info!("Detected CSD buffer size bug for window '{}'. Activating workaround.", (*window).get_title_string().unwrap_or_default());
 909                         (*(*window).server).wm.dirty_windowing();
 910                     }
 911                 }
 912             }
 913 
 914             match (*toplevel).configure_state {
 915                 ConfigureState::Acked => {
 916                     (*toplevel).configure_state = ConfigureState::Committed;
 917                     (*(*window).server).wm.notify_configured();
 918                 }
 919                 ConfigureState::TimedOutAcked => {
 920                     (*toplevel).configure_state = ConfigureState::Idle;
 921                     (*(*window).server).wm.dirty_rendering();
 922                 }
 923                 _ => unreachable!(),
 924             }
 925         }
 926     }
 927 
 928     // Left/top-edge anchoring on the committed geometry — the shared
 929     // Window::anchor_resize_commit; Xwayland windows take the same path from
 930     // handle_window_commit.
 931     let geometry = (*toplevel).geometry;
 932     if let Some((final_x, final_y)) = (*window).anchor_resize_commit(geometry.width, geometry.height) {
 933         let server = (*window).server;
 934         // Keep the displayed buffer and the compensating position atomic.
 935         // Live buffer (no configure in flight): the commit is already on
 936         // screen, so move the scene tree in the same commit — waiting for
 937         // the next render pass lets a frame composite the new size at the
 938         // old position, jittering the anchored edges. Frozen buffer (saved
 939         // for an in-flight configure): moving the tree now would shift the
 940         // OLD-size buffer instead, so leave the position to the render pass
 941         // (which restores the new buffer and applies it together) and make
 942         // sure that pass runs promptly.
 943         if !(*window).surfaces.saved {
 944             ffi::river_scene_node_set_position_if_changed((*window).tree as *mut ffi::wlr_scene_node, final_x, final_y);
 945             ffi::river_scene_node_set_position_if_changed((*window).popup_tree as *mut ffi::wlr_scene_node, final_x, final_y);
 946             (*window).box_geom.width = geometry.width;
 947             (*window).box_geom.height = geometry.height;
 948             (*window).draw_borders();
 949         } else {
 950             (*server).wm.dirty_rendering();
 951         }
 952     }
 953 }
 954 
 955 unsafe extern "C" fn handle_request_show_window_menu(
 956     listener: *mut ffi::wl_listener,
 957     data: *mut std::ffi::c_void,
 958 ) {
 959     let toplevel = crate::container_of!(listener, XdgToplevel, request_show_window_menu);
 960     let event = data as *mut ffi::wlr_xdg_toplevel_show_window_menu_event;
 961     let window = (*toplevel).window;
 962 
 963     (*window).wm_scheduled.show_window_menu_requested = Some(crate::window::ShowWindowMenuRequest {
 964         x: (*event).x - (*toplevel).geometry.x,
 965         y: (*event).y - (*toplevel).geometry.y,
 966     });
 967     (*(*window).server).wm.dirty_windowing();
 968 }
 969 
 970 unsafe extern "C" fn handle_request_fullscreen(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
 971     let toplevel = crate::container_of!(listener, XdgToplevel, request_fullscreen);
 972     let window = (*toplevel).window;
 973 
 974     if ffi::river_wlr_xdg_toplevel_get_requested_fullscreen((*toplevel).wlr_toplevel) {
 975         let wlr_output = ffi::river_wlr_xdg_toplevel_get_requested_fullscreen_output((*toplevel).wlr_toplevel);
 976         if !wlr_output.is_null() {
 977             let output = ffi::river_wlr_output_get_data(wlr_output) as *mut crate::output::Output;
 978             (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Fullscreen(output);
 979         } else {
 980             (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Fullscreen(std::ptr::null_mut());
 981         }
 982     } else {
 983         (*window).wm_scheduled.fullscreen_requested = crate::window::FullscreenRequest::Exit;
 984     }
 985     (*(*window).server).wm.dirty_windowing();
 986     let enter = ffi::river_wlr_xdg_toplevel_get_requested_fullscreen((*toplevel).wlr_toplevel);
 987     (*(*window).server).wm.apply_client_fullscreen(window, enter);
 988 }
 989 
 990 unsafe extern "C" fn handle_request_maximize(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
 991     let toplevel = crate::container_of!(listener, XdgToplevel, request_maximize);
 992     let window = (*toplevel).window;
 993 
 994     // A Utility window declared itself content-shaped; a maximize would hand
 995     // sizing back to the compositor. Explicitly ignored, not just unmapped
 996     // from any affordance.
 997     if (*window).tiling_mode == crate::tiling::TilingMode::Utility {
 998         return;
 999     }
1000 
1001     if ffi::river_wlr_xdg_toplevel_get_requested_maximized((*toplevel).wlr_toplevel) {
1002         (*window).tiling_mode = crate::tiling::TilingMode::Tiled;
1003         (*window).mode_locked = true;
1004         (*window).wm_scheduled.maximize_requested = crate::window::MaximizeRequest::Maximize;
1005     } else {
1006         (*window).tiling_mode = crate::tiling::TilingMode::Floating;
1007         (*window).mode_locked = true;
1008         (*window).wm_scheduled.maximize_requested = crate::window::MaximizeRequest::Unmaximize;
1009     }
1010     (*(*window).server).wm.dirty_windowing();
1011 }
1012 
1013 unsafe extern "C" fn handle_request_minimize(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1014     let toplevel = crate::container_of!(listener, XdgToplevel, request_minimize);
1015     let window = (*toplevel).window;
1016 
1017     (*window).wm_scheduled.minimize_requested = true;
1018     (*(*window).server).wm.dirty_windowing();
1019 }
1020 
1021 unsafe extern "C" fn handle_request_move(
1022     listener: *mut ffi::wl_listener,
1023     data: *mut std::ffi::c_void,
1024 ) {
1025     let toplevel = crate::container_of!(listener, XdgToplevel, request_move);
1026     let event = data as *mut ffi::wlr_xdg_toplevel_move_event;
1027     let window = (*toplevel).window;
1028     let seat = ffi::river_wlr_seat_get_data((*(*event).seat).seat) as *mut crate::seat::Seat;
1029 
1030     if ffi::wlr_seat_validate_pointer_grab_serial((*seat).wlr_seat, std::ptr::null_mut(), (*event).serial) {
1031         let initial_mode = (*window).tiling_mode;
1032         if initial_mode != crate::tiling::TilingMode::Floating
1033             && initial_mode != crate::tiling::TilingMode::Popup
1034             && initial_mode != crate::tiling::TilingMode::Fullscreen
1035             && initial_mode != crate::tiling::TilingMode::Overlay
1036             // A move must not cost a window its Utility mode.
1037             && initial_mode != crate::tiling::TilingMode::Utility
1038         {
1039             (*window).tiling_mode = crate::tiling::TilingMode::Floating;
1040             (*window).mode_locked = true;
1041         }
1042 
1043         (*seat).focus(crate::seat::Focus::Window(window));
1044         (*(*window).server).wm.stop_panning_animation();
1045         let cursor = &mut (*seat).cursor;
1046         let cursor_x = (*cursor.wlr_cursor).x;
1047         let cursor_y = (*cursor.wlr_cursor).y;
1048 
1049         (*seat).op = Some(crate::seat::SeatOp {
1050             sent_release: false,
1051             input: crate::seat::SeatOpInput::Pointer,
1052             start_x: cursor_x as i32,
1053             start_y: cursor_y as i32,
1054             x: cursor_x as i32,
1055             y: cursor_y as i32,
1056             window_ptr: window,
1057             op_type: crate::seat::PointerOpType::Move,
1058             start_win_x: (*window).box_geom.x,
1059             start_win_y: (*window).box_geom.y,
1060             start_win_w: (*window).box_geom.width as u32,
1061             start_win_h: (*window).box_geom.height as u32,
1062             start_win_virtual_x: (*window).virtual_x,
1063             start_win_virtual_y: (*window).virtual_y,
1064             start_tiling_mode: (*window).tiling_mode,
1065             start_was_tiled: (*window).tiling_mode == crate::tiling::TilingMode::Tiled,
1066             start_mode_locked: (*window).mode_locked,
1067             start_pan_x: (*(*window).server).wm.desk_pan_x,
1068             start_pan_y: (*(*window).server).wm.desk_pan_y,
1069             started_in_overview: (*(*window).server).wm.mode == crate::window_manager::WindowManagerMode::Overview,
1070         });
1071         cursor.op_start_pointer();
1072         cursor.set_xcursor(b"grab\0".as_ptr() as *const _);
1073 
1074         (*window).wm_scheduled.pointer_move_requested = seat;
1075         (*(*window).server).wm.dirty_windowing();
1076     }
1077 }
1078 
1079 unsafe extern "C" fn handle_request_resize(
1080     listener: *mut ffi::wl_listener,
1081     data: *mut std::ffi::c_void,
1082 ) {
1083     let toplevel = crate::container_of!(listener, XdgToplevel, request_resize);
1084     let event = data as *mut ffi::wlr_xdg_toplevel_resize_event;
1085     let window = (*toplevel).window;
1086 
1087     // Nothing may interactively resize a Utility window — its size is the
1088     // client's `settings()` data, not a drag. Rejected at the request, not
1089     // just left without an affordance.
1090     if (*window).tiling_mode == crate::tiling::TilingMode::Utility {
1091         return;
1092     }
1093 
1094     let seat = ffi::river_wlr_seat_get_data((*(*event).seat).seat) as *mut crate::seat::Seat;
1095 
1096     if ffi::wlr_seat_validate_pointer_grab_serial((*seat).wlr_seat, std::ptr::null_mut(), (*event).serial) {
1097         let initial_mode = (*window).tiling_mode;
1098         if initial_mode != crate::tiling::TilingMode::Floating
1099             && initial_mode != crate::tiling::TilingMode::Popup
1100             && initial_mode != crate::tiling::TilingMode::Fullscreen
1101         {
1102             (*window).tiling_mode = crate::tiling::TilingMode::Floating;
1103             (*window).mode_locked = true;
1104         }
1105 
1106         (*seat).focus(crate::seat::Focus::Window(window));
1107         (*(*window).server).wm.stop_panning_animation();
1108         let cursor = &mut (*seat).cursor;
1109         let cursor_x = (*cursor.wlr_cursor).x;
1110         let cursor_y = (*cursor.wlr_cursor).y;
1111 
1112         let edges = crate::window::Edges::from_u32((*event).edges);
1113         (*seat).op = Some(crate::seat::SeatOp {
1114             sent_release: false,
1115             input: crate::seat::SeatOpInput::Pointer,
1116             start_x: cursor_x as i32,
1117             start_y: cursor_y as i32,
1118             x: cursor_x as i32,
1119             y: cursor_y as i32,
1120             window_ptr: window,
1121             op_type: crate::seat::PointerOpType::Resize {
1122                 edges,
1123             },
1124             start_win_x: (*window).box_geom.x,
1125             start_win_y: (*window).box_geom.y,
1126             start_win_w: (*window).box_geom.width as u32,
1127             start_win_h: (*window).box_geom.height as u32,
1128             start_win_virtual_x: (*window).virtual_x,
1129             start_win_virtual_y: (*window).virtual_y,
1130             start_tiling_mode: (*window).tiling_mode,
1131             start_was_tiled: (*window).tiling_mode == crate::tiling::TilingMode::Tiled,
1132             start_mode_locked: (*window).mode_locked,
1133             start_pan_x: (*(*window).server).wm.desk_pan_x,
1134             start_pan_y: (*(*window).server).wm.desk_pan_y,
1135             started_in_overview: (*(*window).server).wm.mode == crate::window_manager::WindowManagerMode::Overview,
1136         });
1137         cursor.op_start_pointer();
1138         let cursor_name = crate::cursor::get_resize_cursor_name(edges);
1139         cursor.set_xcursor(cursor_name.as_ptr() as *const _);
1140 
1141         (*window).wm_scheduled.pointer_resize_requested = Some(crate::window::PointerResizeRequest {
1142             seat,
1143             edges: (*event).edges,
1144         });
1145         (*(*window).server).wm.dirty_windowing();
1146     }
1147 }
1148 
1149 unsafe extern "C" fn handle_set_parent(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1150     let toplevel = crate::container_of!(listener, XdgToplevel, set_parent);
1151     let window = (*toplevel).window;
1152     (*(*window).server).wm.dirty_windowing();
1153 }
1154 
1155 unsafe extern "C" fn handle_set_title(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1156     let toplevel = crate::container_of!(listener, XdgToplevel, set_title);
1157     let window = (*toplevel).window;
1158     (*window).notify_title();
1159 }
1160 
1161 unsafe extern "C" fn handle_set_app_id(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1162     let toplevel = crate::container_of!(listener, XdgToplevel, set_app_id);
1163     let window = (*toplevel).window;
1164     (*window).notify_app_id();
1165 }
1166 
1167 unsafe extern "C" fn handle_decoration_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1168     let decoration = crate::container_of!(listener, XdgDecoration, destroy);
1169     XdgDecoration::deinit(decoration);
1170 }
1171 
1172 unsafe extern "C" fn handle_decoration_request_mode(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
1173     let decoration = crate::container_of!(listener, XdgDecoration, request_mode);
1174     
1175     let base = ffi::river_wlr_xdg_toplevel_get_base((*(*decoration).wlr_decoration).toplevel);
1176     let toplevel = ffi::river_wlr_xdg_surface_get_data(base) as *mut XdgToplevel;
1177     let window = (*toplevel).window;
1178 
1179     let hint = match (*(*decoration).wlr_decoration).requested_mode {
1180         ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE => {
1181             ffi::zcce_window_v1_decoration_hint_ZCCE_WINDOW_V1_DECORATION_HINT_NO_PREFERENCE
1182         }
1183         ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE => {
1184             ffi::zcce_window_v1_decoration_hint_ZCCE_WINDOW_V1_DECORATION_HINT_PREFERS_CSD
1185         }
1186         ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE => {
1187             ffi::zcce_window_v1_decoration_hint_ZCCE_WINDOW_V1_DECORATION_HINT_PREFERS_SSD
1188         }
1189         _ => ffi::zcce_window_v1_decoration_hint_ZCCE_WINDOW_V1_DECORATION_HINT_NO_PREFERENCE,
1190     };
1191     (*window).set_decoration_hint(hint);
1192 
1193     if ffi::river_wlr_xdg_surface_get_initialized(base) {
1194         let mut mode = (*(*decoration).wlr_decoration).requested_mode;
1195         if mode == ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE {
1196             let server = (*window).server;
1197             let rule_ssd = (*server).wm.get_rule_for_window(window).and_then(|r| r.ssd);
1198             if let Some(true) = rule_ssd {
1199                 mode = ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
1200             } else {
1201                 mode = ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
1202             }
1203         }
1204         ffi::wlr_xdg_toplevel_decoration_v1_set_mode((*decoration).wlr_decoration, mode);
1205         (*window).wm_requested.ssd = mode == ffi::wlr_xdg_toplevel_decoration_v1_mode_WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
1206         (*(*window).server).wm.dirty_windowing();
1207     }
1208 }
1209 
1210 impl XdgDecoration {
1211     pub unsafe fn init(wlr_decoration: *mut ffi::wlr_xdg_toplevel_decoration_v1) -> *mut Self {
1212         let base = ffi::river_wlr_xdg_toplevel_get_base((*wlr_decoration).toplevel);
1213         let toplevel = ffi::river_wlr_xdg_surface_get_data(base) as *mut XdgToplevel;
1214 
1215         let decoration = Box::into_raw(Box::new(XdgDecoration {
1216             wlr_decoration,
1217             destroy: std::mem::zeroed(),
1218             request_mode: std::mem::zeroed(),
1219         }));
1220 
1221         (*toplevel).decoration = decoration;
1222 
1223         let destroy_ptr = &mut (*decoration).destroy as *mut ffi::wl_listener as *mut WlListener;
1224         (*destroy_ptr).notify = Some(handle_decoration_destroy);
1225         wl_signal_add(&mut (*wlr_decoration).events.destroy, &mut (*decoration).destroy);
1226 
1227         let req_mode_ptr = &mut (*decoration).request_mode as *mut ffi::wl_listener as *mut WlListener;
1228         (*req_mode_ptr).notify = Some(handle_decoration_request_mode);
1229         wl_signal_add(&mut (*wlr_decoration).events.request_mode, &mut (*decoration).request_mode);
1230 
1231         if ffi::river_wlr_xdg_surface_get_initialized(base) {
1232             handle_decoration_request_mode(&mut (*decoration).request_mode, std::ptr::null_mut());
1233         }
1234 
1235         decoration
1236     }
1237 
1238     pub unsafe fn deinit(decoration: *mut XdgDecoration) {
1239         let base = ffi::river_wlr_xdg_toplevel_get_base((*(*decoration).wlr_decoration).toplevel);
1240         let toplevel = ffi::river_wlr_xdg_surface_get_data(base) as *mut XdgToplevel;
1241 
1242         wl_listener_remove(&mut (*decoration).destroy);
1243         wl_listener_remove(&mut (*decoration).request_mode);
1244 
1245         assert!(!(*toplevel).decoration.is_null());
1246         (*toplevel).decoration = std::ptr::null_mut();
1247 
1248         let _ = Box::from_raw(decoration);
1249     }
1250 }
1251 
1252