portal: implement RemoteDesktop portal
This commit is contained in:
parent
40e87f8f91
commit
665127e6c0
22 changed files with 994 additions and 35 deletions
|
|
@ -4,8 +4,8 @@ use {
|
|||
ifs::wl_seat::POINTER,
|
||||
object::Version,
|
||||
portal::{
|
||||
ptl_render_ctx::PortalRenderCtx, ptl_screencast::ScreencastSession,
|
||||
ptr_gui::WindowData, PortalState,
|
||||
ptl_remote_desktop::RemoteDesktopSession, ptl_render_ctx::PortalRenderCtx,
|
||||
ptl_screencast::ScreencastSession, ptr_gui::WindowData, PortalState,
|
||||
},
|
||||
utils::{
|
||||
bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||
|
|
@ -74,6 +74,7 @@ pub struct PortalDisplay {
|
|||
|
||||
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
|
||||
pub screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
|
||||
pub remote_desktop_sessions: CopyHashMap<String, Rc<RemoteDesktopSession>>,
|
||||
}
|
||||
|
||||
pub struct PortalOutput {
|
||||
|
|
@ -303,7 +304,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
|||
con: dpy.con.clone(),
|
||||
owner: Default::default(),
|
||||
caps: Default::default(),
|
||||
version: Version(version.min(4)),
|
||||
version: Version(version.min(5)),
|
||||
});
|
||||
dpy.con.add_object(jc.clone());
|
||||
dpy.registry.request_bind(name, version, jc.deref());
|
||||
|
|
@ -395,6 +396,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
|||
vp,
|
||||
windows: Default::default(),
|
||||
screencasts: Default::default(),
|
||||
remote_desktop_sessions: Default::default(),
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue