portal: implement window capture
This commit is contained in:
parent
f0600917ff
commit
4e10415e5c
27 changed files with 840 additions and 136 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue