Skip to main content

fotos_lib/capture/
portal.rs

1/// Wayland screenshot capture via xdg-desktop-portal (ashpd).
2///
3/// Used on GNOME Wayland, KDE Wayland, and other Wayland compositors
4/// that implement the Screenshot portal.
5use anyhow::{bail, Context, Result};
6
7pub async fn capture_via_portal() -> Result<image::DynamicImage> {
8    use ashpd::desktop::screenshot::Screenshot;
9
10    // Use interactive=true so GNOME shows a confirmation dialog on first use,
11    // which also grants the screenshot permission. Without this, GNOME's portal
12    // returns "Other" (error code 2) immediately if no permission has been stored.
13    tracing::info!("portal: sending screenshot request (interactive=true)");
14
15    let request = Screenshot::request()
16        .interactive(true)
17        .send()
18        .await
19        .map_err(|e| {
20            tracing::error!("portal: send() failed: {e}");
21            anyhow::anyhow!("Portal unavailable: {e}")
22        })?;
23
24    tracing::info!("portal: request sent, awaiting user confirmation");
25
26    let response = request.response().map_err(|e| {
27        tracing::error!("portal: response() failed: {e}");
28        anyhow::anyhow!("Screenshot portal request failed: {e}")
29    })?;
30
31    let uri = response.uri();
32    tracing::info!("portal: got URI {uri}");
33
34    if uri.scheme() != "file" {
35        bail!(
36            "Portal returned unsupported URI scheme '{}' (expected file://)",
37            uri.scheme()
38        );
39    }
40
41    let path = uri
42        .to_file_path()
43        .map_err(|_| anyhow::anyhow!("Portal URI is not a valid file path: {uri}"))?;
44
45    tracing::info!("portal: loading image from {}", path.display());
46
47    let img = image::open(&path)
48        .with_context(|| format!("Failed to load portal screenshot from {}", path.display()))?;
49
50    tracing::info!(
51        "portal: image loaded ({}x{}), cleaning up temp file",
52        img.width(),
53        img.height()
54    );
55
56    // Clean up the temp file left by the portal.
57    let _ = std::fs::remove_file(&path);
58
59    Ok(img)
60}