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::delegate_compositor;
12use smithay::delegate_cursor_shape;
13use smithay::delegate_data_device;
14use smithay::delegate_dmabuf;
15use smithay::delegate_fractional_scale;
16use smithay::delegate_output;
17use smithay::delegate_text_input_manager;
18use smithay::delegate_primary_selection;
19use smithay::delegate_seat;
20use smithay::delegate_shm;
21use smithay::delegate_viewporter;
22use smithay::delegate_xdg_activation;
23use smithay::delegate_xdg_decoration;
24use smithay::delegate_xdg_shell;
25use smithay::delegate_xdg_toplevel_icon;
26use smithay::desktop::{Space, Window};
27use smithay::input::keyboard::FilterResult;
28use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};
29use smithay::input::{Seat, SeatHandler, SeatState};
30use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
31use smithay::reexports::calloop::generic::Generic;
32use smithay::reexports::calloop::{EventLoop, Interest, LoopSignal, PostAction};
33use smithay::reexports::wayland_server::protocol::wl_buffer;
34use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
35use smithay::reexports::wayland_server::protocol::wl_shm;
36use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
37use smithay::reexports::wayland_server::{Client, Display, DisplayHandle, Resource};
38use smithay::utils::{Serial, Transform, SERIAL_COUNTER};
39use smithay::wayland::buffer::BufferHandler;
40use smithay::wayland::compositor::{
41 self, CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes,
42 with_states, with_surface_tree_downward, TraversalAction, get_parent,
43};
44use smithay::wayland::output::OutputHandler;
45use smithay::wayland::selection::data_device::{
46 ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
47 set_data_device_focus, set_data_device_selection, request_data_device_client_selection,
48};
49use smithay::wayland::selection::{SelectionHandler, SelectionSource, SelectionTarget};
50use smithay::wayland::shell::xdg::{
51 PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
52 XdgToplevelSurfaceData,
53};
54use smithay::wayland::dmabuf::{DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf};
55use smithay::wayland::drm_syncobj::{DrmSyncobjHandler, DrmSyncobjState, supports_syncobj_eventfd};
56use smithay::backend::drm::DrmDeviceFd;
57use smithay::wayland::shell::xdg::decoration::{XdgDecorationHandler, XdgDecorationState};
58use smithay::wayland::shm::{BufferData, ShmHandler, ShmState, with_buffer_contents};
59use smithay::wayland::socket::ListeningSocketSource;
60use smithay::wayland::cursor_shape::CursorShapeManagerState;
61use smithay::wayland::tablet_manager::TabletSeatHandler;
62use smithay::wayland::fractional_scale::{FractionalScaleHandler, FractionalScaleManagerState};
63use smithay::wayland::selection::primary_selection::{PrimarySelectionHandler, PrimarySelectionState, set_primary_focus};
64use smithay::wayland::text_input::TextInputManagerState;
65use smithay::wayland::viewporter::ViewporterState;
66use smithay::wayland::xdg_activation::{
67 XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
68};
69use smithay::wayland::xdg_toplevel_icon::XdgToplevelIconHandler;
70use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;
71
72#[derive(Clone)]
77pub enum PixelData {
78 Bgra(Arc<Vec<u8>>),
81 Rgba(Arc<Vec<u8>>),
84 Nv12 {
89 data: Arc<Vec<u8>>,
90 y_stride: usize,
91 uv_stride: usize,
92 },
93 DmaBuf {
100 fd: Arc<std::os::fd::OwnedFd>,
101 fourcc: u32,
103 modifier: u64,
105 stride: u32,
106 offset: u32,
107 },
108}
109
110#[derive(Clone)]
112pub struct PixelLayer {
113 pub x: i32,
114 pub y: i32,
115 pub width: u32,
116 pub height: u32,
117 pub pixels: PixelData,
118}
119
120pub mod drm_fourcc {
122 pub const ARGB8888: u32 = u32::from_le_bytes(*b"AR24");
124 pub const XRGB8888: u32 = u32::from_le_bytes(*b"XR24");
126 pub const ABGR8888: u32 = u32::from_le_bytes(*b"AB24");
128 pub const XBGR8888: u32 = u32::from_le_bytes(*b"XB24");
130 pub const NV12: u32 = u32::from_le_bytes(*b"NV12");
132}
133
134impl PixelData {
135 pub fn to_rgba(&self, width: u32, height: u32) -> Vec<u8> {
138 let w = width as usize;
139 let h = height as usize;
140 match self {
141 PixelData::Rgba(data) => data.as_ref().clone(),
142 PixelData::Bgra(data) => {
143 let mut rgba = Vec::with_capacity(w * h * 4);
144 for px in data.chunks_exact(4) {
145 rgba.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
146 }
147 rgba
148 }
149 PixelData::Nv12 {
150 data,
151 y_stride,
152 uv_stride,
153 } => {
154 let y_plane_size = *y_stride * h;
155 let uv_h = h.div_ceil(2);
156 let uv_plane_size = *uv_stride * uv_h;
157 if data.len() < y_plane_size + uv_plane_size {
158 return Vec::new();
159 }
160 let y_plane = &data[..y_plane_size];
161 let uv_plane = &data[y_plane_size..];
162 let mut rgba = Vec::with_capacity(w * h * 4);
163 for row in 0..h {
164 for col in 0..w {
165 let y = y_plane[row * y_stride + col];
166 let uv_idx = (row / 2) * uv_stride + (col / 2) * 2;
167 if uv_idx + 1 >= uv_plane.len() {
168 rgba.extend_from_slice(&[0, 0, 0, 255]);
169 continue;
170 }
171 let u = uv_plane[uv_idx];
172 let v = uv_plane[uv_idx + 1];
173 let [r, g, b] = yuv420_to_rgb(y, u, v);
174 rgba.extend_from_slice(&[r, g, b, 255]);
175 }
176 }
177 rgba
178 }
179 PixelData::DmaBuf {
180 fd, fourcc, stride, ..
181 } => {
182 use std::os::fd::AsRawFd;
184 let raw = fd.as_raw_fd();
185 let stride_usize = *stride as usize;
186 let map_size = stride_usize * h;
187 if map_size == 0 {
188 return Vec::new();
189 }
190 let sync_start: u64 = 1 | 4; unsafe { libc::ioctl(raw, 0x40086200u64 as _, &sync_start) };
193 let ptr = unsafe {
194 libc::mmap(
195 std::ptr::null_mut(),
196 map_size,
197 libc::PROT_READ,
198 libc::MAP_SHARED,
199 raw,
200 0,
201 )
202 };
203 if ptr == libc::MAP_FAILED {
204 return Vec::new();
205 }
206 let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_size) };
207 let row_bytes = w * 4;
208 let mut pixels = Vec::with_capacity(w * h * 4);
209 for row in 0..h {
210 let start = row * stride_usize;
211 if start + row_bytes <= slice.len() {
212 pixels.extend_from_slice(&slice[start..start + row_bytes]);
213 }
214 }
215 if matches!(*fourcc, drm_fourcc::XRGB8888 | drm_fourcc::XBGR8888) {
217 for px in pixels.chunks_exact_mut(4) {
218 px[3] = 255;
219 }
220 }
221 let sync_end: u64 = 2 | 4; unsafe {
223 libc::ioctl(raw, 0x40086200u64 as _, &sync_end);
224 libc::munmap(ptr, map_size);
225 }
226 pixels
227 }
228 }
229 }
230
231 pub fn to_bgra_vec(&self, width: u32, height: u32) -> Vec<u8> {
234 match self {
235 PixelData::Bgra(v) => v.as_ref().to_vec(),
236 PixelData::Rgba(v) => {
237 let mut bgra = v.as_ref().to_vec();
239 for px in bgra.chunks_exact_mut(4) {
240 px.swap(0, 2);
241 }
242 bgra
243 }
244 PixelData::DmaBuf {
245 fd, stride, fourcc, ..
246 } => {
247 use std::os::fd::AsRawFd;
249 let raw = fd.as_raw_fd();
250 let s = *stride as usize;
251 let h = height as usize;
252 let w = width as usize;
253 let map_size = s * h;
254 if map_size == 0 {
255 return Vec::new();
256 }
257 let sync_start: u64 = 1 | 4;
258 unsafe { libc::ioctl(raw, 0x40086200u64 as _, &sync_start) };
259 let ptr = unsafe {
260 libc::mmap(
261 std::ptr::null_mut(),
262 map_size,
263 libc::PROT_READ,
264 libc::MAP_SHARED,
265 raw,
266 0,
267 )
268 };
269 if ptr == libc::MAP_FAILED {
270 return Vec::new();
271 }
272 let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_size) };
273 let row_bytes = w * 4;
274 let is_bgr = matches!(fourcc, 0x34325241 | 0x34325258);
275 let mut bgra = Vec::with_capacity(w * h * 4);
276 for row in 0..h {
277 let src = &slice[row * s..row * s + row_bytes.min(slice.len() - row * s)];
278 if is_bgr {
279 bgra.extend_from_slice(src);
280 } else {
281 for px in src.chunks_exact(4) {
282 bgra.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
283 }
284 }
285 }
286 let sync_end: u64 = 2 | 4;
287 unsafe {
288 libc::ioctl(raw, 0x40086200u64 as _, &sync_end);
289 libc::munmap(ptr, map_size);
290 }
291 bgra
292 }
293 _ => Vec::new(),
294 }
295 }
296
297 pub fn is_empty(&self) -> bool {
298 match self {
299 PixelData::Bgra(v) | PixelData::Rgba(v) => v.is_empty(),
300 PixelData::Nv12 { data, .. } => data.is_empty(),
301 PixelData::DmaBuf { .. } => false,
302 }
303 }
304
305 pub fn is_dmabuf(&self) -> bool {
307 matches!(self, PixelData::DmaBuf { .. })
308 }
309}
310
311pub enum CompositorEvent {
312 SurfaceCreated {
313 surface_id: u16,
314 title: String,
315 app_id: String,
316 parent_id: u16,
317 width: u16,
318 height: u16,
319 },
320 SurfaceDestroyed {
321 surface_id: u16,
322 },
323 SurfaceCommit {
324 surface_id: u16,
325 width: u32,
326 height: u32,
327 pixels: PixelData,
328 },
329 SurfaceTitle {
330 surface_id: u16,
331 title: String,
332 },
333 SurfaceAppId {
334 surface_id: u16,
335 app_id: String,
336 },
337 SurfaceResized {
338 surface_id: u16,
339 width: u16,
340 height: u16,
341 },
342 ClipboardContent {
343 surface_id: u16,
344 mime_type: String,
345 data: Vec<u8>,
346 },
347}
348
349pub enum CompositorCommand {
350 KeyInput {
351 surface_id: u16,
352 keycode: u32,
353 pressed: bool,
354 },
355 PointerMotion {
356 surface_id: u16,
357 x: f64,
358 y: f64,
359 },
360 PointerButton {
361 surface_id: u16,
362 button: u32,
363 pressed: bool,
364 },
365 PointerAxis {
366 surface_id: u16,
367 axis: u8,
368 value: f64,
369 },
370 SurfaceResize {
371 surface_id: u16,
372 width: u16,
373 height: u16,
374 scale_120: u16,
376 },
377 SurfaceFocus {
378 surface_id: u16,
379 },
380 SurfaceClose {
381 surface_id: u16,
382 },
383 ClipboardOffer {
384 surface_id: u16,
385 mime_type: String,
386 data: Vec<u8>,
387 },
388 Capture {
389 surface_id: u16,
390 reply: mpsc::SyncSender<Option<(u32, u32, Vec<u8>)>>,
391 },
392 RequestFrame {
396 surface_id: u16,
397 },
398 ReleaseKeys {
402 keycodes: Vec<u32>,
403 },
404 Shutdown,
405}
406
407struct SurfaceInfo {
408 surface_id: u16,
409 window: Window,
410 last_width: u32,
411 last_height: u32,
412 last_title: String,
413 last_app_id: String,
414 unreadable_warned: bool,
416}
417
418struct ClientData {
419 compositor_state: CompositorClientState,
420}
421
422impl smithay::reexports::wayland_server::backend::ClientData for ClientData {
423 fn initialized(&self, _client_id: smithay::reexports::wayland_server::backend::ClientId) {}
424 fn disconnected(
425 &self,
426 _client_id: smithay::reexports::wayland_server::backend::ClientId,
427 _reason: smithay::reexports::wayland_server::backend::DisconnectReason,
428 ) {
429 }
430}
431
432pub struct Compositor {
433 display_handle: DisplayHandle,
434 compositor_state: CompositorState,
435 xdg_shell_state: XdgShellState,
436 shm_state: ShmState,
437 seat_state: SeatState<Self>,
438 data_device_state: DataDeviceState,
439 #[allow(dead_code)]
440 viewporter_state: ViewporterState,
441 #[allow(dead_code)]
442 xdg_decoration_state: XdgDecorationState,
443 dmabuf_state: DmabufState,
444 #[allow(dead_code)]
445 dmabuf_global: DmabufGlobal,
446 syncobj_state: Option<DrmSyncobjState>,
447 primary_selection_state: PrimarySelectionState,
448 activation_state: XdgActivationState,
449 seat: Seat<Self>,
450 output: Output,
451 space: Space<Window>,
452
453 surfaces: HashMap<u64, SurfaceInfo>,
454 surface_lookup: HashMap<u16, u64>,
455 next_surface_id: u16,
456
457 event_tx: mpsc::Sender<CompositorEvent>,
458 event_notify: Arc<dyn Fn() + Send + Sync>,
459 loop_signal: LoopSignal,
460
461 verbose: bool,
462
463 focused_surface_id: u16,
465
466 pending_commits: HashMap<u16, (u32, u32, PixelData)>,
470
471 surface_pixel_cache: HashMap<u64, (u32, u32, PixelData)>,
478
479 gpu_renderer: Option<super::render::SurfaceRenderer>,
481}
482
483impl Compositor {
484 fn flush_pending_commits(&mut self) {
488 for (surface_id, (width, height, pixels)) in self.pending_commits.drain() {
489 let _ = self.event_tx.send(CompositorEvent::SurfaceCommit {
490 surface_id,
491 width,
492 height,
493 pixels,
494 });
495 }
496 (self.event_notify)();
497 }
498
499 fn allocate_surface_id(&mut self) -> u16 {
500 let mut id = self.next_surface_id;
503 let start = id;
504 loop {
505 if !self.surface_lookup.contains_key(&id) {
506 break;
507 }
508 id = id.wrapping_add(1);
509 if id == 0 {
510 id = 1;
511 }
512 if id == start {
513 break;
515 }
516 }
517 self.next_surface_id = id.wrapping_add(1);
518 if self.next_surface_id == 0 {
519 self.next_surface_id = 1;
520 }
521 id
522 }
523
524 fn handle_command(&mut self, cmd: CompositorCommand) {
525 match cmd {
526 CompositorCommand::KeyInput {
527 surface_id,
528 keycode,
529 pressed,
530 } => {
531 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
532 && let Some(info) = self.surfaces.get(&obj_id)
533 && let Some(toplevel) = info.window.toplevel()
534 && let Some(keyboard) = self.seat.get_keyboard()
535 {
536 if self.verbose {
537 eprintln!(
538 "[compositor] key: sid={surface_id} evdev={keycode} pressed={pressed}"
539 );
540 }
541 let serial = SERIAL_COUNTER.next_serial();
542 let time = elapsed_ms();
543 let state = if pressed {
544 KeyState::Pressed
545 } else {
546 KeyState::Released
547 };
548 keyboard.set_focus(self, Some(toplevel.wl_surface().clone()), serial);
549 keyboard.input::<(), _>(
553 self,
554 (keycode + 8).into(),
555 state,
556 serial,
557 time,
558 |_, _, _| FilterResult::Forward,
559 );
560 }
561 }
562 CompositorCommand::PointerMotion { surface_id, x, y } => {
563 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
564 && let Some(info) = self.surfaces.get(&obj_id)
565 && let Some(toplevel) = info.window.toplevel()
566 && let Some(pointer) = self.seat.get_pointer()
567 {
568 let serial = SERIAL_COUNTER.next_serial();
569 let time = elapsed_ms();
570 let wl_surface = toplevel.wl_surface().clone();
571 if pointer.is_grabbed() {
579 let stale = pointer
580 .grab_start_data()
581 .and_then(|d| d.focus.as_ref().map(|(s, _)| s.id() != wl_surface.id()))
582 .unwrap_or(false);
583 if stale {
584 pointer.unset_grab(self, serial, time);
585 }
586 }
587 pointer.motion(
588 self,
589 Some((wl_surface, (0.0, 0.0).into())),
590 &MotionEvent {
591 location: (x, y).into(),
592 serial,
593 time,
594 },
595 );
596 pointer.frame(self);
597 }
598 }
599 CompositorCommand::PointerButton {
600 surface_id,
601 button,
602 pressed,
603 } => {
604 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
605 && self.surfaces.contains_key(&obj_id)
606 && let Some(pointer) = self.seat.get_pointer()
607 {
608 let serial = SERIAL_COUNTER.next_serial();
609 let state = if pressed {
610 ButtonState::Pressed
611 } else {
612 ButtonState::Released
613 };
614 pointer.button(
615 self,
616 &ButtonEvent {
617 button,
618 state,
619 serial,
620 time: elapsed_ms(),
621 },
622 );
623 pointer.frame(self);
624 }
625 }
626 CompositorCommand::PointerAxis {
627 surface_id: _,
628 axis,
629 value,
630 } => {
631 let Some(pointer) = self.seat.get_pointer() else {
632 return;
633 };
634 let ax = if axis == 0 {
635 Axis::Vertical
636 } else {
637 Axis::Horizontal
638 };
639 pointer.axis(self, AxisFrame::new(elapsed_ms()).value(ax, value));
640 pointer.frame(self);
641 }
642 CompositorCommand::SurfaceResize {
643 surface_id: _,
644 width,
645 height,
646 scale_120,
647 } => {
648 let scale_frac = if scale_120 >= 120 {
651 scale_120 as f64
652 } else {
653 120.0
654 };
655 let cur = self.output.current_scale().fractional_scale();
656 if (cur - scale_frac).abs() > 0.01 {
657 let int_scale = ((scale_frac / 120.0) + 0.5) as i32;
659 self.output.change_current_state(
660 None,
661 None,
662 Some(smithay::output::Scale::Custom {
663 advertised_integer: int_scale.max(1),
664 fractional: scale_frac,
665 }),
666 None,
667 );
668 }
669
670 let scale_f = scale_frac / 120.0;
673 let logical_w = ((width as f64) / scale_f).round() as i32;
674 let logical_h = ((height as f64) / scale_f).round() as i32;
675
676 let mode = smithay::output::Mode {
678 size: (width as i32, height as i32).into(),
679 refresh: 60_000,
680 };
681 self.output
682 .change_current_state(Some(mode), None, None, None);
683 self.output.set_preferred(mode);
684
685 for info in self.surfaces.values() {
687 if let Some(toplevel) = info.window.toplevel() {
688 toplevel.with_pending_state(|state| {
689 state.size = Some((logical_w.max(1), logical_h.max(1)).into());
690 });
691 toplevel.send_pending_configure();
692 }
693 }
694 self.space.refresh();
697 }
698 CompositorCommand::SurfaceFocus { surface_id } => {
699 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
700 && let Some(info) = self.surfaces.get(&obj_id)
701 && let Some(toplevel) = info.window.toplevel()
702 && let Some(keyboard) = self.seat.get_keyboard()
703 {
704 let serial = SERIAL_COUNTER.next_serial();
705 keyboard.set_focus(self, Some(toplevel.wl_surface().clone()), serial);
706 }
707 }
708 CompositorCommand::SurfaceClose { surface_id } => {
709 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
710 && let Some(info) = self.surfaces.get(&obj_id)
711 && let Some(toplevel) = info.window.toplevel()
712 {
713 toplevel.send_close();
714 }
715 }
716 CompositorCommand::ClipboardOffer {
717 surface_id: _,
718 mime_type,
719 data,
720 } => {
721 let mime_types = if mime_type == "text/plain" {
724 vec![
725 "text/plain".to_string(),
726 "text/plain;charset=utf-8".to_string(),
727 "UTF8_STRING".to_string(),
728 "TEXT".to_string(),
729 "STRING".to_string(),
730 ]
731 } else {
732 vec![mime_type]
733 };
734 set_data_device_selection(
735 &self.display_handle,
736 &self.seat,
737 mime_types,
738 Arc::new(data),
739 );
740 }
741 CompositorCommand::Capture { surface_id, reply } => {
742 let result = if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
743 && let Some(info) = self.surfaces.get(&obj_id)
744 && let Some(toplevel) = info.window.toplevel()
745 {
746 let wl_surface = toplevel.wl_surface().clone();
747 self.read_surface_pixels(&wl_surface)
748 } else {
749 None
750 };
751 let _ = reply.send(result);
752 }
753 CompositorCommand::RequestFrame { surface_id } => {
754 self.fire_frame_callbacks(surface_id);
755 }
756 CompositorCommand::ReleaseKeys { keycodes } => {
757 if let Some(keyboard) = self.seat.get_keyboard() {
758 let serial = SERIAL_COUNTER.next_serial();
759 let time = elapsed_ms();
760 for keycode in keycodes {
761 keyboard.input::<(), _>(
762 self,
763 (keycode + 8).into(),
764 KeyState::Released,
765 serial,
766 time,
767 |_, _, _| FilterResult::Forward,
768 );
769 }
770 }
771 }
772 CompositorCommand::Shutdown => {
773 self.loop_signal.stop();
774 }
775 }
776 }
777
778 fn fire_frame_callbacks(&self, surface_id: u16) {
780 if let Some(&obj_id) = self.surface_lookup.get(&surface_id)
781 && let Some(info) = self.surfaces.get(&obj_id)
782 && let Some(toplevel) = info.window.toplevel()
783 {
784 let surface = toplevel.wl_surface().clone();
785 let time = elapsed_ms();
786 let mut fired = 0u32;
787 with_surface_tree_downward(
788 &surface,
789 (),
790 |_, _, &()| TraversalAction::DoChildren(()),
791 |_, states, &()| {
792 for callback in states
793 .cached_state
794 .get::<SurfaceAttributes>()
795 .current()
796 .frame_callbacks
797 .drain(..)
798 {
799 callback.done(time);
800 fired += 1;
801 }
802 },
803 |_, _, &()| true,
804 );
805 let _ = fired;
806 }
807 }
808
809 fn read_surface_pixels(&mut self, surface: &WlSurface) -> Option<(u32, u32, Vec<u8>)> {
810 let mut result: Option<(u32, u32, PixelData)> = None;
811 with_states(surface, |states| {
812 let mut guard = states.cached_state.get::<SurfaceAttributes>();
813 let attrs = guard.current();
814 if let Some(compositor::BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref() {
815 result = read_shm_buffer(buffer);
816 if result.is_none()
817 && let Ok(dmabuf) = get_dmabuf(buffer)
818 {
819 result = read_dmabuf_pixels(dmabuf);
820 }
821 }
822 });
823 result.map(|(w, h, pd)| (w, h, pd.to_rgba(w, h)))
825 }
826}
827
828fn read_shm_buffer(buffer: &wl_buffer::WlBuffer) -> Option<(u32, u32, PixelData)> {
831 let mut result = None;
832 let _ = with_buffer_contents(buffer, |ptr, len, data: BufferData| {
833 let width = data.width as u32;
834 let height = data.height as u32;
835 let stride = data.stride as usize;
836 let offset = data.offset as usize;
837 let pixel_data = unsafe { std::slice::from_raw_parts(ptr, len) };
838 let row_bytes = width as usize * 4;
839 let mut bgra = if stride == row_bytes
840 && offset == 0
841 && pixel_data.len() >= row_bytes * height as usize
842 {
843 pixel_data[..row_bytes * height as usize].to_vec()
844 } else {
845 let mut packed = Vec::with_capacity(row_bytes * height as usize);
846 for row in 0..height as usize {
847 let row_start = offset + row * stride;
848 let row_end = row_start + row_bytes;
849 if row_end <= pixel_data.len() {
850 packed.extend_from_slice(&pixel_data[row_start..row_end]);
851 }
852 }
853 packed
854 };
855 if data.format == wl_shm::Format::Xrgb8888 || data.format == wl_shm::Format::Xbgr8888 {
859 for px in bgra.chunks_exact_mut(4) {
860 px[3] = 255;
861 }
862 }
863 if matches!(
865 data.format,
866 wl_shm::Format::Abgr8888 | wl_shm::Format::Xbgr8888
867 ) {
868 result = Some((width, height, PixelData::Rgba(Arc::new(bgra))));
869 } else {
870 result = Some((width, height, PixelData::Bgra(Arc::new(bgra))));
871 }
872 });
873 result
874}
875
876fn elapsed_ms() -> u32 {
877 use std::sync::OnceLock;
878 static START: OnceLock<Instant> = OnceLock::new();
879 START.get_or_init(Instant::now).elapsed().as_millis() as u32
880}
881
882impl CompositorHandler for Compositor {
883 fn compositor_state(&mut self) -> &mut CompositorState {
884 &mut self.compositor_state
885 }
886
887 fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
888 &client.get_data::<ClientData>().unwrap().compositor_state
889 }
890
891 fn commit(&mut self, surface: &WlSurface) {
892 smithay::backend::renderer::utils::on_commit_buffer_handler::<Self>(surface);
895
896 let (root_surface, key, surface_id) = {
898 let key = surface.id().protocol_id() as u64;
899 if let Some(info) = self.surfaces.get(&key) {
900 (surface.clone(), key, info.surface_id)
901 } else {
902 let mut current = surface.clone();
904 while let Some(parent) = get_parent(¤t) {
905 current = parent;
906 }
907 let parent_key = current.id().protocol_id() as u64;
908 match self.surfaces.get(&parent_key) {
909 Some(info) => (current, parent_key, info.surface_id),
910 None => return, }
912 }
913 };
914
915 let mut committed_pixels: Option<(u32, u32, PixelData)> = None;
916 let mut new_title = String::new();
917 let mut new_app_id = String::new();
918
919 let have_pending = self.pending_commits.contains_key(&surface_id);
920 let warned_unreadable = self.surfaces.get(&key).is_none_or(|i| i.unreadable_warned);
921
922 if !have_pending {
923 let geometry = self.surfaces.get(&key).map(|i| i.window.geometry());
928
929 if let Some(renderer) = &mut self.gpu_renderer
931 && let Some((w, h, bgra)) =
932 renderer.render_surface(&root_surface, geometry.as_ref())
933 {
934 committed_pixels = Some((w, h, PixelData::Bgra(Arc::new(bgra))));
935 }
936
937 if committed_pixels.is_none() {
940 with_states(&root_surface, |states| {
941 let mut guard = states.cached_state.get::<SurfaceAttributes>();
942 let attrs = guard.current();
943 if let Some(compositor::BufferAssignment::NewBuffer(buffer)) =
944 attrs.buffer.as_ref()
945 && let Some((w, h, pd)) = read_shm_buffer(buffer)
946 {
947 committed_pixels = Some((w, h, pd));
948 }
949 });
950 }
951
952 if committed_pixels.is_none() && !warned_unreadable {
953 eprintln!(
954 "compositor: commit with no readable buffer — suppressing further warnings for this surface"
955 );
956 }
957 }
958
959 with_surface_tree_downward(
961 &root_surface,
962 (),
963 |_, _, _| TraversalAction::DoChildren(()),
964 |_, states, _| {
965 let mut guard = states.cached_state.get::<SurfaceAttributes>();
966 let attrs = guard.current();
967 if let Some(compositor::BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref()
968 {
969 buffer.release();
970 }
971 },
972 |_, _, _| true,
973 );
974
975 with_states(&root_surface, |states| {
977 if let Some(data) = states.data_map.get::<XdgToplevelSurfaceData>() {
978 let lock = data.lock().unwrap();
979 new_title = lock.title.clone().unwrap_or_default();
980 new_app_id = lock.app_id.clone().unwrap_or_default();
981 }
982 });
983
984 if committed_pixels.is_none()
986 && !have_pending
987 && !warned_unreadable
988 && let Some(info) = self.surfaces.get_mut(&key)
989 {
990 info.unreadable_warned = true;
991 }
992
993 if let Some(info) = self.surfaces.get_mut(&key)
994 && new_title != info.last_title
995 {
996 info.last_title = new_title.clone();
997 let _ = self.event_tx.send(CompositorEvent::SurfaceTitle {
998 surface_id,
999 title: new_title,
1000 });
1001 }
1002
1003 if let Some(info) = self.surfaces.get_mut(&key)
1004 && new_app_id != info.last_app_id
1005 {
1006 info.last_app_id = new_app_id.clone();
1007 let _ = self.event_tx.send(CompositorEvent::SurfaceAppId {
1008 surface_id,
1009 app_id: new_app_id,
1010 });
1011 }
1012
1013 if let Some((width, height, pixels)) = committed_pixels {
1014 let info = self.surfaces.get_mut(&key).unwrap();
1015 if width != info.last_width || height != info.last_height {
1016 info.last_width = width;
1017 info.last_height = height;
1018 let _ = self.event_tx.send(CompositorEvent::SurfaceResized {
1019 surface_id,
1020 width: width as u16,
1021 height: height as u16,
1022 });
1023 }
1024
1025 if !pixels.is_empty() {
1026 self.pending_commits
1027 .insert(surface_id, (width, height, pixels));
1028 }
1029 }
1030
1031 }
1034}
1035
1036impl BufferHandler for Compositor {
1037 fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
1038}
1039
1040impl ShmHandler for Compositor {
1041 fn shm_state(&self) -> &ShmState {
1042 &self.shm_state
1043 }
1044}
1045
1046impl XdgShellHandler for Compositor {
1047 fn xdg_shell_state(&mut self) -> &mut XdgShellState {
1048 &mut self.xdg_shell_state
1049 }
1050
1051 fn new_toplevel(&mut self, surface: ToplevelSurface) {
1052 if self.verbose {
1053 eprintln!("[compositor] new_toplevel");
1054 }
1055 let window = Window::new_wayland_window(surface.clone());
1056 let wl_surface = surface.wl_surface().clone();
1057 let key = wl_surface.id().protocol_id() as u64;
1058 let surface_id = self.allocate_surface_id();
1059
1060 self.space.map_element(window.clone(), (0, 0), false);
1061 self.space.refresh();
1062
1063 let info = SurfaceInfo {
1064 surface_id,
1065 window,
1066 last_width: 0,
1067 last_height: 0,
1068 last_title: String::new(),
1069 last_app_id: String::new(),
1070 unreadable_warned: false,
1071 };
1072 self.surfaces.insert(key, info);
1073 self.surface_lookup.insert(surface_id, key);
1074
1075 surface.with_pending_state(|state| {
1076 state.states.set(
1077 smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State::Activated,
1078 );
1079 });
1080 surface.send_configure();
1081
1082 let _ = self.event_tx.send(CompositorEvent::SurfaceCreated {
1083 surface_id,
1084 title: String::new(),
1085 app_id: String::new(),
1086 parent_id: 0,
1087 width: 0,
1088 height: 0,
1089 });
1090 }
1091
1092 fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
1093 let wl_surface = surface.wl_surface();
1094 let key = wl_surface.id().protocol_id() as u64;
1095 self.surface_pixel_cache.remove(&key);
1096 if let Some(info) = self.surfaces.remove(&key) {
1097 self.surface_lookup.remove(&info.surface_id);
1098 self.space.unmap_elem(&info.window);
1099 let _ = self.event_tx.send(CompositorEvent::SurfaceDestroyed {
1100 surface_id: info.surface_id,
1101 });
1102 }
1103 }
1104
1105 fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {}
1106
1107 fn grab(&mut self, _surface: PopupSurface, _seat: WlSeat, _serial: Serial) {}
1108
1109 fn reposition_request(
1110 &mut self,
1111 _surface: PopupSurface,
1112 _positioner: PositionerState,
1113 _token: u32,
1114 ) {
1115 }
1116}
1117
1118impl OutputHandler for Compositor {}
1119
1120impl SeatHandler for Compositor {
1121 type KeyboardFocus = WlSurface;
1122 type PointerFocus = WlSurface;
1123 type TouchFocus = WlSurface;
1124
1125 fn seat_state(&mut self) -> &mut SeatState<Self> {
1126 &mut self.seat_state
1127 }
1128
1129 fn cursor_image(
1130 &mut self,
1131 _seat: &Seat<Self>,
1132 _image: smithay::input::pointer::CursorImageStatus,
1133 ) {
1134 }
1135
1136 fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
1137 if let Some(surface) = focused {
1138 let key = surface.id().protocol_id() as u64;
1139 if let Some(info) = self.surfaces.get(&key) {
1140 self.focused_surface_id = info.surface_id;
1141 }
1142 }
1143 let client = focused.and_then(|s| self.display_handle.get_client(s.id()).ok());
1144 set_data_device_focus(&self.display_handle, seat, client.clone());
1145 set_primary_focus(&self.display_handle, seat, client);
1146 }
1147}
1148
1149impl SelectionHandler for Compositor {
1150 type SelectionUserData = Arc<Vec<u8>>;
1151
1152 fn new_selection(
1153 &mut self,
1154 ty: SelectionTarget,
1155 source: Option<SelectionSource>,
1156 seat: Seat<Self>,
1157 ) {
1158 if ty != SelectionTarget::Clipboard {
1159 return;
1160 }
1161 let Some(source) = source else { return };
1162 let mime_types = source.mime_types();
1163
1164 let preferred = [
1166 "text/plain;charset=utf-8",
1167 "text/plain",
1168 "UTF8_STRING",
1169 "TEXT",
1170 "STRING",
1171 ];
1172 let Some(mime) = preferred
1173 .iter()
1174 .map(|m| m.to_string())
1175 .find(|m| mime_types.contains(m))
1176 else {
1177 return;
1178 };
1179
1180 let (mut read_stream, write_stream) = match std::os::unix::net::UnixStream::pair() {
1183 Ok(pair) => pair,
1184 Err(_) => return,
1185 };
1186 let write_fd: OwnedFd = write_stream.into();
1187
1188 if request_data_device_client_selection::<Self>(&seat, mime.clone(), write_fd).is_err() {
1189 return;
1190 }
1191
1192 let event_tx = self.event_tx.clone();
1193 let event_notify = self.event_notify.clone();
1194 let surface_id = self.focused_surface_id;
1195 std::thread::Builder::new()
1196 .name("clipboard-read".into())
1197 .spawn(move || {
1198 use std::io::Read;
1199 const MAX_CLIPBOARD_SIZE: usize = 16 * 1024 * 1024; let _ = read_stream.set_read_timeout(Some(std::time::Duration::from_secs(1)));
1201 let mut data = Vec::new();
1202 let mut buf = [0u8; 8192];
1203 loop {
1204 match read_stream.read(&mut buf) {
1205 Ok(0) => break,
1206 Ok(n) => {
1207 data.extend_from_slice(&buf[..n]);
1208 if data.len() > MAX_CLIPBOARD_SIZE {
1209 break;
1210 }
1211 }
1212 Err(_) => break,
1213 }
1214 }
1215 data.truncate(MAX_CLIPBOARD_SIZE);
1216 if !data.is_empty() {
1217 let _ = event_tx.send(CompositorEvent::ClipboardContent {
1218 surface_id,
1219 mime_type: mime,
1220 data,
1221 });
1222 (event_notify)();
1223 }
1224 })
1225 .expect("failed to spawn clipboard-read thread");
1226 }
1227
1228 fn send_selection(
1229 &mut self,
1230 _ty: SelectionTarget,
1231 _mime_type: String,
1232 fd: OwnedFd,
1233 _seat: Seat<Self>,
1234 user_data: &Self::SelectionUserData,
1235 ) {
1236 let data = user_data.clone();
1238 std::thread::Builder::new()
1239 .name("clipboard-write".into())
1240 .spawn(move || {
1241 use std::io::Write;
1242 let mut file = std::fs::File::from(fd);
1243 let _ = file.write_all(&data);
1244 })
1245 .expect("failed to spawn clipboard-write thread");
1246 }
1247}
1248
1249impl DataDeviceHandler for Compositor {
1250 fn data_device_state(&self) -> &DataDeviceState {
1251 &self.data_device_state
1252 }
1253}
1254
1255impl ClientDndGrabHandler for Compositor {}
1256impl ServerDndGrabHandler for Compositor {}
1257
1258impl XdgDecorationHandler for Compositor {
1259 fn new_decoration(&mut self, toplevel: ToplevelSurface) {
1260 toplevel.with_pending_state(|state| {
1261 state.decoration_mode = Some(DecorationMode::ServerSide);
1262 });
1263 toplevel.send_configure();
1264 }
1265
1266 fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: DecorationMode) {
1267 toplevel.with_pending_state(|state| {
1268 state.decoration_mode = Some(DecorationMode::ServerSide);
1269 });
1270 toplevel.send_configure();
1271 }
1272
1273 fn unset_mode(&mut self, toplevel: ToplevelSurface) {
1274 toplevel.with_pending_state(|state| {
1275 state.decoration_mode = Some(DecorationMode::ServerSide);
1276 });
1277 toplevel.send_configure();
1278 }
1279}
1280
1281impl DmabufHandler for Compositor {
1282 fn dmabuf_state(&mut self) -> &mut DmabufState {
1283 &mut self.dmabuf_state
1284 }
1285
1286 fn dmabuf_imported(
1287 &mut self,
1288 _global: &DmabufGlobal,
1289 _dmabuf: Dmabuf,
1290 notifier: ImportNotifier,
1291 ) {
1292 let _ = notifier.successful::<Compositor>();
1293 }
1294}
1295
1296impl DrmSyncobjHandler for Compositor {
1297 fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> {
1298 self.syncobj_state.as_mut()
1299 }
1300}
1301
1302fn with_dmabuf_plane_bytes<T>(
1303 dmabuf: &Dmabuf,
1304 plane_idx: usize,
1305 f: impl FnOnce(&[u8]) -> Option<T>,
1306) -> Option<T> {
1307 let _ = dmabuf.sync_plane(
1308 plane_idx,
1309 smithay::backend::allocator::dmabuf::DmabufSyncFlags::START
1310 | smithay::backend::allocator::dmabuf::DmabufSyncFlags::READ,
1311 );
1312 struct PlaneSyncGuard<'a> {
1313 dmabuf: &'a Dmabuf,
1314 plane_idx: usize,
1315 }
1316
1317 impl Drop for PlaneSyncGuard<'_> {
1318 fn drop(&mut self) {
1319 let _ = self.dmabuf.sync_plane(
1320 self.plane_idx,
1321 smithay::backend::allocator::dmabuf::DmabufSyncFlags::END
1322 | smithay::backend::allocator::dmabuf::DmabufSyncFlags::READ,
1323 );
1324 }
1325 }
1326
1327 let _sync_guard = PlaneSyncGuard { dmabuf, plane_idx };
1328 let mapping = dmabuf.map_plane(plane_idx, DmabufMappingMode::READ).ok()?;
1329 let ptr = mapping.ptr() as *const u8;
1330 let len = mapping.length();
1331 let plane_data = unsafe { std::slice::from_raw_parts(ptr, len) };
1332 f(plane_data)
1333}
1334
1335fn yuv420_to_rgb(y: u8, u: u8, v: u8) -> [u8; 3] {
1336 let y = (y as i32 - 16).max(0);
1337 let u = u as i32 - 128;
1338 let v = v as i32 - 128;
1339
1340 let r = ((298 * y + 409 * v + 128) >> 8).clamp(0, 255) as u8;
1341 let g = ((298 * y - 100 * u - 208 * v + 128) >> 8).clamp(0, 255) as u8;
1342 let b = ((298 * y + 516 * u + 128) >> 8).clamp(0, 255) as u8;
1343
1344 [r, g, b]
1345}
1346
1347fn read_le_u16(bytes: &[u8], offset: usize) -> Option<u16> {
1348 let end = offset.checked_add(2)?;
1349 let raw = bytes.get(offset..end)?;
1350 Some(u16::from_le_bytes([raw[0], raw[1]]))
1351}
1352
1353#[allow(dead_code)]
1357fn read_packed_dmabuf_fd(
1358 raw_fd: std::os::fd::RawFd,
1359 stride: usize,
1360 width: usize,
1361 height: usize,
1362 format: Fourcc,
1363) -> Option<PixelData> {
1364 let file_size = unsafe { libc::lseek(raw_fd, 0, libc::SEEK_END) };
1365 if file_size <= 0 {
1366 return None;
1367 }
1368 let map_len = file_size as usize;
1369
1370 #[repr(C)]
1371 struct DmaBufSync {
1372 flags: u64,
1373 }
1374 const DMA_BUF_SYNC_READ: u64 = 1;
1375 const DMA_BUF_SYNC_START: u64 = 0;
1376 const DMA_BUF_SYNC_END: u64 = 4;
1377 const DMA_BUF_IOCTL_SYNC: libc::c_ulong = 0x40086200;
1378
1379 let sync_start = DmaBufSync {
1380 flags: DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ,
1381 };
1382 unsafe {
1383 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_start);
1384 }
1385
1386 let ptr = unsafe {
1387 libc::mmap(
1388 std::ptr::null_mut(),
1389 map_len,
1390 libc::PROT_READ,
1391 libc::MAP_SHARED,
1392 raw_fd,
1393 0,
1394 )
1395 };
1396
1397 let result = if ptr == libc::MAP_FAILED {
1398 None
1399 } else {
1400 let plane_data = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_len) };
1401 let r = read_packed_dmabuf(plane_data, stride, width, height, false, format);
1402 unsafe {
1403 libc::munmap(ptr, map_len);
1404 }
1405 r
1406 };
1407
1408 let sync_end = DmaBufSync {
1409 flags: DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ,
1410 };
1411 unsafe {
1412 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_end);
1413 }
1414
1415 result
1416}
1417
1418fn read_packed_dmabuf(
1424 plane_data: &[u8],
1425 stride: usize,
1426 width: usize,
1427 height: usize,
1428 y_inverted: bool,
1429 format: Fourcc,
1430) -> Option<PixelData> {
1431 let row_bytes = width * 4;
1432 let total = row_bytes * height;
1433
1434 let (is_bgra, force_opaque) = match format {
1437 Fourcc::Argb8888 => (true, false), Fourcc::Xrgb8888 => (true, true), Fourcc::Abgr8888 => (false, false), Fourcc::Xbgr8888 => (false, true), _ => return None,
1442 };
1443
1444 if !y_inverted && stride == row_bytes && plane_data.len() >= total {
1446 let mut buf = plane_data[..total].to_vec();
1447 if force_opaque {
1448 for px in buf.chunks_exact_mut(4) {
1450 px[3] = 255;
1451 }
1452 }
1453 return Some(if is_bgra {
1454 PixelData::Bgra(Arc::new(buf))
1455 } else {
1456 PixelData::Rgba(Arc::new(buf))
1457 });
1458 }
1459
1460 let mut buf = Vec::with_capacity(total);
1462 for row in 0..height {
1463 let src_row = if y_inverted { height - 1 - row } else { row };
1464 let row_start = src_row * stride;
1465 let row_end = row_start + row_bytes;
1466 if row_end > plane_data.len() {
1467 return None;
1468 }
1469 buf.extend_from_slice(&plane_data[row_start..row_end]);
1470 }
1471 if force_opaque {
1472 for px in buf.chunks_exact_mut(4) {
1473 px[3] = 255;
1474 }
1475 }
1476 Some(if is_bgra {
1477 PixelData::Bgra(Arc::new(buf))
1478 } else {
1479 PixelData::Rgba(Arc::new(buf))
1480 })
1481}
1482
1483#[cfg(test)]
1484fn read_nv12_dmabuf(
1485 y_plane: &[u8],
1486 y_stride: usize,
1487 uv_plane: &[u8],
1488 uv_stride: usize,
1489 width: usize,
1490 height: usize,
1491 y_inverted: bool,
1492) -> Option<Vec<u8>> {
1493 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1494 return None;
1495 }
1496
1497 let mut rgba = Vec::with_capacity(width * height * 4);
1498 for row in 0..height {
1499 let src_row = if y_inverted { height - 1 - row } else { row };
1500 let y_row_start = src_row * y_stride;
1501 let uv_row_start = (src_row / 2) * uv_stride;
1502 for col in 0..width {
1503 let y = y_plane[y_row_start + col];
1504 let uv_idx = uv_row_start + (col / 2) * 2;
1505 let u = uv_plane[uv_idx];
1506 let v = uv_plane[uv_idx + 1];
1507 let [r, g, b] = yuv420_to_rgb(y, u, v);
1508 rgba.extend_from_slice(&[r, g, b, 255]);
1509 }
1510 }
1511 Some(rgba)
1512}
1513
1514#[cfg(test)]
1515fn read_p010_dmabuf(
1516 y_plane: &[u8],
1517 y_stride: usize,
1518 uv_plane: &[u8],
1519 uv_stride: usize,
1520 width: usize,
1521 height: usize,
1522 y_inverted: bool,
1523) -> Option<Vec<u8>> {
1524 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1525 return None;
1526 }
1527
1528 let mut rgba = Vec::with_capacity(width * height * 4);
1529 for row in 0..height {
1530 let src_row = if y_inverted { height - 1 - row } else { row };
1531 let y_row_start = src_row * y_stride;
1532 let y_row_end = y_row_start + width * 2;
1533 if y_row_end > y_plane.len() {
1534 return None;
1535 }
1536
1537 let uv_row_start = (src_row / 2) * uv_stride;
1538 let uv_row_end = uv_row_start + width * 2;
1539 if uv_row_end > uv_plane.len() {
1540 return None;
1541 }
1542
1543 for col in 0..width {
1544 let y = (read_le_u16(y_plane, y_row_start + col * 2)? >> 8) as u8;
1545 let uv_idx = uv_row_start + (col / 2) * 4;
1546 let u = (read_le_u16(uv_plane, uv_idx)? >> 8) as u8;
1547 let v = (read_le_u16(uv_plane, uv_idx + 2)? >> 8) as u8;
1548 let [r, g, b] = yuv420_to_rgb(y, u, v);
1549 rgba.extend_from_slice(&[r, g, b, 255]);
1550 }
1551 }
1552
1553 Some(rgba)
1554}
1555
1556fn read_nv12_dmabuf_passthrough(
1559 y_plane: &[u8],
1560 y_stride: usize,
1561 uv_plane: &[u8],
1562 uv_stride: usize,
1563 width: usize,
1564 height: usize,
1565 y_inverted: bool,
1566) -> Option<(Vec<u8>, usize, usize)> {
1567 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1568 return None;
1569 }
1570
1571 let uv_height = height / 2;
1572 let out_y_stride = width;
1574 let out_uv_stride = width;
1576 let mut data = vec![0u8; out_y_stride * height + out_uv_stride * uv_height];
1577
1578 for row in 0..height {
1580 let src_row = if y_inverted { height - 1 - row } else { row };
1581 let src_start = src_row * y_stride;
1582 let src_end = src_start + width;
1583 if src_end > y_plane.len() {
1584 return None;
1585 }
1586 let dst_start = row * out_y_stride;
1587 data[dst_start..dst_start + width].copy_from_slice(&y_plane[src_start..src_end]);
1588 }
1589
1590 let uv_dst_offset = out_y_stride * height;
1592 for row in 0..uv_height {
1593 let src_row = if y_inverted { uv_height - 1 - row } else { row };
1594 let src_start = src_row * uv_stride;
1595 let src_end = src_start + width; if src_end > uv_plane.len() {
1597 return None;
1598 }
1599 let dst_start = uv_dst_offset + row * out_uv_stride;
1600 data[dst_start..dst_start + width].copy_from_slice(&uv_plane[src_start..src_end]);
1601 }
1602
1603 Some((data, out_y_stride, out_uv_stride))
1604}
1605
1606fn read_p010_to_nv12(
1609 y_plane: &[u8],
1610 y_stride: usize,
1611 uv_plane: &[u8],
1612 uv_stride: usize,
1613 width: usize,
1614 height: usize,
1615 y_inverted: bool,
1616) -> Option<(Vec<u8>, usize, usize)> {
1617 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
1618 return None;
1619 }
1620
1621 let uv_height = height / 2;
1622 let out_y_stride = width;
1623 let out_uv_stride = width;
1624 let mut data = vec![0u8; out_y_stride * height + out_uv_stride * uv_height];
1625
1626 for row in 0..height {
1628 let src_row = if y_inverted { height - 1 - row } else { row };
1629 let dst_start = row * out_y_stride;
1630 for col in 0..width {
1631 let src_offset = src_row * y_stride + col * 2;
1632 let val = read_le_u16(y_plane, src_offset)?;
1633 data[dst_start + col] = (val >> 8) as u8;
1634 }
1635 }
1636
1637 let uv_dst_offset = out_y_stride * height;
1639 for row in 0..uv_height {
1640 let src_row = if y_inverted { uv_height - 1 - row } else { row };
1641 let dst_start = uv_dst_offset + row * out_uv_stride;
1642 for col in 0..width / 2 {
1643 let src_offset = src_row * uv_stride + col * 4;
1644 let u = (read_le_u16(uv_plane, src_offset)? >> 8) as u8;
1645 let v = (read_le_u16(uv_plane, src_offset + 2)? >> 8) as u8;
1646 data[dst_start + col * 2] = u;
1647 data[dst_start + col * 2 + 1] = v;
1648 }
1649 }
1650
1651 Some((data, out_y_stride, out_uv_stride))
1652}
1653
1654fn fourcc_to_drm(code: Fourcc) -> u32 {
1656 code as u32
1657}
1658
1659fn read_dmabuf_pixels(dmabuf: &Dmabuf) -> Option<(u32, u32, PixelData)> {
1660 let size = dmabuf.size();
1661 let width = size.w as u32;
1662 let height = size.h as u32;
1663 if width == 0 || height == 0 {
1664 return None;
1665 }
1666
1667 let format = dmabuf.format();
1668
1669 let y_inverted = dmabuf.y_inverted();
1688
1689 if !y_inverted
1709 && matches!(
1710 format.code,
1711 Fourcc::Argb8888 | Fourcc::Xrgb8888 | Fourcc::Abgr8888 | Fourcc::Xbgr8888
1712 )
1713 && let Some(borrowed_fd) = dmabuf.handles().next()
1714 && let Ok(owned) = borrowed_fd.try_clone_to_owned()
1715 {
1716 let stride = dmabuf.strides().next().unwrap_or(width * 4);
1717 let offset = dmabuf.offsets().next().unwrap_or(0);
1718 return Some((
1719 width,
1720 height,
1721 PixelData::DmaBuf {
1722 fd: Arc::new(owned),
1723 fourcc: fourcc_to_drm(format.code),
1724 modifier: format.modifier.into(),
1725 stride,
1726 offset,
1727 },
1728 ));
1729 }
1730
1731 let modifier_is_linear = matches!(format.modifier, Modifier::Linear);
1735 if !modifier_is_linear {
1736 return None;
1737 }
1738
1739 let width_usize = width as usize;
1740 let height_usize = height as usize;
1741 let pixel_data = match format.code {
1742 Fourcc::Argb8888 | Fourcc::Xrgb8888 | Fourcc::Abgr8888 | Fourcc::Xbgr8888 => {
1743 let stride = dmabuf.strides().next().unwrap_or(width * 4) as usize;
1745 with_dmabuf_plane_bytes(dmabuf, 0, |plane_data| {
1746 read_packed_dmabuf(
1747 plane_data,
1748 stride,
1749 width_usize,
1750 height_usize,
1751 y_inverted,
1752 format.code,
1753 )
1754 })?
1755 }
1756 Fourcc::Nv12 => {
1757 let mut strides = dmabuf.strides();
1759 let y_stride = strides.next().unwrap_or(width) as usize;
1760 let uv_stride = strides.next().unwrap_or(width) as usize;
1761 let nv12 = with_dmabuf_plane_bytes(dmabuf, 0, |y_plane_data| {
1762 with_dmabuf_plane_bytes(dmabuf, 1, |uv_plane_data| {
1763 read_nv12_dmabuf_passthrough(
1764 y_plane_data,
1765 y_stride,
1766 uv_plane_data,
1767 uv_stride,
1768 width_usize,
1769 height_usize,
1770 y_inverted,
1771 )
1772 })
1773 })?;
1774 PixelData::Nv12 {
1775 data: Arc::new(nv12.0),
1776 y_stride: nv12.1,
1777 uv_stride: nv12.2,
1778 }
1779 }
1780 Fourcc::P010 => {
1781 let mut strides = dmabuf.strides();
1784 let y_stride = strides.next().unwrap_or(width * 2) as usize;
1785 let uv_stride = strides.next().unwrap_or(width * 2) as usize;
1786 let nv12 = with_dmabuf_plane_bytes(dmabuf, 0, |y_plane| {
1787 with_dmabuf_plane_bytes(dmabuf, 1, |uv_plane| {
1788 read_p010_to_nv12(
1789 y_plane,
1790 y_stride,
1791 uv_plane,
1792 uv_stride,
1793 width_usize,
1794 height_usize,
1795 y_inverted,
1796 )
1797 })
1798 })?;
1799 PixelData::Nv12 {
1800 data: Arc::new(nv12.0),
1801 y_stride: nv12.1,
1802 uv_stride: nv12.2,
1803 }
1804 }
1805 _ => return None,
1806 };
1807
1808 Some((width, height, pixel_data))
1809}
1810
1811impl PrimarySelectionHandler for Compositor {
1812 fn primary_selection_state(&self) -> &PrimarySelectionState {
1813 &self.primary_selection_state
1814 }
1815}
1816
1817impl XdgActivationHandler for Compositor {
1818 fn activation_state(&mut self) -> &mut XdgActivationState {
1819 &mut self.activation_state
1820 }
1821
1822 fn request_activation(
1823 &mut self,
1824 _token: XdgActivationToken,
1825 _token_data: XdgActivationTokenData,
1826 _surface: WlSurface,
1827 ) {
1828 }
1829}
1830
1831impl FractionalScaleHandler for Compositor {
1832 fn new_fractional_scale(&mut self, _surface: WlSurface) {}
1833}
1834
1835impl XdgToplevelIconHandler for Compositor {}
1836impl TabletSeatHandler for Compositor {}
1837
1838delegate_compositor!(Compositor);
1839delegate_cursor_shape!(Compositor);
1840delegate_shm!(Compositor);
1841delegate_xdg_shell!(Compositor);
1842delegate_seat!(Compositor);
1843delegate_data_device!(Compositor);
1844delegate_primary_selection!(Compositor);
1845delegate_output!(Compositor);
1846delegate_dmabuf!(Compositor);
1847smithay::delegate_drm_syncobj!(Compositor);
1848delegate_fractional_scale!(Compositor);
1849delegate_viewporter!(Compositor);
1850delegate_xdg_activation!(Compositor);
1851delegate_xdg_decoration!(Compositor);
1852delegate_xdg_toplevel_icon!(Compositor);
1853delegate_text_input_manager!(Compositor);
1854
1855pub struct CompositorHandle {
1856 pub event_rx: mpsc::Receiver<CompositorEvent>,
1857 pub command_tx: mpsc::Sender<CompositorCommand>,
1858 pub socket_name: String,
1859 pub thread: std::thread::JoinHandle<()>,
1860 pub shutdown: Arc<AtomicBool>,
1861 loop_signal: LoopSignal,
1862}
1863
1864impl CompositorHandle {
1865 pub fn wake(&self) {
1868 self.loop_signal.wakeup();
1869 }
1870}
1871
1872pub fn spawn_compositor(
1873 verbose: bool,
1874 event_notify: Arc<dyn Fn() + Send + Sync>,
1875 gpu_device: &str,
1876) -> CompositorHandle {
1877 let gpu_device = gpu_device.to_string();
1878 let (event_tx, event_rx) = mpsc::channel();
1879 let (command_tx, command_rx) = mpsc::channel();
1880 let (socket_tx, socket_rx) = mpsc::sync_channel(1);
1881 let (signal_tx, signal_rx) = mpsc::sync_channel::<LoopSignal>(1);
1882 let shutdown = Arc::new(AtomicBool::new(false));
1883 let shutdown_clone = shutdown.clone();
1884
1885 let runtime_dir = std::env::var_os("XDG_RUNTIME_DIR")
1886 .map(std::path::PathBuf::from)
1887 .filter(|p| {
1888 let probe = p.join(".blit-probe");
1895 if std::fs::write(&probe, b"").is_ok() {
1896 let _ = std::fs::remove_file(&probe);
1897 true
1898 } else {
1899 false
1900 }
1901 })
1902 .unwrap_or_else(std::env::temp_dir);
1903
1904 let runtime_dir_clone = runtime_dir.clone();
1905 let thread = std::thread::Builder::new()
1906 .name("compositor".into())
1907 .spawn(move || {
1908 unsafe { std::env::set_var("XDG_RUNTIME_DIR", &runtime_dir_clone) };
1909 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1910 run_compositor(
1911 event_tx,
1912 command_rx,
1913 socket_tx,
1914 signal_tx,
1915 event_notify,
1916 shutdown_clone,
1917 verbose,
1918 gpu_device,
1919 );
1920 }));
1921 if let Err(e) = result {
1922 let msg = if let Some(s) = e.downcast_ref::<&str>() {
1923 s.to_string()
1924 } else if let Some(s) = e.downcast_ref::<String>() {
1925 s.clone()
1926 } else {
1927 "unknown panic".to_string()
1928 };
1929 eprintln!("[compositor] PANIC: {msg}");
1930 }
1931 })
1932 .expect("failed to spawn compositor thread");
1933
1934 let socket_name = socket_rx.recv().expect("compositor failed to start");
1935 let socket_name = runtime_dir
1936 .join(&socket_name)
1937 .to_string_lossy()
1938 .into_owned();
1939 let loop_signal = signal_rx
1940 .recv()
1941 .expect("compositor failed to send loop signal");
1942
1943 CompositorHandle {
1944 event_rx,
1945 command_tx,
1946 socket_name,
1947 thread,
1948 shutdown,
1949 loop_signal,
1950 }
1951}
1952
1953#[allow(clippy::too_many_arguments)]
1954fn run_compositor(
1955 event_tx: mpsc::Sender<CompositorEvent>,
1956 command_rx: mpsc::Receiver<CompositorCommand>,
1957 socket_tx: mpsc::SyncSender<String>,
1958 signal_tx: mpsc::SyncSender<LoopSignal>,
1959 event_notify: Arc<dyn Fn() + Send + Sync>,
1960 shutdown: Arc<AtomicBool>,
1961 verbose: bool,
1962 gpu_device: String,
1963) {
1964 let mut event_loop: EventLoop<Compositor> =
1965 EventLoop::try_new().expect("failed to create event loop");
1966 let display: Display<Compositor> = Display::new().expect("failed to create display");
1967 let dh = display.handle();
1968
1969 let compositor_state = CompositorState::new::<Compositor>(&dh);
1970 let xdg_shell_state = XdgShellState::new::<Compositor>(&dh);
1971 let shm_state = ShmState::new::<Compositor>(&dh, vec![]);
1972 let data_device_state = DataDeviceState::new::<Compositor>(&dh);
1973 let viewporter_state = ViewporterState::new::<Compositor>(&dh);
1974 let xdg_decoration_state = XdgDecorationState::new::<Compositor>(&dh);
1975 let primary_selection_state = PrimarySelectionState::new::<Compositor>(&dh);
1976 let activation_state = XdgActivationState::new::<Compositor>(&dh);
1977 FractionalScaleManagerState::new::<Compositor>(&dh);
1978 CursorShapeManagerState::new::<Compositor>(&dh);
1979 TextInputManagerState::new::<Compositor>(&dh);
1983
1984 let mut dmabuf_state = DmabufState::new();
1985 let dmabuf_formats = {
2003 use Fourcc::*;
2004 let packed = [Argb8888, Xrgb8888, Abgr8888, Xbgr8888];
2005 let yuv = [Fourcc::Nv12, Fourcc::P010];
2006 let mut fmts = Vec::new();
2007 for code in packed {
2008 fmts.push(DmabufFormat {
2009 code,
2010 modifier: Modifier::Linear,
2011 });
2012 fmts.push(DmabufFormat {
2013 code,
2014 modifier: Modifier::Invalid,
2015 });
2016 }
2017 for code in yuv {
2018 fmts.push(DmabufFormat {
2019 code,
2020 modifier: Modifier::Linear,
2021 });
2022 }
2023 fmts
2024 };
2025 let dmabuf_global = match std::fs::metadata(&gpu_device).ok().and_then(|m| {
2029 use std::os::unix::fs::MetadataExt;
2030 let dev = m.rdev();
2031 let formats = dmabuf_formats.clone();
2032 let feedback = DmabufFeedbackBuilder::new(dev, formats).build().ok()?;
2033 Some(dmabuf_state.create_global_with_default_feedback::<Compositor>(&dh, &feedback))
2034 }) {
2035 Some(global) => {
2036 if verbose {
2037 eprintln!("[compositor] DMA-BUF feedback enabled ({gpu_device})");
2038 }
2039 global
2040 }
2041 None => {
2042 if verbose {
2043 eprintln!("[compositor] DMA-BUF feedback unavailable, using basic global");
2044 }
2045 dmabuf_state.create_global::<Compositor>(&dh, dmabuf_formats)
2046 }
2047 };
2048
2049 let mut seat_state = SeatState::new();
2050 let mut seat = seat_state.new_wl_seat(&dh, "headless");
2051 let keymap = include_str!("../data/us-qwerty.xkb").to_string();
2052 seat.add_keyboard_from_keymap_string(keymap, 200, 25)
2053 .expect("failed to add keyboard from embedded keymap");
2054 seat.add_pointer();
2055
2056 let output = Output::new(
2057 "headless-0".to_string(),
2058 PhysicalProperties {
2059 size: (0, 0).into(),
2060 subpixel: Subpixel::Unknown,
2061 make: "Virtual".into(),
2062 model: "Headless".into(),
2063 },
2064 );
2065 let mode = Mode {
2066 size: (1920, 1080).into(),
2067 refresh: 60_000,
2068 };
2069 output.create_global::<Compositor>(&dh);
2070 output.change_current_state(
2071 Some(mode),
2072 Some(Transform::Normal),
2073 Some(smithay::output::Scale::Integer(1)),
2074 Some((0, 0).into()),
2075 );
2076 output.set_preferred(mode);
2077
2078 let mut space = Space::default();
2079 space.map_output(&output, (0, 0));
2080
2081 let listening_socket = ListeningSocketSource::new_auto().unwrap_or_else(|e| {
2082 let dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "(unset)".into());
2083 panic!(
2084 "failed to create wayland socket in XDG_RUNTIME_DIR={dir}: {e}\n\
2085 hint: ensure the directory exists and is writable by the current user"
2086 );
2087 });
2088 let socket_name = listening_socket
2089 .socket_name()
2090 .to_string_lossy()
2091 .into_owned();
2092 socket_tx.send(socket_name).unwrap();
2093
2094 let handle = event_loop.handle();
2095
2096 handle
2097 .insert_source(listening_socket, |client_stream, _, state| {
2098 if let Err(e) = state.display_handle.insert_client(
2099 client_stream,
2100 Arc::new(ClientData {
2101 compositor_state: CompositorClientState::default(),
2102 }),
2103 ) && verbose
2104 {
2105 eprintln!("[compositor] insert_client error: {e}");
2106 }
2107 })
2108 .expect("failed to insert listening socket");
2109
2110 let loop_signal = event_loop.get_signal();
2111
2112 let mut compositor = Compositor {
2113 display_handle: dh.clone(),
2114 compositor_state,
2115 xdg_shell_state,
2116 shm_state,
2117 seat_state,
2118 data_device_state,
2119 viewporter_state,
2120 xdg_decoration_state,
2121 dmabuf_state,
2122 dmabuf_global,
2123 syncobj_state: {
2124 std::fs::OpenOptions::new()
2128 .read(true)
2129 .write(true)
2130 .open(&gpu_device)
2131 .ok()
2132 .and_then(|f| {
2133 use std::os::fd::OwnedFd;
2134 let owned: OwnedFd = f.into();
2135 let dev_fd = smithay::utils::DeviceFd::from(owned);
2136 let drm_fd = DrmDeviceFd::new(dev_fd);
2137 if supports_syncobj_eventfd(&drm_fd) {
2138 let state = DrmSyncobjState::new::<Compositor>(&dh, drm_fd);
2139 eprintln!("[compositor] explicit sync (drm-syncobj) enabled");
2140 Some(state)
2141 } else {
2142 eprintln!("[compositor] explicit sync not supported by GPU");
2143 None
2144 }
2145 })
2146 },
2147 primary_selection_state,
2148 activation_state,
2149 seat,
2150 output,
2151 space,
2152 surfaces: HashMap::new(),
2153 surface_lookup: HashMap::new(),
2154 next_surface_id: 1,
2155 event_tx,
2156 event_notify,
2157 loop_signal: loop_signal.clone(),
2158 verbose,
2159 focused_surface_id: 0,
2160 pending_commits: HashMap::new(),
2161 surface_pixel_cache: HashMap::new(),
2162 gpu_renderer: super::render::SurfaceRenderer::try_new(&gpu_device),
2163 };
2164
2165 let _ = signal_tx.send(loop_signal.clone());
2167
2168 let display_source = Generic::new(display, Interest::READ, calloop::Mode::Level);
2169 handle
2170 .insert_source(display_source, |_, display, state| {
2171 let d = unsafe { display.get_mut() };
2172 if let Err(e) = d.dispatch_clients(state)
2173 && verbose
2174 {
2175 eprintln!("[compositor] dispatch_clients error: {e}");
2176 }
2177 if let Err(e) = d.flush_clients()
2178 && verbose
2179 {
2180 eprintln!("[compositor] flush_clients error: {e}");
2181 }
2182 Ok(PostAction::Continue)
2183 })
2184 .expect("failed to insert display source");
2185
2186 if verbose {
2187 eprintln!("[compositor] entering event loop");
2188 }
2189 while !shutdown.load(Ordering::Relaxed) {
2190 while let Ok(cmd) = command_rx.try_recv() {
2191 match cmd {
2192 CompositorCommand::Shutdown => {
2193 shutdown.store(true, Ordering::Relaxed);
2194 return;
2195 }
2196 other => compositor.handle_command(other),
2197 }
2198 }
2199
2200 if let Err(e) =
2204 event_loop.dispatch(Some(std::time::Duration::from_secs(1)), &mut compositor)
2205 && verbose
2206 {
2207 eprintln!("[compositor] event loop error: {e}");
2208 }
2209
2210 if !compositor.pending_commits.is_empty() {
2212 compositor.flush_pending_commits();
2213 }
2214
2215 if let Err(e) = compositor.display_handle.flush_clients()
2216 && verbose
2217 {
2218 eprintln!("[compositor] flush error: {e}");
2219 }
2220 }
2221 if verbose {
2222 eprintln!("[compositor] event loop exited");
2223 }
2224}
2225
2226#[cfg(test)]
2227mod tests {
2228 use super::{Fourcc, read_nv12_dmabuf, read_p010_dmabuf, read_packed_dmabuf};
2229
2230 fn read_packed_rgba_dmabuf(
2232 plane_data: &[u8],
2233 stride: usize,
2234 width: usize,
2235 height: usize,
2236 y_inverted: bool,
2237 format: Fourcc,
2238 ) -> Option<Vec<u8>> {
2239 let pd = read_packed_dmabuf(plane_data, stride, width, height, y_inverted, format)?;
2240 Some(pd.to_rgba(width as u32, height as u32))
2241 }
2242
2243 #[test]
2244 fn xrgb_dmabuf_forces_opaque_alpha() {
2245 let pixels = [
2246 0x10, 0x20, 0x30, 0x00, 0x40, 0x50, 0x60, 0x7f,
2248 ];
2249
2250 let rgba = read_packed_rgba_dmabuf(&pixels, 8, 2, 1, false, Fourcc::Xrgb8888).unwrap();
2251
2252 assert_eq!(rgba, vec![0x30, 0x20, 0x10, 0xff, 0x60, 0x50, 0x40, 0xff]);
2253 }
2254
2255 #[test]
2256 fn nv12_black_decodes_to_opaque_black() {
2257 let y_plane = [16, 16, 16, 16];
2258 let uv_plane = [128, 128];
2259
2260 let rgba = read_nv12_dmabuf(&y_plane, 2, &uv_plane, 2, 2, 2, false).unwrap();
2261
2262 assert_eq!(rgba, [0, 0, 0, 255].repeat(4));
2263 }
2264
2265 #[test]
2266 fn p010_white_decodes_to_opaque_white() {
2267 let y_plane = [0x00, 0xeb, 0x00, 0xeb, 0x00, 0xeb, 0x00, 0xeb];
2268 let uv_plane = [0x00, 0x80, 0x00, 0x80];
2269
2270 let rgba = read_p010_dmabuf(&y_plane, 4, &uv_plane, 4, 2, 2, false).unwrap();
2271
2272 assert_eq!(rgba, [255, 255, 255, 255].repeat(4));
2273 }
2274}