// SPDX-License-Identifier: GPL-3.0-only

use calloop::LoopHandle;
use smithay::{
    backend::{
        allocator::{Buffer, Fourcc, format::get_transparent},
        renderer::{
            BufferType, Color32F, ExportMem, ImportAll, ImportMem, Offscreen, Renderer,
            buffer_dimensions, buffer_type,
            damage::{Error as DTError, OutputDamageTracker, RenderOutputResult},
            element::{
                RenderElement, UnderlyingStorage,
                utils::{Relocate, RelocateRenderElement},
            },
            gles::{GlesError, GlesRenderbuffer},
            sync::SyncPoint,
            utils::with_renderer_surface_state,
        },
    },
    desktop::space::SpaceElement,
    input::Seat,
    output::{Output, OutputNoMode},
    reexports::wayland_server::protocol::wl_buffer::WlBuffer,
    utils::{
        Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Size,
        Transform,
    },
    wayland::{
        dmabuf::get_dmabuf,
        image_copy_capture::{CaptureFailureReason, CursorSessionRef, Frame, SessionRef},
        seat::WaylandFocus,
        shm::{shm_format_to_fourcc, with_buffer_contents, with_buffer_contents_mut},
    },
};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tracing::warn;

use crate::{
    backend::render::{
        CursorMode, ElementFilter, RendererRef,
        cursor::{self, CursorRenderElement},
        element::{AsGlowRenderer, CosmicElement, DamageElement},
        render_workspace,
        wayland::SurfaceRenderElement,
    },
    shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement},
    state::{Common, KmsNodes, State},
    utils::prelude::{PointExt, PointGlobalExt, RectExt, RectLocalExt, SeatExt},
    wayland::{
        handlers::image_copy_capture::{
            SessionData, SessionUserData, constraints_for_output, constraints_for_toplevel,
        },
        protocols::workspace::WorkspaceHandle,
    },
};

use super::{
    super::data_device::get_dnd_icon, cursor_capture_constraints, user_data::SessionHolder,
};

pub fn render_element_buffers<R, E>(
    renderer: &mut R,
    elements: &[E],
) -> Vec<smithay::backend::renderer::utils::Buffer>
where
    R: AsGlowRenderer,
    E: RenderElement<R>,
{
    elements
        .iter()
        .filter_map(|elem| match elem.underlying_storage(renderer) {
            Some(UnderlyingStorage::Wayland(buffer)) => Some(buffer.clone()),
            Some(UnderlyingStorage::Memory(_)) | None => None,
        })
        .collect()
}

pub struct PendingImageCopyData {
    pub frame: Frame,
    pub damage: Vec<smithay::utils::Rectangle<i32, BufferCoords>>,
    pub sync: SyncPoint,
    // Hold reference so `wl_buffer` isn't released and sync point isn't signaled
    // until image copy completes.
    _buffers: Vec<smithay::backend::renderer::utils::Buffer>,
}

impl PendingImageCopyData {
    /// Send `success` to image copy frame, once sync point is reached
    pub fn send_success_when_ready<LoopData>(
        self,
        transform: Transform,
        loop_handle: &LoopHandle<'static, LoopData>,
        presented: impl Into<Duration>,
    ) {
        let presented = presented.into();
        if self.sync.is_reached() {
            self.frame.success(transform, self.damage, presented);
        } else if let Some(fence_fd) = self.sync.export() {
            let source = calloop::generic::Generic::new(
                fence_fd,
                calloop::Interest::READ,
                calloop::Mode::OneShot,
            );
            let mut data = Some(self);
            loop_handle
                .insert_source(source, move |_, _, _| {
                    let data = data.take().unwrap();
                    data.frame.success(transform, data.damage, presented);
                    Ok(calloop::PostAction::Remove)
                })
                .expect("Failed to wait on sync point");
        } else {
            // Should be able to export fence; but otherwise wait
            let _ = self.sync.wait();
            self.frame.success(transform, self.damage, presented);
        }
    }
}

pub fn submit_buffer<R>(
    frame: Frame,
    renderer: &mut R,
    offscreen: Option<&mut R::Framebuffer<'_>>,
    transform: Transform,
    damage: Option<&[Rectangle<i32, Physical>]>,
    mut sync: SyncPoint,
    buffers: Vec<smithay::backend::renderer::utils::Buffer>,
) -> Result<Option<PendingImageCopyData>, R::Error>
where
    R: ExportMem + AsGlowRenderer,
{
    let Some(damage) = damage else {
        frame.success(
            transform,
            None,
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap_or(Duration::ZERO),
        );
        return Ok(None);
    };

    let buffer = frame.buffer();
    let buffer_size = buffer_dimensions(&buffer).unwrap();

    if let Some(fb) = offscreen {
        assert!(matches!(buffer_type(&buffer), Some(BufferType::Shm)));
        if let Err(err) = with_buffer_contents_mut(&buffer, |ptr, len, data| {
            let offset = data.offset;
            let width = data.width;
            let height = data.height;
            let stride = data.stride;
            let format = shm_format_to_fourcc(data.format)
                .expect("We should be able to convert all hardcoded shm screencopy formats");

            // number of bytes per pixel
            // TODO: compute from data.format
            let pixelsize = 4i32;

            // ensure consistency, the SHM handler of smithay should ensure this
            assert!((offset + (height - 1) * stride + width * pixelsize) as usize <= len);

            // ensure rendering is done
            renderer.wait(&sync)?;

            let format = get_transparent(format).unwrap_or(format);
            let mapping =
                renderer.copy_framebuffer(fb, Rectangle::from_size(buffer_size), format)?;
            let gl_data = renderer.map_texture(&mapping)?;
            assert!((width * height * pixelsize) as usize <= gl_data.len());

            for i in 0..height {
                unsafe {
                    std::ptr::copy_nonoverlapping::<u8>(
                        gl_data.as_ptr().offset((width * pixelsize * i) as isize),
                        ptr.offset((offset + stride * i) as isize),
                        (width * pixelsize) as usize,
                    );
                }
            }

            // We've already waited on the sync point and copied from the texture
            // TODO: Don't block in this function, and defer copy until ready?
            sync = SyncPoint::signaled();

            Ok(())
        })
        .map_err(|err| R::from_gles_error(GlesError::BufferAccessError(err)))
        .and_then(|x| x)
        {
            frame.fail(CaptureFailureReason::Unknown);
            return Err(err);
        }
    }

    Ok(Some(PendingImageCopyData {
        frame,
        damage: damage
            .iter()
            .map(|rect| {
                let logical = rect.to_logical(1);
                logical.to_buffer(1, transform.invert(), &buffer_size.to_logical(1, transform))
            })
            .collect(),
        sync,
        _buffers: buffers,
    }))
}

pub fn render_session<F, R>(
    renderer: &mut R,
    session: &SessionData,
    frame: Frame,
    transform: Transform,
    render_fn: F,
) -> Result<Option<PendingImageCopyData>, DTError<R::Error>>
where
    R: AsGlowRenderer,
    F: for<'d> FnOnce(
        &WlBuffer,
        &mut R,
        Option<&mut R::Framebuffer<'_>>,
        &'d mut OutputDamageTracker,
        usize,
        Vec<Rectangle<i32, BufferCoords>>,
    ) -> Result<
        (
            RenderOutputResult<'d>,
            Vec<smithay::backend::renderer::utils::Buffer>,
        ),
        DTError<R::Error>,
    >,
{
    let mut session_user_data = session.lock().unwrap();

    let buffer = frame.buffer();

    let mut age = 1;
    if matches!(buffer_type(&buffer), Some(BufferType::Shm)) {
        let size = buffer_dimensions(&buffer).ok_or(DTError::OutputNoMode(OutputNoMode))?;
        let format = with_buffer_contents(&buffer, |_, _, data| {
            shm_format_to_fourcc(data.format)
                .expect("We should be able to convert all hardcoded shm screencopy formats")
        })
        .map_err(|_| DTError::OutputNoMode(OutputNoMode))?;

        // Re-allocate if context id, size, or format are different
        session_user_data
            .offscreen
            .take_if(|(context_id, renderbuffer)| {
                renderer.glow_renderer().context_id() != *context_id
                    || renderbuffer.size() != size
                    || renderbuffer.format() != Some(format)
            });

        if session_user_data.offscreen.is_none() {
            let renderbuffer = Offscreen::<GlesRenderbuffer>::create_buffer(renderer, format, size)
                .map_err(DTError::Rendering)?;
            session_user_data.offscreen =
                Some((renderer.glow_renderer().context_id(), renderbuffer));
            // If we're allocating a new offscreen buffer, we need to re-render everything
            // (or copy the contexts of the shm buffer)
            age = 0;
        }
    } else {
        // If for some reason a capture session is used for shm, but then changes to dmabuf capture,
        // remove the offscreen buffer.
        session_user_data.offscreen = None;
    }

    let SessionUserData { dt, offscreen } = &mut *session_user_data;
    let mut fb = offscreen
        .as_mut()
        .map(|(_, tex)| renderer.bind(tex).map_err(DTError::Rendering))
        .transpose()?;
    let (result, buffers) = render_fn(
        &frame.buffer(),
        renderer,
        fb.as_mut(),
        dt,
        age,
        frame.damage(),
    )?;

    submit_buffer(
        frame,
        renderer,
        fb.as_mut(),
        transform,
        result.damage.map(|x| x.as_slice()),
        result.sync,
        buffers,
    )
    .map_err(DTError::Rendering)
}

pub fn render_workspace_to_buffer(
    state: &mut State,
    session: &SessionRef,
    frame: Frame,
    handle: WorkspaceHandle,
) {
    let shell = state.common.shell.read();
    let Some(workspace) = shell.workspaces.space_for_handle(&handle) else {
        return;
    };

    let mut output = workspace.output().clone();
    let idx = shell.workspaces.idx_for_handle(&output, &handle).unwrap();
    std::mem::drop(shell);

    let mode = output
        .current_mode()
        .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal));

    let buffer = frame.buffer();
    let buffer_size = buffer_dimensions(&buffer).unwrap();
    if mode != Some(buffer_size) {
        let Some(constraints) = constraints_for_output(&output, &mut state.backend) else {
            output.remove_session(session);
            return;
        };
        session.update_constraints(constraints);
        if let Some(data) = session.user_data().get::<SessionData>() {
            *data.lock().unwrap() = SessionUserData::new(OutputDamageTracker::from_output(&output));
        }
        frame.fail(CaptureFailureReason::BufferConstraints);
        return;
    }

    fn render_fn<'d, R>(
        buffer: &WlBuffer,
        renderer: &mut R,
        offscreen: Option<&mut R::Framebuffer<'_>>,
        dt: &'d mut OutputDamageTracker,
        age: usize,
        additional_damage: Vec<Rectangle<i32, BufferCoords>>,
        draw_cursor: bool,
        common: &mut Common,
        output: &Output,
        handle: (WorkspaceHandle, usize),
    ) -> Result<
        (
            RenderOutputResult<'d>,
            Vec<smithay::backend::renderer::utils::Buffer>,
        ),
        DTError<R::Error>,
    >
    where
        R: AsGlowRenderer,
        R::TextureId: Send + Clone + 'static,
        CosmicElement<R>: RenderElement<R>,
        CosmicMappedRenderElement<R>: RenderElement<R>,
        WorkspaceRenderElement<R>: RenderElement<R>,
    {
        let cursor_mode = if draw_cursor {
            CursorMode::All
        } else {
            CursorMode::None
        };

        let area = output
            .current_mode()
            .ok_or(DTError::OutputNoMode(OutputNoMode))
            .map(
                |mode| {
                    mode.size
                        .to_logical(1)
                        .to_buffer(1, Transform::Normal)
                        .to_f64()
                }, /* TODO: Mode is Buffer..., why is this Physical in the first place */
            )?;
        let additional_damage = (!additional_damage.is_empty()).then(|| {
            additional_damage
                .into_iter()
                .map(|rect| {
                    rect.to_f64()
                        .to_logical(
                            output.current_scale().fractional_scale(),
                            output.current_transform(),
                            &area,
                        )
                        .to_i32_round()
                })
                .collect()
        });

        let (res, elements) = if let Ok(dmabuf) = get_dmabuf(buffer) {
            let mut dmabuf = dmabuf.clone();
            let mut fb = renderer.bind(&mut dmabuf).map_err(DTError::Rendering)?;
            render_workspace(
                None,
                renderer,
                &mut fb,
                dt,
                age,
                additional_damage,
                &common.shell,
                None,
                common.clock.now(),
                output,
                None,
                handle,
                cursor_mode,
                ElementFilter::ExcludeWorkspaceOverview,
            )?
        } else {
            let target = offscreen.expect("shm buffers should have an offscreen target");
            render_workspace(
                None,
                renderer,
                target,
                dt,
                age,
                additional_damage,
                &common.shell,
                None,
                common.clock.now(),
                output,
                None,
                handle,
                cursor_mode,
                ElementFilter::ExcludeWorkspaceOverview,
            )?
        };

        let buffers = render_element_buffers(renderer, &elements);

        Ok((res, buffers))
    }

    let draw_cursor = session.draw_cursor();
    let transform = output.current_transform();
    let common = &mut state.common;

    let renderer = match state.backend.offscreen_renderer(|kms| {
        let render_node = kms
            .target_node_for_output(&output)
            .or(*kms.primary_node.read().unwrap())?;
        let target_node = get_dmabuf(&buffer)
            .ok()
            .and_then(|dma| dma.node())
            .unwrap_or(render_node);

        let buffer_format = match buffer_type(&buffer) {
            Some(BufferType::Dma) => Some(get_dmabuf(&buffer).unwrap().format().code),
            Some(BufferType::Shm) => {
                with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format))
                    .unwrap()
            }
            _ => None,
        };

        Some(KmsNodes {
            render_node,
            target_node,
            copy_format: buffer_format.unwrap_or(Fourcc::Abgr8888),
        })
    }) {
        Ok(renderer) => renderer,
        Err(err) => {
            warn!(?err, "Couldn't use node for screencopy");
            frame.fail(CaptureFailureReason::Unknown);
            return;
        }
    };
    let result = match renderer {
        RendererRef::Glow(renderer) => {
            match render_session(
                renderer,
                session.user_data().get::<SessionData>().unwrap(),
                frame,
                transform,
                |buffer, renderer, offscreen, dt, age, additional_damage| {
                    render_fn(
                        buffer,
                        renderer,
                        offscreen,
                        dt,
                        age,
                        additional_damage,
                        draw_cursor,
                        common,
                        &output,
                        (handle, idx),
                    )
                },
            ) {
                Ok(frame) => frame,
                Err(err) => {
                    tracing::warn!(?err, "Failed to render to screencopy buffer");
                    None
                }
            }
        }
        RendererRef::GlMulti(mut renderer) => {
            match render_session(
                &mut renderer,
                session.user_data().get::<SessionData>().unwrap(),
                frame,
                transform,
                |buffer, renderer, offscreen, dt, age, additional_damage| {
                    render_fn(
                        buffer,
                        renderer,
                        offscreen,
                        dt,
                        age,
                        additional_damage,
                        draw_cursor,
                        common,
                        &output,
                        (handle, idx),
                    )
                },
            ) {
                Ok(frame) => frame,
                Err(err) => {
                    tracing::warn!(?err, "Failed to render to screencopy buffer");
                    None
                }
            }
        }
    };

    if let Some(pending_image_copy_data) = result {
        pending_image_copy_data.send_success_when_ready(
            transform,
            &common.event_loop_handle,
            common.clock.now(),
        );
    }
}

smithay::render_elements! {
    pub WindowCaptureElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
    WaylandElement=SurfaceRenderElement<R>,
    CursorElement=RelocateRenderElement<cursor::CursorRenderElement<R>>,
    DamageElement=DamageElement,
}

pub fn render_window_to_buffer(
    state: &mut State,
    session: &SessionRef,
    frame: Frame,
    toplevel: &CosmicSurface,
) {
    if !toplevel.alive() {
        toplevel.clone().remove_session(session);
        return;
    }

    let buffer = frame.buffer();
    let geometry = toplevel.geometry();
    let buffer_size = buffer_dimensions(&buffer).unwrap();
    if buffer_size != geometry.size.to_buffer(1, Transform::Normal) {
        let Some(constraints) = constraints_for_toplevel(toplevel, &mut state.backend) else {
            toplevel.clone().remove_session(session);
            return;
        };
        session.update_constraints(constraints);

        if let Some(data) = session.user_data().get::<SessionData>() {
            let size = geometry.size.to_physical(1);
            *data.lock().unwrap() =
                SessionUserData::new(OutputDamageTracker::new(size, 1.0, Transform::Normal));
        }
        frame.fail(CaptureFailureReason::BufferConstraints);
        return;
    }

    fn render_fn<'d, R>(
        buffer: &WlBuffer,
        renderer: &mut R,
        offscreen: Option<&mut R::Framebuffer<'_>>,
        dt: &'d mut OutputDamageTracker,
        age: usize,
        additional_damage: Vec<Rectangle<i32, BufferCoords>>,
        draw_cursor: bool,
        common: &mut Common,
        toplevel: &CosmicSurface,
        geometry: Rectangle<i32, Logical>,
    ) -> Result<
        (
            RenderOutputResult<'d>,
            Vec<smithay::backend::renderer::utils::Buffer>,
        ),
        DTError<R::Error>,
    >
    where
        R: AsGlowRenderer,
        R::TextureId: Send + Clone + 'static,
        CosmicElement<R>: RenderElement<R>,
        CosmicMappedRenderElement<R>: RenderElement<R>,
    {
        let mut elements: Vec<_> = additional_damage
            .into_iter()
            .filter_map(|rect| {
                let logical_rect = rect.to_logical(
                    1,
                    Transform::Normal,
                    &geometry.size.to_buffer(1, Transform::Normal),
                );
                logical_rect.intersection(Rectangle::from_size(geometry.size))
            })
            .map(DamageElement::new)
            .map(WindowCaptureElement::<R>::from)
            .collect();

        let shell = common.shell.read();
        let blur_strength = 9; // TODO
        let seat = shell.seats.last_active().clone();
        let pointer = seat.get_pointer().unwrap();
        let pointer_loc = pointer.current_location().to_i32_round().as_global();
        let mut location = None;
        if let Some(element) = shell.element_for_surface(toplevel)
            && pointer
                .current_focus()
                .and_then(|f| f.toplevel(&shell))
                .as_ref()
                == Some(toplevel)
            && element.has_active_window(toplevel)
            && let Some(workspace) = shell.space_for(element)
            && let Some(geometry) = workspace.element_geometry(element)
        {
            let mut surface_geo = element.active_window_geometry().as_local();
            surface_geo.loc += geometry.loc;
            let global_geo = surface_geo.to_global(workspace.output());
            if global_geo.contains(pointer_loc) {
                location = Some((pointer_loc - global_geo.loc).as_logical().to_f64());
            }
        };
        std::mem::drop(shell);

        if let Some(location) = location {
            if draw_cursor {
                cursor::draw_cursor(
                    renderer,
                    &seat,
                    location,
                    1.0.into(),
                    1.0,
                    common.clock.now(),
                    blur_strength,
                    true,
                    &mut |elem, hotspot| {
                        elements.push(WindowCaptureElement::CursorElement(
                            RelocateRenderElement::from_element(
                                elem,
                                Point::from((-hotspot.x, -hotspot.y)),
                                Relocate::Relative,
                            ),
                        ));
                    },
                );
            }

            // TODO cosmic-workspaces wants to omit, but metadata cursor capture in portal should
            // still include dnd surface in window capture buffer?
            if draw_cursor && let Some(dnd_icon) = get_dnd_icon(&seat) {
                cursor::draw_dnd_icon(
                    renderer,
                    &dnd_icon.surface,
                    (location + dnd_icon.offset.to_f64()).to_i32_round(),
                    1.0,
                    blur_strength,
                    &mut |elem| {
                        elements.push(
                            RelocateRenderElement::from_element(
                                CursorRenderElement::Surface(elem),
                                Point::new(0, 0),
                                Relocate::Relative,
                            )
                            .into(),
                        )
                    },
                );
            }
        }

        toplevel.push_render_elements(
            renderer,
            (-geometry.loc.x, -geometry.loc.y).into(),
            Scale::from(1.0),
            1.0,
            None,
            None,
            false,
            [0; 4],
            blur_strength,
            &mut |elem| elements.push(elem.into()),
            None,
        );

        let res = if let Ok(dmabuf) = get_dmabuf(buffer) {
            let mut dmabuf_clone = dmabuf.clone();
            let mut fb = renderer
                .bind(&mut dmabuf_clone)
                .map_err(DTError::Rendering)?;
            dt.render_output(renderer, &mut fb, age, &elements, Color32F::TRANSPARENT)?
        } else {
            let fb = offscreen.expect("shm buffer should have an offscreen target");
            dt.render_output(renderer, fb, age, &elements, Color32F::TRANSPARENT)?
        };

        let buffers = render_element_buffers(renderer, &elements);

        Ok((res, buffers))
    }

    let common = &mut state.common;
    let draw_cursor = session.draw_cursor();

    let renderer = match state.backend.offscreen_renderer(|kms| {
        get_dmabuf(&buffer)
            .ok()
            .and_then(|dmabuf| dmabuf.node())
            .or_else(|| {
                toplevel
                    .wl_surface()
                    .and_then(|wl_surface| {
                        with_renderer_surface_state(&wl_surface, |state| {
                            let buffer = state.buffer()?;
                            let dmabuf = get_dmabuf(buffer).ok()?;
                            dmabuf.node()
                        })
                    })
                    .flatten()
            })
            .or(*kms.primary_node.read().unwrap())
    }) {
        Ok(renderer) => renderer,
        Err(err) => {
            warn!(?err, "Couldn't use node for screencopy");
            frame.fail(CaptureFailureReason::Unknown);
            return;
        }
    };
    let result = match renderer {
        RendererRef::Glow(renderer) => match render_session(
            renderer,
            session.user_data().get::<SessionData>().unwrap(),
            frame,
            Transform::Normal,
            |buffer, renderer, offscreen, dt, age, additional_damage| {
                render_fn(
                    buffer,
                    renderer,
                    offscreen,
                    dt,
                    age,
                    additional_damage,
                    draw_cursor,
                    common,
                    toplevel,
                    geometry,
                )
            },
        ) {
            Ok(frame) => frame,
            Err(err) => {
                tracing::warn!(?err, "Failed to render to screencopy buffer");
                None
            }
        },
        RendererRef::GlMulti(mut renderer) => match render_session(
            &mut renderer,
            session.user_data().get::<SessionData>().unwrap(),
            frame,
            Transform::Normal,
            |buffer, renderer, offscreen, dt, age, additional_damage| {
                render_fn(
                    buffer,
                    renderer,
                    offscreen,
                    dt,
                    age,
                    additional_damage,
                    draw_cursor,
                    common,
                    toplevel,
                    geometry,
                )
            },
        ) {
            Ok(frame) => frame,
            Err(err) => {
                tracing::warn!(?err, "Failed to render to screencopy buffer");
                None
            }
        },
    };

    if let Some(pending_image_copy_data) = result {
        pending_image_copy_data.send_success_when_ready(
            Transform::Normal,
            &common.event_loop_handle,
            common.clock.now(),
        );
    }
}

pub fn render_cursor_to_buffer(
    state: &mut State,
    session: &CursorSessionRef,
    frame: Frame,
    seat: &Seat<State>,
) {
    let buffer = frame.buffer();
    let cursor_geometry = seat.cursor_geometry((0.0, 0.0), state.common.clock.now());
    let constraints = cursor_capture_constraints(cursor_geometry);
    let buffer_size = buffer_dimensions(&buffer).unwrap();
    if buffer_size != constraints.size {
        session.update_constraints(constraints.clone());
        if let Some(data) = session.user_data().get::<SessionData>() {
            *data.lock().unwrap() = SessionUserData::new(OutputDamageTracker::new(
                constraints
                    .size
                    .to_logical(1, Transform::Normal)
                    .to_physical(1),
                1.0,
                Transform::Normal,
            ));
        }
        frame.fail(CaptureFailureReason::BufferConstraints);
        return;
    }

    fn render_fn<'d, R>(
        buffer: &WlBuffer,
        renderer: &mut R,
        offscreen: Option<&mut R::Framebuffer<'_>>,
        dt: &'d mut OutputDamageTracker,
        age: usize,
        additional_damage: Vec<Rectangle<i32, BufferCoords>>,
        common: &mut Common,
        seat: &Seat<State>,
    ) -> Result<
        (
            RenderOutputResult<'d>,
            Vec<smithay::backend::renderer::utils::Buffer>,
        ),
        DTError<R::Error>,
    >
    where
        R: AsGlowRenderer,
        R::TextureId: Send + Clone + 'static,
        CosmicElement<R>: RenderElement<R>,
        CosmicMappedRenderElement<R>: RenderElement<R>,
    {
        let mut elements: Vec<_> = additional_damage
            .into_iter()
            .filter_map(|rect| {
                let logical_rect = rect.to_logical(1, Transform::Normal, &Size::from((64, 64)));
                logical_rect.intersection(Rectangle::from_size((64, 64).into()))
            })
            .map(DamageElement::new)
            .map(WindowCaptureElement::from)
            .collect();

        cursor::draw_cursor(
            renderer,
            seat,
            Point::from((0.0, 0.0)),
            1.0.into(),
            1.0,
            common.clock.now(),
            0,
            true,
            &mut |elem, _| {
                elements.push(
                    RelocateRenderElement::from_element(elem, (0, 0), Relocate::Relative).into(),
                )
            },
        );

        let res = if let Ok(dmabuf) = get_dmabuf(buffer) {
            let mut dmabuf_clone = dmabuf.clone();
            let mut fb = renderer
                .bind(&mut dmabuf_clone)
                .map_err(DTError::Rendering)?;
            dt.render_output(renderer, &mut fb, age, &elements, [0.0, 0.0, 0.0, 0.0])?
        } else {
            let fb = offscreen.expect("shm buffers should have offscreen target");
            dt.render_output(renderer, fb, age, &elements, [0.0, 0.0, 0.0, 0.0])?
        };

        let buffers = render_element_buffers(renderer, &elements);

        Ok((res, buffers))
    }

    let common = &mut state.common;
    let renderer = match state
        .backend
        .offscreen_renderer(|kms| *kms.primary_node.read().unwrap())
    {
        Ok(renderer) => renderer,
        Err(err) => {
            warn!(?err, "Couldn't use node for screencopy");
            frame.fail(CaptureFailureReason::Unknown);
            return;
        }
    };
    let result = match renderer {
        RendererRef::Glow(renderer) => {
            match render_session(
                renderer,
                session.user_data().get::<SessionData>().unwrap(),
                frame,
                Transform::Normal,
                |buffer, renderer, offscreen, dt, age, additional_damage| {
                    render_fn(
                        buffer,
                        renderer,
                        offscreen,
                        dt,
                        age,
                        additional_damage,
                        common,
                        seat,
                    )
                },
            ) {
                Ok(frame) => frame,
                Err(err) => {
                    tracing::warn!(?err, "Failed to render to screencopy buffer");
                    None
                }
            }
        }
        RendererRef::GlMulti(mut renderer) => {
            match render_session(
                &mut renderer,
                session.user_data().get::<SessionData>().unwrap(),
                frame,
                Transform::Normal,
                |buffer, renderer, offscreen, dt, age, additional_damage| {
                    render_fn(
                        buffer,
                        renderer,
                        offscreen,
                        dt,
                        age,
                        additional_damage,
                        common,
                        seat,
                    )
                },
            ) {
                Ok(frame) => frame,
                Err(err) => {
                    tracing::warn!(?err, "Failed to render to screencopy buffer");
                    None
                }
            }
        }
    };

    if let Some(pending_image_copy_data) = result {
        pending_image_copy_data.send_success_when_ready(
            Transform::Normal,
            &common.event_loop_handle,
            common.clock.now(),
        );
    }
}
