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