portal: implement RemoteDesktop portal
This commit is contained in:
parent
40e87f8f91
commit
665127e6c0
22 changed files with 994 additions and 35 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
[preferred]
|
[preferred]
|
||||||
default=gtk
|
default=gtk
|
||||||
org.freedesktop.impl.portal.ScreenCast=jay
|
org.freedesktop.impl.portal.ScreenCast=jay
|
||||||
|
org.freedesktop.impl.portal.RemoteDesktop=jay
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
[portal]
|
[portal]
|
||||||
DBusName=org.freedesktop.impl.portal.desktop.jay
|
DBusName=org.freedesktop.impl.portal.desktop.jay
|
||||||
Interfaces=org.freedesktop.impl.portal.ScreenCast;
|
Interfaces=org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
- Add support for tearing.
|
- Add support for tearing.
|
||||||
- Add support for touch input.
|
- Add support for touch input.
|
||||||
- Add support for libei.
|
- Add support for libei.
|
||||||
|
- Add support for RemoteDesktop portal.
|
||||||
|
|
||||||
# 1.4.0 (2024-07-07)
|
# 1.4.0 (2024-07-07)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,7 @@ async fn accept(fd: Rc<OwnedFd>, state: Rc<State>) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let id = state.clients.id();
|
if let Err(e) = state.ei_clients.spawn(&state, fd) {
|
||||||
if let Err(e) = state.ei_clients.spawn(id, &state, fd) {
|
|
||||||
log::error!("Could not spawn a client: {}", ErrorFmt(e));
|
log::error!("Could not spawn a client: {}", ErrorFmt(e));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use {
|
||||||
asyncevent::AsyncEvent,
|
asyncevent::AsyncEvent,
|
||||||
buffd::{EiMsgFormatter, EiMsgParser, EiMsgParserError, OutBufferSwapchain},
|
buffd::{EiMsgFormatter, EiMsgParser, EiMsgParserError, OutBufferSwapchain},
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
|
debug_fn::debug_fn,
|
||||||
errorfmt::ErrorFmt,
|
errorfmt::ErrorFmt,
|
||||||
numcell::NumCell,
|
numcell::NumCell,
|
||||||
pid_info::{get_pid_info, get_socket_creds, PidInfo},
|
pid_info::{get_pid_info, get_socket_creds, PidInfo},
|
||||||
|
|
@ -31,7 +32,7 @@ use {
|
||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
},
|
},
|
||||||
uapi::{c, OwnedFd},
|
uapi::OwnedFd,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod ei_error;
|
mod ei_error;
|
||||||
|
|
@ -64,26 +65,21 @@ impl EiClients {
|
||||||
mem::take(self.shutdown_clients.borrow_mut().deref_mut());
|
mem::take(self.shutdown_clients.borrow_mut().deref_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(
|
pub fn spawn(&self, global: &Rc<State>, socket: Rc<OwnedFd>) -> Result<(), EiClientError> {
|
||||||
&self,
|
|
||||||
id: ClientId,
|
|
||||||
global: &Rc<State>,
|
|
||||||
socket: Rc<OwnedFd>,
|
|
||||||
) -> Result<(), EiClientError> {
|
|
||||||
let Some((uid, pid)) = get_socket_creds(&socket) else {
|
let Some((uid, pid)) = get_socket_creds(&socket) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
self.spawn2(id, global, socket, uid, pid)?;
|
let pid_info = get_pid_info(uid, pid);
|
||||||
|
self.spawn2(global, socket, Some(pid_info), None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn2(
|
pub fn spawn2(
|
||||||
&self,
|
&self,
|
||||||
id: ClientId,
|
|
||||||
global: &Rc<State>,
|
global: &Rc<State>,
|
||||||
socket: Rc<OwnedFd>,
|
socket: Rc<OwnedFd>,
|
||||||
uid: c::uid_t,
|
pid_info: Option<PidInfo>,
|
||||||
pid: c::pid_t,
|
app_id: Option<String>,
|
||||||
) -> Result<Rc<EiClient>, EiClientError> {
|
) -> Result<Rc<EiClient>, EiClientError> {
|
||||||
let versions = EiInterfaceVersions {
|
let versions = EiInterfaceVersions {
|
||||||
ei_button: EiInterfaceVersion::new(1),
|
ei_button: EiInterfaceVersion::new(1),
|
||||||
|
|
@ -100,7 +96,7 @@ impl EiClients {
|
||||||
ei_touchscreen: EiInterfaceVersion::new(1),
|
ei_touchscreen: EiInterfaceVersion::new(1),
|
||||||
};
|
};
|
||||||
let data = Rc::new(EiClient {
|
let data = Rc::new(EiClient {
|
||||||
id,
|
id: global.clients.id(),
|
||||||
state: global.clone(),
|
state: global.clone(),
|
||||||
context: Cell::new(EiContext::Receiver),
|
context: Cell::new(EiContext::Receiver),
|
||||||
connection: Default::default(),
|
connection: Default::default(),
|
||||||
|
|
@ -111,10 +107,11 @@ impl EiClients {
|
||||||
flush_request: Default::default(),
|
flush_request: Default::default(),
|
||||||
shutdown: Default::default(),
|
shutdown: Default::default(),
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
pid_info: get_pid_info(uid, pid),
|
pid_info,
|
||||||
disconnect_announced: Cell::new(false),
|
disconnect_announced: Cell::new(false),
|
||||||
versions,
|
versions,
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
|
app_id,
|
||||||
last_serial: Default::default(),
|
last_serial: Default::default(),
|
||||||
});
|
});
|
||||||
track!(data, data);
|
track!(data, data);
|
||||||
|
|
@ -127,12 +124,17 @@ impl EiClients {
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
};
|
};
|
||||||
log::info!(
|
log::info!(
|
||||||
"Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}",
|
"Client {} connected{:?}",
|
||||||
id,
|
data.id,
|
||||||
pid,
|
debug_fn(|fmt| {
|
||||||
uid,
|
if let Some(p) = &data.pid_info {
|
||||||
client.data.socket.raw(),
|
write!(fmt, ", pid: {}, uid: {}, comm: {:?}", p.pid, p.uid, p.comm)?;
|
||||||
data.pid_info.comm,
|
}
|
||||||
|
if let Some(app_id) = &data.app_id {
|
||||||
|
write!(fmt, ", app-id: {app_id:?}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
self.clients.borrow_mut().insert(client.data.id, client);
|
self.clients.borrow_mut().insert(client.data.id, client);
|
||||||
Ok(data)
|
Ok(data)
|
||||||
|
|
@ -199,10 +201,11 @@ pub struct EiClient {
|
||||||
flush_request: AsyncEvent,
|
flush_request: AsyncEvent,
|
||||||
shutdown: AsyncEvent,
|
shutdown: AsyncEvent,
|
||||||
pub tracker: Tracker<EiClient>,
|
pub tracker: Tracker<EiClient>,
|
||||||
pub pid_info: PidInfo,
|
pub pid_info: Option<PidInfo>,
|
||||||
pub disconnect_announced: Cell<bool>,
|
pub disconnect_announced: Cell<bool>,
|
||||||
pub versions: EiInterfaceVersions,
|
pub versions: EiInterfaceVersions,
|
||||||
pub name: RefCell<Option<String>>,
|
pub name: RefCell<Option<String>>,
|
||||||
|
pub app_id: Option<String>,
|
||||||
pub last_serial: NumCell<u64>,
|
pub last_serial: NumCell<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ pub mod ext_session_lock_v1;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
pub mod jay_compositor;
|
pub mod jay_compositor;
|
||||||
pub mod jay_damage_tracking;
|
pub mod jay_damage_tracking;
|
||||||
|
pub mod jay_ei_session;
|
||||||
|
pub mod jay_ei_session_builder;
|
||||||
pub mod jay_idle;
|
pub mod jay_idle;
|
||||||
pub mod jay_input;
|
pub mod jay_input;
|
||||||
pub mod jay_log_file;
|
pub mod jay_log_file;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use {
|
||||||
client::{Client, ClientCaps, ClientError, CAP_JAY_COMPOSITOR},
|
client::{Client, ClientCaps, ClientError, CAP_JAY_COMPOSITOR},
|
||||||
globals::{Global, GlobalName},
|
globals::{Global, GlobalName},
|
||||||
ifs::{
|
ifs::{
|
||||||
|
jay_ei_session_builder::JayEiSessionBuilder,
|
||||||
jay_idle::JayIdle,
|
jay_idle::JayIdle,
|
||||||
jay_input::JayInput,
|
jay_input::JayInput,
|
||||||
jay_log_file::JayLogFile,
|
jay_log_file::JayLogFile,
|
||||||
|
|
@ -30,6 +31,8 @@ use {
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
|
||||||
|
|
||||||
pub struct JayCompositorGlobal {
|
pub struct JayCompositorGlobal {
|
||||||
name: GlobalName,
|
name: GlobalName,
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +69,7 @@ impl Global for JayCompositorGlobal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version(&self) -> u32 {
|
fn version(&self) -> u32 {
|
||||||
4
|
5
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_caps(&self) -> ClientCaps {
|
fn required_caps(&self) -> ClientCaps {
|
||||||
|
|
@ -377,6 +380,19 @@ impl JayCompositorRequestHandler for JayCompositor {
|
||||||
seat.global.select_workspace(selector);
|
seat.global.select_workspace(selector);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_ei_session(&self, req: CreateEiSession, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
let obj = Rc::new(JayEiSessionBuilder {
|
||||||
|
id: req.id,
|
||||||
|
client: self.client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
version: self.version,
|
||||||
|
app_id: Default::default(),
|
||||||
|
});
|
||||||
|
track!(self.client, obj);
|
||||||
|
self.client.add_client_obj(&obj)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_base! {
|
object_base! {
|
||||||
|
|
|
||||||
81
src/ifs/jay_ei_session.rs
Normal file
81
src/ifs/jay_ei_session.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError, ClientId},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::{Object, Version},
|
||||||
|
wire::{
|
||||||
|
jay_ei_session::{Created, Destroyed, Failed, JayEiSessionRequestHandler, Release},
|
||||||
|
JayEiSessionId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
thiserror::Error,
|
||||||
|
uapi::OwnedFd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct JayEiSession {
|
||||||
|
pub id: JayEiSessionId,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub ei_client_id: Option<ClientId>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
pub version: Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayEiSession {
|
||||||
|
pub fn send_created(&self, fd: &Rc<OwnedFd>) {
|
||||||
|
self.client.event(Created {
|
||||||
|
self_id: self.id,
|
||||||
|
fd: fd.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_failed(&self, reason: &str) {
|
||||||
|
self.client.event(Failed {
|
||||||
|
self_id: self.id,
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_destroyed(&self) {
|
||||||
|
self.client.event(Destroyed { self_id: self.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill(&self, send_destroyed: bool) {
|
||||||
|
if let Some(id) = self.ei_client_id {
|
||||||
|
self.client.state.ei_clients.shutdown(id);
|
||||||
|
}
|
||||||
|
if send_destroyed {
|
||||||
|
self.send_destroyed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayEiSessionRequestHandler for JayEiSession {
|
||||||
|
type Error = JayEiSessionError;
|
||||||
|
|
||||||
|
fn release(&self, _req: Release, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.kill(false);
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = JayEiSession;
|
||||||
|
version = self.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for JayEiSession {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.kill(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_obj!(JayEiSession);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum JayEiSessionError {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
}
|
||||||
|
efrom!(JayEiSessionError, ClientError);
|
||||||
97
src/ifs/jay_ei_session_builder.rs
Normal file
97
src/ifs/jay_ei_session_builder.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
ei::ei_client::EiClientError,
|
||||||
|
ifs::jay_ei_session::JayEiSession,
|
||||||
|
leaks::Tracker,
|
||||||
|
object::{Object, Version},
|
||||||
|
utils::{errorfmt::ErrorFmt, oserror::OsError},
|
||||||
|
wire::{
|
||||||
|
jay_ei_session_builder::{Commit, JayEiSessionBuilderRequestHandler, SetAppId},
|
||||||
|
JayEiSessionBuilderId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::{cell::RefCell, rc::Rc},
|
||||||
|
thiserror::Error,
|
||||||
|
uapi::c,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct JayEiSessionBuilder {
|
||||||
|
pub id: JayEiSessionBuilderId,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
pub version: Version,
|
||||||
|
pub app_id: RefCell<Option<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayEiSessionBuilderRequestHandler for JayEiSessionBuilder {
|
||||||
|
type Error = JayEiSessionBuilderError;
|
||||||
|
|
||||||
|
fn commit(&self, req: Commit, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
let app_id = self.app_id.borrow().clone();
|
||||||
|
if app_id.is_none() {
|
||||||
|
return Err(JayEiSessionBuilderError::NoAppId);
|
||||||
|
}
|
||||||
|
let res = (move || {
|
||||||
|
let con = uapi::socketpair(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0);
|
||||||
|
let (server, client) = match con {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => return Err(JayEiSessionBuilderError::SocketPair(e.into())),
|
||||||
|
};
|
||||||
|
let ei_client_id = self
|
||||||
|
.client
|
||||||
|
.state
|
||||||
|
.ei_clients
|
||||||
|
.spawn2(&self.client.state, Rc::new(server), None, app_id)
|
||||||
|
.map_err(JayEiSessionBuilderError::SpawnClient)?
|
||||||
|
.id;
|
||||||
|
Ok((ei_client_id, Rc::new(client)))
|
||||||
|
})();
|
||||||
|
let obj = Rc::new(JayEiSession {
|
||||||
|
id: req.id,
|
||||||
|
client: self.client.clone(),
|
||||||
|
ei_client_id: res.as_ref().ok().map(|v| v.0),
|
||||||
|
tracker: Default::default(),
|
||||||
|
version: self.version,
|
||||||
|
});
|
||||||
|
track!(self.client, obj);
|
||||||
|
self.client.add_client_obj(&obj)?;
|
||||||
|
match res {
|
||||||
|
Ok((_, fd)) => obj.send_created(&fd),
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("Could not spawn client: {}", ErrorFmt(e));
|
||||||
|
log::error!("{}", e);
|
||||||
|
obj.send_failed(&e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_app_id(&self, req: SetAppId<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
*self.app_id.borrow_mut() = Some(req.app_id.to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = JayEiSessionBuilder;
|
||||||
|
version = self.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for JayEiSessionBuilder {}
|
||||||
|
|
||||||
|
simple_add_obj!(JayEiSessionBuilder);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum JayEiSessionBuilderError {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
#[error("Could not create a socketpair")]
|
||||||
|
SocketPair(#[source] OsError),
|
||||||
|
#[error("Could not spawn a new client")]
|
||||||
|
SpawnClient(#[source] EiClientError),
|
||||||
|
#[error("Commit called without app-id")]
|
||||||
|
NoAppId,
|
||||||
|
}
|
||||||
|
efrom!(JayEiSessionBuilderError, ClientError);
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod ptl_display;
|
mod ptl_display;
|
||||||
|
mod ptl_remote_desktop;
|
||||||
mod ptl_render_ctx;
|
mod ptl_render_ctx;
|
||||||
mod ptl_screencast;
|
mod ptl_screencast;
|
||||||
mod ptr_gui;
|
mod ptr_gui;
|
||||||
|
|
@ -17,6 +18,7 @@ use {
|
||||||
pipewire::pw_con::{PwConHolder, PwConOwner},
|
pipewire::pw_con::{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_render_ctx::PortalRenderCtx,
|
ptl_render_ctx::PortalRenderCtx,
|
||||||
ptl_screencast::{add_screencast_dbus_members, ScreencastSession},
|
ptl_screencast::{add_screencast_dbus_members, ScreencastSession},
|
||||||
},
|
},
|
||||||
|
|
@ -195,6 +197,7 @@ async fn run_async(
|
||||||
displays: Default::default(),
|
displays: Default::default(),
|
||||||
dbus,
|
dbus,
|
||||||
screencasts: Default::default(),
|
screencasts: 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(),
|
||||||
|
|
@ -210,6 +213,7 @@ async fn run_async(
|
||||||
if let Some(pw_con) = &pw_con {
|
if let Some(pw_con) = &pw_con {
|
||||||
add_screencast_dbus_members(&state, &pw_con.con, &obj);
|
add_screencast_dbus_members(&state, &pw_con.con, &obj);
|
||||||
}
|
}
|
||||||
|
add_remote_desktop_dbus_members(&state, &obj);
|
||||||
obj
|
obj
|
||||||
};
|
};
|
||||||
watch_displays(state.clone()).await;
|
watch_displays(state.clone()).await;
|
||||||
|
|
@ -288,6 +292,7 @@ struct PortalState {
|
||||||
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
|
displays: CopyHashMap<PortalDisplayId, Rc<PortalDisplay>>,
|
||||||
dbus: Rc<DbusSocket>,
|
dbus: Rc<DbusSocket>,
|
||||||
screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
||||||
|
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>,
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ use {
|
||||||
ifs::wl_seat::POINTER,
|
ifs::wl_seat::POINTER,
|
||||||
object::Version,
|
object::Version,
|
||||||
portal::{
|
portal::{
|
||||||
ptl_render_ctx::PortalRenderCtx, ptl_screencast::ScreencastSession,
|
ptl_remote_desktop::RemoteDesktopSession, ptl_render_ctx::PortalRenderCtx,
|
||||||
ptr_gui::WindowData, PortalState,
|
ptl_screencast::ScreencastSession, ptr_gui::WindowData, PortalState,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||||
|
|
@ -74,6 +74,7 @@ pub struct PortalDisplay {
|
||||||
|
|
||||||
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
|
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
|
||||||
pub screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
pub screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
||||||
|
pub remote_desktop_sessions: CopyHashMap<String, Rc<RemoteDesktopSession>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PortalOutput {
|
pub struct PortalOutput {
|
||||||
|
|
@ -303,7 +304,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(4)),
|
version: Version(version.min(5)),
|
||||||
});
|
});
|
||||||
dpy.con.add_object(jc.clone());
|
dpy.con.add_object(jc.clone());
|
||||||
dpy.registry.request_bind(name, version, jc.deref());
|
dpy.registry.request_bind(name, version, jc.deref());
|
||||||
|
|
@ -395,6 +396,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
||||||
vp,
|
vp,
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
screencasts: Default::default(),
|
screencasts: Default::default(),
|
||||||
|
remote_desktop_sessions: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
dpy.state.displays.set(dpy.id, dpy.clone());
|
dpy.state.displays.set(dpy.id, dpy.clone());
|
||||||
|
|
|
||||||
384
src/portal/ptl_remote_desktop.rs
Normal file
384
src/portal/ptl_remote_desktop.rs
Normal file
|
|
@ -0,0 +1,384 @@
|
||||||
|
mod remote_desktop_gui;
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
dbus::{prelude::Variant, DbusObject, DictEntry, DynamicType, PendingReply, FALSE},
|
||||||
|
ifs::jay_compositor::CREATE_EI_SESSION_SINCE,
|
||||||
|
portal::{
|
||||||
|
ptl_display::{PortalDisplay, PortalDisplayId},
|
||||||
|
ptl_remote_desktop::remote_desktop_gui::SelectionGui,
|
||||||
|
PortalState, PORTAL_SUCCESS,
|
||||||
|
},
|
||||||
|
utils::{
|
||||||
|
clonecell::{CloneCell, UnsafeCellCloneSafe},
|
||||||
|
copyhashmap::CopyHashMap,
|
||||||
|
hash_map_ext::HashMapExt,
|
||||||
|
},
|
||||||
|
wire_dbus::{
|
||||||
|
org,
|
||||||
|
org::freedesktop::impl_::portal::{
|
||||||
|
remote_desktop::{
|
||||||
|
ConnectToEIS, ConnectToEISReply, CreateSession, CreateSessionReply,
|
||||||
|
SelectDevices, SelectDevicesReply, Start, StartReply,
|
||||||
|
},
|
||||||
|
session::{CloseReply as SessionCloseReply, Closed},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wl_usr::usr_ifs::usr_jay_ei_session::{UsrJayEiSession, UsrJayEiSessionOwner},
|
||||||
|
},
|
||||||
|
std::{borrow::Cow, cell::Cell, ops::Deref, rc::Rc},
|
||||||
|
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)]
|
||||||
|
pub enum RemoteDesktopPhase {
|
||||||
|
Init,
|
||||||
|
DevicesSelected,
|
||||||
|
Selecting(Rc<SelectingDisplay>),
|
||||||
|
Starting(Rc<StartingRemoteDesktop>),
|
||||||
|
Started(Rc<StartedRemoteDesktop>),
|
||||||
|
Terminated,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl UnsafeCellCloneSafe for RemoteDesktopPhase {}
|
||||||
|
|
||||||
|
pub struct SelectingDisplay {
|
||||||
|
pub session: Rc<RemoteDesktopSession>,
|
||||||
|
pub request_obj: Rc<DbusObject>,
|
||||||
|
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
||||||
|
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StartingRemoteDesktop {
|
||||||
|
pub session: Rc<RemoteDesktopSession>,
|
||||||
|
pub _request_obj: Rc<DbusObject>,
|
||||||
|
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
||||||
|
pub dpy: Rc<PortalDisplay>,
|
||||||
|
pub ei_session: Rc<UsrJayEiSession>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StartedRemoteDesktop {
|
||||||
|
session: Rc<RemoteDesktopSession>,
|
||||||
|
dpy: Rc<PortalDisplay>,
|
||||||
|
ei_session: Rc<UsrJayEiSession>,
|
||||||
|
ei_fd: Cell<Option<Rc<OwnedFd>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
DeviceTypes: u32;
|
||||||
|
|
||||||
|
KEYBOARD = 1,
|
||||||
|
POINTER = 2,
|
||||||
|
TOUCHSCREEN = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrJayEiSessionOwner for StartingRemoteDesktop {
|
||||||
|
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 {
|
||||||
|
session: self.session.clone(),
|
||||||
|
dpy: self.dpy.clone(),
|
||||||
|
ei_session: self.ei_session.clone(),
|
||||||
|
ei_fd: Cell::new(Some(fd.clone())),
|
||||||
|
});
|
||||||
|
self.session
|
||||||
|
.phase
|
||||||
|
.set(RemoteDesktopPhase::Started(started.clone()));
|
||||||
|
started.ei_session.owner.set(Some(started.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn failed(&self, reason: &str) {
|
||||||
|
log::error!("Could not create session: {}", reason);
|
||||||
|
self.reply.err(reason);
|
||||||
|
self.session.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectingDisplay {
|
||||||
|
pub fn starting(&self, dpy: &Rc<PortalDisplay>) {
|
||||||
|
let builder = dpy.jc.create_ei_session();
|
||||||
|
builder.set_app_id(&self.session.app);
|
||||||
|
let ei_session = builder.commit();
|
||||||
|
let starting = Rc::new(StartingRemoteDesktop {
|
||||||
|
session: self.session.clone(),
|
||||||
|
_request_obj: self.request_obj.clone(),
|
||||||
|
reply: self.reply.clone(),
|
||||||
|
dpy: dpy.clone(),
|
||||||
|
ei_session,
|
||||||
|
});
|
||||||
|
self.session
|
||||||
|
.phase
|
||||||
|
.set(RemoteDesktopPhase::Starting(starting.clone()));
|
||||||
|
starting.ei_session.owner.set(Some(starting.clone()));
|
||||||
|
dpy.remote_desktop_sessions.set(
|
||||||
|
self.session.session_obj.path().to_owned(),
|
||||||
|
self.session.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteDesktopSession {
|
||||||
|
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(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
_req: SelectDevices,
|
||||||
|
reply: PendingReply<SelectDevicesReply<'static>>,
|
||||||
|
) {
|
||||||
|
match self.phase.get() {
|
||||||
|
RemoteDesktopPhase::Init => {}
|
||||||
|
_ => {
|
||||||
|
self.kill();
|
||||||
|
reply.err("Devices have already been selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.phase.set(RemoteDesktopPhase::DevicesSelected);
|
||||||
|
reply.ok(&SelectDevicesReply {
|
||||||
|
response: PORTAL_SUCCESS,
|
||||||
|
results: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbus_start(self: &Rc<Self>, req: Start<'_>, reply: PendingReply<StartReply<'static>>) {
|
||||||
|
match self.phase.get() {
|
||||||
|
RemoteDesktopPhase::DevicesSelected => {}
|
||||||
|
_ => {
|
||||||
|
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) => 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let guis = CopyHashMap::new();
|
||||||
|
for dpy in self.state.displays.lock().values() {
|
||||||
|
if dpy.outputs.len() > 0 && dpy.jc.version >= CREATE_EI_SESSION_SINCE {
|
||||||
|
guis.set(dpy.id, SelectionGui::new(self, dpy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if guis.is_empty() {
|
||||||
|
self.kill();
|
||||||
|
reply.err("There are no running displays");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.phase
|
||||||
|
.set(RemoteDesktopPhase::Selecting(Rc::new(SelectingDisplay {
|
||||||
|
session: self.clone(),
|
||||||
|
request_obj: Rc::new(request_obj),
|
||||||
|
reply: Rc::new(reply),
|
||||||
|
guis,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbus_connect_to_eis(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
_req: ConnectToEIS,
|
||||||
|
reply: PendingReply<ConnectToEISReply>,
|
||||||
|
) {
|
||||||
|
let RemoteDesktopPhase::Started(started) = self.phase.get() else {
|
||||||
|
self.kill();
|
||||||
|
reply.err("Sources have already been selected");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(fd) = started.ei_fd.take() else {
|
||||||
|
self.kill();
|
||||||
|
reply.err("EI file descriptor has already been consumed");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
reply.ok(&ConnectToEISReply { fd });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrJayEiSessionOwner for StartedRemoteDesktop {
|
||||||
|
fn destroyed(&self) {
|
||||||
|
self.session.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn add_remote_desktop_dbus_members(state_: &Rc<PortalState>, object: &DbusObject) {
|
||||||
|
use org::freedesktop::impl_::portal::remote_desktop::*;
|
||||||
|
let state = state_.clone();
|
||||||
|
object.add_method::<CreateSession, _>(move |req, pr| {
|
||||||
|
dbus_create_session(&state, req, pr);
|
||||||
|
});
|
||||||
|
let state = state_.clone();
|
||||||
|
object.add_method::<SelectDevices, _>(move |req, pr| {
|
||||||
|
dbus_select_devices(&state, req, pr);
|
||||||
|
});
|
||||||
|
let state = state_.clone();
|
||||||
|
object.add_method::<Start, _>(move |req, pr| {
|
||||||
|
dbus_start(&state, req, pr);
|
||||||
|
});
|
||||||
|
let state = state_.clone();
|
||||||
|
object.add_method::<ConnectToEIS, _>(move |req, pr| {
|
||||||
|
dbus_connect_to_eis(&state, req, pr);
|
||||||
|
});
|
||||||
|
object.set_property::<AvailableDeviceTypes>(Variant::U32(DeviceTypes::all().0));
|
||||||
|
object.set_property::<version>(Variant::U32(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbus_create_session(
|
||||||
|
state: &Rc<PortalState>,
|
||||||
|
req: CreateSession,
|
||||||
|
reply: PendingReply<CreateSessionReply<'static>>,
|
||||||
|
) {
|
||||||
|
log::info!("Create remote desktop session {:#?}", req);
|
||||||
|
if state
|
||||||
|
.remote_desktop_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(RemoteDesktopSession {
|
||||||
|
_id: state.id(),
|
||||||
|
state: state.clone(),
|
||||||
|
app: req.app_id.to_string(),
|
||||||
|
session_obj: obj,
|
||||||
|
phase: CloneCell::new(RemoteDesktopPhase::Init),
|
||||||
|
});
|
||||||
|
{
|
||||||
|
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(2));
|
||||||
|
}
|
||||||
|
state
|
||||||
|
.remote_desktop_sessions
|
||||||
|
.set(req.session_handle.0.to_string(), session);
|
||||||
|
reply.ok(&CreateSessionReply {
|
||||||
|
response: PORTAL_SUCCESS,
|
||||||
|
results: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbus_select_devices(
|
||||||
|
state: &Rc<PortalState>,
|
||||||
|
req: SelectDevices,
|
||||||
|
reply: PendingReply<SelectDevicesReply<'static>>,
|
||||||
|
) {
|
||||||
|
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
|
||||||
|
s.dbus_select_devices(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(req, reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbus_connect_to_eis(
|
||||||
|
state: &Rc<PortalState>,
|
||||||
|
req: ConnectToEIS,
|
||||||
|
reply: PendingReply<ConnectToEISReply>,
|
||||||
|
) {
|
||||||
|
if let Some(s) = get_session(state, &reply, &req.session_handle.0) {
|
||||||
|
s.dbus_connect_to_eis(req, reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_session<T>(
|
||||||
|
state: &Rc<PortalState>,
|
||||||
|
reply: &PendingReply<T>,
|
||||||
|
handle: &str,
|
||||||
|
) -> Option<Rc<RemoteDesktopSession>> {
|
||||||
|
let res = state.remote_desktop_sessions.get(handle);
|
||||||
|
if res.is_none() {
|
||||||
|
let msg = format!("Remote desktop session `{}` does not exist", handle);
|
||||||
|
reply.err(&msg);
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
159
src/portal/ptl_remote_desktop/remote_desktop_gui.rs
Normal file
159
src/portal/ptl_remote_desktop/remote_desktop_gui.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
|
||||||
|
portal::{
|
||||||
|
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
||||||
|
ptl_remote_desktop::{RemoteDesktopPhase, RemoteDesktopSession},
|
||||||
|
ptr_gui::{
|
||||||
|
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
|
||||||
|
OverlayWindowOwner,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
theme::Color,
|
||||||
|
utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
const H_MARGIN: f32 = 30.0;
|
||||||
|
const V_MARGIN: f32 = 20.0;
|
||||||
|
|
||||||
|
pub struct SelectionGui {
|
||||||
|
remote_desktop_session: Rc<RemoteDesktopSession>,
|
||||||
|
dpy: Rc<PortalDisplay>,
|
||||||
|
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelectionGuiSurface {
|
||||||
|
gui: Rc<SelectionGui>,
|
||||||
|
output: Rc<PortalOutput>,
|
||||||
|
overlay: Rc<OverlayWindow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StaticButton {
|
||||||
|
surface: Rc<SelectionGuiSurface>,
|
||||||
|
role: ButtonRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum ButtonRole {
|
||||||
|
Accept,
|
||||||
|
Reject,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionGui {
|
||||||
|
pub fn kill(&self, upwards: bool) {
|
||||||
|
for surface in self.surfaces.lock().drain_values() {
|
||||||
|
surface.overlay.data.kill(false);
|
||||||
|
}
|
||||||
|
if let RemoteDesktopPhase::Selecting(s) = self.remote_desktop_session.phase.get() {
|
||||||
|
s.guis.remove(&self.dpy.id);
|
||||||
|
if upwards && s.guis.is_empty() {
|
||||||
|
self.remote_desktop_session.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
||||||
|
let app = &surface.gui.remote_desktop_session.app;
|
||||||
|
let text = if app.is_empty() {
|
||||||
|
format!("An application wants to generate/monitor input")
|
||||||
|
} else {
|
||||||
|
format!("`{}` wants to generate/monitor input", app)
|
||||||
|
};
|
||||||
|
let label = Rc::new(Label::default());
|
||||||
|
*label.text.borrow_mut() = text;
|
||||||
|
let accept_button = static_button(surface, ButtonRole::Accept, "Allow");
|
||||||
|
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
||||||
|
for button in [&accept_button, &reject_button] {
|
||||||
|
button.border_color.set(Color::from_gray(100));
|
||||||
|
button.border.set(2.0);
|
||||||
|
button.padding.set(5.0);
|
||||||
|
}
|
||||||
|
accept_button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||||
|
accept_button
|
||||||
|
.bg_hover_color
|
||||||
|
.set(Color::from_rgb(170, 255, 170));
|
||||||
|
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
|
||||||
|
reject_button
|
||||||
|
.bg_hover_color
|
||||||
|
.set(Color::from_rgb(255, 170, 170));
|
||||||
|
let flow = Rc::new(Flow::default());
|
||||||
|
flow.orientation.set(Orientation::Vertical);
|
||||||
|
flow.cross_align.set(Align::Center);
|
||||||
|
flow.in_margin.set(V_MARGIN);
|
||||||
|
flow.cross_margin.set(H_MARGIN);
|
||||||
|
*flow.elements.borrow_mut() = vec![label, accept_button, reject_button];
|
||||||
|
flow
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayWindowOwner for SelectionGuiSurface {
|
||||||
|
fn kill(&self, upwards: bool) {
|
||||||
|
self.gui.dpy.windows.remove(&self.overlay.data.surface.id);
|
||||||
|
self.gui.surfaces.remove(&self.output.global_id);
|
||||||
|
if upwards && self.gui.surfaces.is_empty() {
|
||||||
|
self.gui.kill(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionGui {
|
||||||
|
pub fn new(ss: &Rc<RemoteDesktopSession>, dpy: &Rc<PortalDisplay>) -> Rc<Self> {
|
||||||
|
let gui = Rc::new(SelectionGui {
|
||||||
|
remote_desktop_session: ss.clone(),
|
||||||
|
dpy: dpy.clone(),
|
||||||
|
surfaces: Default::default(),
|
||||||
|
});
|
||||||
|
for output in dpy.outputs.lock().values() {
|
||||||
|
let sgs = Rc::new(SelectionGuiSurface {
|
||||||
|
gui: gui.clone(),
|
||||||
|
output: output.clone(),
|
||||||
|
overlay: OverlayWindow::new(output),
|
||||||
|
});
|
||||||
|
let element = create_accept_gui(&sgs);
|
||||||
|
sgs.overlay.data.content.set(Some(element));
|
||||||
|
gui.dpy
|
||||||
|
.windows
|
||||||
|
.set(sgs.overlay.data.surface.id, sgs.overlay.data.clone());
|
||||||
|
gui.surfaces.set(output.global_id, sgs);
|
||||||
|
}
|
||||||
|
gui
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonOwner for StaticButton {
|
||||||
|
fn button(&self, _seat: &PortalSeat, button: u32, state: u32) {
|
||||||
|
if button != BTN_LEFT || state != PRESSED {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match self.role {
|
||||||
|
ButtonRole::Accept => {
|
||||||
|
log::info!("User has accepted the request");
|
||||||
|
let selecting = match self.surface.gui.remote_desktop_session.phase.get() {
|
||||||
|
RemoteDesktopPhase::Selecting(selecting) => selecting,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
for gui in selecting.guis.lock().drain_values() {
|
||||||
|
gui.kill(false);
|
||||||
|
}
|
||||||
|
selecting.starting(&self.surface.output.dpy);
|
||||||
|
}
|
||||||
|
ButtonRole::Reject => {
|
||||||
|
log::info!("User has rejected the remote desktop request");
|
||||||
|
self.surface.gui.remote_desktop_session.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn static_button(surface: &Rc<SelectionGuiSurface>, role: ButtonRole, text: &str) -> Rc<Button> {
|
||||||
|
let button = Rc::new(Button::default());
|
||||||
|
let slf = Rc::new(StaticButton {
|
||||||
|
surface: surface.clone(),
|
||||||
|
role,
|
||||||
|
});
|
||||||
|
button.owner.set(Some(slf));
|
||||||
|
*button.text.borrow_mut() = text.to_string();
|
||||||
|
button
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PidInfo {
|
pub struct PidInfo {
|
||||||
pub _uid: c::uid_t,
|
pub uid: c::uid_t,
|
||||||
pub pid: c::pid_t,
|
pub pid: c::pid_t,
|
||||||
pub comm: String,
|
pub comm: String,
|
||||||
}
|
}
|
||||||
|
|
@ -18,11 +18,7 @@ pub fn get_pid_info(uid: c::uid_t, pid: c::pid_t) -> PidInfo {
|
||||||
"Unknown".to_string()
|
"Unknown".to_string()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
PidInfo {
|
PidInfo { uid, pid, comm }
|
||||||
_uid: uid,
|
|
||||||
pid,
|
|
||||||
comm,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_socket_creds(socket: &OwnedFd) -> Option<(c::uid_t, c::pid_t)> {
|
pub fn get_socket_creds(socket: &OwnedFd) -> Option<(c::uid_t, c::pid_t)> {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod usr_jay_compositor;
|
pub mod usr_jay_compositor;
|
||||||
|
pub mod usr_jay_ei_session;
|
||||||
|
pub mod usr_jay_ei_session_builder;
|
||||||
pub mod usr_jay_output;
|
pub mod usr_jay_output;
|
||||||
pub mod usr_jay_pointer;
|
pub mod usr_jay_pointer;
|
||||||
pub mod usr_jay_render_ctx;
|
pub mod usr_jay_render_ctx;
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ use {
|
||||||
wire::{jay_compositor::*, JayCompositorId},
|
wire::{jay_compositor::*, JayCompositorId},
|
||||||
wl_usr::{
|
wl_usr::{
|
||||||
usr_ifs::{
|
usr_ifs::{
|
||||||
usr_jay_output::UsrJayOutput, usr_jay_pointer::UsrJayPointer,
|
usr_jay_ei_session_builder::UsrJayEiSessionBuilder, usr_jay_output::UsrJayOutput,
|
||||||
usr_jay_render_ctx::UsrJayRenderCtx, usr_jay_screencast::UsrJayScreencast,
|
usr_jay_pointer::UsrJayPointer, usr_jay_render_ctx::UsrJayRenderCtx,
|
||||||
|
usr_jay_screencast::UsrJayScreencast,
|
||||||
usr_jay_select_toplevel::UsrJaySelectToplevel,
|
usr_jay_select_toplevel::UsrJaySelectToplevel,
|
||||||
usr_jay_select_workspace::UsrJaySelectWorkspace,
|
usr_jay_select_workspace::UsrJaySelectWorkspace,
|
||||||
usr_jay_workspace_watcher::UsrJayWorkspaceWatcher, usr_wl_output::UsrWlOutput,
|
usr_jay_workspace_watcher::UsrJayWorkspaceWatcher, usr_wl_output::UsrWlOutput,
|
||||||
|
|
@ -156,6 +157,20 @@ impl UsrJayCompositor {
|
||||||
self.con.add_object(sc.clone());
|
self.con.add_object(sc.clone());
|
||||||
sc
|
sc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_ei_session(&self) -> Rc<UsrJayEiSessionBuilder> {
|
||||||
|
let obj = Rc::new(UsrJayEiSessionBuilder {
|
||||||
|
id: self.con.id(),
|
||||||
|
con: self.con.clone(),
|
||||||
|
version: self.version,
|
||||||
|
});
|
||||||
|
self.con.request(CreateEiSession {
|
||||||
|
self_id: self.id,
|
||||||
|
id: obj.id,
|
||||||
|
});
|
||||||
|
self.con.add_object(obj.clone());
|
||||||
|
obj
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JayCompositorEventHandler for UsrJayCompositor {
|
impl JayCompositorEventHandler for UsrJayCompositor {
|
||||||
|
|
|
||||||
72
src/wl_usr/usr_ifs/usr_jay_ei_session.rs
Normal file
72
src/wl_usr/usr_ifs/usr_jay_ei_session.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
object::Version,
|
||||||
|
utils::clonecell::CloneCell,
|
||||||
|
wire::{
|
||||||
|
jay_ei_session::{Created, Destroyed, Failed, JayEiSessionEventHandler, Release},
|
||||||
|
JayEiSessionId,
|
||||||
|
},
|
||||||
|
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||||
|
},
|
||||||
|
std::{convert::Infallible, rc::Rc},
|
||||||
|
uapi::OwnedFd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UsrJayEiSession {
|
||||||
|
pub id: JayEiSessionId,
|
||||||
|
pub con: Rc<UsrCon>,
|
||||||
|
pub owner: CloneCell<Option<Rc<dyn UsrJayEiSessionOwner>>>,
|
||||||
|
pub version: Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UsrJayEiSessionOwner {
|
||||||
|
fn destroyed(&self) {}
|
||||||
|
|
||||||
|
fn created(&self, fd: &Rc<OwnedFd>) {
|
||||||
|
let _ = fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn failed(&self, reason: &str) {
|
||||||
|
let _ = reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayEiSessionEventHandler for UsrJayEiSession {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn destroyed(&self, _ev: Destroyed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
if let Some(owner) = self.owner.get() {
|
||||||
|
owner.destroyed();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn created(&self, ev: Created, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
if let Some(owner) = self.owner.get() {
|
||||||
|
owner.created(&ev.fd);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn failed(&self, ev: Failed<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
if let Some(owner) = self.owner.get() {
|
||||||
|
owner.failed(ev.reason);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usr_object_base! {
|
||||||
|
self = UsrJayEiSession = JayEiSession;
|
||||||
|
version = self.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrObject for UsrJayEiSession {
|
||||||
|
fn destroy(&self) {
|
||||||
|
self.con.request(Release { self_id: self.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.owner.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/wl_usr/usr_ifs/usr_jay_ei_session_builder.rs
Normal file
57
src/wl_usr/usr_ifs/usr_jay_ei_session_builder.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
object::Version,
|
||||||
|
wire::{
|
||||||
|
jay_ei_session_builder::{Commit, JayEiSessionBuilderEventHandler, SetAppId},
|
||||||
|
JayEiSessionBuilderId,
|
||||||
|
},
|
||||||
|
wl_usr::{usr_ifs::usr_jay_ei_session::UsrJayEiSession, usr_object::UsrObject, UsrCon},
|
||||||
|
},
|
||||||
|
std::{convert::Infallible, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UsrJayEiSessionBuilder {
|
||||||
|
pub id: JayEiSessionBuilderId,
|
||||||
|
pub con: Rc<UsrCon>,
|
||||||
|
pub version: Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrJayEiSessionBuilder {
|
||||||
|
pub fn set_app_id(&self, app_id: &str) {
|
||||||
|
self.con.request(SetAppId {
|
||||||
|
self_id: self.id,
|
||||||
|
app_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(&self) -> Rc<UsrJayEiSession> {
|
||||||
|
let obj = Rc::new(UsrJayEiSession {
|
||||||
|
id: self.con.id(),
|
||||||
|
con: self.con.clone(),
|
||||||
|
owner: Default::default(),
|
||||||
|
version: self.version,
|
||||||
|
});
|
||||||
|
self.con.add_object(obj.clone());
|
||||||
|
self.con.request(Commit {
|
||||||
|
self_id: self.id,
|
||||||
|
id: obj.id,
|
||||||
|
});
|
||||||
|
self.con.remove_obj(self);
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayEiSessionBuilderEventHandler for UsrJayEiSessionBuilder {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
usr_object_base! {
|
||||||
|
self = UsrJayEiSessionBuilder = JayEiSessionBuilder;
|
||||||
|
version = self.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrObject for UsrJayEiSessionBuilder {
|
||||||
|
fn destroy(&self) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
41
wire-dbus/org.freedesktop.impl.portal.RemoteDesktop.txt
Normal file
41
wire-dbus/org.freedesktop.impl.portal.RemoteDesktop.txt
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
fn CreateSession(
|
||||||
|
handle: object_path,
|
||||||
|
session_handle: object_path,
|
||||||
|
app_id: string,
|
||||||
|
options: array(dict(string, variant)),
|
||||||
|
) {
|
||||||
|
response: u32,
|
||||||
|
results: array(dict(string, variant)),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn SelectDevices(
|
||||||
|
handle: object_path,
|
||||||
|
session_handle: object_path,
|
||||||
|
app_id: string,
|
||||||
|
options: array(dict(string, variant)),
|
||||||
|
) {
|
||||||
|
response: u32,
|
||||||
|
results: array(dict(string, variant)),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Start(
|
||||||
|
handle: object_path,
|
||||||
|
session_handle: object_path,
|
||||||
|
app_id: string,
|
||||||
|
parent_window: string,
|
||||||
|
options: array(dict(string, variant)),
|
||||||
|
) {
|
||||||
|
response: u32,
|
||||||
|
results: array(dict(string, variant)),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ConnectToEIS(
|
||||||
|
session_handle: object_path,
|
||||||
|
app_id: string,
|
||||||
|
options: array(dict(string, variant)),
|
||||||
|
) {
|
||||||
|
fd: fd,
|
||||||
|
}
|
||||||
|
|
||||||
|
prop AvailableDeviceTypes = u32
|
||||||
|
prop version = u32
|
||||||
|
|
@ -88,6 +88,10 @@ request select_workspace {
|
||||||
seat: id(wl_seat),
|
seat: id(wl_seat),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request create_ei_session (since = 5) {
|
||||||
|
id: id(jay_ei_session_builder),
|
||||||
|
}
|
||||||
|
|
||||||
# events
|
# events
|
||||||
|
|
||||||
event client_id {
|
event client_id {
|
||||||
|
|
|
||||||
15
wire/jay_ei_session.txt
Normal file
15
wire/jay_ei_session.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
request release (since = 5) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
event destroyed (since = 5) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
event created (since = 5) {
|
||||||
|
fd: fd,
|
||||||
|
}
|
||||||
|
|
||||||
|
event failed (since = 5) {
|
||||||
|
reason: str,
|
||||||
|
}
|
||||||
7
wire/jay_ei_session_builder.txt
Normal file
7
wire/jay_ei_session_builder.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
request commit (since = 5) {
|
||||||
|
id: id(jay_ei_session),
|
||||||
|
}
|
||||||
|
|
||||||
|
request set_app_id (since = 5) {
|
||||||
|
app_id: str,
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue