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