Skip to main content

crabshell_ui/
screencopy.rs

1//! [`ScreencopyView`], a widget that shows what an output or a window
2//! shows, and what the platform reads and writes of it: the views to
3//! capture for, and the frames it captured.
4
5use std::rc::Rc;
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use crate::geometry::Size;
9use crate::prop::{IntoProp, Prop};
10use crate::style::Style;
11use crate::tree::{Change, NodeId, NodeKind, Tree, borrow, replace};
12use crate::widgets::{Element, ElementKind, Fit, Widget};
13use crate::window::Window;
14
15/// Numbers every view mounted, in every window, so a platform's capture
16/// and a renderer's cache can't mix two up.
17static NEXT_VIEW: AtomicU64 = AtomicU64::new(0);
18
19/// What a [`ScreencopyView`] shows.
20#[non_exhaustive]
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub enum CaptureSource {
23  /// The output of this name, such as `DP-1`.
24  Output(String),
25  /// The window of this id: a `Toplevel::id` from `cx.toplevels()`.
26  Toplevel(u64),
27}
28
29pub(crate) struct ScreencopyProps {
30  pub(crate) source: Prop<CaptureSource>,
31  pub(crate) live: Option<Prop<bool>>,
32  pub(crate) paint_cursors: Option<Prop<bool>>,
33  pub(crate) fit: Option<Prop<Fit>>,
34}
35
36/// Shows what an output or a window shows, captured by the compositor,
37/// in the box its style's `width` and `height` give it, as an
38/// [`Image`](crate::Image) shows a picture. It draws nothing until the
39/// first frame arrives.
40///
41/// It captures one frame, unless it is `live`: then each change to the
42/// source brings a new frame, and a source that doesn't change sends
43/// none, so it wakes nothing. A source that isn't there yet, such as an
44/// output not plugged in, is waited for. When it goes, the last frame
45/// stays.
46///
47/// ```
48/// use crabshell_ui::{CaptureSource, ScreencopyView, Style};
49///
50/// let thumbnail = ScreencopyView::new(CaptureSource::Output(String::from("DP-1")))
51///     .live(true)
52///     .style(Style::new().width(192.0).height(108.0));
53/// ```
54pub struct ScreencopyView {
55  props: ScreencopyProps,
56  style: Option<Prop<Style>>,
57}
58
59impl ScreencopyView {
60  /// A view of `source`: a [`CaptureSource`], or a closure that returns
61  /// one.
62  pub fn new(source: impl IntoProp<CaptureSource>) -> ScreencopyView {
63    ScreencopyView {
64      props: ScreencopyProps {
65        source: source.into_prop(),
66        live: None,
67        paint_cursors: None,
68        fit: None,
69      },
70      style: None,
71    }
72  }
73
74  /// Whether each change to the source shows, rather than only the first
75  /// frame. Off by default.
76  pub fn live(mut self, live: impl IntoProp<bool>) -> ScreencopyView {
77    self.props.live = Some(live.into_prop());
78    self
79  }
80
81  /// Whether the pointer is drawn into the frames. Off by default.
82  pub fn paint_cursors(mut self, paint: impl IntoProp<bool>) -> ScreencopyView {
83    self.props.paint_cursors = Some(paint.into_prop());
84    self
85  }
86
87  /// Sets how the frame fills the view's box when their shapes differ.
88  pub fn fit(mut self, fit: impl IntoProp<Fit>) -> ScreencopyView {
89    self.props.fit = Some(fit.into_prop());
90    self
91  }
92
93  /// Sets the style, whose `width` and `height` give the view its size.
94  pub fn style(mut self, style: impl IntoProp<Style>) -> ScreencopyView {
95    self.style = Some(style.into_prop());
96    self
97  }
98}
99
100impl Widget for ScreencopyView {
101  fn into_element(self: Box<Self>) -> Element {
102    Element {
103      name: "ScreencopyView",
104      style: self.style,
105      kind: ElementKind::Capture(self.props),
106      children: Vec::new(),
107    }
108  }
109}
110
111/// A captured frame, as a view keeps it.
112#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct CaptureFrame {
114  /// The width in pixels.
115  pub width: u32,
116  /// The height in pixels.
117  pub height: u32,
118  /// Premultiplied RGBA, row by row: `width * height * 4` bytes. Shared,
119  /// so a renderer can tell a new frame from the one it already scaled.
120  pub rgba: Rc<[u8]>,
121}
122
123/// One view the platform captures for, from [`Window::captures`].
124#[derive(Clone, Debug, PartialEq)]
125pub struct CaptureRequest {
126  /// Names the view while it is in a window. Never reused.
127  pub id: u64,
128  /// What it shows.
129  pub source: CaptureSource,
130  /// Whether it shows each new frame.
131  pub live: bool,
132  /// Whether the pointer is drawn into the frames.
133  pub paint_cursors: bool,
134  /// The view's content box at the last layout, in logical pixels: a
135  /// frame needn't be larger.
136  pub size: Size,
137}
138
139pub(crate) struct CaptureNode {
140  pub(crate) id: u64,
141  /// `None` until the source's binding first runs.
142  pub(crate) source: Option<CaptureSource>,
143  pub(crate) live: bool,
144  pub(crate) paint_cursors: bool,
145  pub(crate) fit: Fit,
146  pub(crate) frame: Option<CaptureFrame>,
147}
148
149impl CaptureNode {
150  pub(crate) fn new() -> CaptureNode {
151    CaptureNode {
152      id: NEXT_VIEW.fetch_add(1, Ordering::Relaxed),
153      source: None,
154      live: false,
155      paint_cursors: false,
156      fit: Fit::default(),
157      frame: None,
158    }
159  }
160}
161
162impl Tree {
163  /// Applies a view's properties. Only the source and fit change what
164  /// shows; the platform reads the rest when it next captures.
165  pub(crate) fn bind_capture(&mut self, id: NodeId, props: ScreencopyProps) {
166    self.captures.push(id);
167    self.bind(id, props.source, "source", |tree, id, value| {
168      tree.update_capture(id, |capture| {
169        if capture.source.as_ref() == Some(&value) {
170          return Change::None;
171        }
172        capture.source = Some(value);
173        // The old source's frame isn't this one's.
174        match capture.frame.take() {
175          Some(_) => Change::Paint,
176          None => Change::None,
177        }
178      });
179    });
180    self.bind_opt(id, props.live, "live", |tree, id, value| {
181      tree.update_capture(id, |capture| {
182        capture.live = value;
183        Change::None
184      });
185    });
186    self.bind_opt(
187      id,
188      props.paint_cursors,
189      "paint_cursors",
190      |tree, id, value| {
191        tree.update_capture(id, |capture| {
192          capture.paint_cursors = value;
193          Change::None
194        });
195      },
196    );
197    self.bind_opt(id, props.fit, "fit", |tree, id, value| {
198      tree.update_capture(id, |capture| match replace(&mut capture.fit, value) {
199        Change::None => Change::None,
200        _ => Change::Paint,
201      });
202    });
203  }
204
205  fn update_capture(&mut self, id: NodeId, f: impl FnOnce(&mut CaptureNode) -> Change) {
206    self.update(id, |node| match &mut node.kind {
207      NodeKind::Capture(capture) => f(capture),
208      _ => Change::None,
209    });
210  }
211}
212
213impl Window {
214  /// The views to capture for, in the order they were built: those with
215  /// a source and a box that isn't empty at the last layout.
216  pub fn captures(&self) -> Vec<CaptureRequest> {
217    let tree = self.tree.borrow();
218    tree
219      .captures
220      .iter()
221      .filter_map(|&id| {
222        let node = tree.node(id)?;
223        let NodeKind::Capture(capture) = &node.kind else {
224          return None;
225        };
226        let content = node.content;
227        if node.rect.is_none() || content.is_empty() {
228          return None;
229        }
230        Some(CaptureRequest {
231          id: capture.id,
232          source: capture.source.clone()?,
233          live: capture.live,
234          paint_cursors: capture.paint_cursors,
235          size: Size::new(content.width, content.height),
236        })
237      })
238      .collect()
239  }
240
241  /// Shows `frame` in view `id`, repainting only that view, and returns
242  /// whether it did. A frame the same as the one shown repaints nothing,
243  /// so a live view of its own output settles. A view no longer in the
244  /// window is ignored.
245  pub fn show_capture(&self, id: u64, frame: CaptureFrame) -> bool {
246    let mut tree = borrow(&self.tree);
247    let found = tree.captures.iter().copied().find(|&node| {
248      matches!(tree.node(node).map(|node| &node.kind),
249        Some(NodeKind::Capture(capture)) if capture.id == id)
250    });
251    let Some(node) = found else {
252      return false;
253    };
254    let mut shown = false;
255    tree.update(node, |node| match &mut node.kind {
256      NodeKind::Capture(capture) if capture.frame.as_ref() != Some(&frame) => {
257        capture.frame = Some(frame);
258        shown = true;
259        Change::Paint
260      }
261      _ => Change::None,
262    });
263    shown
264  }
265}