use auv_driver_common::permission::{PermissionProbe, PermissionStatus};
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PortalInterfaceProbe {
pub available: PermissionStatus,
pub version: Option<u32>,
pub details: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct LinuxPortalProbe {
pub wayland_session: PermissionStatus,
pub session_type: Option<String>,
pub desktop: Option<String>,
pub portal_bus: PermissionStatus,
pub screencast: PortalInterfaceProbe,
pub remote_desktop: PortalInterfaceProbe,
pub screenshot: PortalInterfaceProbe,
}
impl LinuxPortalProbe {
pub fn as_permission_probe(&self) -> PermissionProbe {
PermissionProbe {
screen_recording: if self.screencast.available == PermissionStatus::Missing {
PermissionStatus::Missing
} else {
PermissionStatus::Unknown
},
screen_capture_kit: PermissionStatus::Unknown,
accessibility: PermissionStatus::Unknown,
automation_to_system_events: if self.remote_desktop.available == PermissionStatus::Missing {
PermissionStatus::Missing
} else {
PermissionStatus::Unknown
},
}
}
}
pub fn probe_portals() -> LinuxPortalProbe {
let session_type = std::env::var("XDG_SESSION_TYPE").ok();
let desktop = std::env::var("XDG_CURRENT_DESKTOP").ok().or_else(|| std::env::var("DESKTOP_SESSION").ok());
let wayland_session =
if session_type.as_deref().is_some_and(|value| value.eq_ignore_ascii_case("wayland")) || std::env::var_os("WAYLAND_DISPLAY").is_some() {
PermissionStatus::Granted
} else {
PermissionStatus::Missing
};
#[cfg(target_os = "linux")]
let (portal_bus, screencast, remote_desktop, screenshot) = probe_portal_bus();
#[cfg(not(target_os = "linux"))]
let (portal_bus, screencast, remote_desktop, screenshot) = (
PermissionStatus::Missing,
PortalInterfaceProbe {
available: PermissionStatus::Missing,
version: None,
details: Some("not running on Linux".to_string()),
},
PortalInterfaceProbe {
available: PermissionStatus::Missing,
version: None,
details: Some("not running on Linux".to_string()),
},
PortalInterfaceProbe {
available: PermissionStatus::Missing,
version: None,
details: Some("not running on Linux".to_string()),
},
);
LinuxPortalProbe {
wayland_session,
session_type,
desktop,
portal_bus,
screencast,
remote_desktop,
screenshot,
}
}
#[cfg(target_os = "linux")]
fn probe_portal_bus() -> (PermissionStatus, PortalInterfaceProbe, PortalInterfaceProbe, PortalInterfaceProbe) {
use ashpd::desktop::{remote_desktop::RemoteDesktop, screencast::Screencast, screenshot::ScreenshotProxy};
match crate::native::portal::session_connection(None) {
Ok(connection) => {
let screencast = probe_interface(async {
let proxy = Screencast::with_connection(connection.clone()).await?;
Ok(proxy.get_property::<u32>("version").await?)
});
let remote_desktop = probe_interface(async {
let proxy = RemoteDesktop::with_connection(connection.clone()).await?;
Ok(proxy.get_property::<u32>("version").await?)
});
let screenshot = probe_interface(async {
let proxy = ScreenshotProxy::with_connection(connection).await?;
Ok(proxy.get_property::<u32>("version").await?)
});
(PermissionStatus::Granted, screencast, remote_desktop, screenshot)
}
Err(error) => {
let missing = PortalInterfaceProbe {
available: PermissionStatus::Unknown,
version: None,
details: Some(error.to_string()),
};
(PermissionStatus::Unknown, missing.clone(), missing.clone(), missing)
}
}
}
#[cfg(target_os = "linux")]
fn probe_interface(probe: impl std::future::Future<Output = ashpd::Result<u32>>) -> PortalInterfaceProbe {
match crate::native::portal::run("probe Portal interface", async { Ok(probe.await) }) {
Ok(Ok(version)) => PortalInterfaceProbe {
available: PermissionStatus::Granted,
version: Some(version),
details: Some("interface available; user authorization has not been tested".into()),
},
Ok(Err(error)) => PortalInterfaceProbe {
available: if matches!(error, ashpd::Error::PortalNotFound(_)) {
PermissionStatus::Missing
} else {
PermissionStatus::Unknown
},
version: None,
details: Some(error.to_string()),
},
Err(error) => PortalInterfaceProbe {
available: PermissionStatus::Unknown,
version: None,
details: Some(error.to_string()),
},
}
}
#[cfg(test)]
#[path = "permission_test.rs"]
mod tests;
#[cfg(target_os = "linux")]
#[zbus::proxy(
interface = "org.freedesktop.impl.portal.PermissionStore",
default_service = "org.freedesktop.impl.portal.PermissionStore",
default_path = "/org/freedesktop/impl/portal/PermissionStore",
gen_async = false
)]
trait PermissionStore {
fn lookup(&self, table: &str, id: &str) -> zbus::Result<(std::collections::HashMap<String, Vec<String>>, zbus::zvariant::OwnedValue)>;
fn set_permission(&self, table: &str, create: bool, id: &str, app: &str, permissions: &[&str]) -> zbus::Result<()>;
}
#[cfg(target_os = "linux")]
pub fn verify_portal_identity(app_id: &str) -> auv_driver_common::DriverResult<()> {
let app_id = app_id.parse::<ashpd::AppID>().map_err(|error| crate::error::invalid_input(error.to_string()))?;
let _connection = crate::native::portal::session_connection(Some(&app_id))?;
Ok(())
}
#[cfg(target_os = "linux")]
pub fn kde_authorization(app_id: &str) -> auv_driver_common::DriverResult<PermissionStatus> {
let app_id = app_id.parse::<ashpd::AppID>().map_err(|error| crate::error::invalid_input(error.to_string()))?;
let connection = zbus::blocking::connection::Builder::session()
.and_then(|builder| builder.method_timeout(std::time::Duration::from_secs(3)).build())
.map_err(|error| crate::error::backend(error.to_string()))?;
let proxy = PermissionStoreProxy::new(&connection).map_err(|error| crate::error::backend(error.to_string()))?;
let result = proxy.lookup("kde-authorized", "remote-desktop");
match result {
Ok((permissions, _)) => Ok(if permissions.get::<str>(app_id.as_ref()).is_some_and(|values| values.iter().any(|value| value == "yes")) {
PermissionStatus::Granted
} else {
PermissionStatus::Missing
}),
Err(zbus::Error::MethodError(name, _, _)) if name.as_str() == "org.freedesktop.portal.Error.NotFound" => Ok(PermissionStatus::Missing),
Err(error) => Err(crate::error::backend(format!("failed to read KDE application authorization: {error}"))),
}
}
#[cfg(target_os = "linux")]
pub fn set_kde_authorization(app_id: &str, allow: bool) -> auv_driver_common::DriverResult<()> {
let app_id = app_id.parse::<ashpd::AppID>().map_err(|error| crate::error::invalid_input(error.to_string()))?;
let connection = zbus::blocking::connection::Builder::session()
.and_then(|builder| builder.method_timeout(std::time::Duration::from_secs(3)).build())
.map_err(|error| crate::error::backend(error.to_string()))?;
let proxy = PermissionStoreProxy::new(&connection).map_err(|error| crate::error::backend(error.to_string()))?;
let permissions: Vec<&str> = if allow { vec!["yes"] } else { vec![] };
proxy
.set_permission("kde-authorized", true, "remote-desktop", app_id.as_ref(), &permissions)
.map_err(|error| crate::error::backend(format!("failed to update KDE application authorization: {error}")))
}