use std::collections::HashMap;
use futures_util::Stream;
use serde::{Deserialize, Serialize};
use zbus::zvariant::{
ObjectPath, OwnedFd, OwnedObjectPath, OwnedValue, Type,
as_value::{self, optional},
};
use crate::{
Error, WindowIdentifier,
desktop::{CreateSessionOptions, HandleToken, Session, SessionPortal},
proxy::Proxy,
};
#[derive(Debug, Clone, Hash, Serialize, Deserialize, Type, PartialEq, Eq)]
#[zvariant(signature = "s")]
pub struct DeviceID(String);
impl DeviceID {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for DeviceID {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for DeviceID {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<String> for DeviceID {
fn from(s: String) -> Self {
Self(s)
}
}
impl std::fmt::Display for DeviceID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct EnumerateDevicesOptions {}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct ReleaseDevicesOptions {}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
struct FinishAcquireDevicesOptions {}
#[derive(Serialize, Deserialize, Clone, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct UsbDevice {
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
parent: Option<DeviceID>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
readable: Option<bool>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
writable: Option<bool>,
#[serde(
default,
rename = "device-file",
with = "optional",
skip_serializing_if = "Option::is_none"
)]
device_file: Option<String>,
#[serde(default, with = "as_value", skip_serializing_if = "HashMap::is_empty")]
properties: HashMap<String, OwnedValue>,
}
impl UsbDevice {
pub fn parent(&self) -> Option<&DeviceID> {
self.parent.as_ref()
}
pub fn is_readable(&self) -> bool {
self.readable.unwrap_or(false)
}
pub fn is_writable(&self) -> bool {
self.writable.unwrap_or(false)
}
pub fn device_file(&self) -> Option<&str> {
self.device_file.as_deref()
}
pub fn vendor(&self) -> Option<String> {
self.properties
.get("ID_VENDOR_FROM_DATABASE")
.or_else(|| self.properties.get("ID_VENDOR_ENC"))
.and_then(|v| v.downcast_ref::<String>().ok())
}
pub fn model(&self) -> Option<String> {
self.properties
.get("ID_MODEL_FROM_DATABASE")
.or_else(|| self.properties.get("ID_MODEL_ENC"))
.and_then(|v| v.downcast_ref::<String>().ok())
.map(|model| {
model
.replace("\\x20", " ") .split_whitespace()
.collect::<Vec<_>>()
.join(" ")
})
}
pub fn properties(&self) -> &HashMap<String, OwnedValue> {
&self.properties
}
}
#[derive(Debug)]
pub struct UsbError(pub Option<String>);
impl std::fmt::Display for UsbError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_deref().unwrap_or(""))
}
}
impl std::error::Error for UsbError {}
impl From<AcquiredDevice> for Result<OwnedFd, UsbError> {
fn from(v: AcquiredDevice) -> Result<OwnedFd, UsbError> {
if let Some(fd) = v.fd {
if v.success {
Ok(fd)
} else {
Err(UsbError(v.error))
}
} else {
Err(UsbError(None))
}
}
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
struct AcquireDevice {
#[serde(with = "as_value")]
writable: bool,
}
pub struct Device(DeviceID, bool );
impl Device {
pub fn new(id: DeviceID, writable: bool) -> Self {
Self(id, writable)
}
pub fn id(&self) -> &DeviceID {
&self.0
}
pub fn is_writable(&self) -> bool {
self.1
}
}
#[derive(Serialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct AcquireDevicesOptions {
#[serde(with = "as_value")]
handle_token: HandleToken,
}
#[derive(Deserialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
struct AcquiredDevice {
#[serde(with = "as_value")]
success: bool,
#[serde(default, with = "optional")]
fd: Option<OwnedFd>,
#[serde(default, with = "optional")]
error: Option<String>,
}
#[derive(Debug, Deserialize, Type)]
pub struct UsbEvent(UsbEventAction, DeviceID, UsbDevice);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Type)]
#[zvariant(signature = "s")]
#[serde(rename_all = "lowercase")]
pub enum UsbEventAction {
Add,
Change,
Remove,
}
impl UsbEvent {
pub fn action(&self) -> UsbEventAction {
self.0
}
pub fn device_id(&self) -> &DeviceID {
&self.1
}
pub fn device(&self) -> &UsbDevice {
&self.2
}
}
#[derive(Debug, Deserialize, Type)]
pub struct UsbDeviceEvent(OwnedObjectPath, Vec<UsbEvent>);
impl UsbDeviceEvent {
pub fn session_handle(&self) -> ObjectPath<'_> {
self.0.as_ref()
}
pub fn events(&self) -> &[UsbEvent] {
&self.1
}
}
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.Usb")]
pub struct UsbProxy(Proxy<'static>);
impl UsbProxy {
pub async fn new() -> Result<Self, Error> {
let proxy = Proxy::new_desktop("org.freedesktop.portal.Usb").await?;
Ok(Self(proxy))
}
pub async fn with_connection(connection: zbus::Connection) -> Result<Self, Error> {
let proxy =
Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Usb").await?;
Ok(Self(proxy))
}
pub fn version(&self) -> u32 {
self.0.version()
}
#[doc(alias = "CreateSession")]
pub async fn create_session(
&self,
options: CreateSessionOptions,
) -> Result<Session<Self>, Error> {
let session: OwnedObjectPath = self.0.call("CreateSession", &(&options)).await?;
Session::with_connection(self.0.connection().clone(), session).await
}
#[doc(alias = "EnumerateDevices")]
pub async fn enumerate_devices(
&self,
options: EnumerateDevicesOptions,
) -> Result<Vec<(DeviceID, UsbDevice)>, Error> {
self.0.call("EnumerateDevices", &(&options)).await
}
#[doc(alias = "AcquireDevices")]
pub async fn acquire_devices(
&self,
parent_window: Option<&WindowIdentifier>,
devices: &[Device],
options: AcquireDevicesOptions,
) -> Result<Vec<(DeviceID, Result<OwnedFd, UsbError>)>, Error> {
let parent_window = parent_window.map(|i| i.to_string()).unwrap_or_default();
let acquire_devices: Vec<(DeviceID, AcquireDevice)> = devices
.iter()
.map(|dev| {
let device = AcquireDevice { writable: dev.1 };
(dev.id().clone(), device)
})
.collect();
let request = self
.0
.empty_request(
&options.handle_token,
"AcquireDevices",
&(&parent_window, &acquire_devices, &options),
)
.await?;
let mut devices: Vec<(DeviceID, Result<OwnedFd, UsbError>)> = vec![];
if request.response().is_ok() {
let path = request.path();
loop {
let (mut new_devices, finished) = self
.finish_acquire_devices(path, Default::default())
.await?;
devices.append(&mut new_devices);
if finished {
break;
}
}
}
Ok(devices)
}
#[doc(alias = "FinishAcquireDevices")]
async fn finish_acquire_devices(
&self,
request_path: &ObjectPath<'_>,
options: FinishAcquireDevicesOptions,
) -> Result<(Vec<(DeviceID, Result<OwnedFd, UsbError>)>, bool), Error> {
self.0
.call("FinishAcquireDevices", &(request_path, &options))
.await
.map(|result: (Vec<(DeviceID, AcquiredDevice)>, bool)| {
let finished = result.1;
(
result
.0
.into_iter()
.map(|item| (item.0, item.1.into()))
.collect::<Vec<_>>(),
finished,
)
})
}
#[doc(alias = "ReleaseDevices")]
pub async fn release_devices(
&self,
devices: &[&DeviceID],
options: ReleaseDevicesOptions,
) -> Result<(), Error> {
self.0.call("ReleaseDevices", &(devices, &options)).await
}
#[doc(alias = "DeviceEvents")]
pub async fn receive_device_events(
&self,
) -> Result<impl Stream<Item = UsbDeviceEvent> + use<>, Error> {
self.0.signal("DeviceEvents").await
}
}
impl crate::Sealed for UsbProxy {}
impl SessionPortal for UsbProxy {}