### Run Demo Portal Backend Source: https://github.com/bilelmoussaoui/ashpd/blob/main/demo/backend/README.md Set the XDG_DESKTOP_PORTAL_DIR environment variable to the demo backend's data directory and run the xdg-desktop-portal service. This is useful for testing without installing the backend. ```shell XDG_DESKTOP_PORTAL_DIR=$PWD/demo/backend/data /usr/libexec/xdg-desktop-portal -v -r ``` -------------------------------- ### File Open/Save Dialogs (`SelectedFiles`) Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Demonstrates how to use `SelectedFiles` to open one or more files, save a single file, or save multiple files. It includes examples of setting dialog titles, accept labels, filters, choices, and handling user selections. ```APIDOC ## `ashpd::desktop::file_chooser::SelectedFiles` — File open/save dialogs Wraps `org.freedesktop.portal.FileChooser`. Builder methods produce `OpenFileRequest`, `SaveFileRequest`, and `SaveFilesRequest`. Supports file filters by MIME type or glob, custom choice widgets (dropdowns, checkboxes), and multiple selection. ```rust use ashpd::desktop::file_chooser::{Choice, FileFilter, SelectedFiles}; async fn run() -> ashpd::Result<()> { // Open one or more files let files = SelectedFiles::open_file() .title("Select an image") .accept_label("Open") .modal(true) .multiple(true) .filter(FileFilter::new("JPEG").mimetype("image/jpeg")) .filter(FileFilter::new("PNG").mimetype("image/png")) .filter(FileFilter::new("All images").mimetype("image/*")) .choice(Choice::boolean("edit-in-place", "Edit in place", false)) .choice( Choice::new("quality", "Export quality", "high") .insert("low", "Low (fast)") .insert("high", "High (slow)"), ) .send() .await?; .response()?; for uri in files.uris() { println!("Selected: {}", uri); } for (key, value) in files.choices() { println!("Choice {} = {}", key, value); } // Save a single file let save = SelectedFiles::save_file() .title("Save document") .accept_label("Save") .current_name("document.pdf") .filter(FileFilter::new("PDF").glob("*.pdf")) .send() .await?; .response()?; println!("Save to: {:?}", save.uris()); // Save multiple files to a folder let saves = SelectedFiles::save_files() .title("Export images") .current_folder("/home/user/Pictures")?; .files(&["photo1.jpg", "photo2.jpg"])?; .send() .await?; .response()?; println!("Saved files: {:?}", saves.uris()); Ok(()) } ``` ``` -------------------------------- ### Access Camera Stream with ASHPD Source: https://github.com/bilelmoussaoui/ashpd/blob/main/client/README.md This example demonstrates how to access the user's camera stream using the `ashpd::desktop::camera::Camera` module. It checks if a camera is present, requests access, and retrieves a PipeWire remote file descriptor. ```rust use ashpd::desktop::camera::Camera; pub async fn run() -> ashpd::Result<()> { let camera = Camera::new().await?; if camera.is_present().await? { camera.request_access(Default::default()).await?; let remote_fd = camera.open_pipe_wire_remote(Default::default()).await?; // pass the remote fd to GStreamer for example } Ok(()) } ``` -------------------------------- ### Screencast Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Initiate screen casting sessions using PipeWire. This API allows creating a session, letting the user select screen sources (monitors, windows), starting the cast, and obtaining PipeWire node IDs for media pipelines. It supports embedding the cursor and selecting multiple sources. ```APIDOC ## `ashpd::desktop::screencast::Screencast` — Screen cast via PipeWire Wraps `org.freedesktop.portal.ScreenCast`. Creates a session, lets the user select sources (monitors, windows, virtual), starts the cast, and provides PipeWire node IDs to connect a media pipeline. ```rust use ashpd::desktop::{ PersistMode, screencast::{CursorMode, Screencast, SelectSourcesOptions, SourceType}, }; async fn run() -> ashpd::Result<()> { let proxy = Screencast::new().await?; let session = proxy.create_session(Default::default()).await?; // Configure: allow multiple sources, embed cursor, record monitors & windows proxy .select_sources( &session, SelectSourcesOptions::default() .set_cursor_mode(CursorMode::Embedded) .set_sources(SourceType::Monitor | SourceType::Window) .set_multiple(true) .set_persist_mode(PersistMode::DoNot), ) .await?; // Present source-selection dialog to user let streams_response = proxy .start(&session, None, Default::default()) .await? .response()?; for stream in streams_response.streams() { println!("PipeWire node id: {}", stream.pipe_wire_node_id()); println!(" size: {:?}", stream.size()); println!(" position: {:?}", stream.position()); println!(" source type: {:?}", stream.source_type()); } // Open the PipeWire remote to actually consume the stream let _fd = proxy .open_pipe_wire_remote(&session, Default::default()) .await?; Ok(()) } ``` ``` -------------------------------- ### Remote Desktop Control Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Control remote desktop sessions, enabling injection of keyboard and pointer events. This API can be integrated with `Screencast` to stream video of the remote session. It allows selecting input devices and starting a session. ```APIDOC ## `ashpd::desktop::remote_desktop::RemoteDesktop` — Remote desktop control Wraps `org.freedesktop.portal.RemoteDesktop`. Creates a session that can inject keyboard and pointer events. Can be combined with the `Screencast` portal so the session also streams video. ```rust use ashpd::desktop::{ PersistMode, remote_desktop::{DeviceType, KeyState, RemoteDesktop, SelectDevicesOptions}, screencast::{CursorMode, Screencast, SelectSourcesOptions, SourceType}, }; async fn run() -> ashpd::Result<()> { let remote_desktop = RemoteDesktop::new().await?; let screencast = Screencast::new().await?; // Create session on the RemoteDesktop portal let session = remote_desktop.create_session(Default::default()).await?; // Select which input devices to control remote_desktop .select_devices( &session, SelectDevicesOptions::default() .set_devices(DeviceType::Keyboard | DeviceType::Pointer), ) .await?; // Optionally attach screen cast sources to the same session screencast .select_sources( &session, SelectSourcesOptions::default() .set_cursor_mode(CursorMode::Metadata) .set_sources(SourceType::Monitor) .set_multiple(false) .set_persist_mode(PersistMode::DoNot), ) .await?; let response = remote_desktop .start(&session, None, Default::default()) .await? .response()?; println!("Devices granted: {:?}", response.devices()); // Inject a key press (key code 13 = Enter) remote_desktop .notify_keyboard_keycode(&session, 13, KeyState::Pressed, Default::default()) .await?; remote_desktop .notify_keyboard_keycode(&session, 13, KeyState::Released, Default::default()) .await?; Ok(()) } ``` ``` -------------------------------- ### Spawn processes and monitor updates (Flatpak only) Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps `org.freedesktop.portal.Flatpak`. Spawns host commands from inside a Flatpak sandbox (with controlled permissions) and monitors for available application updates. Only meaningful when running inside a Flatpak. ```APIDOC ## `ashpd::flatpak::Flatpak` — Spawn processes and monitor updates (Flatpak only) Wraps `org.freedesktop.portal.Flatpak`. Spawns host commands from inside a Flatpak sandbox (with controlled permissions) and monitors for available application updates. Only meaningful when running inside a Flatpak. ```rust use std::collections::HashMap; use ashpd::flatpak::{Flatpak, SpawnFlags, SpawnOptions}; async fn run() -> ashpd::Result<()> { let proxy = Flatpak::new().await?; // Spawn a host process with no network and a clean environment let pid = proxy .spawn( "/", &["notify-send", "Hello from Flatpak!"], HashMap::new(), // fd map HashMap::new(), // env vars SpawnFlags::ClearEnv | SpawnFlags::NoNetwork, SpawnOptions::default(), ) .await?; println!("Spawned PID: {pid}"); // Check for and install Flatpak updates let monitor = proxy.create_update_monitor().await?; // monitor.update_available() is a signal stream Ok(()) } ``` ``` -------------------------------- ### Open, Save, and Save Files with ashpd::desktop::file_chooser::SelectedFiles Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Demonstrates opening one or more files, saving a single file, and exporting multiple files using the `SelectedFiles` builder. Supports custom titles, labels, filters (MIME type and glob), choices (boolean and enumerated), and multiple selection. Ensure the necessary desktop portal is available. ```rust use ashpd::desktop::file_chooser::{Choice, FileFilter, SelectedFiles}; async fn run() -> ashpd::Result<()> { // Open one or more files let files = SelectedFiles::open_file() .title("Select an image") .accept_label("Open") .modal(true) .multiple(true) .filter(FileFilter::new("JPEG").mimetype("image/jpeg")) .filter(FileFilter::new("PNG").mimetype("image/png")) .filter(FileFilter::new("All images").mimetype("image/*")) .choice(Choice::boolean("edit-in-place", "Edit in place", false)) .choice( Choice::new("quality", "Export quality", "high") .insert("low", "Low (fast)") .insert("high", "High (slow)"), ) .send() .await?; for uri in files.response()?.uris() { println!("Selected: {}", uri); } for (key, value) in files.response()?.choices() { println!("Choice {} = {}", key, value); } // Save a single file let save = SelectedFiles::save_file() .title("Save document") .accept_label("Save") .current_name("document.pdf") .filter(FileFilter::new("PDF").glob("*.pdf")) .send() .await?; println!("Save to: {:?}", save.response()?.uris()); // Save multiple files to a folder let saves = SelectedFiles::save_files() .title("Export images") .current_folder("/home/user/Pictures")? // Ensure this path is valid and accessible .files(&["photo1.jpg", "photo2.jpg"])? // Provide actual file names .send() .await?; println!("Saved files: {:?}", saves.response()?.uris()); Ok(()) } ``` -------------------------------- ### ashpd::desktop::settings::Settings Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps `org.freedesktop.portal.Settings`. Provides read access to host appearance settings and an async stream that fires when any setting changes. ```APIDOC ## `ashpd::desktop::settings::Settings` — Read system settings Wraps `org.freedesktop.portal.Settings`. Provides read access to host appearance settings (color scheme, accent color, contrast, reduced motion) and an async stream that fires when any setting changes. ```rust use ashpd::desktop::settings::{ColorScheme, Settings}; use futures_util::StreamExt; async fn run() -> ashpd::Result<()> { let proxy = Settings::new().await?; // Query high-level appearance helpers let scheme = proxy.color_scheme().await?; println!("Color scheme: {:?}", scheme); // NoPreference | PreferDark | PreferLight let accent = proxy.accent_color().await?; println!( "Accent color: R={:.2} G={:.2} B={:.2}", accent.red(), accent.green(), accent.blue() ); let contrast = proxy.contrast().await?; println!("High contrast: {:?}", contrast); // Read an arbitrary key let clock_fmt = proxy .read::("org.gnome.desktop.interface", "clock-format") .await?; println!("Clock format: {clock_fmt}"); // Read an entire namespace let iface_settings = proxy .read_all(&["org.gnome.desktop.interface"]) .await?; println!("Interface keys: {:?}", iface_settings.keys().collect::>()); // React to dark-mode toggles let mut scheme_stream = proxy.receive_color_scheme_changed().await?; while let Some(new_scheme) = scheme_stream.next().await { match new_scheme { ColorScheme::PreferDark => println!("Switched to dark mode"), ColorScheme::PreferLight => println!("Switched to light mode"), ColorScheme::NoPreference => println!("No color scheme preference"), } } Ok(()) } ``` ``` -------------------------------- ### Spawn Processes and Monitor Updates with Flatpak Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps the Flatpak portal to spawn host processes from within a Flatpak sandbox and monitor for application updates. This functionality is only relevant when running inside a Flatpak. ```rust use std::collections::HashMap; use ashpd::flatpak::{Flatpak, SpawnFlags, SpawnOptions}; async fn run() -> ashpd::Result<()> { let proxy = Flatpak::new().await?; // Spawn a host process with no network and a clean environment let pid = proxy .spawn( "/", &["notify-send", "Hello from Flatpak!"], HashMap::new(), // fd map HashMap::new(), // env vars SpawnFlags::ClearEnv | SpawnFlags::NoNetwork, SpawnOptions::default(), ) .await?; println!("Spawned PID: {pid}"); // Check for and install Flatpak updates let monitor = proxy.create_update_monitor().await?; // monitor.update_available() is a signal stream Ok(()) } ``` -------------------------------- ### Desktop Notifications (`NotificationProxy`) Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Shows how to send desktop notifications with actions, icons, and priorities, and how to handle user interactions with these notifications. It also covers withdrawing notifications. ```APIDOC ## `ashpd::desktop::notification::NotificationProxy` — Send desktop notifications Wraps `org.freedesktop.portal.Notification`. Sends and withdraws sandboxed notifications with title, body, icon, priority, action buttons, and category. Action invocations are received as an async stream. ```rust use ashpd::desktop::{ Icon, notification::{Action, Button, Notification, NotificationProxy, Priority}, }; use futures_util::StreamExt; async fn run() -> ashpd::Result<()> { let proxy = NotificationProxy::new().await?; // Send a notification with two action buttons proxy .add_notification( "com.example.myapp.upload-complete", Notification::new("Upload Complete") .body("your-photo.jpg was uploaded successfully") .priority(Priority::Normal) .icon(Icon::with_names(&["emblem-ok-symbolic"])) .default_action("open-folder") .button(Button::new("Open Folder", "open-folder").target(1u32)) .button(Button::new("Dismiss", "dismiss")), ) .await?; // Wait for the user to click a button let mut actions = proxy.receive_action_invoked().await?; if let Some(action) = actions.next().await { match action.name() { "open-folder" => println!("User clicked Open Folder (id={})", action.id()), "dismiss" => println!("User dismissed the notification"), other => println!("Unknown action: {other}"), } } // Withdraw the notification programmatically proxy .remove_notification("com.example.myapp.upload-complete") .await?; Ok(()) } ``` ``` -------------------------------- ### Take a screenshot Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps `org.freedesktop.portal.Screenshot`. Presents a screenshot dialog to the user and returns a URI pointing to the saved image file. The `interactive` flag lets the user select a region or window before capturing. ```APIDOC ## `ashpd::desktop::screenshot::Screenshot` — Take a screenshot Wraps `org.freedesktop.portal.Screenshot`. Presents a screenshot dialog to the user and returns a URI pointing to the saved image file. The `interactive` flag lets the user select a region or window before capturing. ```rust use ashpd::desktop::screenshot::{AvailableTargets, Screenshot}; async fn run() -> ashpd::Result<()> { // Simple screenshot with user interaction let response = Screenshot::request() .interactive(true) .modal(true) .send() .await? .response()?; println!("Screenshot saved at: {}", response.uri()); // Target only the active window let window_shot = Screenshot::request() .interactive(false) .target(AvailableTargets::ActiveWindow) .send() .await? .response()?; println!("Window screenshot: {}", window_shot.uri()); Ok(()) } ``` ``` -------------------------------- ### Read System Settings with Ashpd Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Query appearance settings like color scheme, accent color, and contrast. Also demonstrates reading arbitrary keys and reacting to setting changes asynchronously. ```rust use ashpd::desktop::settings::{ColorScheme, Settings}; use futures_util::StreamExt; async fn run() -> ashpd::Result<()> { let proxy = Settings::new().await?; // Query high-level appearance helpers let scheme = proxy.color_scheme().await?; println!("Color scheme: {:?}", scheme); // NoPreference | PreferDark | PreferLight let accent = proxy.accent_color().await?; println!( "Accent color: R={:.2} G={:.2} B={:.2}", accent.red(), accent.green(), accent.blue() ); let contrast = proxy.contrast().await?; println!("High contrast: {:?}", contrast); // Read an arbitrary key let clock_fmt = proxy .read::("org.gnome.desktop.interface", "clock-format") .await?; println!("Clock format: {clock_fmt}"); // Read an entire namespace let iface_settings = proxy .read_all(&["org.gnome.desktop.interface"]) .await?; println!("Interface keys: {:?}", iface_settings.keys().collect::>()); // React to dark-mode toggles let mut scheme_stream = proxy.receive_color_scheme_changed().await?; while let Some(new_scheme) = scheme_stream.next().await { match new_scheme { ColorScheme::PreferDark => println!("Switched to dark mode"), ColorScheme::PreferLight => println!("Switched to light mode"), ColorScheme::NoPreference => println!("No color scheme preference"), } } Ok(()) } ``` -------------------------------- ### Access Camera via PipeWire Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Initializes camera access, checks for presence, requests user permission, and obtains a PipeWire remote file descriptor. Requires the `pipewire` feature to enumerate individual camera nodes. ```rust use ashpd::desktop::camera::{Camera, pipewire_streams}; async fn run() -> ashpd::Result<()> { let camera = Camera::new().await?; if !camera.is_present().await? { eprintln!("No camera found."); return Ok(()); } // Request user permission camera.request_access(Default::default()).await?; // Get the PipeWire file descriptor let remote_fd = camera.open_pipe_wire_remote(Default::default()).await?; println!("PipeWire remote fd obtained: {:?}", remote_fd); // With the `pipewire` feature: enumerate camera streams // let streams = pipewire_streams(remote_fd).await?; // for stream in streams { // println!("Camera node id: {}", stream.node_id()); // } Ok(()) } ``` -------------------------------- ### Screen Cast via PipeWire Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Creates a screen casting session, allowing the user to select sources like monitors or windows. It configures options such as cursor embedding and multiple source selection, then provides PipeWire node IDs for media pipelines. ```rust use ashpd::desktop::{ PersistMode, screencast::{CursorMode, Screencast, SelectSourcesOptions, SourceType}, }; async fn run() -> ashpd::Result<()> { let proxy = Screencast::new().await?; let session = proxy.create_session(Default::default()).await?; // Configure: allow multiple sources, embed cursor, record monitors & windows proxy .select_sources( &session, SelectSourcesOptions::default() .set_cursor_mode(CursorMode::Embedded) .set_sources(SourceType::Monitor | SourceType::Window) .set_multiple(true) .set_persist_mode(PersistMode::DoNot), ) .await?; // Present source-selection dialog to user let streams_response = proxy .start(&session, None, Default::default()) .await? .response()?; for stream in streams_response.streams() { println!("PipeWire node id: {}", stream.pipe_wire_node_id()); println!(" size: {:?}", stream.size()); println!(" position: {:?}", stream.position()); println!(" source type: {:?}", stream.source_type()); } // Open the PipeWire remote to actually consume the stream let _fd = proxy .open_pipe_wire_remote(&session, Default::default()) .await?; Ok(()) } ``` -------------------------------- ### Send Desktop Notifications with ashpd::desktop::notification::NotificationProxy Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Shows how to send desktop notifications with actions and handle user interactions using `NotificationProxy`. Requires the `org.freedesktop.portal.Notification` portal. Action invocations are received as an async stream. ```rust use ashpd::desktop::notification::{Action, Button, Notification, NotificationProxy, Priority}; use ashpd::desktop::Icon; use futures_util::StreamExt; async fn run() -> ashpd::Result<()> { let proxy = NotificationProxy::new().await?; // Send a notification with two action buttons proxy .add_notification( "com.example.myapp.upload-complete", Notification::new("Upload Complete") .body("your-photo.jpg was uploaded successfully") .priority(Priority::Normal) .icon(Icon::with_names(&["emblem-ok-symbolic"])) .default_action("open-folder") .button(Button::new("Open Folder", "open-folder").target(1u32)) .button(Button::new("Dismiss", "dismiss")), ) .await?; // Wait for the user to click a button let mut actions = proxy.receive_action_invoked().await?; if let Some(action) = actions.next().await { match action.name() { "open-folder" => println!("User clicked Open Folder (id={})", action.id()), "dismiss" => println!("User dismissed the notification"), other => println!("Unknown action: {other}"), } } // Withdraw the notification programmatically proxy .remove_notification("com.example.myapp.upload-complete") .await?; Ok(()) } ``` -------------------------------- ### Pick a color from screen Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps the `PickColor` method of `org.freedesktop.portal.Screenshot`. Opens a color picker that lets the user click any pixel on screen; returns an RGB tuple with values normalized to `[0.0, 1.0]`. ```APIDOC ## `ashpd::desktop::Color::pick` — Pick a color from screen Wraps the `PickColor` method of `org.freedesktop.portal.Screenshot`. Opens a color picker that lets the user click any pixel on screen; returns an RGB tuple with values normalized to `[0.0, 1.0]`. ```rust use ashpd::desktop::Color; async fn run() -> ashpd::Result<()> { let color = Color::pick().send().await?.response()?; println!( "Picked color — R: {:.3}, G: {:.3}, B: {:.3}", color.red(), color.green(), color.blue() ); // Convert to 8-bit let r = (color.red() * 255.0) as u8; let g = (color.green() * 255.0) as u8; let b = (color.blue() * 255.0) as u8; println!("Hex: #{:02X}{:02X}{:02X}", r, g, b); // With gtk4 feature: From for gdk4::RGBA is implemented automatically // let rgba: gdk4::RGBA = color.into(); Ok(()) } ``` ``` -------------------------------- ### Take a Screenshot with `ashpd::desktop::screenshot::Screenshot` Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt The `Screenshot` struct allows taking screenshots. Use the `interactive` flag for user selection and `modal` to make the dialog blocking. You can also target specific windows. ```rust use ashpd::desktop::screenshot::{AvailableTargets, Screenshot}; async fn run() -> ashpd::Result<()> { // Simple screenshot with user interaction let response = Screenshot::request() .interactive(true) .modal(true) .send() .await?; .response()?; println!("Screenshot saved at: {}", response.uri()); // Target only the active window let window_shot = Screenshot::request() .interactive(false) .target(AvailableTargets::ActiveWindow) .send() .await?; .response()?; println!("Window screenshot: {}", window_shot.uri()); Ok(()) } ``` -------------------------------- ### Open Files, URIs, and Directories with Ashpd Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Requests the host to open files, URIs, or directories with their default applications. Supports opening files via file descriptors or URIs, and directories via file descriptors. ```rust use std::{fs::File, os::fd::AsFd}; use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest}; async fn run() -> ashpd::Result<()> { // Open a local file — let the user choose the app let file = File::open("/home/user/Downloads/report.pdf").unwrap(); OpenFileRequest::default() .ask(true) .send_file(&file.as_fd()) .await?; // Open a web URL in the default browser let uri = ashpd::Uri::parse("https://example.com").unwrap(); OpenFileRequest::default() .ask(false) .send_uri(&uri) .await?; // Open a directory in the file manager let dir = File::open("/home/user/Pictures").unwrap(); OpenDirectoryRequest::default() .send(&dir.as_fd()) .await?; Ok(()) } ``` -------------------------------- ### Set Desktop Wallpaper with Ashpd Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Set the desktop or lock screen wallpaper using a local file descriptor or a URI. Optionally displays a preview dialog before applying. ```rust use std::{fs::File, os::fd::AsFd}; use ashpd::desktop::wallpaper::{SetOn, WallpaperRequest}; async fn run() -> ashpd::Result<()> { // Set from a local file descriptor (no file:// URI needed) let file = File::open("/home/user/Pictures/wallpaper.jpg").unwrap(); WallpaperRequest::default() .set_on(SetOn::Background) .show_preview(true) .build_file(&file.as_fd()) .await?; // Set from a URI on both lock screen and background let uri = ashpd::Uri::parse( "file:///home/user/Pictures/lockscreen.png" ).unwrap(); WallpaperRequest::default() .set_on(SetOn::Both) .show_preview(false) .build_uri(&uri) .await?; Ok(()) } ``` -------------------------------- ### ashpd::desktop::wallpaper::WallpaperRequest Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps `org.freedesktop.portal.Wallpaper`. Sets the wallpaper on the lock screen, desktop background, or both, either from a file descriptor or a URI. Optionally shows a preview dialog. ```APIDOC ## `ashpd::desktop::wallpaper::WallpaperRequest` — Set desktop wallpaper Wraps `org.freedesktop.portal.Wallpaper`. Sets the wallpaper on the lock screen, desktop background, or both, either from a file descriptor or a URI. Optionally shows a preview dialog. ```rust use std::{fs::File, os::fd::AsFd}; use ashpd::desktop::wallpaper::{SetOn, WallpaperRequest}; async fn run() -> ashpd::Result<()> { // Set from a local file descriptor (no file:// URI needed) let file = File::open("/home/user/Pictures/wallpaper.jpg").unwrap(); WallpaperRequest::default() .set_on(SetOn::Background) .show_preview(true) .build_file(&file.as_fd()) .await?; // Set from a URI on both lock screen and background let uri = ashpd::Uri::parse( "file:///home/user/Pictures/lockscreen.png" ).unwrap(); WallpaperRequest::default() .set_on(SetOn::Both) .show_preview(false) .build_uri(&uri) .await?; Ok(()) } ``` ``` -------------------------------- ### Query user account info Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps `org.freedesktop.portal.Account`. Requests the user's login name, display name, and avatar URI after showing a permission dialog with the provided `reason` string. ```APIDOC ## `ashpd::desktop::account::UserInformation` — Query user account info Wraps `org.freedesktop.portal.Account`. Requests the user's login name, display name, and avatar URI after showing a permission dialog with the provided `reason` string. ```rust use ashpd::desktop::account::UserInformation; async fn run() -> ashpd::Result<()> { let info = UserInformation::request() .reason("The application would like to personalize your experience") .send() .await? .response()?; println!("Login ID: {}", info.id()); println!("Display name: {}", info.name()); println!("Avatar URI: {}", info.image()); Ok(()) } ``` ``` -------------------------------- ### Remote Desktop Control via PipeWire Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Manages remote desktop sessions, enabling injection of keyboard and pointer events. It can be combined with screen casting to stream video and allows selection of input devices. ```rust use ashpd::desktop::{ PersistMode, remote_desktop::{DeviceType, KeyState, RemoteDesktop, SelectDevicesOptions}, screencast::{CursorMode, Screencast, SelectSourcesOptions, SourceType}, }; async fn run() -> ashpd::Result<()> { let remote_desktop = RemoteDesktop::new().await?; let screencast = Screencast::new().await?; // Create session on the RemoteDesktop portal let session = remote_desktop.create_session(Default::default()).await?; // Select which input devices to control remote_desktop .select_devices( &session, SelectDevicesOptions::default() .set_devices(DeviceType::Keyboard | DeviceType::Pointer), ) .await?; // Optionally attach screen cast sources to the same session screencast .select_sources( &session, SelectSourcesOptions::default() .set_cursor_mode(CursorMode::Metadata) .set_sources(SourceType::Monitor) .set_multiple(false) .set_persist_mode(PersistMode::DoNot), ) .await?; let response = remote_desktop .start(&session, None, Default::default()) .await? .response()?; println!("Devices granted: {:?}", response.devices()); // Inject a key press (key code 13 = Enter) remote_desktop .notify_keyboard_keycode(&session, 13, KeyState::Pressed, Default::default()) .await?; remote_desktop .notify_keyboard_keycode(&session, 13, KeyState::Released, Default::default()) .await?; Ok(()) } ``` -------------------------------- ### Pick a Color with ASHPD Source: https://github.com/bilelmoussaoui/ashpd/blob/main/client/README.md Use the `Color::pick()` method to prompt the compositor for a color selection. The selected color's RGB values are then printed. ```rust use ashpd::desktop::Color; async fn run() -> ashpd::Result<()> { let color = Color::pick().send().await?.response()?; println!("({}, {}, {})", color.red(), color.green(), color.blue()); Ok(()) } ``` -------------------------------- ### Query User Account Information Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Requests the user's login name, display name, and avatar URI. Requires showing a permission dialog. ```rust use ashpd::desktop::account::UserInformation; async fn run() -> ashpd::Result<()> { let info = UserInformation::request() .reason("The application would like to personalize your experience") .send() .await?; .response()?; println!("Login ID: {}", info.id()); println!("Display name: {}", info.name()); println!("Avatar URI: {}", info.image()); Ok(()) } ``` -------------------------------- ### Pick a Color from Screen with `ashpd::desktop::Color::pick` Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt The `Color::pick` function opens a color picker dialog, allowing the user to select any pixel on the screen. It returns an RGB tuple normalized to [0.0, 1.0]. ```rust use ashpd::desktop::Color; async fn run() -> ashpd::Result<()> { let color = Color::pick().send().await?.response()?; println!( "Picked color — R: {:.3}, G: {:.3}, B: {:.3}", color.red(), color.green(), color.blue() ); // Convert to 8-bit let r = (color.red() * 255.0) as u8; let g = (color.green() * 255.0) as u8; let b = (color.blue() * 255.0) as u8; println!("Hex: #{:02X}{:02X}{:02X}", r, g, b); // With gtk4 feature: From for gdk4::RGBA is implemented automatically // let rgba: gdk4::RGBA = color.into(); Ok(()) } ``` -------------------------------- ### ashpd::desktop::open_uri Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps `org.freedesktop.portal.OpenURI`. Asks the host to open a file or URI with the appropriate application, or open a directory in the file browser. File URIs must use the file-descriptor variant (`send_file`); non-file URIs use `send_uri`. ```APIDOC ## `ashpd::desktop::open_uri` — Open files, URIs, and directories Wraps `org.freedesktop.portal.OpenURI`. Asks the host to open a file or URI with the appropriate application, or open a directory in the file browser. File URIs must use the file-descriptor variant (`send_file`); non-file URIs use `send_uri`. ```rust use std::{fs::File, os::fd::AsFd}; use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest}; async fn run() -> ashpd::Result<()> { // Open a local file — let the user choose the app let file = File::open("/home/user/Downloads/report.pdf").unwrap(); OpenFileRequest::default() .ask(true) .send_file(&file.as_fd()) .await?; // Open a web URL in the default browser let uri = ashpd::Uri::parse("https://example.com").unwrap(); OpenFileRequest::default() .ask(false) .send_uri(&uri) .await?; // Open a directory in the file manager let dir = File::open("/home/user/Pictures").unwrap(); OpenDirectoryRequest::default() .send(&dir.as_fd()) .await?; Ok(()) } ``` ``` -------------------------------- ### Error Handling with ashpd::Error and ashpd::PortalError Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Demonstrates handling various errors that can occur when interacting with XDG portals, including DBus transport errors, portal-specific errors, user cancellations, version mismatches, and missing portal frontends. ```rust use ashpd::{Error, PortalError, desktop::screenshot::Screenshot}; async fn run() { match Screenshot::request().send().await { Ok(request) => match request.response() { Ok(screenshot) => println!("Screenshot: {}", screenshot.uri()), Err(Error::Response(e)) => println!("User cancelled or dialog failed: {e:?}"), Err(e) => eprintln!("Other error: {e}"), }, Err(Error::PortalNotFound(iface)) => { eprintln!("No portal frontend for {iface} is installed"); } Err(Error::Portal(PortalError::NotAllowed(msg))) => { eprintln!("Permission denied: {msg}"); } Err(Error::RequiresVersion(required, current)) => { eprintln!("Portal version {required} required, but {current} available"); } Err(e) => eprintln!("Unexpected error: {e}"), } } ``` -------------------------------- ### Detect sandbox environment Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Checks whether the running application is inside a Flatpak or Snap sandbox. The result is cached after the first call. ```APIDOC ## `ashpd::is_sandboxed` — Detect sandbox environment Checks whether the running application is inside a Flatpak or Snap sandbox by inspecting `/.flatpak-info`, Snap environment variables, or `GTK_USE_PORTAL=1`. The result is cached after the first call. ```rust use ashpd::is_sandboxed; fn main() { if is_sandboxed() { println!("Running inside a Flatpak/Snap sandbox — portals will be used."); } else { println!("Running outside sandbox — direct filesystem access available."); } } ``` ``` -------------------------------- ### Camera Access Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Access camera devices via the PipeWire portal. This includes checking for camera presence, requesting user permission, and obtaining a file descriptor for the PipeWire remote. The `pipewire` feature allows enumerating individual camera streams. ```APIDOC ## `ashpd::desktop::camera::Camera` — Access camera via PipeWire Wraps `org.freedesktop.portal.Camera`. Checks for camera presence, requests user permission, and returns an `OwnedFd` to a PipeWire remote. The optional `pipewire` feature adds `pipewire_streams()` to enumerate individual camera nodes. ```rust use ashpd::desktop::camera::{Camera, pipewire_streams}; async fn run() -> ashpd::Result<()> { let camera = Camera::new().await?; if !camera.is_present().await? { eprintln!("No camera found."); return Ok(()) } // Request user permission camera.request_access(Default::default()).await?; // Get the PipeWire file descriptor let remote_fd = camera.open_pipe_wire_remote(Default::default()).await?; println!("PipeWire remote fd obtained: {:?}", remote_fd); // With the `pipewire` feature: enumerate camera streams // let streams = pipewire_streams(remote_fd).await?; // for stream in streams { // println!("Camera node id: {}", stream.node_id()); // } Ok(()) } ``` ``` -------------------------------- ### Compose Email with Ashpd Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Opens the default email client with pre-filled recipient(s), subject, body, and optional file attachments provided as file descriptors. ```rust use std::{fs::File, os::fd::OwnedFd}; use ashpd::desktop::email::EmailRequest; async fn run() -> ashpd::Result<()> { let attachment = File::open("/home/user/report.pdf").unwrap(); EmailRequest::default() .address("recipient@example.com") .addresses(["cc1@example.com", "cc2@example.com"]) .cc(["manager@example.com"]) .subject("Q4 Report") .body("Please find the quarterly report attached.") .attach(OwnedFd::from(attachment)) .send() .await?; Ok(()) } ``` -------------------------------- ### ashpd::desktop::email::EmailRequest Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Wraps `org.freedesktop.portal.Email`. Opens the default mail client pre-filled with recipient(s), subject, body text, and optional file attachments passed as file descriptors. ```APIDOC ## `ashpd::desktop::email::EmailRequest` — Compose an email Wraps `org.freedesktop.portal.Email`. Opens the default mail client pre-filled with recipient(s), subject, body text, and optional file attachments passed as file descriptors. ```rust use std::{fs::File, os::fd::OwnedFd}; use ashpd::desktop::email::EmailRequest; async fn run() -> ashpd::Result<()> { let attachment = File::open("/home/user/report.pdf").unwrap(); EmailRequest::default() .address("recipient@example.com") .addresses(["cc1@example.com", "cc2@example.com"]) .cc(["manager@example.com"]) .subject("Q4 Report") .body("Please find the quarterly report attached.") .attach(OwnedFd::from(attachment)) .send() .await?; Ok(()) } ``` ``` -------------------------------- ### Add ASHPD to Cargo.toml Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Configure your Cargo.toml to include ASHPD. You can enable all frontend portals or select individual ones for a smaller binary size. ```toml [dependencies] # Enable all frontend portals + tokio runtime ashpd = { version = "0.13.0", features = ["frontend", "tokio"] } # Or enable individual portals for a smaller binary ashpd = { version = "0.13.0", features = [ "screenshot", "file_chooser", "notification", "settings", "camera", "screencast", "wallpaper", "email", "open_uri", "account", ] } ``` -------------------------------- ### Detect Sandbox Environment with `is_sandboxed` Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt Use the `is_sandboxed` function to check if the application is running within a Flatpak or Snap sandbox. The result is cached after the first call. ```rust use ashpd::is_sandboxed; fn main() { if is_sandboxed() { println!("Running inside a Flatpak/Snap sandbox — portals will be used."); } else { println!("Running outside sandbox — direct filesystem access available."); } } ``` -------------------------------- ### Error Handling Source: https://context7.com/bilelmoussaoui/ashpd/llms.txt `ashpd::Result` aliases `std::result::Result`. The `Error` enum covers DBus transport errors, portal-specific errors (`org.freedesktop.portal.Error.*`), user cancellation, version mismatches, and missing portal frontends. ```APIDOC ## Error Handling — `ashpd::Error` and `ashpd::PortalError` `ashpd::Result` aliases `std::result::Result`. The `Error` enum covers DBus transport errors, portal-specific errors (`org.freedesktop.portal.Error.*`), user cancellation, version mismatches, and missing portal frontends. ```rust use ashpd::{Error, PortalError, desktop::screenshot::Screenshot}; async fn run() { match Screenshot::request().send().await { Ok(request) => match request.response() { Ok(screenshot) => println!("Screenshot: {}", screenshot.uri()), Err(Error::Response(e)) => println!("User cancelled or dialog failed: {e:?}"), Err(e) => eprintln!("Other error: {e}"), }, Err(Error::PortalNotFound(iface)) => { eprintln!("No portal frontend for {iface} is installed"); } Err(Error::Portal(PortalError::NotAllowed(msg))) => { eprintln!("Permission denied: {msg}"); } Err(Error::RequiresVersion(required, current)) => { eprintln!("Portal version {required} required, but {current} available"); } Err(e) => eprintln!("Unexpected error: {e}"), } } ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.