What is ASHPD?
mainzbus. It provides a high-level, easy-to-use API for interacting with various desktop portals as defined by the XDG specifications. It serves as a Rust-native alternative to the C library libportal.repository·main·Indexed 18 days ago
https://github.com/bilelmoussaoui/ashpdA Rust wrapper for XDG desktop portals via DBus, built on top of zbus. It provides a type-safe, async-friendly, Rust-native alternative to libportal for accessing system features such as cameras, color pickers, screen casting, GameMode, and global shortcuts.
zbus. It provides a high-level, easy-to-use API for interacting with various desktop portals as defined by the XDG specifications. It serves as a Rust-native alternative to the C library libportal.When moving from a demo to a production portal backend, you must satisfy two requirements:
.portal file must be installed under {DATADIR}/xdg-desktop-portal/portals/.The ASHPD Demo is a client application designed to let users play with portals. It is available on Flathub.
https://flathub.org/apps/details/com.belmoussaoui.ashpd.demoYou can test the ashpd-backend-demo without installing it by using the $XDG_DESKTOP_PORTAL_DIR environment variable. This variable instructs the frontend where to look for the portal and configuration files.
Warning: Setting this variable temporarily overrides your system's default portals (like GNOME or KDE). If the demo backend does not implement a specific interface that your applications expect, those interfaces will be unavailable during testing.
XDG_DESKTOP_PORTAL_DIR=$PWD/demo/backend/data /usr/libexec/xdg-desktop-portal -v -rASHPD provides several modules for interacting with desktop services:
desktop: Interact with the user's desktop (e.g., screenshots, background settings, location).documents: (Requires documents feature) Interact with the documents store or transfer files across apps.flatpak: (Requires flatpak feature) Spawn commands outside the sandbox or monitor/install updates.backend: (Requires backend feature) Build your own custom portals backend.Commonly used types:
AppID: Represents an application ID.Uri: Represents a URI.FilePath: Represents a file path.ActivationToken: Used for activation-related tasks.WindowIdentifier: Used to identify specific windows.Error / PortalError: Error types for handling failures.Most portals (like file pickers or permission dialogs) need to be displayed on top of the application window that triggered them. To achieve this, the compositor requires a WindowIdentifier to associate the portal dialog with the parent application window.
WindowIdentifier abstracts the differences between display protocols:
x11:XID, where XID is the hexadecimal XID of the window.wayland:HANDLE, where HANDLE is a surface handle obtained via the xdg-foreign protocol.Depending on your toolkit and features, you can create these identifiers from GTK 4 natives, Wayland surfaces, or raw window handles.
// Example: Creating an identifier from an X11 XID
let identifier = WindowIdentifier::from_xid(212321);
// Example: Creating an identifier from a Wayland surface (requires `wayland` feature)
// let identifier = WindowIdentifier::from_wayland(wl_surface).await;The GameMode struct provides an interface for sandboxed applications to access the org.freedesktop.portal.GameMode DBus interface. It allows applications to register themselves (or other processes) as games to request GameMode activation, unregister them, and query the current status of a process.
Key features:
unregister, GameMode will automatically unregister it after a small delay.pidfd), or a combination of a target PID/FD and a requester PID/FD.use ashpd::desktop::game_mode::GameMode;
async fn run() -> ashpd::Result<()> {
let proxy = GameMode::new().await?;
// Register a process
proxy.register(246612).await?;
// Query status
let status = proxy.query_status(246612).await?;
println!("{:#?}", status);
// Unregister a process
proxy.unregister(246612).await?;
Ok(())
}The Session<T> type is a wrapper around the org.freedesktop.portal.Session DBus interface. It is used by portals that involve long-lived interactions (such as ScreenCast, Remote Desktop, or Global Shortcuts). When a portal method creates a session, it returns a session handle (an object path) that identifies a Session object which remains alive for the duration of that interaction.
Key capabilities:
.close()..receive_closed() stream.Note: You should not create a Session instance manually. Instead, it is provided as a response to a session-creating method from a SessionPortal.
// Example of interacting with an existing session
// (Assuming `session` is obtained from a SessionPortal call)
// Listen for the session closing
let mut closed_stream = session.receive_closed().await?;
while let Some(details) = closed_stream.next().await {
println!("Session closed with details: {:?}", details);
}
// Or manually close the session
session.close().await?;ASHPD uses Cargo features to enable specific functionalities. By default, tokio is enabled. Other features include:
tracing: Enables debug information via the tracing library.async-io: Enables compatibility with async-io crates (e.g., smol or glib).frontend: Enables all frontend APIs. You can also enable individual APIs like account, camera, or screencast.glib: Makes all enums derive glib::Enum (flags not yet supported).gtk4: Implements From<Color> for gdk4::RGBA and provides WindowIdentifier::from_native for IsA<gtk4::Native>.gtk4_wayland / gtk4_x11: Specialized WindowIdentifier::from_native support for specific backends.pipewire: Provides ashpd::desktop::camera::pipewire_streams to help retrieve camera streams from a file descriptor.raw_handle: Provides WindowIdentifier integration with the raw-window-handle crate.wayland: Provides WindowIdentifier::from_wayland for the wayland-client crate.backend: Enables support for implementing portal backend interfaces.The FileTransfer API acts as a middle-man for transferring files between applications, typically via drag-and-drop or copy-paste.
Workflow:
start_transfer() to obtain a unique key.add_files() using that key and a list of file descriptors to register files in the session.key to the target application (e.g., via the application/vnd.portal.filetransfer mimetype).key.retrieve_files() with the key to obtain the list of file paths. The portal handles exporting the files so they are accessible to the receiver.stop_transfer() to end the session.Note: Only regular files (not directories) can be added via add_files.
use std::{fs::File, os::fd::AsFd};
use ashpd::documents::file_transfer::{FileTransfer, StartTransferOptions};
async fn run() -> ashpd::Result<()> {
let proxy = FileTransfer::new().await?;
// 1. Start transfer and get key
let key = proxy
.start_transfer(
StartTransferOptions::default()
.set_writable(true)
.set_auto_stop(true),
)
.await?;
// 2. Add files to the session
let file = File::open("/path/to/file.jpg").unwrap();
proxy
.add_files(&key, &[&file.as_fd()], Default::default()),
.await?;
// 3. Receiver retrieves files using the key
let files = proxy.retrieve_files(&key, Default::default()).await?;
println!("{:#?}", files);
// 4. Stop the transfer
proxy.stop_transfer(&key).await?;
Ok(())
}ASHPD requires an asynchronous runtime. You must enable either the tokio feature or the async-io feature. You cannot enable both at the same time.
Error conditions:
tokio and async-io will cause a compilation error.tokio nor async-io will cause a compilation error.The NetworkMonitor provides information about the host's network status, including availability, metered status, and detailed connectivity levels. It is a wrapper for the org.freedesktop.portal.NetworkMonitor DBus interface.
Note: This portal does not work for sandboxed applications. Applications are typically expected to use this interface indirectly via libraries like GLib's gio::NetworkMonitor.
use ashpd::desktop::network_monitor::NetworkMonitor;
async fn run() -> ashpd::Result<()> {
let proxy = NetworkMonitor::new().await?;
println!("{}", proxy.can_reach("www.google.com", 80).await?);
println!("{}", proxy.is_available().await?);
println!("{:#?}", proxy.connectivity().await?);
println!("{}", proxy.is_metered().await?);
println!("{:#?}", proxy.status().await?);
Ok(())
}