export_dmabuf: Initial support

This commit is contained in:
Victoria Brekenfeld 2022-08-05 14:28:37 +02:00
parent 52507f25bc
commit 944af9ab85
11 changed files with 733 additions and 32 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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<GbmBufferedSurface<Rc<RefCell<GbmDevice<SessionFd>>>, SessionFd>>,
connector: connector::Handle,
output: Output,
last_render: Option<(Dmabuf, Instant)>,
last_submit: Option<DrmEventTime>,
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))
)
)
}
}

View file

@ -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<R>(
hardware_cursor: bool,
#[cfg(feature = "debug")] mut fps: Option<&mut Fps>,
) -> Result<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
{
let workspace = state.shell.active_space(output).idx;
render_workspace(
gpu,
renderer,
age,
state,
workspace,
output,
hardware_cursor,
)
}
pub fn render_workspace<R>(
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<Option<Vec<Rectangle<i32, Physical>>>, RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
<R as Renderer>::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<R>(
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,

View file

@ -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<State>,
pub primary_selection_state: PrimarySelectionState,
@ -286,6 +289,11 @@ impl State {
let compositor_state = CompositorState::new::<Self, _>(dh, None);
let data_device_state = DataDeviceState::new::<Self, _>(dh, None);
let dmabuf_state = DmabufState::new();
let export_dmabuf_state = ExportDmabufState::new::<Self, _>(
dh,
//|client| client.get_data::<ClientState>().unwrap().privileged,
|_| true,
);
let output_state = OutputManagerState::new_with_xdg_output::<Self>(dh);
let output_configuration_state = OutputConfigurationState::new(dh, |_| true);
let primary_selection_state = PrimarySelectionState::new::<Self, _>(dh, None);
@ -336,6 +344,7 @@ impl State {
compositor_state,
data_device_state,
dmabuf_state,
export_dmabuf_state,
shm_state,
seat_state,
output_state,

View file

@ -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<Capture, CaptureError> {
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::<Gles2Renderbuffer>::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<Capture, CaptureError> {
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::<Gles2Renderbuffer>(&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<Capture, CaptureError> {
let Kind::Xdg(xdg) = window.toplevel();
let surface = xdg.wl_surface();
let window_transform = with_states(surface, |states| states
.cached_state
.current::<SurfaceAttributes>()
.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::<RefCell<CursorImageStatus>>()
.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::<Vec<_>>()
} 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::<ClientState>()
.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::<Gles2Renderbuffer>(&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::<Gles2Renderbuffer>::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::<Gles2Renderer>::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<E, T, R>(
gpu: DrmNode,
renderer: &mut R,
output: &Output,
idx: u8,
state: &mut Common,
) -> Result<Capture, CaptureError>
where
E: std::error::Error + Send + Sync + 'static,
T: Clone + 'static,
R: Renderer<Error=E, TextureId=T>
+ ImportAll
+ AsGles2Renderer
+ Offscreen<Gles2Renderbuffer>
+ Bind<Gles2Renderbuffer>
+ ExportDma,
CustomElem: RenderElement<R>,
{
let size = output.geometry().size.to_f64().to_buffer(
output.current_scale().fractional_scale(),
output.current_transform().into()
).to_i32_round();
let buffer = Offscreen::<Gles2Renderbuffer>::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<DrmNode> {
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);

View file

@ -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;

View file

@ -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<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
}
impl ExportDmabufState {
/// Create a new dmabuf global
pub fn new<D, F>(display: &DisplayHandle, client_filter: F) -> ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler
+ 'static,
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
{
ExportDmabufState {
global: display.create_global::<D, ZcosmicExportDmabufManagerV1, _>(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<dyn std::error::Error>),
Permanent(Box<dyn std::error::Error>),
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<Capture, CaptureError>;
fn capture_workspace(&mut self, dh: &DisplayHandle, workspace: WorkspaceHandle, output: WlOutput, overlay_cursor: bool) -> Result<Capture, CaptureError>;
fn capture_toplevel(&mut self, dh: &DisplayHandle, toplevel: Window, overlay_cursor: bool) -> Result<Capture, CaptureError>;
fn start_time(&mut self) -> Instant;
}
impl<D> GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData, D> for ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler,
{
fn bind(
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: wayland_server::New<ZcosmicExportDmabufManagerV1>,
_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<D> Dispatch<ZcosmicExportDmabufManagerV1, (), D> for ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler
+ WorkspaceHandler
+ ToplevelInfoHandler
{
fn request(
state: &mut D,
_client: &wayland_server::Client,
_resource: &ZcosmicExportDmabufManagerV1,
request: <ZcosmicExportDmabufManagerV1 as wayland_server::Resource>::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<CaptureError> 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<D> Dispatch<ZcosmicExportDmabufFrameV1, (), D> for ExportDmabufState
where
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
+ ExportDmabufHandler,
{
fn request(
_state: &mut D,
_client: &wayland_server::Client,
_resource: &ZcosmicExportDmabufFrameV1,
request: <ZcosmicExportDmabufFrameV1 as wayland_server::Resource>::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;

View file

@ -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;

View file

@ -473,14 +473,16 @@ fn send_toplevel_to_client<D>(
}
}
pub(super) fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Window {
pub fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Option<Window> {
handle
.data::<ToplevelHandleState>()
.unwrap()
.lock()
.unwrap()
.window
.clone()
.map(|state|
state
.lock()
.unwrap()
.window
.clone()
)
}
macro_rules! delegate_toplevel_info {

View file

@ -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::<ToplevelHandleState>()
.unwrap()
.lock()
.unwrap()
.window
.clone();
let window = window_from_handle(toplevel).unwrap();
if let Some(toplevel_state) = window.user_data().get::<ToplevelState>() {
let mut toplevel_state = toplevel_state.lock().unwrap();
if let Some(client) = surface.client_id() {

View file

@ -477,6 +477,25 @@ where
.map(|w| w.states.iter())
})
}
pub fn group_handle(
&self,
group: &ZcosmicWorkspaceGroupHandleV1,
) -> Option<WorkspaceGroupHandle> {
self.groups
.iter()
.find(|g| g.instances.contains(group))
.map(|g| WorkspaceGroupHandle { id: g.id })
}
pub fn workspace_handle(
&self,
workspace: &ZcosmicWorkspaceHandleV1,
) -> Option<WorkspaceHandle> {
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,