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

src/server/seat.rs (110.2K)

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