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 }