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

src/server/seat.rs (97.9K)

   1 use crate::ffi;
   2 use crate::server::{Server, WlList, WlListener, wl_listener_remove, wl_signal_add};
   3 use crate::cursor::Cursor;
   4 
   5 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
   6 pub enum SeatOpInput {
   7     Pointer,
   8 }
   9 
  10 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
  11 pub enum PointerOpType {
  12     Move,
  13     Resize { edges: crate::window::Edges },
  14 }
  15 
  16 #[derive(Clone, Copy, Debug)]
  17 pub struct SeatOp {
  18     pub sent_release: bool,
  19     pub input: SeatOpInput,
  20     pub start_x: i32,
  21     pub start_y: i32,
  22     pub x: i32,
  23     pub y: i32,
  24     pub window_ptr: *mut crate::window::Window,
  25     pub op_type: PointerOpType,
  26     pub start_win_x: i32,
  27     pub start_win_y: i32,
  28     pub start_win_w: u32,
  29     pub start_win_h: u32,
  30     pub start_win_virtual_x: f64,
  31     pub start_win_virtual_y: f64,
  32     /// Desk pan at op start. The op tracks the cursor in virtual space as
  33     /// `start + cursor_delta/zoom + (pan - start_pan)`: the pan-delta term
  34     /// keeps the dragged window/edge pinned to the cursor when edge auto-pan
  35     /// (or anything else) scrolls the desktop mid-drag.
  36     pub start_pan_x: f64,
  37     pub start_pan_y: f64,
  38     pub start_tiling_mode: crate::tiling::TilingMode,
  39     /// Was the window Tiled when the drag was GRABBED? Every op site un-tiles
  40     /// a tiled window before building this struct (the drag needs it floating
  41     /// to follow the pointer), so `start_tiling_mode` already reads Floating
  42     /// and cannot answer this. A tiled window's move snaps hard to whole
  43     /// squares, so the motion handler has to know.
  44     pub start_was_tiled: bool,
  45     pub start_mode_locked: bool,
  46     pub started_in_overview: bool,
  47 }
  48 
  49 #[derive(Clone, Copy, PartialEq, Debug)]
  50 pub enum Focus {
  51     None,
  52     LayerSurface(*mut ffi::wlr_surface),
  53     Window(*mut crate::window::Window),
  54     LockSurface(*mut crate::lock_manager::LockSurface),
  55     OverrideRedirect(*mut crate::xwayland_override_redirect::XwaylandOverrideRedirect),
  56     ShellSurface(*mut crate::shell_surface::ShellSurface),
  57 }
  58 
  59 impl Focus {
  60     pub unsafe fn surface(&self) -> *mut ffi::wlr_surface {
  61         match *self {
  62             Focus::None => std::ptr::null_mut(),
  63             Focus::LayerSurface(surface) => surface,
  64             Focus::Window(window) => if window.is_null() { std::ptr::null_mut() } else { (*window).root_surface() },
  65             Focus::LockSurface(lock_surface) => if lock_surface.is_null() { std::ptr::null_mut() } else { (*(*lock_surface).wlr_lock_surface).surface },
  66             Focus::OverrideRedirect(or) => if or.is_null() { std::ptr::null_mut() } else { (*(*or).xsurface).surface },
  67             Focus::ShellSurface(shell_surface) => if shell_surface.is_null() { std::ptr::null_mut() } else { (*shell_surface).surface },
  68         }
  69     }
  70 }
  71 
  72 
  73 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  74 pub enum DragState {
  75     None,
  76     Pointer,
  77     Touch,
  78 }
  79 
  80 pub struct Seat {
  81     pub server: *mut Server,
  82     pub wlr_seat: *mut ffi::wlr_seat,
  83     pub cursor: Cursor,
  84     pub focused: Focus,
  85     pub relay: crate::input_relay::InputRelay,
  86     pub layer_shell: crate::layer_shell::LayerShellSeat,
  87     pub xkb_bindings_seat: crate::xkb_bindings::XkbBindingsSeat,
  88     pub xkb_bindings: ffi::wl_list,
  89     pub pointer_bindings: ffi::wl_list,
  90     pub keyboard_groups: ffi::wl_list,
  91     /// A device-less keyboard group, created on demand by
  92     /// [`Seat::ensure_synthetic_keyboard`] when the backend supplies no
  93     /// keyboard at all. Null on any seat that has a real one.
  94     pub synthetic_keyboard: *mut crate::keyboard_group::KeyboardGroup,
  95     pub modifiers_old: u32,
  96     pub op: Option<SeatOp>,
  97     pub op_release: bool,
  98     /// One-shot focus-follow-pan suppression, set around refocuses caused
  99     /// by a window GOING AWAY (close/unmap/minimize): the camera stays
 100     /// where the user left it instead of chasing the fallback focus.
 101     /// Explicit focus changes (clicks, directional focus, the switcher)
 102     /// pan as always. Set-call-clear by the caller so a blocked focus
 103     /// can't leak the flag into a later, legitimate pan.
 104     pub suppress_focus_pan: bool,
 105     /// Overview-move displacement ledger: windows currently displaced out
 106     /// of the way of the active move op, with their pre-displacement
 107     /// virtual positions. While the button is held displacement is
 108     /// PROVISIONAL — every motion re-evaluates each entry at the spot it
 109     /// was pushed FROM, so a drag that moves away releases the window back
 110     /// home. Cleared (finalizing the positions) in `op_end`. Lives on the
 111     /// Seat because `SeatOp` is `Copy`.
 112     pub overview_displaced: Vec<(*mut crate::window::Window, f64, f64)>,
 113     pub wm_sent_x: i32,
 114     pub wm_sent_y: i32,
 115 
 116     pub request_set_cursor: ffi::wl_listener,
 117     /// The Xwayland cursor surface currently being shown at 1/`x11_cursor_scale`,
 118     /// null when the pointer image is anyone else's. See
 119     /// `handle_x11_cursor_commit` for why an X11 cursor needs shrinking at all.
 120     pub x11_cursor_surface: *mut ffi::wlr_surface,
 121     pub x11_cursor_scale: f32,
 122     pub x11_cursor_commit: ffi::wl_listener,
 123     pub x11_cursor_destroy: ffi::wl_listener,
 124     pub request_set_selection: ffi::wl_listener,
 125     pub request_start_drag: ffi::wl_listener,
 126     pub start_drag: ffi::wl_listener,
 127     pub request_set_primary_selection: ffi::wl_listener,
 128 
 129     pub drag: DragState,
 130     pub drag_destroy: ffi::wl_listener,
 131 
 132     pub link: ffi::wl_list,
 133     pub link_sent: ffi::wl_list,
 134     pub object: *mut ffi::wl_resource,
 135     pub destroying: bool,
 136     pub focus_requested: bool,
 137 }
 138 
 139 impl Seat {
 140     pub unsafe fn create(server: *mut Server, name: &str) -> Result<*mut Self, &'static str> {
 141         let name_c = std::ffi::CString::new(name).unwrap();
 142         let wlr_seat = ffi::wlr_seat_create((*server).wl_server, name_c.as_ptr());
 143         if wlr_seat.is_null() {
 144             return Err("Failed to create wlr_seat");
 145         }
 146 
 147         let seat = Box::into_raw(Box::new(Self {
 148             server,
 149             wlr_seat,
 150             cursor: Cursor::default(),
 151             focused: Focus::None,
 152             relay: std::mem::zeroed(),
 153             layer_shell: crate::layer_shell::LayerShellSeat::default(),
 154             xkb_bindings_seat: crate::xkb_bindings::XkbBindingsSeat::default(),
 155             xkb_bindings: std::mem::zeroed(),
 156             pointer_bindings: std::mem::zeroed(),
 157             keyboard_groups: std::mem::zeroed(),
 158             synthetic_keyboard: std::ptr::null_mut(),
 159             modifiers_old: 0,
 160             op: None,
 161             op_release: false,
 162             suppress_focus_pan: false,
 163             overview_displaced: Vec::new(),
 164             wm_sent_x: 0,
 165             wm_sent_y: 0,
 166             request_set_cursor: std::mem::zeroed(),
 167             x11_cursor_surface: std::ptr::null_mut(),
 168             x11_cursor_scale: 1.0,
 169             x11_cursor_commit: std::mem::zeroed(),
 170             x11_cursor_destroy: std::mem::zeroed(),
 171             request_set_selection: std::mem::zeroed(),
 172             request_start_drag: std::mem::zeroed(),
 173             start_drag: std::mem::zeroed(),
 174             request_set_primary_selection: std::mem::zeroed(),
 175             drag: DragState::None,
 176             drag_destroy: std::mem::zeroed(),
 177             link: std::mem::zeroed(),
 178             link_sent: std::mem::zeroed(),
 179             object: std::ptr::null_mut(),
 180             destroying: false,
 181             focus_requested: false,
 182         }));
 183 
 184         ffi::wl_list_init(&mut (*seat).link);
 185         ffi::wl_list_init(&mut (*seat).link_sent);
 186         ffi::wl_list_init(&mut (*seat).xkb_bindings);
 187         ffi::wl_list_init(&mut (*seat).pointer_bindings);
 188         ffi::wl_list_init(&mut (*seat).keyboard_groups);
 189 
 190         // Add to input_manager seats
 191         let seats_list = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut crate::server::WlList;
 192         crate::server::wl_list_insert((*seats_list).prev, &mut (*seat).link as *mut ffi::wl_list as *mut crate::server::WlList);
 193 
 194         ffi::river_wlr_seat_set_data(wlr_seat, seat as *mut _);
 195         (*seat).relay.init(seat);
 196 
 197         // Initialize Cursor
 198         let output_layout = (*server).om.output_layout;
 199         (*seat).cursor.init(seat, output_layout)?;
 200 
 201         // Setup listeners
 202         let set_cursor_ptr = &mut (*seat).request_set_cursor as *mut ffi::wl_listener as *mut WlListener;
 203         (*set_cursor_ptr).notify = Some(handle_request_set_cursor);
 204         wl_signal_add(
 205             ffi::river_wlr_seat_get_request_set_cursor_signal(wlr_seat),
 206             &mut (*seat).request_set_cursor,
 207         );
 208 
 209         let set_sel_ptr = &mut (*seat).request_set_selection as *mut ffi::wl_listener as *mut WlListener;
 210         (*set_sel_ptr).notify = Some(handle_request_set_selection);
 211         wl_signal_add(
 212             ffi::river_wlr_seat_get_request_set_selection_signal(wlr_seat),
 213             &mut (*seat).request_set_selection,
 214         );
 215 
 216         let start_drag_req_ptr = &mut (*seat).request_start_drag as *mut ffi::wl_listener as *mut WlListener;
 217         (*start_drag_req_ptr).notify = Some(handle_request_start_drag);
 218         wl_signal_add(
 219             ffi::river_wlr_seat_get_request_start_drag_signal(wlr_seat),
 220             &mut (*seat).request_start_drag,
 221         );
 222 
 223         let start_drag_ptr = &mut (*seat).start_drag as *mut ffi::wl_listener as *mut WlListener;
 224         (*start_drag_ptr).notify = Some(handle_start_drag);
 225         wl_signal_add(
 226             ffi::river_wlr_seat_get_start_drag_signal(wlr_seat),
 227             &mut (*seat).start_drag,
 228         );
 229 
 230         let set_prim_ptr = &mut (*seat).request_set_primary_selection as *mut ffi::wl_listener as *mut WlListener;
 231         (*set_prim_ptr).notify = Some(handle_request_set_primary_selection);
 232         wl_signal_add(
 233             ffi::river_wlr_seat_get_request_set_primary_selection_signal(wlr_seat),
 234             &mut (*seat).request_set_primary_selection,
 235         );
 236 
 237         (*seat).update_capabilities();
 238 
 239         Ok(seat)
 240     }
 241 
 242     pub unsafe fn destroy(seat: *mut Self) {
 243         (*seat).layer_shell.make_inert();
 244         (*seat).xkb_bindings_seat.make_inert();
 245 
 246         let bindings_head = &mut (*seat).xkb_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
 247         let mut curr = (*bindings_head).next;
 248         while curr != bindings_head {
 249             let next = (*curr).next;
 250             let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
 251             crate::xkb_bindings::XkbBinding::destroy(binding);
 252             curr = next;
 253         }
 254 
 255         let ptr_bindings_head = &mut (*seat).pointer_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
 256         let mut curr_ptr = (*ptr_bindings_head).next;
 257         while curr_ptr != ptr_bindings_head {
 258             let next = (*curr_ptr).next;
 259             let binding = crate::container_of!(curr_ptr, crate::pointer_binding::PointerBinding, link);
 260             crate::pointer_binding::PointerBinding::destroy(binding);
 261             curr_ptr = next;
 262         }
 263 
 264         (*seat).cursor.deinit();
 265 
 266         // The synthetic keyboard has no device to outlive, so nothing else
 267         // will ever drop its reference — and the assert below requires the
 268         // group list to be empty by now.
 269         if !(*seat).synthetic_keyboard.is_null() {
 270             let group = (*seat).synthetic_keyboard;
 271             (*seat).synthetic_keyboard = std::ptr::null_mut();
 272             (*group).unref(&[]);
 273         }
 274 
 275         // Verify keyboard_groups is empty
 276         let groups_head = &mut (*seat).keyboard_groups as *mut ffi::wl_list as *mut crate::server::WlList;
 277         assert_eq!((*groups_head).next, groups_head);
 278 
 279         crate::server::wl_list_remove(&mut (*seat).link as *mut ffi::wl_list as *mut crate::server::WlList);
 280         crate::server::wl_list_remove(&mut (*seat).link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
 281 
 282         (*seat).unwatch_x11_cursor();
 283         wl_listener_remove(&mut (*seat).request_set_cursor);
 284         wl_listener_remove(&mut (*seat).request_set_selection);
 285         wl_listener_remove(&mut (*seat).request_start_drag);
 286         wl_listener_remove(&mut (*seat).start_drag);
 287         wl_listener_remove(&mut (*seat).request_set_primary_selection);
 288 
 289         if (*seat).drag != DragState::None {
 290             wl_listener_remove(&mut (*seat).drag_destroy);
 291         }
 292 
 293         ffi::wlr_seat_destroy((*seat).wlr_seat);
 294         let _boxed = Box::from_raw(seat);
 295     }
 296 
 297     /// Start (or stop) shrinking an X11 client's cursor surface to 1/`scale`.
 298     ///
 299     /// `scale` of 1 — a Wayland client's cursor, or an X11 window exempted
 300     /// from `xwayland_hidpi` — just drops any surface being watched. The
 301     /// commit listener is added BEFORE the caller hands the surface to
 302     /// `wlr_cursor_set_surface`, so it sits ahead of wlroots' own commit
 303     /// listener in the signal and the size is already right when wlroots
 304     /// reads it.
 305     unsafe fn watch_x11_cursor(&mut self, surface: *mut ffi::wlr_surface, scale: f32) {
 306         if scale == 1.0 || surface.is_null() {
 307             self.unwatch_x11_cursor();
 308             return;
 309         }
 310         if self.x11_cursor_surface == surface {
 311             self.x11_cursor_scale = scale;
 312             return;
 313         }
 314         self.unwatch_x11_cursor();
 315         self.x11_cursor_surface = surface;
 316         self.x11_cursor_scale = scale;
 317 
 318         let commit = &mut self.x11_cursor_commit as *mut ffi::wl_listener as *mut WlListener;
 319         (*commit).notify = Some(handle_x11_cursor_commit);
 320         wl_signal_add(
 321             ffi::river_wlr_surface_get_commit_signal(surface),
 322             &mut self.x11_cursor_commit,
 323         );
 324 
 325         let destroy = &mut self.x11_cursor_destroy as *mut ffi::wl_listener as *mut WlListener;
 326         (*destroy).notify = Some(handle_x11_cursor_destroy);
 327         wl_signal_add(
 328             ffi::river_wlr_surface_get_destroy_signal(surface),
 329             &mut self.x11_cursor_destroy,
 330         );
 331 
 332         // Whatever is already committed on the surface is what wlroots reads
 333         // first; the fresh buffer only arrives on the commit that follows.
 334         ffi::river_wlr_surface_scale_logical_size(surface, scale);
 335     }
 336 
 337     unsafe fn unwatch_x11_cursor(&mut self) {
 338         if self.x11_cursor_surface.is_null() {
 339             return;
 340         }
 341         self.x11_cursor_surface = std::ptr::null_mut();
 342         self.x11_cursor_scale = 1.0;
 343         wl_listener_remove(&mut self.x11_cursor_commit);
 344         wl_listener_remove(&mut self.x11_cursor_destroy);
 345     }
 346 
 347     pub unsafe fn attach_device(&mut self, device: *mut crate::input_device::InputDevice) {
 348         (*device).seat = self;
 349         let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
 350         match dev_type {
 351             ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD => {
 352                 let keyboard = (*device).destroy_data as *mut crate::keyboard::Keyboard;
 353                 if !keyboard.is_null() {
 354                     (*keyboard).set_group();
 355                     if !(*keyboard).group.is_null() {
 356                         ffi::wlr_seat_set_keyboard(self.wlr_seat, &mut (*(*keyboard).group).wlr_keyboard);
 357                         let focused_surface = ffi::river_wlr_seat_get_keyboard_focused_surface(self.wlr_seat);
 358                         if !focused_surface.is_null() {
 359                             self.keyboard_notify_enter(focused_surface);
 360                         }
 361                     }
 362                 }
 363             }
 364             ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER => {
 365                 ffi::wlr_cursor_attach_input_device(self.cursor.wlr_cursor, (*device).wlr_device);
 366             }
 367             ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH | ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {
 368                 ffi::wlr_cursor_attach_input_device(self.cursor.wlr_cursor, (*device).wlr_device);
 369                 if !(*device).config.map_to_output.is_null() {
 370                     ffi::wlr_cursor_map_input_to_output(self.cursor.wlr_cursor, (*device).wlr_device, (*device).config.map_to_output);
 371                 }
 372                 ffi::wlr_cursor_map_input_to_region(self.cursor.wlr_cursor, (*device).wlr_device, &mut (*device).config.map_to_rectangle);
 373             }
 374             _ => {}
 375         }
 376         self.update_capabilities();
 377     }
 378 
 379     pub unsafe fn detach_device(&mut self, device: *mut crate::input_device::InputDevice) {
 380         ffi::wlr_cursor_detach_input_device(self.cursor.wlr_cursor, (*device).wlr_device);
 381 
 382         let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
 383         if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
 384             let keyboard = (*device).destroy_data as *mut crate::keyboard::Keyboard;
 385             if !keyboard.is_null() {
 386                 if !(*keyboard).group.is_null() {
 387                     let keys: Vec<u32> = (*keyboard).pressed.iter().cloned().collect();
 388                     crate::server::wl_list_remove(&mut (*keyboard).group_link as *mut ffi::wl_list as *mut crate::server::WlList);
 389                     (*(*keyboard).group).unref(&keys);
 390                     (*keyboard).group = std::ptr::null_mut();
 391                 }
 392             }
 393         }
 394         self.update_capabilities();
 395     }
 396 
 397     pub unsafe fn handle_activity(&mut self) {
 398         (*self.server).idle.on_activity();
 399         let notifier = (*self.server).input_manager.idle_notifier;
 400         if !notifier.is_null() {
 401             ffi::wlr_idle_notifier_v1_notify_activity(notifier, self.wlr_seat);
 402         }
 403     }
 404 
 405     pub unsafe fn update_capabilities(&mut self) {
 406         let caps = ffi::wl_seat_capability_WL_SEAT_CAPABILITY_POINTER
 407             | ffi::wl_seat_capability_WL_SEAT_CAPABILITY_KEYBOARD;
 408         ffi::wlr_seat_set_capabilities(self.wlr_seat, caps);
 409     }
 410 
 411     pub unsafe fn focus(&mut self, new_focus: Focus) {
 412         if let Focus::Window(window) = new_focus {
 413             // The grid is the canvas, not a window: it must never take focus.
 414             // It became clickable when it started advertising an input region
 415             // for its desktop items, and the click path focuses whatever it
 416             // hits — which handed focus to a surface the size of the whole
 417             // patch and then let focus-follow pan the camera to "reveal" it,
 418             // so every press on an item dragged the desktop out from under
 419             // the pointer.
 420             if !window.is_null()
 421                 && ((*window).is_status_bar() || (*window).is_wallpaper() || (*window).is_grid())
 422             {
 423                 log::info!("[FocusDebug] Seat::focus blocking focus to status bar/wallpaper/grid window");
 424                 return;
 425             }
 426             // A shy helper window declines focus by its own hints
 427             // (WM_HINTS input = False); honour that — see `Window::is_shy`.
 428             if !window.is_null() && (*window).is_shy() {
 429                 log::info!("[FocusDebug] Seat::focus blocking focus to a no-activate helper window");
 430                 return;
 431             }
 432         }
 433 
 434         if let Focus::Window(window) = new_focus {
 435             if !window.is_null() && (*window).tiling_mode == crate::tiling::TilingMode::Floating {
 436                 (*self.server).wm.raise_window(window);
 437                 (*self.server).wm.dirty_windowing();
 438             }
 439         }
 440 
 441         if self.focused == new_focus {
 442             // Re-focusing the already-focused window is still intent: a
 443             // click on the sliver of a mostly-hidden focused window (or on
 444             // a clipped one) should bring it over — its popups/menus open
 445             // relative to the window and land off-viewport otherwise. The
 446             // pan no-ops once the window is fully visible.
 447             if let Focus::Window(window) = new_focus {
 448                 self.focus_follow_pan(window);
 449             }
 450             return;
 451         }
 452 
 453         if log::log_enabled!(log::Level::Debug) {
 454             let bt = std::backtrace::Backtrace::capture();
 455             log::debug!("[FocusDebug] Seat::focus changing from {:?} to {:?}. Backtrace:\n{}", self.focused, new_focus, bt);
 456         }
 457 
 458         // If an exclusive layer surface is active and scheduled for focus,
 459         // block any window manager or other client focus requests (via focus_requested)
 460         // from stealing focus back to a regular window or shell surface.
 461         if self.focus_requested {
 462             if let crate::layer_shell::LayerShellSeatFocus::Exclusive(key) = self.layer_shell.scheduled_focus {
 463                 let server = self.server;
 464                 if let Some(&layer_surface) = (*server).layer_shell.surfaces.get(key) {
 465                     let wlr_surf = (*(*layer_surface).wlr_layer_surface).surface;
 466                     if new_focus != Focus::LayerSurface(wlr_surf) {
 467                         if let Focus::Window(_) | Focus::ShellSurface(_) | Focus::OverrideRedirect(_) | Focus::None = new_focus {
 468                             log::info!("[FocusDebug] Blocking window manager focus request because Exclusive layer surface {:?} is active", key);
 469                             return;
 470                         }
 471                     }
 472                 }
 473             }
 474         }
 475 
 476         match new_focus {
 477             Focus::None => log::info!("[FocusDebug] Seat::focus set to None"),
 478             Focus::LayerSurface(surface) => log::info!("[FocusDebug] Seat::focus set to LayerSurface {:?}", surface),
 479             Focus::Window(window) => {
 480                 let title = if window.is_null() { "null".to_string() } else { (*window).get_title_string().unwrap_or_default() };
 481                 let app_id = if window.is_null() { "null".to_string() } else { (*window).get_app_id_string().unwrap_or_default() };
 482                 log::info!("[FocusDebug] Seat::focus set to Window {:?} (title={:?}, app_id={:?})", window, title, app_id);
 483             }
 484             Focus::LockSurface(lock) => log::info!("[FocusDebug] Seat::focus set to LockSurface {:?}", lock),
 485             Focus::OverrideRedirect(or) => log::info!("[FocusDebug] Seat::focus set to OverrideRedirect {:?}", or),
 486             Focus::ShellSurface(ss) => log::info!("[FocusDebug] Seat::focus set to ShellSurface {:?}", ss),
 487         }
 488 
 489         match self.focused {
 490             Focus::None => {}
 491             Focus::LayerSurface(_) | Focus::Window(_) | Focus::LockSurface(_) | Focus::OverrideRedirect(_) | Focus::ShellSurface(_) => {
 492                 ffi::wlr_seat_keyboard_notify_clear_focus(self.wlr_seat);
 493                 let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(self.wlr_seat);
 494                 // Keep pointer focus through an active implicit grab (held
 495                 // client-notified button): clicking a window changes focus,
 496                 // and dropping pointer focus here orphans the grab until the
 497                 // next in-surface motion re-enters — a press-then-leave drag
 498                 // (cursor straight out of the window) lost its target.
 499                 if !focused_client.is_null() && self.cursor.notified_pressed.is_empty() {
 500                     ffi::wlr_seat_pointer_notify_clear_focus(self.wlr_seat);
 501                 }
 502             }
 503         }
 504 
 505         self.focused = new_focus;
 506         // The overview resize ring is drawn on the focused window only and
 507         // eases in and out through the border fade — a focus change has to
 508         // arm that timer or the old ring lingers and the new one waits for an
 509         // unrelated redraw (the same reason WindowManager::set_mode arms it).
 510         (*self.server).wm.arm_border_fade();
 511         if let Focus::Window(window) = new_focus {
 512             if !window.is_null() {
 513                 (*self.server).wm.record_focus(window);
 514             }
 515         }
 516         (*self.server).wm.update_status();
 517 
 518         match new_focus {
 519             Focus::None => {}
 520             Focus::LayerSurface(surface) => {
 521                 if !surface.is_null() {
 522                     let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
 523                     if !kbd.is_null() {
 524                         let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
 525                         ffi::wlr_seat_keyboard_notify_enter(
 526                             self.wlr_seat,
 527                             surface,
 528                             std::ptr::null_mut(),
 529                             0,
 530                             modifiers,
 531                         );
 532                     } else {
 533                         ffi::wlr_seat_keyboard_notify_enter(
 534                             self.wlr_seat,
 535                             surface,
 536                             std::ptr::null_mut(),
 537                             0,
 538                             std::ptr::null_mut(),
 539                         );
 540                     }
 541                 }
 542 
 543                 let lx = self.cursor.x();
 544                 let ly = self.cursor.y();
 545                 let server = self.server;
 546                 if let Some(result) = (*server).scene.at(lx, ly) {
 547                     if result.surface == surface {
 548                         ffi::wlr_seat_pointer_notify_enter(self.wlr_seat, surface, result.sx, result.sy);
 549                     }
 550                 }
 551             }
 552             Focus::Window(window) => {
 553                 let is_new = if !window.is_null() {
 554                     let was_new = (*window).is_new;
 555                     (*window).is_new = false;
 556                     was_new
 557                 } else {
 558                     false
 559                 };
 560 
 561                 // A window newly on screen pulls the viewport over to it only when
 562                 // `window_manager.center_on_spawn` allows it; one coming back from the
 563                 // saved session at startup never did. Reopening an app mid-session is a
 564                 // spawn even though `restored` is set — it only borrowed its old geometry
 565                 // from `last_window_states`. Focus moving between windows that were
 566                 // already up still pans either way; the key is about spawning.
 567                 if !window.is_null() {
 568                     let spawn_pan = !(*window).session_restored
 569                         && !(*window).hint_placed
 570                         && (*self.server).wm.center_on_spawn;
 571                     // A restored window maps unfocused and keeps is_new until
 572                     // its first focus — which, after a session restart, is
 573                     // the user's first CLICK on it. Suppressing that pan made
 574                     // every window seem to ignore focus-follow right after
 575                     // login. Once real input has been seen the settling phase
 576                     // is over: a first focus is user intent and pans like any
 577                     // other, except for placement-hinted spawns (pickers that
 578                     // open at their control and must not yank the camera).
 579                     let user_focus = (*self.server).wm.startup_input_seen && !(*window).hint_placed;
 580                     if !is_new || spawn_pan || user_focus {
 581                         self.focus_follow_pan(window);
 582                     }
 583                 }
 584 
 585 
 586                 // Focus root surface of window
 587                 let surface = (*window).root_surface();
 588                 if !surface.is_null() {
 589                     let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
 590                     if !kbd.is_null() {
 591                         let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
 592                         ffi::wlr_seat_keyboard_notify_enter(
 593                             self.wlr_seat,
 594                             surface,
 595                             std::ptr::null_mut(),
 596                             0,
 597                             modifiers,
 598                         );
 599                     } else {
 600                         ffi::wlr_seat_keyboard_notify_enter(
 601                             self.wlr_seat,
 602                             surface,
 603                             std::ptr::null_mut(),
 604                             0,
 605                             std::ptr::null_mut(),
 606                         );
 607                     }
 608 
 609                     let lx = self.cursor.x();
 610                     let ly = self.cursor.y();
 611                     let server = self.server;
 612                     if let Some(result) = (*server).scene.at(lx, ly) {
 613                         if result.surface == surface {
 614                             ffi::wlr_seat_pointer_notify_enter(self.wlr_seat, surface, result.sx, result.sy);
 615                         }
 616                     }
 617                 }
 618             }
 619             Focus::LockSurface(lock_surface) => {
 620                 let surface = (*(*lock_surface).wlr_lock_surface).surface;
 621                 if !surface.is_null() {
 622                     let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
 623                     if !kbd.is_null() {
 624                         let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
 625                         ffi::wlr_seat_keyboard_notify_enter(
 626                             self.wlr_seat,
 627                             surface,
 628                             std::ptr::null_mut(),
 629                             0,
 630                             modifiers,
 631                         );
 632                     } else {
 633                         ffi::wlr_seat_keyboard_notify_enter(
 634                             self.wlr_seat,
 635                             surface,
 636                             std::ptr::null_mut(),
 637                             0,
 638                             std::ptr::null_mut(),
 639                         );
 640                     }
 641 
 642                     let lx = self.cursor.x();
 643                     let ly = self.cursor.y();
 644                     let server = self.server;
 645                     if let Some(result) = (*server).scene.at(lx, ly) {
 646                         if result.surface == surface {
 647                             ffi::wlr_seat_pointer_notify_enter(self.wlr_seat, surface, result.sx, result.sy);
 648                         }
 649                     }
 650                 }
 651             }
 652             Focus::OverrideRedirect(or) => {
 653                 let surface = (*(*or).xsurface).surface;
 654                 if !surface.is_null() {
 655                     let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
 656                     if !kbd.is_null() {
 657                         let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
 658                         ffi::wlr_seat_keyboard_notify_enter(
 659                             self.wlr_seat,
 660                             surface,
 661                             std::ptr::null_mut(),
 662                             0,
 663                             modifiers,
 664                         );
 665                     } else {
 666                         ffi::wlr_seat_keyboard_notify_enter(
 667                             self.wlr_seat,
 668                             surface,
 669                             std::ptr::null_mut(),
 670                             0,
 671                             std::ptr::null_mut(),
 672                         );
 673                     }
 674                 }
 675             }
 676             Focus::ShellSurface(shell_surface) => {
 677                 let surface = (*shell_surface).surface;
 678                 if !surface.is_null() {
 679                     let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
 680                     if !kbd.is_null() {
 681                         let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
 682                         ffi::wlr_seat_keyboard_notify_enter(
 683                             self.wlr_seat,
 684                             surface,
 685                             std::ptr::null_mut(),
 686                             0,
 687                             modifiers,
 688                         );
 689                     } else {
 690                         ffi::wlr_seat_keyboard_notify_enter(
 691                             self.wlr_seat,
 692                             surface,
 693                             std::ptr::null_mut(),
 694                             0,
 695                             std::ptr::null_mut(),
 696                         );
 697                     }
 698                 }
 699             }
 700         }
 701         let target_surface = new_focus.surface();
 702         self.relay.focus(target_surface);
 703     }
 704 
 705     /// Give this seat a keyboard if the backend never supplied one, so that
 706     /// synthetic keys have somewhere to land. Returns false only when no
 707     /// keymap is configured, leaving nothing worth attaching.
 708     ///
 709     /// The seat advertises `WL_SEAT_CAPABILITY_KEYBOARD` unconditionally (see
 710     /// `update_capabilities`), so a client always binds `wl_keyboard`. But the
 711     /// keymap reaches that client from `wlr_seat_set_keyboard`, which only ever
 712     /// ran from `attach_device` — i.e. only once a real keyboard device
 713     /// existed. On the headless backend there is no keyboard device, so no
 714     /// keymap was ever sent, and a client with no keymap cannot turn a keycode
 715     /// into a keysym: `wlr_seat_keyboard_notify_key` delivered events that were
 716     /// silently dropped. That is why injected keys did nothing in a shadow
 717     /// session while injected pointer events worked.
 718     ///
 719     /// A real keyboard always wins — this is a no-op the moment the seat has
 720     /// one, so a normal session never reaches the creation path.
 721     pub unsafe fn ensure_synthetic_keyboard(&mut self) -> bool {
 722         if !ffi::river_wlr_seat_get_keyboard(self.wlr_seat).is_null() {
 723             return true;
 724         }
 725 
 726         let keymap = (*self.server).xkb_config.default_keymap;
 727         if keymap.is_null() {
 728             log::warn!("[seat] no keymap configured; synthetic keys cannot be delivered");
 729             return false;
 730         }
 731 
 732         // `KeyboardGroup::create` takes its own keymap reference and does the
 733         // `wlr_keyboard_init`/`set_keymap` wiring, so this borrows the group
 734         // machinery whole rather than hand-rolling a bare wlr_keyboard — which
 735         // would also leave `river_wlr_keyboard_get_data` null and cost
 736         // `keyboard_notify_enter` its pressed-key tracking.
 737         let config = crate::keyboard::KeyboardConfig {
 738             keymap,
 739             repeat_rate: 40,
 740             repeat_delay: 400,
 741         };
 742         match crate::keyboard_group::KeyboardGroup::create(self, config, true) {
 743             Ok(group) => {
 744                 self.synthetic_keyboard = group;
 745                 // set_keyboard is what pushes the keymap out to every bound
 746                 // client; the enter re-announces focus with it in place.
 747                 ffi::wlr_seat_set_keyboard(self.wlr_seat, &mut (*group).wlr_keyboard);
 748                 let focused = ffi::river_wlr_seat_get_keyboard_focused_surface(self.wlr_seat);
 749                 if !focused.is_null() {
 750                     self.keyboard_notify_enter(focused);
 751                 }
 752                 log::info!("[seat] no keyboard device on this backend — created a synthetic one so injected keys reach clients");
 753                 true
 754             }
 755             Err(err) => {
 756                 log::error!("[seat] failed to create synthetic keyboard: {}", err);
 757                 false
 758             }
 759         }
 760     }
 761 
 762     pub unsafe fn keyboard_notify_enter(&mut self, wlr_surface: *mut ffi::wlr_surface) {
 763         if wlr_surface.is_null() {
 764             return;
 765         }
 766         let kbd = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
 767         if !kbd.is_null() {
 768             let group_ptr = ffi::river_wlr_keyboard_get_data(kbd) as *mut crate::keyboard_group::KeyboardGroup;
 769             if !group_ptr.is_null() {
 770                 // Raw evdev keycodes, NOT xkb ones: `wl_keyboard.enter`'s key
 771                 // array is the same space as `wl_keyboard.key`, and the client
 772                 // is the one that adds 8 to reach an xkb keycode. Adding it
 773                 // here shifted every held key up by 8 on the way out — and an
 774                 // X11 popup that opens under a held key is exactly when this
 775                 // array is sent, so opening Houdini's TAB menu (evdev 15)
 776                 // handed Xwayland keycode 31 and typed an `i` into it, with no
 777                 // release to follow, so X autorepeated it.
 778                 let mut buffer = [0u32; 32];
 779                 let mut count = 0;
 780                 for &keycode in (*group_ptr).pressed.keys() {
 781                     if count >= 32 {
 782                         break;
 783                     }
 784                     buffer[count] = keycode;
 785                     count += 1;
 786                 }
 787                 let modifiers = ffi::river_wlr_keyboard_get_modifiers(kbd);
 788                 ffi::wlr_seat_keyboard_notify_enter(
 789                     self.wlr_seat,
 790                     wlr_surface,
 791                     buffer.as_mut_ptr(),
 792                     count,
 793                     modifiers,
 794                 );
 795                 return;
 796             }
 797         }
 798         ffi::wlr_seat_keyboard_notify_enter(
 799             self.wlr_seat,
 800             wlr_surface,
 801             std::ptr::null_mut(),
 802             0,
 803             std::ptr::null_mut(),
 804         );
 805     }
 806 
 807     pub unsafe fn keyboard_enter_or_leave(&mut self, target_surface: *mut ffi::wlr_surface) {
 808         if !target_surface.is_null() {
 809             self.keyboard_notify_enter(target_surface);
 810         } else {
 811             ffi::wlr_seat_keyboard_notify_clear_focus(self.wlr_seat);
 812         }
 813         self.relay.focus(target_surface);
 814     }
 815 
 816     pub unsafe fn manage_start(&mut self) {
 817         if self.destroying {
 818             Self::destroy(self);
 819             return;
 820         }
 821 
 822         self.focus_requested = false;
 823         self.layer_shell.manage_start();
 824 
 825         let wm_v1 = (*self.server).wm.object;
 826         if !wm_v1.is_null() {
 827             let new = self.object.is_null();
 828             if new {
 829                 let client = ffi::wl_resource_get_client(wm_v1);
 830                 let version = ffi::wl_resource_get_version(wm_v1);
 831                 let seat_v1 = ffi::wl_resource_create(client, &ffi::zcce_seat_v1_interface, version, 0);
 832                 if seat_v1.is_null() {
 833                     log::error!("out of memory creating zcce_seat_v1");
 834                     return;
 835                 }
 836                 self.object = seat_v1;
 837                 
 838                 ffi::wl_resource_set_implementation(
 839                     seat_v1,
 840                     &SEAT_INTERFACE as *const _ as *const _,
 841                     self as *mut Seat as *mut _,
 842                     Some(handle_destroy_resource),
 843                 );
 844                 
 845                 ffi::wl_resource_post_event(wm_v1, ffi::ZCCE_WINDOW_MANAGER_V1_SEAT, seat_v1); // zcce_window_manager_v1.seat
 846 
 847                 crate::server::wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
 848                 let sent_seats = &mut (*self.server).wm.sent.seats as *mut ffi::wl_list as *mut crate::server::WlList;
 849                 crate::server::wl_list_insert((*sent_seats).prev, &mut self.link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
 850             }
 851 
 852             if new {
 853                 let seat_v1 = self.object;
 854                 let client = ffi::wl_resource_get_client(seat_v1);
 855                 let wl_seat_name = ffi::wl_global_get_name(ffi::river_wlr_seat_get_global(self.wlr_seat), client);
 856                 ffi::wl_resource_post_event(seat_v1, 1, wl_seat_name); // river_seat_v1.wl_seat
 857             }
 858 
 859             self.xkb_bindings_seat.manage_start();
 860 
 861             // Dispatch xkb binding events
 862             let bindings_head = &mut self.xkb_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
 863             let mut curr = (*bindings_head).next;
 864             while curr != bindings_head {
 865                 let next = (*curr).next;
 866                 let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
 867                 for state in (*binding).wm_scheduled.state_changes.drain(..) {
 868                     match state {
 869                         crate::xkb_bindings::XkbBindingStateChange::None => {},
 870                         crate::xkb_bindings::XkbBindingStateChange::Pressed => {
 871                             if !(*binding).sent_pressed {
 872                                 (*binding).sent_pressed = true;
 873                                 ffi::wl_resource_post_event((*binding).object, 0);
 874                             }
 875                         },
 876                         crate::xkb_bindings::XkbBindingStateChange::StopRepeat => {
 877                             if (*binding).sent_pressed {
 878                                 if ffi::wl_resource_get_version((*binding).object) >= 2 {
 879                                     ffi::wl_resource_post_event((*binding).object, 2);
 880                                 }
 881                             }
 882                         },
 883                         crate::xkb_bindings::XkbBindingStateChange::Released => {
 884                             if (*binding).sent_pressed {
 885                                 (*binding).sent_pressed = false;
 886                                 ffi::wl_resource_post_event((*binding).object, 1);
 887                             }
 888                         },
 889                     }
 890                 }
 891                 curr = next;
 892             }
 893 
 894             // Dispatch pointer binding events
 895             let ptr_bindings_head = &mut self.pointer_bindings as *mut ffi::wl_list as *mut crate::server::WlList;
 896             let mut curr_ptr = (*ptr_bindings_head).next;
 897             while curr_ptr != ptr_bindings_head {
 898                 let next = (*curr_ptr).next;
 899                 let binding = crate::container_of!(curr_ptr, crate::pointer_binding::PointerBinding, link);
 900                 for state in (*binding).wm_scheduled.state_changes.drain(..) {
 901                     match state {
 902                         crate::pointer_binding::PointerBindingStateChange::None => {}
 903                         crate::pointer_binding::PointerBindingStateChange::Pressed => {
 904                             if !(*binding).sent_pressed {
 905                                 (*binding).sent_pressed = true;
 906                                 ffi::wl_resource_post_event((*binding).object, 0); // pressed
 907                             }
 908                         }
 909                         crate::pointer_binding::PointerBindingStateChange::Released => {
 910                             if (*binding).sent_pressed {
 911                                 (*binding).sent_pressed = false;
 912                                 ffi::wl_resource_post_event((*binding).object, 1); // released
 913                             }
 914                         }
 915                     }
 916                 }
 917                 curr_ptr = next;
 918             }
 919 
 920             // Dispatch pointer operation events
 921             if let Some(ref mut op) = self.op {
 922                 let dx = op.x - op.start_x;
 923                 let dy = op.y - op.start_y;
 924                 ffi::wl_resource_post_event(self.object, 6, dx, dy); // op_delta
 925 
 926                 if self.op_release && !op.sent_release {
 927                     ffi::wl_resource_post_event(self.object, 7); // op_release
 928                     self.op_release = false;
 929                     op.sent_release = true;
 930                 }
 931             }
 932 
 933             // Dispatch pointer position event
 934             if ffi::wl_resource_get_version(self.object) >= 2 {
 935                 let x = (*self.cursor.wlr_cursor).x as i32;
 936                 let y = (*self.cursor.wlr_cursor).y as i32;
 937                 if x != self.wm_sent_x || y != self.wm_sent_y {
 938                     ffi::wl_resource_post_event(self.object, 8, x, y); // pointer_position
 939                     self.wm_sent_x = x;
 940                     self.wm_sent_y = y;
 941                 }
 942             }
 943         } else {
 944             crate::server::wl_list_remove(&mut self.link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
 945             let sent_seats = &mut (*self.server).wm.sent.seats as *mut ffi::wl_list as *mut crate::server::WlList;
 946             crate::server::wl_list_insert((*sent_seats).prev, &mut self.link_sent as *mut ffi::wl_list as *mut crate::server::WlList);
 947         }
 948     }
 949 
 950     pub unsafe fn manage_finish(&mut self) {
 951         self.xkb_bindings_seat.manage_finish();
 952 
 953         if (*self.server).lock_manager.state != crate::lock_manager::LockState::Unlocked {
 954             return;
 955         }
 956 
 957         match self.layer_shell.sent_focus {
 958             crate::layer_shell::LayerShellSeatFocus::Exclusive(key) => {
 959                 let server = self.server;
 960                 if let Some(&layer_surface) = (*server).layer_shell.surfaces.get(key) {
 961                     let wlr_surf = (*(*layer_surface).wlr_layer_surface).surface;
 962                     self.focus(Focus::LayerSurface(wlr_surf));
 963                 }
 964             }
 965             crate::layer_shell::LayerShellSeatFocus::NonExclusive(key) => {
 966                 if !self.focus_requested {
 967                     let server = self.server;
 968                     if let Some(&layer_surface) = (*server).layer_shell.surfaces.get(key) {
 969                         let wlr_surf = (*(*layer_surface).wlr_layer_surface).surface;
 970                         self.focus(Focus::LayerSurface(wlr_surf));
 971                     }
 972                 } else {
 973                     self.layer_shell.scheduled_focus = crate::layer_shell::LayerShellSeatFocus::None;
 974                     (*self.server).wm.dirty_windowing();
 975                 }
 976             }
 977             crate::layer_shell::LayerShellSeatFocus::None => {}
 978         }
 979     }
 980 
 981 
 982     /// Is this seat's keyboard focus desktop chrome — a Popup/Overlay window
 983     /// (the cce-cloud launcher, a dock) or a cce-cloud layer surface (a
 984     /// context menu)? Chrome stays keyboard-interactive in overview and is
 985     /// dismissed by using it (Escape, a pick, a click-away), so the
 986     /// overview-mode key and hover paths consult this before treating the
 987     /// focus as a world window's: keys are delivered rather than eaten, and
 988     /// hover-to-focus leaves the ring where it is instead of pulling the
 989     /// keyboard out from under the launcher.
 990     pub unsafe fn focus_is_chrome(&self) -> bool {
 991         match self.focused {
 992             Focus::Window(w) if !w.is_null() => matches!(
 993                 (*w).tiling_mode,
 994                 crate::tiling::TilingMode::Popup | crate::tiling::TilingMode::Overlay
 995             ),
 996             Focus::LayerSurface(s) if !s.is_null() => {
 997                 let wlr_layer_surface = ffi::wlr_layer_surface_v1_try_from_wlr_surface(s);
 998                 !wlr_layer_surface.is_null()
 999                     && !(*wlr_layer_surface).namespace.is_null()
1000                     && std::ffi::CStr::from_ptr((*wlr_layer_surface).namespace)
1001                         .to_string_lossy()
1002                         .starts_with("cce-cloud")
1003             }
1004             _ => false,
1005         }
1006     }
1007 
1008     /// Focus-follow: pan the camera to a focused Floating/Maximized window —
1009     /// centering when it is mostly hidden, nudging a clipped edge into view
1010     /// otherwise. Fullscreen is pinned to an output and popups/overlays are
1011     /// not desk citizens, so other modes no-op, as do cce-cloud and windows
1012     /// already fully visible.
1013     pub unsafe fn focus_follow_pan(&mut self, window: *mut crate::window::Window) {
1014         if self.suppress_focus_pan {
1015             return;
1016         }
1017         // While a camera ramp owns the camera (an overview enter/exit
1018         // flight), the current camera is a mid-flight sample — any pan
1019         // target computed from it is stale by construction. Never retarget
1020         // out from under the ramp.
1021         if (*self.server).wm.camera_ramp_anim.is_some() {
1022             return;
1023         }
1024         if window.is_null()
1025             || !matches!(
1026                 (*window).tiling_mode,
1027                 // Utility included: it pans on the virtual surface like any
1028                 // floating window, so focusing one off-view should bring it in.
1029                 crate::tiling::TilingMode::Floating
1030                     | crate::tiling::TilingMode::Tiled
1031                     | crate::tiling::TilingMode::Utility
1032             )
1033         {
1034             return;
1035         }
1036         let app_id = (*window).get_app_id_string();
1037         if app_id.as_ref().map(|id| id == "cce-cloud").unwrap_or(false) {
1038             return;
1039         }
1040         let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
1041         let mut curr_out = (*outputs_list).next;
1042         let mut target_output: *mut crate::output::Output = std::ptr::null_mut();
1043         while curr_out != outputs_list {
1044             let output = crate::container_of!(curr_out, crate::output::Output, link);
1045             if (*output).sent.state == crate::output::OutputStateValue::Enabled {
1046                 if target_output.is_null() {
1047                     target_output = output;
1048                 }
1049                 let wlr_box = (*output).sent.box_layout();
1050                 let wx = (*window).box_geom.x;
1051                 let wy = (*window).box_geom.y;
1052                 if wx >= wlr_box.x && wx < wlr_box.x + wlr_box.width
1053                     && wy >= wlr_box.y && wy < wlr_box.y + wlr_box.height
1054                 {
1055                     target_output = output;
1056                     break;
1057                 }
1058             }
1059             curr_out = (*curr_out).next;
1060         }
1061 
1062         if !target_output.is_null() {
1063             let wlr_box = (*target_output).sent.box_layout();
1064             let viewport_w = wlr_box.width as f64;
1065             let viewport_h = wlr_box.height as f64;
1066 
1067             let fw = if (*window).box_geom.width > 0 {
1068                 (*window).box_geom.width as f64
1069             } else if (*window).wm_scheduled.dimensions_hint.min_width > 32 {
1070                 (*window).wm_scheduled.dimensions_hint.min_width as f64
1071             } else {
1072                 800.0
1073             };
1074             let fh = if (*window).box_geom.height > 0 {
1075                 (*window).box_geom.height as f64
1076             } else if (*window).wm_scheduled.dimensions_hint.min_height > 32 {
1077                 (*window).wm_scheduled.dimensions_hint.min_height as f64
1078             } else {
1079                 600.0
1080             };
1081 
1082             let wm = &mut (*self.server).wm;
1083             let cam = wm.camera();
1084             // box_geom is already virtual units (its screen footprint is
1085             // box_geom * zoom) — dividing by zoom here inflated the window
1086             // whenever zoom != 1 and mistargeted the pan.
1087             let vw_w = fw;
1088             let vw_h = fh;
1089             // The camera moves as little as the focus demands: enough to
1090             // show the whole window with a margin, and no further. A window
1091             // half off the edge and one a screen away take the same rule —
1092             // `policy::camera::pan_into_view` carries the reasoning, and
1093             // `WindowManager::pan_to_virtual_rect` applies it to the
1094             // non-window rects (restore placeholders) from the same place.
1095             if let Some(target) = crate::policy::camera::pan_into_view(
1096                 (*window).virtual_x,
1097                 (*window).virtual_y,
1098                 vw_w,
1099                 vw_h,
1100                 cam,
1101                 viewport_w,
1102                 viewport_h,
1103             ) {
1104                 wm.target_desk_pan_x = Some(target.pan_x);
1105                 wm.target_desk_pan_y = Some(target.pan_y);
1106                 wm.start_panning_animation();
1107             }
1108         }
1109     }
1110 
1111     pub unsafe fn make_inert(&mut self) {
1112         if !self.object.is_null() {
1113             ffi::wl_resource_post_event(self.object, 0); // river_seat_v1.removed
1114             ffi::wl_resource_set_implementation(
1115                 self.object,
1116                 &INERT_SEAT_INTERFACE as *const _ as *const _,
1117                 std::ptr::null_mut(),
1118                 None,
1119             );
1120             self.object = std::ptr::null_mut();
1121             (*self.server).wm.dirty_windowing();
1122         }
1123         self.layer_shell.make_inert();
1124         self.xkb_bindings_seat.make_inert();
1125     }
1126 
1127     pub unsafe fn match_xkb_binding(
1128         &self,
1129         keycode: u32,
1130         wlr_keyboard: *mut ffi::wlr_keyboard,
1131     ) -> Option<*mut crate::xkb_bindings::XkbBinding> {
1132         let xkb_state = (*wlr_keyboard).xkb_state;
1133         if xkb_state.is_null() {
1134             return None;
1135         }
1136         
1137         let modifiers = ffi::wlr_keyboard_get_modifiers(wlr_keyboard);
1138         
1139         let bindings_head = &self.xkb_bindings as *const ffi::wl_list as *mut crate::server::WlList;
1140         let mut curr = (*bindings_head).next;
1141         let mut found: Option<*mut crate::xkb_bindings::XkbBinding> = None;
1142         
1143         while curr != bindings_head {
1144             let next = (*curr).next;
1145             let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
1146             if (*binding).match_keycode(keycode, modifiers, xkb_state, false) {
1147                 if found.is_none() {
1148                     found = Some(binding);
1149                 } else {
1150                     log::debug!("already found a matching xkb_binding, ignoring additional match");
1151                 }
1152             }
1153             curr = next;
1154         }
1155         
1156         if found.is_some() {
1157             return found;
1158         }
1159         
1160         curr = (*bindings_head).next;
1161         while curr != bindings_head {
1162             let next = (*curr).next;
1163             let binding = crate::container_of!(curr, crate::xkb_bindings::XkbBinding, link);
1164             if (*binding).match_keycode(keycode, modifiers, xkb_state, true) {
1165                 if found.is_none() {
1166                     found = Some(binding);
1167                 } else {
1168                     log::debug!("already found a matching xkb_binding, ignoring additional match");
1169                 }
1170             }
1171             curr = next;
1172         }
1173         
1174         found
1175     }
1176 
1177     pub unsafe fn match_pointer_binding(
1178         &self,
1179         button: u32,
1180     ) -> Option<*mut crate::pointer_binding::PointerBinding> {
1181         let wlr_keyboard = ffi::river_wlr_seat_get_keyboard(self.wlr_seat);
1182         if wlr_keyboard.is_null() {
1183             return None;
1184         }
1185         let modifiers = ffi::wlr_keyboard_get_modifiers(wlr_keyboard);
1186 
1187         let bindings_head = &self.pointer_bindings as *const ffi::wl_list as *mut crate::server::WlList;
1188         let mut curr = (*bindings_head).next;
1189         let mut found: Option<*mut crate::pointer_binding::PointerBinding> = None;
1190 
1191         while curr != bindings_head {
1192             let next = (*curr).next;
1193             let binding = crate::container_of!(curr, crate::pointer_binding::PointerBinding, link);
1194             if (*binding).match_binding(button, modifiers) {
1195                 if found.is_none() {
1196                     found = Some(binding);
1197                 } else {
1198                     log::debug!("already found a matching pointer binding, ignoring additional match");
1199                 }
1200             }
1201             curr = next;
1202         }
1203         found
1204     }
1205 
1206     /// Snap parameters for interactive ops, from the current layout config.
1207     unsafe fn snap_params(&self) -> crate::policy::snap::SnapParams {
1208         // Zoom-aware: the felt grab distance stays constant in screen px.
1209         (*self.server).wm.layout.snap_params().for_zoom((*self.server).wm.desk_zoom)
1210     }
1211 
1212     pub unsafe fn op_update(&mut self, x: i32, y: i32) {
1213         let sp = self.snap_params();
1214         if let Some(ref mut op) = self.op {
1215             op.x = x;
1216             op.y = y;
1217             let dx = op.x - op.start_x;
1218             let dy = op.y - op.start_y;
1219             
1220             let win = op.window_ptr;
1221             if !win.is_null() && !(*win).closed {
1222                 // Every drag step can bring a Floating window over the
1223                 // adjust target or take it off: re-evaluate the overlap dim.
1224                 (*self.server).wm.arm_border_fade();
1225                 if (*win).tiling_mode != crate::tiling::TilingMode::Floating
1226                     && (*win).tiling_mode != crate::tiling::TilingMode::Overlay
1227                     // A drag moves a Utility window; it must not re-class it.
1228                     && (*win).tiling_mode != crate::tiling::TilingMode::Utility
1229                 {
1230                     // Un-tile for the drag but KEEP the geometry (clearing
1231                     // was_tiled suppresses the arrange Exit restore);
1232                     // landing grid-aligned re-tiles it in op_end.
1233                     (*win).was_tiled = false;
1234                     (*win).tiling_mode = crate::tiling::TilingMode::Floating;
1235                     (*win).mode_locked = true;
1236                     (*self.server).wm.raise_window(win);
1237                 }
1238                 
1239                 match op.op_type {
1240                     PointerOpType::Move => {
1241                         #[allow(unused_assignments)]
1242                         if (*win).is_status_bar() {
1243                             let final_x = op.start_win_x + dx;
1244                             let final_y = op.start_win_y + dy;
1245                             (*win).rendering_requested.x = final_x;
1246                             (*win).rendering_requested.y = final_y;
1247                             (*win).box_geom.x = final_x;
1248                             (*win).box_geom.y = final_y;
1249 
1250                             // Dynamically update orientation during drag
1251                             let lx = x as f64;
1252                             let ly = y as f64;
1253                             let mut closest_edge = crate::window::StatusEdge::TopLeft;
1254                             let mut min_dist = f64::MAX;
1255 
1256                             let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
1257                             let mut curr_out = (*outputs_list).next;
1258                             let mut best_output: *mut crate::output::Output = std::ptr::null_mut();
1259                             let mut min_output_dist = f64::MAX;
1260                             
1261                             while curr_out != outputs_list {
1262                                 let output = crate::container_of!(curr_out, crate::output::Output, link);
1263                                 if (*output).sent.state == crate::output::OutputStateValue::Enabled {
1264                                     let wlr_box = (*output).sent.box_layout();
1265                                     let ox = wlr_box.x as f64;
1266                                     let oy = wlr_box.y as f64;
1267                                     let ow = wlr_box.width as f64;
1268                                     let oh = wlr_box.height as f64;
1269                                     
1270                                     let clamp = |val: f64, min: f64, max: f64| {
1271                                         if val < min { min } else if val > max { max } else { val }
1272                                     };
1273                                     let cx = clamp(lx, ox, ox + ow);
1274                                     let cy = clamp(ly, oy, oy + oh);
1275                                     let dx = lx - cx;
1276                                     let dy = ly - cy;
1277                                     let dist = dx * dx + dy * dy;
1278                                     if dist < min_output_dist {
1279                                         min_output_dist = dist;
1280                                         best_output = output;
1281                                     }
1282                                 }
1283                                 curr_out = (*curr_out).next;
1284                             }
1285 
1286                             let mut found_out = false;
1287                             if !best_output.is_null() {
1288                                 found_out = true;
1289                                 let wlr_box = (*best_output).sent.box_layout();
1290                                 let ox = wlr_box.x as f64;
1291                                 let oy = wlr_box.y as f64;
1292                                 let ow = wlr_box.width as f64;
1293                                 let oh = wlr_box.height as f64;
1294 
1295                                 let dt = ly - oy;
1296                                 let db = (oy + oh) - ly;
1297                                 let dl = lx - ox;
1298                                 let dr = (ox + ow) - lx;
1299 
1300                                 enum EdgeBasic { Top, Bottom, Left, Right }
1301                                 let mut edge = EdgeBasic::Top;
1302                                 if dt < min_dist { min_dist = dt; edge = EdgeBasic::Top; }
1303                                 if db < min_dist { min_dist = db; edge = EdgeBasic::Bottom; }
1304                                 if dl < min_dist { min_dist = dl; edge = EdgeBasic::Left; }
1305                                 if dr < min_dist { min_dist = dr; edge = EdgeBasic::Right; }
1306 
1307                                 let corner_threshold = 120.0;
1308                                 let is_near_top = ly < oy + corner_threshold;
1309                                 let is_near_bottom = ly > oy + oh - corner_threshold;
1310                                 let is_near_left = lx < ox + corner_threshold;
1311                                 let is_near_right = lx > ox + ow - corner_threshold;
1312 
1313                                 let semicircle_centers = [
1314                                     (crate::window::StatusEdge::TopLeft, ox + 60.0, oy + 0.0),
1315                                     (crate::window::StatusEdge::TopCenter, ox + ow / 2.0, oy + 0.0),
1316                                     (crate::window::StatusEdge::TopRight, ox + ow - 60.0, oy + 0.0),
1317                                     (crate::window::StatusEdge::BottomLeft, ox + 60.0, oy + oh),
1318                                     (crate::window::StatusEdge::BottomCenter, ox + ow / 2.0, oy + oh),
1319                                     (crate::window::StatusEdge::BottomRight, ox + ow - 60.0, oy + oh),
1320                                     (crate::window::StatusEdge::Left, ox + 0.0, oy + oh / 2.0),
1321                                     (crate::window::StatusEdge::Right, ox + ow, oy + oh / 2.0),
1322                                 ];
1323 
1324                                 let mut snapped_to_semicircle = false;
1325                                 for (edge_type, cx, cy) in semicircle_centers {
1326                                     let dx = lx - cx;
1327                                     let dy = ly - cy;
1328                                     if dx * dx + dy * dy <= 60.0 * 60.0 {
1329                                         closest_edge = edge_type;
1330                                         snapped_to_semicircle = true;
1331                                         break;
1332                                     }
1333                                 }
1334 
1335                                 if !snapped_to_semicircle {
1336                                     match edge {
1337                                         EdgeBasic::Top => {
1338                                             if is_near_left {
1339                                                 closest_edge = crate::window::StatusEdge::TopLeft;
1340                                             } else if is_near_right {
1341                                                 closest_edge = crate::window::StatusEdge::TopRight;
1342                                             } else {
1343                                                 closest_edge = crate::window::StatusEdge::TopCenter;
1344                                             }
1345                                         }
1346                                         EdgeBasic::Bottom => {
1347                                             if is_near_left {
1348                                                 closest_edge = crate::window::StatusEdge::BottomLeft;
1349                                             } else if is_near_right {
1350                                                 closest_edge = crate::window::StatusEdge::BottomRight;
1351                                             } else {
1352                                                 closest_edge = crate::window::StatusEdge::BottomCenter;
1353                                             }
1354                                         }
1355                                         EdgeBasic::Left => {
1356                                             if is_near_top {
1357                                                 closest_edge = crate::window::StatusEdge::TopLeft;
1358                                             } else if is_near_bottom {
1359                                                 closest_edge = crate::window::StatusEdge::BottomLeft;
1360                                             } else {
1361                                                 closest_edge = crate::window::StatusEdge::Left;
1362                                             }
1363                                         }
1364                                         EdgeBasic::Right => {
1365                                             if is_near_top {
1366                                                 closest_edge = crate::window::StatusEdge::TopRight;
1367                                             } else if is_near_bottom {
1368                                                 closest_edge = crate::window::StatusEdge::BottomRight;
1369                                             } else {
1370                                                 closest_edge = crate::window::StatusEdge::Right;
1371                                             }
1372                                         }
1373                                     }
1374                                 }
1375                             }
1376 
1377                             if found_out {
1378                                 let bar_h = (*self.server).wm.layout.bar_height as u32;
1379                                 let original_length = std::cmp::max((*win).box_geom.width, (*win).box_geom.height) as u32;
1380                                 let (target_w, target_h) = match closest_edge {
1381                                     crate::window::StatusEdge::Left | crate::window::StatusEdge::Right => (bar_h, original_length),
1382                                     _ => (original_length, bar_h),
1383                                 };
1384 
1385                                 if (*win).box_geom.width as u32 != target_w || (*win).box_geom.height as u32 != target_h {
1386                                     (*win).wm_requested.dimensions = Some(crate::window::Dimensions { width: target_w, height: target_h });
1387                                     (*win).wm_requested.bounds = crate::window::Dimensions { width: target_w, height: target_h };
1388                                     (*self.server).wm.dirty_windowing();
1389                                 }
1390                             }
1391                         } else {
1392                             let scale = (*(*self.server).wm.server).wm.desk_zoom;
1393                             let pan_x = (*(*self.server).wm.server).wm.desk_pan_x;
1394                             let pan_y = (*(*self.server).wm.server).wm.desk_pan_y;
1395                             let virtual_dx = dx as f64 / scale + (pan_x - op.start_pan_x);
1396                             let virtual_dy = dy as f64 / scale + (pan_y - op.start_pan_y);
1397 
1398                             let vx = op.start_win_virtual_x + virtual_dx;
1399                             let vy = op.start_win_virtual_y + virtual_dy;
1400                             // A Tiled window only ever occupies whole squares,
1401                             // so its drag snaps hard to the nearest one. The
1402                             // magnetic snap below is for Floating windows,
1403                             // which use it to decide whether they land aligned
1404                             // (and so become Tiled) at op_end.
1405                             //
1406                             // This asks what the window was when GRABBED, not
1407                             // what it is now: op_update un-tiles a tiled window
1408                             // on the first motion event so the drag can follow
1409                             // the pointer, so the live mode is always Floating
1410                             // here and a test against it never fires.
1411                             let (vx, vy) = if op.start_was_tiled {
1412                                 crate::policy::snap::snap_move_tiled(vx, vy, &sp)
1413                             } else {
1414                                 crate::policy::snap::snap_move(
1415                                     vx,
1416                                     vy,
1417                                     (*win).box_geom.width as f64,
1418                                     (*win).box_geom.height as f64,
1419                                     &sp,
1420                                 )
1421                             };
1422                             (*win).virtual_x = vx;
1423                             (*win).virtual_y = vy;
1424 
1425                             // Overview moves displace what they cover: any
1426                             // window the drag covers past the threshold
1427                             // scoots to the side the drag vacated.
1428                             if (*self.server).wm.mode
1429                                 == crate::window_manager::WindowManagerMode::Overview
1430                             {
1431                                 displace_covered(
1432                                     self.server,
1433                                     win,
1434                                     op.start_was_tiled,
1435                                     (virtual_dx, virtual_dy),
1436                                     &sp,
1437                                     &mut self.overview_displaced,
1438                                 );
1439                             }
1440 
1441                             let (final_x, final_y) = (*win).virtual_to_screen(vx, vy);
1442                             (*win).rendering_requested.x = final_x;
1443                             (*win).rendering_requested.y = final_y;
1444                             (*win).box_geom.x = final_x;
1445                             (*win).box_geom.y = final_y;
1446                         }
1447                     }
1448                     PointerOpType::Resize { edges } => {
1449                         let scale = (*(*self.server).wm.server).wm.desk_zoom;
1450                         let pan_x = (*(*self.server).wm.server).wm.desk_pan_x;
1451                         let pan_y = (*(*self.server).wm.server).wm.desk_pan_y;
1452                         let virtual_dx = dx as f64 / scale + (pan_x - op.start_pan_x);
1453                         let virtual_dy = dy as f64 / scale + (pan_y - op.start_pan_y);
1454 
1455                         let mut vx = op.start_win_virtual_x;
1456                         let mut vy = op.start_win_virtual_y;
1457 
1458                         if edges.left {
1459                             vx = (*win).virtual_x;
1460                         }
1461                         if edges.top {
1462                             vy = (*win).virtual_y;
1463                         }
1464 
1465                         if (*win).resize_edges != Some(edges) {
1466                             (*win).resize_start_vx = op.start_win_virtual_x;
1467                             (*win).resize_start_vy = op.start_win_virtual_y;
1468                             (*win).resize_start_w = op.start_win_w;
1469                             (*win).resize_start_h = op.start_win_h;
1470                             (*win).resize_edges = Some(edges);
1471                         }
1472 
1473                         // A window grabbed Tiled snaps HARD: the dragged edge
1474                         // lands on a cell edge from any distance and the size
1475                         // stays whole cells, so it is still Tiled on release.
1476                         // A Floating one gets the magnetic pull onto the
1477                         // visible cell edges; the anchored edge is untouched
1478                         // either way. Must match get_active_resize_dimensions,
1479                         // which recomputes this for the arrange snapshot.
1480                         let (new_w, new_h) = if op.start_was_tiled {
1481                             (
1482                                 crate::policy::snap::resize_axis_tiled(
1483                                     op.start_win_virtual_x, op.start_win_w as f64, virtual_dx,
1484                                     edges.left, edges.right, &sp.x(),
1485                                 ) as u32,
1486                                 crate::policy::snap::resize_axis_tiled(
1487                                     op.start_win_virtual_y, op.start_win_h as f64, virtual_dy,
1488                                     edges.top, edges.bottom, &sp.y(),
1489                                 ) as u32,
1490                             )
1491                         } else {
1492                             (
1493                                 crate::policy::snap::resize_axis(
1494                                     op.start_win_virtual_x, op.start_win_w as f64, virtual_dx,
1495                                     edges.left, edges.right, 50.0, &sp.x(),
1496                                 ) as u32,
1497                                 crate::policy::snap::resize_axis(
1498                                     op.start_win_virtual_y, op.start_win_h as f64, virtual_dy,
1499                                     edges.top, edges.bottom, 50.0, &sp.y(),
1500                                 ) as u32,
1501                             )
1502                         };
1503                         // The client's xdg min/max size is a contract, not a
1504                         // suggestion: a configure below it is applied by
1505                         // cce-ui as-is, and a layout with less room than its
1506                         // fixed parts panicked cce-data-editor mid-drag.
1507                         let (new_w, new_h) = (*win).wm_scheduled.dimensions_hint.clamp(new_w, new_h);
1508 
1509                         (*win).virtual_x = vx;
1510                         (*win).virtual_y = vy;
1511 
1512                         let (final_x, final_y) = (*win).virtual_to_screen(vx, vy);
1513 
1514                         (*win).rendering_requested.x = final_x;
1515                         (*win).rendering_requested.y = final_y;
1516                         (*win).box_geom.x = final_x;
1517                         (*win).box_geom.y = final_y;
1518 
1519                         (*win).wm_requested.resizing = true;
1520                         (*win).wm_requested.dimensions = Some(crate::window::Dimensions {
1521                             width: new_w,
1522                             height: new_h,
1523                         });
1524                         (*win).wm_requested.bounds = crate::window::Dimensions {
1525                             width: new_w,
1526                             height: new_h,
1527                         };
1528                         (*win).set_dimensions(new_w, new_h);
1529                     }
1530                 }
1531             }
1532             // The configure and the relayout go out once per output frame,
1533             // for wherever the pointer is by then (WindowManager::
1534             // step_op_frame), not once per motion event.
1535             (*self.server).wm.queue_op_frame();
1536         }
1537         self.update_edge_pan(x as f64, y as f64);
1538     }
1539 
1540     /// Edge auto-pan eligibility + velocity for the current op: while an
1541     /// interactive move/resize holds the cursor inside the band at an output
1542     /// edge, the desktop scrolls that way, ramping from 0 at the band's inner
1543     /// rim to full speed at the screen edge. Called on every op motion AND
1544     /// from the edge-pan tick's op_update, which is what re-arms the timer —
1545     /// so the scroll continues while the cursor rests pinned at the edge.
1546     unsafe fn update_edge_pan(&mut self, lx: f64, ly: f64) {
1547         let wm = &mut (*self.server).wm;
1548         let mut vx = 0.0;
1549         let mut vy = 0.0;
1550         let eligible = wm.layout.desktop_edge_pan
1551             && match self.op {
1552                 Some(ref op) => {
1553                     !op.window_ptr.is_null()
1554                         && !(*op.window_ptr).closed
1555                         && !(*op.window_ptr).is_status_bar()
1556                 }
1557                 None => false,
1558             };
1559         if eligible {
1560             let wlr_output = (*self.server).om.output_at(lx, ly);
1561             if !wlr_output.is_null() {
1562                 let mut ob = ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 };
1563                 ffi::wlr_output_layout_get_box((*self.server).om.output_layout, wlr_output, &mut ob);
1564                 let band = wm.layout.desktop_edge_pan_band.max(1.0);
1565                 let speed = wm.layout.desktop_edge_pan_speed.max(0.0);
1566                 // 0 outside the band, 1 at (or past) the screen edge.
1567                 let ramp = |dist_to_edge: f64| ((band - dist_to_edge) / band).clamp(0.0, 1.0);
1568                 vx = speed
1569                     * (ramp((ob.x + ob.width) as f64 - lx) - ramp(lx - ob.x as f64));
1570                 vy = speed
1571                     * (ramp((ob.y + ob.height) as f64 - ly) - ramp(ly - ob.y as f64));
1572             }
1573         }
1574         wm.set_edge_pan_velocity(vx, vy);
1575     }
1576 
1577     pub unsafe fn op_end(&mut self) {
1578         // Wherever everything sits now is final.
1579         self.overview_displaced.clear();
1580         if let Some(op) = self.op.take() {
1581             log::debug!("end seat op");
1582             let wm = &mut (*self.server).wm;
1583             wm.edge_pan_vx = 0.0;
1584             wm.edge_pan_vy = 0.0;
1585             let win = op.window_ptr;
1586             if !win.is_null() && !(*win).closed {
1587                 if let PointerOpType::Resize { .. } = op.op_type {
1588                     (*win).wm_requested.resizing = false;
1589                     (*win).manage_finish();
1590                     (*self.server).wm.dirty_windowing();
1591                 }
1592                 if let PointerOpType::Move = op.op_type {
1593                     if (*win).tiling_mode == crate::tiling::TilingMode::Overlay {
1594                         (*self.server).wm.dirty_windowing();
1595                     }
1596                 }
1597                 // Geometric mode detection: a move/resize that lands every
1598                 // content edge on a visible desktop-grid cell edge makes the
1599                 // window Tiled (it then reports the maximized state to its
1600                 // client); landing off-grid makes it Floating, in place.
1601                 // Only windows resolving Floating/Tiled participate —
1602                 // Popup/Overlay/Status/Fullscreen are untouched.
1603                 let resolved = (*self.server).wm.get_mode_for_window(win);
1604                 if matches!(
1605                     resolved,
1606                     crate::tiling::TilingMode::Floating | crate::tiling::TilingMode::Tiled
1607                 ) {
1608                     // Unscaled params: alignment classifies the resting
1609                     // geometry, the zoom-aware grab distance is irrelevant.
1610                     let sp = (*self.server).wm.layout.snap_params();
1611                     let (w, h) = match (*win).wm_requested.dimensions {
1612                         // A just-finished resize may not be acked into
1613                         // box_geom yet; the requested size is what the
1614                         // window is about to become.
1615                         Some(d) => (d.width as f64, d.height as f64),
1616                         None => ((*win).box_geom.width as f64, (*win).box_geom.height as f64),
1617                     };
1618                     let aligned = crate::policy::snap::is_cell_aligned(
1619                         (*win).virtual_x,
1620                         (*win).virtual_y,
1621                         w,
1622                         h,
1623                         &sp,
1624                         1.0,
1625                     );
1626                     if aligned && resolved != crate::tiling::TilingMode::Tiled {
1627                         (*win).tiling_mode = crate::tiling::TilingMode::Tiled;
1628                         (*win).mode_locked = true;
1629                         (*self.server).wm.dirty_windowing();
1630                     } else if !aligned && resolved == crate::tiling::TilingMode::Tiled {
1631                         // Un-tile in place: clearing was_tiled keeps the
1632                         // arrange Exit transition from restoring the old
1633                         // floating geometry.
1634                         (*win).was_tiled = false;
1635                         (*win).tiling_mode = crate::tiling::TilingMode::Floating;
1636                         (*win).mode_locked = true;
1637                         (*self.server).wm.dirty_windowing();
1638                     }
1639                 }
1640                 // A TAP — press+release without meaningful motion — is a
1641                 // click, not a drag. A drag never focuses the window it
1642                 // moves or resizes (the press grabs without focusing), but
1643                 // a click on a window chooses it as any click does, so the
1644                 // tap focuses here. And it pans: the press killed any
1645                 // focus-follow pan (drag protection), which left a
1646                 // mostly-hidden window stranded — aiming at a thin content
1647                 // sliver at the screen edge, it is easy to land on the
1648                 // border band instead and see nothing happen. Real drags
1649                 // (any actual motion) keep the camera still. An overview
1650                 // tap is excluded: its release already launched the exit
1651                 // flight centered on this window, and a second pan computed
1652                 // from the still-overview camera drags that flight off
1653                 // target (hover focused it there anyway).
1654                 let dx = (op.x - op.start_x).abs();
1655                 let dy = (op.y - op.start_y).abs();
1656                 if dx < 4 && dy < 4 && !op.started_in_overview && !(*win).is_status_bar() {
1657                     self.focus(Focus::Window(win));
1658                     self.focus_follow_pan(win);
1659                 }
1660             }
1661             match op.input {
1662                 SeatOpInput::Pointer => {
1663                     self.cursor.op_end_pointer();
1664                 }
1665             }
1666         }
1667     }
1668 }
1669 
1670 /// Live overview displacement for one motion step of a move op: every
1671 /// mapped, visible Floating/Tiled window the drag covers past the policy
1672 /// threshold relocates to the side the drag vacated
1673 /// (`crate::policy::overview::displace`). Single-level on purpose — a
1674 /// displaced window does not cascade into a third.
1675 ///
1676 /// Displacement is provisional while the button is held: `ledger` remembers
1677 /// every displaced window's pre-displacement position, and each motion
1678 /// re-evaluates the window AT THAT SPOT — a drag that stops covering it
1679 /// releases it back home. The ledger drops with the op on release, which
1680 /// finalizes wherever everything currently sits.
1681 ///
1682 /// `moved_tiled` is what the dragged window was when it was GRABBED (the op's
1683 /// `start_was_tiled`), for the reason the snap call above gives: the drag
1684 /// un-tiles it on the first motion event, so its live mode reads Floating
1685 /// however it started. The policy skips candidates of the other kind.
1686 unsafe fn displace_covered(
1687     server: *mut Server,
1688     win: *mut crate::window::Window,
1689     moved_tiled: bool,
1690     drag_delta: (f64, f64),
1691     sp: &crate::policy::snap::SnapParams,
1692     ledger: &mut Vec<(*mut crate::window::Window, f64, f64)>,
1693 ) {
1694     let wm = &mut (*server).wm;
1695     // A window can close mid-drag; drop its entry before any deref.
1696     ledger.retain(|&(w, _, _)| wm.windows.iter().any(|&p| p == w));
1697     let moved = (
1698         (*win).virtual_x,
1699         (*win).virtual_y,
1700         (*win).box_geom.width as f64,
1701         (*win).box_geom.height as f64,
1702     );
1703     let mut ptrs: Vec<*mut crate::window::Window> = Vec::new();
1704     let mut cands: Vec<crate::policy::overview::DisplaceCandidate> = Vec::new();
1705     for &w in wm.windows.iter() {
1706         if w.is_null() || w == win || (*w).closed || (*w).minimized {
1707             continue;
1708         }
1709         if !matches!((*w).state, crate::window::WindowState::Mapped) {
1710             continue;
1711         }
1712         if (*w).is_status_bar() || (*w).is_wallpaper() || (*w).is_grid() {
1713             continue;
1714         }
1715         let mode = wm.get_mode_for_window(w);
1716         if mode != crate::tiling::TilingMode::Floating
1717             && mode != crate::tiling::TilingMode::Tiled
1718         {
1719             continue;
1720         }
1721         // Judge an already-displaced window at its ORIGINAL spot, not
1722         // where it fled to.
1723         let (ox, oy) = ledger
1724             .iter()
1725             .find(|&&(p, _, _)| p == w)
1726             .map(|&(_, x, y)| (x, y))
1727             .unwrap_or(((*w).virtual_x, (*w).virtual_y));
1728         ptrs.push(w);
1729         cands.push(crate::policy::overview::DisplaceCandidate {
1730             x: ox,
1731             y: oy,
1732             w: (*w).box_geom.width as f64,
1733             h: (*w).box_geom.height as f64,
1734             tiled: mode == crate::tiling::TilingMode::Tiled,
1735         });
1736     }
1737     let moves = crate::policy::overview::displace(
1738         moved, moved_tiled, drag_delta, &cands, sp, sp.gap_width,
1739     );
1740 
1741     let mut changed = false;
1742     let mut displaced_now: Vec<*mut crate::window::Window> = Vec::new();
1743     for &(idx, (nx, ny)) in &moves {
1744         let w = ptrs[idx];
1745         displaced_now.push(w);
1746         if !ledger.iter().any(|&(p, _, _)| p == w) {
1747             ledger.push((w, cands[idx].x, cands[idx].y));
1748         }
1749         if (*w).virtual_x != nx || (*w).virtual_y != ny {
1750             (*w).virtual_x = nx;
1751             (*w).virtual_y = ny;
1752             changed = true;
1753         }
1754     }
1755     // No longer covered at its original spot: back home.
1756     ledger.retain(|&(w, ox, oy)| {
1757         if displaced_now.contains(&w) {
1758             return true;
1759         }
1760         if !(*w).closed && ((*w).virtual_x != ox || (*w).virtual_y != oy) {
1761             (*w).virtual_x = ox;
1762             (*w).virtual_y = oy;
1763             changed = true;
1764         }
1765         false
1766     });
1767     if changed {
1768         wm.dirty_windowing();
1769     }
1770 }
1771 
1772 unsafe extern "C" fn handle_request_set_cursor(
1773     listener: *mut ffi::wl_listener,
1774     data: *mut std::ffi::c_void,
1775 ) {
1776     let seat = &mut *crate::container_of!(listener, Seat, request_set_cursor);
1777     let event = data as *mut ffi::wlr_seat_pointer_request_set_cursor_event;
1778     
1779     let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(seat.wlr_seat);
1780     
1781     let event_client = ffi::river_wlr_seat_client_get_client((*event).seat_client);
1782     let wm_client = if !(*seat.server).wm.object.is_null() {
1783         ffi::wl_resource_get_client((*seat.server).wm.object)
1784     } else {
1785         std::ptr::null_mut()
1786     };
1787     let is_wm = !wm_client.is_null() && event_client == wm_client;
1788 
1789     if focused_client == (*event).seat_client || is_wm {
1790         // The client owns the cursor image from here; a compositor-driven
1791         // xcursor animation would paint over it on its next tick.
1792         seat.cursor.stop_xcursor_animation();
1793         // An X11 client's cursor is a physical-pixel bitmap like the rest of
1794         // its drawing, but Xwayland commits it at buffer scale 1, so wlroots
1795         // would show it at that many LOGICAL pixels — Houdini's 48px
1796         // crosshair came out 96 physical px against the desktop's 48. Show it
1797         // at 1/scale, the way the window's own buffer already is
1798         // (`Window::x11_buffer_scale`), and put the hotspot in the same units.
1799         let scale = if is_xwayland_client(seat.server, event_client) {
1800             crate::xwayland_window::x11_scale_for_surface(
1801                 seat.server,
1802                 ffi::river_wlr_seat_get_pointer_focused_surface(seat.wlr_seat),
1803             )
1804         } else {
1805             1.0
1806         };
1807         if scale != 1.0 {
1808             log::debug!(
1809                 "X11 client cursor: drawn at 1/{scale} (hotspot {}, {})",
1810                 (*event).hotspot_x, (*event).hotspot_y
1811             );
1812         }
1813         seat.watch_x11_cursor((*event).surface, scale);
1814         let (hotspot_x, hotspot_y) = if scale != 1.0 {
1815             (
1816                 ((*event).hotspot_x as f32 / scale).round() as i32,
1817                 ((*event).hotspot_y as f32 / scale).round() as i32,
1818             )
1819         } else {
1820             ((*event).hotspot_x, (*event).hotspot_y)
1821         };
1822         ffi::wlr_cursor_set_surface(
1823             seat.cursor.wlr_cursor,
1824             (*event).surface,
1825             hotspot_x,
1826             hotspot_y,
1827         );
1828     }
1829 }
1830 
1831 /// Is this the Xwayland client itself? Every X11 window's requests arrive as
1832 /// that one client, which is what separates an X11 cursor from a Wayland one.
1833 unsafe fn is_xwayland_client(
1834     server: *mut crate::server::Server,
1835     client: *mut ffi::wl_client,
1836 ) -> bool {
1837     if server.is_null() || (*server).xwayland.is_null() || client.is_null() {
1838         return false;
1839     }
1840     let xwayland = (*server).xwayland as *mut crate::server::WlrXwayland;
1841     let xserver = (*xwayland).server as *mut ffi::wlr_xwayland_server;
1842     if xserver.is_null() {
1843         return false;
1844     }
1845     !(*xserver).client.is_null() && (*xserver).client == client
1846 }
1847 
1848 /// Keep an X11 cursor surface shown at 1/scale for as long as it is the
1849 /// pointer image: `wlr_cursor` re-reads the surface's logical size on every
1850 /// commit, so the shrink has to be re-applied there — before wlroots reads it,
1851 /// which is why this listener is added ahead of `wlr_cursor_set_surface`.
1852 unsafe extern "C" fn handle_x11_cursor_commit(
1853     listener: *mut ffi::wl_listener,
1854     _data: *mut std::ffi::c_void,
1855 ) {
1856     let seat = &mut *crate::container_of!(listener, Seat, x11_cursor_commit);
1857     ffi::river_wlr_surface_scale_logical_size(seat.x11_cursor_surface, seat.x11_cursor_scale);
1858 }
1859 
1860 unsafe extern "C" fn handle_x11_cursor_destroy(
1861     listener: *mut ffi::wl_listener,
1862     _data: *mut std::ffi::c_void,
1863 ) {
1864     let seat = &mut *crate::container_of!(listener, Seat, x11_cursor_destroy);
1865     seat.unwatch_x11_cursor();
1866 }
1867 
1868 unsafe extern "C" fn handle_request_set_selection(
1869     listener: *mut ffi::wl_listener,
1870     data: *mut std::ffi::c_void,
1871 ) {
1872     let seat = &mut *crate::container_of!(listener, Seat, request_set_selection);
1873     let event = data as *mut ffi::wlr_seat_request_set_selection_event;
1874     ffi::wlr_seat_set_selection(seat.wlr_seat, (*event).source, (*event).serial);
1875 }
1876 
1877 unsafe extern "C" fn handle_request_start_drag(
1878     listener: *mut ffi::wl_listener,
1879     data: *mut std::ffi::c_void,
1880 ) {
1881     let seat = &mut *crate::container_of!(listener, Seat, request_start_drag);
1882     let event = data as *mut ffi::wlr_seat_request_start_drag_event;
1883 
1884     assert!(seat.drag == DragState::None);
1885 
1886     if ffi::wlr_seat_validate_pointer_grab_serial(seat.wlr_seat, (*event).origin, (*event).serial) {
1887         ffi::wlr_seat_start_pointer_drag(seat.wlr_seat, (*event).drag, (*event).serial);
1888         return;
1889     }
1890 
1891     let mut point: *mut ffi::wlr_touch_point = std::ptr::null_mut();
1892     if ffi::wlr_seat_validate_touch_grab_serial(seat.wlr_seat, (*event).origin, (*event).serial, &mut point) {
1893         ffi::wlr_seat_start_touch_drag(seat.wlr_seat, (*event).drag, (*event).serial, point);
1894         return;
1895     }
1896 
1897     let source = ffi::river_wlr_drag_get_source((*event).drag);
1898     if !source.is_null() {
1899         ffi::wlr_data_source_destroy(source);
1900     }
1901 }
1902 
1903 unsafe extern "C" fn handle_start_drag(
1904     listener: *mut ffi::wl_listener,
1905     data: *mut std::ffi::c_void,
1906 ) {
1907     let seat = &mut *crate::container_of!(listener, Seat, start_drag);
1908     let wlr_drag = data as *mut ffi::wlr_drag;
1909 
1910     assert!(seat.drag == DragState::None);
1911     let grab_type = ffi::river_wlr_drag_get_grab_type(wlr_drag);
1912     log::debug!("[drag] started (grab type {grab_type})");
1913     match grab_type {
1914         ffi::wlr_drag_grab_type_WLR_DRAG_GRAB_KEYBOARD_POINTER => {
1915             seat.drag = DragState::Pointer;
1916         }
1917         ffi::wlr_drag_grab_type_WLR_DRAG_GRAB_KEYBOARD_TOUCH => {
1918             seat.drag = DragState::Touch;
1919         }
1920         _ => {}
1921     }
1922 
1923     let drag_destroy_ptr = &mut seat.drag_destroy as *mut ffi::wl_listener as *mut WlListener;
1924     (*drag_destroy_ptr).notify = Some(handle_drag_destroy);
1925     wl_signal_add(
1926         ffi::river_wlr_drag_get_destroy_signal(wlr_drag),
1927         &mut seat.drag_destroy,
1928     );
1929 
1930     let wlr_drag_icon = ffi::river_wlr_drag_get_icon(wlr_drag);
1931     if !wlr_drag_icon.is_null() {
1932         if let Err(err) = crate::drag_icon::DragIcon::create(wlr_drag_icon, &mut seat.cursor) {
1933             log::error!("Failed to create drag icon: {}", err);
1934             let seat_client = ffi::river_wlr_drag_get_seat_client(wlr_drag);
1935             if !seat_client.is_null() {
1936                 let client = ffi::river_wlr_seat_client_get_client(seat_client);
1937                 if !client.is_null() {
1938                     ffi::wl_client_post_no_memory(client);
1939                 }
1940             }
1941         }
1942     }
1943 }
1944 
1945 unsafe extern "C" fn handle_drag_destroy(
1946     listener: *mut ffi::wl_listener,
1947     _data: *mut std::ffi::c_void,
1948 ) {
1949     let seat = &mut *crate::container_of!(listener, Seat, drag_destroy);
1950     wl_listener_remove(&mut seat.drag_destroy);
1951 
1952     match seat.drag {
1953         DragState::None => unreachable!(),
1954         DragState::Pointer => {
1955             seat.cursor.update_state();
1956         }
1957         DragState::Touch => {}
1958     }
1959     seat.drag = DragState::None;
1960 }
1961 
1962 unsafe extern "C" fn handle_request_set_primary_selection(
1963     listener: *mut ffi::wl_listener,
1964     data: *mut std::ffi::c_void,
1965 ) {
1966     let seat = &mut *crate::container_of!(listener, Seat, request_set_primary_selection);
1967     let event = data as *mut ffi::wlr_seat_request_set_primary_selection_event;
1968     ffi::wlr_seat_set_primary_selection(seat.wlr_seat, (*event).source, (*event).serial);
1969 }
1970 
1971 unsafe extern "C" fn seat_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
1972     ffi::wl_resource_destroy(resource);
1973 }
1974 
1975 unsafe extern "C" fn seat_focus_window(
1976     _client: *mut ffi::wl_client,
1977     resource: *mut ffi::wl_resource,
1978     window_resource: *mut ffi::wl_resource,
1979 ) {
1980     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
1981     if seat.is_null() {
1982         return;
1983     }
1984     if !(*(*seat).server).wm.ensure_windowing() {
1985         return;
1986     }
1987     (*seat).focus_requested = true;
1988     if window_resource.is_null() {
1989         (*seat).focus(Focus::None);
1990         return;
1991     }
1992     let window = ffi::wl_resource_get_user_data(window_resource) as *mut crate::window::Window;
1993     if !window.is_null() {
1994         (*seat).focus(Focus::Window(window));
1995     }
1996 }
1997 
1998 unsafe extern "C" fn seat_focus_shell_surface(
1999     _client: *mut ffi::wl_client,
2000     resource: *mut ffi::wl_resource,
2001     shell_surface_resource: *mut ffi::wl_resource,
2002 ) {
2003     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2004     if seat.is_null() {
2005         return;
2006     }
2007     if !(*(*seat).server).wm.ensure_windowing() {
2008         return;
2009     }
2010     (*seat).focus_requested = true;
2011     if shell_surface_resource.is_null() {
2012         (*seat).focus(Focus::None);
2013         return;
2014     }
2015     let shell_surface = ffi::wl_resource_get_user_data(shell_surface_resource) as *mut crate::shell_surface::ShellSurface;
2016     if !shell_surface.is_null() {
2017         (*seat).focus(Focus::ShellSurface(shell_surface));
2018     }
2019 }
2020 
2021 unsafe extern "C" fn seat_clear_focus(
2022     _client: *mut ffi::wl_client,
2023     resource: *mut ffi::wl_resource,
2024 ) {
2025     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2026     if !seat.is_null() {
2027         if (*(*seat).server).wm.ensure_windowing() {
2028             (*seat).focus_requested = true;
2029             (*seat).focus(Focus::None);
2030         }
2031     }
2032 }
2033 
2034 unsafe extern "C" fn seat_op_start_pointer(
2035     _client: *mut ffi::wl_client,
2036     resource: *mut ffi::wl_resource,
2037 ) {
2038     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2039     if seat.is_null() {
2040         return;
2041     }
2042     if !(*(*seat).server).wm.ensure_windowing() {
2043         return;
2044     }
2045     if (*seat).op.is_none() {
2046         log::debug!("start seat op pointer");
2047         let cursor_x = (*(*seat).cursor.wlr_cursor).x;
2048         let cursor_y = (*(*seat).cursor.wlr_cursor).y;
2049         (*seat).op = Some(SeatOp {
2050             sent_release: false,
2051             input: SeatOpInput::Pointer,
2052             start_x: cursor_x as i32,
2053             start_y: cursor_y as i32,
2054             x: cursor_x as i32,
2055             y: cursor_y as i32,
2056             window_ptr: std::ptr::null_mut(),
2057             op_type: PointerOpType::Move,
2058             start_win_x: 0,
2059             start_win_y: 0,
2060             start_win_w: 0,
2061             start_win_h: 0,
2062             start_win_virtual_x: 0.0,
2063             start_win_virtual_y: 0.0,
2064             start_pan_x: (*(*seat).server).wm.desk_pan_x,
2065             start_pan_y: (*(*seat).server).wm.desk_pan_y,
2066             start_tiling_mode: crate::tiling::TilingMode::Floating,
2067             start_was_tiled: false,
2068             start_mode_locked: false,
2069             started_in_overview: false,
2070         });
2071         (*(*seat).server).wm.stop_panning_animation();
2072         (*seat).cursor.op_start_pointer();
2073     }
2074 }
2075 
2076 unsafe extern "C" fn seat_op_end(
2077     _client: *mut ffi::wl_client,
2078     resource: *mut ffi::wl_resource,
2079 ) {
2080     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2081     if seat.is_null() {
2082         return;
2083     }
2084     if !(*(*seat).server).wm.ensure_windowing() {
2085         return;
2086     }
2087     (*seat).op_end();
2088 }
2089 
2090 unsafe extern "C" fn seat_get_pointer_binding(
2091     client: *mut ffi::wl_client,
2092     resource: *mut ffi::wl_resource,
2093     id: u32,
2094     button: u32,
2095     modifiers: u32,
2096 ) {
2097     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2098     if seat.is_null() {
2099         return;
2100     }
2101     let version = ffi::wl_resource_get_version(resource) as u32;
2102     if let Err(err) = crate::pointer_binding::PointerBinding::create(
2103         seat,
2104         client,
2105         version,
2106         id,
2107         button,
2108         modifiers,
2109     ) {
2110         log::error!("failed to create pointer binding: {}", err);
2111         ffi::wl_client_post_no_memory(client);
2112     }
2113 }
2114 
2115 unsafe extern "C" fn seat_set_xcursor_theme(
2116     client: *mut ffi::wl_client,
2117     resource: *mut ffi::wl_resource,
2118     name: *const ::std::os::raw::c_char,
2119     size: u32,
2120 ) {
2121     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2122     if seat.is_null() {
2123         return;
2124     }
2125     if let Err(err) = (*seat).cursor.set_theme(name, size) {
2126         log::error!("failed to set xcursor theme: {}", err);
2127         ffi::wl_client_post_no_memory(client);
2128     }
2129 }
2130 
2131 unsafe extern "C" fn seat_pointer_warp(
2132     _client: *mut ffi::wl_client,
2133     resource: *mut ffi::wl_resource,
2134     x: i32,
2135     y: i32,
2136 ) {
2137     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2138     if seat.is_null() {
2139         return;
2140     }
2141     if !(*(*seat).server).wm.ensure_windowing() {
2142         return;
2143     }
2144     let cursor = &mut (*seat).cursor;
2145     ffi::wlr_cursor_warp_absolute(cursor.wlr_cursor, std::ptr::null_mut(), x as f64, y as f64);
2146 }
2147 
2148 static SEAT_INTERFACE: ffi::zcce_seat_v1_interface = ffi::zcce_seat_v1_interface {
2149     destroy: Some(seat_destroy),
2150     focus_window: Some(seat_focus_window),
2151     focus_shell_surface: Some(seat_focus_shell_surface),
2152     clear_focus: Some(seat_clear_focus),
2153     op_start_pointer: Some(seat_op_start_pointer),
2154     op_end: Some(seat_op_end),
2155     get_pointer_binding: Some(seat_get_pointer_binding),
2156     set_xcursor_theme: Some(seat_set_xcursor_theme),
2157     pointer_warp: Some(seat_pointer_warp),
2158 };
2159 
2160 unsafe extern "C" fn seat_inert_focus_window(
2161     _client: *mut ffi::wl_client,
2162     _resource: *mut ffi::wl_resource,
2163     _window_resource: *mut ffi::wl_resource,
2164 ) {}
2165 
2166 unsafe extern "C" fn seat_inert_focus_shell_surface(
2167     _client: *mut ffi::wl_client,
2168     _resource: *mut ffi::wl_resource,
2169     _shell_surface_resource: *mut ffi::wl_resource,
2170 ) {}
2171 
2172 unsafe extern "C" fn seat_inert_clear_focus(
2173     _client: *mut ffi::wl_client,
2174     _resource: *mut ffi::wl_resource,
2175 ) {}
2176 
2177 unsafe extern "C" fn seat_inert_op_start_pointer(
2178     _client: *mut ffi::wl_client,
2179     _resource: *mut ffi::wl_resource,
2180 ) {}
2181 
2182 unsafe extern "C" fn seat_inert_op_end(
2183     _client: *mut ffi::wl_client,
2184     _resource: *mut ffi::wl_resource,
2185 ) {}
2186 
2187 unsafe extern "C" fn seat_inert_get_pointer_binding(
2188     _client: *mut ffi::wl_client,
2189     _resource: *mut ffi::wl_resource,
2190     _id: u32,
2191     _button: u32,
2192     _modifiers: u32,
2193 ) {}
2194 
2195 unsafe extern "C" fn seat_inert_set_xcursor_theme(
2196     _client: *mut ffi::wl_client,
2197     _resource: *mut ffi::wl_resource,
2198     _name: *const ::std::os::raw::c_char,
2199     _size: u32,
2200 ) {}
2201 
2202 unsafe extern "C" fn seat_inert_pointer_warp(
2203     _client: *mut ffi::wl_client,
2204     _resource: *mut ffi::wl_resource,
2205     _x: i32,
2206     _y: i32,
2207 ) {}
2208 
2209 static INERT_SEAT_INTERFACE: ffi::zcce_seat_v1_interface = ffi::zcce_seat_v1_interface {
2210     destroy: Some(seat_destroy),
2211     focus_window: Some(seat_inert_focus_window),
2212     focus_shell_surface: Some(seat_inert_focus_shell_surface),
2213     clear_focus: Some(seat_inert_clear_focus),
2214     op_start_pointer: Some(seat_inert_op_start_pointer),
2215     op_end: Some(seat_inert_op_end),
2216     get_pointer_binding: Some(seat_inert_get_pointer_binding),
2217     set_xcursor_theme: Some(seat_inert_set_xcursor_theme),
2218     pointer_warp: Some(seat_inert_pointer_warp),
2219 };
2220 
2221 unsafe extern "C" fn handle_destroy_resource(resource: *mut ffi::wl_resource) {
2222     let seat = ffi::wl_resource_get_user_data(resource) as *mut Seat;
2223     if !seat.is_null() {
2224         if (*seat).object != resource {
2225             return;
2226         }
2227         (*seat).object = std::ptr::null_mut();
2228     }
2229 }