use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier}; use libc::{O_CREAT, O_RDWR, S_IRUSR, S_IWUSR}; use std::{ any::Any, ffi::CString, os::fd::{BorrowedFd, RawFd}, sync::{ atomic::{AtomicUsize, Ordering}, mpsc::{self, SyncSender}, }, thread::JoinHandle, }; use wayland_client::{ Connection, Dispatch, Proxy, QueueHandle, WEnum, protocol::{wl_buffer::WlBuffer, wl_shm::Format, wl_shm_pool::WlShmPool}, }; use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1; use smithay_client_toolkit::reexports::protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{ZwlrScreencopyFrameV1, self}; use crate::{ WlxCapture, frame::{DmaExporter, FrameFormat, FramePlane, MemFdFrame, WlxFrame}, wayland::WlxClient, }; enum BufData { Shm { wl_buffer: WlBuffer, wl_pool: WlShmPool, fd: RawFd, }, Dma { wl_buffer: WlBuffer, }, } impl Drop for BufData { fn drop(&mut self) { match self { Self::Shm { wl_buffer, wl_pool, fd, .. } => { wl_buffer.destroy(); wl_pool.destroy(); unsafe { libc::close(*fd); } } Self::Dma { wl_buffer } => { wl_buffer.destroy(); } } } } enum ScreenCopyEvent { Buffer { shm_format: Format, width: u32, height: u32, stride: u32, }, DmaBuf { format: DrmFourcc, width: u32, height: u32, }, BuffersDone, Ready, Failed, } struct CaptureData { sender: mpsc::SyncSender, receiver: mpsc::Receiver, user_data: Option>, receive_callback: fn(&U, WlxFrame) -> Option, } pub struct WlrScreencopyCapture { output_id: u32, wl: Option>, handle: Option, Box)>>, data: Option>, } impl WlrScreencopyCapture where U: Any + Send + DmaExporter, R: Any + Send, { pub fn new(wl: WlxClient, output_id: u32) -> Self { Self { output_id, wl: Some(Box::new(wl)), handle: None, data: None, } } } impl WlxCapture for WlrScreencopyCapture where U: Any + Send + DmaExporter, R: Any + Send, { fn init( &mut self, _: &[DrmFormat], user_data: U, receive_callback: fn(&U, WlxFrame) -> Option, ) { debug_assert!(self.wl.is_some()); let (sender, receiver) = mpsc::sync_channel(2); self.data = Some(CaptureData { sender, receiver, user_data: Some(Box::new(user_data)), receive_callback, }); } fn is_ready(&self) -> bool { self.data.is_some() } fn supports_dmbuf(&self) -> bool { true // screencopy v3+ } fn receive(&mut self) -> Option { if let Some(data) = self.data.as_ref() { data.receiver.try_iter().last() } else { None } } fn pause(&mut self) {} fn resume(&mut self) { if self.data.is_none() { return; } self.receive(); // clear old frames self.request_new_frame(); } fn request_new_frame(&mut self) { let mut wait_for_damage = false; if let Some(handle) = self.handle.take() { if handle.is_finished() { wait_for_damage = true; let (wl, u) = handle.join().unwrap(); // safe to unwrap because is_finished self.wl = Some(wl); self.data.as_mut().unwrap().user_data = Some(u); } else { self.handle = Some(handle); return; } } let Some(wl) = self.wl.take() else { return; }; let data = self .data .as_mut() .expect("must call init once before request_new_frame"); self.handle = Some(std::thread::spawn({ let sender = data.sender.clone(); let user_data = data.user_data.take().unwrap(); let receive_callback = data.receive_callback; let output_id = self.output_id; move || { request_screencopy_frame( wl, output_id, sender, user_data, receive_callback, wait_for_damage, ) } })); } } /// Request a new DMA-Buf frame using the wlr-screencopy protocol. fn request_screencopy_frame( client: Box, output_id: u32, sender: SyncSender, mut user_data: Box, receive_callback: fn(&U, WlxFrame) -> Option, wait_for_damage: bool, ) -> (Box, Box) where U: Any + Send + DmaExporter, R: Any + Send, { let Some(screencopy_manager) = client.maybe_wlr_screencopy_mgr.as_ref() else { return (client, user_data); }; let Some(output) = client.outputs.get(output_id) else { return (client, user_data); }; let transform = output.transform; let (tx, rx) = mpsc::sync_channel::(16); let proxy = screencopy_manager.capture_output(1, &output.wl_output, &client.queue_handle, tx.clone()); let name = output.name.clone(); let mut client = client; client.dispatch(); let mut frame_buffer = None; let mut maybe_buffer = None; let mut maybe_dmabuf = None; 'receiver: loop { for event in rx.try_iter() { match event { ScreenCopyEvent::Buffer { .. } => { log::trace!("{name}: ScreenCopy Buffer event received"); maybe_buffer = Some(event); } ScreenCopyEvent::DmaBuf { .. } => { log::trace!("{name}: ScreenCopy LinuxDmabuf event received"); maybe_dmabuf = Some(event); } ScreenCopyEvent::BuffersDone => { log::trace!("{name}: ScreenCopy BuffersDone event received"); if let Some(zwp_linux_dmabuf) = client.maybe_zwp_linux_dmabuf.as_ref() && let Some(ScreenCopyEvent::DmaBuf { format, width, height, }) = maybe_dmabuf && let Some((plane, modifier)) = user_data.next_frame(width, height, format) { let mod_hi = (u64::from(modifier) >> 32) as _; let mod_lo = (u64::from(modifier) & 0xFFFFFFFF) as _; let fd = unsafe { BorrowedFd::borrow_raw(plane.fd.unwrap()) }; let params = zwp_linux_dmabuf.create_params(&client.queue_handle, ()); params.add(fd, 0, plane.offset, plane.stride as _, mod_hi, mod_lo); let wl_buffer = params.create_immed( width as _, height as _, format as _, zwp_linux_buffer_params_v1::Flags::empty(), &client.queue_handle, (), ); log::trace!("{name}: ScreenCopy with Dmabuf"); // copy_with_damage seems to not work here proxy.copy(&wl_buffer); frame_buffer = Some((WlxFrame::Implicit, BufData::Dma { wl_buffer })); } else if let Some(ScreenCopyEvent::Buffer { shm_format, width, height, stride, }) = maybe_buffer && let Some(fourcc) = fourcc_from_wlshm(shm_format) { let fd_num = FD_COUNTER.fetch_add(1, Ordering::Relaxed); let shm_name = CString::new(format!("wlx-{}", fd_num)).unwrap(); // safe let size = stride * height; let fd = unsafe { let fd = libc::shm_open( shm_name.as_ptr(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, ); libc::shm_unlink(shm_name.as_ptr()); libc::ftruncate(fd, size as _); fd }; let borrowed_fd = unsafe { BorrowedFd::borrow_raw(fd) }; let wl_pool = client.wl_shm.create_pool( borrowed_fd, size as _, &client.queue_handle, (), ); let wl_buffer = wl_pool.create_buffer( 0, width as _, height as _, stride as _, shm_format, &client.queue_handle, (), ); log::trace!("{name}: ScreenCopy with SHM"); if wait_for_damage { proxy.copy_with_damage(&wl_buffer); } else { proxy.copy(&wl_buffer); } let frame = MemFdFrame { format: FrameFormat { width, height, drm_format: DrmFormat { code: fourcc, modifier: DrmModifier::Invalid, }, transform, }, plane: FramePlane { fd: Some(fd), offset: 0, stride: stride as _, }, mouse: None, }; frame_buffer = Some(( WlxFrame::MemFd(frame), BufData::Shm { wl_buffer, wl_pool, fd, }, )); } else { log::error!("{name}: No usable ScreenCopy buffers received."); proxy.destroy(); break 'receiver; } client.dispatch(); } ScreenCopyEvent::Ready => { log::trace!("{}: Frame ready?", name.as_ref()); if let Some((frame, buffer)) = frame_buffer { if let Some(r) = receive_callback(&user_data, frame) { let _ = sender.send(r); log::trace!("{}: Frame ready!", name.as_ref()); } drop(buffer); } break 'receiver; } ScreenCopyEvent::Failed => { log::trace!("{}: Frame failed", name.as_ref()); break 'receiver; } }; } } (client, user_data) } static FD_COUNTER: AtomicUsize = AtomicUsize::new(0); impl Dispatch> for WlxClient { fn event( _state: &mut Self, proxy: &ZwlrScreencopyFrameV1, event: ::Event, data: &SyncSender, _conn: &Connection, _qhandle: &QueueHandle, ) { match event { zwlr_screencopy_frame_v1::Event::Failed => { let _ = data.send(ScreenCopyEvent::Failed); proxy.destroy(); } zwlr_screencopy_frame_v1::Event::Buffer { format, width, height, stride, } => { let WEnum::Value(shm_format) = format else { log::warn!("Unknown screencopy format"); let _ = data.send(ScreenCopyEvent::Failed); proxy.destroy(); return; }; let _ = data.send(ScreenCopyEvent::Buffer { width, height, stride, shm_format, }); } zwlr_screencopy_frame_v1::Event::Ready { .. } => { let _ = data.send(ScreenCopyEvent::Ready); proxy.destroy(); } zwlr_screencopy_frame_v1::Event::LinuxDmabuf { format, width, height, } => { let Ok(format) = DrmFourcc::try_from(format) else { log::warn!("{format} is not a known FourCC"); return; }; let _ = data.send(ScreenCopyEvent::DmaBuf { width, height, format, }); } zwlr_screencopy_frame_v1::Event::BufferDone => { let _ = data.send(ScreenCopyEvent::BuffersDone); } _ => {} } } } fn fourcc_from_wlshm(shm_format: Format) -> Option { match shm_format { Format::Argb8888 => Some(DrmFourcc::Argb8888), Format::Xrgb8888 => Some(DrmFourcc::Xrgb8888), Format::Abgr8888 => Some(DrmFourcc::Abgr8888), Format::Xbgr8888 => Some(DrmFourcc::Xbgr8888), _ => None, } } // Plumbing below impl Dispatch for WlxClient { fn event( _state: &mut Self, _proxy: &WlShmPool, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for WlxClient { fn event( _state: &mut Self, _proxy: &WlBuffer, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { } }