diff --git a/src/wayland/handlers/export_dmabuf.rs b/src/wayland/handlers/export_dmabuf.rs deleted file mode 100644 index 81b3b77e..00000000 --- a/src/wayland/handlers/export_dmabuf.rs +++ /dev/null @@ -1,424 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use anyhow::{anyhow, Context, Result}; - -use std::{cell::RefCell, time::Instant}; - -use smithay::{ - backend::{ - drm::{DrmNode, NodeType}, - egl::EGLDevice, - renderer::{ - gles2::{Gles2Error, Gles2Renderbuffer, Gles2Renderer}, - utils::with_renderer_surface_state, - Bind, ExportDma, ImportAll, Offscreen, Renderer, - }, - }, - desktop::{draw_window, draw_window_popups, space::RenderElement, Kind, Window}, - input::pointer::CursorImageStatus, - output::Output, - reexports::wayland_server::{protocol::wl_output::WlOutput, DisplayHandle, Resource}, - utils::{IsAlive, Size, Transform}, - wayland::{ - compositor::{get_children, with_states, SurfaceAttributes}, - dmabuf::get_dmabuf, - }, -}; - -use crate::{ - backend::render::{ - cursor::draw_cursor, render_output, render_workspace, AsGles2Renderer, CustomElem, - }, - state::{BackendData, ClientState, Common}, - utils::prelude::*, - wayland::protocols::{ - export_dmabuf::{delegate_export_dmabuf, Capture, CaptureError, ExportDmabufHandler}, - workspace::WorkspaceHandle, - }, -}; - -impl ExportDmabufHandler for State { - fn capture_output( - &mut self, - _dh: &DisplayHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result { - let output = Output::from_resource(&output) - .ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?; - - let renderer = match self.backend { - BackendData::Kms(ref mut kms) => { - // the kms backend just keeps its dmabufs easily accessible for capture. - return kms - .capture_output(&output) - .map(|(device, dmabuf, presentation_time)| Capture { - device, - dmabuf, - presentation_time, - }) - .ok_or(CaptureError::Temporary( - anyhow!("Surface not initialized yet").into(), - )); - } - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - _ => unreachable!(), - }; - let device = device_from_renderer(renderer) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - render_output( - None, - renderer, - 0, - &mut self.common, - &output, - !overlay_cursor, - #[cfg(feature = "debug")] - None, - ) - .context("Failed to render desktop for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - let dmabuf = renderer - .export_framebuffer(size) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device, - dmabuf, - presentation_time: Instant::now(), - }) - } - - fn capture_workspace( - &mut self, - _dh: &DisplayHandle, - workspace: WorkspaceHandle, - wl_output: WlOutput, - overlay_cursor: bool, - ) -> Result { - let output = Output::from_resource(&wl_output) - .ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?; - let workspace = self - .common - .shell - .spaces - .iter() - .find(|w| w.handle == workspace) - .ok_or(CaptureError::Permanent(anyhow!("Workspace is gone").into()))? - .idx; - if self.common.shell.active_space(&output).idx == workspace { - self.capture_output(_dh, wl_output, overlay_cursor) - } else { - match self.backend { - BackendData::Winit(ref mut winit) => { - let device = device_from_renderer(winit.backend.renderer()) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - capture_workspace( - device, - winit.backend.renderer(), - &output, - workspace, - &mut self.common, - ) - } - BackendData::X11(ref mut x11) => { - let device = device_from_renderer(&x11.renderer) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - capture_workspace( - device, - &mut x11.renderer, - &output, - workspace, - &mut self.common, - ) - } - BackendData::Kms(ref mut kms) => { - let node = kms - .target_node_for_output(&output) - .unwrap_or(kms.primary) - .node_with_type(NodeType::Render) - .with_context(|| "Unable to find node") - .map_err(|x| CaptureError::Permanent(x.into()))? - .map_err(|x| CaptureError::Permanent(x.into()))?; - let mut renderer = kms - .api - .renderer::(&node, &node) - .with_context(|| format!("Failed to optain renderer for {:?}", node)) - .map_err(|x| CaptureError::Permanent(x.into()))?; - capture_workspace(node, &mut renderer, &output, workspace, &mut self.common) - } - BackendData::Unset => unreachable!(), - } - } - } - - fn capture_toplevel( - &mut self, - dh: &DisplayHandle, - window: Window, - overlay_cursor: bool, - ) -> Result { - let Kind::Xdg(xdg) = window.toplevel(); - let surface = xdg.wl_surface(); - let window_transform = with_states(surface, |states| { - states - .cached_state - .current::() - .buffer_transform - .into() - }); - - let workspace = self.common.shell.space_for_window(surface); - let pointers = if overlay_cursor && workspace.is_some() { - self.common - .seats - .iter() - .filter_map(|seat| { - let cursor_status = seat - .user_data() - .get::>() - .map(|cell| { - let mut cursor_status = cell.borrow_mut(); - if let CursorImageStatus::Surface(ref surface) = *cursor_status { - if !surface.alive() { - *cursor_status = CursorImageStatus::Default; - } - } - cursor_status.clone() - }) - .unwrap_or(CursorImageStatus::Default); - - if cursor_status != CursorImageStatus::Hidden { - let workspace = workspace.as_deref()?; - let loc = seat.get_pointer().map(|ptr| ptr.current_location())?; - let output = seat.active_output(); - - if self.common.shell.active_space(&output).idx == workspace.idx { - let relative = self - .common - .shell - .space_relative_output_geometry(loc, &output); - // unwrap is safe, because we got this workspace from `space_for_window`. It has to contain the window. - let bbox = workspace.space.window_bbox(&window).unwrap(); - bbox.contains(relative.to_i32_round()) - .then_some((seat, (relative - bbox.loc.to_f64()).to_i32_round())) - } else { - None - } - } else { - None - } - }) - .collect::>() - } else { - Vec::with_capacity(0) - }; - - let device = match self.backend { - BackendData::Winit(ref mut winit) => device_from_renderer(winit.backend.renderer()), - BackendData::X11(ref x11) => device_from_renderer(&x11.renderer), - BackendData::Kms(ref kms) => Ok(dh - .get_client(window.toplevel().wl_surface().id()) - .ok() - .with_context(|| "Unable to find matching wayland client") - .map_err(|x| CaptureError::Permanent(x.into()))? - .get_data::() - .unwrap() - .drm_node - .clone() - .unwrap_or_else(|| kms.primary.clone())), - _ => unreachable!(), - } - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - - // first lets check, if we can just send a dmabuf from the client directly - if pointers.is_empty() - && window_transform == Transform::Normal - && get_children(surface).is_empty() - && self.common.shell.popups.find_popup(surface).is_none() - { - let dmabuf = with_renderer_surface_state(surface, |state| { - state.wl_buffer().and_then(|buf| get_dmabuf(buf).ok()) - }); - if let Some(dmabuf) = dmabuf { - return Ok(Capture { - device, - dmabuf, - presentation_time: std::time::Instant::now(), - }); - } - } - - // we need to composite - let mut _tmp_multirenderer = None; - let renderer = match self.backend { - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - BackendData::Kms(ref mut kms) => { - _tmp_multirenderer = Some( - kms.api - .renderer::(&device, &device) - .with_context(|| format!("Failed to optain renderer for {:?}", device)) - .map_err(|x| CaptureError::Permanent(x.into()))?, - ); - _tmp_multirenderer.as_mut().unwrap().as_gles2() - } - BackendData::Unset => unreachable!(), - }; - - let bbox = window.bbox_with_popups(); - let size = bbox.size + Size::from((-bbox.loc.x, -bbox.loc.y)); - let buffer = Offscreen::::create_buffer( - renderer, - size.to_buffer(1, window_transform), - ) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .render(size.to_physical(1), Transform::Normal, |renderer, frame| { - let log = slog_scope::logger(); - let damage = &[window.physical_bbox_with_popups((0.0, 0.0), 1.0)]; - draw_window(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?; - draw_window_popups(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?; - for (seat, loc) in pointers.into_iter() { - if let Some(cursor_elem) = draw_cursor::<_, CustomElem>( - renderer, - seat, - loc, - &self.common.start_time, - true, - ) { - let damage = RenderElement::::accumulated_damage( - &cursor_elem, - 1.0, - None, - ); - cursor_elem.draw( - renderer, - frame, - 1.0, - loc.to_physical(1.0), - &damage, - &log, - )?; - } - } - Result::<(), Gles2Error>::Ok(()) - }) - .context("Failed to render window for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))? - .context("Failed to render window for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - let dmabuf = renderer - .export_framebuffer(size.to_buffer(1, window_transform)) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device, - dmabuf, - presentation_time: Instant::now(), - }) - } - - fn start_time(&mut self) -> Instant { - self.common.start_time - } -} - -fn capture_workspace( - gpu: DrmNode, - renderer: &mut R, - output: &Output, - idx: u8, - state: &mut Common, -) -> Result -where - E: std::error::Error + Send + Sync + 'static, - T: Clone + 'static, - R: Renderer - + ImportAll - + AsGles2Renderer - + Offscreen - + Bind - + ExportDma, - CustomElem: RenderElement, -{ - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - render_workspace( - Some(&gpu), - renderer, - 0, - state, - idx, - &output, - true, - #[cfg(feature = "debug")] - None, - ) - .map_err(|err| anyhow!("Failed to render desktop for offscreen capture: {:?}", err)) // meh.. - .map_err(|err| CaptureError::Temporary(err.into()))?; - let dmabuf = renderer - .export_framebuffer(size) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device: gpu, - dmabuf, - presentation_time: Instant::now(), - }) -} - -fn device_from_renderer(renderer: &Gles2Renderer) -> Result { - EGLDevice::device_for_display(renderer.egl_context().display())? - .try_get_render_node()? - .ok_or(anyhow!( - "No node associated with context (software context?)" - )) -} - -delegate_export_dmabuf!(State); diff --git a/src/wayland/protocols/export_dmabuf.rs b/src/wayland/protocols/export_dmabuf.rs deleted file mode 100644 index 0288eebf..00000000 --- a/src/wayland/protocols/export_dmabuf.rs +++ /dev/null @@ -1,308 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use smithay::{ - backend::{ - allocator::{dmabuf::Dmabuf, Buffer}, - drm::DrmNode, - }, - desktop::Window, - reexports::wayland_server::{ - self, backend::GlobalId, protocol::wl_output::WlOutput, Client, Dispatch, DisplayHandle, - GlobalDispatch, - }, -}; -use std::{ - fs::File, - io::{Seek, SeekFrom}, - os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}, - time::Instant, -}; - -use cosmic_protocols::export_dmabuf::v1::server::{ - zcosmic_export_dmabuf_frame_v1::{self, CancelReason, Flags, ZcosmicExportDmabufFrameV1}, - zcosmic_export_dmabuf_manager_v1::{self, ZcosmicExportDmabufManagerV1}, -}; - -use crate::wayland::protocols::{ - toplevel_info::{window_from_handle, ToplevelInfoHandler}, - workspace::{WorkspaceHandle, WorkspaceHandler}, -}; - -/// Export Dmabuf global state -#[derive(Debug)] -pub struct ExportDmabufState { - global: GlobalId, -} - -pub struct ExportDmabufGlobalData { - filter: Box Fn(&'a Client) -> bool + Send + Sync>, -} - -impl ExportDmabufState { - /// Create a new dmabuf global - pub fn new(display: &DisplayHandle, client_filter: F) -> ExportDmabufState - where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler - + 'static, - F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, - { - ExportDmabufState { - global: display.create_global::( - 1, - ExportDmabufGlobalData { - filter: Box::new(client_filter), - }, - ), - } - } - - /// Returns the export dmabuf global. - pub fn global(&self) -> GlobalId { - self.global.clone() - } -} - -pub enum CaptureError { - Temporary(Box), - Permanent(Box), - Resizing, -} - -pub struct Capture { - pub device: DrmNode, - pub dmabuf: Dmabuf, - pub presentation_time: Instant, -} - -pub trait ExportDmabufHandler { - fn capture_output( - &mut self, - dh: &DisplayHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result; - fn capture_workspace( - &mut self, - dh: &DisplayHandle, - workspace: WorkspaceHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result; - fn capture_toplevel( - &mut self, - dh: &DisplayHandle, - toplevel: Window, - overlay_cursor: bool, - ) -> Result; - fn start_time(&mut self) -> Instant; -} - -impl GlobalDispatch - for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler, -{ - fn bind( - _state: &mut D, - _handle: &DisplayHandle, - _client: &Client, - resource: wayland_server::New, - _global_data: &ExportDmabufGlobalData, - data_init: &mut wayland_server::DataInit<'_, D>, - ) { - data_init.init(resource, ()); - } - - fn can_view(client: Client, global_data: &ExportDmabufGlobalData) -> bool { - (global_data.filter)(&client) - } -} - -impl Dispatch for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler - + WorkspaceHandler - + ToplevelInfoHandler, -{ - fn request( - state: &mut D, - _client: &wayland_server::Client, - _resource: &ZcosmicExportDmabufManagerV1, - request: ::Request, - _data: &(), - dhandle: &DisplayHandle, - data_init: &mut wayland_server::DataInit<'_, D>, - ) { - let start_time = state.start_time(); - match request { - zcosmic_export_dmabuf_manager_v1::Request::CaptureOutput { - frame, - overlay_cursor, - output, - } => { - let frame = data_init.init(frame, ()); - match state.capture_output(dhandle, output, overlay_cursor != 0) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - zcosmic_export_dmabuf_manager_v1::Request::CaptureWorkspace { - frame, - overlay_cursor, - workspace, - output, - } => { - let frame = data_init.init(frame, ()); - match state.workspace_state().workspace_handle(&workspace) { - Some(workspace) => { - match state.capture_workspace( - dhandle, - workspace, - output, - overlay_cursor != 0, - ) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - None => frame.cancel(CancelReason::Permanent), - } - } - zcosmic_export_dmabuf_manager_v1::Request::CaptureToplevel { - frame, - overlay_cursor, - toplevel, - } => { - let frame = data_init.init(frame, ()); - match window_from_handle(toplevel) { - Some(window) => { - match state.capture_toplevel(dhandle, window, overlay_cursor != 0) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - None => frame.cancel(CancelReason::Permanent), - } - } - zcosmic_export_dmabuf_manager_v1::Request::Destroy => {} - _ => {} - } - } -} - -impl From for CancelReason { - fn from(err: CaptureError) -> Self { - match err { - CaptureError::Temporary(err) => { - slog_scope::debug!("Temporary Capture Error: {}", err); - CancelReason::Temporary - } - CaptureError::Permanent(err) => { - slog_scope::warn!("Permanent Capture Error: {}", err); - CancelReason::Permanent - } - CaptureError::Resizing => CancelReason::Resizing, - } - } -} - -fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_time: Instant) { - let Capture { - device, - dmabuf, - presentation_time, - } = capture; - let format = dmabuf.format(); - let modifier: u64 = format.modifier.into(); - - frame.device(Vec::from(device.dev_id().to_ne_bytes())); - frame.frame( - dmabuf.width(), - dmabuf.height(), - 0, - 0, - if dmabuf.y_inverted() { 1 } else { 0 }, - Flags::Transient, - format.code as u32, - (modifier >> 32) as u32, - (modifier & 0xFFFFFFFF) as u32, - dmabuf.num_planes() as u32, - ); - - for (i, (handle, (offset, stride))) in dmabuf - .handles() - .zip(dmabuf.offsets().zip(dmabuf.strides())) - .enumerate() - { - // SAFETY: BorrowedFd is used for seeking - let mut file = unsafe { File::from_raw_fd(handle.as_raw_fd()) }; - let size = match file.seek(SeekFrom::End(0)) { - Ok(size) => size, - Err(err) => { - slog_scope::debug!("Temporary Capture Error: {}", err); - frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary); - return; - } - }; - if let Err(err) = file.rewind() { - slog_scope::debug!("Temporary Capture Error: {}", err); - frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary); - return; - } - // SAFETY: Converted back to raw_fd, no chance in ownership - let handle = file.into_raw_fd(); - // FDs are dup'ed by wayland-rs before sending them - frame.object(i as u32, handle, size as u32, offset, stride, i as u32); - } - - let duration = presentation_time.duration_since(start_time); - let (tv_sec, tv_nsec) = (duration.as_secs(), duration.subsec_nanos()); - frame.ready((tv_sec >> 32) as u32, (tv_sec & 0xFFFFFFFF) as u32, tv_nsec); -} - -impl Dispatch for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler, -{ - fn request( - _state: &mut D, - _client: &wayland_server::Client, - _resource: &ZcosmicExportDmabufFrameV1, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - _data_init: &mut wayland_server::DataInit<'_, D>, - ) { - match request { - zcosmic_export_dmabuf_frame_v1::Request::Destroy => {} - _ => {} - } - } -} - -macro_rules! delegate_export_dmabuf { - ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { - smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: $crate::wayland::protocols::export_dmabuf::ExportDmabufGlobalData - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: () - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_frame_v1::ZcosmicExportDmabufFrameV1: () - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - }; -} -pub(crate) use delegate_export_dmabuf;