Merge pull request #291 from mahkoh/jorth/session-restore
portal: implement session restoration
This commit is contained in:
commit
06d7fff905
39 changed files with 987 additions and 380 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -586,6 +586,7 @@ dependencies = [
|
||||||
"repc",
|
"repc",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"shaderc",
|
"shaderc",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
@ -1067,12 +1068,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.114"
|
version = "1.0.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ ash = "0.38.0"
|
||||||
gpu-alloc = "0.6.0"
|
gpu-alloc = "0.6.0"
|
||||||
gpu-alloc-ash = "0.7.0"
|
gpu-alloc-ash = "0.7.0"
|
||||||
serde = { version = "1.0.196", features = ["derive"] }
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
|
serde_json = "1.0.128"
|
||||||
enum-map = "2.7.3"
|
enum-map = "2.7.3"
|
||||||
png = "0.17.13"
|
png = "0.17.13"
|
||||||
rustc-demangle = { version = "0.1.24", optional = true }
|
rustc-demangle = { version = "0.1.24", optional = true }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
- Allow X windows to scale themselves.
|
- Allow X windows to scale themselves.
|
||||||
- Implement ext-image-capture-source-v1.
|
- Implement ext-image-capture-source-v1.
|
||||||
- Implement ext-image-copy-capture-v1.
|
- Implement ext-image-copy-capture-v1.
|
||||||
|
- Implement screencast session restoration.
|
||||||
|
- Fix screen sharing in zoom.
|
||||||
|
|
||||||
# 1.6.0 (2024-09-25)
|
# 1.6.0 (2024-09-25)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,7 @@ fn start_compositor2(
|
||||||
cpu_worker,
|
cpu_worker,
|
||||||
ui_drag_enabled: Cell::new(true),
|
ui_drag_enabled: Cell::new(true),
|
||||||
ui_drag_threshold_squared: Cell::new(10),
|
ui_drag_threshold_squared: Cell::new(10),
|
||||||
|
toplevels: Default::default(),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ use {
|
||||||
wire_ei::{
|
wire_ei::{
|
||||||
ei_device::{
|
ei_device::{
|
||||||
ClientFrame, ClientStartEmulating, ClientStopEmulating, Destroyed, DeviceType,
|
ClientFrame, ClientStartEmulating, ClientStopEmulating, Destroyed, DeviceType,
|
||||||
Done, EiDeviceRequestHandler, Interface, Paused, Region, Release, Resumed,
|
Done, EiDeviceRequestHandler, Interface, Paused, Region, RegionMappingId, Release,
|
||||||
ServerFrame, ServerStartEmulating,
|
Resumed, ServerFrame, ServerStartEmulating,
|
||||||
},
|
},
|
||||||
EiDeviceId,
|
EiDeviceId,
|
||||||
},
|
},
|
||||||
|
|
@ -100,6 +100,13 @@ impl EiDevice {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_region_mapping_id(&self, mapping_id: &str) {
|
||||||
|
self.client.event(RegionMappingId {
|
||||||
|
self_id: self.id,
|
||||||
|
mapping_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(dead_code)]
|
#[expect(dead_code)]
|
||||||
pub fn send_paused(&self, serial: u32) {
|
pub fn send_paused(&self, serial: u32) {
|
||||||
self.client.event(Paused {
|
self.client.event(Paused {
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,7 @@ impl EiSeat {
|
||||||
apply!(EI_CAP_KEYBOARD, create_keyboard);
|
apply!(EI_CAP_KEYBOARD, create_keyboard);
|
||||||
apply!(EI_CAP_TOUCHSCREEN, create_touchscreen);
|
apply!(EI_CAP_TOUCHSCREEN, create_touchscreen);
|
||||||
for output in self.client.state.root.outputs.lock().values() {
|
for output in self.client.state.root.outputs.lock().values() {
|
||||||
|
device.send_region_mapping_id(&output.global.connector.name);
|
||||||
device.send_region(
|
device.send_region(
|
||||||
output.node_absolute_position(),
|
output.node_absolute_position(),
|
||||||
output.global.persistent.scale.get(),
|
output.global.persistent.scale.get(),
|
||||||
|
|
|
||||||
|
|
@ -23,17 +23,18 @@ use {
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::{Object, Version},
|
object::{Object, Version},
|
||||||
screenshoter::take_screenshot,
|
screenshoter::take_screenshot,
|
||||||
utils::errorfmt::ErrorFmt,
|
utils::{errorfmt::ErrorFmt, toplevel_identifier::ToplevelIdentifier},
|
||||||
wire::{jay_compositor::*, JayCompositorId, JayScreenshotId},
|
wire::{jay_compositor::*, JayCompositorId, JayScreenshotId},
|
||||||
},
|
},
|
||||||
bstr::ByteSlice,
|
bstr::ByteSlice,
|
||||||
log::Level,
|
log::Level,
|
||||||
std::{cell::Cell, ops::Deref, rc::Rc},
|
std::{cell::Cell, ops::Deref, rc::Rc, str::FromStr},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
|
pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
|
||||||
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
|
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
|
||||||
|
pub const GET_TOPLEVEL_SINCE: Version = Version(12);
|
||||||
|
|
||||||
pub struct JayCompositorGlobal {
|
pub struct JayCompositorGlobal {
|
||||||
name: GlobalName,
|
name: GlobalName,
|
||||||
|
|
@ -71,7 +72,7 @@ impl Global for JayCompositorGlobal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version(&self) -> u32 {
|
fn version(&self) -> u32 {
|
||||||
11
|
12
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_caps(&self) -> ClientCaps {
|
fn required_caps(&self) -> ClientCaps {
|
||||||
|
|
@ -364,12 +365,7 @@ impl JayCompositorRequestHandler for JayCompositor {
|
||||||
|
|
||||||
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
let seat = self.client.lookup(req.seat)?;
|
let seat = self.client.lookup(req.seat)?;
|
||||||
let obj = Rc::new(JaySelectToplevel {
|
let obj = JaySelectToplevel::new(&self.client, req.id, self.version);
|
||||||
id: req.id,
|
|
||||||
client: self.client.clone(),
|
|
||||||
tracker: Default::default(),
|
|
||||||
destroyed: Cell::new(false),
|
|
||||||
});
|
|
||||||
track!(self.client, obj);
|
track!(self.client, obj);
|
||||||
self.client.add_client_obj(&obj)?;
|
self.client.add_client_obj(&obj)?;
|
||||||
let selector = JayToplevelSelector {
|
let selector = JayToplevelSelector {
|
||||||
|
|
@ -422,6 +418,26 @@ impl JayCompositorRequestHandler for JayCompositor {
|
||||||
self.client.add_client_obj(&obj)?;
|
self.client.add_client_obj(&obj)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_toplevel(&self, req: GetToplevel<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
let obj = JaySelectToplevel::new(&self.client, req.id, self.version);
|
||||||
|
track!(self.client, obj);
|
||||||
|
self.client.add_client_obj(&obj)?;
|
||||||
|
let tl = match ToplevelIdentifier::from_str(req.toplevel_id) {
|
||||||
|
Ok(id) => self
|
||||||
|
.client
|
||||||
|
.state
|
||||||
|
.toplevels
|
||||||
|
.get(&id)
|
||||||
|
.and_then(|w| w.upgrade()),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not parse toplevel id: {}", ErrorFmt(e));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
obj.done(tl);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_base! {
|
object_base! {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError},
|
||||||
ifs::{jay_toplevel::JayToplevel, wl_seat::ToplevelSelector},
|
ifs::{
|
||||||
|
jay_toplevel::{JayToplevel, ID_SINCE},
|
||||||
|
wl_seat::ToplevelSelector,
|
||||||
|
},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::{Object, Version},
|
object::{Object, Version},
|
||||||
tree::ToplevelNode,
|
tree::ToplevelNode,
|
||||||
|
|
@ -17,6 +20,7 @@ pub struct JaySelectToplevel {
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
pub destroyed: Cell<bool>,
|
pub destroyed: Cell<bool>,
|
||||||
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct JayToplevelSelector {
|
pub struct JayToplevelSelector {
|
||||||
|
|
@ -35,38 +39,62 @@ impl Drop for JayToplevelSelector {
|
||||||
if self.jst.destroyed.get() {
|
if self.jst.destroyed.get() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let id = match self.tl.take() {
|
self.jst.done(self.tl.take());
|
||||||
None => JayToplevelId::NONE,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JaySelectToplevel {
|
||||||
|
pub fn done(&self, tl: Option<Rc<dyn ToplevelNode>>) {
|
||||||
|
let jtl = match tl {
|
||||||
|
None => None,
|
||||||
Some(toplevel) => {
|
Some(toplevel) => {
|
||||||
let id = match self.jst.client.new_id() {
|
let id = match self.client.new_id() {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.jst.client.error(e);
|
self.client.error(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let jtl = Rc::new(JayToplevel {
|
let jtl = Rc::new(JayToplevel {
|
||||||
id,
|
id,
|
||||||
client: self.jst.client.clone(),
|
client: self.client.clone(),
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
toplevel,
|
toplevel,
|
||||||
destroyed: Cell::new(false),
|
destroyed: Cell::new(false),
|
||||||
|
version: self.version,
|
||||||
});
|
});
|
||||||
track!(self.jst.client, jtl);
|
track!(self.client, jtl);
|
||||||
self.jst.client.add_server_obj(&jtl);
|
self.client.add_server_obj(&jtl);
|
||||||
jtl.toplevel
|
jtl.toplevel
|
||||||
.tl_data()
|
.tl_data()
|
||||||
.jay_toplevels
|
.jay_toplevels
|
||||||
.set((jtl.client.id, jtl.id), jtl.clone());
|
.set((jtl.client.id, jtl.id), jtl.clone());
|
||||||
jtl.id
|
Some(jtl)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.jst.send_done(id);
|
match jtl {
|
||||||
let _ = self.jst.client.remove_obj(&*self.jst);
|
None => self.send_done(JayToplevelId::NONE),
|
||||||
|
Some(jtl) => {
|
||||||
|
self.send_done(jtl.id);
|
||||||
|
if jtl.version >= ID_SINCE {
|
||||||
|
jtl.send_id();
|
||||||
|
jtl.send_done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = self.client.remove_obj(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(client: &Rc<Client>, id: JaySelectToplevelId, version: Version) -> Rc<Self> {
|
||||||
|
Rc::new(JaySelectToplevel {
|
||||||
|
id,
|
||||||
|
client: client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
destroyed: Cell::new(false),
|
||||||
|
version,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl JaySelectToplevel {
|
|
||||||
fn send_done(&self, id: JayToplevelId) {
|
fn send_done(&self, id: JayToplevelId) {
|
||||||
self.client.event(Done {
|
self.client.event(Done {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@ use {
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ID_SINCE: Version = Version(12);
|
||||||
|
|
||||||
pub struct JayToplevel {
|
pub struct JayToplevel {
|
||||||
pub id: JayToplevelId,
|
pub id: JayToplevelId,
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
pub toplevel: Rc<dyn ToplevelNode>,
|
pub toplevel: Rc<dyn ToplevelNode>,
|
||||||
pub destroyed: Cell<bool>,
|
pub destroyed: Cell<bool>,
|
||||||
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JayToplevel {
|
impl JayToplevel {
|
||||||
|
|
@ -35,6 +38,18 @@ impl JayToplevel {
|
||||||
fn send_destroyed(&self) {
|
fn send_destroyed(&self) {
|
||||||
self.client.event(Destroyed { self_id: self.id });
|
self.client.event(Destroyed { self_id: self.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_id(&self) {
|
||||||
|
let s = self.toplevel.tl_data().identifier.get().to_string();
|
||||||
|
self.client.event(Id {
|
||||||
|
self_id: self.id,
|
||||||
|
id: &s,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_done(&self) {
|
||||||
|
self.client.event(Done { self_id: self.id })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JayToplevelRequestHandler for JayToplevel {
|
impl JayToplevelRequestHandler for JayToplevel {
|
||||||
|
|
|
||||||
|
|
@ -1227,7 +1227,7 @@ impl UiDragUsecase for TileDragUsecase {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let detach = || {
|
let detach = || {
|
||||||
let placeholder = Rc::new(PlaceholderNode::new_empty(&seat.state));
|
let placeholder = Rc::new_cyclic(|weak| PlaceholderNode::new_empty(&seat.state, weak));
|
||||||
src_parent
|
src_parent
|
||||||
.clone()
|
.clone()
|
||||||
.cnode_replace_child(src.tl_as_node(), placeholder.clone());
|
.cnode_replace_child(src.tl_as_node(), placeholder.clone());
|
||||||
|
|
|
||||||
|
|
@ -205,18 +205,21 @@ impl Xwindow {
|
||||||
if xsurface.xwindow.is_some() {
|
if xsurface.xwindow.is_some() {
|
||||||
return Err(XWindowError::AlreadyAttached);
|
return Err(XWindowError::AlreadyAttached);
|
||||||
}
|
}
|
||||||
let tld = ToplevelData::new(
|
let slf = Rc::new_cyclic(|weak| {
|
||||||
&data.state,
|
let tld = ToplevelData::new(
|
||||||
data.info.title.borrow_mut().clone().unwrap_or_default(),
|
&data.state,
|
||||||
Some(surface.client.clone()),
|
data.info.title.borrow_mut().clone().unwrap_or_default(),
|
||||||
);
|
Some(surface.client.clone()),
|
||||||
tld.pos.set(surface.extents.get());
|
weak,
|
||||||
let slf = Rc::new(Self {
|
);
|
||||||
id: data.state.node_ids.next(),
|
tld.pos.set(surface.extents.get());
|
||||||
data: data.clone(),
|
Self {
|
||||||
display_link: Default::default(),
|
id: data.state.node_ids.next(),
|
||||||
toplevel_data: tld,
|
data: data.clone(),
|
||||||
x: xsurface,
|
display_link: Default::default(),
|
||||||
|
toplevel_data: tld,
|
||||||
|
x: xsurface,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
slf.x.xwindow.set(Some(slf.clone()));
|
slf.x.xwindow.set(Some(slf.clone()));
|
||||||
slf.x.surface.set_toplevel(Some(slf.clone()));
|
slf.x.surface.set_toplevel(Some(slf.clone()));
|
||||||
|
|
@ -344,12 +347,7 @@ impl Node for Xwindow {
|
||||||
}
|
}
|
||||||
let rect = self.x.surface.buffer_abs_pos.get();
|
let rect = self.x.surface.buffer_abs_pos.get();
|
||||||
if x < rect.width() && y < rect.height() {
|
if x < rect.width() && y < rect.height() {
|
||||||
tree.push(FoundNode {
|
return self.x.surface.find_tree_at_(x, y, tree);
|
||||||
node: self.x.surface.clone(),
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
});
|
|
||||||
return FindTreeResult::AcceptsInput;
|
|
||||||
}
|
}
|
||||||
FindTreeResult::Other
|
FindTreeResult::Other
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ impl XdgSurfaceRequestHandler for XdgSurface {
|
||||||
);
|
);
|
||||||
return Err(XdgSurfaceError::AlreadyConstructed);
|
return Err(XdgSurfaceError::AlreadyConstructed);
|
||||||
}
|
}
|
||||||
let toplevel = Rc::new(XdgToplevel::new(req.id, slf));
|
let toplevel = Rc::new_cyclic(|weak| XdgToplevel::new(req.id, slf, weak));
|
||||||
track!(self.surface.client, toplevel);
|
track!(self.surface.client, toplevel);
|
||||||
self.surface.client.add_client_obj(&toplevel)?;
|
self.surface.client.add_client_obj(&toplevel)?;
|
||||||
self.ext.set(Some(toplevel.clone()));
|
self.ext.set(Some(toplevel.clone()));
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ use {
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
mem,
|
mem,
|
||||||
rc::Rc,
|
rc::{Rc, Weak},
|
||||||
},
|
},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
@ -115,7 +115,7 @@ impl Debug for XdgToplevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XdgToplevel {
|
impl XdgToplevel {
|
||||||
pub fn new(id: XdgToplevelId, surface: &Rc<XdgSurface>) -> Self {
|
pub fn new(id: XdgToplevelId, surface: &Rc<XdgSurface>, slf: &Weak<Self>) -> Self {
|
||||||
let mut states = AHashSet::new();
|
let mut states = AHashSet::new();
|
||||||
states.insert(STATE_TILED_LEFT);
|
states.insert(STATE_TILED_LEFT);
|
||||||
states.insert(STATE_TILED_RIGHT);
|
states.insert(STATE_TILED_RIGHT);
|
||||||
|
|
@ -141,6 +141,7 @@ impl XdgToplevel {
|
||||||
state,
|
state,
|
||||||
String::new(),
|
String::new(),
|
||||||
Some(surface.surface.client.clone()),
|
Some(surface.surface.client.clone()),
|
||||||
|
slf,
|
||||||
),
|
),
|
||||||
drag: Default::default(),
|
drag: Default::default(),
|
||||||
is_mapped: Cell::new(false),
|
is_mapped: Cell::new(false),
|
||||||
|
|
|
||||||
|
|
@ -740,20 +740,28 @@ macro_rules! ei_object_base {
|
||||||
|
|
||||||
macro_rules! logical_to_client_wire_scale {
|
macro_rules! logical_to_client_wire_scale {
|
||||||
($client:expr, $($field:expr),+ $(,)?) => {
|
($client:expr, $($field:expr),+ $(,)?) => {
|
||||||
if let Some(scale) = $client.wire_scale.get() {
|
#[expect(clippy::allow_attributes)]
|
||||||
$(
|
{
|
||||||
$field = $field * scale;
|
#[allow(clippy::assign_op_pattern)]
|
||||||
)+
|
if let Some(scale) = $client.wire_scale.get() {
|
||||||
|
$(
|
||||||
|
$field = $field * scale;
|
||||||
|
)+
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! client_wire_scale_to_logical {
|
macro_rules! client_wire_scale_to_logical {
|
||||||
($client:expr, $($field:expr),+ $(,)?) => {
|
($client:expr, $($field:expr),+ $(,)?) => {
|
||||||
if let Some(scale) = $client.wire_scale.get() {
|
#[expect(clippy::allow_attributes)]
|
||||||
$(
|
{
|
||||||
$field = $field / scale;
|
#[allow(clippy::assign_op_pattern)]
|
||||||
)+
|
if let Some(scale) = $client.wire_scale.get() {
|
||||||
|
$(
|
||||||
|
$field = $field / scale;
|
||||||
|
)+
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ mod ptl_display;
|
||||||
mod ptl_remote_desktop;
|
mod ptl_remote_desktop;
|
||||||
mod ptl_render_ctx;
|
mod ptl_render_ctx;
|
||||||
mod ptl_screencast;
|
mod ptl_screencast;
|
||||||
|
mod ptl_session;
|
||||||
mod ptl_text;
|
mod ptl_text;
|
||||||
mod ptr_gui;
|
mod ptr_gui;
|
||||||
|
|
||||||
|
|
@ -16,12 +17,13 @@ use {
|
||||||
forker::ForkerError,
|
forker::ForkerError,
|
||||||
io_uring::IoUring,
|
io_uring::IoUring,
|
||||||
logger::Logger,
|
logger::Logger,
|
||||||
pipewire::pw_con::{PwConHolder, PwConOwner},
|
pipewire::pw_con::{PwCon, PwConHolder, PwConOwner},
|
||||||
portal::{
|
portal::{
|
||||||
ptl_display::{watch_displays, PortalDisplay, PortalDisplayId},
|
ptl_display::{watch_displays, PortalDisplay, PortalDisplayId},
|
||||||
ptl_remote_desktop::{add_remote_desktop_dbus_members, RemoteDesktopSession},
|
ptl_remote_desktop::add_remote_desktop_dbus_members,
|
||||||
ptl_render_ctx::PortalRenderCtx,
|
ptl_render_ctx::PortalRenderCtx,
|
||||||
ptl_screencast::{add_screencast_dbus_members, ScreencastSession},
|
ptl_screencast::add_screencast_dbus_members,
|
||||||
|
ptl_session::PortalSession,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clone3::{fork_with_pidfd, Forked},
|
clone3::{fork_with_pidfd, Forked},
|
||||||
|
|
@ -200,11 +202,11 @@ async fn run_async(
|
||||||
wheel,
|
wheel,
|
||||||
displays: Default::default(),
|
displays: Default::default(),
|
||||||
dbus,
|
dbus,
|
||||||
screencasts: Default::default(),
|
sessions: Default::default(),
|
||||||
remote_desktop_sessions: Default::default(),
|
|
||||||
next_id: NumCell::new(1),
|
next_id: NumCell::new(1),
|
||||||
render_ctxs: Default::default(),
|
render_ctxs: Default::default(),
|
||||||
dma_buf_ids: Default::default(),
|
dma_buf_ids: Default::default(),
|
||||||
|
pw_con: pw_con.as_ref().map(|c| c.con.clone()),
|
||||||
});
|
});
|
||||||
if let Some(pw_con) = &pw_con {
|
if let Some(pw_con) = &pw_con {
|
||||||
pw_con.con.owner.set(Some(state.clone()));
|
pw_con.con.owner.set(Some(state.clone()));
|
||||||
|
|
@ -295,11 +297,11 @@ struct PortalState {
|
||||||
wheel: Rc<Wheel>,
|
wheel: Rc<Wheel>,
|
||||||
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
|
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
|
||||||
dbus: Rc<DbusSocket>,
|
dbus: Rc<DbusSocket>,
|
||||||
screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
sessions: CopyHashMap<String, Rc<PortalSession>>,
|
||||||
remote_desktop_sessions: CopyHashMap<String, Rc<RemoteDesktopSession>>,
|
|
||||||
next_id: NumCell<u32>,
|
next_id: NumCell<u32>,
|
||||||
render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>,
|
render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>,
|
||||||
dma_buf_ids: Rc<DmaBufIds>,
|
dma_buf_ids: Rc<DmaBufIds>,
|
||||||
|
pw_con: Option<Rc<PwCon>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortalState {
|
impl PortalState {
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,19 @@ use {
|
||||||
ifs::wl_seat::POINTER,
|
ifs::wl_seat::POINTER,
|
||||||
object::Version,
|
object::Version,
|
||||||
portal::{
|
portal::{
|
||||||
ptl_remote_desktop::RemoteDesktopSession,
|
|
||||||
ptl_render_ctx::{PortalRenderCtx, PortalServerRenderCtx},
|
ptl_render_ctx::{PortalRenderCtx, PortalServerRenderCtx},
|
||||||
ptl_screencast::ScreencastSession,
|
ptl_session::PortalSession,
|
||||||
ptr_gui::WindowData,
|
ptr_gui::WindowData,
|
||||||
PortalState,
|
PortalState,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
bitflags::BitflagsExt,
|
||||||
errorfmt::ErrorFmt, hash_map_ext::HashMapExt, oserror::OsError,
|
clonecell::CloneCell,
|
||||||
|
copyhashmap::CopyHashMap,
|
||||||
|
errorfmt::ErrorFmt,
|
||||||
|
hash_map_ext::HashMapExt,
|
||||||
|
opaque::{opaque, Opaque},
|
||||||
|
oserror::OsError,
|
||||||
},
|
},
|
||||||
video::drm::Drm,
|
video::drm::Drm,
|
||||||
wire::{
|
wire::{
|
||||||
|
|
@ -26,6 +30,8 @@ use {
|
||||||
usr_jay_output::{UsrJayOutput, UsrJayOutputOwner},
|
usr_jay_output::{UsrJayOutput, UsrJayOutputOwner},
|
||||||
usr_jay_pointer::UsrJayPointer,
|
usr_jay_pointer::UsrJayPointer,
|
||||||
usr_jay_render_ctx::UsrJayRenderCtxOwner,
|
usr_jay_render_ctx::UsrJayRenderCtxOwner,
|
||||||
|
usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner},
|
||||||
|
usr_jay_workspace_watcher::{UsrJayWorkspaceWatcher, UsrJayWorkspaceWatcherOwner},
|
||||||
usr_linux_dmabuf::UsrLinuxDmabuf,
|
usr_linux_dmabuf::UsrLinuxDmabuf,
|
||||||
usr_wl_compositor::UsrWlCompositor,
|
usr_wl_compositor::UsrWlCompositor,
|
||||||
usr_wl_output::{UsrWlOutput, UsrWlOutputOwner},
|
usr_wl_output::{UsrWlOutput, UsrWlOutputOwner},
|
||||||
|
|
@ -61,9 +67,11 @@ struct PortalDisplayPrelude {
|
||||||
shared_ids!(PortalDisplayId);
|
shared_ids!(PortalDisplayId);
|
||||||
pub struct PortalDisplay {
|
pub struct PortalDisplay {
|
||||||
pub id: PortalDisplayId,
|
pub id: PortalDisplayId,
|
||||||
|
pub unique_id: Opaque,
|
||||||
pub con: Rc<UsrCon>,
|
pub con: Rc<UsrCon>,
|
||||||
pub(super) state: Rc<PortalState>,
|
pub(super) state: Rc<PortalState>,
|
||||||
registry: Rc<UsrWlRegistry>,
|
registry: Rc<UsrWlRegistry>,
|
||||||
|
_workspace_watcher: Rc<UsrJayWorkspaceWatcher>,
|
||||||
pub dmabuf: CloneCell<Option<Rc<UsrLinuxDmabuf>>>,
|
pub dmabuf: CloneCell<Option<Rc<UsrLinuxDmabuf>>>,
|
||||||
|
|
||||||
pub jc: Rc<UsrJayCompositor>,
|
pub jc: Rc<UsrJayCompositor>,
|
||||||
|
|
@ -75,10 +83,10 @@ pub struct PortalDisplay {
|
||||||
|
|
||||||
pub outputs: CopyHashMap<u32, Rc<PortalOutput>>,
|
pub outputs: CopyHashMap<u32, Rc<PortalOutput>>,
|
||||||
pub seats: CopyHashMap<u32, Rc<PortalSeat>>,
|
pub seats: CopyHashMap<u32, Rc<PortalSeat>>,
|
||||||
|
pub workspaces: CopyHashMap<u32, Rc<UsrJayWorkspace>>,
|
||||||
|
|
||||||
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
|
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
|
||||||
pub screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
pub sessions: CopyHashMap<String, Rc<PortalSession>>,
|
||||||
pub remote_desktop_sessions: CopyHashMap<String, Rc<RemoteDesktopSession>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PortalOutput {
|
pub struct PortalOutput {
|
||||||
|
|
@ -215,7 +223,7 @@ impl UsrJayRenderCtxOwner for PortalDisplay {
|
||||||
impl UsrConOwner for PortalDisplay {
|
impl UsrConOwner for PortalDisplay {
|
||||||
fn killed(&self) {
|
fn killed(&self) {
|
||||||
log::info!("Removing display {}", self.id);
|
log::info!("Removing display {}", self.id);
|
||||||
for sc in self.screencasts.lock().drain_values() {
|
for sc in self.sessions.lock().drain_values() {
|
||||||
sc.kill();
|
sc.kill();
|
||||||
}
|
}
|
||||||
self.windows.clear();
|
self.windows.clear();
|
||||||
|
|
@ -243,6 +251,20 @@ impl UsrWlRegistryOwner for PortalDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UsrJayWorkspaceWatcherOwner for PortalDisplay {
|
||||||
|
fn new(self: Rc<Self>, ev: Rc<UsrJayWorkspace>, linear_id: u32) {
|
||||||
|
ev.owner.set(Some(self.clone()));
|
||||||
|
self.workspaces.set(linear_id, ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrJayWorkspaceOwner for PortalDisplay {
|
||||||
|
fn destroyed(&self, ws: &UsrJayWorkspace) {
|
||||||
|
self.workspaces.remove(&ws.linear_id.get());
|
||||||
|
self.con.remove_obj(ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl UsrJayOutputOwner for PortalOutput {
|
impl UsrJayOutputOwner for PortalOutput {
|
||||||
fn destroyed(&self) {
|
fn destroyed(&self) {
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
@ -323,7 +345,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
||||||
con: dpy.con.clone(),
|
con: dpy.con.clone(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
caps: Default::default(),
|
caps: Default::default(),
|
||||||
version: Version(version.min(9)),
|
version: Version(version.min(12)),
|
||||||
});
|
});
|
||||||
dpy.con.add_object(jc.clone());
|
dpy.con.add_object(jc.clone());
|
||||||
dpy.registry.request_bind(name, jc.version.0, jc.deref());
|
dpy.registry.request_bind(name, jc.version.0, jc.deref());
|
||||||
|
|
@ -398,12 +420,15 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
||||||
let comp = get!(comp_opt, WlCompositor);
|
let comp = get!(comp_opt, WlCompositor);
|
||||||
let fsm = get!(fsm_opt, WpFractionalScaleManagerV1);
|
let fsm = get!(fsm_opt, WpFractionalScaleManagerV1);
|
||||||
let vp = get!(vp_opt, WpViewporter);
|
let vp = get!(vp_opt, WpViewporter);
|
||||||
|
let ww = jc.watch_workspaces();
|
||||||
|
|
||||||
let dpy = Rc::new(PortalDisplay {
|
let dpy = Rc::new(PortalDisplay {
|
||||||
id: dpy.state.id(),
|
id: dpy.state.id(),
|
||||||
|
unique_id: opaque(),
|
||||||
con: dpy.con.clone(),
|
con: dpy.con.clone(),
|
||||||
state: dpy.state.clone(),
|
state: dpy.state.clone(),
|
||||||
registry: dpy.registry.clone(),
|
registry: dpy.registry.clone(),
|
||||||
|
_workspace_watcher: ww.clone(),
|
||||||
dmabuf: CloneCell::new(dmabuf_opt),
|
dmabuf: CloneCell::new(dmabuf_opt),
|
||||||
jc,
|
jc,
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
|
|
@ -414,13 +439,14 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
||||||
fsm,
|
fsm,
|
||||||
vp,
|
vp,
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
screencasts: Default::default(),
|
sessions: Default::default(),
|
||||||
remote_desktop_sessions: Default::default(),
|
workspaces: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
dpy.state.displays.set(dpy.id, dpy.clone());
|
dpy.state.displays.set(dpy.id, dpy.clone());
|
||||||
dpy.con.owner.set(Some(dpy.clone()));
|
dpy.con.owner.set(Some(dpy.clone()));
|
||||||
dpy.registry.owner.set(Some(dpy.clone()));
|
dpy.registry.owner.set(Some(dpy.clone()));
|
||||||
|
ww.owner.set(Some(dpy.clone()));
|
||||||
|
|
||||||
let jrc = dpy.jc.get_render_context();
|
let jrc = dpy.jc.get_render_context();
|
||||||
jrc.owner.set(Some(dpy.clone()));
|
jrc.owner.set(Some(dpy.clone()));
|
||||||
|
|
@ -464,6 +490,7 @@ fn add_output(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
|
||||||
con: dpy.con.clone(),
|
con: dpy.con.clone(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
version: Version(version.min(4)),
|
version: Version(version.min(4)),
|
||||||
|
name: Default::default(),
|
||||||
});
|
});
|
||||||
dpy.con.add_object(wl.clone());
|
dpy.con.add_object(wl.clone());
|
||||||
dpy.registry.request_bind(name, wl.version.0, wl.deref());
|
dpy.registry.request_bind(name, wl.version.0, wl.deref());
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,18 @@ mod remote_desktop_gui;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply, FALSE},
|
dbus::{prelude::Variant, DbusObject, PendingReply},
|
||||||
ifs::jay_compositor::CREATE_EI_SESSION_SINCE,
|
ifs::jay_compositor::CREATE_EI_SESSION_SINCE,
|
||||||
portal::{
|
portal::{
|
||||||
ptl_display::{PortalDisplay, PortalDisplayId},
|
ptl_display::{PortalDisplay, PortalDisplayId},
|
||||||
ptl_remote_desktop::remote_desktop_gui::SelectionGui,
|
ptl_remote_desktop::remote_desktop_gui::SelectionGui,
|
||||||
|
ptl_screencast::ScreencastPhase,
|
||||||
|
ptl_session::{PortalSession, PortalSessionReply},
|
||||||
PortalState, PORTAL_SUCCESS,
|
PortalState, PORTAL_SUCCESS,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::{CloneCell, UnsafeCellCloneSafe},
|
clonecell::{CloneCell, UnsafeCellCloneSafe},
|
||||||
copyhashmap::CopyHashMap,
|
copyhashmap::CopyHashMap,
|
||||||
hash_map_ext::HashMapExt,
|
|
||||||
},
|
},
|
||||||
wire_dbus::{
|
wire_dbus::{
|
||||||
org,
|
org,
|
||||||
|
|
@ -21,24 +22,15 @@ use {
|
||||||
ConnectToEIS, ConnectToEISReply, CreateSession, CreateSessionReply,
|
ConnectToEIS, ConnectToEISReply, CreateSession, CreateSessionReply,
|
||||||
SelectDevices, SelectDevicesReply, Start, StartReply,
|
SelectDevices, SelectDevicesReply, Start, StartReply,
|
||||||
},
|
},
|
||||||
session::{CloseReply as SessionCloseReply, Closed},
|
session::CloseReply as SessionCloseReply,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wl_usr::usr_ifs::usr_jay_ei_session::{UsrJayEiSession, UsrJayEiSessionOwner},
|
wl_usr::usr_ifs::usr_jay_ei_session::{UsrJayEiSession, UsrJayEiSessionOwner},
|
||||||
},
|
},
|
||||||
std::{borrow::Cow, cell::Cell, ops::Deref, rc::Rc},
|
std::{cell::Cell, ops::Deref, rc::Rc},
|
||||||
uapi::OwnedFd,
|
uapi::OwnedFd,
|
||||||
};
|
};
|
||||||
|
|
||||||
shared_ids!(ScreencastSessionId);
|
|
||||||
pub struct RemoteDesktopSession {
|
|
||||||
_id: ScreencastSessionId,
|
|
||||||
state: Rc<PortalState>,
|
|
||||||
pub app: String,
|
|
||||||
session_obj: DbusObject,
|
|
||||||
pub phase: CloneCell<RemoteDesktopPhase>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum RemoteDesktopPhase {
|
pub enum RemoteDesktopPhase {
|
||||||
Init,
|
Init,
|
||||||
|
|
@ -52,25 +44,23 @@ pub enum RemoteDesktopPhase {
|
||||||
unsafe impl UnsafeCellCloneSafe for RemoteDesktopPhase {}
|
unsafe impl UnsafeCellCloneSafe for RemoteDesktopPhase {}
|
||||||
|
|
||||||
pub struct SelectingDisplay {
|
pub struct SelectingDisplay {
|
||||||
pub session: Rc<RemoteDesktopSession>,
|
pub session: Rc<PortalSession>,
|
||||||
pub request_obj: Rc<DbusObject>,
|
pub request_obj: Rc<DbusObject>,
|
||||||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
|
||||||
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StartingRemoteDesktop {
|
pub struct StartingRemoteDesktop {
|
||||||
pub session: Rc<RemoteDesktopSession>,
|
pub session: Rc<PortalSession>,
|
||||||
pub _request_obj: Rc<DbusObject>,
|
pub request_obj: Rc<DbusObject>,
|
||||||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
|
||||||
pub dpy: Rc<PortalDisplay>,
|
pub dpy: Rc<PortalDisplay>,
|
||||||
pub ei_session: Rc<UsrJayEiSession>,
|
pub ei_session: Rc<UsrJayEiSession>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StartedRemoteDesktop {
|
pub struct StartedRemoteDesktop {
|
||||||
session: Rc<RemoteDesktopSession>,
|
pub session: Rc<PortalSession>,
|
||||||
dpy: Rc<PortalDisplay>,
|
pub dpy: Rc<PortalDisplay>,
|
||||||
ei_session: Rc<UsrJayEiSession>,
|
pub ei_session: Rc<UsrJayEiSession>,
|
||||||
ei_fd: Cell<Option<Rc<OwnedFd>>>,
|
pub ei_fd: Cell<Option<Rc<OwnedFd>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
|
@ -83,34 +73,6 @@ bitflags! {
|
||||||
|
|
||||||
impl UsrJayEiSessionOwner for StartingRemoteDesktop {
|
impl UsrJayEiSessionOwner for StartingRemoteDesktop {
|
||||||
fn created(&self, fd: &Rc<OwnedFd>) {
|
fn created(&self, fd: &Rc<OwnedFd>) {
|
||||||
{
|
|
||||||
let inner_type = DynamicType::DictEntry(
|
|
||||||
Box::new(DynamicType::String),
|
|
||||||
Box::new(DynamicType::Variant),
|
|
||||||
);
|
|
||||||
let kt = DynamicType::Struct(vec![
|
|
||||||
DynamicType::U32,
|
|
||||||
DynamicType::Array(Box::new(inner_type.clone())),
|
|
||||||
]);
|
|
||||||
let variants = [
|
|
||||||
DictEntry {
|
|
||||||
key: "devices".into(),
|
|
||||||
value: Variant::U32(DeviceTypes::all().0),
|
|
||||||
},
|
|
||||||
DictEntry {
|
|
||||||
key: "clipboard_enabled".into(),
|
|
||||||
value: Variant::Bool(FALSE),
|
|
||||||
},
|
|
||||||
DictEntry {
|
|
||||||
key: "streams".into(),
|
|
||||||
value: Variant::Array(kt, vec![]),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
self.reply.ok(&StartReply {
|
|
||||||
response: PORTAL_SUCCESS,
|
|
||||||
results: Cow::Borrowed(&variants[..]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let started = Rc::new(StartedRemoteDesktop {
|
let started = Rc::new(StartedRemoteDesktop {
|
||||||
session: self.session.clone(),
|
session: self.session.clone(),
|
||||||
dpy: self.dpy.clone(),
|
dpy: self.dpy.clone(),
|
||||||
|
|
@ -118,14 +80,23 @@ impl UsrJayEiSessionOwner for StartingRemoteDesktop {
|
||||||
ei_fd: Cell::new(Some(fd.clone())),
|
ei_fd: Cell::new(Some(fd.clone())),
|
||||||
});
|
});
|
||||||
self.session
|
self.session
|
||||||
.phase
|
.rd_phase
|
||||||
.set(RemoteDesktopPhase::Started(started.clone()));
|
.set(RemoteDesktopPhase::Started(started.clone()));
|
||||||
started.ei_session.owner.set(Some(started.clone()));
|
started.ei_session.owner.set(Some(started.clone()));
|
||||||
|
if let ScreencastPhase::SourcesSelected(s) = self.session.sc_phase.get() {
|
||||||
|
self.session.screencast_restore(
|
||||||
|
&self.request_obj,
|
||||||
|
s.restore_data.take(),
|
||||||
|
Some(self.dpy.clone()),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.session.send_start_reply(None, None, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn failed(&self, reason: &str) {
|
fn failed(&self, reason: &str) {
|
||||||
log::error!("Could not create session: {}", reason);
|
log::error!("Could not create session: {}", reason);
|
||||||
self.reply.err(reason);
|
self.session.reply_err(reason);
|
||||||
self.session.kill();
|
self.session.kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,60 +108,28 @@ impl SelectingDisplay {
|
||||||
let ei_session = builder.commit();
|
let ei_session = builder.commit();
|
||||||
let starting = Rc::new(StartingRemoteDesktop {
|
let starting = Rc::new(StartingRemoteDesktop {
|
||||||
session: self.session.clone(),
|
session: self.session.clone(),
|
||||||
_request_obj: self.request_obj.clone(),
|
request_obj: self.request_obj.clone(),
|
||||||
reply: self.reply.clone(),
|
|
||||||
dpy: dpy.clone(),
|
dpy: dpy.clone(),
|
||||||
ei_session,
|
ei_session,
|
||||||
});
|
});
|
||||||
self.session
|
self.session
|
||||||
.phase
|
.rd_phase
|
||||||
.set(RemoteDesktopPhase::Starting(starting.clone()));
|
.set(RemoteDesktopPhase::Starting(starting.clone()));
|
||||||
starting.ei_session.owner.set(Some(starting.clone()));
|
starting.ei_session.owner.set(Some(starting.clone()));
|
||||||
dpy.remote_desktop_sessions.set(
|
dpy.sessions.set(
|
||||||
self.session.session_obj.path().to_owned(),
|
self.session.session_obj.path().to_owned(),
|
||||||
self.session.clone(),
|
self.session.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoteDesktopSession {
|
impl PortalSession {
|
||||||
pub(super) fn kill(&self) {
|
|
||||||
self.session_obj.emit_signal(&Closed);
|
|
||||||
self.state
|
|
||||||
.remote_desktop_sessions
|
|
||||||
.remove(self.session_obj.path());
|
|
||||||
match self.phase.set(RemoteDesktopPhase::Terminated) {
|
|
||||||
RemoteDesktopPhase::Init => {}
|
|
||||||
RemoteDesktopPhase::DevicesSelected => {}
|
|
||||||
RemoteDesktopPhase::Terminated => {}
|
|
||||||
RemoteDesktopPhase::Selecting(s) => {
|
|
||||||
s.reply.err("Session has been terminated");
|
|
||||||
for gui in s.guis.lock().drain_values() {
|
|
||||||
gui.kill(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RemoteDesktopPhase::Starting(s) => {
|
|
||||||
s.reply.err("Session has been terminated");
|
|
||||||
s.ei_session.con.remove_obj(s.ei_session.deref());
|
|
||||||
s.dpy
|
|
||||||
.remote_desktop_sessions
|
|
||||||
.remove(self.session_obj.path());
|
|
||||||
}
|
|
||||||
RemoteDesktopPhase::Started(s) => {
|
|
||||||
s.ei_session.con.remove_obj(s.ei_session.deref());
|
|
||||||
s.dpy
|
|
||||||
.remote_desktop_sessions
|
|
||||||
.remove(self.session_obj.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dbus_select_devices(
|
fn dbus_select_devices(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
_req: SelectDevices,
|
_req: SelectDevices,
|
||||||
reply: PendingReply<SelectDevicesReply<'static>>,
|
reply: PendingReply<SelectDevicesReply<'static>>,
|
||||||
) {
|
) {
|
||||||
match self.phase.get() {
|
match self.rd_phase.get() {
|
||||||
RemoteDesktopPhase::Init => {}
|
RemoteDesktopPhase::Init => {}
|
||||||
_ => {
|
_ => {
|
||||||
self.kill();
|
self.kill();
|
||||||
|
|
@ -198,15 +137,19 @@ impl RemoteDesktopSession {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.phase.set(RemoteDesktopPhase::DevicesSelected);
|
self.rd_phase.set(RemoteDesktopPhase::DevicesSelected);
|
||||||
reply.ok(&SelectDevicesReply {
|
reply.ok(&SelectDevicesReply {
|
||||||
response: PORTAL_SUCCESS,
|
response: PORTAL_SUCCESS,
|
||||||
results: Default::default(),
|
results: Default::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
|
fn dbus_start_remote_desktop(
|
||||||
match self.phase.get() {
|
self: &Rc<Self>,
|
||||||
|
req: Start<'_>,
|
||||||
|
reply: PendingReply<StartReply<'static>>,
|
||||||
|
) {
|
||||||
|
match self.rd_phase.get() {
|
||||||
RemoteDesktopPhase::DevicesSelected => {}
|
RemoteDesktopPhase::DevicesSelected => {}
|
||||||
_ => {
|
_ => {
|
||||||
self.kill();
|
self.kill();
|
||||||
|
|
@ -243,11 +186,12 @@ impl RemoteDesktopSession {
|
||||||
reply.err("There are no running displays");
|
reply.err("There are no running displays");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.phase
|
self.start_reply
|
||||||
|
.set(Some(PortalSessionReply::RemoteDesktop(reply)));
|
||||||
|
self.rd_phase
|
||||||
.set(RemoteDesktopPhase::Selecting(Rc::new(SelectingDisplay {
|
.set(RemoteDesktopPhase::Selecting(Rc::new(SelectingDisplay {
|
||||||
session: self.clone(),
|
session: self.clone(),
|
||||||
request_obj: Rc::new(request_obj),
|
request_obj: Rc::new(request_obj),
|
||||||
reply: Rc::new(reply),
|
|
||||||
guis,
|
guis,
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
@ -257,7 +201,7 @@ impl RemoteDesktopSession {
|
||||||
_req: ConnectToEIS,
|
_req: ConnectToEIS,
|
||||||
reply: PendingReply<ConnectToEISReply>,
|
reply: PendingReply<ConnectToEISReply>,
|
||||||
) {
|
) {
|
||||||
let RemoteDesktopPhase::Started(started) = self.phase.get() else {
|
let RemoteDesktopPhase::Started(started) = self.rd_phase.get() else {
|
||||||
self.kill();
|
self.kill();
|
||||||
reply.err("Sources have already been selected");
|
reply.err("Sources have already been selected");
|
||||||
return;
|
return;
|
||||||
|
|
@ -305,10 +249,7 @@ fn dbus_create_session(
|
||||||
reply: PendingReply<CreateSessionReply<'static>>,
|
reply: PendingReply<CreateSessionReply<'static>>,
|
||||||
) {
|
) {
|
||||||
log::info!("Create remote desktop session {:#?}", req);
|
log::info!("Create remote desktop session {:#?}", req);
|
||||||
if state
|
if state.sessions.contains(req.session_handle.0.deref()) {
|
||||||
.remote_desktop_sessions
|
|
||||||
.contains(req.session_handle.0.deref())
|
|
||||||
{
|
|
||||||
reply.err("Session already exists");
|
reply.err("Session already exists");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -319,12 +260,15 @@ fn dbus_create_session(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let session = Rc::new(RemoteDesktopSession {
|
let session = Rc::new(PortalSession {
|
||||||
_id: state.id(),
|
_id: state.id(),
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
|
pw_con: state.pw_con.clone(),
|
||||||
app: req.app_id.to_string(),
|
app: req.app_id.to_string(),
|
||||||
session_obj: obj,
|
session_obj: obj,
|
||||||
phase: CloneCell::new(RemoteDesktopPhase::Init),
|
sc_phase: CloneCell::new(ScreencastPhase::Init),
|
||||||
|
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
|
||||||
|
start_reply: Default::default(),
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
use org::freedesktop::impl_::portal::session::*;
|
use org::freedesktop::impl_::portal::session::*;
|
||||||
|
|
@ -336,7 +280,7 @@ fn dbus_create_session(
|
||||||
session.session_obj.set_property::<version>(Variant::U32(2));
|
session.session_obj.set_property::<version>(Variant::U32(2));
|
||||||
}
|
}
|
||||||
state
|
state
|
||||||
.remote_desktop_sessions
|
.sessions
|
||||||
.set(req.session_handle.0.to_string(), session);
|
.set(req.session_handle.0.to_string(), session);
|
||||||
reply.ok(&CreateSessionReply {
|
reply.ok(&CreateSessionReply {
|
||||||
response: PORTAL_SUCCESS,
|
response: PORTAL_SUCCESS,
|
||||||
|
|
@ -356,7 +300,7 @@ fn dbus_select_devices(
|
||||||
|
|
||||||
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
|
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
|
||||||
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
|
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
|
||||||
s.dbus_start(req, reply);
|
s.dbus_start_remote_desktop(req, reply);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,8 +318,8 @@ fn get_session<T>(
|
||||||
state: &Rc<PortalState>,
|
state: &Rc<PortalState>,
|
||||||
reply: &PendingReply<T>,
|
reply: &PendingReply<T>,
|
||||||
handle: &str,
|
handle: &str,
|
||||||
) -> Option<Rc<RemoteDesktopSession>> {
|
) -> Option<Rc<PortalSession>> {
|
||||||
let res = state.remote_desktop_sessions.get(handle);
|
let res = state.sessions.get(handle);
|
||||||
if res.is_none() {
|
if res.is_none() {
|
||||||
let msg = format!("Remote desktop session `{}` does not exist", handle);
|
let msg = format!("Remote desktop session `{}` does not exist", handle);
|
||||||
reply.err(&msg);
|
reply.err(&msg);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use {
|
||||||
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
|
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
|
||||||
portal::{
|
portal::{
|
||||||
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
||||||
ptl_remote_desktop::{RemoteDesktopPhase, RemoteDesktopSession},
|
ptl_remote_desktop::{PortalSession, RemoteDesktopPhase},
|
||||||
ptr_gui::{
|
ptr_gui::{
|
||||||
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
|
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
|
||||||
OverlayWindowOwner,
|
OverlayWindowOwner,
|
||||||
|
|
@ -19,7 +19,7 @@ const H_MARGIN: f32 = 30.0;
|
||||||
const V_MARGIN: f32 = 20.0;
|
const V_MARGIN: f32 = 20.0;
|
||||||
|
|
||||||
pub struct SelectionGui {
|
pub struct SelectionGui {
|
||||||
remote_desktop_session: Rc<RemoteDesktopSession>,
|
remote_desktop_session: Rc<PortalSession>,
|
||||||
dpy: Rc<PortalDisplay>,
|
dpy: Rc<PortalDisplay>,
|
||||||
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
|
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ impl SelectionGui {
|
||||||
for surface in self.surfaces.lock().drain_values() {
|
for surface in self.surfaces.lock().drain_values() {
|
||||||
surface.overlay.data.kill(false);
|
surface.overlay.data.kill(false);
|
||||||
}
|
}
|
||||||
if let RemoteDesktopPhase::Selecting(s) = self.remote_desktop_session.phase.get() {
|
if let RemoteDesktopPhase::Selecting(s) = self.remote_desktop_session.rd_phase.get() {
|
||||||
s.guis.remove(&self.dpy.id);
|
s.guis.remove(&self.dpy.id);
|
||||||
if upwards && s.guis.is_empty() {
|
if upwards && s.guis.is_empty() {
|
||||||
self.remote_desktop_session.kill();
|
self.remote_desktop_session.kill();
|
||||||
|
|
@ -99,7 +99,7 @@ impl OverlayWindowOwner for SelectionGuiSurface {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionGui {
|
impl SelectionGui {
|
||||||
pub fn new(ss: &Rc<RemoteDesktopSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
|
pub fn new(ss: &Rc<PortalSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
|
||||||
let gui = Rc::new(SelectionGui {
|
let gui = Rc::new(SelectionGui {
|
||||||
remote_desktop_session: ss.clone(),
|
remote_desktop_session: ss.clone(),
|
||||||
dpy: dpy.clone(),
|
dpy: dpy.clone(),
|
||||||
|
|
@ -130,7 +130,7 @@ impl ButtonOwner for StaticButton {
|
||||||
match self.role {
|
match self.role {
|
||||||
ButtonRole::Accept => {
|
ButtonRole::Accept => {
|
||||||
log::info!("User has accepted the request");
|
log::info!("User has accepted the request");
|
||||||
let selecting = match self.surface.gui.remote_desktop_session.phase.get() {
|
let selecting = match self.surface.gui.remote_desktop_session.rd_phase.get() {
|
||||||
RemoteDesktopPhase::Selecting(selecting) => selecting,
|
RemoteDesktopPhase::Selecting(selecting) => selecting,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ mod screencast_gui;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING},
|
allocator::{AllocatorError, BufferObject, BufferUsage, BO_USE_RENDERING},
|
||||||
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply},
|
dbus::{prelude::Variant, DbusObject, DictEntry, PendingReply},
|
||||||
format::{Format, XRGB8888},
|
format::{Format, XRGB8888},
|
||||||
ifs::jay_screencast::CLIENT_BUFFERS_SINCE,
|
ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE},
|
||||||
pipewire::{
|
pipewire::{
|
||||||
pw_con::PwCon,
|
pw_con::PwCon,
|
||||||
pw_ifs::pw_client_node::{
|
pw_ifs::pw_client_node::{
|
||||||
|
|
@ -21,14 +21,16 @@ use {
|
||||||
},
|
},
|
||||||
portal::{
|
portal::{
|
||||||
ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput},
|
ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput},
|
||||||
|
ptl_remote_desktop::RemoteDesktopPhase,
|
||||||
ptl_screencast::screencast_gui::SelectionGui,
|
ptl_screencast::screencast_gui::SelectionGui,
|
||||||
|
ptl_session::{PortalSession, PortalSessionReply},
|
||||||
PortalState, PORTAL_SUCCESS,
|
PortalState, PORTAL_SUCCESS,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::{CloneCell, UnsafeCellCloneSafe},
|
clonecell::{CloneCell, UnsafeCellCloneSafe},
|
||||||
copyhashmap::CopyHashMap,
|
copyhashmap::CopyHashMap,
|
||||||
errorfmt::ErrorFmt,
|
errorfmt::ErrorFmt,
|
||||||
hash_map_ext::HashMapExt,
|
opaque::Opaque,
|
||||||
},
|
},
|
||||||
video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER},
|
video::{dmabuf::DmaBuf, Modifier, LINEAR_MODIFIER},
|
||||||
wire::jay_screencast::Ready,
|
wire::jay_screencast::Ready,
|
||||||
|
|
@ -39,7 +41,7 @@ use {
|
||||||
CreateSession, CreateSessionReply, SelectSources, SelectSourcesReply, Start,
|
CreateSession, CreateSessionReply, SelectSources, SelectSourcesReply, Start,
|
||||||
StartReply,
|
StartReply,
|
||||||
},
|
},
|
||||||
session::{CloseReply as SessionCloseReply, Closed},
|
session::CloseReply as SessionCloseReply,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wl_usr::usr_ifs::{
|
wl_usr::usr_ifs::{
|
||||||
|
|
@ -54,6 +56,7 @@ use {
|
||||||
usr_wl_buffer::UsrWlBuffer,
|
usr_wl_buffer::UsrWlBuffer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
serde::{Deserialize, Serialize},
|
||||||
std::{
|
std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
|
@ -64,20 +67,10 @@ use {
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
shared_ids!(ScreencastSessionId);
|
|
||||||
pub struct ScreencastSession {
|
|
||||||
_id: ScreencastSessionId,
|
|
||||||
state: Rc<PortalState>,
|
|
||||||
pw_con: Rc<PwCon>,
|
|
||||||
pub app: String,
|
|
||||||
session_obj: DbusObject,
|
|
||||||
pub phase: CloneCell<ScreencastPhase>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ScreencastPhase {
|
pub enum ScreencastPhase {
|
||||||
Init,
|
Init,
|
||||||
SourcesSelected,
|
SourcesSelected(Rc<SourcesSelectedScreencast>),
|
||||||
Selecting(Rc<SelectingScreencast>),
|
Selecting(Rc<SelectingScreencast>),
|
||||||
SelectingWindow(Rc<SelectingWindowScreencast>),
|
SelectingWindow(Rc<SelectingWindowScreencast>),
|
||||||
SelectingWorkspace(Rc<SelectingWorkspaceScreencast>),
|
SelectingWorkspace(Rc<SelectingWorkspaceScreencast>),
|
||||||
|
|
@ -88,22 +81,27 @@ pub enum ScreencastPhase {
|
||||||
|
|
||||||
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
|
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
|
||||||
|
|
||||||
|
pub struct SourcesSelectedScreencast {
|
||||||
|
pub restore_data: Cell<Option<Result<RestoreData, RestoreError>>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SelectingScreencastCore {
|
pub struct SelectingScreencastCore {
|
||||||
pub session: Rc<ScreencastSession>,
|
pub session: Rc<PortalSession>,
|
||||||
pub request_obj: Rc<DbusObject>,
|
pub request_obj: Rc<DbusObject>,
|
||||||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SelectingScreencast {
|
pub struct SelectingScreencast {
|
||||||
pub core: SelectingScreencastCore,
|
pub core: SelectingScreencastCore,
|
||||||
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
||||||
|
pub restore_data: Cell<Option<RestoreData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SelectingWindowScreencast {
|
pub struct SelectingWindowScreencast {
|
||||||
pub core: SelectingScreencastCore,
|
pub core: SelectingScreencastCore,
|
||||||
pub dpy: Rc<PortalDisplay>,
|
pub dpy: Rc<PortalDisplay>,
|
||||||
pub selector: Rc<UsrJaySelectToplevel>,
|
pub selector: Rc<UsrJaySelectToplevel>,
|
||||||
|
pub restoring: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SelectingWorkspaceScreencast {
|
pub struct SelectingWorkspaceScreencast {
|
||||||
|
|
@ -113,9 +111,8 @@ pub struct SelectingWorkspaceScreencast {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StartingScreencast {
|
pub struct StartingScreencast {
|
||||||
pub session: Rc<ScreencastSession>,
|
pub session: Rc<PortalSession>,
|
||||||
pub _request_obj: Rc<DbusObject>,
|
pub _request_obj: Rc<DbusObject>,
|
||||||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
|
||||||
pub node: Rc<PwClientNode>,
|
pub node: Rc<PwClientNode>,
|
||||||
pub dpy: Rc<PortalDisplay>,
|
pub dpy: Rc<PortalDisplay>,
|
||||||
pub target: ScreencastTarget,
|
pub target: ScreencastTarget,
|
||||||
|
|
@ -123,26 +120,26 @@ pub struct StartingScreencast {
|
||||||
|
|
||||||
pub enum ScreencastTarget {
|
pub enum ScreencastTarget {
|
||||||
Output(Rc<PortalOutput>),
|
Output(Rc<PortalOutput>),
|
||||||
Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>),
|
Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>, bool),
|
||||||
Toplevel(Rc<UsrJayToplevel>),
|
Toplevel(Rc<UsrJayToplevel>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StartedScreencast {
|
pub struct StartedScreencast {
|
||||||
session: Rc<ScreencastSession>,
|
pub session: Rc<PortalSession>,
|
||||||
node: Rc<PwClientNode>,
|
pub node: Rc<PwClientNode>,
|
||||||
port: Rc<PwClientNodePort>,
|
pub port: Rc<PwClientNodePort>,
|
||||||
buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>,
|
pub buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>,
|
||||||
buffers: RefCell<Vec<DmaBuf>>,
|
pub buffers: RefCell<Vec<DmaBuf>>,
|
||||||
pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>,
|
pub pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>,
|
||||||
buffers_valid: Cell<bool>,
|
pub buffers_valid: Cell<bool>,
|
||||||
dpy: Rc<PortalDisplay>,
|
pub dpy: Rc<PortalDisplay>,
|
||||||
jay_screencast: Rc<UsrJayScreencast>,
|
pub jay_screencast: Rc<UsrJayScreencast>,
|
||||||
port_buffer_valid: Cell<bool>,
|
pub port_buffer_valid: Cell<bool>,
|
||||||
fixated: Cell<bool>,
|
pub fixated: Cell<bool>,
|
||||||
format: Cell<&'static Format>,
|
pub format: Cell<&'static Format>,
|
||||||
modifier: Cell<Modifier>,
|
pub modifier: Cell<Modifier>,
|
||||||
width: Cell<i32>,
|
pub width: Cell<i32>,
|
||||||
height: Cell<i32>,
|
pub height: Cell<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
|
@ -163,25 +160,17 @@ bitflags! {
|
||||||
impl PwClientNodeOwner for StartingScreencast {
|
impl PwClientNodeOwner for StartingScreencast {
|
||||||
fn bound_id(&self, node_id: u32) {
|
fn bound_id(&self, node_id: u32) {
|
||||||
{
|
{
|
||||||
let inner_type = DynamicType::DictEntry(
|
let output = match &self.target {
|
||||||
Box::new(DynamicType::String),
|
ScreencastTarget::Output(o) => Some(o),
|
||||||
Box::new(DynamicType::Variant),
|
ScreencastTarget::Workspace(o, _, _) => Some(o),
|
||||||
|
ScreencastTarget::Toplevel(_) => None,
|
||||||
|
};
|
||||||
|
let mapping_id = output.and_then(|o| o.wl.name.borrow().clone());
|
||||||
|
self.session.send_start_reply(
|
||||||
|
Some(node_id),
|
||||||
|
create_restore_data(&self.dpy, &self.target),
|
||||||
|
mapping_id.as_deref(),
|
||||||
);
|
);
|
||||||
let kt = DynamicType::Struct(vec![
|
|
||||||
DynamicType::U32,
|
|
||||||
DynamicType::Array(Box::new(inner_type.clone())),
|
|
||||||
]);
|
|
||||||
let variants = &[DictEntry {
|
|
||||||
key: "streams".into(),
|
|
||||||
value: Variant::Array(
|
|
||||||
kt,
|
|
||||||
vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])],
|
|
||||||
),
|
|
||||||
}];
|
|
||||||
self.reply.ok(&StartReply {
|
|
||||||
response: PORTAL_SUCCESS,
|
|
||||||
results: Cow::Borrowed(variants),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let mut supported_formats = PwClientNodePortSupportedFormats {
|
let mut supported_formats = PwClientNodePortSupportedFormats {
|
||||||
media_type: Some(SPA_MEDIA_TYPE_video),
|
media_type: Some(SPA_MEDIA_TYPE_video),
|
||||||
|
|
@ -221,7 +210,7 @@ impl PwClientNodeOwner for StartingScreencast {
|
||||||
jsc.set_output(&o.jay);
|
jsc.set_output(&o.jay);
|
||||||
jsc.set_allow_all_workspaces(true);
|
jsc.set_allow_all_workspaces(true);
|
||||||
}
|
}
|
||||||
ScreencastTarget::Workspace(o, ws) => {
|
ScreencastTarget::Workspace(o, ws, _) => {
|
||||||
jsc.set_output(&o.jay);
|
jsc.set_output(&o.jay);
|
||||||
jsc.allow_workspace(ws);
|
jsc.allow_workspace(ws);
|
||||||
}
|
}
|
||||||
|
|
@ -231,9 +220,10 @@ impl PwClientNodeOwner for StartingScreencast {
|
||||||
jsc.configure();
|
jsc.configure();
|
||||||
match &self.target {
|
match &self.target {
|
||||||
ScreencastTarget::Output(_) => {}
|
ScreencastTarget::Output(_) => {}
|
||||||
ScreencastTarget::Workspace(_, w) => {
|
ScreencastTarget::Workspace(_, w, true) => {
|
||||||
self.dpy.con.remove_obj(&**w);
|
self.dpy.con.remove_obj(&**w);
|
||||||
}
|
}
|
||||||
|
ScreencastTarget::Workspace(_, _, false) => {}
|
||||||
ScreencastTarget::Toplevel(t) => {
|
ScreencastTarget::Toplevel(t) => {
|
||||||
self.dpy.con.remove_obj(&**t);
|
self.dpy.con.remove_obj(&**t);
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +246,7 @@ impl PwClientNodeOwner for StartingScreencast {
|
||||||
height: Cell::new(1),
|
height: Cell::new(1),
|
||||||
});
|
});
|
||||||
self.session
|
self.session
|
||||||
.phase
|
.sc_phase
|
||||||
.set(ScreencastPhase::Started(started.clone()));
|
.set(ScreencastPhase::Started(started.clone()));
|
||||||
started.jay_screencast.owner.set(Some(started.clone()));
|
started.jay_screencast.owner.set(Some(started.clone()));
|
||||||
self.node.owner.set(Some(started.clone()));
|
self.node.owner.set(Some(started.clone()));
|
||||||
|
|
@ -409,7 +399,11 @@ impl StartedScreencast {
|
||||||
|
|
||||||
impl SelectingScreencastCore {
|
impl SelectingScreencastCore {
|
||||||
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
|
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
|
||||||
let node = self.session.pw_con.create_client_node(&[
|
let Some(pw_con) = &self.session.pw_con else {
|
||||||
|
self.session.kill();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let node = pw_con.create_client_node(&[
|
||||||
("media.class".to_string(), "Video/Source".to_string()),
|
("media.class".to_string(), "Video/Source".to_string()),
|
||||||
("node.name".to_string(), "jay-desktop-portal".to_string()),
|
("node.name".to_string(), "jay-desktop-portal".to_string()),
|
||||||
("node.driver".to_string(), "true".to_string()),
|
("node.driver".to_string(), "true".to_string()),
|
||||||
|
|
@ -417,75 +411,28 @@ impl SelectingScreencastCore {
|
||||||
let starting = Rc::new(StartingScreencast {
|
let starting = Rc::new(StartingScreencast {
|
||||||
session: self.session.clone(),
|
session: self.session.clone(),
|
||||||
_request_obj: self.request_obj.clone(),
|
_request_obj: self.request_obj.clone(),
|
||||||
reply: self.reply.clone(),
|
|
||||||
node,
|
node,
|
||||||
dpy: dpy.clone(),
|
dpy: dpy.clone(),
|
||||||
target,
|
target,
|
||||||
});
|
});
|
||||||
self.session
|
self.session
|
||||||
.phase
|
.sc_phase
|
||||||
.set(ScreencastPhase::Starting(starting.clone()));
|
.set(ScreencastPhase::Starting(starting.clone()));
|
||||||
starting.node.owner.set(Some(starting.clone()));
|
starting.node.owner.set(Some(starting.clone()));
|
||||||
dpy.screencasts.set(
|
dpy.sessions.set(
|
||||||
self.session.session_obj.path().to_owned(),
|
self.session.session_obj.path().to_owned(),
|
||||||
self.session.clone(),
|
self.session.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScreencastSession {
|
impl PortalSession {
|
||||||
pub(super) fn kill(&self) {
|
|
||||||
self.session_obj.emit_signal(&Closed);
|
|
||||||
self.state.screencasts.remove(self.session_obj.path());
|
|
||||||
match self.phase.set(ScreencastPhase::Terminated) {
|
|
||||||
ScreencastPhase::Init => {}
|
|
||||||
ScreencastPhase::SourcesSelected => {}
|
|
||||||
ScreencastPhase::Terminated => {}
|
|
||||||
ScreencastPhase::Selecting(s) => {
|
|
||||||
s.core.reply.err("Session has been terminated");
|
|
||||||
for gui in s.guis.lock().drain_values() {
|
|
||||||
gui.kill(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ScreencastPhase::SelectingWindow(s) => {
|
|
||||||
s.dpy.con.remove_obj(&*s.selector);
|
|
||||||
s.core.reply.err("Session has been terminated");
|
|
||||||
}
|
|
||||||
ScreencastPhase::SelectingWorkspace(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());
|
|
||||||
match &s.target {
|
|
||||||
ScreencastTarget::Output(_) => {}
|
|
||||||
ScreencastTarget::Workspace(_, w) => {
|
|
||||||
s.dpy.con.remove_obj(&**w);
|
|
||||||
}
|
|
||||||
ScreencastTarget::Toplevel(t) => {
|
|
||||||
s.dpy.con.remove_obj(&**t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ScreencastPhase::Started(s) => {
|
|
||||||
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
|
|
||||||
s.node.con.destroy_obj(s.node.deref());
|
|
||||||
s.dpy.screencasts.remove(self.session_obj.path());
|
|
||||||
for buffer in s.pending_buffers.borrow_mut().drain(..) {
|
|
||||||
s.dpy.con.remove_obj(&*buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dbus_select_sources(
|
fn dbus_select_sources(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
_req: SelectSources,
|
req: SelectSources,
|
||||||
reply: PendingReply<SelectSourcesReply<'static>>,
|
reply: PendingReply<SelectSourcesReply<'static>>,
|
||||||
) {
|
) {
|
||||||
match self.phase.get() {
|
match self.sc_phase.get() {
|
||||||
ScreencastPhase::Init => {}
|
ScreencastPhase::Init => {}
|
||||||
_ => {
|
_ => {
|
||||||
self.kill();
|
self.kill();
|
||||||
|
|
@ -493,24 +440,32 @@ impl ScreencastSession {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.phase.set(ScreencastPhase::SourcesSelected);
|
self.sc_phase.set(ScreencastPhase::SourcesSelected(Rc::new(
|
||||||
|
SourcesSelectedScreencast {
|
||||||
|
restore_data: Cell::new(get_restore_data(&req)),
|
||||||
|
},
|
||||||
|
)));
|
||||||
reply.ok(&SelectSourcesReply {
|
reply.ok(&SelectSourcesReply {
|
||||||
response: PORTAL_SUCCESS,
|
response: PORTAL_SUCCESS,
|
||||||
results: Default::default(),
|
results: Default::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
|
fn dbus_start_screencast(
|
||||||
match self.phase.get() {
|
self: &Rc<Self>,
|
||||||
ScreencastPhase::SourcesSelected => {}
|
req: Start<'_>,
|
||||||
|
reply: PendingReply<StartReply<'static>>,
|
||||||
|
) {
|
||||||
|
let restore_data = match self.sc_phase.get() {
|
||||||
|
ScreencastPhase::SourcesSelected(s) => s.restore_data.take(),
|
||||||
_ => {
|
_ => {
|
||||||
self.kill();
|
self.kill();
|
||||||
reply.err("Session is not in the correct phase for starting");
|
reply.err("Session is not in the correct phase for starting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
let request_obj = match self.state.dbus.add_object(req.handle.to_string()) {
|
let request_obj = match self.state.dbus.add_object(req.handle.to_string()) {
|
||||||
Ok(r) => r,
|
Ok(r) => Rc::new(r),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.kill();
|
self.kill();
|
||||||
reply.err("Request handle is not unique");
|
reply.err("Request handle is not unique");
|
||||||
|
|
@ -527,27 +482,141 @@ impl ScreencastSession {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
self.start_reply
|
||||||
|
.set(Some(PortalSessionReply::ScreenCast(reply)));
|
||||||
|
self.screencast_restore(&request_obj, restore_data, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_interactive_selection(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
request_obj: &Rc<DbusObject>,
|
||||||
|
restore_data: Option<RestoreData>,
|
||||||
|
) {
|
||||||
let guis = CopyHashMap::new();
|
let guis = CopyHashMap::new();
|
||||||
for dpy in self.state.displays.lock().values() {
|
for dpy in self.state.displays.lock().values() {
|
||||||
if dpy.outputs.len() > 0 {
|
if dpy.outputs.len() > 0 {
|
||||||
guis.set(dpy.id, SelectionGui::new(self, dpy));
|
guis.set(dpy.id, SelectionGui::new(self, dpy, restore_data.is_some()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if guis.is_empty() {
|
if guis.is_empty() {
|
||||||
self.kill();
|
self.kill();
|
||||||
reply.err("There are no running displays");
|
self.reply_err("There are no running displays");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.phase
|
self.sc_phase
|
||||||
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
|
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
|
||||||
core: SelectingScreencastCore {
|
core: SelectingScreencastCore {
|
||||||
session: self.clone(),
|
session: self.clone(),
|
||||||
request_obj: Rc::new(request_obj),
|
request_obj: request_obj.clone(),
|
||||||
reply: Rc::new(reply),
|
|
||||||
},
|
},
|
||||||
guis,
|
guis,
|
||||||
|
restore_data: Cell::new(restore_data),
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn screencast_restore(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
request_obj: &Rc<DbusObject>,
|
||||||
|
restore_data: Option<Result<RestoreData, RestoreError>>,
|
||||||
|
display: Option<Rc<PortalDisplay>>,
|
||||||
|
) {
|
||||||
|
if let Some(rd) = restore_data {
|
||||||
|
if let Err(e) = self.try_restore(&request_obj, rd, display) {
|
||||||
|
log::error!("Could not restore session: {}", ErrorFmt(e));
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.start_interactive_selection(&request_obj, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_restore(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
request_obj: &Rc<DbusObject>,
|
||||||
|
restore_data: Result<RestoreData, RestoreError>,
|
||||||
|
display: Option<Rc<PortalDisplay>>,
|
||||||
|
) -> Result<(), RestoreError> {
|
||||||
|
let rd = restore_data?;
|
||||||
|
let dpy = if let Some(dpy) = display {
|
||||||
|
dpy
|
||||||
|
} else {
|
||||||
|
let dpy = self
|
||||||
|
.state
|
||||||
|
.displays
|
||||||
|
.lock()
|
||||||
|
.values()
|
||||||
|
.find(|d| d.unique_id == rd.display)
|
||||||
|
.cloned();
|
||||||
|
match dpy {
|
||||||
|
Some(dpy) => dpy,
|
||||||
|
_ => {
|
||||||
|
if self.state.displays.len() == 0 {
|
||||||
|
return Err(RestoreError::UnknownDisplay);
|
||||||
|
} else if self.state.displays.len() == 1 {
|
||||||
|
self.state.displays.lock().values().next().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
self.start_interactive_selection(&request_obj, Some(rd));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let start = |target: ScreencastTarget| {
|
||||||
|
SelectingScreencastCore {
|
||||||
|
session: self.clone(),
|
||||||
|
request_obj: request_obj.clone(),
|
||||||
|
}
|
||||||
|
.starting(&dpy, target);
|
||||||
|
};
|
||||||
|
match &rd.ty {
|
||||||
|
RestoreDataType::Output(d) => {
|
||||||
|
let output = dpy
|
||||||
|
.outputs
|
||||||
|
.lock()
|
||||||
|
.values()
|
||||||
|
.find(|o| o.wl.name.borrow().as_ref() == Some(&d.name))
|
||||||
|
.cloned();
|
||||||
|
let Some(output) = output else {
|
||||||
|
return Err(RestoreError::UnknownOutput);
|
||||||
|
};
|
||||||
|
start(ScreencastTarget::Output(output));
|
||||||
|
}
|
||||||
|
RestoreDataType::Workspace(ws) => {
|
||||||
|
let ws = dpy
|
||||||
|
.workspaces
|
||||||
|
.lock()
|
||||||
|
.values()
|
||||||
|
.find(|w| w.name.borrow().as_ref() == Some(&ws.name))
|
||||||
|
.cloned();
|
||||||
|
let Some(ws) = ws else {
|
||||||
|
return Err(RestoreError::UnknownWorkspace);
|
||||||
|
};
|
||||||
|
let Some(output) = dpy.outputs.get(&ws.output.get()) else {
|
||||||
|
return Err(RestoreError::UnknownOutput);
|
||||||
|
};
|
||||||
|
start(ScreencastTarget::Workspace(output, ws, false));
|
||||||
|
}
|
||||||
|
RestoreDataType::Toplevel(d) => {
|
||||||
|
if dpy.jc.version < GET_TOPLEVEL_SINCE {
|
||||||
|
return Err(RestoreError::GetToplevel);
|
||||||
|
}
|
||||||
|
let selector = dpy.jc.get_toplevel(&d.id);
|
||||||
|
let selecting = Rc::new(SelectingWindowScreencast {
|
||||||
|
core: SelectingScreencastCore {
|
||||||
|
session: self.clone(),
|
||||||
|
request_obj: request_obj.clone(),
|
||||||
|
},
|
||||||
|
dpy: dpy.clone(),
|
||||||
|
selector: selector.clone(),
|
||||||
|
restoring: true,
|
||||||
|
});
|
||||||
|
selector.owner.set(Some(selecting.clone()));
|
||||||
|
self.sc_phase
|
||||||
|
.set(ScreencastPhase::SelectingWindow(selecting));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrJayScreencastOwner for StartedScreencast {
|
impl UsrJayScreencastOwner for StartedScreencast {
|
||||||
|
|
@ -695,7 +764,7 @@ fn dbus_create_session(
|
||||||
reply: PendingReply<CreateSessionReply<'static>>,
|
reply: PendingReply<CreateSessionReply<'static>>,
|
||||||
) {
|
) {
|
||||||
log::info!("Create Session {:#?}", req);
|
log::info!("Create Session {:#?}", req);
|
||||||
if state.screencasts.contains(req.session_handle.0.deref()) {
|
if state.sessions.contains(req.session_handle.0.deref()) {
|
||||||
reply.err("Session already exists");
|
reply.err("Session already exists");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -706,13 +775,15 @@ fn dbus_create_session(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let session = Rc::new(ScreencastSession {
|
let session = Rc::new(PortalSession {
|
||||||
_id: state.id(),
|
_id: state.id(),
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
pw_con: pw_con.clone(),
|
pw_con: Some(pw_con.clone()),
|
||||||
app: req.app_id.to_string(),
|
app: req.app_id.to_string(),
|
||||||
session_obj: obj,
|
session_obj: obj,
|
||||||
phase: CloneCell::new(ScreencastPhase::Init),
|
sc_phase: CloneCell::new(ScreencastPhase::Init),
|
||||||
|
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
|
||||||
|
start_reply: Default::default(),
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
use org::freedesktop::impl_::portal::session::*;
|
use org::freedesktop::impl_::portal::session::*;
|
||||||
|
|
@ -724,7 +795,7 @@ fn dbus_create_session(
|
||||||
session.session_obj.set_property::<version>(Variant::U32(4));
|
session.session_obj.set_property::<version>(Variant::U32(4));
|
||||||
}
|
}
|
||||||
state
|
state
|
||||||
.screencasts
|
.sessions
|
||||||
.set(req.session_handle.0.to_string(), session);
|
.set(req.session_handle.0.to_string(), session);
|
||||||
reply.ok(&CreateSessionReply {
|
reply.ok(&CreateSessionReply {
|
||||||
response: PORTAL_SUCCESS,
|
response: PORTAL_SUCCESS,
|
||||||
|
|
@ -744,7 +815,7 @@ fn dbus_select_sources(
|
||||||
|
|
||||||
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
|
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
|
||||||
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
|
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
|
||||||
s.dbus_start(req, reply);
|
s.dbus_start_screencast(req, reply);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -752,11 +823,130 @@ fn get_session<T>(
|
||||||
state: &Rc<PortalState>,
|
state: &Rc<PortalState>,
|
||||||
reply: &PendingReply<T>,
|
reply: &PendingReply<T>,
|
||||||
handle: &str,
|
handle: &str,
|
||||||
) -> Option<Rc<ScreencastSession>> {
|
) -> Option<Rc<PortalSession>> {
|
||||||
let res = state.screencasts.get(handle);
|
let res = state.sessions.get(handle);
|
||||||
if res.is_none() {
|
if res.is_none() {
|
||||||
let msg = format!("Screencast session `{}` does not exist", handle);
|
let msg = format!("Screencast session `{}` does not exist", handle);
|
||||||
reply.err(&msg);
|
reply.err(&msg);
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_restore_data(dpy: &PortalDisplay, rd: &ScreencastTarget) -> Option<Variant<'static>> {
|
||||||
|
let rd = RestoreData {
|
||||||
|
display: dpy.unique_id,
|
||||||
|
ty: match rd {
|
||||||
|
ScreencastTarget::Output(o) => RestoreDataType::Output(RestoreDataOutput {
|
||||||
|
name: o.wl.name.borrow().clone()?,
|
||||||
|
}),
|
||||||
|
ScreencastTarget::Workspace(_, w, _) => {
|
||||||
|
RestoreDataType::Workspace(RestoreDataWorkspace {
|
||||||
|
name: w.name.borrow().clone()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ScreencastTarget::Toplevel(tl) => RestoreDataType::Toplevel(RestoreDataToplevel {
|
||||||
|
id: tl.toplevel_id.borrow().clone()?,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Some(Variant::Struct(vec![
|
||||||
|
Variant::String("Jay".into()),
|
||||||
|
Variant::U32(1),
|
||||||
|
Variant::Variant(Box::new(Variant::String(
|
||||||
|
serde_json::to_string(&rd).unwrap().into(),
|
||||||
|
))),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RestoreError {
|
||||||
|
#[error("DBus restore data is not a struct")]
|
||||||
|
NotAStruct,
|
||||||
|
#[error("DBus restore data is not a struct with 3 fields")]
|
||||||
|
NotLen3,
|
||||||
|
#[error("DBus restore data first field is not a string")]
|
||||||
|
FirstNotString,
|
||||||
|
#[error("DBus restore data second field is not a u32")]
|
||||||
|
SecondNotU32,
|
||||||
|
#[error("DBus restore data third field is not a variant")]
|
||||||
|
ThirdNotVariant,
|
||||||
|
#[error("DBus restore data third field is not a string")]
|
||||||
|
ThirdNotString,
|
||||||
|
#[error("DBus restore data is not for Jay")]
|
||||||
|
NotJay,
|
||||||
|
#[error("DBus restore data is not version 1")]
|
||||||
|
NotVersion1,
|
||||||
|
#[error("DBus restore data could not be deserialized")]
|
||||||
|
Parse(#[source] serde_json::Error),
|
||||||
|
#[error("The display no longer exists")]
|
||||||
|
UnknownDisplay,
|
||||||
|
#[error("The output no longer exists")]
|
||||||
|
UnknownOutput,
|
||||||
|
#[error("The workspace no longer exists")]
|
||||||
|
UnknownWorkspace,
|
||||||
|
#[error("The display does not support toplevel restoration")]
|
||||||
|
GetToplevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_restore_data(req: &SelectSources) -> Option<Result<RestoreData, RestoreError>> {
|
||||||
|
let restore_data = req.options.iter().find(|n| n.key == "restore_data")?;
|
||||||
|
Some(get_restore_data_(restore_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_restore_data_(
|
||||||
|
restore_data: &DictEntry<Cow<str>, Variant>,
|
||||||
|
) -> Result<RestoreData, RestoreError> {
|
||||||
|
let Variant::Struct(s) = &restore_data.value else {
|
||||||
|
return Err(RestoreError::NotAStruct);
|
||||||
|
};
|
||||||
|
if s.len() != 3 {
|
||||||
|
return Err(RestoreError::NotLen3);
|
||||||
|
}
|
||||||
|
let Variant::String(compositor) = &s[0] else {
|
||||||
|
return Err(RestoreError::FirstNotString);
|
||||||
|
};
|
||||||
|
let Variant::U32(version) = &s[1] else {
|
||||||
|
return Err(RestoreError::SecondNotU32);
|
||||||
|
};
|
||||||
|
let Variant::Variant(restore_data) = &s[2] else {
|
||||||
|
return Err(RestoreError::ThirdNotVariant);
|
||||||
|
};
|
||||||
|
let Variant::String(restore_data) = &**restore_data else {
|
||||||
|
return Err(RestoreError::ThirdNotString);
|
||||||
|
};
|
||||||
|
if compositor != "Jay" {
|
||||||
|
return Err(RestoreError::NotJay);
|
||||||
|
}
|
||||||
|
if *version != 1 {
|
||||||
|
return Err(RestoreError::NotVersion1);
|
||||||
|
}
|
||||||
|
serde_json::from_str(restore_data).map_err(RestoreError::Parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RestoreData {
|
||||||
|
display: Opaque,
|
||||||
|
ty: RestoreDataType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
enum RestoreDataType {
|
||||||
|
Output(RestoreDataOutput),
|
||||||
|
Workspace(RestoreDataWorkspace),
|
||||||
|
Toplevel(RestoreDataToplevel),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct RestoreDataOutput {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct RestoreDataWorkspace {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct RestoreDataToplevel {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use {
|
||||||
portal::{
|
portal::{
|
||||||
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
||||||
ptl_screencast::{
|
ptl_screencast::{
|
||||||
ScreencastPhase, ScreencastSession, ScreencastTarget, SelectingWindowScreencast,
|
PortalSession, ScreencastPhase, ScreencastTarget, SelectingWindowScreencast,
|
||||||
SelectingWorkspaceScreencast,
|
SelectingWorkspaceScreencast,
|
||||||
},
|
},
|
||||||
ptr_gui::{
|
ptr_gui::{
|
||||||
|
|
@ -27,7 +27,7 @@ const H_MARGIN: f32 = 30.0;
|
||||||
const V_MARGIN: f32 = 20.0;
|
const V_MARGIN: f32 = 20.0;
|
||||||
|
|
||||||
pub struct SelectionGui {
|
pub struct SelectionGui {
|
||||||
screencast_session: Rc<ScreencastSession>,
|
screencast_session: Rc<PortalSession>,
|
||||||
dpy: Rc<PortalDisplay>,
|
dpy: Rc<PortalDisplay>,
|
||||||
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
|
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +45,7 @@ struct StaticButton {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
enum ButtonRole {
|
enum ButtonRole {
|
||||||
|
Restore,
|
||||||
Accept,
|
Accept,
|
||||||
SelectWorkspace,
|
SelectWorkspace,
|
||||||
SelectWindow,
|
SelectWindow,
|
||||||
|
|
@ -56,7 +57,7 @@ impl SelectionGui {
|
||||||
for surface in self.surfaces.lock().drain_values() {
|
for surface in self.surfaces.lock().drain_values() {
|
||||||
surface.overlay.data.kill(false);
|
surface.overlay.data.kill(false);
|
||||||
}
|
}
|
||||||
if let ScreencastPhase::Selecting(s) = self.screencast_session.phase.get() {
|
if let ScreencastPhase::Selecting(s) = self.screencast_session.sc_phase.get() {
|
||||||
s.guis.remove(&self.dpy.id);
|
s.guis.remove(&self.dpy.id);
|
||||||
if upwards && s.guis.is_empty() {
|
if upwards && s.guis.is_empty() {
|
||||||
self.screencast_session.kill();
|
self.screencast_session.kill();
|
||||||
|
|
@ -65,7 +66,7 @@ impl SelectionGui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
fn create_accept_gui(surface: &Rc<SelectionGuiSurface>, for_restore: bool) -> Rc<dyn GuiElement> {
|
||||||
let app = &surface.gui.screencast_session.app;
|
let app = &surface.gui.screencast_session.app;
|
||||||
let text = if app.is_empty() {
|
let text = if app.is_empty() {
|
||||||
format!("An application wants to capture the screen")
|
format!("An application wants to capture the screen")
|
||||||
|
|
@ -74,11 +75,13 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
||||||
};
|
};
|
||||||
let label = Rc::new(Label::default());
|
let label = Rc::new(Label::default());
|
||||||
*label.text.borrow_mut() = text;
|
*label.text.borrow_mut() = text;
|
||||||
|
let restore_button = static_button(surface, ButtonRole::Restore, "Restore Session");
|
||||||
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
|
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
|
||||||
let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace");
|
let workspace_button = static_button(surface, ButtonRole::SelectWorkspace, "Share A Workspace");
|
||||||
let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window");
|
let window_button = static_button(surface, ButtonRole::SelectWindow, "Share A Window");
|
||||||
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
||||||
for button in [
|
for button in [
|
||||||
|
&restore_button,
|
||||||
&accept_button,
|
&accept_button,
|
||||||
&workspace_button,
|
&workspace_button,
|
||||||
&window_button,
|
&window_button,
|
||||||
|
|
@ -88,6 +91,10 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
||||||
button.border.set(2.0);
|
button.border.set(2.0);
|
||||||
button.padding.set(5.0);
|
button.padding.set(5.0);
|
||||||
}
|
}
|
||||||
|
restore_button.bg_color.set(Color::from_rgb(170, 170, 200));
|
||||||
|
restore_button
|
||||||
|
.bg_hover_color
|
||||||
|
.set(Color::from_rgb(170, 170, 255));
|
||||||
for button in [&accept_button, &workspace_button, &window_button] {
|
for button in [&accept_button, &workspace_button, &window_button] {
|
||||||
button.bg_color.set(Color::from_rgb(170, 200, 170));
|
button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||||
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
|
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
|
||||||
|
|
@ -101,7 +108,11 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
||||||
flow.cross_align.set(Align::Center);
|
flow.cross_align.set(Align::Center);
|
||||||
flow.in_margin.set(V_MARGIN);
|
flow.in_margin.set(V_MARGIN);
|
||||||
flow.cross_margin.set(H_MARGIN);
|
flow.cross_margin.set(H_MARGIN);
|
||||||
let mut elements: Vec<Rc<dyn GuiElement>> = vec![label, accept_button];
|
let mut elements: Vec<Rc<dyn GuiElement>> = vec![label];
|
||||||
|
if for_restore {
|
||||||
|
elements.push(restore_button);
|
||||||
|
}
|
||||||
|
elements.push(accept_button);
|
||||||
if surface.gui.dpy.jc.caps.select_workspace.get() {
|
if surface.gui.dpy.jc.caps.select_workspace.get() {
|
||||||
elements.push(workspace_button);
|
elements.push(workspace_button);
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +135,7 @@ impl OverlayWindowOwner for SelectionGuiSurface {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionGui {
|
impl SelectionGui {
|
||||||
pub fn new(ss: &Rc<ScreencastSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
|
pub fn new(ss: &Rc<PortalSession>, dpy: &Rc<PortalDisplay>, for_restore: bool) -> Rc<Self> {
|
||||||
let gui = Rc::new(SelectionGui {
|
let gui = Rc::new(SelectionGui {
|
||||||
screencast_session: ss.clone(),
|
screencast_session: ss.clone(),
|
||||||
dpy: dpy.clone(),
|
dpy: dpy.clone(),
|
||||||
|
|
@ -136,7 +147,7 @@ impl SelectionGui {
|
||||||
output: output.clone(),
|
output: output.clone(),
|
||||||
overlay: OverlayWindow::new(output),
|
overlay: OverlayWindow::new(output),
|
||||||
});
|
});
|
||||||
let element = create_accept_gui(&sgs);
|
let element = create_accept_gui(&sgs, for_restore);
|
||||||
sgs.overlay.data.content.set(Some(element));
|
sgs.overlay.data.content.set(Some(element));
|
||||||
gui.dpy
|
gui.dpy
|
||||||
.windows
|
.windows
|
||||||
|
|
@ -153,9 +164,12 @@ impl ButtonOwner for StaticButton {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match self.role {
|
match self.role {
|
||||||
ButtonRole::Accept | ButtonRole::SelectWorkspace | ButtonRole::SelectWindow => {
|
ButtonRole::Restore
|
||||||
|
| ButtonRole::Accept
|
||||||
|
| ButtonRole::SelectWorkspace
|
||||||
|
| ButtonRole::SelectWindow => {
|
||||||
log::info!("User has accepted the request");
|
log::info!("User has accepted the request");
|
||||||
let selecting = match self.surface.gui.screencast_session.phase.get() {
|
let selecting = match self.surface.gui.screencast_session.sc_phase.get() {
|
||||||
ScreencastPhase::Selecting(selecting) => selecting,
|
ScreencastPhase::Selecting(selecting) => selecting,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
@ -163,7 +177,13 @@ impl ButtonOwner for StaticButton {
|
||||||
gui.kill(false);
|
gui.kill(false);
|
||||||
}
|
}
|
||||||
let dpy = &self.surface.output.dpy;
|
let dpy = &self.surface.output.dpy;
|
||||||
if self.role == ButtonRole::Accept {
|
if self.role == ButtonRole::Restore {
|
||||||
|
selecting.core.session.screencast_restore(
|
||||||
|
&selecting.core.request_obj,
|
||||||
|
selecting.restore_data.take().map(Ok),
|
||||||
|
Some(self.surface.gui.dpy.clone()),
|
||||||
|
);
|
||||||
|
} else if self.role == ButtonRole::Accept {
|
||||||
selecting
|
selecting
|
||||||
.core
|
.core
|
||||||
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
|
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
|
||||||
|
|
@ -178,7 +198,7 @@ impl ButtonOwner for StaticButton {
|
||||||
self.surface
|
self.surface
|
||||||
.gui
|
.gui
|
||||||
.screencast_session
|
.screencast_session
|
||||||
.phase
|
.sc_phase
|
||||||
.set(ScreencastPhase::SelectingWorkspace(selecting));
|
.set(ScreencastPhase::SelectingWorkspace(selecting));
|
||||||
} else {
|
} else {
|
||||||
let selector = dpy.jc.select_toplevel(&seat.wl);
|
let selector = dpy.jc.select_toplevel(&seat.wl);
|
||||||
|
|
@ -186,12 +206,13 @@ impl ButtonOwner for StaticButton {
|
||||||
core: selecting.core.clone(),
|
core: selecting.core.clone(),
|
||||||
dpy: dpy.clone(),
|
dpy: dpy.clone(),
|
||||||
selector: selector.clone(),
|
selector: selector.clone(),
|
||||||
|
restoring: false,
|
||||||
});
|
});
|
||||||
selector.owner.set(Some(selecting.clone()));
|
selector.owner.set(Some(selecting.clone()));
|
||||||
self.surface
|
self.surface
|
||||||
.gui
|
.gui
|
||||||
.screencast_session
|
.screencast_session
|
||||||
.phase
|
.sc_phase
|
||||||
.set(ScreencastPhase::SelectingWindow(selecting));
|
.set(ScreencastPhase::SelectingWindow(selecting));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,11 +227,18 @@ impl ButtonOwner for StaticButton {
|
||||||
impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
|
impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
|
||||||
fn done(&self, tl: Option<Rc<UsrJayToplevel>>) {
|
fn done(&self, tl: Option<Rc<UsrJayToplevel>>) {
|
||||||
let Some(tl) = tl else {
|
let Some(tl) = tl else {
|
||||||
|
if self.restoring {
|
||||||
|
log::warn!("Could not restore session because toplevel no longer exists");
|
||||||
|
self.core
|
||||||
|
.session
|
||||||
|
.start_interactive_selection(&self.core.request_obj, None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
log::info!("User has aborted the selection");
|
log::info!("User has aborted the selection");
|
||||||
self.core.session.kill();
|
self.core.session.kill();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match self.core.session.phase.get() {
|
match self.core.session.sc_phase.get() {
|
||||||
ScreencastPhase::SelectingWindow(s) => {
|
ScreencastPhase::SelectingWindow(s) => {
|
||||||
self.dpy.con.remove_obj(&*s.selector);
|
self.dpy.con.remove_obj(&*s.selector);
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +260,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
|
||||||
self.core.session.kill();
|
self.core.session.kill();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match self.core.session.phase.get() {
|
match self.core.session.sc_phase.get() {
|
||||||
ScreencastPhase::SelectingWorkspace(s) => {
|
ScreencastPhase::SelectingWorkspace(s) => {
|
||||||
self.dpy.con.remove_obj(&*s.selector);
|
self.dpy.con.remove_obj(&*s.selector);
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +280,7 @@ impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.core
|
self.core
|
||||||
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws));
|
.starting(&self.dpy, ScreencastTarget::Workspace(output, ws, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
169
src/portal/ptl_session.rs
Normal file
169
src/portal/ptl_session.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply, FALSE},
|
||||||
|
pipewire::pw_con::PwCon,
|
||||||
|
portal::{
|
||||||
|
ptl_remote_desktop::{DeviceTypes, RemoteDesktopPhase},
|
||||||
|
ptl_screencast::{ScreencastPhase, ScreencastTarget},
|
||||||
|
PortalState, PORTAL_SUCCESS,
|
||||||
|
},
|
||||||
|
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
|
||||||
|
wire_dbus::org::freedesktop::impl_::portal::{
|
||||||
|
remote_desktop::StartReply as RdStartReply, screen_cast::StartReply as ScStartReply,
|
||||||
|
session::Closed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::{borrow::Cow, cell::Cell, ops::Deref, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
shared_ids!(SessionId);
|
||||||
|
pub struct PortalSession {
|
||||||
|
pub _id: SessionId,
|
||||||
|
pub state: Rc<PortalState>,
|
||||||
|
pub pw_con: Option<Rc<PwCon>>,
|
||||||
|
pub app: String,
|
||||||
|
pub session_obj: DbusObject,
|
||||||
|
pub sc_phase: CloneCell<ScreencastPhase>,
|
||||||
|
pub rd_phase: CloneCell<RemoteDesktopPhase>,
|
||||||
|
pub start_reply: Cell<Option<PortalSessionReply>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PortalSessionReply {
|
||||||
|
RemoteDesktop(PendingReply<RdStartReply<'static>>),
|
||||||
|
ScreenCast(PendingReply<ScStartReply<'static>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortalSession {
|
||||||
|
pub(super) fn kill(&self) {
|
||||||
|
self.session_obj.emit_signal(&Closed);
|
||||||
|
self.state.sessions.remove(self.session_obj.path());
|
||||||
|
self.reply_err("Session has been terminated");
|
||||||
|
match self.rd_phase.set(RemoteDesktopPhase::Terminated) {
|
||||||
|
RemoteDesktopPhase::Init => {}
|
||||||
|
RemoteDesktopPhase::DevicesSelected => {}
|
||||||
|
RemoteDesktopPhase::Terminated => {}
|
||||||
|
RemoteDesktopPhase::Selecting(s) => {
|
||||||
|
for gui in s.guis.lock().drain_values() {
|
||||||
|
gui.kill(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoteDesktopPhase::Starting(s) => {
|
||||||
|
s.ei_session.con.remove_obj(s.ei_session.deref());
|
||||||
|
s.dpy.sessions.remove(self.session_obj.path());
|
||||||
|
}
|
||||||
|
RemoteDesktopPhase::Started(s) => {
|
||||||
|
s.ei_session.con.remove_obj(s.ei_session.deref());
|
||||||
|
s.dpy.sessions.remove(self.session_obj.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self.sc_phase.set(ScreencastPhase::Terminated) {
|
||||||
|
ScreencastPhase::Init => {}
|
||||||
|
ScreencastPhase::SourcesSelected(_) => {}
|
||||||
|
ScreencastPhase::Terminated => {}
|
||||||
|
ScreencastPhase::Selecting(s) => {
|
||||||
|
for gui in s.guis.lock().drain_values() {
|
||||||
|
gui.kill(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScreencastPhase::SelectingWindow(s) => {
|
||||||
|
s.dpy.con.remove_obj(&*s.selector);
|
||||||
|
}
|
||||||
|
ScreencastPhase::SelectingWorkspace(s) => {
|
||||||
|
s.dpy.con.remove_obj(&*s.selector);
|
||||||
|
}
|
||||||
|
ScreencastPhase::Starting(s) => {
|
||||||
|
s.node.con.destroy_obj(s.node.deref());
|
||||||
|
s.dpy.sessions.remove(self.session_obj.path());
|
||||||
|
match &s.target {
|
||||||
|
ScreencastTarget::Output(_) => {}
|
||||||
|
ScreencastTarget::Workspace(_, w, true) => {
|
||||||
|
s.dpy.con.remove_obj(&**w);
|
||||||
|
}
|
||||||
|
ScreencastTarget::Workspace(_, _, false) => {}
|
||||||
|
ScreencastTarget::Toplevel(t) => {
|
||||||
|
s.dpy.con.remove_obj(&**t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScreencastPhase::Started(s) => {
|
||||||
|
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
|
||||||
|
s.node.con.destroy_obj(s.node.deref());
|
||||||
|
s.dpy.sessions.remove(self.session_obj.path());
|
||||||
|
for buffer in s.pending_buffers.borrow_mut().drain(..) {
|
||||||
|
s.dpy.con.remove_obj(&*buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn send_start_reply(
|
||||||
|
&self,
|
||||||
|
pw_node_id: Option<u32>,
|
||||||
|
restore_data: Option<Variant<'static>>,
|
||||||
|
mapping_id: Option<&str>,
|
||||||
|
) {
|
||||||
|
let inner_type = DynamicType::DictEntry(
|
||||||
|
Box::new(DynamicType::String),
|
||||||
|
Box::new(DynamicType::Variant),
|
||||||
|
);
|
||||||
|
let kt = DynamicType::Struct(vec![
|
||||||
|
DynamicType::U32,
|
||||||
|
DynamicType::Array(Box::new(inner_type.clone())),
|
||||||
|
]);
|
||||||
|
let mut streams = vec![];
|
||||||
|
if let Some(node_id) = pw_node_id {
|
||||||
|
streams = vec![Variant::U32(node_id), Variant::Array(inner_type, vec![])];
|
||||||
|
}
|
||||||
|
let mut variants = vec![
|
||||||
|
DictEntry {
|
||||||
|
key: "devices".into(),
|
||||||
|
value: Variant::U32(DeviceTypes::all().0),
|
||||||
|
},
|
||||||
|
DictEntry {
|
||||||
|
key: "clipboard_enabled".into(),
|
||||||
|
value: Variant::Bool(FALSE),
|
||||||
|
},
|
||||||
|
DictEntry {
|
||||||
|
key: "streams".into(),
|
||||||
|
value: Variant::Array(kt, streams),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if let Some(rd) = restore_data {
|
||||||
|
variants.push(DictEntry {
|
||||||
|
key: "restore_data".into(),
|
||||||
|
value: rd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(mapping_id) = mapping_id {
|
||||||
|
variants.push(DictEntry {
|
||||||
|
key: "mapping_id".into(),
|
||||||
|
value: Variant::String(mapping_id.into()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(reply) = self.start_reply.take() {
|
||||||
|
match reply {
|
||||||
|
PortalSessionReply::RemoteDesktop(reply) => {
|
||||||
|
reply.ok(&RdStartReply {
|
||||||
|
response: PORTAL_SUCCESS,
|
||||||
|
results: Cow::Borrowed(&variants),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PortalSessionReply::ScreenCast(reply) => {
|
||||||
|
reply.ok(&ScStartReply {
|
||||||
|
response: PORTAL_SUCCESS,
|
||||||
|
results: Cow::Borrowed(&variants),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn reply_err(&self, err: &str) {
|
||||||
|
if let Some(reply) = self.start_reply.take() {
|
||||||
|
match reply {
|
||||||
|
PortalSessionReply::RemoteDesktop(r) => r.err(err),
|
||||||
|
PortalSessionReply::ScreenCast(r) => r.err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -661,6 +661,7 @@ impl WindowData {
|
||||||
buf.free.set(false);
|
buf.free.set(false);
|
||||||
|
|
||||||
self.surface.attach(&buf.wl);
|
self.surface.attach(&buf.wl);
|
||||||
|
self.surface.damage();
|
||||||
self.surface.commit();
|
self.surface.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ use {
|
||||||
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser,
|
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser,
|
||||||
hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue,
|
hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue,
|
||||||
refcounted::RefCounted, run_toplevel::RunToplevel,
|
refcounted::RefCounted, run_toplevel::RunToplevel,
|
||||||
|
toplevel_identifier::ToplevelIdentifier,
|
||||||
},
|
},
|
||||||
video::{
|
video::{
|
||||||
dmabuf::DmaBufIds,
|
dmabuf::DmaBufIds,
|
||||||
|
|
@ -107,7 +108,7 @@ use {
|
||||||
mem,
|
mem,
|
||||||
num::Wrapping,
|
num::Wrapping,
|
||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
rc::Rc,
|
rc::{Rc, Weak},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
},
|
},
|
||||||
|
|
@ -220,6 +221,7 @@ pub struct State {
|
||||||
pub cpu_worker: Rc<CpuWorker>,
|
pub cpu_worker: Rc<CpuWorker>,
|
||||||
pub ui_drag_enabled: Cell<bool>,
|
pub ui_drag_enabled: Cell<bool>,
|
||||||
pub ui_drag_threshold_squared: Cell<i32>,
|
pub ui_drag_threshold_squared: Cell<i32>,
|
||||||
|
pub toplevels: CopyHashMap<ToplevelIdentifier, Weak<dyn ToplevelNode>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -875,6 +877,7 @@ impl State {
|
||||||
self.ei_acceptor_future.take();
|
self.ei_acceptor_future.take();
|
||||||
self.ei_clients.clear();
|
self.ei_clients.clear();
|
||||||
self.slow_ei_clients.clear();
|
self.slow_ei_clients.clear();
|
||||||
|
self.toplevels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn damage_hardware_cursors(&self, render: bool) {
|
pub fn damage_hardware_cursors(&self, render: bool) {
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ impl ContainerNode {
|
||||||
let child_node_ref = child_node.clone();
|
let child_node_ref = child_node.clone();
|
||||||
let mut child_nodes = AHashMap::new();
|
let mut child_nodes = AHashMap::new();
|
||||||
child_nodes.insert(child.node_id(), child_node);
|
child_nodes.insert(child.node_id(), child_node);
|
||||||
let slf = Rc::new(Self {
|
let slf = Rc::new_cyclic(|weak| Self {
|
||||||
id: state.node_ids.next(),
|
id: state.node_ids.next(),
|
||||||
split: Cell::new(split),
|
split: Cell::new(split),
|
||||||
mono_child: CloneCell::new(None),
|
mono_child: CloneCell::new(None),
|
||||||
|
|
@ -238,7 +238,7 @@ impl ContainerNode {
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
render_data: Default::default(),
|
render_data: Default::default(),
|
||||||
scroller: Default::default(),
|
scroller: Default::default(),
|
||||||
toplevel_data: ToplevelData::new(state, Default::default(), None),
|
toplevel_data: ToplevelData::new(state, Default::default(), None, weak),
|
||||||
attention_requests: Default::default(),
|
attention_requests: Default::default(),
|
||||||
});
|
});
|
||||||
child.tl_set_parent(slf.clone());
|
child.tl_set_parent(slf.clone());
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use {
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
rc::Rc,
|
rc::{Rc, Weak},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -48,13 +48,14 @@ pub async fn placeholder_render_textures(state: Rc<State>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceholderNode {
|
impl PlaceholderNode {
|
||||||
pub fn new_for(state: &Rc<State>, node: Rc<dyn ToplevelNode>) -> Self {
|
pub fn new_for(state: &Rc<State>, node: Rc<dyn ToplevelNode>, slf: &Weak<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: state.node_ids.next(),
|
id: state.node_ids.next(),
|
||||||
toplevel: ToplevelData::new(
|
toplevel: ToplevelData::new(
|
||||||
state,
|
state,
|
||||||
node.tl_data().title.borrow().clone(),
|
node.tl_data().title.borrow().clone(),
|
||||||
node.node_client(),
|
node.node_client(),
|
||||||
|
slf,
|
||||||
),
|
),
|
||||||
destroyed: Default::default(),
|
destroyed: Default::default(),
|
||||||
update_textures_scheduled: Cell::new(false),
|
update_textures_scheduled: Cell::new(false),
|
||||||
|
|
@ -63,10 +64,10 @@ impl PlaceholderNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_empty(state: &Rc<State>) -> Self {
|
pub fn new_empty(state: &Rc<State>, slf: &Weak<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: state.node_ids.next(),
|
id: state.node_ids.next(),
|
||||||
toplevel: ToplevelData::new(state, String::new(), None),
|
toplevel: ToplevelData::new(state, String::new(), None, slf),
|
||||||
destroyed: Default::default(),
|
destroyed: Default::default(),
|
||||||
update_textures_scheduled: Default::default(),
|
update_textures_scheduled: Default::default(),
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
|
|
|
||||||
|
|
@ -282,10 +282,18 @@ pub struct ToplevelData {
|
||||||
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
|
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
|
||||||
pub ext_copy_sessions:
|
pub ext_copy_sessions:
|
||||||
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
|
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
|
||||||
|
pub slf: Weak<dyn ToplevelNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelData {
|
impl ToplevelData {
|
||||||
pub fn new(state: &Rc<State>, title: String, client: Option<Rc<Client>>) -> Self {
|
pub fn new<T: ToplevelNode>(
|
||||||
|
state: &Rc<State>,
|
||||||
|
title: String,
|
||||||
|
client: Option<Rc<Client>>,
|
||||||
|
slf: &Weak<T>,
|
||||||
|
) -> Self {
|
||||||
|
let id = toplevel_identifier();
|
||||||
|
state.toplevels.set(id, slf.clone());
|
||||||
Self {
|
Self {
|
||||||
self_active: Cell::new(false),
|
self_active: Cell::new(false),
|
||||||
client,
|
client,
|
||||||
|
|
@ -307,12 +315,13 @@ impl ToplevelData {
|
||||||
wants_attention: Cell::new(false),
|
wants_attention: Cell::new(false),
|
||||||
requested_attention: Cell::new(false),
|
requested_attention: Cell::new(false),
|
||||||
app_id: Default::default(),
|
app_id: Default::default(),
|
||||||
identifier: Cell::new(toplevel_identifier()),
|
identifier: Cell::new(id),
|
||||||
handles: Default::default(),
|
handles: Default::default(),
|
||||||
render_highlight: Default::default(),
|
render_highlight: Default::default(),
|
||||||
jay_toplevels: Default::default(),
|
jay_toplevels: Default::default(),
|
||||||
jay_screencasts: Default::default(),
|
jay_screencasts: Default::default(),
|
||||||
ext_copy_sessions: Default::default(),
|
ext_copy_sessions: Default::default(),
|
||||||
|
slf: slf.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,7 +368,12 @@ impl ToplevelData {
|
||||||
for screencast in self.ext_copy_sessions.lock().drain_values() {
|
for screencast in self.ext_copy_sessions.lock().drain_values() {
|
||||||
screencast.stop();
|
screencast.stop();
|
||||||
}
|
}
|
||||||
self.identifier.set(toplevel_identifier());
|
{
|
||||||
|
let id = toplevel_identifier();
|
||||||
|
let prev = self.identifier.replace(id);
|
||||||
|
self.state.toplevels.remove(&prev);
|
||||||
|
self.state.toplevels.set(id, self.slf.clone());
|
||||||
|
}
|
||||||
{
|
{
|
||||||
let mut handles = self.handles.lock();
|
let mut handles = self.handles.lock();
|
||||||
for handle in handles.drain_values() {
|
for handle in handles.drain_values() {
|
||||||
|
|
@ -476,7 +490,8 @@ impl ToplevelData {
|
||||||
log::warn!("Cannot fullscreen root container in a workspace");
|
log::warn!("Cannot fullscreen root container in a workspace");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let placeholder = Rc::new(PlaceholderNode::new_for(state, node.clone()));
|
let placeholder =
|
||||||
|
Rc::new_cyclic(|weak| PlaceholderNode::new_for(state, node.clone(), weak));
|
||||||
parent.cnode_replace_child(node.tl_as_node(), placeholder.clone());
|
parent.cnode_replace_child(node.tl_as_node(), placeholder.clone());
|
||||||
let mut kb_foci = Default::default();
|
let mut kb_foci = Default::default();
|
||||||
if ws.visible.get() {
|
if ws.visible.get() {
|
||||||
|
|
@ -599,6 +614,12 @@ impl ToplevelData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for ToplevelData {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.state.toplevels.remove(&self.identifier.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TileDragDestination {
|
pub struct TileDragDestination {
|
||||||
pub highlight: Rect,
|
pub highlight: Rect,
|
||||||
pub ty: TddType,
|
pub ty: TddType,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::utils::opaque::{opaque, Opaque, OpaqueError},
|
crate::utils::opaque::{opaque, Opaque, OpaqueError, OPAQUE_LEN},
|
||||||
|
arrayvec::ArrayString,
|
||||||
std::{
|
std::{
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
|
@ -13,6 +14,12 @@ pub fn activation_token() -> ActivationToken {
|
||||||
ActivationToken(opaque())
|
ActivationToken(opaque())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ActivationToken {
|
||||||
|
pub fn to_string(self) -> ArrayString<OPAQUE_LEN> {
|
||||||
|
self.0.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ActivationToken {
|
impl Display for ActivationToken {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use {
|
use {
|
||||||
|
arrayvec::ArrayString,
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
|
serde::{de, Deserialize, Deserializer, Serialize, Serializer},
|
||||||
std::{
|
std::{
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
num::ParseIntError,
|
num::ParseIntError,
|
||||||
|
|
@ -22,6 +24,15 @@ pub fn opaque() -> Opaque {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Opaque {
|
||||||
|
pub fn to_string(self) -> ArrayString<OPAQUE_LEN> {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut s = ArrayString::new();
|
||||||
|
write!(s, "{}", self).unwrap();
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Opaque {
|
impl Display for Opaque {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:016x}", self.hi)?;
|
write!(f, "{:016x}", self.hi)?;
|
||||||
|
|
@ -36,24 +47,44 @@ impl Debug for Opaque {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for Opaque {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let s = self.to_string();
|
||||||
|
serializer.serialize_str(&s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Opaque {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = <&str>::deserialize(deserializer)?;
|
||||||
|
Opaque::from_str(s).map_err(de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for Opaque {
|
impl FromStr for Opaque {
|
||||||
type Err = OpaqueError;
|
type Err = OpaqueError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if s.len() != LEN {
|
if s.len() != OPAQUE_LEN {
|
||||||
return Err(OpaqueError::InvalidLength);
|
return Err(OpaqueError::InvalidLength);
|
||||||
}
|
}
|
||||||
if !s.is_char_boundary(LEN / 2) {
|
if !s.is_char_boundary(OPAQUE_LEN / 2) {
|
||||||
return Err(OpaqueError::NotAscii);
|
return Err(OpaqueError::NotAscii);
|
||||||
}
|
}
|
||||||
let (hi, lo) = s.split_at(LEN / 2);
|
let (hi, lo) = s.split_at(OPAQUE_LEN / 2);
|
||||||
let hi = u64::from_str_radix(hi, 16).map_err(OpaqueError::Parse)?;
|
let hi = u64::from_str_radix(hi, 16).map_err(OpaqueError::Parse)?;
|
||||||
let lo = u64::from_str_radix(lo, 16).map_err(OpaqueError::Parse)?;
|
let lo = u64::from_str_radix(lo, 16).map_err(OpaqueError::Parse)?;
|
||||||
Ok(Self { lo, hi })
|
Ok(Self { lo, hi })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEN: usize = 32;
|
pub const OPAQUE_LEN: usize = 32;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum OpaqueError {
|
pub enum OpaqueError {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::utils::opaque::{opaque, Opaque, OpaqueError},
|
crate::utils::opaque::{opaque, Opaque, OpaqueError, OPAQUE_LEN},
|
||||||
|
arrayvec::ArrayString,
|
||||||
std::{
|
std::{
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
|
@ -13,6 +14,12 @@ pub fn toplevel_identifier() -> ToplevelIdentifier {
|
||||||
ToplevelIdentifier(opaque())
|
ToplevelIdentifier(opaque())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToplevelIdentifier {
|
||||||
|
pub fn to_string(self) -> ArrayString<OPAQUE_LEN> {
|
||||||
|
self.0.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ToplevelIdentifier {
|
impl Display for ToplevelIdentifier {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ impl UsrJayCompositor {
|
||||||
jo
|
jo
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(dead_code)]
|
|
||||||
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
|
pub fn watch_workspaces(&self) -> Rc<UsrJayWorkspaceWatcher> {
|
||||||
let ww = Rc::new(UsrJayWorkspaceWatcher {
|
let ww = Rc::new(UsrJayWorkspaceWatcher {
|
||||||
id: self.con.id(),
|
id: self.con.id(),
|
||||||
|
|
@ -143,6 +142,22 @@ impl UsrJayCompositor {
|
||||||
sc
|
sc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_toplevel(&self, id: &str) -> Rc<UsrJaySelectToplevel> {
|
||||||
|
let sc = Rc::new(UsrJaySelectToplevel {
|
||||||
|
id: self.con.id(),
|
||||||
|
con: self.con.clone(),
|
||||||
|
owner: Default::default(),
|
||||||
|
version: self.version,
|
||||||
|
});
|
||||||
|
self.con.request(GetToplevel {
|
||||||
|
self_id: self.id,
|
||||||
|
id: sc.id,
|
||||||
|
toplevel_id: id,
|
||||||
|
});
|
||||||
|
self.con.add_object(sc.clone());
|
||||||
|
sc
|
||||||
|
}
|
||||||
|
|
||||||
pub fn select_workspace(&self, seat: &UsrWlSeat) -> Rc<UsrJaySelectWorkspace> {
|
pub fn select_workspace(&self, seat: &UsrWlSeat) -> Rc<UsrJaySelectWorkspace> {
|
||||||
let sc = Rc::new(UsrJaySelectWorkspace {
|
let sc = Rc::new(UsrJaySelectWorkspace {
|
||||||
id: self.con.id(),
|
id: self.con.id(),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
ifs::jay_toplevel::ID_SINCE,
|
||||||
object::Version,
|
object::Version,
|
||||||
utils::clonecell::CloneCell,
|
utils::clonecell::CloneCell,
|
||||||
wire::{jay_select_toplevel::*, JaySelectToplevelId},
|
wire::{jay_select_toplevel::*, JaySelectToplevelId},
|
||||||
wl_usr::{usr_ifs::usr_jay_toplevel::UsrJayToplevel, usr_object::UsrObject, UsrCon},
|
wl_usr::{
|
||||||
|
usr_ifs::usr_jay_toplevel::{UsrJayToplevel, UsrJayToplevelOwner},
|
||||||
|
usr_object::UsrObject,
|
||||||
|
UsrCon,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::{convert::Infallible, rc::Rc},
|
std::{convert::Infallible, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
@ -15,6 +20,19 @@ pub struct UsrJaySelectToplevel {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UsrJaySelectToplevel {
|
||||||
|
fn send(&self, tl: Option<Rc<UsrJayToplevel>>) {
|
||||||
|
if let Some(owner) = self.owner.get() {
|
||||||
|
owner.done(tl);
|
||||||
|
} else {
|
||||||
|
if let Some(tl) = tl {
|
||||||
|
self.con.remove_obj(&*tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.con.remove_obj(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait UsrJaySelectToplevelOwner {
|
pub trait UsrJaySelectToplevelOwner {
|
||||||
fn done(&self, toplevel: Option<Rc<UsrJayToplevel>>);
|
fn done(&self, toplevel: Option<Rc<UsrJayToplevel>>);
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +40,7 @@ pub trait UsrJaySelectToplevelOwner {
|
||||||
impl JaySelectToplevelEventHandler for UsrJaySelectToplevel {
|
impl JaySelectToplevelEventHandler for UsrJaySelectToplevel {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
fn done(&self, ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn done(&self, ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
let tl = if ev.id.is_none() {
|
let tl = if ev.id.is_none() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -31,23 +49,31 @@ impl JaySelectToplevelEventHandler for UsrJaySelectToplevel {
|
||||||
con: self.con.clone(),
|
con: self.con.clone(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
version: self.version,
|
version: self.version,
|
||||||
|
toplevel_id: Default::default(),
|
||||||
});
|
});
|
||||||
self.con.add_object(tl.clone());
|
self.con.add_object(tl.clone());
|
||||||
Some(tl)
|
Some(tl)
|
||||||
};
|
};
|
||||||
match self.owner.get() {
|
'send: {
|
||||||
Some(owner) => owner.done(tl),
|
if self.version >= ID_SINCE {
|
||||||
_ => {
|
|
||||||
if let Some(tl) = tl {
|
if let Some(tl) = tl {
|
||||||
self.con.remove_obj(&*tl);
|
tl.owner.set(Some(slf.clone()));
|
||||||
|
break 'send;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.send(tl);
|
||||||
}
|
}
|
||||||
self.con.remove_obj(self);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UsrJayToplevelOwner for UsrJaySelectToplevel {
|
||||||
|
fn done(&self, tl: &Rc<UsrJayToplevel>) {
|
||||||
|
tl.owner.take();
|
||||||
|
self.send(Some(tl.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
usr_object_base! {
|
usr_object_base! {
|
||||||
self = UsrJaySelectToplevel = JaySelectToplevel;
|
self = UsrJaySelectToplevel = JaySelectToplevel;
|
||||||
version = self.version;
|
version = self.version;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ use {
|
||||||
object::Version,
|
object::Version,
|
||||||
utils::clonecell::CloneCell,
|
utils::clonecell::CloneCell,
|
||||||
wire::{jay_select_workspace::*, JaySelectWorkspaceId},
|
wire::{jay_select_workspace::*, JaySelectWorkspaceId},
|
||||||
wl_usr::{usr_ifs::usr_jay_workspace::UsrJayWorkspace, usr_object::UsrObject, UsrCon},
|
wl_usr::{
|
||||||
|
usr_ifs::usr_jay_workspace::{UsrJayWorkspace, UsrJayWorkspaceOwner},
|
||||||
|
usr_object::UsrObject,
|
||||||
|
UsrCon,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::{convert::Infallible, rc::Rc},
|
std::{convert::Infallible, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
@ -30,20 +34,30 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected(&self, ev: Selected, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn selected(&self, ev: Selected, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
let tl = Rc::new(UsrJayWorkspace {
|
let tl = Rc::new(UsrJayWorkspace {
|
||||||
id: ev.id,
|
id: ev.id,
|
||||||
con: self.con.clone(),
|
con: self.con.clone(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
version: self.version,
|
version: self.version,
|
||||||
|
linear_id: Default::default(),
|
||||||
|
output: Default::default(),
|
||||||
|
name: Default::default(),
|
||||||
});
|
});
|
||||||
self.con.add_object(tl.clone());
|
self.con.add_object(tl.clone());
|
||||||
|
tl.owner.set(Some(slf.clone()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrJayWorkspaceOwner for UsrJaySelectWorkspace {
|
||||||
|
fn done(&self, ws: &Rc<UsrJayWorkspace>) {
|
||||||
|
ws.owner.take();
|
||||||
match self.owner.get() {
|
match self.owner.get() {
|
||||||
Some(owner) => owner.done(ev.output, Some(tl)),
|
Some(owner) => owner.done(ws.output.get(), Some(ws.clone())),
|
||||||
_ => self.con.remove_obj(&*tl),
|
_ => self.con.remove_obj(&**ws),
|
||||||
}
|
}
|
||||||
self.con.remove_obj(self);
|
self.con.remove_obj(self);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use {
|
||||||
wire::{jay_toplevel::*, JayToplevelId},
|
wire::{jay_toplevel::*, JayToplevelId},
|
||||||
wl_usr::{usr_object::UsrObject, UsrCon},
|
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||||
},
|
},
|
||||||
std::{convert::Infallible, rc::Rc},
|
std::{cell::RefCell, convert::Infallible, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UsrJayToplevel {
|
pub struct UsrJayToplevel {
|
||||||
|
|
@ -13,10 +13,12 @@ pub struct UsrJayToplevel {
|
||||||
pub con: Rc<UsrCon>,
|
pub con: Rc<UsrCon>,
|
||||||
pub owner: CloneCell<Option<Rc<dyn UsrJayToplevelOwner>>>,
|
pub owner: CloneCell<Option<Rc<dyn UsrJayToplevelOwner>>>,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
|
pub toplevel_id: RefCell<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UsrJayToplevelOwner {
|
pub trait UsrJayToplevelOwner {
|
||||||
fn destroyed(&self) {}
|
fn destroyed(&self) {}
|
||||||
|
fn done(&self, tl: &Rc<UsrJayToplevel>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JayToplevelEventHandler for UsrJayToplevel {
|
impl JayToplevelEventHandler for UsrJayToplevel {
|
||||||
|
|
@ -28,6 +30,18 @@ impl JayToplevelEventHandler for UsrJayToplevel {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn id_(&self, ev: Id<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
*self.toplevel_id.borrow_mut() = Some(ev.id.to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn done(&self, _ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
if let Some(owner) = self.owner.get() {
|
||||||
|
owner.done(slf);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usr_object_base! {
|
usr_object_base! {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,11 @@ use {
|
||||||
wire::{jay_workspace::*, JayWorkspaceId},
|
wire::{jay_workspace::*, JayWorkspaceId},
|
||||||
wl_usr::{usr_object::UsrObject, UsrCon},
|
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||||
},
|
},
|
||||||
std::{convert::Infallible, rc::Rc},
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
convert::Infallible,
|
||||||
|
rc::Rc,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UsrJayWorkspace {
|
pub struct UsrJayWorkspace {
|
||||||
|
|
@ -13,21 +17,20 @@ pub struct UsrJayWorkspace {
|
||||||
pub con: Rc<UsrCon>,
|
pub con: Rc<UsrCon>,
|
||||||
pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceOwner>>>,
|
pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceOwner>>>,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
|
pub linear_id: Cell<u32>,
|
||||||
|
pub output: Cell<u32>,
|
||||||
|
pub name: RefCell<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UsrJayWorkspaceOwner {
|
pub trait UsrJayWorkspaceOwner {
|
||||||
fn linear_id(self: Rc<Self>, ev: &LinearId) {
|
fn destroyed(&self, ws: &UsrJayWorkspace) {
|
||||||
let _ = ev;
|
let _ = ws;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self, ev: &Name) {
|
fn done(&self, ws: &Rc<UsrJayWorkspace>) {
|
||||||
let _ = ev;
|
let _ = ws;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroyed(&self) {}
|
|
||||||
|
|
||||||
fn done(&self) {}
|
|
||||||
|
|
||||||
fn output(self: Rc<Self>, ev: &Output) {
|
fn output(self: Rc<Self>, ev: &Output) {
|
||||||
let _ = ev;
|
let _ = ev;
|
||||||
}
|
}
|
||||||
|
|
@ -41,34 +44,31 @@ impl JayWorkspaceEventHandler for UsrJayWorkspace {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
fn linear_id(&self, ev: LinearId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn linear_id(&self, ev: LinearId, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
if let Some(owner) = self.owner.get() {
|
self.linear_id.set(ev.linear_id);
|
||||||
owner.linear_id(&ev);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
*self.name.borrow_mut() = Some(ev.name.to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroyed(&self, _ev: Destroyed, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
if let Some(owner) = self.owner.get() {
|
if let Some(owner) = self.owner.get() {
|
||||||
owner.name(&ev);
|
owner.destroyed(slf);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroyed(&self, _ev: Destroyed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn done(&self, _ev: Done, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
if let Some(owner) = self.owner.get() {
|
if let Some(owner) = self.owner.get() {
|
||||||
owner.destroyed();
|
owner.done(slf);
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn done(&self, _ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
|
||||||
if let Some(owner) = self.owner.get() {
|
|
||||||
owner.done();
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output(&self, ev: Output, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn output(&self, ev: Output, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.output.set(ev.global_name);
|
||||||
if let Some(owner) = self.owner.get() {
|
if let Some(owner) = self.owner.get() {
|
||||||
owner.output(&ev);
|
owner.output(&ev);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher {
|
||||||
con: self.con.clone(),
|
con: self.con.clone(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
version: self.version,
|
version: self.version,
|
||||||
|
linear_id: Default::default(),
|
||||||
|
output: Default::default(),
|
||||||
|
name: Default::default(),
|
||||||
});
|
});
|
||||||
self.con.add_object(jw.clone());
|
self.con.add_object(jw.clone());
|
||||||
if let Some(owner) = self.owner.get() {
|
if let Some(owner) = self.owner.get() {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use {
|
||||||
wire::{wl_output::*, WlOutputId},
|
wire::{wl_output::*, WlOutputId},
|
||||||
wl_usr::{usr_object::UsrObject, UsrCon},
|
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||||
},
|
},
|
||||||
std::{convert::Infallible, rc::Rc},
|
std::{cell::RefCell, convert::Infallible, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UsrWlOutput {
|
pub struct UsrWlOutput {
|
||||||
|
|
@ -13,6 +13,7 @@ pub struct UsrWlOutput {
|
||||||
pub con: Rc<UsrCon>,
|
pub con: Rc<UsrCon>,
|
||||||
pub owner: CloneCell<Option<Rc<dyn UsrWlOutputOwner>>>,
|
pub owner: CloneCell<Option<Rc<dyn UsrWlOutputOwner>>>,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
|
pub name: RefCell<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UsrWlOutputOwner {
|
pub trait UsrWlOutputOwner {
|
||||||
|
|
@ -71,6 +72,7 @@ impl WlOutputEventHandler for UsrWlOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn name(&self, ev: Name<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
*self.name.borrow_mut() = Some(ev.name.to_string());
|
||||||
if let Some(owner) = self.owner.get() {
|
if let Some(owner) = self.owner.get() {
|
||||||
owner.name(&ev);
|
owner.name(&ev);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,16 @@ impl UsrWlSurface {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn damage(&self) {
|
||||||
|
self.con.request(DamageBuffer {
|
||||||
|
self_id: self.id,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: i32::MAX,
|
||||||
|
height: i32::MAX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn frame<F>(&self, f: F)
|
pub fn frame<F>(&self, f: F)
|
||||||
where
|
where
|
||||||
F: FnOnce() + 'static,
|
F: FnOnce() + 'static,
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,11 @@ request get_xwayland (since = 11) {
|
||||||
id: id(jay_xwayland),
|
id: id(jay_xwayland),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request get_toplevel (since = 12) {
|
||||||
|
id: id(jay_select_toplevel),
|
||||||
|
toplevel_id: str,
|
||||||
|
}
|
||||||
|
|
||||||
# events
|
# events
|
||||||
|
|
||||||
event client_id {
|
event client_id {
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,10 @@ request destroy {
|
||||||
|
|
||||||
event destroyed {
|
event destroyed {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event id (since = 12) {
|
||||||
|
id: str,
|
||||||
|
}
|
||||||
|
|
||||||
|
event done (since = 12) {
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue