mirror of
https://github.com/YaLTeR/niri.git
synced 2024-10-03 23:43:26 +03:00
Implement wlr-screencopy v1 (#243)
* Implement wlr-screencopy * Finish the implementation Lots of changes, mainly to fix transform handling. Turns out, grim expects transformed buffers and untransforms them by itself using info from wl_output. This means that render helpers needed to learn how to actually render transformed buffers. Also, it meant that y_invert is no longer needed. Next, moved the rendering to the Screencopy frame handler. Turns out, copy() is more or less expected to return immediately, whereas copy_with_damage() is expected to wait until the next VBlank. At least that's the intent I parse reading the protocol. Finally, brought the version from 3 down to 1, because copy_with_damage() will need bigger changes. Grim still works, others not really, mainly because they bind v3 unnecessarily, even if they don't use the damage request. --------- Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
parent
1a784e6e66
commit
ca22e70cc4
30
flake.lock
30
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": {
|
||||
|
@ -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
|
||||
|
94
src/niri.rs
94
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,
|
||||
)?;
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod foreign_toplevel;
|
||||
pub mod screencopy;
|
||||
|
386
src/protocols/screencopy.rs
Normal file
386
src/protocols/screencopy.rs
Normal file
@ -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();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ impl OffscreenRenderElement {
|
||||
renderer,
|
||||
geo.size,
|
||||
Scale::from(scale as f64),
|
||||
Transform::Normal,
|
||||
Fourcc::Abgr8888,
|
||||
elements,
|
||||
) {
|
||||
|
Loading…
Reference in New Issue
Block a user