Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/server.rs (44.8K)
1 // SPDX-FileCopyrightText: © 2020 The River Developers
2 // SPDX-License-Identifier: GPL-3.0-only
3
4 use crate::ffi;
5 use std::ptr;
6
7 use crate::window_manager::WindowManager;
8 use crate::xkb_bindings::XkbBindings;
9 use crate::layer_shell::LayerShell;
10 use crate::scene::Scene;
11 use crate::output_manager::OutputManager;
12 use crate::input_manager::InputManager;
13 use crate::libinput_config::LibinputConfig;
14 use crate::xkb_config::XkbConfig;
15 use crate::idle_inhibit_manager::IdleInhibitManager;
16 use crate::idle::IdleManager;
17 use crate::lock_manager::LockManager;
18
19 // Activation-attention notifications ("<app> needs attention / has requested
20 // activation") are suppressed during the compositor's startup sequence: session
21 // restore respawns every previously-open window, and each one issues an
22 // xdg-activation request as it maps, which would otherwise spray a burst of
23 // "needs attention" popups the moment you log in. `begin_startup_activation_grace`
24 // is called once when startup begins; until the deadline passes, the handler
25 // swallows the notification. Outside the startup path the cell is never set, so
26 // activation notifications behave normally.
27 static STARTUP_ACTIVATION_GRACE_UNTIL: std::sync::OnceLock<std::time::Instant> =
28 std::sync::OnceLock::new();
29
30 /// How long after startup begins to keep suppressing activation notifications.
31 /// Generous enough to cover restored heavyweight apps (e.g. a browser) that take
32 /// several seconds to launch and request focus.
33 const STARTUP_ACTIVATION_GRACE: std::time::Duration = std::time::Duration::from_secs(8);
34
35 /// Begin the startup grace window during which activation-attention
36 /// notifications are suppressed. Idempotent — only the first call takes effect.
37 pub fn begin_startup_activation_grace() {
38 let _ = STARTUP_ACTIVATION_GRACE_UNTIL.set(std::time::Instant::now() + STARTUP_ACTIVATION_GRACE);
39 }
40
41 /// True while we're still inside the post-startup grace window.
42 fn in_startup_activation_grace() -> bool {
43 STARTUP_ACTIVATION_GRACE_UNTIL
44 .get()
45 .is_some_and(|deadline| std::time::Instant::now() < *deadline)
46 }
47
48 // Helper macro equivalent to @fieldParentPtr in Zig
49 #[macro_export]
50 macro_rules! container_of {
51 ($ptr:expr, $container:path, $field:ident) => {{
52 let offset = {
53 let dummy = std::mem::MaybeUninit::<$container>::uninit();
54 let dummy_ptr = dummy.as_ptr();
55 let field_ptr = std::ptr::addr_of!((*dummy_ptr).$field);
56 (field_ptr as usize).wrapping_sub(dummy_ptr as usize)
57 };
58 ((($ptr as *const _) as usize).wrapping_sub(offset)) as *mut $container
59 }};
60 }
61
62 // Custom non-opaque layouts for FFI casting
63 #[repr(C)]
64 pub struct WlList {
65 pub prev: *mut WlList,
66 pub next: *mut WlList,
67 }
68
69 #[repr(C)]
70 pub struct WlListener {
71 pub link: WlList,
72 pub notify: Option<unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void)>,
73 }
74
75 #[repr(C)]
76 pub struct WlrRendererEvents {
77 pub destroy: ffi::wl_signal,
78 pub lost: ffi::wl_signal,
79 }
80
81 #[repr(C)]
82 pub struct WlrRendererFeatures {
83 pub output_color_transform: bool,
84 pub timeline: bool,
85 }
86
87 #[repr(C)]
88 pub struct WlrRenderer {
89 pub render_buffer_caps: u32,
90 pub events: WlrRendererEvents,
91 pub features: WlrRendererFeatures,
92 }
93
94 #[repr(C)]
95 pub struct WlrBackendFeatures {
96 pub timeline: bool,
97 }
98
99 #[repr(C)]
100 pub struct WlrBackendEvents {
101 pub destroy: ffi::wl_signal,
102 pub new_input: ffi::wl_signal,
103 pub new_output: ffi::wl_signal,
104 }
105
106 #[repr(C)]
107 pub struct WlrBackend {
108 pub impl_: *const std::ffi::c_void,
109 pub buffer_caps: u32,
110 pub features: WlrBackendFeatures,
111 pub events: WlrBackendEvents,
112 }
113
114 #[repr(C)]
115 pub struct WlrXdgShellEvents {
116 pub new_surface: ffi::wl_signal,
117 pub new_toplevel: ffi::wl_signal,
118 pub new_popup: ffi::wl_signal,
119 pub destroy: ffi::wl_signal,
120 }
121
122 #[repr(C)]
123 pub struct WlrXdgShell {
124 pub global: *mut ffi::wl_global,
125 pub version: u32,
126 pub clients: ffi::wl_list,
127 pub popup_grabs: ffi::wl_list,
128 pub ping_timeout: u32,
129 pub events: WlrXdgShellEvents,
130 }
131
132 #[repr(C)]
133 pub struct WlrXdgDecorationManagerV1Events {
134 pub new_toplevel_decoration: ffi::wl_signal,
135 pub destroy: ffi::wl_signal,
136 }
137
138 #[repr(C)]
139 pub struct WlrXdgDecorationManagerV1 {
140 pub global: *mut ffi::wl_global,
141 pub decorations: ffi::wl_list,
142 pub events: WlrXdgDecorationManagerV1Events,
143 }
144
145 #[repr(C)]
146 pub struct WlrXdgActivationV1Events {
147 pub destroy: ffi::wl_signal,
148 pub request_activate: ffi::wl_signal,
149 pub new_token: ffi::wl_signal,
150 }
151
152 #[repr(C)]
153 pub struct WlrXdgActivationV1 {
154 pub global: *mut ffi::wl_global,
155 pub token_timeout_msec: u32,
156 pub tokens: ffi::wl_list,
157 pub events: WlrXdgActivationV1Events,
158 }
159
160 #[repr(C)]
161 pub struct WlrCursorShapeManagerV1Events {
162 pub request_set_shape: ffi::wl_signal,
163 pub destroy: ffi::wl_signal,
164 }
165
166 #[repr(C)]
167 pub struct WlrCursorShapeManagerV1 {
168 pub global: *mut ffi::wl_global,
169 pub events: WlrCursorShapeManagerV1Events,
170 }
171
172 #[repr(C)]
173 pub struct WlrExtForeignToplevelImageCaptureSourceManagerV1Events {
174 pub destroy: ffi::wl_signal,
175 pub new_request: ffi::wl_signal,
176 }
177
178 #[repr(C)]
179 pub struct WlrExtForeignToplevelImageCaptureSourceManagerV1 {
180 pub global: *mut ffi::wl_global,
181 pub events: WlrExtForeignToplevelImageCaptureSourceManagerV1Events,
182 }
183
184 #[repr(C)]
185 pub struct WlrXwaylandEvents {
186 pub destroy: ffi::wl_signal,
187 pub ready: ffi::wl_signal,
188 pub new_surface: ffi::wl_signal,
189 pub remove_startup_info: ffi::wl_signal,
190 }
191
192 #[repr(C)]
193 pub struct WlrXwayland {
194 pub server: *mut std::ffi::c_void,
195 pub own_server: bool,
196 pub xwm: *mut std::ffi::c_void,
197 pub shell_v1: *mut std::ffi::c_void,
198 pub display_name: *const std::os::raw::c_char,
199 pub wl_display: *mut ffi::wl_display,
200 pub compositor: *mut ffi::wlr_compositor,
201 pub seat: *mut std::ffi::c_void,
202 pub events: WlrXwaylandEvents,
203 }
204
205 // Wayland list manipulation utilities
206 pub unsafe fn wl_list_insert(list: *mut WlList, elm: *mut WlList) {
207 if list.is_null() {
208 log::error!("wl_list_insert: list is null!");
209 return;
210 }
211 if elm.is_null() {
212 log::error!("wl_list_insert: elm is null!");
213 return;
214 }
215 if list == elm {
216 // Inserting a node after itself severs it into a self-loop while
217 // outside pointers may still reference it — always a caller bug
218 // (reachable when a stale head.prev names the node being inserted).
219 log::error!("wl_list_insert: elm == list, refusing self-insert");
220 return;
221 }
222 (*elm).prev = list;
223 (*elm).next = (*list).next;
224 (*(*list).next).prev = elm;
225 (*list).next = elm;
226 }
227
228 pub unsafe fn wl_list_remove(elm: *mut WlList) {
229 // Upstream libwayland nulls the removed element's pointers; this port
230 // originally left them stale, so a second remove — or any later
231 // tail/linked check against them — wrote through pointers into whatever
232 // list the node USED to be in, silently corrupting live members. That
233 // corruption class is how a status segment ended up self-looped and
234 // invisible to every re-link heal (the tray parked outside the right
235 // group after bar restarts). Match C semantics: null after unlinking,
236 // and no-op an already-removed node instead of dereferencing null.
237 if (*elm).prev.is_null() || (*elm).next.is_null() {
238 return;
239 }
240 (*(*elm).next).prev = (*elm).prev;
241 (*(*elm).prev).next = (*elm).next;
242 (*elm).prev = std::ptr::null_mut();
243 (*elm).next = std::ptr::null_mut();
244 }
245
246 pub unsafe fn wl_list_remove_and_reinit(elm: *mut WlList) {
247 wl_list_remove(elm);
248 (*elm).prev = elm;
249 (*elm).next = elm;
250 }
251
252 pub unsafe fn wl_signal_add(signal: *mut ffi::wl_signal, listener: *mut ffi::wl_listener) {
253 log::info!("wl_signal_add: signal={:?}, listener={:?}", signal, listener);
254 if signal.is_null() {
255 log::error!("wl_signal_add: signal is null!");
256 return;
257 }
258 let sig_list = &mut (*signal).listener_list as *mut ffi::wl_list as *mut WlList;
259 log::info!("wl_signal_add: sig_list={:?}, prev={:?}, next={:?}", sig_list, (*sig_list).prev, (*sig_list).next);
260 let listener_custom = listener as *mut WlListener;
261 wl_list_insert((*sig_list).prev, &mut (*listener_custom).link);
262 }
263
264 pub unsafe fn wl_listener_remove(listener: *mut ffi::wl_listener) {
265 let listener_custom = listener as *mut WlListener;
266 wl_list_remove(&mut (*listener_custom).link);
267 }
268
269
270 pub struct Server {
271 pub wl_server: *mut ffi::wl_display,
272 pub sigint_source: *mut ffi::wl_event_source,
273 pub sigterm_source: *mut ffi::wl_event_source,
274 pub sigchld_source: *mut ffi::wl_event_source,
275 // pub fixes: *mut ffi::wlr_fixes,
276 pub backend: *mut ffi::wlr_backend,
277 pub session: *mut ffi::wlr_session,
278 pub renderer: *mut ffi::wlr_renderer,
279 pub allocator: *mut ffi::wlr_allocator,
280 pub gpu_reset_recover: *mut ffi::wl_event_source,
281 pub security_context_manager: *mut ffi::wlr_security_context_manager_v1,
282 pub shm: *mut ffi::wlr_shm,
283 pub linux_dmabuf: *mut ffi::wlr_linux_dmabuf_v1,
284 pub linux_drm_syncobj_manager: *mut ffi::wlr_linux_drm_syncobj_manager_v1,
285 pub single_pixel_buffer_manager: *mut ffi::wlr_single_pixel_buffer_manager_v1,
286 pub alpha_modifier: *mut ffi::wlr_alpha_modifier_v1,
287 pub color_manager: *mut ffi::wlr_color_manager_v1,
288 // pub color_representation_manager: *mut ffi::wlr_color_representation_manager_v1,
289 pub viewporter: *mut ffi::wlr_viewporter,
290 pub fractional_scale_manager: *mut ffi::wlr_fractional_scale_manager_v1,
291 pub compositor: *mut ffi::wlr_compositor,
292 pub subcompositor: *mut ffi::wlr_subcompositor,
293 pub cursor_shape_manager: *mut ffi::wlr_cursor_shape_manager_v1,
294 pub xdg_shell: *mut ffi::wlr_xdg_shell,
295 pub xdg_decoration_manager: *mut ffi::wlr_xdg_decoration_manager_v1,
296 pub xdg_activation: *mut ffi::wlr_xdg_activation_v1,
297 pub xdg_foreign_registry: *mut ffi::wlr_xdg_foreign_registry,
298 pub xdg_foreign_v2: *mut ffi::wlr_xdg_foreign_v2,
299 pub data_device_manager: *mut ffi::wlr_data_device_manager,
300 pub primary_selection_manager: *mut ffi::wlr_primary_selection_v1_device_manager,
301 pub data_control_manager: *mut ffi::wlr_ext_data_control_manager_v1,
302 pub wlr_data_control_manager: *mut ffi::wlr_data_control_manager_v1,
303 pub export_dmabuf_manager: *mut ffi::wlr_export_dmabuf_manager_v1,
304 pub screencopy_manager: *mut ffi::wlr_screencopy_manager_v1,
305 pub image_copy_capture_manager: *mut ffi::wlr_ext_image_copy_capture_manager_v1,
306 pub output_image_capture_source_manager: *mut ffi::wlr_ext_output_image_capture_source_manager_v1,
307 pub wlr_foreign_toplevel_manager: *mut ffi::wlr_foreign_toplevel_manager_v1,
308 pub foreign_toplevel_list: *mut ffi::wlr_ext_foreign_toplevel_list_v1,
309 // pub toplevel_capture_source_manager: *mut ffi::wlr_ext_foreign_toplevel_image_capture_source_manager_v1,
310 pub tearing_control_manager: *mut ffi::wlr_tearing_control_manager_v1,
311
312 pub xwayland: *mut ffi::wlr_xwayland,
313
314 // Subcomponents
315 pub wm: WindowManager,
316 pub xkb_bindings: XkbBindings,
317 pub layer_shell: LayerShell,
318 pub scene: Scene,
319 pub om: OutputManager,
320 pub input_manager: InputManager,
321 pub libinput_config: LibinputConfig,
322 pub xkb_config: XkbConfig,
323 pub idle_inhibit_manager: IdleInhibitManager,
324 pub idle: IdleManager,
325 pub lock_manager: LockManager,
326 pub inspector: crate::inspector::Inspector,
327 pub cce_window_management: crate::cce_window_management::CceWindowManagement,
328
329 // Event listeners
330 pub renderer_lost: ffi::wl_listener,
331 pub new_xdg_toplevel: ffi::wl_listener,
332 pub new_toplevel_decoration: ffi::wl_listener,
333 pub request_activate: ffi::wl_listener,
334 pub request_set_cursor_shape: ffi::wl_listener,
335 // pub toplevel_capture_request: ffi::wl_listener,
336 pub new_xsurface: ffi::wl_listener,
337 pub xwayland_ready: ffi::wl_listener,
338 }
339
340 unsafe extern "C" fn terminate(_signum: std::os::raw::c_int, data: *mut std::ffi::c_void) -> std::os::raw::c_int {
341 let wl_server = data as *mut ffi::wl_display;
342 ffi::wl_display_terminate(wl_server);
343 0
344 }
345
346 unsafe extern "C" fn handle_sigchld(_signum: std::os::raw::c_int, _data: *mut std::ffi::c_void) -> std::os::raw::c_int {
347 let mut status = 0;
348 while libc::waitpid(-1, &mut status, libc::WNOHANG) > 0 {}
349 0
350 }
351
352 unsafe extern "C" fn handle_renderer_lost(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
353 let _server = container_of!(listener, Server, renderer_lost);
354 log::info!("received GPU reset event");
355 }
356
357 unsafe extern "C" fn handle_new_xdg_toplevel(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
358 let server = container_of!(listener, Server, new_xdg_toplevel);
359 let xdg_toplevel = data as *mut ffi::wlr_xdg_toplevel;
360 log::info!("new xdg toplevel surface");
361 if let Err(e) = crate::xdg_toplevel::XdgToplevel::create(xdg_toplevel, server) {
362 log::error!("Failed to create xdg toplevel: {}", e);
363 let client = ffi::wl_resource_get_client((*xdg_toplevel).resource);
364 ffi::wl_client_post_no_memory(client);
365 }
366 }
367
368 unsafe extern "C" fn handle_new_toplevel_decoration(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
369 let _server = container_of!(listener, Server, new_toplevel_decoration);
370 let decoration = data as *mut ffi::wlr_xdg_toplevel_decoration_v1;
371 log::info!("new toplevel decoration");
372 crate::xdg_toplevel::XdgDecoration::init(decoration);
373 }
374
375 unsafe extern "C" fn handle_request_activate(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
376 let server = container_of!(listener, Server, request_activate);
377 log::info!("xdg activation request received");
378 if data.is_null() {
379 return;
380 }
381 let event = data as *mut ffi::wlr_xdg_activation_v1_request_activate_event;
382 let surface = (*event).surface;
383 if surface.is_null() {
384 return;
385 }
386
387 let default_seat = (*server).input_manager.default_seat;
388 if !default_seat.is_null() {
389 let focused_surf = ffi::river_wlr_seat_get_keyboard_focused_surface((*default_seat).wlr_seat);
390 if focused_surf == surface {
391 log::info!("xdg activation request ignored: window is already focused");
392 return;
393 }
394 }
395
396 for &win_ptr in (*server).wm.windows.iter() {
397 if !win_ptr.is_null() && (*win_ptr).root_surface() == surface {
398 let title_ptr = (*win_ptr).get_title();
399 let title = if title_ptr.is_null() {
400 "Window".to_string()
401 } else {
402 std::ffi::CStr::from_ptr(title_ptr).to_string_lossy().into_owned()
403 };
404
405 let app_id_ptr = (*win_ptr).get_app_id();
406 let app_id = if app_id_ptr.is_null() {
407 "unknown".to_string()
408 } else {
409 std::ffi::CStr::from_ptr(app_id_ptr).to_string_lossy().into_owned()
410 };
411
412 log::info!("xdg activation request for window '{}' ({})", title, app_id);
413
414 if in_startup_activation_grace() {
415 log::info!(
416 "activation notification suppressed during startup grace for '{}' ({})",
417 title,
418 app_id
419 );
420 break;
421 }
422
423 // A valid token (wlroots has already checked it against a recent
424 // input serial or the requesting surface's focus) is the user's
425 // intent to see this window, so honour it like `ccectl
426 // focus-window`: un-minimize, focus and raise — `Seat::focus`
427 // raises a Floating window itself, and `raise_window` covers
428 // the rest. Until now this handler only fired the "needs
429 // attention" notification, so an activation for an already-mapped
430 // window changed nothing: the window stayed beneath whatever
431 // covered it. A window that has not mapped yet (the request
432 // often lands between app_id and map) is left to the map path,
433 // which focuses new windows under its own settle rules.
434 if matches!((*win_ptr).state, crate::window::WindowState::Mapped) && !(*win_ptr).is_shy() {
435 if let Some(seat) = (*server).wm.first_seat() {
436 if (*win_ptr).minimized {
437 (*win_ptr).minimized = false;
438 }
439 (*seat).focus(crate::seat::Focus::Window(win_ptr));
440 (*server).wm.raise_window(win_ptr);
441 (*server).wm.dirty_windowing();
442 log::info!("xdg activation focused and raised '{}' ({})", title, app_id);
443 }
444 } else {
445 log::info!("xdg activation for unmapped window '{}' ({}): left to the map path", title, app_id);
446 }
447
448 let uid = unsafe { libc::getuid() };
449 let bus_address = format!("unix:path=/run/user/{}/bus", uid);
450
451 std::process::Command::new("gdbus")
452 .env("DBUS_SESSION_BUS_ADDRESS", &bus_address)
453 .args([
454 "call",
455 "--session",
456 "--dest",
457 "org.kde.StatusNotifierWatcher",
458 "--object-path",
459 "/StatusInterface",
460 "--method",
461 "org.clear.StatusInterface.NotifyAttention",
462 &app_id,
463 &title,
464 ])
465 .spawn()
466 .ok();
467 break;
468 }
469 }
470 }
471
472 unsafe extern "C" fn handle_request_set_cursor_shape(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
473 let server = container_of!(listener, Server, request_set_cursor_shape);
474 let event = data as *mut ffi::wlr_cursor_shape_manager_v1_request_set_shape_event;
475
476 let wlr_seat = (*(*event).seat_client).seat;
477 let focused_client = ffi::river_wlr_seat_get_pointer_focused_client(wlr_seat);
478
479 let event_client = ffi::river_wlr_seat_client_get_client((*event).seat_client);
480 let wm_client = if !(*server).wm.object.is_null() {
481 ffi::wl_resource_get_client((*server).wm.object)
482 } else {
483 std::ptr::null_mut()
484 };
485 let is_wm = !wm_client.is_null() && event_client == wm_client;
486
487 let shape_name = ffi::wlr_cursor_shape_v1_name((*event).shape);
488 let shape_str = if shape_name.is_null() {
489 "unknown".to_string()
490 } else {
491 std::ffi::CStr::from_ptr(shape_name).to_string_lossy().into_owned()
492 };
493
494 log::debug!(
495 "set_cursor_shape: event_client={:?}, wm_client={:?}, is_wm={}, focused={:?}, shape={}",
496 event_client,
497 wm_client,
498 is_wm,
499 focused_client,
500 shape_str
501 );
502
503 if focused_client == (*event).seat_client || is_wm {
504 let seat = ffi::river_wlr_seat_get_data(wlr_seat) as *mut crate::seat::Seat;
505 if !seat.is_null() {
506 (*seat).cursor.set_xcursor(shape_name);
507 }
508 }
509 }
510
511 // unsafe extern "C" fn handle_toplevel_capture_request(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
512 // let _server = container_of!(listener, Server, toplevel_capture_request);
513 // log::info!("toplevel capture request");
514 // }
515
516 unsafe extern "C" fn handle_new_xwayland_surface(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
517 let server = container_of!(listener, Server, new_xsurface);
518 let xsurface = data as *mut ffi::wlr_xwayland_surface;
519 log::info!("new xwayland surface");
520
521 if (*xsurface).override_redirect {
522 if let Err(e) = crate::xwayland_override_redirect::XwaylandOverrideRedirect::create(xsurface, server) {
523 log::error!("Failed to create xwayland override redirect surface: {}", e);
524 }
525 } else {
526 if let Err(e) = crate::xwayland_window::XwaylandWindow::create(xsurface, server) {
527 log::error!("Failed to create xwayland window surface: {}", e);
528 }
529 }
530 }
531
532 /// The first field of `struct wl_interface`; bindgen leaves the type opaque.
533 #[repr(C)]
534 struct WlInterfaceHead {
535 name: *const std::os::raw::c_char,
536 }
537
538 /// Hide the xdg-output global from the Xwayland client while
539 /// `xwayland_hidpi` is on. Xwayland sizes its root window from xdg-output's
540 /// LOGICAL size when it can see one (1920x1200 on the scale-2 panel), and
541 /// then every X11 app draws at logical resolution and is upscaled — blurry.
542 /// Without xdg-output it falls back to the wl_output mode, the physical
543 /// pixel grid, and a DPI-aware X11 app renders sharp; the compositor draws
544 /// X11 surfaces at 1/scale and converts X11 geometry to match
545 /// (`xwayland_window.rs`). Every other client keeps seeing xdg-output.
546 unsafe extern "C" fn xwayland_global_filter(
547 client: *const ffi::wl_client,
548 global: *const ffi::wl_global,
549 data: *mut std::ffi::c_void,
550 ) -> bool {
551 let server = data as *mut Server;
552 if server.is_null() || (*server).xwayland.is_null() || !(*server).wm.xwayland_hidpi {
553 return true;
554 }
555 let xserver = (*((*server).xwayland as *mut WlrXwayland)).server as *mut ffi::wlr_xwayland_server;
556 if xserver.is_null() || (*xserver).client.is_null() || (*xserver).client as *const ffi::wl_client != client {
557 return true;
558 }
559 let iface = ffi::wl_global_get_interface(global) as *const WlInterfaceHead;
560 if iface.is_null() || (*iface).name.is_null() {
561 return true;
562 }
563 std::ffi::CStr::from_ptr((*iface).name).to_bytes() != b"zxdg_output_manager_v1"
564 }
565
566 unsafe extern "C" fn handle_xwayland_ready(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
567 let server = container_of!(listener, Server, xwayland_ready);
568 let xwayland_cast = (*server).xwayland as *mut WlrXwayland;
569 if !xwayland_cast.is_null() && !(*xwayland_cast).display_name.is_null() {
570 let display_name = std::ffi::CStr::from_ptr((*xwayland_cast).display_name)
571 .to_string_lossy()
572 .into_owned();
573 log::info!("Xwayland is ready on display {}", display_name);
574 std::env::set_var("DISPLAY", &display_name);
575
576 // Under `xwayland_hidpi` X11 is a physical-pixel world (see
577 // `xwayland_window::x11_scale`), so tell X11 clients the DPI that goes
578 // with it: Qt 6 (Houdini) and Xft-based toolkits read Xft.dpi and scale
579 // themselves to match. GTK on X11 wants GDK_SCALE in its own environment
580 // on top of this; that is the app launcher's to provide.
581 //
582 // `Xcursor.size` goes with them, and is where X11 clients get their
583 // cursor size from — the session deliberately exports no XCURSOR_SIZE,
584 // which would beat this resource (see `scripts/startcce`). It is the
585 // physical size for the same reason: an X11 cursor bitmap is drawn at
586 // 1/scale like the rest of the client's drawing (`seat.rs`,
587 // `handle_request_set_cursor`), so 24 logical pixels is 24*scale of
588 // them. Sent even at scale 1, because otherwise libXcursor guesses
589 // from the screen height and lands somewhere else entirely.
590 let s = crate::xwayland_window::x11_scale(server);
591 let dpi = (96.0 * s).round() as i32;
592 let cursor = (24.0 * s).round() as i32;
593 let mut resources = format!("Xcursor.size: {}\n", cursor);
594 if s != 1.0 {
595 resources.push_str(&format!("Xft.dpi: {}\n", dpi));
596 }
597 match std::process::Command::new("xrdb")
598 .args(["-merge", "-"])
599 .env("DISPLAY", &display_name)
600 .stdin(std::process::Stdio::piped())
601 .stdout(std::process::Stdio::null())
602 .stderr(std::process::Stdio::null())
603 .spawn()
604 {
605 Ok(mut child) => {
606 use std::io::Write;
607 if let Some(mut stdin) = child.stdin.take() {
608 let _ = write!(stdin, "{}", resources);
609 }
610 std::thread::spawn(move || { let _ = child.wait(); });
611 log::info!("Xwayland: X11 scale {} — set Xcursor.size {}{} via xrdb", s, cursor,
612 if s != 1.0 { format!(", Xft.dpi {}", dpi) } else { String::new() });
613 }
614 Err(e) => log::warn!("Xwayland: could not run xrdb to set X resources: {}", e),
615 }
616 }
617 }
618
619 impl Server {
620 pub fn init(&mut self, runtime_xwayland: bool) -> Result<(), &'static str> {
621 unsafe {
622 let wl_server = ffi::wl_display_create();
623 if wl_server.is_null() {
624 return Err("Failed to create wayland server");
625 }
626 self.wl_server = wl_server;
627
628 let loop_ = ffi::wl_display_get_event_loop(wl_server);
629 if loop_.is_null() {
630 return Err("Failed to get event loop");
631 }
632
633 self.sigint_source = ffi::wl_event_loop_add_signal(loop_, libc::SIGINT, Some(terminate), wl_server as *mut _);
634 self.sigterm_source = ffi::wl_event_loop_add_signal(loop_, libc::SIGTERM, Some(terminate), wl_server as *mut _);
635 self.sigchld_source = ffi::wl_event_loop_add_signal(loop_, libc::SIGCHLD, Some(handle_sigchld), wl_server as *mut _);
636
637 let mut session: *mut ffi::wlr_session = ptr::null_mut();
638 let backend = ffi::wlr_backend_autocreate(loop_, &mut session);
639 if backend.is_null() {
640 return Err("Failed to autocreate wlr_backend");
641 }
642 self.backend = backend;
643 self.session = session;
644
645 let renderer = ffi::fx_renderer_create(backend);
646 if renderer.is_null() {
647 return Err("Failed to create fx_renderer");
648 }
649 self.renderer = renderer;
650
651 let compositor = ffi::wlr_compositor_create(wl_server, 6, renderer);
652 if compositor.is_null() {
653 return Err("Failed to create wlr_compositor");
654 }
655 self.compositor = compositor;
656
657 let xdg_foreign_registry = ffi::wlr_xdg_foreign_registry_create(wl_server);
658 if xdg_foreign_registry.is_null() {
659 return Err("Failed to create xdg_foreign_registry");
660 }
661 self.xdg_foreign_registry = xdg_foreign_registry;
662
663 // let fixes = ffi::wlr_fixes_create(wl_server, 1);
664 // if fixes.is_null() {
665 // return Err("Failed to create wlr_fixes");
666 // }
667 // self.fixes = fixes;
668
669 let allocator = ffi::wlr_allocator_autocreate(backend, renderer);
670 if allocator.is_null() {
671 return Err("Failed to autocreate allocator");
672 }
673 self.allocator = allocator;
674
675 let security_context_manager = ffi::wlr_security_context_manager_v1_create(wl_server);
676 if security_context_manager.is_null() {
677 return Err("Failed to create security context manager");
678 }
679 self.security_context_manager = security_context_manager;
680
681 let shm = ffi::wlr_shm_create_with_renderer(wl_server, 2, renderer);
682 if shm.is_null() {
683 return Err("Failed to create shm");
684 }
685 self.shm = shm;
686
687 let single_pixel_buffer_manager = ffi::wlr_single_pixel_buffer_manager_v1_create(wl_server);
688 if single_pixel_buffer_manager.is_null() {
689 return Err("Failed to create single pixel buffer manager");
690 }
691 self.single_pixel_buffer_manager = single_pixel_buffer_manager;
692
693 let alpha_modifier = ffi::wlr_alpha_modifier_v1_create(wl_server);
694 if alpha_modifier.is_null() {
695 return Err("Failed to create alpha modifier");
696 }
697 self.alpha_modifier = alpha_modifier;
698
699 // let color_representation_manager = ffi::wlr_color_representation_manager_v1_create_with_renderer(wl_server, 1, renderer);
700 // if color_representation_manager.is_null() {
701 // return Err("Failed to create color representation manager");
702 // }
703 // self.color_representation_manager = color_representation_manager;
704
705 let viewporter = ffi::wlr_viewporter_create(wl_server);
706 if viewporter.is_null() {
707 return Err("Failed to create viewporter");
708 }
709 self.viewporter = viewporter;
710
711 let fractional_scale_manager = ffi::wlr_fractional_scale_manager_v1_create(wl_server, 1);
712 if fractional_scale_manager.is_null() {
713 return Err("Failed to create fractional scale manager");
714 }
715 self.fractional_scale_manager = fractional_scale_manager;
716
717 let subcompositor = ffi::wlr_subcompositor_create(wl_server);
718 if subcompositor.is_null() {
719 return Err("Failed to create subcompositor");
720 }
721 self.subcompositor = subcompositor;
722
723 let cursor_shape_manager = ffi::wlr_cursor_shape_manager_v1_create(wl_server, 1);
724 if cursor_shape_manager.is_null() {
725 return Err("Failed to create cursor shape manager");
726 }
727 self.cursor_shape_manager = cursor_shape_manager;
728
729 let xdg_shell = ffi::wlr_xdg_shell_create(wl_server, 5);
730 if xdg_shell.is_null() {
731 return Err("Failed to create xdg shell");
732 }
733 self.xdg_shell = xdg_shell;
734
735 let xdg_decoration_manager = ffi::wlr_xdg_decoration_manager_v1_create(wl_server);
736 if xdg_decoration_manager.is_null() {
737 return Err("Failed to create xdg decoration manager");
738 }
739 self.xdg_decoration_manager = xdg_decoration_manager;
740
741 let xdg_activation = ffi::wlr_xdg_activation_v1_create(wl_server);
742 if xdg_activation.is_null() {
743 return Err("Failed to create xdg activation");
744 }
745 self.xdg_activation = xdg_activation;
746
747 let xdg_foreign_v2 = ffi::wlr_xdg_foreign_v2_create(wl_server, xdg_foreign_registry);
748 if xdg_foreign_v2.is_null() {
749 return Err("Failed to create xdg foreign v2");
750 }
751 self.xdg_foreign_v2 = xdg_foreign_v2;
752
753 let data_device_manager = ffi::wlr_data_device_manager_create(wl_server);
754 if data_device_manager.is_null() {
755 return Err("Failed to create data device manager");
756 }
757 self.data_device_manager = data_device_manager;
758
759 let primary_selection_manager = ffi::wlr_primary_selection_v1_device_manager_create(wl_server);
760 if primary_selection_manager.is_null() {
761 return Err("Failed to create primary selection manager");
762 }
763 self.primary_selection_manager = primary_selection_manager;
764
765 let data_control_manager = ffi::wlr_ext_data_control_manager_v1_create(wl_server, 1);
766 if data_control_manager.is_null() {
767 return Err("Failed to create ext data control manager");
768 }
769 self.data_control_manager = data_control_manager;
770
771 let wlr_data_control_manager = ffi::wlr_data_control_manager_v1_create(wl_server);
772 if wlr_data_control_manager.is_null() {
773 return Err("Failed to create data control manager");
774 }
775 self.wlr_data_control_manager = wlr_data_control_manager;
776
777 let export_dmabuf_manager = ffi::wlr_export_dmabuf_manager_v1_create(wl_server);
778 if export_dmabuf_manager.is_null() {
779 return Err("Failed to create export dmabuf manager");
780 }
781 self.export_dmabuf_manager = export_dmabuf_manager;
782
783 let screencopy_manager = ffi::wlr_screencopy_manager_v1_create(wl_server);
784 if screencopy_manager.is_null() {
785 return Err("Failed to create screencopy manager");
786 }
787 self.screencopy_manager = screencopy_manager;
788
789 let image_copy_capture_manager = ffi::wlr_ext_image_copy_capture_manager_v1_create(wl_server, 1);
790 if image_copy_capture_manager.is_null() {
791 return Err("Failed to create image copy capture manager");
792 }
793 self.image_copy_capture_manager = image_copy_capture_manager;
794
795 let output_image_capture_source_manager = ffi::wlr_ext_output_image_capture_source_manager_v1_create(wl_server, 1);
796 if output_image_capture_source_manager.is_null() {
797 return Err("Failed to create output image capture source manager");
798 }
799 self.output_image_capture_source_manager = output_image_capture_source_manager;
800
801 let wlr_foreign_toplevel_manager = ffi::wlr_foreign_toplevel_manager_v1_create(wl_server);
802 if wlr_foreign_toplevel_manager.is_null() {
803 return Err("Failed to create foreign toplevel manager");
804 }
805 self.wlr_foreign_toplevel_manager = wlr_foreign_toplevel_manager;
806
807 let foreign_toplevel_list = ffi::wlr_ext_foreign_toplevel_list_v1_create(wl_server, 1);
808 if foreign_toplevel_list.is_null() {
809 return Err("Failed to create foreign toplevel list");
810 }
811 self.foreign_toplevel_list = foreign_toplevel_list;
812
813 // let toplevel_capture_source_manager = ffi::wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(wl_server, 1);
814 // if toplevel_capture_source_manager.is_null() {
815 // return Err("Failed to create toplevel capture source manager");
816 // }
817 // self.toplevel_capture_source_manager = toplevel_capture_source_manager;
818
819 let tearing_control_manager = ffi::wlr_tearing_control_manager_v1_create(wl_server, 1);
820 if tearing_control_manager.is_null() {
821 return Err("Failed to create tearing control manager");
822 }
823 self.tearing_control_manager = tearing_control_manager;
824
825 // Setup Xwayland if runtime requested
826 if runtime_xwayland {
827 let xwayland = ffi::wlr_xwayland_create(wl_server, compositor, false);
828 if xwayland.is_null() {
829 return Err("Failed to create xwayland server");
830 }
831 self.xwayland = xwayland;
832 // See `xwayland_global_filter`.
833 ffi::wl_display_set_global_filter(
834 wl_server,
835 Some(xwayland_global_filter),
836 self as *mut Server as *mut std::ffi::c_void,
837 );
838 }
839
840 // Setup linux dmabuf if supported
841 if !ffi::wlr_renderer_get_texture_formats(renderer, ffi::wlr_buffer_cap_WLR_BUFFER_CAP_DMABUF).is_null() {
842 self.linux_dmabuf = ffi::wlr_linux_dmabuf_v1_create_with_renderer(wl_server, 5, renderer);
843 }
844
845 // Setup linux drm syncobj if supported
846 let renderer_cast = renderer as *mut WlrRenderer;
847 let backend_cast = backend as *mut WlrBackend;
848 if (*renderer_cast).features.timeline && (*backend_cast).features.timeline {
849 let drm_fd = ffi::wlr_renderer_get_drm_fd(renderer);
850 if drm_fd >= 0 {
851 self.linux_drm_syncobj_manager = ffi::wlr_linux_drm_syncobj_manager_v1_create(wl_server, 1, drm_fd);
852 }
853 }
854
855 // Setup color manager if supported
856 // (Commented out for wlroots 0.19 compatibility)
857 self.color_manager = std::ptr::null_mut();
858
859 // Setup subcomponents stubs
860 let server_ptr = self as *mut Server;
861 self.wm.init_with_server(server_ptr).map_err(|_| "Failed to init wm")?;
862 self.xkb_bindings.init(server_ptr, self.wl_server).map_err(|_| "Failed to init xkb_bindings")?;
863 self.layer_shell.init(server_ptr, self.wl_server).map_err(|_| "Failed to init layer_shell")?;
864 self.scene.init(self.linux_dmabuf, self.color_manager).map_err(|_| "Failed to init scene")?;
865 self.om.init(server_ptr).map_err(|_| "Failed to init om")?;
866 self.input_manager.init(server_ptr).map_err(|_| "Failed to init input_manager")?;
867 self.libinput_config.init(server_ptr).map_err(|_| "Failed to init libinput_config")?;
868 self.xkb_config.init(server_ptr).map_err(|_| "Failed to init xkb_config")?;
869 self.idle_inhibit_manager.init(server_ptr).map_err(|_| "Failed to init idle_inhibit_manager")?;
870 self.idle.init(server_ptr).map_err(|_| "Failed to init idle")?;
871 self.lock_manager.init(server_ptr).map_err(|_| "Failed to init lock_manager")?;
872 self.inspector.init(server_ptr).map_err(|_| "Failed to init inspector")?;
873 self.cce_window_management.init(server_ptr).map_err(|_| "Failed to init cce_window_management")?;
874
875 // Setup listeners
876 let r_lost = &mut self.renderer_lost as *mut ffi::wl_listener as *mut WlListener;
877 (*r_lost).notify = Some(handle_renderer_lost);
878
879 let new_xdg = &mut self.new_xdg_toplevel as *mut ffi::wl_listener as *mut WlListener;
880 (*new_xdg).notify = Some(handle_new_xdg_toplevel);
881
882 let new_dec = &mut self.new_toplevel_decoration as *mut ffi::wl_listener as *mut WlListener;
883 (*new_dec).notify = Some(handle_new_toplevel_decoration);
884
885 let req_act = &mut self.request_activate as *mut ffi::wl_listener as *mut WlListener;
886 (*req_act).notify = Some(handle_request_activate);
887
888 let req_cursor = &mut self.request_set_cursor_shape as *mut ffi::wl_listener as *mut WlListener;
889 (*req_cursor).notify = Some(handle_request_set_cursor_shape);
890
891 // let cap_req = &mut self.toplevel_capture_request as *mut ffi::wl_listener as *mut WlListener;
892 // (*cap_req).notify = Some(handle_toplevel_capture_request);
893
894 let xdg_shell_cast = self.xdg_shell as *mut WlrXdgShell;
895 let xdg_decoration_manager_cast = self.xdg_decoration_manager as *mut WlrXdgDecorationManagerV1;
896 let xdg_activation_cast = self.xdg_activation as *mut WlrXdgActivationV1;
897 let cursor_shape_manager_cast = self.cursor_shape_manager as *mut WlrCursorShapeManagerV1;
898 // let toplevel_capture_source_manager_cast = self.toplevel_capture_source_manager as *mut WlrExtForeignToplevelImageCaptureSourceManagerV1;
899
900 wl_signal_add(&mut (*renderer_cast).events.lost, &mut self.renderer_lost);
901 wl_signal_add(&mut (*xdg_shell_cast).events.new_toplevel, &mut self.new_xdg_toplevel);
902 wl_signal_add(&mut (*xdg_decoration_manager_cast).events.new_toplevel_decoration, &mut self.new_toplevel_decoration);
903 wl_signal_add(&mut (*xdg_activation_cast).events.request_activate, &mut self.request_activate);
904 wl_signal_add(&mut (*cursor_shape_manager_cast).events.request_set_shape, &mut self.request_set_cursor_shape);
905 // wl_signal_add(&mut (*toplevel_capture_source_manager_cast).events.new_request, &mut self.toplevel_capture_request);
906
907 // Register Xwayland surface listener if active
908 if !self.xwayland.is_null() {
909 let new_x = &mut self.new_xsurface as *mut ffi::wl_listener as *mut WlListener;
910 (*new_x).notify = Some(handle_new_xwayland_surface);
911
912 let xwayland_cast = self.xwayland as *mut WlrXwayland;
913 wl_signal_add(&mut (*xwayland_cast).events.new_surface, &mut self.new_xsurface);
914
915 let ready_x = &mut self.xwayland_ready as *mut ffi::wl_listener as *mut WlListener;
916 (*ready_x).notify = Some(handle_xwayland_ready);
917 wl_signal_add(&mut (*xwayland_cast).events.ready, &mut self.xwayland_ready);
918 }
919 }
920
921 Ok(())
922 }
923
924 pub fn deinit(&mut self) {
925 unsafe {
926 log::info!("[deinit] Server::deinit started");
927 // 1. Terminate all client connections first
928 log::info!("[deinit] wl_display_destroy_clients started");
929 ffi::wl_display_destroy_clients(self.wl_server);
930 log::info!("[deinit] wl_display_destroy_clients finished");
931
932 // 2. Deinitialize subcomponents while backend, renderer, allocator, and display are valid
933 log::info!("[deinit] self.om.deinit started");
934 self.om.deinit();
935 log::info!("[deinit] self.om.deinit finished");
936
937 log::info!("[deinit] self.input_manager.deinit started");
938 self.input_manager.deinit();
939 log::info!("[deinit] self.input_manager.deinit finished");
940
941 log::info!("[deinit] deinitializing other subcomponents");
942 self.idle.deinit();
943 self.idle_inhibit_manager.deinit();
944 self.lock_manager.deinit();
945 self.layer_shell.deinit();
946 self.inspector.deinit();
947 self.xkb_bindings.deinit();
948 self.libinput_config.deinit();
949 self.xkb_config.deinit();
950 log::info!("[deinit] other subcomponents deinitialized");
951
952 // 3. Remove signal listeners registered directly by the server
953 log::info!("[deinit] removing server listeners");
954 ffi::wl_event_source_remove(self.sigint_source);
955 ffi::wl_event_source_remove(self.sigterm_source);
956 if !self.sigchld_source.is_null() {
957 ffi::wl_event_source_remove(self.sigchld_source);
958 }
959
960 wl_listener_remove(&mut self.renderer_lost);
961 wl_listener_remove(&mut self.new_xdg_toplevel);
962 wl_listener_remove(&mut self.new_toplevel_decoration);
963 wl_listener_remove(&mut self.request_activate);
964 wl_listener_remove(&mut self.request_set_cursor_shape);
965
966 // 4. Destroy Xwayland if active
967 if !self.xwayland.is_null() {
968 wl_listener_remove(&mut self.new_xsurface);
969 wl_listener_remove(&mut self.xwayland_ready);
970 ffi::wlr_xwayland_destroy(self.xwayland);
971 }
972 log::info!("[deinit] server listeners removed");
973
974 // 5. Destroy wlroots core hardware interfaces
975 log::info!("[deinit] destroying backend");
976 ffi::wlr_backend_destroy(self.backend);
977 if !self.session.is_null() {
978 log::info!("[deinit] destroying session");
979 ffi::wlr_session_destroy(self.session);
980 }
981 log::info!("[deinit] destroying renderer");
982 ffi::wlr_renderer_destroy(self.renderer);
983 log::info!("[deinit] destroying allocator");
984 ffi::wlr_allocator_destroy(self.allocator);
985
986 // 6. Finally, destroy the display
987 log::info!("[deinit] destroying display");
988 ffi::wl_display_destroy(self.wl_server);
989 log::info!("[deinit] Server::deinit finished successfully");
990 }
991 }
992 }
993
994 impl Default for Server {
995 fn default() -> Self {
996 let mut server = std::mem::MaybeUninit::<Server>::uninit();
997 unsafe {
998 // Zero-initialize the memory (C structures and primitive fields)
999 std::ptr::write_bytes(server.as_mut_ptr(), 0, 1);
1000 // Overwrite collections and SlotMap with valid instances to avoid UB/segfaults from null pointers
1001 std::ptr::write(&mut (*server.as_mut_ptr()).wm.windows, crate::slotmap::SlotMap::new());
1002 std::ptr::write(&mut (*server.as_mut_ptr()).wm.focus_history, Vec::new());
1003 std::ptr::write(&mut (*server.as_mut_ptr()).wm.mode_rules, Vec::new());
1004 std::ptr::write(&mut (*server.as_mut_ptr()).wm.keybinds, Vec::new());
1005 std::ptr::write(&mut (*server.as_mut_ptr()).wm.pointer_binds, Vec::new());
1006 std::ptr::write(&mut (*server.as_mut_ptr()).wm.gesture_binds, Vec::new());
1007 std::ptr::write(&mut (*server.as_mut_ptr()).wm.ipc_rx, None);
1008 // Same reason as ipc_rx above, and not optional: an mpsc endpoint
1009 // has no null niche, so `Option` tags it out of band and zeroed
1010 // bytes decode as `Some(<null channel>)` — dropping that segfaults.
1011 // pending_screenshot holds one too (its deferred IPC reply), which
1012 // is what makes zeroed bytes decode as a live `Some` there as well.
1013 std::ptr::write(&mut (*server.as_mut_ptr()).wm.pending_ipc_reply, None);
1014 std::ptr::write(&mut (*server.as_mut_ptr()).wm.pending_screenshot, None);
1015 std::ptr::write(&mut (*server.as_mut_ptr()).wm.startup, Vec::new());
1016 std::ptr::write(&mut (*server.as_mut_ptr()).wm.startup_pids, Vec::new());
1017 std::ptr::write(&mut (*server.as_mut_ptr()).wm.status_sender, None);
1018 // A zeroed Vec is a null data pointer, which Vec's NonNull
1019 // invariant forbids — the same reason `startup` above is written
1020 // explicitly rather than left to the zeroed MaybeUninit.
1021 std::ptr::write(&mut (*server.as_mut_ptr()).wm.status_backdrops, std::cell::RefCell::new(Vec::new()));
1022 std::ptr::write(&mut (*server.as_mut_ptr()).wm.last_saved_state_json, None);
1023 std::ptr::write(&mut (*server.as_mut_ptr()).layer_shell.surfaces, crate::slotmap::SlotMap::new());
1024 std::ptr::write(&mut (*server.as_mut_ptr()).inspector, crate::inspector::Inspector::new());
1025 std::ptr::write(&mut (*server.as_mut_ptr()).cce_window_management, crate::cce_window_management::CceWindowManagement::new());
1026 server.assume_init()
1027 }
1028 }
1029 }