1
0
Fork 0
forked from wry/wry
wry/src/portal/ptl_screencast.rs
2025-07-01 11:20:48 +02:00

967 lines
32 KiB
Rust

mod screencast_gui;
use {
crate::{
allocator::{AllocatorError, BO_USE_RENDERING, BufferObject, BufferUsage},
dbus::{DbusObject, DictEntry, PendingReply, prelude::Variant},
format::{Format, XRGB8888},
ifs::{jay_compositor::GET_TOPLEVEL_SINCE, jay_screencast::CLIENT_BUFFERS_SINCE},
pipewire::{
pw_con::PwCon,
pw_ifs::pw_client_node::{
PwClientNode, PwClientNodeBufferConfig, PwClientNodeOwner, PwClientNodePort,
PwClientNodePortSupportedFormat, PwClientNodePortSupportedFormats,
SUPPORTED_META_VIDEO_CROP,
},
pw_pod::{
PwPodRectangle, SPA_DATA_DmaBuf, SPA_MEDIA_SUBTYPE_raw, SPA_MEDIA_TYPE_video,
SPA_STATUS_HAVE_DATA, SPA_VIDEO_FORMAT_UNKNOWN, SpaChunkFlags, spa_point,
spa_rectangle, spa_region,
},
},
portal::{
PORTAL_SUCCESS, PortalState,
ptl_display::{PortalDisplay, PortalDisplayId, PortalOutput},
ptl_remote_desktop::RemoteDesktopPhase,
ptl_screencast::screencast_gui::SelectionGui,
ptl_session::{PortalSession, PortalSessionReply},
},
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
opaque::Opaque,
},
video::{LINEAR_MODIFIER, Modifier, dmabuf::DmaBuf},
wire::jay_screencast::Ready,
wire_dbus::{
org,
org::freedesktop::impl_::portal::{
screen_cast::{
CreateSession, CreateSessionReply, SelectSources, SelectSourcesReply, Start,
StartReply,
},
session::CloseReply as SessionCloseReply,
},
},
wl_usr::usr_ifs::{
usr_jay_screencast::{
UsrJayScreencast, UsrJayScreencastOwner, UsrJayScreencastServerConfig,
},
usr_jay_select_toplevel::UsrJaySelectToplevel,
usr_jay_select_workspace::UsrJaySelectWorkspace,
usr_jay_toplevel::UsrJayToplevel,
usr_jay_workspace::UsrJayWorkspace,
usr_linux_buffer_params::{UsrLinuxBufferParams, UsrLinuxBufferParamsOwner},
usr_wl_buffer::UsrWlBuffer,
},
},
serde::{Deserialize, Serialize},
std::{
borrow::Cow,
cell::{Cell, RefCell},
ops::Deref,
rc::Rc,
sync::atomic::Ordering::{Acquire, Relaxed, Release},
},
thiserror::Error,
};
#[derive(Clone)]
pub enum ScreencastPhase {
Init,
SourcesSelected(Rc<SourcesSelectedScreencast>),
Selecting(Rc<SelectingScreencast>),
SelectingWindow(Rc<SelectingWindowScreencast>),
SelectingWorkspace(Rc<SelectingWorkspaceScreencast>),
Starting(Rc<StartingScreencast>),
Started(Rc<StartedScreencast>),
Terminated,
}
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
pub struct SourcesSelectedScreencast {
pub restore_data: Cell<Option<Result<RestoreData, RestoreError>>>,
}
#[derive(Clone)]
pub struct SelectingScreencastCore {
pub session: Rc<PortalSession>,
pub request_obj: Rc<DbusObject>,
}
pub struct SelectingScreencast {
pub core: SelectingScreencastCore,
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
pub restore_data: Cell<Option<RestoreData>>,
}
pub struct SelectingWindowScreencast {
pub core: SelectingScreencastCore,
pub dpy: Rc<PortalDisplay>,
pub selector: Rc<UsrJaySelectToplevel>,
pub restoring: bool,
}
pub struct SelectingWorkspaceScreencast {
pub core: SelectingScreencastCore,
pub dpy: Rc<PortalDisplay>,
pub selector: Rc<UsrJaySelectWorkspace>,
}
pub struct StartingScreencast {
pub session: Rc<PortalSession>,
pub _request_obj: Rc<DbusObject>,
pub node: Rc<PwClientNode>,
pub dpy: Rc<PortalDisplay>,
pub target: ScreencastTarget,
}
pub enum ScreencastTarget {
Output(Rc<PortalOutput>),
Workspace(Rc<PortalOutput>, Rc<UsrJayWorkspace>, bool),
Toplevel(Rc<UsrJayToplevel>),
}
pub struct StartedScreencast {
pub session: Rc<PortalSession>,
pub node: Rc<PwClientNode>,
pub port: Rc<PwClientNodePort>,
pub buffer_objects: RefCell<Vec<Rc<dyn BufferObject>>>,
pub buffers: RefCell<Vec<DmaBuf>>,
pub pending_buffers: RefCell<Vec<Rc<UsrLinuxBufferParams>>>,
pub buffers_valid: Cell<bool>,
pub dpy: Rc<PortalDisplay>,
pub jay_screencast: Rc<UsrJayScreencast>,
pub port_buffer_valid: Cell<bool>,
pub fixated: Cell<bool>,
pub format: Cell<&'static Format>,
pub modifier: Cell<Modifier>,
pub width: Cell<i32>,
pub height: Cell<i32>,
}
bitflags! {
CursorModes: u32;
HIDDEN = 1,
EMBEDDED = 2,
METADATA = 4,
}
bitflags! {
SourceTypes: u32;
MONITOR = 1,
WINDOW = 2,
}
impl PwClientNodeOwner for StartingScreencast {
fn bound_id(&self, node_id: u32) {
{
let output = match &self.target {
ScreencastTarget::Output(o) => Some(o),
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 mut supported_formats = PwClientNodePortSupportedFormats {
media_type: Some(SPA_MEDIA_TYPE_video),
media_sub_type: Some(SPA_MEDIA_SUBTYPE_raw),
video_size: None,
formats: vec![PwClientNodePortSupportedFormat {
format: XRGB8888,
modifiers: vec![LINEAR_MODIFIER],
}],
};
init_supported_formats(&mut supported_formats, &self.dpy);
let jsc_version = self.dpy.jc.version;
let num_buffers = (jsc_version >= CLIENT_BUFFERS_SINCE).then_some(3);
let port = self.node.create_port(true, supported_formats, num_buffers);
port.can_alloc_buffers.set(true);
port.supported_metas.set(SUPPORTED_META_VIDEO_CROP);
let jsc = self.dpy.jc.create_screencast();
match &self.target {
ScreencastTarget::Output(o) => {
jsc.set_output(&o.jay);
jsc.set_allow_all_workspaces(true);
}
ScreencastTarget::Workspace(o, ws, _) => {
jsc.set_output(&o.jay);
jsc.allow_workspace(ws);
}
ScreencastTarget::Toplevel(t) => jsc.set_toplevel(t),
}
jsc.set_use_linear_buffers(true);
jsc.configure();
match &self.target {
ScreencastTarget::Output(_) => {}
ScreencastTarget::Workspace(_, w, true) => {
self.dpy.con.remove_obj(&**w);
}
ScreencastTarget::Workspace(_, _, false) => {}
ScreencastTarget::Toplevel(t) => {
self.dpy.con.remove_obj(&**t);
}
}
let started = Rc::new(StartedScreencast {
session: self.session.clone(),
node: self.node.clone(),
port,
buffer_objects: Default::default(),
buffers: Default::default(),
pending_buffers: Default::default(),
buffers_valid: Cell::new(false),
dpy: self.dpy.clone(),
jay_screencast: jsc,
port_buffer_valid: Cell::new(false),
fixated: Cell::new(jsc_version < CLIENT_BUFFERS_SINCE),
format: Cell::new(XRGB8888),
modifier: Cell::new(LINEAR_MODIFIER),
width: Cell::new(1),
height: Cell::new(1),
});
self.session
.sc_phase
.set(ScreencastPhase::Started(started.clone()));
started.jay_screencast.owner.set(Some(started.clone()));
self.node.owner.set(Some(started.clone()));
}
}
impl PwClientNodeOwner for StartedScreencast {
fn port_format_changed(&self, port: &Rc<PwClientNodePort>) {
let format = &*port.negotiated_format.borrow();
if self.fixated.get() {
return;
}
let (Some(fmt), Some(modifiers)) = (format.format, &format.modifiers) else {
return;
};
let modifier;
let planes;
match self.allocate_buffer(fmt, modifiers) {
Ok(bo) => {
let dmabuf = bo.dmabuf();
modifier = dmabuf.modifier;
planes = dmabuf.planes.len();
}
Err(e) => {
log::error!("Could not allocate buffer: {}", ErrorFmt(e));
self.session.kill();
return;
}
};
log::debug!(
"Negotiated format {} with modifier 0x{modifier:08x} at size {}x{}",
fmt.name,
self.width.get(),
self.height.get(),
);
self.port.supported_formats.borrow_mut().formats = vec![PwClientNodePortSupportedFormat {
format: fmt,
modifiers: vec![modifier],
}];
self.port.buffer_config.borrow_mut().planes = Some(planes);
self.node.send_port_update(&self.port, true);
self.format.set(fmt);
self.modifier.set(modifier);
self.fixated.set(true);
}
fn use_buffers(self: Rc<Self>, port: &Rc<PwClientNodePort>) {
if self.jay_screencast.version < CLIENT_BUFFERS_SINCE {
self.node
.send_port_output_buffers(port, &self.buffers.borrow_mut());
self.buffers_valid.set(true);
return;
}
self.buffers_valid.set(false);
self.port_buffer_valid.set(false);
let Some(dmabuf) = self.dpy.dmabuf.get() else {
log::error!("Display does not support dmabuf");
self.session.kill();
return;
};
self.jay_screencast.clear_buffers();
self.jay_screencast.configure();
self.buffer_objects.borrow_mut().clear();
self.buffers.borrow_mut().clear();
for buffer in self.pending_buffers.borrow_mut().drain(..) {
self.dpy.con.remove_obj(&*buffer);
}
for _ in 0..self.port.buffers.borrow().len() {
let res = self.allocate_buffer(self.format.get(), &[self.modifier.get()]);
match res {
Ok(b) => {
let params = dmabuf.create_params();
params.create(&b.dmabuf());
params.owner.set(Some(self.clone()));
self.buffers.borrow_mut().push(b.dmabuf().clone());
self.buffer_objects.borrow_mut().push(b);
self.pending_buffers.borrow_mut().push(params);
}
Err(e) => {
log::error!("Could not allocate buffer: {}", ErrorFmt(e));
self.session.kill();
return;
}
}
}
self.node
.send_port_output_buffers(&self.port, &self.buffers.borrow());
}
fn start(self: Rc<Self>) {
self.jay_screencast.set_running(true);
self.jay_screencast.configure();
}
fn pause(self: Rc<Self>) {
self.jay_screencast.set_running(false);
self.jay_screencast.configure();
}
fn suspend(self: Rc<Self>) {
self.jay_screencast.set_running(false);
self.jay_screencast.configure();
}
}
#[derive(Debug, Error)]
enum BufferAllocationError {
#[error("Display has no render context")]
NoRenderContext,
#[error(transparent)]
Allocator(#[from] AllocatorError),
}
impl StartedScreencast {
fn allocate_buffer(
&self,
format: &'static Format,
modifiers: &[Modifier],
) -> Result<Rc<dyn BufferObject>, BufferAllocationError> {
let Some(ctx) = self.dpy.render_ctx.get() else {
return Err(BufferAllocationError::NoRenderContext);
};
let mut usage = BO_USE_RENDERING;
if let Some(sf) = &ctx.server_formats
&& let Some(format) = sf.get(&format.drm)
{
let no_render_usage = modifiers.iter().all(|m| {
format
.write_modifiers
.get(m)
.map(|w| !w.needs_render_usage)
.unwrap_or(false)
});
if no_render_usage {
usage = BufferUsage::none();
}
}
let buffer = ctx.ctx.ctx.allocator().create_bo(
&self.dpy.state.dma_buf_ids,
self.width.get(),
self.height.get(),
format,
modifiers,
usage,
)?;
Ok(buffer)
}
}
impl SelectingScreencastCore {
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
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()),
("node.name".to_string(), "jay-desktop-portal".to_string()),
("node.driver".to_string(), "true".to_string()),
]);
let starting = Rc::new(StartingScreencast {
session: self.session.clone(),
_request_obj: self.request_obj.clone(),
node,
dpy: dpy.clone(),
target,
});
self.session
.sc_phase
.set(ScreencastPhase::Starting(starting.clone()));
starting.node.owner.set(Some(starting.clone()));
dpy.sessions.set(
self.session.session_obj.path().to_owned(),
self.session.clone(),
);
}
}
impl PortalSession {
fn dbus_select_sources(
self: &Rc<Self>,
req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>,
) {
match self.sc_phase.get() {
ScreencastPhase::Init => {}
_ => {
self.kill();
reply.err("Sources have already been selected");
return;
}
}
self.sc_phase.set(ScreencastPhase::SourcesSelected(Rc::new(
SourcesSelectedScreencast {
restore_data: Cell::new(get_restore_data(&req)),
},
)));
reply.ok(&SelectSourcesReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_start_screencast(
self: &Rc<Self>,
req: Start<'_>,
reply: PendingReply<StartReply<'static>>,
) {
let restore_data = match self.sc_phase.get() {
ScreencastPhase::SourcesSelected(s) => s.restore_data.take(),
_ => {
self.kill();
reply.err("Session is not in the correct phase for starting");
return;
}
};
let request_obj = match self.state.dbus.add_object(req.handle.to_string()) {
Ok(r) => Rc::new(r),
Err(_) => {
self.kill();
reply.err("Request handle is not unique");
return;
}
};
{
use org::freedesktop::impl_::portal::request::*;
request_obj.add_method::<Close, _>({
let slf = self.clone();
move |_, pr| {
slf.kill();
pr.ok(&CloseReply);
}
});
}
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();
for dpy in self.state.displays.lock().values() {
if dpy.outputs.len() > 0 {
guis.set(dpy.id, SelectionGui::new(self, dpy, restore_data.is_some()));
}
}
if guis.is_empty() {
self.kill();
self.reply_err("There are no running displays");
return;
}
self.sc_phase
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
core: SelectingScreencastCore {
session: self.clone(),
request_obj: request_obj.clone(),
},
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 {
fn buffers(&self, buffers: Vec<DmaBuf>) {
if buffers.len() == 0 {
return;
}
let buffer = &buffers[0];
*self.port.supported_formats.borrow_mut() = PwClientNodePortSupportedFormats {
media_type: Some(SPA_MEDIA_TYPE_video),
media_sub_type: Some(SPA_MEDIA_SUBTYPE_raw),
video_size: Some(PwPodRectangle {
width: buffer.width as _,
height: buffer.height as _,
}),
formats: vec![PwClientNodePortSupportedFormat {
format: buffer.format,
modifiers: vec![buffer.modifier],
}],
};
*self.port.buffer_config.borrow_mut() = PwClientNodeBufferConfig {
num_buffers: Some(buffers.len()),
planes: Some(buffer.planes.len()),
data_type: SPA_DATA_DmaBuf,
};
self.node.send_port_update(&self.port, true);
self.node.send_active(true);
self.fixated.set(true);
*self.buffers.borrow_mut() = buffers;
self.buffers_valid.set(false);
}
fn ready(&self, ev: &Ready) {
let idx = ev.idx as usize;
let buffers = &*self.buffers.borrow();
let pbuffers = self.port.buffers.borrow();
let buffer = &buffers[idx];
let discard_buffer = || {
self.jay_screencast.release_buffer(idx);
};
if !self.buffers_valid.get() {
return;
}
let Some(io) = self.port.io_buffers.get() else {
discard_buffer();
return;
};
let Some(pbuffer) = pbuffers.get(idx) else {
discard_buffer();
return;
};
let io = unsafe { io.read() };
if io.status.load(Acquire) == SPA_STATUS_HAVE_DATA.0 {
discard_buffer();
return;
}
for (chunk, plane) in pbuffer.chunks.iter().zip(buffer.planes.iter()) {
let chunk = unsafe { chunk.write() };
chunk.flags = SpaChunkFlags::none();
chunk.offset = plane.offset;
chunk.stride = plane.stride;
chunk.size = plane.stride * buffer.height as u32;
}
if let Some(crop) = &pbuffer.meta_video_crop {
unsafe { crop.write() }.region = spa_region {
position: spa_point { x: 0, y: 0 },
size: spa_rectangle {
width: buffer.width as _,
height: buffer.height as _,
},
};
}
let buffer_id = io.buffer_id.load(Relaxed) as usize;
if self.port_buffer_valid.get() {
if buffer_id != idx {
if buffer_id < buffers.len() {
self.jay_screencast.release_buffer(buffer_id);
}
}
}
io.buffer_id.store(ev.idx, Relaxed);
io.status.store(SPA_STATUS_HAVE_DATA.0, Release);
self.port_buffer_valid.set(true);
self.port.node.drive();
}
fn destroyed(&self) {
self.session.kill();
}
fn config(&self, config: UsrJayScreencastServerConfig) {
let mut changed = false;
let width = config.width.max(1);
let height = config.height.max(1);
changed |= self.width.replace(width) != width;
changed |= self.height.replace(height) != height;
self.port.supported_formats.borrow_mut().video_size = Some(PwPodRectangle {
width: self.width.get() as _,
height: self.height.get() as _,
});
if changed && self.dpy.jc.version >= CLIENT_BUFFERS_SINCE {
self.fixated.set(false);
init_supported_formats(&mut self.port.supported_formats.borrow_mut(), &self.dpy);
}
self.node.send_port_update(&self.port, self.fixated.get());
self.node.send_active(true);
}
}
fn init_supported_formats(
supported_formats: &mut PwClientNodePortSupportedFormats,
dpy: &PortalDisplay,
) {
let Some(ctx) = dpy.render_ctx.get() else {
return;
};
let Some(server_formats) = &ctx.server_formats else {
return;
};
supported_formats.formats.clear();
for format in server_formats.values() {
if format.write_modifiers.is_empty() {
continue;
}
if format.format.pipewire == SPA_VIDEO_FORMAT_UNKNOWN {
continue;
}
let ptl_format = PwClientNodePortSupportedFormat {
format: format.format,
modifiers: format.write_modifiers.keys().copied().collect(),
};
supported_formats.formats.push(ptl_format);
}
}
impl UsrLinuxBufferParamsOwner for StartedScreencast {
fn created(&self, buffer: Rc<UsrWlBuffer>) {
self.buffers_valid.set(true);
self.jay_screencast.add_buffer(&buffer);
self.jay_screencast.configure();
self.dpy.con.remove_obj(&*buffer);
}
fn failed(&self) {
log::error!("Buffer import failed");
self.session.kill();
}
}
pub(super) fn add_screencast_dbus_members(
state_: &Rc<PortalState>,
pw_con: &Rc<PwCon>,
object: &DbusObject,
) {
use org::freedesktop::impl_::portal::screen_cast::*;
let state = state_.clone();
let pw_con = pw_con.clone();
object.add_method::<CreateSession, _>(move |req, pr| {
dbus_create_session(&state, &pw_con, req, pr);
});
let state = state_.clone();
object.add_method::<SelectSources, _>(move |req, pr| {
dbus_select_sources(&state, req, pr);
});
let state = state_.clone();
object.add_method::<Start, _>(move |req, pr| {
dbus_start(&state, req, pr);
});
object.set_property::<AvailableSourceTypes>(Variant::U32(MONITOR.0));
object.set_property::<AvailableCursorModes>(Variant::U32(EMBEDDED.0));
object.set_property::<version>(Variant::U32(5));
}
fn dbus_create_session(
state: &Rc<PortalState>,
pw_con: &Rc<PwCon>,
req: CreateSession,
reply: PendingReply<CreateSessionReply<'static>>,
) {
log::info!("Create Session {:#?}", req);
if state.sessions.contains(req.session_handle.0.deref()) {
reply.err("Session already exists");
return;
}
let obj = match state.dbus.add_object(req.session_handle.0.to_string()) {
Ok(obj) => obj,
Err(_) => {
reply.err("Session path is not unique");
return;
}
};
let session = Rc::new(PortalSession {
_id: state.id(),
state: state.clone(),
pw_con: Some(pw_con.clone()),
app: req.app_id.to_string(),
session_obj: obj,
sc_phase: CloneCell::new(ScreencastPhase::Init),
rd_phase: CloneCell::new(RemoteDesktopPhase::Init),
start_reply: Default::default(),
});
{
use org::freedesktop::impl_::portal::session::*;
let ses = session.clone();
session.session_obj.add_method::<Close, _>(move |_, pr| {
ses.kill();
pr.ok(&SessionCloseReply);
});
session.session_obj.set_property::<version>(Variant::U32(4));
}
state
.sessions
.set(req.session_handle.0.to_string(), session);
reply.ok(&CreateSessionReply {
response: PORTAL_SUCCESS,
results: Default::default(),
});
}
fn dbus_select_sources(
state: &Rc<PortalState>,
req: SelectSources,
reply: PendingReply<SelectSourcesReply<'static>>,
) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_select_sources(req, reply);
}
}
fn dbus_start(state: &Rc<PortalState>, req: Start, reply: PendingReply<StartReply<'static>>) {
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
s.dbus_start_screencast(req, reply);
}
}
fn get_session<T>(
state: &Rc<PortalState>,
reply: &PendingReply<T>,
handle: &str,
) -> Option<Rc<PortalSession>> {
let res = state.sessions.get(handle);
if res.is_none() {
let msg = format!("Screencast session `{}` does not exist", handle);
reply.err(&msg);
}
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,
}