use std::{
collections::HashMap,
fmt::Debug,
os::fd::{AsFd, OwnedFd},
path::Path,
};
use enumflags2::{BitFlags, bitflags};
use futures_util::Stream;
use serde::Serialize;
use serde_repr::{Deserialize_repr, Serialize_repr};
use zbus::zvariant::{
self, Fd, OwnedObjectPath, Type,
as_value::{self, optional},
};
use crate::{Error, FilePath, Pid, flatpak::update_monitor::UpdateMonitor, proxy::Proxy};
#[bitflags]
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)]
#[repr(u32)]
pub enum SandboxFlags {
DisplayAccess,
SoundAccess,
GpuAccess,
SessionBusAccess,
AccessibilityBusAccess,
}
#[bitflags]
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)]
#[repr(u32)]
#[doc(alias = "XdpSpawnFlags")]
pub enum SpawnFlags {
#[doc(alias = "XDP_SPAWN_FLAG_CLEARENV")]
ClearEnv,
#[doc(alias = "XDP_SPAWN_FLAG_LATEST")]
LatestVersion,
#[doc(alias = "XDP_SPAWN_FLAG_SANDBOX")]
Sandbox,
#[doc(alias = "XDP_SPAWN_FLAG_NO_NETWORK")]
NoNetwork,
#[doc(alias = "XDP_SPAWN_FLAG_WATCH")]
WatchBus,
ExposePids,
NotifyStart,
SharePids,
EmptyApp,
}
#[bitflags]
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)]
#[repr(u32)]
pub enum SupportsFlags {
ExposePids,
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
#[serde(rename_all = "kebab-case")]
pub struct SpawnOptions {
#[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
sandbox_expose: Vec<String>,
#[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
sandbox_expose_ro: Vec<String>,
#[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
sandbox_expose_fd: Vec<zvariant::OwnedFd>,
#[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
sandbox_expose_fd_ro: Vec<zvariant::OwnedFd>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
sandbox_flags: Option<BitFlags<SandboxFlags>>,
#[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
unset_env: Vec<String>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
usr_fd: Option<zvariant::OwnedFd>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
app_fd: Option<zvariant::OwnedFd>,
}
impl SpawnOptions {
#[must_use]
pub fn sandbox_expose<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
mut self,
sandbox_expose: impl Into<Option<P>>,
) -> Self {
self.sandbox_expose = sandbox_expose
.into()
.map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect())
.unwrap_or_default();
self
}
#[must_use]
pub fn sandbox_expose_ro<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
mut self,
sandbox_expose_ro: impl Into<Option<P>>,
) -> Self {
self.sandbox_expose_ro = sandbox_expose_ro
.into()
.map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect())
.unwrap_or_default();
self
}
#[must_use]
pub fn sandbox_expose_fd<P: IntoIterator<Item = OwnedFd>>(
mut self,
sandbox_expose_fd: impl Into<Option<P>>,
) -> Self {
self.sandbox_expose_fd = sandbox_expose_fd
.into()
.map(|a| a.into_iter().map(zvariant::OwnedFd::from).collect())
.unwrap_or_default();
self
}
#[must_use]
pub fn sandbox_expose_fd_ro<P: IntoIterator<Item = OwnedFd>>(
mut self,
sandbox_expose_fd_ro: impl Into<Option<P>>,
) -> Self {
self.sandbox_expose_fd_ro = sandbox_expose_fd_ro
.into()
.map(|a| a.into_iter().map(zvariant::OwnedFd::from).collect())
.unwrap_or_default();
self
}
#[must_use]
pub fn sandbox_flags(
mut self,
sandbox_flags: impl Into<Option<BitFlags<SandboxFlags>>>,
) -> Self {
self.sandbox_flags = sandbox_flags.into();
self
}
#[must_use]
pub fn unset_env<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
mut self,
env: impl Into<Option<P>>,
) -> Self {
self.unset_env = env
.into()
.map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect())
.unwrap_or_default();
self
}
#[must_use]
pub fn usr_fd(mut self, fd: impl Into<Option<OwnedFd>>) -> Self {
self.usr_fd = fd.into().map(|f| f.into());
self
}
#[must_use]
pub fn app_fd(mut self, fd: impl Into<Option<OwnedFd>>) -> Self {
self.app_fd = fd.into().map(|f| f.into());
self
}
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct CreateUpdateMonitorOptions {}
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.Flatpak")]
pub struct Flatpak(Proxy<'static>);
impl Flatpak {
pub async fn new() -> Result<Self, Error> {
let proxy = Proxy::new_flatpak("org.freedesktop.portal.Flatpak").await?;
Ok(Self(proxy))
}
pub async fn with_connection(connection: zbus::Connection) -> Result<Self, Error> {
let proxy =
Proxy::new_flatpak_with_connection(connection, "org.freedesktop.portal.Flatpak")
.await?;
Ok(Self(proxy))
}
pub fn version(&self) -> u32 {
self.0.version()
}
#[doc(alias = "CreateUpdateMonitor")]
#[doc(alias = "xdp_portal_update_monitor_start")]
pub async fn create_update_monitor(
&self,
options: CreateUpdateMonitorOptions,
) -> Result<UpdateMonitor, Error> {
let path = self
.0
.call_versioned::<OwnedObjectPath>("CreateUpdateMonitor", &(options), 2)
.await?;
UpdateMonitor::with_connection(self.connection().clone(), path.into_inner()).await
}
#[doc(alias = "SpawnStarted")]
pub async fn receive_spawn_started(&self) -> Result<impl Stream<Item = (u32, u32)>, Error> {
self.0.signal("SpawnStarted").await
}
#[doc(alias = "SpawnExited")]
#[doc(alias = "XdpPortal::spawn-exited")]
pub async fn receive_spawn_exited(&self) -> Result<impl Stream<Item = (u32, u32)>, Error> {
self.0.signal("SpawnExited").await
}
#[doc(alias = "Spawn")]
#[doc(alias = "xdp_portal_spawn")]
pub async fn spawn(
&self,
cwd_path: impl AsRef<Path>,
argv: &[impl AsRef<Path>],
fds: HashMap<u32, impl AsFd>,
envs: HashMap<&str, &str>,
flags: BitFlags<SpawnFlags>,
options: SpawnOptions,
) -> Result<u32, Error> {
let cwd_path = FilePath::new(cwd_path)?;
let argv = argv
.iter()
.map(FilePath::new)
.collect::<Result<Vec<FilePath>, _>>()?;
let fds: HashMap<u32, Fd> = fds.iter().map(|(k, val)| (*k, Fd::from(val))).collect();
self.0
.call("Spawn", &(cwd_path, argv, fds, envs, flags, options))
.await
}
#[doc(alias = "SpawnSignal")]
#[doc(alias = "xdp_portal_spawn_signal")]
pub async fn spawn_signal(
&self,
pid: Pid,
signal: u32,
to_process_group: bool,
) -> Result<(), Error> {
self.0
.call("SpawnSignal", &(pid, signal, to_process_group))
.await
}
#[doc(alias = "supports")]
pub async fn supported_features(&self) -> Result<BitFlags<SupportsFlags>, Error> {
self.0
.property_versioned::<BitFlags<SupportsFlags>>("supports", 3)
.await
}
}
impl std::ops::Deref for Flatpak {
type Target = zbus::Proxy<'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub mod update_monitor;
mod development;
pub use development::{Development, HostCommandFlags};