portal: implement window capture
This commit is contained in:
parent
f0600917ff
commit
4e10415e5c
27 changed files with 840 additions and 136 deletions
|
|
@ -1,5 +1,6 @@
|
|||
# Unreleased
|
||||
|
||||
- Screencasts now support window capture.
|
||||
- Add support for wp-alpha-modifier.
|
||||
- Add support for per-device keymaps.
|
||||
- Add support for virtual-keyboard-unstable-v1.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use {
|
|||
},
|
||||
jay_output::JayOutput,
|
||||
jay_screencast::JayScreencast,
|
||||
jay_toplevel::JayToplevel,
|
||||
jay_workspace::JayWorkspace,
|
||||
wl_buffer::WlBuffer,
|
||||
wl_display::WlDisplay,
|
||||
|
|
@ -29,10 +30,10 @@ use {
|
|||
copyhashmap::{CopyHashMap, Locked},
|
||||
},
|
||||
wire::{
|
||||
JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
|
||||
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId,
|
||||
WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId,
|
||||
XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
||||
JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId,
|
||||
WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId,
|
||||
WlSurfaceId, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId,
|
||||
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
||||
},
|
||||
},
|
||||
std::{cell::RefCell, mem, rc::Rc},
|
||||
|
|
@ -60,6 +61,7 @@ pub struct Objects {
|
|||
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
|
||||
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
||||
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
|
||||
pub jay_toplevels: CopyHashMap<JayToplevelId, Rc<JayToplevel>>,
|
||||
ids: RefCell<Vec<usize>>,
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +91,7 @@ impl Objects {
|
|||
screencasts: Default::default(),
|
||||
timelines: Default::default(),
|
||||
zwlr_data_sources: Default::default(),
|
||||
jay_toplevels: Default::default(),
|
||||
ids: RefCell::new(vec![]),
|
||||
}
|
||||
}
|
||||
|
|
@ -122,6 +125,7 @@ impl Objects {
|
|||
self.screencasts.clear();
|
||||
self.timelines.clear();
|
||||
self.zwlr_data_sources.clear();
|
||||
self.jay_toplevels.clear();
|
||||
}
|
||||
|
||||
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use {
|
|||
forker,
|
||||
globals::Globals,
|
||||
ifs::{
|
||||
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
|
||||
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
||||
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
|
||||
},
|
||||
|
|
@ -173,6 +174,8 @@ fn start_compositor2(
|
|||
pending_float_layout: Default::default(),
|
||||
pending_float_titles: Default::default(),
|
||||
pending_input_popup_positioning: Default::default(),
|
||||
pending_toplevel_screencasts: Default::default(),
|
||||
pending_toplevel_screencast_reallocs: Default::default(),
|
||||
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
||||
fdcloser: FdCloser::new(),
|
||||
logger: logger.clone(),
|
||||
|
|
@ -329,6 +332,8 @@ fn start_global_event_handlers(
|
|||
eng.spawn2(Phase::PostLayout, float_titles(state.clone())),
|
||||
eng.spawn2(Phase::PostLayout, idle(state.clone(), backend.clone())),
|
||||
eng.spawn2(Phase::PostLayout, input_popup_positioning(state.clone())),
|
||||
eng.spawn2(Phase::Present, perform_toplevel_screencasts(state.clone())),
|
||||
eng.spawn2(Phase::PostLayout, perform_screencast_realloc(state.clone())),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ pub mod jay_render_ctx;
|
|||
pub mod jay_screencast;
|
||||
pub mod jay_screenshot;
|
||||
pub mod jay_seat_events;
|
||||
pub mod jay_select_toplevel;
|
||||
pub mod jay_toplevel;
|
||||
pub mod jay_workspace;
|
||||
pub mod jay_workspace_watcher;
|
||||
pub mod org_kde_kwin_server_decoration;
|
||||
|
|
|
|||
|
|
@ -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! {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
100
src/ifs/jay_select_toplevel.rs
Normal file
100
src/ifs/jay_select_toplevel.rs
Normal 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
68
src/ifs/jay_toplevel.rs
Normal 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);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -297,6 +297,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
|||
id: dpy.con.id(),
|
||||
con: dpy.con.clone(),
|
||||
owner: Default::default(),
|
||||
window_capture: Cell::new(false),
|
||||
});
|
||||
dpy.con.add_object(jc.clone());
|
||||
dpy.registry.request_bind(name, version, jc.deref());
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ use {
|
|||
session::{CloseReply as SessionCloseReply, Closed},
|
||||
},
|
||||
},
|
||||
wl_usr::usr_ifs::usr_jay_screencast::{UsrJayScreencast, UsrJayScreencastOwner},
|
||||
wl_usr::usr_ifs::{
|
||||
usr_jay_screencast::{UsrJayScreencast, UsrJayScreencastOwner},
|
||||
usr_jay_select_toplevel::UsrJaySelectToplevel,
|
||||
usr_jay_toplevel::UsrJayToplevel,
|
||||
},
|
||||
},
|
||||
std::{
|
||||
borrow::Cow,
|
||||
|
|
@ -59,6 +63,7 @@ pub enum ScreencastPhase {
|
|||
Init,
|
||||
SourcesSelected,
|
||||
Selecting(Rc<SelectingScreencast>),
|
||||
SelectingWindow(Rc<SelectingWindowScreencast>),
|
||||
Starting(Rc<StartingScreencast>),
|
||||
Started(Rc<StartedScreencast>),
|
||||
Terminated,
|
||||
|
|
@ -66,12 +71,22 @@ pub enum ScreencastPhase {
|
|||
|
||||
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
|
||||
|
||||
pub struct SelectingScreencast {
|
||||
#[derive(Clone)]
|
||||
pub struct SelectingScreencastCore {
|
||||
pub session: Rc<ScreencastSession>,
|
||||
pub request_obj: Rc<DbusObject>,
|
||||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
||||
}
|
||||
|
||||
pub struct SelectingScreencast {
|
||||
pub core: SelectingScreencastCore,
|
||||
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
||||
pub output_selected: Cell<bool>,
|
||||
}
|
||||
|
||||
pub struct SelectingWindowScreencast {
|
||||
pub core: SelectingScreencastCore,
|
||||
pub dpy: Rc<PortalDisplay>,
|
||||
pub selector: Rc<UsrJaySelectToplevel>,
|
||||
}
|
||||
|
||||
pub struct StartingScreencast {
|
||||
|
|
@ -80,7 +95,12 @@ pub struct StartingScreencast {
|
|||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
||||
pub node: Rc<PwClientNode>,
|
||||
pub dpy: Rc<PortalDisplay>,
|
||||
pub output: Rc<PortalOutput>,
|
||||
pub target: ScreencastTarget,
|
||||
}
|
||||
|
||||
pub enum ScreencastTarget {
|
||||
Output(Rc<PortalOutput>),
|
||||
Toplevel(Rc<UsrJayToplevel>),
|
||||
}
|
||||
|
||||
pub struct StartedScreencast {
|
||||
|
|
@ -135,10 +155,16 @@ impl PwClientNodeOwner for StartingScreencast {
|
|||
port.can_alloc_buffers.set(true);
|
||||
port.supported_metas.set(SUPPORTED_META_VIDEO_CROP);
|
||||
let jsc = self.dpy.jc.create_screencast();
|
||||
jsc.set_output(&self.output.jay);
|
||||
match &self.target {
|
||||
ScreencastTarget::Output(o) => jsc.set_output(&o.jay),
|
||||
ScreencastTarget::Toplevel(t) => jsc.set_toplevel(t),
|
||||
}
|
||||
jsc.set_use_linear_buffers(true);
|
||||
jsc.set_allow_all_workspaces(true);
|
||||
jsc.configure();
|
||||
if let ScreencastTarget::Toplevel(t) = &self.target {
|
||||
self.dpy.con.remove_obj(&**t);
|
||||
}
|
||||
let started = Rc::new(StartedScreencast {
|
||||
session: self.session.clone(),
|
||||
node: self.node.clone(),
|
||||
|
|
@ -183,6 +209,32 @@ impl PwClientNodeOwner for StartedScreencast {
|
|||
}
|
||||
}
|
||||
|
||||
impl SelectingScreencastCore {
|
||||
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
|
||||
let node = dpy.state.pw_con.create_client_node(&[
|
||||
("media.class".to_string(), "Video/Source".to_string()),
|
||||
("node.name".to_string(), "jay-desktop-portal".to_string()),
|
||||
("node.driver".to_string(), "true".to_string()),
|
||||
]);
|
||||
let starting = Rc::new(StartingScreencast {
|
||||
session: self.session.clone(),
|
||||
request_obj: self.request_obj.clone(),
|
||||
reply: self.reply.clone(),
|
||||
node,
|
||||
dpy: dpy.clone(),
|
||||
target,
|
||||
});
|
||||
self.session
|
||||
.phase
|
||||
.set(ScreencastPhase::Starting(starting.clone()));
|
||||
starting.node.owner.set(Some(starting.clone()));
|
||||
dpy.screencasts.set(
|
||||
self.session.session_obj.path().to_owned(),
|
||||
self.session.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreencastSession {
|
||||
pub(super) fn kill(&self) {
|
||||
self.session_obj.emit_signal(&Closed);
|
||||
|
|
@ -192,15 +244,22 @@ impl ScreencastSession {
|
|||
ScreencastPhase::SourcesSelected => {}
|
||||
ScreencastPhase::Terminated => {}
|
||||
ScreencastPhase::Selecting(s) => {
|
||||
s.reply.err("Session has been terminated");
|
||||
s.core.reply.err("Session has been terminated");
|
||||
for (_, gui) in s.guis.lock().drain() {
|
||||
gui.kill(false);
|
||||
}
|
||||
}
|
||||
ScreencastPhase::SelectingWindow(s) => {
|
||||
s.dpy.con.remove_obj(&*s.selector);
|
||||
s.core.reply.err("Session has been terminated");
|
||||
}
|
||||
ScreencastPhase::Starting(s) => {
|
||||
s.reply.err("Session has been terminated");
|
||||
s.node.con.destroy_obj(s.node.deref());
|
||||
s.dpy.screencasts.remove(self.session_obj.path());
|
||||
if let ScreencastTarget::Toplevel(t) = &s.target {
|
||||
s.dpy.con.remove_obj(&**t);
|
||||
}
|
||||
}
|
||||
ScreencastPhase::Started(s) => {
|
||||
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
|
||||
|
|
@ -270,11 +329,12 @@ impl ScreencastSession {
|
|||
}
|
||||
self.phase
|
||||
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
|
||||
session: self.clone(),
|
||||
request_obj: Rc::new(request_obj),
|
||||
reply: Rc::new(reply),
|
||||
core: SelectingScreencastCore {
|
||||
session: self.clone(),
|
||||
request_obj: Rc::new(request_obj),
|
||||
reply: Rc::new(reply),
|
||||
},
|
||||
guis,
|
||||
output_selected: Cell::new(false),
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ use {
|
|||
crate::{
|
||||
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
|
||||
portal::{
|
||||
ptl_display::{PortalDisplay, PortalOutput},
|
||||
ptl_screencast::{ScreencastPhase, ScreencastSession, StartingScreencast},
|
||||
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
||||
ptl_screencast::{
|
||||
ScreencastPhase, ScreencastSession, ScreencastTarget, SelectingWindowScreencast,
|
||||
},
|
||||
ptr_gui::{
|
||||
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
|
||||
OverlayWindowOwner,
|
||||
|
|
@ -11,6 +13,9 @@ use {
|
|||
},
|
||||
theme::Color,
|
||||
utils::copyhashmap::CopyHashMap,
|
||||
wl_usr::usr_ifs::{
|
||||
usr_jay_select_toplevel::UsrJaySelectToplevelOwner, usr_jay_toplevel::UsrJayToplevel,
|
||||
},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
|
@ -38,6 +43,7 @@ struct StaticButton {
|
|||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
enum ButtonRole {
|
||||
Accept,
|
||||
Window,
|
||||
Reject,
|
||||
}
|
||||
|
||||
|
|
@ -65,17 +71,17 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
|||
let label = Rc::new(Label::default());
|
||||
*label.text.borrow_mut() = text;
|
||||
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
|
||||
let window_button = static_button(surface, ButtonRole::Window, "Share A Window");
|
||||
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
||||
let buttons = [&accept_button, &reject_button];
|
||||
for button in buttons {
|
||||
for button in [&accept_button, &window_button, &reject_button] {
|
||||
button.border_color.set(Color::from_gray(100));
|
||||
button.border.set(2.0);
|
||||
button.padding.set(5.0);
|
||||
}
|
||||
accept_button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||
accept_button
|
||||
.bg_hover_color
|
||||
.set(Color::from_rgb(170, 255, 170));
|
||||
for button in [&accept_button, &window_button] {
|
||||
button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
|
||||
}
|
||||
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
|
||||
reject_button
|
||||
.bg_hover_color
|
||||
|
|
@ -85,7 +91,12 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
|||
flow.cross_align.set(Align::Center);
|
||||
flow.in_margin.set(V_MARGIN);
|
||||
flow.cross_margin.set(H_MARGIN);
|
||||
*flow.elements.borrow_mut() = vec![label, accept_button, reject_button];
|
||||
let mut elements: Vec<Rc<dyn GuiElement>> = vec![label, accept_button];
|
||||
if surface.gui.dpy.jc.window_capture.get() {
|
||||
elements.push(window_button);
|
||||
}
|
||||
elements.push(reject_button);
|
||||
*flow.elements.borrow_mut() = elements;
|
||||
flow
|
||||
}
|
||||
|
||||
|
|
@ -124,12 +135,12 @@ impl SelectionGui {
|
|||
}
|
||||
|
||||
impl ButtonOwner for StaticButton {
|
||||
fn button(&self, button: u32, state: u32) {
|
||||
fn button(&self, seat: &PortalSeat, button: u32, state: u32) {
|
||||
if button != BTN_LEFT || state != PRESSED {
|
||||
return;
|
||||
}
|
||||
match self.role {
|
||||
ButtonRole::Accept => {
|
||||
ButtonRole::Accept | ButtonRole::Window => {
|
||||
log::info!("User has accepted the request");
|
||||
let selecting = match self.surface.gui.screencast_session.phase.get() {
|
||||
ScreencastPhase::Selecting(selecting) => selecting,
|
||||
|
|
@ -138,34 +149,25 @@ impl ButtonOwner for StaticButton {
|
|||
for (_, gui) in selecting.guis.lock().drain() {
|
||||
gui.kill(false);
|
||||
}
|
||||
let node = self.surface.gui.dpy.state.pw_con.create_client_node(&[
|
||||
("media.class".to_string(), "Video/Source".to_string()),
|
||||
("node.name".to_string(), "jay-desktop-portal".to_string()),
|
||||
("node.driver".to_string(), "true".to_string()),
|
||||
]);
|
||||
let starting = Rc::new(StartingScreencast {
|
||||
session: self.surface.gui.screencast_session.clone(),
|
||||
request_obj: selecting.request_obj.clone(),
|
||||
reply: selecting.reply.clone(),
|
||||
node,
|
||||
dpy: self.surface.gui.dpy.clone(),
|
||||
output: self.surface.output.clone(),
|
||||
});
|
||||
self.surface
|
||||
.gui
|
||||
.screencast_session
|
||||
.phase
|
||||
.set(ScreencastPhase::Starting(starting.clone()));
|
||||
starting.node.owner.set(Some(starting.clone()));
|
||||
self.surface.gui.dpy.screencasts.set(
|
||||
let dpy = &self.surface.output.dpy;
|
||||
if self.role == ButtonRole::Accept {
|
||||
selecting
|
||||
.core
|
||||
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
|
||||
} else {
|
||||
let selector = dpy.jc.select_toplevel(&seat.wl);
|
||||
let selecting = Rc::new(SelectingWindowScreencast {
|
||||
core: selecting.core.clone(),
|
||||
dpy: dpy.clone(),
|
||||
selector: selector.clone(),
|
||||
});
|
||||
selector.owner.set(Some(selecting.clone()));
|
||||
self.surface
|
||||
.gui
|
||||
.screencast_session
|
||||
.session_obj
|
||||
.path()
|
||||
.to_owned(),
|
||||
self.surface.gui.screencast_session.clone(),
|
||||
);
|
||||
.phase
|
||||
.set(ScreencastPhase::SelectingWindow(selecting));
|
||||
}
|
||||
}
|
||||
ButtonRole::Reject => {
|
||||
log::info!("User has rejected the screencast request");
|
||||
|
|
@ -175,6 +177,28 @@ impl ButtonOwner for StaticButton {
|
|||
}
|
||||
}
|
||||
|
||||
impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
|
||||
fn done(&self, tl: Option<Rc<UsrJayToplevel>>) {
|
||||
let Some(tl) = tl else {
|
||||
log::info!("User has aborted the selection");
|
||||
self.core.session.kill();
|
||||
return;
|
||||
};
|
||||
match self.core.session.phase.get() {
|
||||
ScreencastPhase::SelectingWindow(s) => {
|
||||
self.dpy.con.remove_obj(&*s.selector);
|
||||
}
|
||||
_ => {
|
||||
self.dpy.con.remove_obj(&*tl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::info!("User has selected a window");
|
||||
self.core
|
||||
.starting(&self.dpy, ScreencastTarget::Toplevel(tl));
|
||||
}
|
||||
}
|
||||
|
||||
fn static_button(surface: &Rc<SelectionGuiSurface>, role: ButtonRole, text: &str) -> Rc<Button> {
|
||||
let button = Rc::new(Button::default());
|
||||
let slf = Rc::new(StaticButton {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ pub struct Button {
|
|||
}
|
||||
|
||||
pub trait ButtonOwner {
|
||||
fn button(&self, button: u32, state: u32);
|
||||
fn button(&self, seat: &PortalSeat, button: u32, state: u32);
|
||||
}
|
||||
|
||||
impl Default for Button {
|
||||
|
|
@ -251,9 +251,9 @@ impl GuiElement for Button {
|
|||
self.owner.take();
|
||||
}
|
||||
|
||||
fn button(&self, _seat: &PortalSeat, button: u32, state: u32) {
|
||||
fn button(&self, seat: &PortalSeat, button: u32, state: u32) {
|
||||
if let Some(owner) = self.owner.get() {
|
||||
owner.button(button, state);
|
||||
owner.button(seat, button, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ impl Renderer<'_> {
|
|||
ReleaseSync::None,
|
||||
);
|
||||
}
|
||||
self.render_tl_aux(placeholder.tl_data(), bounds);
|
||||
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
||||
}
|
||||
|
||||
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
||||
|
|
@ -309,16 +309,17 @@ impl Renderer<'_> {
|
|||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||
}
|
||||
}
|
||||
self.render_tl_aux(container.tl_data(), None, false);
|
||||
}
|
||||
|
||||
pub fn render_xwindow(&mut self, tl: &Xwindow, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||
self.render_surface(&tl.x.surface, x, y, bounds);
|
||||
self.render_tl_aux(tl.tl_data(), bounds);
|
||||
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||
}
|
||||
|
||||
pub fn render_xdg_toplevel(&mut self, tl: &XdgToplevel, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||
self.render_xdg_surface(&tl.xdg, x, y, bounds);
|
||||
self.render_tl_aux(tl.tl_data(), bounds);
|
||||
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||
}
|
||||
|
||||
pub fn render_xdg_surface(
|
||||
|
|
@ -335,8 +336,20 @@ impl Renderer<'_> {
|
|||
self.render_surface(surface, x, y, bounds);
|
||||
}
|
||||
|
||||
fn render_tl_aux(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) {
|
||||
self.render_highlight(tl_data, bounds);
|
||||
fn render_tl_aux(
|
||||
&mut self,
|
||||
tl_data: &ToplevelData,
|
||||
bounds: Option<&Rect>,
|
||||
render_highlight: bool,
|
||||
) {
|
||||
if self.result.is_some() {
|
||||
for screencast in tl_data.jay_screencasts.lock().values() {
|
||||
screencast.schedule_toplevel_screencast();
|
||||
}
|
||||
}
|
||||
if render_highlight {
|
||||
self.render_highlight(tl_data, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_highlight(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) {
|
||||
|
|
|
|||
11
src/scale.rs
11
src/scale.rs
|
|
@ -37,6 +37,17 @@ impl Scale {
|
|||
pub fn to_wl(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn pixel_size(self, width: i32, height: i32) -> (i32, i32) {
|
||||
if self == Scale::default() {
|
||||
return (width, height);
|
||||
}
|
||||
let scale = self.to_f64();
|
||||
(
|
||||
(width as f64 * scale).round() as i32,
|
||||
(height as f64 * scale).round() as i32,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u32> for Scale {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use {
|
|||
ext_session_lock_v1::ExtSessionLockV1,
|
||||
ipc::{x_data_device::XIpcDeviceIds, DataOfferIds, DataSourceIds},
|
||||
jay_render_ctx::JayRenderCtx,
|
||||
jay_screencast::JayScreencast,
|
||||
jay_seat_events::JaySeatEvents,
|
||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||
wl_drm::WlDrmGlobal,
|
||||
|
|
@ -136,6 +137,8 @@ pub struct State {
|
|||
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
||||
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
||||
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
||||
pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
|
||||
pub pending_toplevel_screencast_reallocs: AsyncQueue<Rc<JayScreencast>>,
|
||||
pub dbus: Dbus,
|
||||
pub fdcloser: Arc<FdCloser>,
|
||||
pub logger: Option<Arc<Logger>>,
|
||||
|
|
@ -735,6 +738,9 @@ impl State {
|
|||
self.pending_output_render_data.clear();
|
||||
self.pending_float_layout.clear();
|
||||
self.pending_float_titles.clear();
|
||||
self.pending_input_popup_positioning.clear();
|
||||
self.pending_toplevel_screencasts.clear();
|
||||
self.pending_toplevel_screencast_reallocs.clear();
|
||||
self.render_ctx_watchers.clear();
|
||||
self.workspace_watchers.clear();
|
||||
self.toplevel_lists.clear();
|
||||
|
|
|
|||
|
|
@ -409,20 +409,8 @@ impl OutputNode {
|
|||
self.change_extents_(&self.calculate_extents());
|
||||
|
||||
if (old_width, old_height) != (new_width, new_height) {
|
||||
let mut to_destroy = vec![];
|
||||
if let Some(ctx) = self.state.render_ctx.get() {
|
||||
for sc in self.screencasts.lock().values() {
|
||||
if let Err(e) = sc.realloc(&ctx) {
|
||||
log::error!(
|
||||
"Could not re-allocate buffers for screencast after mode change: {}",
|
||||
ErrorFmt(e)
|
||||
);
|
||||
to_destroy.push(sc.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for sc in to_destroy {
|
||||
sc.do_destroy();
|
||||
for sc in self.screencasts.lock().values() {
|
||||
sc.schedule_realloc();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use {
|
|||
ifs::{
|
||||
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||
jay_screencast::JayScreencast,
|
||||
jay_toplevel::JayToplevel,
|
||||
wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId},
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
|
|
@ -18,7 +20,7 @@ use {
|
|||
threshold_counter::ThresholdCounter,
|
||||
toplevel_identifier::{toplevel_identifier, ToplevelIdentifier},
|
||||
},
|
||||
wire::ExtForeignToplevelHandleV1Id,
|
||||
wire::{ExtForeignToplevelHandleV1Id, JayScreencastId, JayToplevelId},
|
||||
},
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
|
|
@ -111,6 +113,12 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
|||
|
||||
fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
|
||||
let data = self.tl_data();
|
||||
let prev = data.desired_extents.replace(*rect);
|
||||
if prev.size() != rect.size() {
|
||||
for sc in data.jay_screencasts.lock().values() {
|
||||
sc.schedule_realloc();
|
||||
}
|
||||
}
|
||||
if data.is_floating.get() {
|
||||
data.float_width.set(rect.width());
|
||||
data.float_height.set(rect.height());
|
||||
|
|
@ -199,6 +207,7 @@ pub struct ToplevelData {
|
|||
pub title: RefCell<String>,
|
||||
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
|
||||
pub pos: Cell<Rect>,
|
||||
pub desired_extents: Cell<Rect>,
|
||||
pub seat_state: NodeSeatState,
|
||||
pub wants_attention: Cell<bool>,
|
||||
pub requested_attention: Cell<bool>,
|
||||
|
|
@ -207,6 +216,8 @@ pub struct ToplevelData {
|
|||
pub handles:
|
||||
CopyHashMap<(ClientId, ExtForeignToplevelHandleV1Id), Rc<ExtForeignToplevelHandleV1>>,
|
||||
pub render_highlight: NumCell<u32>,
|
||||
pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc<JayToplevel>>,
|
||||
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
|
||||
}
|
||||
|
||||
impl ToplevelData {
|
||||
|
|
@ -227,6 +238,7 @@ impl ToplevelData {
|
|||
title: RefCell::new(title),
|
||||
parent: Default::default(),
|
||||
pos: Default::default(),
|
||||
desired_extents: Default::default(),
|
||||
seat_state: Default::default(),
|
||||
wants_attention: Cell::new(false),
|
||||
requested_attention: Cell::new(false),
|
||||
|
|
@ -234,6 +246,8 @@ impl ToplevelData {
|
|||
identifier: Cell::new(toplevel_identifier()),
|
||||
handles: Default::default(),
|
||||
render_highlight: Default::default(),
|
||||
jay_toplevels: Default::default(),
|
||||
jay_screencasts: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +285,12 @@ impl ToplevelData {
|
|||
}
|
||||
|
||||
pub fn destroy_node(&self, node: &dyn Node) {
|
||||
for (_, jay_tl) in self.jay_toplevels.lock().drain() {
|
||||
jay_tl.destroy();
|
||||
}
|
||||
for (_, screencast) in self.jay_screencasts.lock().drain() {
|
||||
screencast.do_destroy();
|
||||
}
|
||||
self.identifier.set(toplevel_identifier());
|
||||
{
|
||||
let mut handles = self.handles.lock();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ pub mod usr_jay_output;
|
|||
pub mod usr_jay_pointer;
|
||||
pub mod usr_jay_render_ctx;
|
||||
pub mod usr_jay_screencast;
|
||||
pub mod usr_jay_select_toplevel;
|
||||
pub mod usr_jay_toplevel;
|
||||
pub mod usr_jay_workspace;
|
||||
pub mod usr_jay_workspace_watcher;
|
||||
pub mod usr_linux_buffer_params;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use {
|
|||
usr_ifs::{
|
||||
usr_jay_output::UsrJayOutput, usr_jay_pointer::UsrJayPointer,
|
||||
usr_jay_render_ctx::UsrJayRenderCtx, usr_jay_screencast::UsrJayScreencast,
|
||||
usr_jay_select_toplevel::UsrJaySelectToplevel,
|
||||
usr_jay_workspace_watcher::UsrJayWorkspaceWatcher, usr_wl_output::UsrWlOutput,
|
||||
usr_wl_seat::UsrWlSeat,
|
||||
},
|
||||
|
|
@ -17,13 +18,14 @@ use {
|
|||
UsrCon,
|
||||
},
|
||||
},
|
||||
std::rc::Rc,
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct UsrJayCompositor {
|
||||
pub id: JayCompositorId,
|
||||
pub con: Rc<UsrCon>,
|
||||
pub owner: CloneCell<Option<Rc<dyn UsrJayCompositorOwner>>>,
|
||||
pub window_capture: Cell<bool>,
|
||||
}
|
||||
|
||||
pub trait UsrJayCompositorOwner {
|
||||
|
|
@ -112,6 +114,21 @@ impl UsrJayCompositor {
|
|||
jp
|
||||
}
|
||||
|
||||
pub fn select_toplevel(&self, seat: &UsrWlSeat) -> Rc<UsrJaySelectToplevel> {
|
||||
let sc = Rc::new(UsrJaySelectToplevel {
|
||||
id: self.con.id(),
|
||||
con: self.con.clone(),
|
||||
owner: Default::default(),
|
||||
});
|
||||
self.con.request(SelectToplevel {
|
||||
self_id: self.id,
|
||||
id: sc.id,
|
||||
seat: seat.id,
|
||||
});
|
||||
self.con.add_object(sc.clone());
|
||||
sc
|
||||
}
|
||||
|
||||
fn client_id(&self, parser: MsgParser<'_, '_>) -> Result<(), MsgParserError> {
|
||||
let ev: ClientId = self.con.parse(self, parser)?;
|
||||
if let Some(owner) = self.owner.get() {
|
||||
|
|
@ -133,6 +150,7 @@ impl UsrJayCompositor {
|
|||
for &cap in ev.cap {
|
||||
match cap {
|
||||
Cap::NONE => {}
|
||||
Cap::WINDOW_CAPTURE => self.window_capture.set(true),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ use {
|
|||
},
|
||||
video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec},
|
||||
wire::{jay_screencast::*, JayScreencastId},
|
||||
wl_usr::{usr_ifs::usr_jay_output::UsrJayOutput, usr_object::UsrObject, UsrCon},
|
||||
wl_usr::{
|
||||
usr_ifs::{usr_jay_output::UsrJayOutput, usr_jay_toplevel::UsrJayToplevel},
|
||||
usr_object::UsrObject,
|
||||
UsrCon,
|
||||
},
|
||||
},
|
||||
std::{cell::RefCell, mem, ops::DerefMut, rc::Rc},
|
||||
thiserror::Error,
|
||||
|
|
@ -60,6 +64,13 @@ impl UsrJayScreencast {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn set_toplevel(&self, tl: &UsrJayToplevel) {
|
||||
self.con.request(SetToplevel {
|
||||
self_id: self.id,
|
||||
id: tl.id,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_allow_all_workspaces(&self, allow_all: bool) {
|
||||
self.con.request(SetAllowAllWorkspaces {
|
||||
self_id: self.id,
|
||||
|
|
|
|||
71
src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs
Normal file
71
src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use {
|
||||
crate::{
|
||||
utils::{
|
||||
buffd::{MsgParser, MsgParserError},
|
||||
clonecell::CloneCell,
|
||||
},
|
||||
wire::{jay_select_toplevel::*, JaySelectToplevelId},
|
||||
wl_usr::{usr_ifs::usr_jay_toplevel::UsrJayToplevel, usr_object::UsrObject, UsrCon},
|
||||
},
|
||||
std::rc::Rc,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct UsrJaySelectToplevel {
|
||||
pub id: JaySelectToplevelId,
|
||||
pub con: Rc<UsrCon>,
|
||||
pub owner: CloneCell<Option<Rc<dyn UsrJaySelectToplevelOwner>>>,
|
||||
}
|
||||
|
||||
pub trait UsrJaySelectToplevelOwner {
|
||||
fn done(&self, toplevel: Option<Rc<UsrJayToplevel>>);
|
||||
}
|
||||
|
||||
impl UsrJaySelectToplevel {
|
||||
fn done(&self, parser: MsgParser<'_, '_>) -> Result<(), UsrJaySelectToplevelError> {
|
||||
let ev: Done = self.con.parse(self, parser)?;
|
||||
let tl = if ev.id.is_none() {
|
||||
None
|
||||
} else {
|
||||
let tl = Rc::new(UsrJayToplevel {
|
||||
id: ev.id,
|
||||
con: self.con.clone(),
|
||||
owner: Default::default(),
|
||||
});
|
||||
self.con.add_object(tl.clone());
|
||||
Some(tl)
|
||||
};
|
||||
match self.owner.get() {
|
||||
Some(owner) => owner.done(tl),
|
||||
_ => {
|
||||
if let Some(tl) = tl {
|
||||
self.con.remove_obj(&*tl);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.con.remove_obj(self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
usr_object_base! {
|
||||
UsrJaySelectToplevel, JaySelectToplevel;
|
||||
|
||||
DONE => done,
|
||||
}
|
||||
|
||||
impl UsrObject for UsrJaySelectToplevel {
|
||||
fn destroy(&self) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
fn break_loops(&self) {
|
||||
self.owner.take();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UsrJaySelectToplevelError {
|
||||
#[error("Parsing failed")]
|
||||
MsgParserError(#[from] MsgParserError),
|
||||
}
|
||||
47
src/wl_usr/usr_ifs/usr_jay_toplevel.rs
Normal file
47
src/wl_usr/usr_ifs/usr_jay_toplevel.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use {
|
||||
crate::{
|
||||
utils::{
|
||||
buffd::{MsgParser, MsgParserError},
|
||||
clonecell::CloneCell,
|
||||
},
|
||||
wire::{jay_toplevel::*, JayToplevelId},
|
||||
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct UsrJayToplevel {
|
||||
pub id: JayToplevelId,
|
||||
pub con: Rc<UsrCon>,
|
||||
pub owner: CloneCell<Option<Rc<dyn UsrJayToplevelOwner>>>,
|
||||
}
|
||||
|
||||
pub trait UsrJayToplevelOwner {
|
||||
fn destroyed(&self) {}
|
||||
}
|
||||
|
||||
impl UsrJayToplevel {
|
||||
fn destroyed(&self, parser: MsgParser<'_, '_>) -> Result<(), MsgParserError> {
|
||||
let _ev: Destroyed = self.con.parse(self, parser)?;
|
||||
if let Some(owner) = self.owner.get() {
|
||||
owner.destroyed();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
usr_object_base! {
|
||||
UsrJayToplevel, JayToplevel;
|
||||
|
||||
DESTROYED => destroyed,
|
||||
}
|
||||
|
||||
impl UsrObject for UsrJayToplevel {
|
||||
fn destroy(&self) {
|
||||
self.con.request(Destroy { self_id: self.id });
|
||||
}
|
||||
|
||||
fn break_loops(&self) {
|
||||
self.owner.set(None);
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +78,11 @@ request take_screenshot2 {
|
|||
include_cursor: u32,
|
||||
}
|
||||
|
||||
request select_toplevel {
|
||||
id: id(jay_select_toplevel),
|
||||
seat: id(wl_seat),
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event client_id {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ request release_buffer {
|
|||
idx: u32,
|
||||
}
|
||||
|
||||
request set_toplevel {
|
||||
id: id(jay_toplevel),
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event plane {
|
||||
|
|
|
|||
3
wire/jay_select_toplevel.txt
Normal file
3
wire/jay_select_toplevel.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
event done {
|
||||
id: id(jay_toplevel),
|
||||
}
|
||||
5
wire/jay_toplevel.txt
Normal file
5
wire/jay_toplevel.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
request destroy {
|
||||
}
|
||||
|
||||
event destroyed {
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue