aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flake.lock30
-rw-r--r--src/handlers/mod.rs15
-rw-r--r--src/niri.rs94
-rw-r--r--src/protocols/mod.rs1
-rw-r--r--src/protocols/screencopy.rs386
-rw-r--r--src/render_helpers/mod.rs102
-rw-r--r--src/render_helpers/offscreen.rs1
7 files changed, 566 insertions, 63 deletions
diff --git a/flake.lock b/flake.lock
index 68b04c86..ee992f09 100644
--- a/flake.lock
+++ b/flake.lock
@@ -7,11 +7,11 @@
]
},
"locked": {
- "lastModified": 1707685877,
- "narHash": "sha256-XoXRS+5whotelr1rHiZle5t5hDg9kpguS5yk8c8qzOc=",
+ "lastModified": 1709610799,
+ "narHash": "sha256-5jfLQx0U9hXbi2skYMGodDJkIgffrjIOgMRjZqms2QE=",
"owner": "ipetkov",
"repo": "crane",
- "rev": "2c653e4478476a52c6aa3ac0495e4dea7449ea0e",
+ "rev": "81c393c776d5379c030607866afef6406ca1be57",
"type": "github"
},
"original": {
@@ -28,11 +28,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
- "lastModified": 1706768574,
- "narHash": "sha256-4o6TMpzBHO659EiJTzd/EGQGUDdbgwKwhqf3u6b23U8=",
+ "lastModified": 1709274179,
+ "narHash": "sha256-O6EC6QELBLHzhdzBOJj0chx8AOcd4nDRECIagfT5Nd0=",
"owner": "nix-community",
"repo": "fenix",
- "rev": "668102037129923cd0fc239d864fce71eabdc6a3",
+ "rev": "4be608f4f81d351aacca01b21ffd91028c23cc22",
"type": "github"
},
"original": {
@@ -47,11 +47,11 @@
"systems": "systems"
},
"locked": {
- "lastModified": 1705309234,
- "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
+ "lastModified": 1709126324,
+ "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
+ "rev": "d465f4819400de7c8d874d50b982301f28a84605",
"type": "github"
},
"original": {
@@ -77,11 +77,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1707619277,
- "narHash": "sha256-vKnYD5GMQbNQyyQm4wRlqi+5n0/F1hnvqSQgaBy4BqY=",
+ "lastModified": 1709386671,
+ "narHash": "sha256-VPqfBnIJ+cfa78pd4Y5Cr6sOWVW8GYHRVucxJGmRf8Q=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "f3a93440fbfff8a74350f4791332a19282cc6dc8",
+ "rev": "fa9a51752f1b5de583ad5213eb621be071806663",
"type": "github"
},
"original": {
@@ -103,11 +103,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
- "lastModified": 1706735270,
- "narHash": "sha256-IJk+UitcJsxzMQWm9pa1ZbJBriQ4ginXOlPyVq+Cu40=",
+ "lastModified": 1709219524,
+ "narHash": "sha256-8HHRXm4kYQLdUohNDUuCC3Rge7fXrtkjBUf0GERxrkM=",
"owner": "rust-lang",
"repo": "rust-analyzer",
- "rev": "42cb1a2bd79af321b0cc503d2960b73f34e2f92b",
+ "rev": "9efa23c4dacee88b93540632eb3d88c5dfebfe17",
"type": "github"
},
"original": {
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index 8e92ea6e..39173471 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -54,12 +54,13 @@ use smithay::{
delegate_text_input_manager, delegate_virtual_keyboard_manager,
};
-use crate::delegate_foreign_toplevel;
use crate::niri::{ClientState, State};
use crate::protocols::foreign_toplevel::{
self, ForeignToplevelHandler, ForeignToplevelManagerState,
};
+use crate::protocols::screencopy::{Screencopy, ScreencopyHandler};
use crate::utils::output_size;
+use crate::{delegate_foreign_toplevel, delegate_screencopy};
impl SeatHandler for State {
type KeyboardFocus = WlSurface;
@@ -380,6 +381,18 @@ impl ForeignToplevelHandler for State {
}
delegate_foreign_toplevel!(State);
+impl ScreencopyHandler for State {
+ fn frame(&mut self, screencopy: Screencopy) {
+ if let Err(err) = self
+ .niri
+ .render_for_screencopy(&mut self.backend, screencopy)
+ {
+ warn!("error rendering for screencopy: {err:?}");
+ }
+ }
+}
+delegate_screencopy!(State);
+
impl DrmLeaseHandler for State {
fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState {
&mut self
diff --git a/src/niri.rs b/src/niri.rs
index 2d1c80ff..c670f0ba 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
use std::{env, mem, thread};
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode;
-use anyhow::Context;
+use anyhow::{ensure, Context};
use calloop::futures::Scheduler;
use niri_config::{Config, TrackLayout};
use smithay::backend::allocator::Fourcc;
@@ -18,7 +18,9 @@ use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRen
use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
};
-use smithay::backend::renderer::element::utils::{select_dmabuf_feedback, RelocateRenderElement};
+use smithay::backend::renderer::element::utils::{
+ select_dmabuf_feedback, Relocate, RelocateRenderElement,
+};
use smithay::backend::renderer::element::{
default_primary_scanout_output_compare, AsRenderElements, Id, Kind, PrimaryScanoutOutput,
RenderElementStates,
@@ -97,9 +99,10 @@ use crate::input::{apply_libinput_settings, TabletData};
use crate::ipc::server::IpcServer;
use crate::layout::{Layout, MonitorRenderElement};
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
+use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
use crate::pw_utils::{Cast, PipeWire};
use crate::render_helpers::renderer::NiriRenderer;
-use crate::render_helpers::{render_to_texture, render_to_vec};
+use crate::render_helpers::{render_to_shm, render_to_texture, render_to_vec};
use crate::ui::config_error_notification::ConfigErrorNotification;
use crate::ui::exit_confirm_dialog::ExitConfirmDialog;
use crate::ui::hotkey_overlay::HotkeyOverlay;
@@ -154,6 +157,7 @@ pub struct Niri {
pub layer_shell_state: WlrLayerShellState,
pub session_lock_state: SessionLockManagerState,
pub foreign_toplevel_state: ForeignToplevelManagerState,
+ pub screencopy_state: ScreencopyManagerState,
pub shm_state: ShmState,
pub output_manager_state: OutputManagerState,
pub dmabuf_state: DmabufState,
@@ -910,6 +914,9 @@ impl Niri {
ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| {
!client.get_data::<ClientState>().unwrap().restricted
});
+ let screencopy_state = ScreencopyManagerState::new::<State, _>(&display_handle, |client| {
+ !client.get_data::<ClientState>().unwrap().restricted
+ });
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
seat.add_keyboard(
@@ -1030,6 +1037,7 @@ impl Niri {
layer_shell_state,
session_lock_state,
foreign_toplevel_state,
+ screencopy_state,
text_input_state,
input_method_state,
virtual_keyboard_state,
@@ -2166,13 +2174,11 @@ impl Niri {
// to err on the safe side.
self.send_frame_callbacks(output);
- // Render and send to PipeWire screencast streams.
- #[cfg(feature = "xdp-gnome-screencast")]
- {
- backend.with_primary_renderer(|renderer| {
- self.render_for_screen_cast(renderer, output, target_presentation_time);
- });
- }
+ backend.with_primary_renderer(|renderer| {
+ // Render and send to PipeWire screencast streams.
+ #[cfg(feature = "xdp-gnome-screencast")]
+ self.render_for_screen_cast(renderer, output, target_presentation_time);
+ });
}
pub fn update_primary_scanout_output(
@@ -2586,7 +2592,9 @@ impl Niri {
.get_or_insert_with(|| self.render::<GlesRenderer>(renderer, output, true));
let elements = elements.iter().rev();
- if let Err(err) = render_to_dmabuf(renderer, dmabuf, size, scale, elements) {
+ if let Err(err) =
+ render_to_dmabuf(renderer, dmabuf, size, scale, Transform::Normal, elements)
+ {
warn!("error rendering to dmabuf: {err:?}");
continue;
}
@@ -2606,6 +2614,42 @@ impl Niri {
}
}
+ pub fn render_for_screencopy(
+ &mut self,
+ backend: &mut Backend,
+ screencopy: Screencopy,
+ ) -> anyhow::Result<()> {
+ let output = screencopy.output().clone();
+ ensure!(self.output_state.contains_key(&output), "output is missing");
+
+ backend
+ .with_primary_renderer(move |renderer| {
+ let elements = self
+ .render(renderer, &output, screencopy.overlay_cursor())
+ .into_iter()
+ .rev();
+
+ let region_loc = screencopy.region_loc();
+ let elements = elements.map(|element| {
+ RelocateRenderElement::from_element(
+ element,
+ region_loc.upscale(-1),
+ Relocate::Relative,
+ )
+ });
+
+ let scale = output.current_scale().fractional_scale().into();
+ let transform = output.current_transform();
+ render_to_shm(renderer, screencopy.buffer(), scale, transform, elements)
+ .context("error rendering to screencopy shm buffer: {err:?}")?;
+
+ screencopy.submit(false);
+
+ Ok(())
+ })
+ .context("primary renderer is missing")?
+ }
+
#[cfg(feature = "xdp-gnome-screencast")]
fn stop_cast(&mut self, session_id: usize) {
let _span = tracy_client::span!("Niri::stop_cast");
@@ -2665,7 +2709,14 @@ impl Niri {
let elements = self.render::<GlesRenderer>(renderer, &output, true);
let elements = elements.iter().rev();
- let res = render_to_texture(renderer, size, scale, Fourcc::Abgr8888, elements);
+ let res = render_to_texture(
+ renderer,
+ size,
+ scale,
+ Transform::Normal,
+ Fourcc::Abgr8888,
+ elements,
+ );
let screenshot = match res {
Ok((texture, _)) => texture,
Err(err) => {
@@ -2695,7 +2746,14 @@ impl Niri {
let scale = Scale::from(output.current_scale().fractional_scale());
let elements = self.render::<GlesRenderer>(renderer, output, true);
let elements = elements.iter().rev();
- let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, elements)?;
+ let pixels = render_to_vec(
+ renderer,
+ size,
+ scale,
+ Transform::Normal,
+ Fourcc::Abgr8888,
+ elements,
+ )?;
self.save_screenshot(size, pixels)
.context("error saving screenshot")
@@ -2721,7 +2779,14 @@ impl Niri {
1.,
);
let elements = elements.iter().rev();
- let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, elements)?;
+ let pixels = render_to_vec(
+ renderer,
+ size,
+ scale,
+ Transform::Normal,
+ Fourcc::Abgr8888,
+ elements,
+ )?;
self.save_screenshot(size, pixels)
.context("error saving screenshot")
@@ -2824,6 +2889,7 @@ impl Niri {
renderer,
size,
Scale::from(f64::from(output_scale)),
+ Transform::Normal,
Fourcc::Abgr8888,
elements,
)?;
diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs
index 06d80157..1e3ef42f 100644
--- a/src/protocols/mod.rs
+++ b/src/protocols/mod.rs
@@ -1 +1,2 @@
pub mod foreign_toplevel;
+pub mod screencopy;
diff --git a/src/protocols/screencopy.rs b/src/protocols/screencopy.rs
new file mode 100644
index 00000000..808be4cb
--- /dev/null
+++ b/src/protocols/screencopy.rs
@@ -0,0 +1,386 @@
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use std::time::UNIX_EPOCH;
+
+use smithay::output::Output;
+use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_frame_v1::{
+ Flags, ZwlrScreencopyFrameV1,
+};
+use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1;
+use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::{
+ zwlr_screencopy_frame_v1, zwlr_screencopy_manager_v1,
+};
+use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
+use smithay::reexports::wayland_server::protocol::wl_shm;
+use smithay::reexports::wayland_server::{
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
+};
+use smithay::utils::{Physical, Point, Rectangle, Size};
+use smithay::wayland::shm;
+
+// We do not support copy_with_damage() semantics yet.
+const VERSION: u32 = 1;
+
+pub struct ScreencopyManagerState;
+
+pub struct ScreencopyManagerGlobalData {
+ filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
+}
+
+impl ScreencopyManagerState {
+ pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
+ where
+ D: GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData>,
+ D: Dispatch<ZwlrScreencopyManagerV1, ()>,
+ D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>,
+ D: ScreencopyHandler,
+ D: 'static,
+ F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
+ {
+ let global_data = ScreencopyManagerGlobalData {
+ filter: Box::new(filter),
+ };
+ display.create_global::<D, ZwlrScreencopyManagerV1, _>(VERSION, global_data);
+
+ Self
+ }
+}
+
+impl<D> GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData, D>
+ for ScreencopyManagerState
+where
+ D: GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData>,
+ D: Dispatch<ZwlrScreencopyManagerV1, ()>,
+ D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>,
+ D: ScreencopyHandler,
+ D: 'static,
+{
+ fn bind(
+ _state: &mut D,
+ _display: &DisplayHandle,
+ _client: &Client,
+ manager: New<ZwlrScreencopyManagerV1>,
+ _manager_state: &ScreencopyManagerGlobalData,
+ data_init: &mut DataInit<'_, D>,
+ ) {
+ data_init.init(manager, ());
+ }
+
+ fn can_view(client: Client, global_data: &ScreencopyManagerGlobalData) -> bool {
+ (global_data.filter)(&client)
+ }
+}
+
+impl<D> Dispatch<ZwlrScreencopyManagerV1, (), D> for ScreencopyManagerState
+where
+ D: GlobalDispatch<ZwlrScreencopyManagerV1, ScreencopyManagerGlobalData>,
+ D: Dispatch<ZwlrScreencopyManagerV1, ()>,
+ D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>,
+ D: ScreencopyHandler,
+ D: 'static,
+{
+ fn request(
+ _state: &mut D,
+ _client: &Client,
+ _manager: &ZwlrScreencopyManagerV1,
+ request: zwlr_screencopy_manager_v1::Request,
+ _data: &(),
+ _display: &DisplayHandle,
+ data_init: &mut DataInit<'_, D>,
+ ) {
+ let (frame, overlay_cursor, buffer_size, region_loc, output) = match request {
+ zwlr_screencopy_manager_v1::Request::CaptureOutput {
+ frame,
+ overlay_cursor,
+ output,
+ } => {
+ let output = Output::from_resource(&output).unwrap();
+ let buffer_size = output.current_mode().unwrap().size;
+ let region_loc = Point::from((0, 0));
+
+ (frame, overlay_cursor, buffer_size, region_loc, output)
+ }
+ zwlr_screencopy_manager_v1::Request::CaptureOutputRegion {
+ frame,
+ overlay_cursor,
+ x,
+ y,
+ width,
+ height,
+ output,
+ } => {
+ if width <= 0 || height <= 0 {
+ trace!("screencopy client requested invalid sized region");
+ let frame = data_init.init(frame, ScreencopyFrameState::Failed);
+ frame.failed();
+ return;
+ }
+
+ let output = Output::from_resource(&output).unwrap();
+ let output_transform = output.current_transform();
+ let output_physical_size =
+ output_transform.transform_size(output.current_mode().unwrap().size);
+ let output_rect = Rectangle::from_loc_and_size((0, 0), output_physical_size);
+
+ let rect = Rectangle::from_loc_and_size((x, y), (width, height));
+
+ let output_scale = output.current_scale().integer_scale();
+ let physical_rect = rect.to_physical(output_scale);
+
+ // Clamp captured region to the output.
+ let Some(clamped_rect) = physical_rect.intersection(output_rect) else {
+ trace!("screencopy client requested region outside of output");
+ let frame = data_init.init(frame, ScreencopyFrameState::Failed);
+ frame.failed();
+ return;
+ };
+
+ let untransformed_rect = output_transform
+ .invert()
+ .transform_rect_in(clamped_rect, &output_physical_size);
+
+ (
+ frame,
+ overlay_cursor,
+ untransformed_rect.size,
+ clamped_rect.loc,
+ output,
+ )
+ }
+ zwlr_screencopy_manager_v1::Request::Destroy => return,
+ _ => unreachable!(),
+ };
+
+ // Create the frame.
+ let overlay_cursor = overlay_cursor != 0;
+ let info = ScreencopyFrameInfo {
+ output,
+ overlay_cursor,
+ buffer_size,
+ region_loc,
+ };
+ let frame = data_init.init(
+ frame,
+ ScreencopyFrameState::Pending {
+ info,
+ copied: Arc::new(AtomicBool::new(false)),
+ },
+ );
+
+ // Send desired SHM buffer parameters.
+ frame.buffer(
+ wl_shm::Format::Argb8888,
+ buffer_size.w as u32,
+ buffer_size.h as u32,
+ buffer_size.w as u32 * 4,
+ );
+
+ // if manager.version() >= 3 {
+ // // Send desired DMA buffer parameters.
+ // frame.linux_dmabuf(
+ // Fourcc::Argb8888 as u32,
+ // buffer_size.w as u32,
+ // buffer_size.h as u32,
+ // );
+ //
+ // // Notify client that all supported buffers were enumerated.
+ // frame.buffer_done();
+ // }
+ }
+}
+
+/// Handler trait for wlr-screencopy.
+pub trait ScreencopyHandler {
+ /// Handle new screencopy request.
+ fn frame(&mut self, frame: Screencopy);
+}
+
+#[allow(missing_docs)]
+#[macro_export]
+macro_rules! delegate_screencopy {
+ ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
+ smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1: $crate::protocols::screencopy::ScreencopyManagerGlobalData
+ ] => $crate::protocols::screencopy::ScreencopyManagerState);
+
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1: ()
+ ] => $crate::protocols::screencopy::ScreencopyManagerState);
+
+ smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
+ smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1: $crate::protocols::screencopy::ScreencopyFrameState
+ ] => $crate::protocols::screencopy::ScreencopyManagerState);
+ };
+}
+
+#[derive(Clone)]
+pub struct ScreencopyFrameInfo {
+ output: Output,
+ buffer_size: Size<i32, Physical>,
+ region_loc: Point<i32, Physical>,
+ overlay_cursor: bool,
+}
+
+pub enum ScreencopyFrameState {
+ Failed,
+ Pending {
+ info: ScreencopyFrameInfo,
+ copied: Arc<AtomicBool>,
+ },
+}
+
+impl<D> Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState, D> for ScreencopyManagerState
+where
+ D: Dispatch<ZwlrScreencopyFrameV1, ScreencopyFrameState>,
+ D: ScreencopyHandler,
+ D: 'static,
+{
+ fn request(
+ state: &mut D,
+ _client: &Client,
+ frame: &ZwlrScreencopyFrameV1,
+ request: zwlr_screencopy_frame_v1::Request,
+ data: &ScreencopyFrameState,
+ _display: &DisplayHandle,
+ _data_init: &mut DataInit<'_, D>,
+ ) {
+ if matches!(request, zwlr_screencopy_frame_v1::Request::Destroy) {
+ return;
+ }
+
+ let (info, copied) = match data {
+ ScreencopyFrameState::Failed => return,
+ ScreencopyFrameState::Pending { info, copied } => (info, copied),
+ };
+
+ if copied.load(Ordering::SeqCst) {
+ frame.post_error(
+ zwlr_screencopy_frame_v1::Error::AlreadyUsed,
+ "copy was already requested",
+ );
+ return;
+ }
+
+ let (buffer, with_damage) = match request {
+ zwlr_screencopy_frame_v1::Request::Copy { buffer } => (buffer, false),
+ // zwlr_screencopy_frame_v1::Request::CopyWithDamage { buffer } => (buffer, true),
+ _ => unreachable!(),
+ };
+
+ if !shm::with_buffer_contents(&buffer, |_buf, shm_len, buffer_data| {
+ buffer_data.format == wl_shm::Format::Argb8888
+ && buffer_data.stride == info.buffer_size.w * 4
+ && buffer_data.height == info.buffer_size.h
+ && shm_len as i32 == buffer_data.stride * buffer_data.height
+ })
+ .unwrap_or(false)
+ {
+ frame.post_error(
+ zwlr_screencopy_frame_v1::Error::InvalidBuffer,
+ "invalid buffer",
+ );
+ return;
+ }
+
+ copied.store(true, Ordering::SeqCst);
+
+ state.frame(Screencopy {
+ with_damage,
+ buffer,
+ frame: frame.clone(),
+ info: info.clone(),
+ submitted: false,
+ });
+ }
+}
+
+/// Screencopy frame.
+pub struct Screencopy {
+ info: ScreencopyFrameInfo,
+ frame: ZwlrScreencopyFrameV1,
+ #[allow(unused)]
+ with_damage: bool,
+ buffer: WlBuffer,
+ submitted: bool,
+}
+
+impl Drop for Screencopy {
+ fn drop(&mut self) {
+ if !self.submitted {
+ self.frame.failed();
+ }
+ }
+}
+
+impl Screencopy {
+ /// Get the target buffer to copy to.
+ pub fn buffer(&self) -> &WlBuffer {
+ &self.buffer
+ }
+
+ pub fn region_loc(&self) -> Point<i32, Physical> {
+ self.info.region_loc
+ }
+
+ pub fn buffer_size(&self) -> Size<i32, Physical> {
+ self.info.buffer_size
+ }
+
+ pub fn output(&self) -> &Output {
+ &self.info.output
+ }
+
+ pub fn overlay_cursor(&self) -> bool {
+ self.info.overlay_cursor
+ }
+
+ // pub fn damage(&mut self, damage: &[Rectangle<i32, Physical>]) {
+ // assert!(self.with_damage);
+ //
+ // for Rectangle { loc, size } in damage {
+ // self.frame
+ // .damage(loc.x as u32, loc.y as u32, size.w as u32, size.h as u32);
+ // }
+ // }
+
+ /// Submit the copied content.
+ pub fn submit(mut self, y_invert: bool) {
+ // Notify client that buffer is ordinary.
+ self.frame.flags(if y_invert {
+ Flags::YInvert
+ } else {
+ Flags::empty()
+ });
+
+ // Notify client about successful copy.
+ let time = UNIX_EPOCH.elapsed().unwrap();
+ let tv_sec_hi = (time.as_secs() >> 32) as u32;
+ let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32;
+ let tv_nsec = time.subsec_nanos();
+ self.frame.ready(tv_sec_hi, tv_sec_lo, tv_nsec);
+
+ // Mark frame as submitted to ensure destructor isn't run.
+ self.submitted = true;
+ }
+
+ // pub fn submit_after_sync<T>(
+ // self,
+ // y_invert: bool,
+ // sync_point: Option<OwnedFd>,
+ // event_loop: &LoopHandle<'_, T>,
+ // ) {
+ // match sync_point {
+ // None => self.submit(y_invert),
+ // Some(sync_fd) => {
+ // let source = Generic::new(sync_fd, Interest::READ, Mode::OneShot);
+ // let mut screencopy = Some(self);
+ // event_loop
+ // .insert_source(source, move |_, _, _| {
+ // screencopy.take().unwrap().submit(y_invert);
+ // Ok(PostAction::Remove)
+ // })
+ // .unwrap();
+ // }
+ // }
+ // }
+}
diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs
index 7d7ea9c1..02ef2853 100644
--- a/src/render_helpers/mod.rs
+++ b/src/render_helpers/mod.rs
@@ -1,10 +1,15 @@
-use anyhow::Context;
+use std::ptr;
+
+use anyhow::{ensure, Context};
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture};
use smithay::backend::renderer::sync::SyncPoint;
-use smithay::backend::renderer::{Bind, ExportMem, Frame, Offscreen, Renderer};
+use smithay::backend::renderer::{buffer_dimensions, Bind, ExportMem, Frame, Offscreen, Renderer};
+use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
+use smithay::reexports::wayland_server::protocol::wl_shm;
use smithay::utils::{Physical, Rectangle, Scale, Size, Transform};
+use smithay::wayland::shm;
pub mod gradient;
pub mod offscreen;
@@ -18,12 +23,12 @@ pub fn render_to_texture(
renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
scale: Scale<f64>,
+ transform: Transform,
fourcc: Fourcc,
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<(GlesTexture, SyncPoint)> {
let _span = tracy_client::span!();
- let output_rect = Rectangle::from_loc_and_size((0, 0), size);
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
let texture: GlesTexture = renderer
@@ -34,27 +39,7 @@ pub fn render_to_texture(
.bind(texture.clone())
.context("error binding texture")?;
- let mut frame = renderer
- .render(size, Transform::Normal)
- .context("error starting frame")?;
-
- frame
- .clear([0., 0., 0., 0.], &[output_rect])
- .context("error clearing")?;
-
- for element in elements {
- let src = element.src();
- let dst = element.geometry(scale);
-
- if let Some(mut damage) = output_rect.intersection(dst) {
- damage.loc -= dst.loc;
- element
- .draw(&mut frame, src, dst, &[damage])
- .context("error drawing element")?;
- }
- }
-
- let sync_point = frame.finish().context("error finishing frame")?;
+ let sync_point = render_elements(renderer, size, scale, transform, elements)?;
Ok((texture, sync_point))
}
@@ -62,12 +47,13 @@ pub fn render_and_download(
renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
scale: Scale<f64>,
+ transform: Transform,
fourcc: Fourcc,
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<GlesMapping> {
let _span = tracy_client::span!();
- let (_, sync_point) = render_to_texture(renderer, size, scale, fourcc, elements)?;
+ let (_, sync_point) = render_to_texture(renderer, size, scale, transform, fourcc, elements)?;
sync_point.wait();
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
@@ -81,13 +67,14 @@ pub fn render_to_vec(
renderer: &mut GlesRenderer,
size: Size<i32, Physical>,
scale: Scale<f64>,
+ transform: Transform,
fourcc: Fourcc,
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<Vec<u8>> {
let _span = tracy_client::span!();
- let mapping =
- render_and_download(renderer, size, scale, fourcc, elements).context("error rendering")?;
+ let mapping = render_and_download(renderer, size, scale, transform, fourcc, elements)
+ .context("error rendering")?;
let copy = renderer
.map_texture(&mapping)
.context("error mapping texture")?;
@@ -100,15 +87,66 @@ pub fn render_to_dmabuf(
dmabuf: smithay::backend::allocator::dmabuf::Dmabuf,
size: Size<i32, Physical>,
scale: Scale<f64>,
+ transform: Transform,
+ elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
+) -> anyhow::Result<SyncPoint> {
+ let _span = tracy_client::span!();
+ renderer.bind(dmabuf).context("error binding texture")?;
+ render_elements(renderer, size, scale, transform, elements)
+}
+
+pub fn render_to_shm(
+ renderer: &mut GlesRenderer,
+ buffer: &WlBuffer,
+ scale: Scale<f64>,
+ transform: Transform,
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
) -> anyhow::Result<()> {
let _span = tracy_client::span!();
- let output_rect = Rectangle::from_loc_and_size((0, 0), size);
+ let buffer_size = buffer_dimensions(buffer).context("error getting buffer dimensions")?;
+ let size = buffer_size.to_logical(1, Transform::Normal).to_physical(1);
+
+ let mapping =
+ render_and_download(renderer, size, scale, transform, Fourcc::Argb8888, elements)?;
+ let bytes = renderer
+ .map_texture(&mapping)
+ .context("error mapping texture")?;
+
+ shm::with_buffer_contents_mut(buffer, |shm_buffer, shm_len, buffer_data| {
+ ensure!(
+ // The buffer prefers pixels in little endian ...
+ buffer_data.format == wl_shm::Format::Argb8888
+ && buffer_data.stride == size.w * 4
+ && buffer_data.height == size.h
+ && shm_len as i32 == buffer_data.stride * buffer_data.height,
+ "invalid buffer format or size"
+ );
+
+ ensure!(bytes.len() == shm_len, "mapped buffer has wrong length");
+
+ unsafe {
+ let _span = tracy_client::span!("copy_nonoverlapping");
+ ptr::copy_nonoverlapping(bytes.as_ptr(), shm_buffer.cast(), shm_len);
+ }
+
+ Ok(())
+ })
+ .context("expected shm buffer, but didn't get one")?
+}
+
+fn render_elements(
+ renderer: &mut GlesRenderer,
+ size: Size<i32, Physical>,
+ scale: Scale<f64>,
+ transform: Transform,
+ elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
+) -> anyhow::Result<SyncPoint> {
+ let transform = transform.invert();
+ let output_rect = Rectangle::from_loc_and_size((0, 0), transform.transform_size(size));
- renderer.bind(dmabuf).context("error binding texture")?;
let mut frame = renderer
- .render(size, Transform::Normal)
+ .render(size, transform)
.context("error starting frame")?;
frame
@@ -127,7 +165,5 @@ pub fn render_to_dmabuf(
}
}
- let _sync_point = frame.finish().context("error finishing frame")?;
-
- Ok(())
+ frame.finish().context("error finishing frame")
}
diff --git a/src/render_helpers/offscreen.rs b/src/render_helpers/offscreen.rs
index 69b0be84..72c2471b 100644
--- a/src/render_helpers/offscreen.rs
+++ b/src/render_helpers/offscreen.rs
@@ -54,6 +54,7 @@ impl OffscreenRenderElement {
renderer,
geo.size,
Scale::from(scale as f64),
+ Transform::Normal,
Fourcc::Abgr8888,
elements,
) {