diff --git a/ashpd-demo/build-aux/com.belmoussaoui.ashpd.demo.Devel.json b/ashpd-demo/build-aux/com.belmoussaoui.ashpd.demo.Devel.json
index edfa68ecf..790b01812 100644
--- a/ashpd-demo/build-aux/com.belmoussaoui.ashpd.demo.Devel.json
+++ b/ashpd-demo/build-aux/com.belmoussaoui.ashpd.demo.Devel.json
@@ -14,6 +14,7 @@
"--socket=wayland",
"--device=dri",
"--own-name=com.belmoussaoui.ashpd.demo",
+ "--usb=all",
"--env=RUST_LOG=ashpd_demo=debug,ashpd=debug",
"--env=G_MESSAGES_DEBUG=none",
"--env=RUST_BACKTRACE=1"
diff --git a/ashpd-demo/data/resources/ui/usb.ui b/ashpd-demo/data/resources/ui/usb.ui
index 6239e3abe..4f402e9ef 100644
--- a/ashpd-demo/data/resources/ui/usb.ui
+++ b/ashpd-demo/data/resources/ui/usb.ui
@@ -22,14 +22,34 @@
-
diff --git a/ashpd-demo/src/portals/desktop/usb.rs b/ashpd-demo/src/portals/desktop/usb.rs
index 395a03afd..4fcb50254 100644
--- a/ashpd-demo/src/portals/desktop/usb.rs
+++ b/ashpd-demo/src/portals/desktop/usb.rs
@@ -1,15 +1,27 @@
+// Copyright (C) 2024 GNOME Foundation
+//
+// Authors:
+// Hubert Figuière
+//
+
use std::cell::RefCell;
+use std::collections::HashMap;
+use std::sync::Arc;
use adw::{prelude::*, subclass::prelude::*};
+use futures_util::{lock::Mutex, StreamExt};
use glib::clone;
use gtk::glib;
use ashpd::{
- desktop::usb::{UsbOptions, UsbProxy},
+ desktop::{
+ usb::{UsbOptions, UsbProxy},
+ Session,
+ },
WindowIdentifier,
};
-use crate::widgets::{PortalPage, PortalPageImpl};
+use crate::widgets::{PortalPage, PortalPageExt, PortalPageImpl};
mod imp {
use super::*;
@@ -19,30 +31,31 @@ mod imp {
pub struct UsbPage {
#[template_child]
pub usb_devices: TemplateChild,
- rows: RefCell>,
+ rows: RefCell>,
+ pub session: Arc>>>,
+ pub event_source: Arc>>,
}
impl UsbPage {
- fn add(&self, row: &impl IsA) {
+ fn add(&self, id: String, row: &impl IsA) {
self.usb_devices.get().add(row.upcast_ref());
- self.rows.borrow_mut().push(row.as_ref().clone());
+ self.rows.borrow_mut().insert(id, row.as_ref().clone());
}
fn clear_devices(&self) {
for row in self.rows.borrow().iter() {
- self.usb_devices.get().remove(row);
+ self.usb_devices.get().remove(row.1);
}
self.rows.borrow_mut().clear();
}
- async fn refresh_devices(&self) {
+ pub(super) async fn refresh_devices(&self) -> ashpd::Result<()> {
let page = self.obj();
self.clear_devices();
- let usb = UsbProxy::new().await.unwrap();
- let devices = usb.enumerate_devices(UsbOptions::default()).await.unwrap();
- println!("devices {} {:?}", devices.len(), devices);
+ let usb = UsbProxy::new().await?;
+ let devices = usb.enumerate_devices(UsbOptions::default()).await?;
for device in devices {
let row = adw::ActionRow::new();
let vendor = device.1.vendor().unwrap_or_default();
@@ -51,30 +64,74 @@ mod imp {
if let Some(devnode) = device.1.device_file {
row.set_subtitle(&devnode);
}
- let activatable = gtk::Button::new();
- row.set_activatable_widget(Some(&activatable));
+ let activatable =
+ gtk::Button::from_icon_name("preferences-system-sharing-symbolic");
+ activatable.set_css_classes(&["circular"]);
+ row.add_suffix(&activatable);
+ row.add_prefix(>k::CheckButton::new());
let device_id = device.0.clone();
let device_writable = device.1.writable.unwrap_or(false);
- row.connect_activated(move |row| {
+ activatable.connect_clicked(move |row| {
glib::spawn_future_local(clone!(@strong row, @strong device_id => async move {
let root = row.native().unwrap();
let identifier = WindowIdentifier::from_native(&root).await;
let usb = UsbProxy::new().await.unwrap();
- if row.is_active() {
- usb.acquire_devices(&identifier, &[
- (&device_id, device_writable)
- ], true).await.map(|_| ());
- } else {
- usb.release_devices(&[&device_id]).await;
- }
- ()
+ // if row.is_active() {
+ println!("acquire {device_id}");
+ let result = usb.acquire_devices(&identifier, &[
+ (&device_id, device_writable)
+ ]).await;
+ println!("result: {result:?}");
+// } else {
+// let _ = usb.release_devices(&[&device_id]).await;
+// }
}));
});
- page.imp().add(&row);
+ page.imp().add(device.0.clone(), &row);
+ }
+ Ok(())
+ }
+
+ pub(super) async fn start_session(&self) -> ashpd::Result<()> {
+ let usb = UsbProxy::new().await?;
+ let session = usb.create_session().await?;
+ self.session.lock().await.replace(session);
+
+ let session = self.session.clone();
+ glib::spawn_future(async move {
+ let usb = UsbProxy::new().await?;
+ loop {
+ if session.lock().await.is_none() {
+ tracing::debug!("session is gone");
+ break;
+ }
+ if let Some(response) = usb.receive_device_events().await?.next().await {
+ let events = response.events();
+ for ev in events {
+ println!(
+ "Received event: {} for device {}",
+ ev.event_action(),
+ ev.event_device_id()
+ );
+ }
+ }
+ }
+ tracing::debug!("Loop is gone");
+ Ok::<(), ashpd::Error>(())
+ });
+
+ Ok(())
+ }
+
+ pub(super) async fn stop_session(&self) -> anyhow::Result<()> {
+ if let Some(session) = self.session.lock().await.take() {
+ session.close().await?;
}
+ Ok(())
}
}
+
#[glib::object_subclass]
impl ObjectSubclass for UsbPage {
const NAME: &'static str = "UsbPage";
@@ -85,7 +142,13 @@ mod imp {
klass.bind_template();
klass.install_action_async("usb.refresh", None, |page, _, _| async move {
- page.imp().refresh_devices().await;
+ page.refresh_devices().await
+ });
+ klass.install_action_async("usb.start_session", None, |page, _, _| async move {
+ page.start_session().await
+ });
+ klass.install_action_async("usb.stop_session", None, |page, _, _| async move {
+ page.stop_session().await;
});
}
@@ -93,16 +156,24 @@ mod imp {
obj.init_template();
}
}
- impl ObjectImpl for UsbPage {}
+
+ impl ObjectImpl for UsbPage {
+ fn constructed(&self) {
+ self.parent_constructed();
+ self.obj().action_set_enabled("usb.stop_session", false);
+ }
+ }
+
impl WidgetImpl for UsbPage {
fn map(&self) {
glib::spawn_future_local(
- clone!(@weak self as widget => async move { widget.refresh_devices().await; } ),
+ clone!(@weak self as widget => async move { widget.obj().refresh_devices().await; } ),
);
self.parent_map();
}
}
+
impl BinImpl for UsbPage {}
impl PortalPageImpl for UsbPage {}
}
@@ -112,4 +183,42 @@ glib::wrapper! {
@extends gtk::Widget, adw::Bin, PortalPage;
}
-impl UsbPage {}
+impl UsbPage {
+ async fn refresh_devices(&self) {
+ match self.imp().refresh_devices().await {
+ Ok(_) => {}
+ Err(err) => {
+ tracing::error!("Failed to refresh USB devices: {err}");
+ self.error("Failed to refresh USB devices.");
+ }
+ }
+ }
+
+ async fn start_session(&self) {
+ self.action_set_enabled("usb.start_session", false);
+ self.action_set_enabled("usb.stop_session", true);
+
+ match self.imp().start_session().await {
+ Ok(_) => self.info("USB session started"),
+ Err(err) => {
+ tracing::error!("Failed to start USB session: {err}");
+ self.error("Failed to start USB session.");
+ self.action_set_enabled("usb.start_session", true);
+ self.action_set_enabled("usb.stop_session", false);
+ }
+ }
+ }
+
+ async fn stop_session(&self) {
+ self.action_set_enabled("usb.start_session", true);
+ self.action_set_enabled("usb.stop_session", false);
+
+ match self.imp().stop_session().await {
+ Ok(_) => self.info("USB session stopped"),
+ Err(err) => {
+ tracing::error!("Failed to stop USB session: {err}");
+ self.error("Failed to stop USB session.");
+ }
+ }
+ }
+}
diff --git a/src/desktop/usb.rs b/src/desktop/usb.rs
index eb25bf4d6..24d9b6bdd 100644
--- a/src/desktop/usb.rs
+++ b/src/desktop/usb.rs
@@ -32,12 +32,6 @@ struct CreateSessionOptions {
session_handle_token: HandleToken,
}
-#[derive(Debug, DeserializeDict, Type)]
-#[zvariant(signature = "dict")]
-struct CreateSessionResponse {
- session_handle: OwnedObjectPath,
-}
-
#[derive(Debug, SerializeDict, Type, Default)]
#[zvariant(signature = "dict")]
struct AcquireDevicesOptions {
@@ -188,15 +182,8 @@ impl<'a> UsbProxy<'a> {
///
pub async fn create_session(&self) -> Result, Error> {
let options = CreateSessionOptions::default();
- let (_, session) = futures_util::try_join!(
- self.0.request::(
- &options.handle_token,
- "CreateSession",
- &options
- ).into_future(),
- Session::from_unique_name(&options.session_handle_token).into_future()
- )?;
- Ok(session)
+ let session: OwnedObjectPath = self.0.call("CreateSession", &(&options)).await?;
+ Session::new(session).await
}
/// Enumerate USB devices.