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