diff --git a/ashpd-demo/data/resources.gresource.xml b/ashpd-demo/data/resources.gresource.xml index 23f573667..e429776f9 100644 --- a/ashpd-demo/data/resources.gresource.xml +++ b/ashpd-demo/data/resources.gresource.xml @@ -27,6 +27,7 @@ resources/ui/screencast.ui resources/ui/secret.ui resources/ui/usb.ui + resources/ui/usbdevicerow.ui resources/ui/wallpaper.ui resources/style.css diff --git a/ashpd-demo/data/resources/ui/usbdevicerow.ui b/ashpd-demo/data/resources/ui/usbdevicerow.ui new file mode 100644 index 000000000..521bb1bc3 --- /dev/null +++ b/ashpd-demo/data/resources/ui/usbdevicerow.ui @@ -0,0 +1,18 @@ + + + + diff --git a/ashpd-demo/src/portals/desktop/usb.rs b/ashpd-demo/src/portals/desktop/usb.rs index 8d0a6c5d6..729695ef1 100644 --- a/ashpd-demo/src/portals/desktop/usb.rs +++ b/ashpd-demo/src/portals/desktop/usb.rs @@ -26,47 +26,87 @@ use crate::widgets::{PortalPage, PortalPageExt, PortalPageImpl}; const SHARE_ICON: &str = "preferences-system-sharing-symbolic"; const UNSHARE_ICON: &str = "view-restore-symbolic"; -#[derive(Debug)] -struct DeviceRow { - row: adw::ActionRow, - checkbox: gtk::CheckButton, - acquire: gtk::Button, +glib::wrapper! { + pub struct DeviceRow(ObjectSubclass) + @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow; } impl DeviceRow { + fn new() -> Self { + glib::Object::new() + } + + fn is_active(&self) -> bool { + self.imp().checkbox.is_active() + } + fn acquire(&self) { - self.checkbox.set_active(true); - self.acquire.set_icon_name(UNSHARE_ICON); + self.imp().checkbox.set_active(true); + self.imp().acquire.set_icon_name(UNSHARE_ICON); } fn release(&self) { - self.checkbox.set_active(false); - self.acquire.set_icon_name(SHARE_ICON); + self.imp().checkbox.set_active(false); + self.imp().acquire.set_icon_name(SHARE_ICON); + } + + fn connect_share_clicked(&self, f: F) -> glib::SignalHandlerId { + self.imp().acquire.connect_clicked(f) } } mod imp { use super::*; + #[derive(Debug, gtk::CompositeTemplate, Default)] + #[template(resource = "/com/belmoussaoui/ashpd/demo/usbdevicerow.ui")] + pub struct DeviceRow { + #[template_child] + pub(super) checkbox: TemplateChild, + #[template_child] + pub(super) acquire: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for DeviceRow { + const NAME: &'static str = "DeviceRow"; + type Type = super::DeviceRow; + type ParentType = adw::ActionRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for DeviceRow {} + impl WidgetImpl for DeviceRow {} + impl ListBoxRowImpl for DeviceRow {} + impl PreferencesRowImpl for DeviceRow {} + impl ActionRowImpl for DeviceRow {} + #[derive(Debug, gtk::CompositeTemplate, Default)] #[template(resource = "/com/belmoussaoui/ashpd/demo/usb.ui")] 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, uuid: String, row: DeviceRow) { - self.usb_devices.get().add(&row.row); + fn add(&self, uuid: String, row: super::DeviceRow) { + self.usb_devices.get().add(&row); self.rows.borrow_mut().insert(uuid, row); } fn clear_devices(&self) { for row in self.rows.borrow().values() { - self.usb_devices.get().remove(&row.row); + self.usb_devices.get().remove(row); } self.rows.borrow_mut().clear(); } @@ -91,35 +131,26 @@ mod imp { let usb = UsbProxy::new().await?; let devices = usb.enumerate_devices(UsbOptions::default()).await?; for device in devices { - let activatable = gtk::Button::from_icon_name(SHARE_ICON); - let row = DeviceRow { - row: adw::ActionRow::new(), - checkbox: gtk::CheckButton::new(), - acquire: activatable.clone(), - }; + let row = super::DeviceRow::new(); let vendor = device.1.vendor().unwrap_or_default(); let dev = device.1.model().unwrap_or_default(); - row.row.set_title(&format!("{} {}", &vendor, &dev)); + row.set_title(&format!("{} {}", &vendor, &dev)); if let Some(devnode) = device.1.device_file { - row.row.set_subtitle(&devnode); + row.set_subtitle(&devnode); } - activatable.set_css_classes(&["circular"]); - row.row.add_suffix(&activatable); - row.checkbox.set_sensitive(false); - row.row.add_prefix(&row.checkbox); let device_id = device.0.clone(); let device_writable = device.1.writable.unwrap_or(false); - activatable.connect_clicked(clone!(@strong page => move |row| { + row.connect_share_clicked(clone!(@strong page => move |row| { glib::spawn_future_local(clone!(@strong row, @strong device_id, @strong page => async move { let root = row.native().unwrap(); let identifier = WindowIdentifier::from_native(&root).await; let usb = UsbProxy::new().await.unwrap(); - let active = page.imp().rows.borrow().get(&device_id).map(|row| row.checkbox.is_active()).unwrap_or(false); + let active = page.imp().rows.borrow().get(&device_id).map(|row| row.is_active()).unwrap_or(false); if !active { let result = usb.acquire_devices(&identifier, &[ (&device_id, device_writable) - ]).await; + ]).await; match result { Ok(_) => { loop {