1use std::collections::HashMap;
2use std::os::fd::OwnedFd;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::mpsc;
6use std::time::Instant;
7
8use smithay::backend::allocator::dmabuf::{Dmabuf, DmabufMappingMode};
9use smithay::backend::allocator::{Buffer, Fourcc, Modifier, Format as DmabufFormat};
10use smithay::backend::input::{Axis, ButtonState, KeyState};
11use smithay::backend::renderer::pixman::PixmanRenderer;
12use smithay::delegate_compositor;
13use smithay::delegate_cursor_shape;
14use smithay::delegate_data_device;
15use smithay::delegate_dmabuf;
16use smithay::delegate_fractional_scale;
17use smithay::delegate_output;
18use smithay::delegate_text_input_manager;
19use smithay::delegate_primary_selection;
20use smithay::delegate_seat;
21use smithay::delegate_shm;
22use smithay::delegate_viewporter;
23use smithay::delegate_xdg_activation;
24use smithay::delegate_xdg_decoration;
25use smithay::delegate_xdg_shell;
26use smithay::delegate_xdg_toplevel_icon;
27use smithay::desktop::{Space, Window};
28use smithay::input::keyboard::{FilterResult, XkbConfig};
29use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};
30use smithay::input::{Seat, SeatHandler, SeatState};
31use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
32use smithay::reexports::calloop::generic::Generic;
33use smithay::reexports::calloop::{EventLoop, Interest, LoopSignal, PostAction};
34use smithay::reexports::wayland_server::protocol::wl_buffer;
35use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
36use smithay::reexports::wayland_server::protocol::wl_shm;
37use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
38use smithay::reexports::wayland_server::{Client, Display, DisplayHandle, Resource};
39use smithay::utils::{Serial, Transform, SERIAL_COUNTER};
40use smithay::wayland::buffer::BufferHandler;
41use smithay::wayland::compositor::{
42 self, CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes,
43 with_states, with_surface_tree_downward, TraversalAction,
44};
45use smithay::wayland::output::OutputHandler;
46use smithay::wayland::selection::data_device::{
47 ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
48 set_data_device_focus, set_data_device_selection, request_data_device_client_selection,
49};
50use smithay::wayland::selection::{SelectionHandler, SelectionSource, SelectionTarget};
51use smithay::wayland::shell::xdg::{
52 PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
53 XdgToplevelSurfaceData,
54};
55use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf};
56use smithay::wayland::shell::xdg::decoration::{XdgDecorationHandler, XdgDecorationState};
57use smithay::wayland::shm::{BufferData, ShmHandler, ShmState, with_buffer_contents};
58use smithay::wayland::socket::ListeningSocketSource;
59use smithay::wayland::cursor_shape::CursorShapeManagerState;
60use smithay::wayland::tablet_manager::TabletSeatHandler;
61use smithay::wayland::fractional_scale::{FractionalScaleHandler, FractionalScaleManagerState};
62use smithay::wayland::selection::primary_selection::{PrimarySelectionHandler, PrimarySelectionState, set_primary_focus};
63use smithay::wayland::text_input::TextInputManagerState;
64use smithay::wayland::viewporter::ViewporterState;
65use smithay::wayland::xdg_activation::{
66 XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
67};
68use smithay::wayland::xdg_toplevel_icon::XdgToplevelIconHandler;
69use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;
70
71#[derive(Clone)]
76pub enum PixelData {
77 Bgra(Arc<Vec<u8>>),
80 Rgba(Arc<Vec<u8>>),
83 Nv12 {
88 data: Arc<Vec<u8>>,
89 y_stride: usize,
90 uv_stride: usize,
91 },
92 DmaBuf {
99 fd: Arc<std::os::fd::OwnedFd>,
100 fourcc: u32,
102 modifier: u64,
104 stride: u32,
105 offset: u32,
106 },
107}
108
109pub mod drm_fourcc {
111 pub const ARGB8888: u32 = u32::from_le_bytes(*b"AR24");
113 pub const XRGB8888: u32 = u32::from_le_bytes(*b"XR24");
115 pub const ABGR8888: u32 = u32::from_le_bytes(*b"AB24");
117 pub const XBGR8888: u32 = u32::from_le_bytes(*b"XB24");
119 pub const NV12: u32 = u32::from_le_bytes(*b"NV12");
121}
122
123impl PixelData {
124 pub fn to_rgba(&self, width: u32, height: u32) -> Vec<u8> {
127 let w = width as usize;
128 let h = height as usize;
129 match self {
130 PixelData::Rgba(data) => data.as_ref().clone(),
131 PixelData::Bgra(data) => {
132 let mut rgba = Vec::with_capacity(w * h * 4);
133 for px in data.chunks_exact(4) {
134 rgba.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
135 }
136 rgba
137 }
138 PixelData::Nv12 {
139 data,
140 y_stride,
141 uv_stride,
142 } => {
143 let y_plane_size = *y_stride * h;
144 let uv_h = h.div_ceil(2);
145 let uv_plane_size = *uv_stride * uv_h;
146 if data.len() < y_plane_size + uv_plane_size {
147 return Vec::new();
148 }
149 let y_plane = &data[..y_plane_size];
150 let uv_plane = &data[y_plane_size..];
151 let mut rgba = Vec::with_capacity(w * h * 4);
152 for row in 0..h {
153 for col in 0..w {
154 let y = y_plane[row * y_stride + col];
155 let uv_idx = (row / 2) * uv_stride + (col / 2) * 2;
156 if uv_idx + 1 >= uv_plane.len() {
157 rgba.extend_from_slice(&[0, 0, 0, 255]);
158 continue;
159 }
160 let u = uv_plane[uv_idx];
161 let v = uv_plane[uv_idx + 1];
162 let [r, g, b] = yuv420_to_rgb(y, u, v);
163 rgba.extend_from_slice(&[r, g, b, 255]);
164 }
165 }
166 rgba
167 }
168 PixelData::DmaBuf { .. } => {
169 Vec::new()
173 }
174 }
175 }
176
177 pub fn is_empty(&self) -> bool {
178 match self {
179 PixelData::Bgra(v) | PixelData::Rgba(v) => v.is_empty(),
180 PixelData::Nv12 { data, .. } => data.is_empty(),
181 PixelData::DmaBuf { .. } => false,
182 }
183 }
184
185 pub fn is_dmabuf(&self) -> bool {
187 matches!(self, PixelData::DmaBuf { .. })
188 }
189}
190
191pub enum CompositorEvent {
192 SurfaceCreated {
193 surface_id: u16,
194 title: String,
195 app_id: String,
196 parent_id: u16,
197 width: u16,
198 height: u16,
199 },
200 SurfaceDestroyed {
201 surface_id: u16,
202 },
203 SurfaceCommit {
204 surface_id: u16,
205 width: u32,
206 height: u32,
207 pixels: PixelData,
208 },
209 SurfaceTitle {
210 surface_id: u16,
211 title: String,
212 },
213 SurfaceAppId {
214 surface_id: u16,
215 app_id: String,
216 },
217 SurfaceResized {
218 surface_id: u16,
219 width: u16,
220 height: u16,
221 },
222 ClipboardContent {
223 surface_id: u16,
224 mime_type: String,
225 data: Vec<u8>,
226 },
227}
228
229pub enum CompositorCommand {
230 KeyInput {
231 surface_id: u16,
232 keycode: u32,
233 pressed: bool,
234 },
235 PointerMotion {
236 surface_id: u16,
237 x: f64,
238 y: f64,
239 },
240 PointerButton {
241 surface_id: u16,
242 button: u32,
243 pressed: bool,
244 },
245 PointerAxis {
246 surface_id: u16,
247 axis: u8,
248 value: f64,
249 },
250 SurfaceResize {
251 surface_id: u16,
252 width: u16,
253 height: u16,
254 scale_120: u16,
256 },
257 SurfaceFocus {
258 surface_id: u16,
259 },
260 SurfaceClose {
261 surface_id: u16,
262 },
263 ClipboardOffer {
264 surface_id: u16,
265 mime_type: String,
266 data: Vec<u8>,
267 },
268 Capture {
269 surface_id: u16,
270 reply: mpsc::SyncSender<Option<(u32, u32, Vec<u8>)>>,
271 },
272 RequestFrame {
276 surface_id: u16,
277 },
278 ReleaseKeys {
282 keycodes: Vec<u32>,
283 },
284 Shutdown,
285}
286
287struct SurfaceInfo {
288 surface_id: u16,
289 window: Window,
290 last_width: u32,
291 last_height: u32,
292 last_title: String,
293 last_app_id: String,
294}
295
296struct ClientData {
297 compositor_state: CompositorClientState,
298}
299
300impl smithay::reexports::wayland_server::backend::ClientData for ClientData {
301 fn initialized(&self, _client_id: smithay::reexports::wayland_server::backend::ClientId) {}
302 fn disconnected(
303 &self,
304 _client_id: smithay::reexports::wayland_server::backend::ClientId,
305 _reason: smithay::reexports::wayland_server::backend::DisconnectReason,
306 ) {
307 }
308}
309
310pub struct Compositor {
311 display_handle: DisplayHandle,
312 compositor_state: CompositorState,
313 xdg_shell_state: XdgShellState,
314 shm_state: ShmState,
315 seat_state: SeatState<Self>,
316 data_device_state: DataDeviceState,
317 #[allow(dead_code)]
318 viewporter_state: ViewporterState,
319 #[allow(dead_code)]
320 xdg_decoration_state: XdgDecorationState,
321 dmabuf_state: DmabufState,
322 #[allow(dead_code)]
323 dmabuf_global: DmabufGlobal,
324 primary_selection_state: PrimarySelectionState,
325 activation_state: XdgActivationState,
326 seat: Seat<Self>,
327 #[allow(dead_code)]
328 output: Output,
329 space: Space<Window>,
330
331 surfaces: HashMap<u64, SurfaceInfo>,
332 surface_lookup: HashMap<u16, u64>,
333 next_surface_id: u16,
334
335 event_tx: mpsc::Sender<CompositorEvent>,
336 event_notify: Arc<dyn Fn() + Send + Sync>,
337 loop_signal: LoopSignal,
338
339 #[allow(dead_code)]
340 renderer: PixmanRenderer,
341
342 verbose: bool,
343
344 focused_surface_id: u16,
346
347 pending_commits: HashMap<u16, (u32, u32, PixelData)>,
351}
352
353impl Compositor {
354 fn flush_pending_commits(&mut self) {
358 for (surface_id, (width, height, pixels)) in self.pending_commits.drain() {
359 let _ = self.event_tx.send(CompositorEvent::SurfaceCommit {
360 surface_id,
361 width,
362 height,
363 pixels,
364 });
365 }
366 (self.event_notify)();
367 }
368
369 fn allocate_surface_id(&mut self) -> u16 {
370 let mut id = self.next_surface_id;
373 let start = id;
374 loop {
375 if !self.surface_lookup.contains_key(&id) {
376 break;
377 }
378 id = id.wrapping_add(1);
379 if id == 0 {
380 id = 1;
381 }
382 if id == start {
383 break;
385 }
386 }
387 self.next_surface_id = id.wrapping_add(1);
388 if self.next_surface_id == 0 {
389 self.next_surface_id = 1;
390 }
391 id
392 }
393
394 fn handle_command(&mut self, cmd: CompositorCommand) {
395 match cmd {
396 CompositorCommand::KeyInput {
397 surface_id,
398 keycode,
399 pressed,
400 } => {
401 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
402 && let Some(info) = self.surfaces.get(&obj_id)
403 && let Some(toplevel) = info.window.toplevel()
404 && let Some(keyboard) = self.seat.get_keyboard()
405 {
406 if self.verbose {
407 eprintln!(
408 "[compositor] key: sid={surface_id} evdev={keycode} pressed={pressed}"
409 );
410 }
411 let serial = SERIAL_COUNTER.next_serial();
412 let time = elapsed_ms();
413 let state = if pressed {
414 KeyState::Pressed
415 } else {
416 KeyState::Released
417 };
418 keyboard.set_focus(self, Some(toplevel.wl_surface().clone()), serial);
419 keyboard.input::<(), _>(
423 self,
424 (keycode + 8).into(),
425 state,
426 serial,
427 time,
428 |_, _, _| FilterResult::Forward,
429 );
430 }
431 }
432 CompositorCommand::PointerMotion { surface_id, x, y } => {
433 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
434 && let Some(info) = self.surfaces.get(&obj_id)
435 && let Some(toplevel) = info.window.toplevel()
436 && let Some(pointer) = self.seat.get_pointer()
437 {
438 let serial = SERIAL_COUNTER.next_serial();
439 let time = elapsed_ms();
440 let wl_surface = toplevel.wl_surface().clone();
441 if pointer.is_grabbed() {
449 let stale = pointer
450 .grab_start_data()
451 .and_then(|d| d.focus.as_ref().map(|(s, _)| s.id() != wl_surface.id()))
452 .unwrap_or(false);
453 if stale {
454 pointer.unset_grab(self, serial, time);
455 }
456 }
457 pointer.motion(
458 self,
459 Some((wl_surface, (0.0, 0.0).into())),
460 &MotionEvent {
461 location: (x, y).into(),
462 serial,
463 time,
464 },
465 );
466 pointer.frame(self);
467 }
468 }
469 CompositorCommand::PointerButton {
470 surface_id,
471 button,
472 pressed,
473 } => {
474 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
475 && self.surfaces.contains_key(&obj_id)
476 && let Some(pointer) = self.seat.get_pointer()
477 {
478 let serial = SERIAL_COUNTER.next_serial();
479 let state = if pressed {
480 ButtonState::Pressed
481 } else {
482 ButtonState::Released
483 };
484 pointer.button(
485 self,
486 &ButtonEvent {
487 button,
488 state,
489 serial,
490 time: elapsed_ms(),
491 },
492 );
493 pointer.frame(self);
494 }
495 }
496 CompositorCommand::PointerAxis {
497 surface_id: _,
498 axis,
499 value,
500 } => {
501 let Some(pointer) = self.seat.get_pointer() else {
502 return;
503 };
504 let ax = if axis == 0 {
505 Axis::Vertical
506 } else {
507 Axis::Horizontal
508 };
509 pointer.axis(self, AxisFrame::new(elapsed_ms()).value(ax, value));
510 pointer.frame(self);
511 }
512 CompositorCommand::SurfaceResize {
513 surface_id: _,
514 width,
515 height,
516 scale_120,
517 } => {
518 let scale_frac = if scale_120 >= 120 {
521 scale_120 as f64
522 } else {
523 120.0
524 };
525 let cur = self.output.current_scale().fractional_scale();
526 if (cur - scale_frac).abs() > 0.01 {
527 let int_scale = ((scale_frac / 120.0) + 0.5) as i32;
529 self.output.change_current_state(
530 None,
531 None,
532 Some(smithay::output::Scale::Custom {
533 advertised_integer: int_scale.max(1),
534 fractional: scale_frac,
535 }),
536 None,
537 );
538 }
539
540 let scale_f = scale_frac / 120.0;
543 let logical_w = ((width as f64) / scale_f).round() as i32;
544 let logical_h = ((height as f64) / scale_f).round() as i32;
545
546 let mode = smithay::output::Mode {
548 size: (width as i32, height as i32).into(),
549 refresh: 60_000,
550 };
551 self.output
552 .change_current_state(Some(mode), None, None, None);
553 self.output.set_preferred(mode);
554
555 for info in self.surfaces.values() {
557 if let Some(toplevel) = info.window.toplevel() {
558 toplevel.with_pending_state(|state| {
559 state.size = Some((logical_w.max(1), logical_h.max(1)).into());
560 });
561 toplevel.send_pending_configure();
562 }
563 }
564 self.space.refresh();
567 }
568 CompositorCommand::SurfaceFocus { surface_id } => {
569 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
570 && let Some(info) = self.surfaces.get(&obj_id)
571 && let Some(toplevel) = info.window.toplevel()
572 && let Some(keyboard) = self.seat.get_keyboard()
573 {
574 let serial = SERIAL_COUNTER.next_serial();
575 keyboard.set_focus(self, Some(toplevel.wl_surface().clone()), serial);
576 }
577 }
578 CompositorCommand::SurfaceClose { surface_id } => {
579 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
580 && let Some(info) = self.surfaces.get(&obj_id)
581 && let Some(toplevel) = info.window.toplevel()
582 {
583 toplevel.send_close();
584 }
585 }
586 CompositorCommand::ClipboardOffer {
587 surface_id: _,
588 mime_type,
589 data,
590 } => {
591 let mime_types = if mime_type == "text/plain" {
594 vec![
595 "text/plain".to_string(),
596 "text/plain;charset=utf-8".to_string(),
597 "UTF8_STRING".to_string(),
598 "TEXT".to_string(),
599 "STRING".to_string(),
600 ]
601 } else {
602 vec![mime_type]
603 };
604 set_data_device_selection(
605 &self.display_handle,
606 &self.seat,
607 mime_types,
608 Arc::new(data),
609 );
610 }
611 CompositorCommand::Capture { surface_id, reply } => {
612 let result = if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
613 && let Some(info) = self.surfaces.get(&obj_id)
614 && let Some(toplevel) = info.window.toplevel()
615 {
616 let wl_surface = toplevel.wl_surface().clone();
617 self.read_surface_pixels(&wl_surface)
618 } else {
619 None
620 };
621 let _ = reply.send(result);
622 }
623 CompositorCommand::RequestFrame { surface_id } => {
624 self.fire_frame_callbacks(surface_id);
625 }
626 CompositorCommand::ReleaseKeys { keycodes } => {
627 if let Some(keyboard) = self.seat.get_keyboard() {
628 let serial = SERIAL_COUNTER.next_serial();
629 let time = elapsed_ms();
630 for keycode in keycodes {
631 keyboard.input::<(), _>(
632 self,
633 (keycode + 8).into(),
634 KeyState::Released,
635 serial,
636 time,
637 |_, _, _| FilterResult::Forward,
638 );
639 }
640 }
641 }
642 CompositorCommand::Shutdown => {
643 self.loop_signal.stop();
644 }
645 }
646 }
647
648 fn fire_frame_callbacks(&self, surface_id: u16) {
650 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
651 && let Some(info) = self.surfaces.get(&obj_id)
652 && let Some(toplevel) = info.window.toplevel()
653 {
654 let surface = toplevel.wl_surface().clone();
655 let time = elapsed_ms();
656 let mut fired = 0u32;
657 with_surface_tree_downward(
658 &surface,
659 (),
660 |_, _, &()| TraversalAction::DoChildren(()),
661 |_, states, &()| {
662 for callback in states
663 .cached_state
664 .get::<SurfaceAttributes>()
665 .current()
666 .frame_callbacks
667 .drain(..)
668 {
669 callback.done(time);
670 fired += 1;
671 }
672 },
673 |_, _, &()| true,
674 );
675 if fired > 0 && self.verbose {
676 eprintln!("[compositor] fire_frame_callbacks sid={surface_id}: {fired}");
677 }
678 }
679 }
680
681 fn read_surface_pixels(&mut self, surface: &WlSurface) -> Option<(u32, u32, Vec<u8>)> {
682 let mut result: Option<(u32, u32, PixelData)> = None;
683 with_states(surface, |states| {
684 let mut guard = states.cached_state.get::<SurfaceAttributes>();
685 let attrs = guard.current();
686 if let Some(compositor::BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref() {
687 result = read_shm_buffer(buffer);
688 if result.is_none()
689 && let Ok(dmabuf) = get_dmabuf(buffer)
690 {
691 result = read_dmabuf_pixels(dmabuf);
692 }
693 }
694 });
695 result.map(|(w, h, pd)| (w, h, pd.to_rgba(w, h)))
697 }
698}
699
700fn read_shm_buffer(buffer: &wl_buffer::WlBuffer) -> Option<(u32, u32, PixelData)> {
703 let mut result = None;
704 let _ = with_buffer_contents(buffer, |ptr, len, data: BufferData| {
705 let width = data.width as u32;
706 let height = data.height as u32;
707 let stride = data.stride as usize;
708 let offset = data.offset as usize;
709 let pixel_data = unsafe { std::slice::from_raw_parts(ptr, len) };
710 let row_bytes = width as usize * 4;
711 let mut bgra = if stride == row_bytes
712 && offset == 0
713 && pixel_data.len() >= row_bytes * height as usize
714 {
715 pixel_data[..row_bytes * height as usize].to_vec()
716 } else {
717 let mut packed = Vec::with_capacity(row_bytes * height as usize);
718 for row in 0..height as usize {
719 let row_start = offset + row * stride;
720 let row_end = row_start + row_bytes;
721 if row_end <= pixel_data.len() {
722 packed.extend_from_slice(&pixel_data[row_start..row_end]);
723 }
724 }
725 packed
726 };
727 if data.format == wl_shm::Format::Xrgb8888 {
731 for px in bgra.chunks_exact_mut(4) {
732 px[3] = 255;
733 }
734 }
735 result = Some((width, height, PixelData::Bgra(Arc::new(bgra))));
736 });
737 result
738}
739
740fn elapsed_ms() -> u32 {
741 use std::sync::OnceLock;
742 static START: OnceLock<Instant> = OnceLock::new();
743 START.get_or_init(Instant::now).elapsed().as_millis() as u32
744}
745
746impl CompositorHandler for Compositor {
747 fn compositor_state(&mut self) -> &mut CompositorState {
748 &mut self.compositor_state
749 }
750
751 fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
752 &client.get_data::<ClientData>().unwrap().compositor_state
753 }
754
755 fn commit(&mut self, surface: &WlSurface) {
756 let key = surface.id().protocol_id() as u64;
757 let surface_id = match self.surfaces.get(&key) {
758 Some(info) => info.surface_id,
759 None => return,
760 };
761
762 let mut committed_buffer: Option<(u32, u32, PixelData)> = None;
763 let mut new_title = String::new();
764 let mut new_app_id = String::new();
765
766 let have_pending = self.pending_commits.contains_key(&surface_id);
770
771 with_states(surface, |states| {
772 let mut guard = states.cached_state.get::<SurfaceAttributes>();
773 let attrs = guard.current();
774 if let Some(compositor::BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref() {
775 if !have_pending {
776 committed_buffer = read_shm_buffer(buffer);
777 let shm_ok = committed_buffer.is_some();
778
779 if !shm_ok && let Ok(dmabuf) = get_dmabuf(buffer) {
780 committed_buffer = read_dmabuf_pixels(dmabuf);
781 }
782 if committed_buffer.is_none() {
783 eprintln!(
784 "compositor: commit with no readable buffer (shm_ok={shm_ok}, has_dmabuf={})",
785 get_dmabuf(buffer).is_ok()
786 );
787 }
788 }
789 buffer.release();
793 }
794
795 if let Some(data) = states.data_map.get::<XdgToplevelSurfaceData>() {
796 let lock = data.lock().unwrap();
797 new_title = lock.title.clone().unwrap_or_default();
798 new_app_id = lock.app_id.clone().unwrap_or_default();
799 }
800 });
801
802 if let Some(info) = self.surfaces.get_mut(&key)
803 && new_title != info.last_title
804 {
805 info.last_title = new_title.clone();
806 let _ = self.event_tx.send(CompositorEvent::SurfaceTitle {
807 surface_id,
808 title: new_title,
809 });
810 }
811
812 if let Some(info) = self.surfaces.get_mut(&key)
813 && new_app_id != info.last_app_id
814 {
815 info.last_app_id = new_app_id.clone();
816 let _ = self.event_tx.send(CompositorEvent::SurfaceAppId {
817 surface_id,
818 app_id: new_app_id,
819 });
820 }
821
822 if let Some((width, height, pixel_data)) = committed_buffer {
823 let info = self.surfaces.get_mut(&key).unwrap();
824 if width != info.last_width || height != info.last_height {
825 info.last_width = width;
826 info.last_height = height;
827 let _ = self.event_tx.send(CompositorEvent::SurfaceResized {
828 surface_id,
829 width: width as u16,
830 height: height as u16,
831 });
832 }
833
834 if !pixel_data.is_empty() {
835 self.pending_commits
839 .insert(surface_id, (width, height, pixel_data));
840 }
841 }
842
843 }
849}
850
851impl BufferHandler for Compositor {
852 fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
853}
854
855impl ShmHandler for Compositor {
856 fn shm_state(&self) -> &ShmState {
857 &self.shm_state
858 }
859}
860
861impl XdgShellHandler for Compositor {
862 fn xdg_shell_state(&mut self) -> &mut XdgShellState {
863 &mut self.xdg_shell_state
864 }
865
866 fn new_toplevel(&mut self, surface: ToplevelSurface) {
867 if self.verbose {
868 eprintln!("[compositor] new_toplevel");
869 }
870 let window = Window::new_wayland_window(surface.clone());
871 let wl_surface = surface.wl_surface().clone();
872 let key = wl_surface.id().protocol_id() as u64;
873 let surface_id = self.allocate_surface_id();
874
875 self.space.map_element(window.clone(), (0, 0), false);
876 self.space.refresh();
877
878 let info = SurfaceInfo {
879 surface_id,
880 window,
881 last_width: 0,
882 last_height: 0,
883 last_title: String::new(),
884 last_app_id: String::new(),
885 };
886 self.surfaces.insert(key, info);
887 self.surface_lookup.insert(surface_id, key);
888
889 surface.with_pending_state(|state| {
890 state.states.set(
891 smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State::Activated,
892 );
893 });
894 surface.send_configure();
895
896 let _ = self.event_tx.send(CompositorEvent::SurfaceCreated {
897 surface_id,
898 title: String::new(),
899 app_id: String::new(),
900 parent_id: 0,
901 width: 0,
902 height: 0,
903 });
904 }
905
906 fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
907 let wl_surface = surface.wl_surface();
908 let key = wl_surface.id().protocol_id() as u64;
909 if let Some(info) = self.surfaces.remove(&key) {
910 self.surface_lookup.remove(&info.surface_id);
911 self.space.unmap_elem(&info.window);
912 let _ = self.event_tx.send(CompositorEvent::SurfaceDestroyed {
913 surface_id: info.surface_id,
914 });
915 }
916 }
917
918 fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {}
919
920 fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {}
921
922 fn reposition_request(
923 &mut self,
924 _surface: PopupSurface,
925 _positioner: PositionerState,
926 _token: u32,
927 ) {
928 }
929}
930
931impl OutputHandler for Compositor {}
932
933impl SeatHandler for Compositor {
934 type KeyboardFocus = WlSurface;
935 type PointerFocus = WlSurface;
936 type TouchFocus = WlSurface;
937
938 fn seat_state(&mut self) -> &mut SeatState<Self> {
939 &mut self.seat_state
940 }
941
942 fn cursor_image(
943 &mut self,
944 _seat: &Seat<Self>,
945 _image: smithay::input::pointer::CursorImageStatus,
946 ) {
947 }
948
949 fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
950 if let Some(surface) = focused {
951 let key = surface.id().protocol_id() as u64;
952 if let Some(info) = self.surfaces.get(&key) {
953 self.focused_surface_id = info.surface_id;
954 }
955 }
956 let client = focused.and_then(|s| self.display_handle.get_client(s.id()).ok());
957 set_data_device_focus(&self.display_handle, seat, client.clone());
958 set_primary_focus(&self.display_handle, seat, client);
959 }
960}
961
962impl SelectionHandler for Compositor {
963 type SelectionUserData = Arc<Vec<u8>>;
964
965 fn new_selection(
966 &mut self,
967 ty: SelectionTarget,
968 source: Option<SelectionSource>,
969 seat: Seat<Self>,
970 ) {
971 if ty != SelectionTarget::Clipboard {
972 return;
973 }
974 let Some(source) = source else { return };
975 let mime_types = source.mime_types();
976
977 let preferred = [
979 "text/plain;charset=utf-8",
980 "text/plain",
981 "UTF8_STRING",
982 "TEXT",
983 "STRING",
984 ];
985 let Some(mime) = preferred
986 .iter()
987 .map(|m| m.to_string())
988 .find(|m| mime_types.contains(m))
989 else {
990 return;
991 };
992
993 let (mut read_stream, write_stream) = match std::os::unix::net::UnixStream::pair() {
996 Ok(pair) => pair,
997 Err(_) => return,
998 };
999 let write_fd: OwnedFd = write_stream.into();
1000
1001 if request_data_device_client_selection::<Self>(&seat, mime.clone(), write_fd).is_err() {
1002 return;
1003 }
1004
1005 let event_tx = self.event_tx.clone();
1006 let event_notify = self.event_notify.clone();
1007 let surface_id = self.focused_surface_id;
1008 std::thread::spawn(move || {
1009 use std::io::Read;
1010 const MAX_CLIPBOARD_SIZE: usize = 16 * 1024 * 1024; let _ = read_stream.set_read_timeout(Some(std::time::Duration::from_secs(1)));
1012 let mut data = Vec::new();
1013 let mut buf = [0u8; 8192];
1014 loop {
1015 match read_stream.read(&mut buf) {
1016 Ok(0) => break,
1017 Ok(n) => {
1018 data.extend_from_slice(&buf[..n]);
1019 if data.len() > MAX_CLIPBOARD_SIZE {
1020 break;
1021 }
1022 }
1023 Err(_) => break,
1024 }
1025 }
1026 data.truncate(MAX_CLIPBOARD_SIZE);
1027 if !data.is_empty() {
1028 let _ = event_tx.send(CompositorEvent::ClipboardContent {
1029 surface_id,
1030 mime_type: mime,
1031 data,
1032 });
1033 (event_notify)();
1034 }
1035 });
1036 }
1037
1038 fn send_selection(
1039 &mut self,
1040 _ty: SelectionTarget,
1041 _mime_type: String,
1042 fd: OwnedFd,
1043 _seat: Seat<Self>,
1044 user_data: &Self::SelectionUserData,
1045 ) {
1046 let data = user_data.clone();
1048 std::thread::spawn(move || {
1049 use std::io::Write;
1050 let mut file = std::fs::File::from(fd);
1051 let _ = file.write_all(&data);
1052 });
1053 }
1054}
1055
1056impl DataDeviceHandler for Compositor {
1057 fn data_device_state(&self) -> &DataDeviceState {
1058 &self.data_device_state
1059 }
1060}
1061
1062impl ClientDndGrabHandler for Compositor {}
1063impl ServerDndGrabHandler for Compositor {}
1064
1065impl XdgDecorationHandler for Compositor {
1066 fn new_decoration(&mut self, toplevel: ToplevelSurface) {
1067 toplevel.with_pending_state(|state| {
1068 state.decoration_mode = Some(DecorationMode::ServerSide);
1069 });
1070 toplevel.send_configure();
1071 }
1072
1073 fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: DecorationMode) {
1074 toplevel.with_pending_state(|state| {
1075 state.decoration_mode = Some(DecorationMode::ServerSide);
1076 });
1077 toplevel.send_configure();
1078 }
1079
1080 fn unset_mode(&mut self, toplevel: ToplevelSurface) {
1081 toplevel.with_pending_state(|state| {
1082 state.decoration_mode = Some(DecorationMode::ServerSide);
1083 });
1084 toplevel.send_configure();
1085 }
1086}
1087
1088impl DmabufHandler for Compositor {
1089 fn dmabuf_state(&mut self) -> &mut DmabufState {
1090 &mut self.dmabuf_state
1091 }
1092
1093 fn dmabuf_imported(
1094 &mut self,
1095 _global: &DmabufGlobal,
1096 _dmabuf: Dmabuf,
1097 notifier: ImportNotifier,
1098 ) {
1099 let _ = notifier.successful::<Compositor>();
1100 }
1101}
1102
1103fn with_dmabuf_plane_bytes<T>(
1104 dmabuf: &Dmabuf,
1105 plane_idx: usize,
1106 f: impl FnOnce(&[u8]) -> Option<T>,
1107) -> Option<T> {
1108 let _ = dmabuf.sync_plane(
1109 plane_idx,
1110 smithay::backend::allocator::dmabuf::DmabufSyncFlags::START
1111 | smithay::backend::allocator::dmabuf::DmabufSyncFlags::READ,
1112 );
1113 struct PlaneSyncGuard<'a> {
1114 dmabuf: &'a Dmabuf,
1115 plane_idx: usize,
1116 }
1117
1118 impl Drop for PlaneSyncGuard<'_> {
1119 fn drop(&mut self) {
1120 let _ = self.dmabuf.sync_plane(
1121 self.plane_idx,
1122 smithay::backend::allocator::dmabuf::DmabufSyncFlags::END
1123 | smithay::backend::allocator::dmabuf::DmabufSyncFlags::READ,
1124 );
1125 }
1126 }
1127
1128 let _sync_guard = PlaneSyncGuard { dmabuf, plane_idx };
1129 let mapping = dmabuf.map_plane(plane_idx, DmabufMappingMode::READ).ok()?;
1130 let ptr = mapping.ptr() as *const u8;
1131 let len = mapping.length();
1132 let plane_data = unsafe { std::slice::from_raw_parts(ptr, len) };
1133 f(plane_data)
1134}
1135
1136fn yuv420_to_rgb(y: u8, u: u8, v: u8) -> [u8; 3] {
1137 let y = (y as i32 - 16).max(0);
1138 let u = u as i32 - 128;
1139 let v = v as i32 - 128;
1140
1141 let r = ((298 * y + 409 * v + 128) >> 8).clamp(0, 255) as u8;
1142 let g = ((298 * y - 100 * u - 208 * v + 128) >> 8).clamp(0, 255) as u8;
1143 let b = ((298 * y + 516 * u + 128) >> 8).clamp(0, 255) as u8;
1144
1145 [r, g, b]
1146}
1147
1148fn read_le_u16(bytes: &[u8], offset: usize) -> Option<u16> {
1149 let end = offset.checked_add(2)?;
1150 let raw = bytes.get(offset..end)?;
1151 Some(u16::from_le_bytes([raw[0], raw[1]]))
1152}
1153
1154fn read_packed_dmabuf(
1160 plane_data: &[u8],
1161 stride: usize,
1162 width: usize,
1163 height: usize,
1164 y_inverted: bool,
1165 format: Fourcc,
1166) -> Option<PixelData> {
1167 let row_bytes = width * 4;
1168 let total = row_bytes * height;
1169
1170 let (is_bgra, force_opaque) = match format {
1173 Fourcc::Argb8888 => (true, false), Fourcc::Xrgb8888 => (true, true), Fourcc::Abgr8888 => (false, false), Fourcc::Xbgr8888 => (false, true), _ => return None,
1178 };
1179
1180 if !y_inverted && stride == row_bytes && plane_data.len() >= total {
1182 let mut buf = plane_data[..total].to_vec();
1183 if force_opaque {
1184 for px in buf.chunks_exact_mut(4) {
1186 px[3] = 255;
1187 }
1188 }
1189 return Some(if is_bgra {
1190 PixelData::Bgra(Arc::new(buf))
1191 } else {
1192 PixelData::Rgba(Arc::new(buf))
1193 });
1194 }
1195
1196 let mut buf = Vec::with_capacity(total);
1198 for row in 0..height {
1199 let src_row = if y_inverted { height - 1 - row } else { row };
1200 let row_start = src_row * stride;
1201 let row_end = row_start + row_bytes;
1202 if row_end > plane_data.len() {
1203 return None;
1204 }
1205 buf.extend_from_slice(&plane_data[row_start..row_end]);
1206 }
1207 if force_opaque {
1208 for px in buf.chunks_exact_mut(4) {
1209 px[3] = 255;
1210 }
1211 }
1212 Some(if is_bgra {
1213 PixelData::Bgra(Arc::new(buf))
1214 } else {
1215 PixelData::Rgba(Arc::new(buf))
1216 })
1217}
1218
1219#[cfg(test)]
1220fn read_nv12_dmabuf(
1221 y_plane: &[u8],
1222 y_stride: usize,
1223 uv_plane: &[u8],
1224 uv_stride: usize,
1225 width: usize,
1226 height: usize,
1227 y_inverted: bool,
1228) -> Option<Vec<u8>> {
1229 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1230 return None;
1231 }
1232
1233 let mut rgba = Vec::with_capacity(width * height * 4);
1234 for row in 0..height {
1235 let src_row = if y_inverted { height - 1 - row } else { row };
1236 let y_row_start = src_row * y_stride;
1237 let uv_row_start = (src_row / 2) * uv_stride;
1238 for col in 0..width {
1239 let y = y_plane[y_row_start + col];
1240 let uv_idx = uv_row_start + (col / 2) * 2;
1241 let u = uv_plane[uv_idx];
1242 let v = uv_plane[uv_idx + 1];
1243 let [r, g, b] = yuv420_to_rgb(y, u, v);
1244 rgba.extend_from_slice(&[r, g, b, 255]);
1245 }
1246 }
1247 Some(rgba)
1248}
1249
1250#[cfg(test)]
1251fn read_p010_dmabuf(
1252 y_plane: &[u8],
1253 y_stride: usize,
1254 uv_plane: &[u8],
1255 uv_stride: usize,
1256 width: usize,
1257 height: usize,
1258 y_inverted: bool,
1259) -> Option<Vec<u8>> {
1260 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1261 return None;
1262 }
1263
1264 let mut rgba = Vec::with_capacity(width * height * 4);
1265 for row in 0..height {
1266 let src_row = if y_inverted { height - 1 - row } else { row };
1267 let y_row_start = src_row * y_stride;
1268 let y_row_end = y_row_start + width * 2;
1269 if y_row_end > y_plane.len() {
1270 return None;
1271 }
1272
1273 let uv_row_start = (src_row / 2) * uv_stride;
1274 let uv_row_end = uv_row_start + width * 2;
1275 if uv_row_end > uv_plane.len() {
1276 return None;
1277 }
1278
1279 for col in 0..width {
1280 let y = (read_le_u16(y_plane, y_row_start + col * 2)? >> 8) as u8;
1281 let uv_idx = uv_row_start + (col / 2) * 4;
1282 let u = (read_le_u16(uv_plane, uv_idx)? >> 8) as u8;
1283 let v = (read_le_u16(uv_plane, uv_idx + 2)? >> 8) as u8;
1284 let [r, g, b] = yuv420_to_rgb(y, u, v);
1285 rgba.extend_from_slice(&[r, g, b, 255]);
1286 }
1287 }
1288
1289 Some(rgba)
1290}
1291
1292fn read_nv12_dmabuf_passthrough(
1295 y_plane: &[u8],
1296 y_stride: usize,
1297 uv_plane: &[u8],
1298 uv_stride: usize,
1299 width: usize,
1300 height: usize,
1301 y_inverted: bool,
1302) -> Option<(Vec<u8>, usize, usize)> {
1303 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1304 return None;
1305 }
1306
1307 let uv_height = height / 2;
1308 let out_y_stride = width;
1310 let out_uv_stride = width;
1312 let mut data = vec![0u8; out_y_stride * height + out_uv_stride * uv_height];
1313
1314 for row in 0..height {
1316 let src_row = if y_inverted { height - 1 - row } else { row };
1317 let src_start = src_row * y_stride;
1318 let src_end = src_start + width;
1319 if src_end > y_plane.len() {
1320 return None;
1321 }
1322 let dst_start = row * out_y_stride;
1323 data[dst_start..dst_start + width].copy_from_slice(&y_plane[src_start..src_end]);
1324 }
1325
1326 let uv_dst_offset = out_y_stride * height;
1328 for row in 0..uv_height {
1329 let src_row = if y_inverted { uv_height - 1 - row } else { row };
1330 let src_start = src_row * uv_stride;
1331 let src_end = src_start + width; if src_end > uv_plane.len() {
1333 return None;
1334 }
1335 let dst_start = uv_dst_offset + row * out_uv_stride;
1336 data[dst_start..dst_start + width].copy_from_slice(&uv_plane[src_start..src_end]);
1337 }
1338
1339 Some((data, out_y_stride, out_uv_stride))
1340}
1341
1342fn read_p010_to_nv12(
1345 y_plane: &[u8],
1346 y_stride: usize,
1347 uv_plane: &[u8],
1348 uv_stride: usize,
1349 width: usize,
1350 height: usize,
1351 y_inverted: bool,
1352) -> Option<(Vec<u8>, usize, usize)> {
1353 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1354 return None;
1355 }
1356
1357 let uv_height = height / 2;
1358 let out_y_stride = width;
1359 let out_uv_stride = width;
1360 let mut data = vec![0u8; out_y_stride * height + out_uv_stride * uv_height];
1361
1362 for row in 0..height {
1364 let src_row = if y_inverted { height - 1 - row } else { row };
1365 let dst_start = row * out_y_stride;
1366 for col in 0..width {
1367 let src_offset = src_row * y_stride + col * 2;
1368 let val = read_le_u16(y_plane, src_offset)?;
1369 data[dst_start + col] = (val >> 8) as u8;
1370 }
1371 }
1372
1373 let uv_dst_offset = out_y_stride * height;
1375 for row in 0..uv_height {
1376 let src_row = if y_inverted { uv_height - 1 - row } else { row };
1377 let dst_start = uv_dst_offset + row * out_uv_stride;
1378 for col in 0..width / 2 {
1379 let src_offset = src_row * uv_stride + col * 4;
1380 let u = (read_le_u16(uv_plane, src_offset)? >> 8) as u8;
1381 let v = (read_le_u16(uv_plane, src_offset + 2)? >> 8) as u8;
1382 data[dst_start + col * 2] = u;
1383 data[dst_start + col * 2 + 1] = v;
1384 }
1385 }
1386
1387 Some((data, out_y_stride, out_uv_stride))
1388}
1389
1390fn fourcc_to_drm(code: Fourcc) -> u32 {
1392 code as u32
1393}
1394
1395fn read_dmabuf_pixels(dmabuf: &Dmabuf) -> Option<(u32, u32, PixelData)> {
1396 let size = dmabuf.size();
1397 let width = size.w as u32;
1398 let height = size.h as u32;
1399 if width == 0 || height == 0 {
1400 return None;
1401 }
1402
1403 let format = dmabuf.format();
1404
1405 if !dmabuf.y_inverted() {
1417 let can_zerocopy = matches!(
1418 format.code,
1419 Fourcc::Argb8888
1420 | Fourcc::Xrgb8888
1421 | Fourcc::Abgr8888
1422 | Fourcc::Xbgr8888
1423 | Fourcc::Nv12
1424 );
1425 if can_zerocopy
1426 && let Some(borrowed_fd) = dmabuf.handles().next()
1427 && let Ok(owned) = borrowed_fd.try_clone_to_owned()
1428 {
1429 let stride = dmabuf.strides().next().unwrap_or(width * 4);
1430 let offset = dmabuf.offsets().next().unwrap_or(0);
1431 let fourcc_u32 = fourcc_to_drm(format.code);
1432 let modifier_u64: u64 = format.modifier.into();
1433 return Some((
1434 width,
1435 height,
1436 PixelData::DmaBuf {
1437 fd: Arc::new(owned),
1438 fourcc: fourcc_u32,
1439 modifier: modifier_u64,
1440 stride,
1441 offset,
1442 },
1443 ));
1444 }
1445 }
1446
1447 let width_usize = width as usize;
1450 let height_usize = height as usize;
1451 let y_inverted = dmabuf.y_inverted();
1452 let pixel_data = match format.code {
1453 Fourcc::Argb8888 | Fourcc::Xrgb8888 | Fourcc::Abgr8888 | Fourcc::Xbgr8888 => {
1454 let stride = dmabuf.strides().next().unwrap_or(width * 4) as usize;
1455 with_dmabuf_plane_bytes(dmabuf, 0, |plane_data| {
1456 read_packed_dmabuf(
1457 plane_data,
1458 stride,
1459 width_usize,
1460 height_usize,
1461 y_inverted,
1462 format.code,
1463 )
1464 })?
1465 }
1466 Fourcc::Nv12 => {
1467 let mut strides = dmabuf.strides();
1469 let y_stride = strides.next().unwrap_or(width) as usize;
1470 let uv_stride = strides.next().unwrap_or(width) as usize;
1471 let nv12 = with_dmabuf_plane_bytes(dmabuf, 0, |y_plane_data| {
1472 with_dmabuf_plane_bytes(dmabuf, 1, |uv_plane_data| {
1473 read_nv12_dmabuf_passthrough(
1474 y_plane_data,
1475 y_stride,
1476 uv_plane_data,
1477 uv_stride,
1478 width_usize,
1479 height_usize,
1480 y_inverted,
1481 )
1482 })
1483 })?;
1484 PixelData::Nv12 {
1485 data: Arc::new(nv12.0),
1486 y_stride: nv12.1,
1487 uv_stride: nv12.2,
1488 }
1489 }
1490 Fourcc::P010 => {
1491 let mut strides = dmabuf.strides();
1494 let y_stride = strides.next().unwrap_or(width * 2) as usize;
1495 let uv_stride = strides.next().unwrap_or(width * 2) as usize;
1496 let nv12 = with_dmabuf_plane_bytes(dmabuf, 0, |y_plane| {
1497 with_dmabuf_plane_bytes(dmabuf, 1, |uv_plane| {
1498 read_p010_to_nv12(
1499 y_plane,
1500 y_stride,
1501 uv_plane,
1502 uv_stride,
1503 width_usize,
1504 height_usize,
1505 y_inverted,
1506 )
1507 })
1508 })?;
1509 PixelData::Nv12 {
1510 data: Arc::new(nv12.0),
1511 y_stride: nv12.1,
1512 uv_stride: nv12.2,
1513 }
1514 }
1515 _ => return None,
1516 };
1517
1518 Some((width, height, pixel_data))
1519}
1520
1521impl PrimarySelectionHandler for Compositor {
1522 fn primary_selection_state(&self) -> &PrimarySelectionState {
1523 &self.primary_selection_state
1524 }
1525}
1526
1527impl XdgActivationHandler for Compositor {
1528 fn activation_state(&mut self) -> &mut XdgActivationState {
1529 &mut self.activation_state
1530 }
1531
1532 fn request_activation(
1533 &mut self,
1534 _token: XdgActivationToken,
1535 _token_data: XdgActivationTokenData,
1536 _surface: WlSurface,
1537 ) {
1538 }
1539}
1540
1541impl FractionalScaleHandler for Compositor {
1542 fn new_fractional_scale(&mut self, _surface: WlSurface) {}
1543}
1544
1545impl XdgToplevelIconHandler for Compositor {}
1546impl TabletSeatHandler for Compositor {}
1547
1548delegate_compositor!(Compositor);
1549delegate_cursor_shape!(Compositor);
1550delegate_shm!(Compositor);
1551delegate_xdg_shell!(Compositor);
1552delegate_seat!(Compositor);
1553delegate_data_device!(Compositor);
1554delegate_primary_selection!(Compositor);
1555delegate_output!(Compositor);
1556delegate_dmabuf!(Compositor);
1557delegate_fractional_scale!(Compositor);
1558delegate_viewporter!(Compositor);
1559delegate_xdg_activation!(Compositor);
1560delegate_xdg_decoration!(Compositor);
1561delegate_xdg_toplevel_icon!(Compositor);
1562delegate_text_input_manager!(Compositor);
1563
1564pub struct CompositorHandle {
1565 pub event_rx: mpsc::Receiver<CompositorEvent>,
1566 pub command_tx: mpsc::Sender<CompositorCommand>,
1567 pub socket_name: String,
1568 pub thread: std::thread::JoinHandle<()>,
1569 pub shutdown: Arc<AtomicBool>,
1570 loop_signal: LoopSignal,
1571}
1572
1573impl CompositorHandle {
1574 pub fn wake(&self) {
1577 self.loop_signal.wakeup();
1578 }
1579}
1580
1581pub fn spawn_compositor(
1582 verbose: bool,
1583 event_notify: Arc<dyn Fn() + Send + Sync>,
1584) -> CompositorHandle {
1585 let (event_tx, event_rx) = mpsc::channel();
1586 let (command_tx, command_rx) = mpsc::channel();
1587 let (socket_tx, socket_rx) = mpsc::sync_channel(1);
1588 let (signal_tx, signal_rx) = mpsc::sync_channel::<LoopSignal>(1);
1589 let shutdown = Arc::new(AtomicBool::new(false));
1590 let shutdown_clone = shutdown.clone();
1591
1592 let runtime_dir = std::env::var_os("XDG_RUNTIME_DIR")
1593 .map(std::path::PathBuf::from)
1594 .filter(|p| {
1595 let probe = p.join(".blit-probe");
1602 if std::fs::write(&probe, b"").is_ok() {
1603 let _ = std::fs::remove_file(&probe);
1604 true
1605 } else {
1606 false
1607 }
1608 })
1609 .unwrap_or_else(std::env::temp_dir);
1610
1611 let runtime_dir_clone = runtime_dir.clone();
1612 let thread = std::thread::spawn(move || {
1613 unsafe { std::env::set_var("XDG_RUNTIME_DIR", &runtime_dir_clone) };
1614 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1615 run_compositor(
1616 event_tx,
1617 command_rx,
1618 socket_tx,
1619 signal_tx,
1620 event_notify,
1621 shutdown_clone,
1622 verbose,
1623 );
1624 }));
1625 if let Err(e) = result {
1626 let msg = if let Some(s) = e.downcast_ref::<&str>() {
1627 s.to_string()
1628 } else if let Some(s) = e.downcast_ref::<String>() {
1629 s.clone()
1630 } else {
1631 "unknown panic".to_string()
1632 };
1633 eprintln!("[compositor] PANIC: {msg}");
1634 }
1635 });
1636
1637 let socket_name = socket_rx.recv().expect("compositor failed to start");
1638 let socket_name = runtime_dir
1639 .join(&socket_name)
1640 .to_string_lossy()
1641 .into_owned();
1642 let loop_signal = signal_rx
1643 .recv()
1644 .expect("compositor failed to send loop signal");
1645
1646 CompositorHandle {
1647 event_rx,
1648 command_tx,
1649 socket_name,
1650 thread,
1651 shutdown,
1652 loop_signal,
1653 }
1654}
1655
1656fn run_compositor(
1657 event_tx: mpsc::Sender<CompositorEvent>,
1658 command_rx: mpsc::Receiver<CompositorCommand>,
1659 socket_tx: mpsc::SyncSender<String>,
1660 signal_tx: mpsc::SyncSender<LoopSignal>,
1661 event_notify: Arc<dyn Fn() + Send + Sync>,
1662 shutdown: Arc<AtomicBool>,
1663 verbose: bool,
1664) {
1665 let mut event_loop: EventLoop<Compositor> =
1666 EventLoop::try_new().expect("failed to create event loop");
1667 let display: Display<Compositor> = Display::new().expect("failed to create display");
1668 let dh = display.handle();
1669
1670 let compositor_state = CompositorState::new::<Compositor>(&dh);
1671 let xdg_shell_state = XdgShellState::new::<Compositor>(&dh);
1672 let shm_state = ShmState::new::<Compositor>(&dh, vec![]);
1673 let data_device_state = DataDeviceState::new::<Compositor>(&dh);
1674 let viewporter_state = ViewporterState::new::<Compositor>(&dh);
1675 let xdg_decoration_state = XdgDecorationState::new::<Compositor>(&dh);
1676 let primary_selection_state = PrimarySelectionState::new::<Compositor>(&dh);
1677 let activation_state = XdgActivationState::new::<Compositor>(&dh);
1678 FractionalScaleManagerState::new::<Compositor>(&dh);
1679 CursorShapeManagerState::new::<Compositor>(&dh);
1680 TextInputManagerState::new::<Compositor>(&dh);
1684
1685 let mut dmabuf_state = DmabufState::new();
1686 let dmabuf_formats = [
1687 DmabufFormat {
1688 code: Fourcc::Argb8888,
1689 modifier: Modifier::Linear,
1690 },
1691 DmabufFormat {
1692 code: Fourcc::Xrgb8888,
1693 modifier: Modifier::Linear,
1694 },
1695 DmabufFormat {
1696 code: Fourcc::Abgr8888,
1697 modifier: Modifier::Linear,
1698 },
1699 DmabufFormat {
1700 code: Fourcc::Xbgr8888,
1701 modifier: Modifier::Linear,
1702 },
1703 DmabufFormat {
1704 code: Fourcc::Nv12,
1705 modifier: Modifier::Linear,
1706 },
1707 DmabufFormat {
1708 code: Fourcc::P010,
1709 modifier: Modifier::Linear,
1710 },
1711 DmabufFormat {
1712 code: Fourcc::Argb8888,
1713 modifier: Modifier::Invalid,
1714 },
1715 DmabufFormat {
1716 code: Fourcc::Xrgb8888,
1717 modifier: Modifier::Invalid,
1718 },
1719 DmabufFormat {
1720 code: Fourcc::Abgr8888,
1721 modifier: Modifier::Invalid,
1722 },
1723 DmabufFormat {
1724 code: Fourcc::Xbgr8888,
1725 modifier: Modifier::Invalid,
1726 },
1727 DmabufFormat {
1728 code: Fourcc::Nv12,
1729 modifier: Modifier::Invalid,
1730 },
1731 DmabufFormat {
1732 code: Fourcc::P010,
1733 modifier: Modifier::Invalid,
1734 },
1735 ];
1736 let dmabuf_global = dmabuf_state.create_global::<Compositor>(&dh, dmabuf_formats);
1737
1738 let mut seat_state = SeatState::new();
1739 let mut seat = seat_state.new_wl_seat(&dh, "headless");
1740 seat.add_keyboard(XkbConfig::default(), 200, 25)
1741 .expect("failed to add keyboard");
1742 seat.add_pointer();
1743
1744 let output = Output::new(
1745 "headless-0".to_string(),
1746 PhysicalProperties {
1747 size: (0, 0).into(),
1748 subpixel: Subpixel::Unknown,
1749 make: "Virtual".into(),
1750 model: "Headless".into(),
1751 },
1752 );
1753 let mode = Mode {
1754 size: (1920, 1080).into(),
1755 refresh: 60_000,
1756 };
1757 output.create_global::<Compositor>(&dh);
1758 output.change_current_state(
1759 Some(mode),
1760 Some(Transform::Normal),
1761 Some(smithay::output::Scale::Integer(1)),
1762 Some((0, 0).into()),
1763 );
1764 output.set_preferred(mode);
1765
1766 let mut space = Space::default();
1767 space.map_output(&output, (0, 0));
1768
1769 let renderer = PixmanRenderer::new().expect("failed to create pixman renderer");
1770
1771 let listening_socket = ListeningSocketSource::new_auto().unwrap_or_else(|e| {
1772 let dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "(unset)".into());
1773 panic!(
1774 "failed to create wayland socket in XDG_RUNTIME_DIR={dir}: {e}\n\
1775 hint: ensure the directory exists and is writable by the current user"
1776 );
1777 });
1778 let socket_name = listening_socket
1779 .socket_name()
1780 .to_string_lossy()
1781 .into_owned();
1782 socket_tx.send(socket_name).unwrap();
1783
1784 let handle = event_loop.handle();
1785
1786 handle
1787 .insert_source(listening_socket, |client_stream, _, state| {
1788 if let Err(e) = state.display_handle.insert_client(
1789 client_stream,
1790 Arc::new(ClientData {
1791 compositor_state: CompositorClientState::default(),
1792 }),
1793 ) && verbose
1794 {
1795 eprintln!("[compositor] insert_client error: {e}");
1796 }
1797 })
1798 .expect("failed to insert listening socket");
1799
1800 let loop_signal = event_loop.get_signal();
1801
1802 let mut compositor = Compositor {
1803 display_handle: dh.clone(),
1804 compositor_state,
1805 xdg_shell_state,
1806 shm_state,
1807 seat_state,
1808 data_device_state,
1809 viewporter_state,
1810 xdg_decoration_state,
1811 dmabuf_state,
1812 dmabuf_global,
1813 primary_selection_state,
1814 activation_state,
1815 seat,
1816 output,
1817 space,
1818 surfaces: HashMap::new(),
1819 surface_lookup: HashMap::new(),
1820 next_surface_id: 1,
1821 event_tx,
1822 event_notify,
1823 loop_signal: loop_signal.clone(),
1824 renderer,
1825 verbose,
1826 focused_surface_id: 0,
1827 pending_commits: HashMap::new(),
1828 };
1829
1830 let _ = signal_tx.send(loop_signal.clone());
1832
1833 let display_source = Generic::new(display, Interest::READ, calloop::Mode::Level);
1834 handle
1835 .insert_source(display_source, |_, display, state| {
1836 let d = unsafe { display.get_mut() };
1837 if let Err(e) = d.dispatch_clients(state)
1838 && verbose
1839 {
1840 eprintln!("[compositor] dispatch_clients error: {e}");
1841 }
1842 if let Err(e) = d.flush_clients()
1843 && verbose
1844 {
1845 eprintln!("[compositor] flush_clients error: {e}");
1846 }
1847 Ok(PostAction::Continue)
1848 })
1849 .expect("failed to insert display source");
1850
1851 if verbose {
1852 eprintln!("[compositor] entering event loop");
1853 }
1854 while !shutdown.load(Ordering::Relaxed) {
1855 while let Ok(cmd) = command_rx.try_recv() {
1856 match cmd {
1857 CompositorCommand::Shutdown => {
1858 shutdown.store(true, Ordering::Relaxed);
1859 return;
1860 }
1861 other => compositor.handle_command(other),
1862 }
1863 }
1864
1865 if let Err(e) =
1869 event_loop.dispatch(Some(std::time::Duration::from_secs(1)), &mut compositor)
1870 && verbose
1871 {
1872 eprintln!("[compositor] event loop error: {e}");
1873 }
1874
1875 if !compositor.pending_commits.is_empty() {
1877 compositor.flush_pending_commits();
1878 }
1879
1880 if let Err(e) = compositor.display_handle.flush_clients()
1881 && verbose
1882 {
1883 eprintln!("[compositor] flush error: {e}");
1884 }
1885 }
1886 if verbose {
1887 eprintln!("[compositor] event loop exited");
1888 }
1889}
1890
1891#[cfg(test)]
1892mod tests {
1893 use super::{Fourcc, read_nv12_dmabuf, read_p010_dmabuf, read_packed_dmabuf};
1894
1895 fn read_packed_rgba_dmabuf(
1897 plane_data: &[u8],
1898 stride: usize,
1899 width: usize,
1900 height: usize,
1901 y_inverted: bool,
1902 format: Fourcc,
1903 ) -> Option<Vec<u8>> {
1904 let pd = read_packed_dmabuf(plane_data, stride, width, height, y_inverted, format)?;
1905 Some(pd.to_rgba(width as u32, height as u32))
1906 }
1907
1908 #[test]
1909 fn xrgb_dmabuf_forces_opaque_alpha() {
1910 let pixels = [
1911 0x10, 0x20, 0x30, 0x00, 0x40, 0x50, 0x60, 0x7f,
1913 ];
1914
1915 let rgba = read_packed_rgba_dmabuf(&pixels, 8, 2, 1, false, Fourcc::Xrgb8888).unwrap();
1916
1917 assert_eq!(rgba, vec![0x30, 0x20, 0x10, 0xff, 0x60, 0x50, 0x40, 0xff]);
1918 }
1919
1920 #[test]
1921 fn nv12_black_decodes_to_opaque_black() {
1922 let y_plane = [16, 16, 16, 16];
1923 let uv_plane = [128, 128];
1924
1925 let rgba = read_nv12_dmabuf(&y_plane, 2, &uv_plane, 2, 2, 2, false).unwrap();
1926
1927 assert_eq!(rgba, vec![0, 0, 0, 255].repeat(4));
1928 }
1929
1930 #[test]
1931 fn p010_white_decodes_to_opaque_white() {
1932 let y_plane = [0x00, 0xeb, 0x00, 0xeb, 0x00, 0xeb, 0x00, 0xeb];
1933 let uv_plane = [0x00, 0x80, 0x00, 0x80];
1934
1935 let rgba = read_p010_dmabuf(&y_plane, 4, &uv_plane, 4, 2, 2, false).unwrap();
1936
1937 assert_eq!(rgba, vec![255, 255, 255, 255].repeat(4));
1938 }
1939}