1
0
Fork 0
forked from wry/wry

portal: implement window capture

This commit is contained in:
Julian Orth 2024-04-19 12:12:49 +02:00
parent f0600917ff
commit 4e10415e5c
27 changed files with 840 additions and 136 deletions

View file

@ -4,10 +4,17 @@ use {
client::{Client, ClientError},
globals::{Global, GlobalName},
ifs::{
jay_idle::JayIdle, jay_input::JayInput, jay_log_file::JayLogFile,
jay_output::JayOutput, jay_pointer::JayPointer, jay_randr::JayRandr,
jay_render_ctx::JayRenderCtx, jay_screencast::JayScreencast,
jay_screenshot::JayScreenshot, jay_seat_events::JaySeatEvents,
jay_idle::JayIdle,
jay_input::JayInput,
jay_log_file::JayLogFile,
jay_output::JayOutput,
jay_pointer::JayPointer,
jay_randr::JayRandr,
jay_render_ctx::JayRenderCtx,
jay_screencast::JayScreencast,
jay_screenshot::JayScreenshot,
jay_seat_events::JaySeatEvents,
jay_select_toplevel::{JaySelectToplevel, JayToplevelSelector},
jay_workspace_watcher::JayWorkspaceWatcher,
},
leaks::Tracker,
@ -18,7 +25,7 @@ use {
},
bstr::ByteSlice,
log::Level,
std::{ops::Deref, rc::Rc},
std::{cell::Cell, ops::Deref, rc::Rc},
thiserror::Error,
};
@ -77,13 +84,14 @@ pub struct Cap;
impl Cap {
pub const NONE: u16 = 0;
pub const WINDOW_CAPTURE: u16 = 1;
}
impl JayCompositor {
fn send_capabilities(&self) {
self.client.event(Capabilities {
self_id: self.id,
cap: &[Cap::NONE],
cap: &[Cap::NONE, Cap::WINDOW_CAPTURE],
});
}
@ -336,6 +344,24 @@ impl JayCompositorRequestHandler for JayCompositor {
self.client.add_client_obj(&sc)?;
Ok(())
}
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let seat = self.client.lookup(req.seat)?;
let obj = Rc::new(JaySelectToplevel {
id: req.id,
client: self.client.clone(),
tracker: Default::default(),
destroyed: Cell::new(false),
});
track!(self.client, obj);
self.client.add_client_obj(&obj)?;
let selector = JayToplevelSelector {
tl: Default::default(),
jst: obj.clone(),
};
seat.global.select_toplevel(selector);
Ok(())
}
}
object_base! {

View file

@ -3,12 +3,17 @@ use {
client::{Client, ClientError},
format::XRGB8888,
gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture},
ifs::jay_output::JayOutput,
ifs::{jay_output::JayOutput, jay_toplevel::JayToplevel},
leaks::Tracker,
object::{Object, Version},
tree::{OutputNode, WorkspaceNodeId},
scale::Scale,
state::State,
tree::{OutputNode, ToplevelNode, WorkspaceNodeId},
utils::{
clonecell::CloneCell, errorfmt::ErrorFmt, numcell::NumCell, option_ext::OptionExt,
clonecell::{CloneCell, UnsafeCellCloneSafe},
errorfmt::ErrorFmt,
numcell::NumCell,
option_ext::OptionExt,
},
video::{
dmabuf::DmaBuf,
@ -19,6 +24,7 @@ use {
},
ahash::AHashSet,
indexmap::{indexset, IndexSet},
jay_config::video::Transform,
once_cell::sync::Lazy,
std::{
cell::{Cell, RefCell},
@ -28,6 +34,28 @@ use {
thiserror::Error,
};
pub async fn perform_toplevel_screencasts(state: Rc<State>) {
loop {
let screencast = state.pending_toplevel_screencasts.pop().await;
screencast.perform_toplevel_screencast();
}
}
pub async fn perform_screencast_realloc(state: Rc<State>) {
loop {
let screencast = state.pending_toplevel_screencast_reallocs.pop().await;
screencast.realloc_scheduled.set(false);
match state.render_ctx.get() {
None => screencast.do_destroy(),
Some(ctx) => {
if let Err(e) = screencast.realloc(&ctx) {
screencast.client.error(e);
}
}
}
}
}
pub struct JayScreencast {
pub id: JayScreencastId,
pub client: Rc<Client>,
@ -38,20 +66,35 @@ pub struct JayScreencast {
buffers_acked: Cell<bool>,
buffers: RefCell<Vec<ScreencastBuffer>>,
missed_frame: Cell<bool>,
output: CloneCell<Option<Rc<OutputNode>>>,
target: CloneCell<Option<Target>>,
destroyed: Cell<bool>,
running: Cell<bool>,
show_all: Cell<bool>,
show_workspaces: RefCell<AHashSet<WorkspaceNodeId>>,
linear: Cell<bool>,
pending: Pending,
need_realloc: Cell<bool>,
realloc_scheduled: Cell<bool>,
}
#[derive(Clone)]
enum Target {
Output(Rc<OutputNode>),
Toplevel(Rc<dyn ToplevelNode>),
}
unsafe impl UnsafeCellCloneSafe for Target {}
enum PendingTarget {
Output(Rc<JayOutput>),
Toplevel(Rc<JayToplevel>),
}
#[derive(Default)]
struct Pending {
linear: Cell<Option<bool>>,
running: Cell<Option<bool>>,
output: Cell<Option<Option<Rc<JayOutput>>>>,
target: Cell<Option<Option<PendingTarget>>>,
show_all: Cell<Option<bool>>,
show_workspaces: RefCell<Option<AHashSet<WorkspaceNodeId>>>,
}
@ -71,19 +114,80 @@ impl JayScreencast {
config_serial: Default::default(),
config_acked: Cell::new(true),
buffers_serial: Default::default(),
buffers_acked: Cell::new(false),
buffers_acked: Cell::new(true),
buffers: Default::default(),
missed_frame: Cell::new(false),
output: Default::default(),
target: Default::default(),
destroyed: Cell::new(false),
running: Cell::new(false),
show_all: Cell::new(false),
show_workspaces: Default::default(),
linear: Cell::new(false),
pending: Default::default(),
need_realloc: Cell::new(false),
realloc_scheduled: Cell::new(false),
}
}
pub fn schedule_toplevel_screencast(self: &Rc<Self>) {
if !self.running.get() {
return;
}
self.client
.state
.pending_toplevel_screencasts
.push(self.clone());
}
fn perform_toplevel_screencast(&self) {
if self.destroyed.get() || !self.running.get() {
return;
}
let Some(target) = self.target.get() else {
return;
};
let Target::Toplevel(tl) = target else {
log::warn!("Tried to perform window screencast for output screencast");
return;
};
let scale = match tl.tl_data().workspace.get() {
None => Scale::default(),
Some(w) => w.output.get().global.persistent.scale.get(),
};
let mut buffer = self.buffers.borrow_mut();
for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() {
if buffer.free {
let res = buffer.fb.render_node(
tl.tl_as_node(),
&self.client.state,
Some(tl.node_absolute_position()),
None,
scale,
true,
true,
false,
Transform::None,
);
match res {
Ok(_) => {
self.client.event(Ready {
self_id: self.id,
idx: idx as _,
});
buffer.free = false;
return;
}
Err(e) => {
log::error!("Could not perform window copy: {}", ErrorFmt(e));
break;
}
}
}
}
self.missed_frame.set(true);
self.client.event(MissedFrame { self_id: self.id })
}
fn send_buffers(&self) {
self.buffers_acked.set(false);
let serial = self.buffers_serial.fetch_add(1) + 1;
@ -115,11 +219,13 @@ impl JayScreencast {
fn send_config(&self) {
self.config_acked.set(false);
let serial = self.config_serial.fetch_add(1) + 1;
if let Some(output) = self.output.get() {
self.client.event(ConfigOutput {
self_id: self.id,
linear_id: output.id.raw(),
});
if let Some(target) = self.target.get() {
if let Target::Output(output) = target {
self.client.event(ConfigOutput {
self_id: self.id,
linear_id: output.id.raw(),
});
}
}
self.client.event(ConfigAllowAllWorkspaces {
self_id: self.id,
@ -200,10 +306,21 @@ impl JayScreencast {
}
fn detach(&self) {
if let Some(output) = self.output.take() {
output.screencasts.remove(&(self.client.id, self.id));
if output.screencasts.is_empty() {
output.state.damage();
if let Some(target) = self.target.take() {
match target {
Target::Output(output) => {
output.screencasts.remove(&(self.client.id, self.id));
if output.screencasts.is_empty() {
output.state.damage();
}
}
Target::Toplevel(tl) => {
let data = tl.tl_data();
data.jay_screencasts.remove(&(self.client.id, self.id));
if data.jay_screencasts.is_empty() {
self.client.state.damage();
}
}
}
}
}
@ -214,17 +331,39 @@ impl JayScreencast {
self.client.event(Destroyed { self_id: self.id });
}
pub fn realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
pub fn schedule_realloc(self: &Rc<Self>) {
self.need_realloc.set(true);
if !self.realloc_scheduled.replace(true) {
self.client
.state
.pending_toplevel_screencast_reallocs
.push(self.clone());
}
}
fn realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
if !self.destroyed.get() && self.buffers_acked.get() {
self.do_realloc(ctx)
} else {
Ok(())
}
}
fn do_realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
self.need_realloc.set(false);
let mut buffers = vec![];
let formats = ctx.formats();
let format = match formats.get(&XRGB8888.drm) {
Some(f) => f,
_ => return Err(JayScreencastError::XRGB8888),
};
if let Some(output) = self.output.get() {
let (width, height) = output.global.pixel_size();
if let Some(target) = self.target.get() {
let (width, height) = target_size(Some(&target));
let num = 3;
for _ in 0..num {
if width == 0 || height == 0 {
continue;
}
let mut usage = GBM_BO_USE_RENDERING;
let modifiers = match self.linear.get() {
true if format.write_modifiers.contains(&LINEAR_MODIFIER) => {
@ -267,8 +406,11 @@ impl JayScreencast {
}
fn damage(&self) {
if let Some(output) = self.output.get() {
output.global.connector.connector.damage();
if let Some(target) = self.target.get() {
match target {
Target::Output(o) => o.global.connector.connector.damage(),
Target::Toplevel(_) => self.client.state.damage(),
}
}
}
}
@ -284,14 +426,14 @@ impl JayScreencastRequestHandler for JayScreencast {
fn set_output(&self, req: SetOutput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let output = if req.output.is_some() {
Some(self.client.lookup(req.output)?)
Some(PendingTarget::Output(self.client.lookup(req.output)?))
} else {
None
};
if self.destroyed.get() || !self.config_acked.get() {
return Ok(());
}
self.pending.output.set(Some(output));
self.pending.target.set(Some(output));
Ok(())
}
@ -362,19 +504,42 @@ impl JayScreencastRequestHandler for JayScreencast {
let mut need_realloc = false;
if let Some(output) = self.pending.output.take() {
let output = output.and_then(|o| o.output.get());
if output_size(&output) != output_size(&self.output.get()) {
if let Some(target) = self.pending.target.take() {
self.detach();
let mut new_target = None;
if let Some(new) = target {
match new {
PendingTarget::Output(o) => {
let Some(o) = o.output.get() else {
self.do_destroy();
return Ok(());
};
if o.screencasts.is_empty() {
o.state.damage();
}
o.screencasts.set((self.client.id, self.id), slf.clone());
new_target = Some(Target::Output(o));
}
PendingTarget::Toplevel(t) => {
if t.destroyed.get() {
self.do_destroy();
return Ok(());
}
let t = t.toplevel.clone();
let data = t.tl_data();
if data.jay_screencasts.is_empty() {
data.state.damage();
}
data.jay_screencasts
.set((self.client.id, self.id), slf.clone());
new_target = Some(Target::Toplevel(t));
}
}
}
if target_size(new_target.as_ref()) != target_size(self.target.get().as_ref()) {
need_realloc = true;
}
self.detach();
if let Some(new) = &output {
if new.screencasts.is_empty() {
new.state.damage();
}
new.screencasts.set((self.client.id, self.id), slf.clone());
}
self.output.set(output);
self.target.set(new_target);
}
if let Some(linear) = self.pending.linear.take() {
if self.linear.replace(linear) != linear {
@ -392,29 +557,21 @@ impl JayScreencastRequestHandler for JayScreencast {
}
if need_realloc {
let ctx = match self.client.state.render_ctx.get() {
Some(ctx) => ctx,
_ => {
self.do_destroy();
return Ok(());
}
};
if let Err(e) = self.realloc(&ctx) {
log::error!("Could not allocate buffers: {}", ErrorFmt(e));
self.do_destroy();
return Ok(());
}
slf.schedule_realloc();
}
Ok(())
}
fn ack_buffers(&self, req: AckBuffers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
fn ack_buffers(&self, req: AckBuffers, slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
if req.serial == self.buffers_serial.get() {
self.buffers_acked.set(true);
if self.need_realloc.get() {
slf.schedule_realloc();
}
}
Ok(())
}
@ -443,6 +600,19 @@ impl JayScreencastRequestHandler for JayScreencast {
}
Ok(())
}
fn set_toplevel(&self, req: SetToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let toplevel = if req.id.is_some() {
Some(PendingTarget::Toplevel(self.client.lookup(req.id)?))
} else {
None
};
if self.destroyed.get() || !self.config_acked.get() {
return Ok(());
}
self.pending.target.set(Some(toplevel));
Ok(())
}
}
object_base! {
@ -477,9 +647,19 @@ pub enum JayScreencastError {
}
efrom!(JayScreencastError, ClientError);
fn output_size(output: &Option<Rc<OutputNode>>) -> (i32, i32) {
match output {
Some(o) => o.global.pixel_size(),
_ => (0, 0),
fn target_size(target: Option<&Target>) -> (i32, i32) {
if let Some(target) = target {
match target {
Target::Output(o) => return o.global.pixel_size(),
Target::Toplevel(t) => {
let data = t.tl_data();
let (dw, dh) = data.desired_extents.get().size();
if let Some(ws) = data.workspace.get() {
let scale = ws.output.get().global.persistent.scale.get();
return scale.pixel_size(dw, dh);
};
}
}
}
(0, 0)
}

View file

@ -0,0 +1,100 @@
use {
crate::{
client::{Client, ClientError},
ifs::{jay_toplevel::JayToplevel, wl_seat::ToplevelSelector},
leaks::Tracker,
object::{Object, Version},
tree::ToplevelNode,
utils::clonecell::CloneCell,
wire::{jay_select_toplevel::*, JaySelectToplevelId, JayToplevelId},
},
std::{cell::Cell, rc::Rc},
thiserror::Error,
};
pub struct JaySelectToplevel {
pub id: JaySelectToplevelId,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub destroyed: Cell<bool>,
}
pub struct JayToplevelSelector {
pub tl: CloneCell<Option<Rc<dyn ToplevelNode>>>,
pub jst: Rc<JaySelectToplevel>,
}
impl ToplevelSelector for JayToplevelSelector {
fn set(&self, toplevel: Rc<dyn ToplevelNode>) {
self.tl.set(Some(toplevel));
}
}
impl Drop for JayToplevelSelector {
fn drop(&mut self) {
if self.jst.destroyed.get() {
return;
}
let id = match self.tl.take() {
None => JayToplevelId::NONE,
Some(toplevel) => {
let id = match self.jst.client.new_id() {
Ok(id) => id,
Err(e) => {
self.jst.client.error(e);
return;
}
};
let jtl = Rc::new(JayToplevel {
id,
client: self.jst.client.clone(),
tracker: Default::default(),
toplevel,
destroyed: Cell::new(false),
});
track!(self.jst.client, jtl);
self.jst.client.add_server_obj(&jtl);
jtl.toplevel
.tl_data()
.jay_toplevels
.set((jtl.client.id, jtl.id), jtl.clone());
jtl.id
}
};
self.jst.send_done(id);
let _ = self.jst.client.remove_obj(&*self.jst);
}
}
impl JaySelectToplevel {
fn send_done(&self, id: JayToplevelId) {
self.client.event(Done {
self_id: self.id,
id,
});
}
}
impl JaySelectToplevelRequestHandler for JaySelectToplevel {
type Error = JaySelectToplevelError;
}
object_base! {
self = JaySelectToplevel;
version = Version(1);
}
impl Object for JaySelectToplevel {
fn break_loops(&self) {
self.destroyed.set(true);
}
}
simple_add_obj!(JaySelectToplevel);
#[derive(Debug, Error)]
pub enum JaySelectToplevelError {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(JaySelectToplevelError, ClientError);

68
src/ifs/jay_toplevel.rs Normal file
View file

@ -0,0 +1,68 @@
use {
crate::{
client::{Client, ClientError},
leaks::Tracker,
object::{Object, Version},
tree::ToplevelNode,
wire::{jay_toplevel::*, JayToplevelId},
},
std::{cell::Cell, rc::Rc},
thiserror::Error,
};
pub struct JayToplevel {
pub id: JayToplevelId,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub toplevel: Rc<dyn ToplevelNode>,
pub destroyed: Cell<bool>,
}
impl JayToplevel {
fn detach(&self) {
self.destroyed.set(true);
self.toplevel
.tl_data()
.jay_toplevels
.remove(&(self.client.id, self.id));
}
pub fn destroy(&self) {
self.destroyed.set(true);
self.send_destroyed();
}
fn send_destroyed(&self) {
self.client.event(Destroyed { self_id: self.id });
}
}
impl JayToplevelRequestHandler for JayToplevel {
type Error = JayToplevelError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
}
object_base! {
self = JayToplevel;
version = Version(1);
}
impl Object for JayToplevel {
fn break_loops(&self) {
self.detach();
}
}
dedicated_add_obj!(JayToplevel, JayToplevelId, jay_toplevels);
#[derive(Debug, Error)]
pub enum JayToplevelError {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(JayToplevelError, ClientError);

View file

@ -38,8 +38,8 @@ use {
wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1,
wp_tearing_control_v1::WpTearingControlV1,
wp_viewport::WpViewport,
x_surface::XSurface,
xdg_surface::{PendingXdgSurfaceData, XdgSurfaceError},
x_surface::{xwindow::Xwindow, XSurface},
xdg_surface::{xdg_toplevel::XdgToplevel, PendingXdgSurfaceData, XdgSurfaceError},
zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error},
},
wp_content_type_v1::ContentType,
@ -51,8 +51,8 @@ use {
rect::{Rect, Region},
renderer::Renderer,
tree::{
FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase, OutputNode,
ToplevelNode,
ContainerNode, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase,
OutputNode, PlaceholderNode, ToplevelNode,
},
utils::{
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
@ -126,11 +126,40 @@ impl SurfaceRole {
}
pub struct SurfaceSendPreferredScaleVisitor;
impl SurfaceSendPreferredScaleVisitor {
fn schedule_realloc(&self, tl: &impl ToplevelNode) {
for sc in tl.tl_data().jay_screencasts.lock().values() {
sc.schedule_realloc();
}
}
}
impl NodeVisitorBase for SurfaceSendPreferredScaleVisitor {
fn visit_surface(&mut self, node: &Rc<WlSurface>) {
node.on_scale_change();
node.node_visit_children(self);
}
fn visit_toplevel(&mut self, node: &Rc<XdgToplevel>) {
self.schedule_realloc(&**node);
node.node_visit_children(self);
}
fn visit_xwindow(&mut self, node: &Rc<Xwindow>) {
self.schedule_realloc(&**node);
node.node_visit_children(self);
}
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
self.schedule_realloc(&**node);
node.node_visit_children(self);
}
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
self.schedule_realloc(&**node);
node.node_visit_children(self);
}
}
pub struct SurfaceSendPreferredTransformVisitor;