Skip to main content

ashpd/flatpak/
mod.rs

1//! # Examples
2//!
3//! Spawn a process inside of the sandbox, only works in a Flatpak.
4//!
5//! ```rust,no_run
6//! use std::{collections::HashMap, os::fd::BorrowedFd};
7//!
8//! use ashpd::flatpak::{Flatpak, SpawnFlags, SpawnOptions};
9//!
10//! async fn run() -> ashpd::Result<()> {
11//!     let proxy = Flatpak::new().await?;
12//!     let fds: HashMap<_, BorrowedFd<'_>> = HashMap::new();
13//!
14//!     proxy
15//!         .spawn(
16//!             "/",
17//!             &["contrast"],
18//!             fds,
19//!             HashMap::new(),
20//!             SpawnFlags::ClearEnv | SpawnFlags::NoNetwork,
21//!             SpawnOptions::default(),
22//!         )
23//!         .await?;
24//!
25//!     Ok(())
26//! }
27//! ```
28
29use std::{
30    collections::HashMap,
31    fmt::Debug,
32    os::fd::{AsFd, OwnedFd},
33    path::Path,
34};
35
36use enumflags2::{BitFlags, bitflags};
37use futures_util::Stream;
38use serde::Serialize;
39use serde_repr::{Deserialize_repr, Serialize_repr};
40use zbus::zvariant::{
41    self, Fd, OwnedObjectPath, Type,
42    as_value::{self, optional},
43};
44
45use crate::{Error, FilePath, Pid, flatpak::update_monitor::UpdateMonitor, proxy::Proxy};
46
47#[bitflags]
48#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)]
49#[repr(u32)]
50/// A bitmask representing the "permissions" of a newly created sandbox.
51pub enum SandboxFlags {
52    /// Share the display access (X11, Wayland) with the caller.
53    DisplayAccess,
54    /// Share the sound access (PulseAudio) with the caller.
55    SoundAccess,
56    /// Share the gpu access with the caller.
57    GpuAccess,
58    /// Allow sandbox access to (filtered) session bus.
59    SessionBusAccess,
60    /// Allow sandbox access to accessibility bus.
61    AccessibilityBusAccess,
62}
63
64#[bitflags]
65#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)]
66#[repr(u32)]
67#[doc(alias = "XdpSpawnFlags")]
68/// Flags affecting the created sandbox.
69pub enum SpawnFlags {
70    #[doc(alias = "XDP_SPAWN_FLAG_CLEARENV")]
71    /// Clear the environment.
72    ClearEnv,
73    #[doc(alias = "XDP_SPAWN_FLAG_LATEST")]
74    /// Spawn the latest version of the app.
75    LatestVersion,
76    #[doc(alias = "XDP_SPAWN_FLAG_SANDBOX")]
77    /// Spawn in a sandbox (equivalent of the sandbox option of `flatpak run`).
78    Sandbox,
79    #[doc(alias = "XDP_SPAWN_FLAG_NO_NETWORK")]
80    /// Spawn without network (equivalent of the `unshare=network` option of
81    /// `flatpak run`).
82    NoNetwork,
83    #[doc(alias = "XDP_SPAWN_FLAG_WATCH")]
84    /// Kill the sandbox when the caller disappears from the session bus.
85    WatchBus,
86    /// Expose the sandbox pids in the callers sandbox, only supported if using
87    /// user namespaces for containers (not setuid), see the support property.
88    ExposePids,
89    /// Emit a SpawnStarted signal once the sandboxed process has been fully
90    /// started.
91    NotifyStart,
92    /// Expose the sandbox process IDs in the caller's sandbox and the caller's
93    /// process IDs in the new sandbox.
94    SharePids,
95    /// Don't provide app files at `/app` in the new sandbox.
96    EmptyApp,
97}
98
99#[bitflags]
100#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)]
101#[repr(u32)]
102/// Flags marking what optional features are available.
103pub enum SupportsFlags {
104    /// Supports the expose sandbox pids flag of Spawn.
105    ExposePids,
106}
107
108#[derive(Serialize, Type, Debug, Default)]
109/// Specified options for a [`Flatpak::spawn`] request.
110#[zvariant(signature = "dict")]
111#[serde(rename_all = "kebab-case")]
112pub struct SpawnOptions {
113    /// A list of filenames for files inside the sandbox that will be exposed to
114    /// the new sandbox, for reading and writing.
115    #[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
116    sandbox_expose: Vec<String>,
117    /// A list of filenames for files inside the sandbox that will be exposed to
118    /// the new sandbox, read-only.
119    #[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
120    sandbox_expose_ro: Vec<String>,
121    /// A list of file descriptor for files inside the sandbox that will be
122    /// exposed to the new sandbox, for reading and writing.
123    #[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
124    sandbox_expose_fd: Vec<zvariant::OwnedFd>,
125    /// A list of file descriptor for files inside the sandbox that will be
126    /// exposed to the new sandbox, read-only.
127    #[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
128    sandbox_expose_fd_ro: Vec<zvariant::OwnedFd>,
129    /// Flags affecting the created sandbox.
130    #[serde(with = "optional", skip_serializing_if = "Option::is_none")]
131    sandbox_flags: Option<BitFlags<SandboxFlags>>,
132    /// A list of environment variables to remove.
133    #[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")]
134    unset_env: Vec<String>,
135    /// A file descriptor of the directory that  will be used as `/usr` in the
136    /// new sandbox.
137    #[serde(with = "optional", skip_serializing_if = "Option::is_none")]
138    usr_fd: Option<zvariant::OwnedFd>,
139    /// A file descriptor of the directory that  will be used as `/app` in the
140    /// new sandbox.
141    #[serde(with = "optional", skip_serializing_if = "Option::is_none")]
142    app_fd: Option<zvariant::OwnedFd>,
143}
144
145impl SpawnOptions {
146    /// Sets the list of filenames for files to expose the new sandbox.
147    /// **Note** absolute paths or subdirectories are not allowed.
148    #[must_use]
149    pub fn sandbox_expose<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
150        mut self,
151        sandbox_expose: impl Into<Option<P>>,
152    ) -> Self {
153        self.sandbox_expose = sandbox_expose
154            .into()
155            .map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect())
156            .unwrap_or_default();
157        self
158    }
159
160    /// Sets the list of filenames for files to expose the new sandbox,
161    /// read-only.
162    /// **Note** absolute paths or subdirectories are not allowed.
163    #[must_use]
164    pub fn sandbox_expose_ro<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
165        mut self,
166        sandbox_expose_ro: impl Into<Option<P>>,
167    ) -> Self {
168        self.sandbox_expose_ro = sandbox_expose_ro
169            .into()
170            .map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect())
171            .unwrap_or_default();
172        self
173    }
174
175    /// Sets the list of file descriptors of files to expose the new sandbox.
176    #[must_use]
177    pub fn sandbox_expose_fd<P: IntoIterator<Item = OwnedFd>>(
178        mut self,
179        sandbox_expose_fd: impl Into<Option<P>>,
180    ) -> Self {
181        self.sandbox_expose_fd = sandbox_expose_fd
182            .into()
183            .map(|a| a.into_iter().map(zvariant::OwnedFd::from).collect())
184            .unwrap_or_default();
185        self
186    }
187
188    /// Sets the list of file descriptors of files to expose the new sandbox,
189    /// read-only.
190    #[must_use]
191    pub fn sandbox_expose_fd_ro<P: IntoIterator<Item = OwnedFd>>(
192        mut self,
193        sandbox_expose_fd_ro: impl Into<Option<P>>,
194    ) -> Self {
195        self.sandbox_expose_fd_ro = sandbox_expose_fd_ro
196            .into()
197            .map(|a| a.into_iter().map(zvariant::OwnedFd::from).collect())
198            .unwrap_or_default();
199        self
200    }
201
202    /// Sets the created sandbox flags.
203    #[must_use]
204    pub fn sandbox_flags(
205        mut self,
206        sandbox_flags: impl Into<Option<BitFlags<SandboxFlags>>>,
207    ) -> Self {
208        self.sandbox_flags = sandbox_flags.into();
209        self
210    }
211
212    /// Env variables to unset.
213    #[must_use]
214    pub fn unset_env<P: IntoIterator<Item = I>, I: AsRef<str> + Type + Serialize>(
215        mut self,
216        env: impl Into<Option<P>>,
217    ) -> Self {
218        self.unset_env = env
219            .into()
220            .map(|a| a.into_iter().map(|s| s.as_ref().to_owned()).collect())
221            .unwrap_or_default();
222        self
223    }
224
225    /// Set a file descriptor of the directory that  will be used as `/usr` in
226    /// the new sandbox.
227    #[must_use]
228    pub fn usr_fd(mut self, fd: impl Into<Option<OwnedFd>>) -> Self {
229        self.usr_fd = fd.into().map(|f| f.into());
230        self
231    }
232
233    /// Set a file descriptor of the directory that  will be used as `/app` in
234    /// the new sandbox.
235    #[must_use]
236    pub fn app_fd(mut self, fd: impl Into<Option<OwnedFd>>) -> Self {
237        self.app_fd = fd.into().map(|f| f.into());
238        self
239    }
240}
241
242#[derive(Serialize, Type, Debug, Default)]
243/// Specified options for a [`Flatpak::create_update_monitor`] request.
244#[zvariant(signature = "dict")]
245pub struct CreateUpdateMonitorOptions {}
246
247/// The interface exposes some interactions with Flatpak on the host to the
248/// sandbox. For example, it allows you to restart the applications or start a
249/// more sandboxed instance.
250///
251/// Wrapper of the DBus interface: [`org.freedesktop.portal.Flatpak`](https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-org.freedesktop.portal.Flatpak).
252#[derive(Debug)]
253#[doc(alias = "org.freedesktop.portal.Flatpak")]
254pub struct Flatpak(Proxy<'static>);
255
256impl Flatpak {
257    /// Create a new instance of [`Flatpak`].
258    pub async fn new() -> Result<Self, Error> {
259        let proxy = Proxy::new_flatpak("org.freedesktop.portal.Flatpak").await?;
260        Ok(Self(proxy))
261    }
262
263    /// Create a new instance of [`Flatpak`].
264    pub async fn with_connection(connection: zbus::Connection) -> Result<Self, Error> {
265        let proxy =
266            Proxy::new_flatpak_with_connection(connection, "org.freedesktop.portal.Flatpak")
267                .await?;
268        Ok(Self(proxy))
269    }
270
271    /// Returns the version of the portal interface.
272    pub fn version(&self) -> u32 {
273        self.0.version()
274    }
275
276    /// Creates an update monitor object that will emit signals
277    /// when an update for the caller becomes available, and can be used to
278    /// install it.
279    ///
280    /// # Required version
281    ///
282    /// The method requires the 2nd version implementation of the portal and
283    /// would fail with [`Error::RequiresVersion`] otherwise.
284    ///
285    /// # Specifications
286    ///
287    /// See also [`CreateUpdateMonitor`](https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-method-org-freedesktop-portal-Flatpak.CreateUpdateMonitor).
288    #[doc(alias = "CreateUpdateMonitor")]
289    #[doc(alias = "xdp_portal_update_monitor_start")]
290    pub async fn create_update_monitor(
291        &self,
292        options: CreateUpdateMonitorOptions,
293    ) -> Result<UpdateMonitor, Error> {
294        let path = self
295            .0
296            .call_versioned::<OwnedObjectPath>("CreateUpdateMonitor", &(options), 2)
297            .await?;
298
299        UpdateMonitor::with_connection(self.connection().clone(), path.into_inner()).await
300    }
301
302    /// Emitted when a process starts by [`spawn()`][`Flatpak::spawn`].
303    ///
304    /// # Specifications
305    ///
306    /// See also [`SpawnStarted`](https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-signal-org-freedesktop-portal-Flatpak.SpawnStarted).
307    #[doc(alias = "SpawnStarted")]
308    pub async fn receive_spawn_started(&self) -> Result<impl Stream<Item = (u32, u32)>, Error> {
309        self.0.signal("SpawnStarted").await
310    }
311
312    /// Emitted when a process started by [`spawn()`][`Flatpak::spawn`]
313    /// exits.
314    ///
315    /// # Specifications
316    ///
317    /// See also [`SpawnExited`](https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-signal-org-freedesktop-portal-Flatpak.SpawnExited).
318    #[doc(alias = "SpawnExited")]
319    #[doc(alias = "XdpPortal::spawn-exited")]
320    pub async fn receive_spawn_exited(&self) -> Result<impl Stream<Item = (u32, u32)>, Error> {
321        self.0.signal("SpawnExited").await
322    }
323
324    /// This methods let you start a new instance of your application,
325    /// optionally enabling a tighter sandbox.
326    ///
327    /// # Arguments
328    ///
329    /// * `cwd_path` - The working directory for the new process.
330    /// * `argv` - The argv for the new process, starting with the executable to
331    ///   launch.
332    /// * `fds` - Array of file descriptors to pass to the new process.
333    /// * `envs` - Array of variable/value pairs for the environment of the new
334    ///   process.
335    /// * `flags`
336    /// * `options` - A [`SpawnOptions`].
337    ///
338    /// # Returns
339    ///
340    /// The PID of the new process.
341    ///
342    /// # Specifications
343    ///
344    /// See also [`Spawn`](https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-method-org-freedesktop-portal-Flatpak.Spawn).
345    #[doc(alias = "Spawn")]
346    #[doc(alias = "xdp_portal_spawn")]
347    pub async fn spawn(
348        &self,
349        cwd_path: impl AsRef<Path>,
350        argv: &[impl AsRef<Path>],
351        fds: HashMap<u32, impl AsFd>,
352        envs: HashMap<&str, &str>,
353        flags: BitFlags<SpawnFlags>,
354        options: SpawnOptions,
355    ) -> Result<u32, Error> {
356        let cwd_path = FilePath::new(cwd_path)?;
357        let argv = argv
358            .iter()
359            .map(FilePath::new)
360            .collect::<Result<Vec<FilePath>, _>>()?;
361        let fds: HashMap<u32, Fd> = fds.iter().map(|(k, val)| (*k, Fd::from(val))).collect();
362        self.0
363            .call("Spawn", &(cwd_path, argv, fds, envs, flags, options))
364            .await
365    }
366
367    /// This methods let you send a Unix signal to a process that was started
368    /// [`spawn()`][`Flatpak::spawn`].
369    ///
370    /// # Arguments
371    ///
372    /// * `pid` - The PID of the process to send the signal to.
373    /// * `signal` - The signal to send.
374    /// * `to_process_group` - Whether to send the signal to the process group.
375    ///
376    /// # Specifications
377    ///
378    /// See also [`SpawnSignal`](https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-method-org-freedesktop-portal-Flatpak.SpawnSignal).
379    #[doc(alias = "SpawnSignal")]
380    #[doc(alias = "xdp_portal_spawn_signal")]
381    pub async fn spawn_signal(
382        &self,
383        pid: Pid,
384        signal: u32,
385        to_process_group: bool,
386    ) -> Result<(), Error> {
387        self.0
388            .call("SpawnSignal", &(pid, signal, to_process_group))
389            .await
390    }
391
392    /// Flags marking what optional features are available.
393    ///
394    /// # Specifications
395    ///
396    /// See also [`supports`](https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-property-org-freedesktop-portal-Flatpak.supports).
397    #[doc(alias = "supports")]
398    pub async fn supported_features(&self) -> Result<BitFlags<SupportsFlags>, Error> {
399        self.0
400            .property_versioned::<BitFlags<SupportsFlags>>("supports", 3)
401            .await
402    }
403}
404
405impl std::ops::Deref for Flatpak {
406    type Target = zbus::Proxy<'static>;
407
408    fn deref(&self) -> &Self::Target {
409        &self.0
410    }
411}
412
413/// Monitor if there's an update it and install it.
414pub mod update_monitor;
415
416/// Provide for a way to execute processes outside of the sandbox
417mod development;
418pub use development::{Development, HostCommandFlags};