srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/winit/capture.rs
blob: 3b1d38ec2b35c853e64d9859f21ae102daaa5800 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
use super::*;

impl WaylandPlatform {

    /// Re-renders the current scene into an offscreen GLES renderbuffer and
    /// serves the queued screencopy captures from it. See the call site for
    /// why capture cannot read the window surface directly.
    pub(super) fn capture_offscreen(&mut self, captures: Vec<screencopy::PendingCapture>) -> PlatformResult<()> {
        use smithay::backend::renderer::{Bind, Offscreen};

        let size = self.output.current_mode().map(|m| m.size).unwrap_or_default();
        if size.w <= 0 || size.h <= 0 {
            return Ok(());
        }
        let renderer = self.backend.renderer();
        let mut target: smithay::backend::renderer::gles::GlesRenderbuffer =
            renderer.create_buffer(Fourcc::Abgr8888, (size.w, size.h).into()).map_err(err)?;
        let mut framebuffer = renderer.bind(&mut target).map_err(err)?;

        // Not full parity with the on-screen render loop above (no border/
        // shadow strips here, same as before this function's content/opacity
        // fix) - a real, pre-existing gap in what a screenshot shows on
        // this backend, flagged rather than grown further in this pass.
        // Content (with each window's own `opacity`, unlike the
        // `self.state.space`-based single-alpha call this replaced) and the
        // bar/dock now render into the capture, at least: a screenshot used
        // to only ever show titlebars for windows that had one, and never
        // any layer-shell surface at all.
        let hide_top_layers = self.wm.borrow().visible_windows_front_to_back().any(|w| w.fullscreen);
        let mut custom_elements: Vec<crate::elements::OverlayElement<GlesRenderer>> = Vec::new();
        if !hide_top_layers {
            custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, |layer| matches!(layer, Layer::Top | Layer::Overlay)));
        }
        for id in self.wm.borrow().visible_windows_front_to_back().map(|w| w.id).collect::<Vec<_>>() {
            let Some(w) = self.wm.borrow().window(id).cloned() else { continue };
            if let Some(deco) = self.state.decorations.get(&id) {
                if let Ok(elem) = MemoryRenderBufferRenderElement::from_buffer(renderer, (w.geometry.x as f64, w.geometry.y as f64), deco, None, None, None, Kind::Unspecified) {
                    custom_elements.push(crate::elements::OverlayElement::Memory(elem));
                }
            }
            if let Some(dwindow) = self.state.id_to_window.get(&id) {
                if let Some(surface) = crate::elements::window_wl_surface(dwindow) {
                    let band = if w.decorated { srdwm_core::TITLEBAR_HEIGHT as i32 } else { 0 };
                    // `content_offset`: same `xdg_surface.set_window_geometry`
                    // subtraction every other render/capture path in this
                    // codebase already does (`udev/render.rs`, `winit/
                    // render.rs`, `udev/capture.rs`) - missed here
                    // specifically. A CSD client's invisible shadow margin
                    // landed at `w.geometry.x, w.geometry.y + band` instead
                    // of its real visible content, so a screenshot taken on
                    // this backend showed the same content_offset-sized gap
                    // the on-screen render loops already had fixed.
                    let content_offset = dwindow.geometry().loc;
                    let pos = (w.geometry.x - content_offset.x, w.geometry.y + band - content_offset.y);
                    custom_elements.extend(crate::elements::surface_content_elements(renderer, &surface, pos, w.opacity));
                }
            }
        }
        custom_elements.extend(crate::elements::output_layer_elements(renderer, &self.output, |layer| matches!(layer, Layer::Background | Layer::Bottom)));

        // A throwaway damage tracker, so this pass always draws the whole
        // scene (age 0) and never perturbs the on-screen tracker's history.
        let mut tracker = OutputDamageTracker::from_output(&self.output);
        tracker
            .render_output(renderer, &mut framebuffer, 0, &custom_elements, [0.05, 0.05, 0.08, 1.0])
            .map_err(err)?;

        screencopy::service_pending(captures, renderer, &framebuffer);
        Ok(())
    }
}