From 944af9ab857668e228b6114df3212e92adf2b7e2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 5 Aug 2022 14:28:37 +0200 Subject: [PATCH] export_dmabuf: Initial support --- Cargo.lock | 2 +- src/backend/kms/mod.rs | 17 +- src/backend/render/mod.rs | 43 ++- src/state.rs | 11 +- src/wayland/handlers/export_dmabuf.rs | 333 +++++++++++++++++++ src/wayland/handlers/mod.rs | 1 + src/wayland/protocols/export_dmabuf.rs | 298 +++++++++++++++++ src/wayland/protocols/mod.rs | 1 + src/wayland/protocols/toplevel_info.rs | 14 +- src/wayland/protocols/toplevel_management.rs | 26 +- src/wayland/protocols/workspace.rs | 19 ++ 11 files changed, 733 insertions(+), 32 deletions(-) create mode 100644 src/wayland/handlers/export_dmabuf.rs create mode 100644 src/wayland/protocols/export_dmabuf.rs diff --git a/Cargo.lock b/Cargo.lock index 136dcb4e..d6367e69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,7 +386,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#42273002257350c4500c1bc2402bf01fd97632a7" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#9225987d8236352d82821af6b59b052e9f79a6eb" dependencies = [ "bitflags", "wayland-backend", diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 9812b7aa..5e9f0959 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -52,7 +52,7 @@ use std::{ collections::{HashMap, HashSet}, path::PathBuf, rc::Rc, - time::Duration, + time::{Duration, Instant}, }; mod drm_helpers; @@ -86,6 +86,7 @@ pub struct Surface { surface: Option>>, SessionFd>>, connector: connector::Handle, output: Output, + last_render: Option<(Dmabuf, Instant)>, last_submit: Option, refresh_rate: u32, vrr: bool, @@ -679,6 +680,7 @@ impl Device { vrr, refresh_rate, last_submit: None, + last_render: None, pending: false, render_timer_token: None, #[cfg(feature = "debug")] @@ -755,7 +757,7 @@ impl Surface { .with_context(|| "Failed to allocate buffer")?; renderer - .bind(buffer) + .bind(buffer.clone()) .with_context(|| "Failed to bind buffer")?; match render::render_output( @@ -769,6 +771,7 @@ impl Surface { Some(&mut self.fps), ) { Ok(_) => { + self.last_render = Some((buffer, Instant::now())); surface .queue_buffer() .with_context(|| "Failed to submit buffer for display")?; @@ -1008,4 +1011,14 @@ impl KmsState { } Ok(()) } + + pub fn capture_output(&self, output: &Output) -> Option<(DrmNode, Dmabuf, Instant)> { + self.devices + .values() + .find_map(|dev| dev.surfaces.values().find(|s| &s.output == output) + .and_then(|s| s.last_render.clone() + .map(|(buf, time)| (dev.render_node.clone(), buf, time)) + ) + ) + } } diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 121e822f..d192a09a 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -35,7 +35,7 @@ use smithay::{ wayland::{output::Output, shell::wlr_layer::Layer as WlrLayer}, }; -mod cursor; +pub mod cursor; use self::cursor::PointerElement; pub type GlMultiRenderer<'a> = @@ -152,6 +152,33 @@ pub fn render_output( hardware_cursor: bool, #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, ) -> Result>>, RenderError> +where + R: Renderer + ImportAll + AsGles2Renderer, + ::TextureId: Clone + 'static, + CustomElem: RenderElement, +{ + let workspace = state.shell.active_space(output).idx; + render_workspace( + gpu, + renderer, + age, + state, + workspace, + output, + hardware_cursor, + ) +} + +pub fn render_workspace( + gpu: Option<&DrmNode>, + renderer: &mut R, + age: u8, + state: &mut Common, + space_idx: u8, + output: &Output, + hardware_cursor: bool, + #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, +) -> Result>>, RenderError> where R: Renderer + ImportAll + AsGles2Renderer, ::TextureId: Clone + 'static, @@ -161,8 +188,11 @@ where if let Some(ref mut fps) = fps { fps.start(); } - let workspace = state.shell.active_space(output); + + let space_idx = space_idx as usize; + let workspace = &mut state.shell.spaces[space_idx]; let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned(); + let res = if let Some(window) = maybe_fullscreen_window { #[cfg(not(feature = "debug"))] { @@ -175,11 +205,11 @@ where } else { #[cfg(not(feature = "debug"))] { - render_desktop(gpu, renderer, age, state, output, hardware_cursor) + render_desktop(gpu, renderer, age, state, space_idx, output, hardware_cursor) } #[cfg(feature = "debug")] { - render_desktop(gpu, renderer, age, state, output, hardware_cursor, fps.as_deref_mut()) + render_desktop(gpu, renderer, age, state, space_idx, output, hardware_cursor, fps.as_deref_mut()) } }; @@ -196,6 +226,7 @@ fn render_desktop( renderer: &mut R, age: u8, state: &mut Common, + space_idx: usize, output: &Output, hardware_cursor: bool, #[cfg(feature = "debug")] fps: Option<&mut Fps>, @@ -209,7 +240,7 @@ where #[cfg(feature = "debug")] { - let workspace = state.shell.active_space(output); + let workspace = &state.shell.spaces[space_idx]; let output_geo = workspace .space .output_geometry(output) @@ -268,7 +299,7 @@ where } } - state.shell.active_space_mut(output).space.render_output( + state.shell.spaces[space_idx].space.render_output( renderer, &output, age as usize, diff --git a/src/state.rs b/src/state.rs index aa395721..c2699450 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,7 +6,9 @@ use crate::{ logger::LogState, shell::Shell, wayland::protocols::{ - drm::WlDrmState, output_configuration::OutputConfigurationState, + drm::WlDrmState, + export_dmabuf::ExportDmabufState, + output_configuration::OutputConfigurationState, workspace::WorkspaceClientState, }, utils::prelude::OutputExt, }; @@ -85,6 +87,7 @@ pub struct Common { pub compositor_state: CompositorState, pub data_device_state: DataDeviceState, pub dmabuf_state: DmabufState, + pub export_dmabuf_state: ExportDmabufState, pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, pub primary_selection_state: PrimarySelectionState, @@ -286,6 +289,11 @@ impl State { let compositor_state = CompositorState::new::(dh, None); let data_device_state = DataDeviceState::new::(dh, None); let dmabuf_state = DmabufState::new(); + let export_dmabuf_state = ExportDmabufState::new::( + dh, + //|client| client.get_data::().unwrap().privileged, + |_| true, + ); let output_state = OutputManagerState::new_with_xdg_output::(dh); let output_configuration_state = OutputConfigurationState::new(dh, |_| true); let primary_selection_state = PrimarySelectionState::new::(dh, None); @@ -336,6 +344,7 @@ impl State { compositor_state, data_device_state, dmabuf_state, + export_dmabuf_state, shm_state, seat_state, output_state, diff --git a/src/wayland/handlers/export_dmabuf.rs b/src/wayland/handlers/export_dmabuf.rs new file mode 100644 index 00000000..de1204be --- /dev/null +++ b/src/wayland/handlers/export_dmabuf.rs @@ -0,0 +1,333 @@ +// 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::{ + Bind, + Offscreen, + ExportDma, + ImportAll, + Renderer, + gles2::{Gles2Renderer, Gles2Renderbuffer, Gles2Error}, + utils::with_renderer_surface_state, + }, + }, + desktop::{space::RenderElement, Kind, Window, draw_window, draw_window_popups}, + wayland::{ + compositor::{get_children, with_states, SurfaceAttributes}, + dmabuf::get_dmabuf, + output::Output, + seat::CursorImageStatus, + }, + reexports::{ + wayland_server::{DisplayHandle, Resource, protocol::wl_output::WlOutput}, + }, + utils::{IsAlive, Size, Transform}, +}; + +use crate::{ + backend::render::{render_output, render_workspace, cursor::draw_cursor, AsGles2Renderer, CustomElem}, + state::{BackendData, ClientState, Common}, + utils::prelude::*, + wayland::protocols::{ + export_dmabuf::{ + delegate_export_dmabuf, ExportDmabufHandler, Capture, CaptureError, + }, + 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::Image(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 = active_output(seat, &self.common); + + 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); \ No newline at end of file diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index eed536b0..91c80de5 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -4,6 +4,7 @@ pub mod buffer; pub mod compositor; pub mod data_device; pub mod dmabuf; +pub mod export_dmabuf; pub mod layer_shell; pub mod output; pub mod output_configuration; diff --git a/src/wayland/protocols/export_dmabuf.rs b/src/wayland/protocols/export_dmabuf.rs new file mode 100644 index 00000000..2dee4203 --- /dev/null +++ b/src/wayland/protocols/export_dmabuf.rs @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + fs::File, + io::{Seek, SeekFrom}, + os::unix::io::{FromRawFd, IntoRawFd}, + time::Instant, +}; +use smithay::{ + backend::{ + allocator::{ + Buffer, + dmabuf::Dmabuf, + }, + drm::DrmNode, + }, + desktop::Window, + reexports::{ + wayland_server::{ + self, + Client, + Dispatch, + GlobalDispatch, + DisplayHandle, + backend::GlobalId, + protocol::wl_output::WlOutput, + }, + }, +}; + +use cosmic_protocols::{ + export_dmabuf::v1::server::{ + zcosmic_export_dmabuf_manager_v1::{self, ZcosmicExportDmabufManagerV1}, + zcosmic_export_dmabuf_frame_v1::{self, CancelReason, Flags, ZcosmicExportDmabufFrameV1}, + }, +}; + +use crate::wayland::protocols::{ + toplevel_info::{ToplevelInfoHandler, window_from_handle}, + 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() { + let mut file = unsafe { File::from_raw_fd(handle) }; + 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; + } + let handle = file.into_raw_fd(); + 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; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index f598db04..2129bd98 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod drm; +pub mod export_dmabuf; pub mod output_configuration; pub mod toplevel_info; pub mod toplevel_management; diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 4a36c710..35f5a221 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -473,14 +473,16 @@ fn send_toplevel_to_client( } } -pub(super) fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Window { +pub fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Option { handle .data::() - .unwrap() - .lock() - .unwrap() - .window - .clone() + .map(|state| + state + .lock() + .unwrap() + .window + .clone() + ) } macro_rules! delegate_toplevel_info { diff --git a/src/wayland/protocols/toplevel_management.rs b/src/wayland/protocols/toplevel_management.rs index f36c7bb1..ae503c2b 100644 --- a/src/wayland/protocols/toplevel_management.rs +++ b/src/wayland/protocols/toplevel_management.rs @@ -23,7 +23,7 @@ use cosmic_protocols::toplevel_management::v1::server::{ }; pub use cosmic_protocols::toplevel_management::v1::server::zcosmic_toplevel_manager_v1::ZcosmicToplelevelManagementCapabilitiesV1 as ManagementCapabilities; -use super::toplevel_info::{ToplevelInfoHandler, ToplevelHandleState, ToplevelState, window_from_handle}; +use super::toplevel_info::{ToplevelInfoHandler, ToplevelState, window_from_handle}; pub struct ToplevelManagementState { @@ -139,45 +139,39 @@ where ) { match request { zcosmic_toplevel_manager_v1::Request::Activate { toplevel, seat } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.activate(dh, &window, Seat::from_resource(&seat)); }, zcosmic_toplevel_manager_v1::Request::Close { toplevel } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.close(dh, &window); }, zcosmic_toplevel_manager_v1::Request::SetFullscreen { toplevel, output } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.fullscreen(dh, &window, output.as_ref().and_then(Output::from_resource)) }, zcosmic_toplevel_manager_v1::Request::UnsetFullscreen { toplevel } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.unfullscreen(dh, &window); }, zcosmic_toplevel_manager_v1::Request::SetMaximized { toplevel } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.maximize(dh, &window); }, zcosmic_toplevel_manager_v1::Request::UnsetMaximized { toplevel } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.unmaximize(dh, &window); }, zcosmic_toplevel_manager_v1::Request::SetMinimized { toplevel } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.minimize(dh, &window); }, zcosmic_toplevel_manager_v1::Request::UnsetMinimized { toplevel } => { - let window = window_from_handle(toplevel); + let window = window_from_handle(toplevel).unwrap(); state.unminimize(dh, &window); }, zcosmic_toplevel_manager_v1::Request::SetRectangle { toplevel, surface, x, y, width, height } => { - let window = toplevel - .data::() - .unwrap() - .lock() - .unwrap() - .window - .clone(); + let window = window_from_handle(toplevel).unwrap(); if let Some(toplevel_state) = window.user_data().get::() { let mut toplevel_state = toplevel_state.lock().unwrap(); if let Some(client) = surface.client_id() { diff --git a/src/wayland/protocols/workspace.rs b/src/wayland/protocols/workspace.rs index 3efe7f1a..d5a3abc7 100644 --- a/src/wayland/protocols/workspace.rs +++ b/src/wayland/protocols/workspace.rs @@ -477,6 +477,25 @@ where .map(|w| w.states.iter()) }) } + + pub fn group_handle( + &self, + group: &ZcosmicWorkspaceGroupHandleV1, + ) -> Option { + self.groups + .iter() + .find(|g| g.instances.contains(group)) + .map(|g| WorkspaceGroupHandle { id: g.id }) + } + pub fn workspace_handle( + &self, + workspace: &ZcosmicWorkspaceHandleV1, + ) -> Option { + self.groups + .iter() + .find_map(|g| g.workspaces.iter().find(|w| w.instances.contains(workspace))) + .map(|w| WorkspaceHandle { id: w.id }) + } pub fn raw_group_handle( &self,