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 @@ - - Request - center - usb.request - + + horizontal + + + _Start Session + True + start + usb.start_session + + + + + + _Stop Session + True + false + end + true + usb.stop_session + + + 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.