Commit 3ea37d969201
tarball5 files changed+265-0
@@ -0,0 +1,243 @@
1
+//! ext-image-copy-capture of whole outputs, for grim and screen sharing2
+//! portals.3
+4
+use crate::scene::Draw;5
+use crate::state::{State, now_ns};6
+use monolith_layout::OutputId;7
+use monolith_render_vk::VulkanImage;8
+use smithay::backend::allocator::Fourcc;9
+use smithay::backend::allocator::dmabuf::Dmabuf;10
+use smithay::backend::renderer::damage::OutputDamageTracker;11
+use smithay::backend::renderer::{Bind, ExportMem, Offscreen};12
+use smithay::output::{Output, WeakOutput};13
+use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;14
+use smithay::reexports::wayland_server::protocol::wl_shm;15
+use smithay::utils::{Point, Rectangle, Size, Transform};16
+use smithay::wayland::dmabuf::get_dmabuf;17
+use smithay::wayland::image_capture_source::{18
+ ImageCaptureSource, ImageCaptureSourceHandler, OutputCaptureSourceHandler,19
+ OutputCaptureSourceState,20
+};21
+use smithay::wayland::image_copy_capture::{22
+ BufferConstraints, CaptureFailureReason, DmabufConstraints, Frame, ImageCopyCaptureHandler,23
+ ImageCopyCaptureState, Session, SessionRef,24
+};25
+use smithay::wayland::shm::{shm_format_to_fourcc, with_buffer_contents_mut};26
+use std::collections::HashSet;27
+use std::time::Duration;28
+29
+#[derive(Default)]30
+pub struct Captures {31
+ sessions: Vec<Session>,32
+ /// Frames waiting for their output to show something new.33
+ waiting: Vec<(SessionRef, Frame)>,34
+ damaged: HashSet<String>,35
+}36
+37
+impl Captures {38
+ /// Records that `output` drew a frame with new content.39
+ pub fn damaged(&mut self, output: &Output) {40
+ if !self.waiting.is_empty() {41
+ self.damaged.insert(output.name());42
+ }43
+ }44
+}45
+46
+impl ImageCaptureSourceHandler for State {47
+ fn source_destroyed(&mut self, _source: ImageCaptureSource) {}48
+}49
+50
+impl OutputCaptureSourceHandler for State {51
+ fn output_capture_source_state(&mut self) -> &mut OutputCaptureSourceState {52
+ &mut self.output_capture_source_state53
+ }54
+55
+ fn output_source_created(&mut self, source: ImageCaptureSource, output: &Output) {56
+ source.user_data().insert_if_missing(|| output.downgrade());57
+ }58
+}59
+60
+impl ImageCopyCaptureHandler for State {61
+ fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {62
+ &mut self.image_copy_capture_state63
+ }64
+65
+ fn capture_constraints(&mut self, source: &ImageCaptureSource) -> Option<BufferConstraints> {66
+ let output = source.user_data().get::<WeakOutput>()?.upgrade()?;67
+ let mode = output.current_mode()?;68
+ let dma = self.render_node.map(|node| {69
+ let mut formats: Vec<(Fourcc, Vec<_>)> = Vec::new();70
+ for f in self.renderer.render_formats().iter() {71
+ match formats.iter_mut().find(|(c, _)| *c == f.code) {72
+ Some((_, mods)) => mods.push(f.modifier),73
+ None => formats.push((f.code, vec![f.modifier])),74
+ }75
+ }76
+ DmabufConstraints { node, formats }77
+ });78
+ Some(BufferConstraints {79
+ size: (mode.size.w, mode.size.h).into(),80
+ shm: vec![wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888],81
+ dma,82
+ })83
+ }84
+85
+ fn new_session(&mut self, session: Session) {86
+ self.captures.sessions.push(session);87
+ }88
+89
+ fn frame(&mut self, session: &SessionRef, frame: Frame) {90
+ if session.user_data().get::<Delivered>().is_some() {91
+ self.captures.waiting.push((session.clone(), frame));92
+ self.needs_redraw = true;93
+ } else {94
+ self.capture(session, frame);95
+ }96
+ }97
+98
+ fn session_destroyed(&mut self, session: SessionRef) {99
+ let c = &mut self.captures;100
+ c.sessions.retain(|s| *s != session);101
+ c.waiting.retain(|(s, _)| *s != session);102
+ }103
+}104
+105
+impl State {106
+ /// Completes waiting frames on outputs that drew something new.107
+ pub fn complete_captures(&mut self) {108
+ if self.captures.damaged.is_empty() {109
+ return;110
+ }111
+ let damaged = std::mem::take(&mut self.captures.damaged);112
+ let (ready, waiting) = std::mem::take(&mut self.captures.waiting)113
+ .into_iter()114
+ .partition::<Vec<_>, _>(|(s, _)| {115
+ source_output(&s.source()).is_some_and(|o| damaged.contains(&o.name()))116
+ });117
+ self.captures.waiting = waiting;118
+ for (session, frame) in ready {119
+ self.capture(&session, frame);120
+ }121
+ }122
+123
+ fn capture(&mut self, session: &SessionRef, frame: Frame) {124
+ let Some(output) = source_output(&session.source()) else {125
+ frame.fail(CaptureFailureReason::Stopped);126
+ return;127
+ };128
+ let Some(o) = self.layout_output(&output) else {129
+ frame.fail(CaptureFailureReason::Stopped);130
+ return;131
+ };132
+ let Some(mode) = output.current_mode() else {133
+ frame.fail(CaptureFailureReason::Unknown);134
+ return;135
+ };136
+ let elements = self.output_elements(o, &output, session.draw_cursor());137
+ let buffer = frame.buffer();138
+ let result = match get_dmabuf(&buffer) {139
+ Ok(dmabuf) => self.capture_dmabuf(dmabuf.clone(), mode.size.into(), &elements),140
+ Err(_) => self.capture_shm(&buffer, mode.size.into(), &elements),141
+ };142
+ match result {143
+ Ok(()) => {144
+ session.user_data().insert_if_missing(|| Delivered);145
+ frame.success(Transform::Normal, None, Duration::from_nanos(now_ns()));146
+ }147
+ Err(e) => {148
+ tracing::warn!("screen capture failed: {e}");149
+ frame.fail(CaptureFailureReason::Unknown);150
+ }151
+ }152
+ }153
+154
+ fn capture_dmabuf(155
+ &mut self,156
+ mut dmabuf: Dmabuf,157
+ size: (i32, i32),158
+ elements: &[Draw],159
+ ) -> Result<(), Box<dyn std::error::Error>> {160
+ let mut fb = self.renderer.bind(&mut dmabuf)?;161
+ let mut tracker = OutputDamageTracker::new(size, 1.0, Transform::Normal);162
+ let result = tracker.render_output(&mut self.renderer, &mut fb, 0, elements, CLEAR)?;163
+ result.sync.wait()?;164
+ Ok(())165
+ }166
+167
+ fn capture_shm(168
+ &mut self,169
+ buffer: &WlBuffer,170
+ size: (i32, i32),171
+ elements: &[Draw],172
+ ) -> Result<(), Box<dyn std::error::Error>> {173
+ let format = with_buffer_contents_mut(buffer, |_, _, data| data.format)?;174
+ let fourcc = shm_format_to_fourcc(format).ok_or("unsupported shm format")?;175
+ let mut image: VulkanImage = self.renderer.create_buffer(fourcc, size.into())?;176
+ let mut fb = self.renderer.bind(&mut image)?;177
+ let mut tracker = OutputDamageTracker::new(size, 1.0, Transform::Normal);178
+ let result = tracker.render_output(&mut self.renderer, &mut fb, 0, elements, CLEAR)?;179
+ result.sync.wait()?;180
+ let region = Rectangle::from_size(Size::from(size));181
+ let mapping = self.renderer.copy_framebuffer(&fb, region, fourcc)?;182
+ let pixels = self.renderer.map_texture(&mapping)?;183
+ let row = size.0 as usize * 4;184
+ with_buffer_contents_mut(buffer, |ptr, len, data| {185
+ // SAFETY: smithay maps the whole pool, `len` bytes from `ptr`.186
+ let pool = unsafe { std::slice::from_raw_parts_mut(ptr, len) };187
+ for (y, src) in pixels.chunks_exact(row).enumerate() {188
+ let start = data.offset as usize + y * data.stride as usize;189
+ if let Some(dst) = pool.get_mut(start..start + row) {190
+ dst.copy_from_slice(src);191
+ }192
+ }193
+ })?;194
+ Ok(())195
+ }196
+197
+ /// Everything output `o` shows, as the backends draw it.198
+ pub fn output_elements(&mut self, o: OutputId, output: &Output, cursor: bool) -> Vec<Draw> {199
+ let now = now_ns();200
+ let mut elements = Vec::new();201
+ if cursor && let Some(area) = self.layout.output(o).map(|out| out.area) {202
+ let local = self.pointer_pos - Point::from((area.x, area.y));203
+ if local.x >= 0.0 && local.y >= 0.0 && local.x < area.w && local.y < area.h {204
+ elements.extend(self.cursor.elements(205
+ &mut self.renderer,206
+ &self.cursor_status,207
+ local,208
+ ));209
+ }210
+ }211
+ match crate::lock::elements(&self.lock, &mut self.renderer, output) {212
+ Some(locked) => elements.extend(locked),213
+ None => elements.extend(crate::scene::build(214
+ &self.layout,215
+ o,216
+ &self.windows,217
+ &self.anim,218
+ &mut self.renderer,219
+ now,220
+ &self.config.decoration,221
+ &mut self.borders,222
+ Some(output),223
+ )),224
+ }225
+ elements226
+ }227
+228
+ pub fn layout_output(&self, output: &Output) -> Option<OutputId> {229
+ self.layout230
+ .outputs()231
+ .map(|(o, _)| o)232
+ .find(|&o| self.smithay_output(o).as_ref() == Some(output))233
+ }234
+}235
+236
+/// Marks a session that has had a frame, so later ones wait for damage.237
+struct Delivered;238
+239
+const CLEAR: [f32; 4] = [0.07, 0.075, 0.09, 1.0];240
+241
+fn source_output(source: &ImageCaptureSource) -> Option<Output> {242
+ source.user_data().get::<WeakOutput>()?.upgrade()243
+}@@ -202,6 +202,7 @@ pub fn init(
202202
.node_with_type(NodeType::Render)203203
.and_then(Result::ok)204204
.unwrap_or(node);205
+ state.render_node = Some(render_node);205206
let allocator = GbmAllocator::new(206207
gbm.clone(),207208
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,@@ -590,6 +591,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
590591
cursor_status,591592
pointer_pos,592593
lock,594
+ captures,593595
..594596
} = state595597
else {@@ -668,6 +670,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
668670
Ok(()) => {669671
surface.queued = true;670672
lock.shown(&surface.output);673
+ captures.damaged(&surface.output);671674
}672675
Err(e) => tracing::warn!("queueing a frame failed: {e}"),673676
}@@ -1,5 +1,6 @@
11
mod activation;22
mod anim;3
+mod capture;34
mod commands;45
mod cursor;56
mod drm;@@ -180,6 +180,7 @@ pub fn render(state: &mut State) {
180180
config,181181
output_id,182182
lock,183
+ captures,183184
..184185
} = state;185186
let Some(host) = host.as_mut() else { return };@@ -264,6 +265,9 @@ pub fn render(state: &mut State) {
264265
});265266
}266267
crate::layers::send_frames(&host.output, elapsed);268
+ if damage.is_some() {269
+ captures.damaged(&host.output);270
+ }267271
let Some(damage) = damage else {268272
if *needs_redraw {269273
host.surface.frame(&host.qh, ());@@ -26,6 +26,8 @@ use smithay::wayland::compositor::{CompositorClientState, CompositorState, with_
2626
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufState};2727
use smithay::wayland::idle_inhibit::IdleInhibitManagerState;2828
use smithay::wayland::idle_notify::IdleNotifierState;29
+use smithay::wayland::image_capture_source::{ImageCaptureSourceState, OutputCaptureSourceState};30
+use smithay::wayland::image_copy_capture::ImageCopyCaptureState;2931
use smithay::wayland::output::OutputManagerState;3032
use smithay::wayland::selection::data_device::DataDeviceState;3133
use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;@@ -88,6 +90,9 @@ pub struct State {
8890
pub placed_by_rule: HashSet<WindowId>,8991
/// A layer surface given the keyboard by a click, like a launcher.9092
pub layer_focus: Option<WlSurface>,93
+ pub captures: crate::capture::Captures,94
+ /// The render node clients get dmabufs for, known on bare metal.95
+ pub render_node: Option<smithay::backend::drm::DrmNode>,9196
pub lock: crate::lock::Lock,9297
pub inhibitors: HashSet<WlSurface>,9398
pub inhibited: bool,@@ -96,6 +101,9 @@ pub struct State {
96101
pub compositor_state: CompositorState,97102
pub xdg_shell_state: XdgShellState,98103
pub layer_shell_state: WlrLayerShellState,104
+ pub _image_capture_source_state: ImageCaptureSourceState,105
+ pub output_capture_source_state: OutputCaptureSourceState,106
+ pub image_copy_capture_state: ImageCopyCaptureState,99107
pub activation_state: XdgActivationState,100108
pub primary_selection_state: PrimarySelectionState,101109
pub wlr_data_control_state: WlrDataControlState,@@ -197,6 +205,8 @@ impl State {
197205
awaiting: HashSet::new(),198206
placed_by_rule: HashSet::new(),199207
layer_focus: None,208
+ captures: Default::default(),209
+ render_node: None,200210
lock: Default::default(),201211
inhibitors: HashSet::new(),202212
inhibited: false,@@ -204,6 +214,9 @@ impl State {
204214
compositor_state: CompositorState::new::<State>(&dh),205215
xdg_shell_state: XdgShellState::new::<State>(&dh),206216
layer_shell_state: WlrLayerShellState::new::<State>(&dh),217
+ _image_capture_source_state: ImageCaptureSourceState::new(),218
+ output_capture_source_state: OutputCaptureSourceState::new::<State>(&dh),219
+ image_copy_capture_state: ImageCopyCaptureState::new::<State>(&dh),207220
activation_state: XdgActivationState::new::<State>(&dh),208221
wlr_data_control_state: WlrDataControlState::new::<State, _>(209222
&dh,@@ -238,6 +251,7 @@ impl State {
238251
crate::nested::render(self);239252
crate::drm::render(self);240253
self.confirm_lock();254
+ self.complete_captures();241255
crate::ipc::broadcast(self);242256
self.publish_workspaces();243257
let _ = self.display.flush_clients();