Skip to content

Commit

Permalink
WIP: demo: add start - stop session
Browse files Browse the repository at this point in the history
Signed-off-by: Hubert Figuière <[email protected]>
  • Loading branch information
hfiguiere committed May 20, 2024
1 parent ca0b572 commit 2395600
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
36 changes: 28 additions & 8 deletions ashpd-demo/data/resources/ui/usb.ui
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,34 @@
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Request</property>
<property name="halign">center</property>
<property name="action-name">usb.request</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<child>
<object class="GtkButton">
<property name="label">_Start Session</property>
<property name="use-underline">True</property>
<property name="halign">start</property>
<property name="action-name">usb.start_session</property>
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">_Stop Session</property>
<property name="use-underline">True</property>
<property name="sensitive">false</property>
<property name="halign">end</property>
<property name="hexpand">true</property>
<property name="action-name">usb.stop_session</property>
<style>
<class name="pill" />
<class name="destructive-action" />
</style>
</object>
</child>
</object>
</child>
</object>
Expand Down
161 changes: 135 additions & 26 deletions ashpd-demo/src/portals/desktop/usb.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
// Copyright (C) 2024 GNOME Foundation
//
// Authors:
// Hubert Figuière <[email protected]>
//

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::*;
Expand All @@ -19,30 +31,31 @@ mod imp {
pub struct UsbPage {
#[template_child]
pub usb_devices: TemplateChild<adw::PreferencesGroup>,
rows: RefCell<Vec<adw::PreferencesRow>>,
rows: RefCell<HashMap<String, adw::PreferencesRow>>,
pub session: Arc<Mutex<Option<Session<'static>>>>,
pub event_source: Arc<Mutex<Option<glib::SourceId>>>,
}

impl UsbPage {
fn add(&self, row: &impl IsA<adw::PreferencesRow>) {
fn add(&self, id: String, row: &impl IsA<adw::PreferencesRow>) {
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();
Expand All @@ -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(&gtk::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";
Expand All @@ -85,24 +142,38 @@ 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;
});
}

fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
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 {}
}
Expand All @@ -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.");
}
}
}
}
17 changes: 2 additions & 15 deletions src/desktop/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -188,15 +182,8 @@ impl<'a> UsbProxy<'a> {
///
pub async fn create_session(&self) -> Result<Session<'a>, Error> {
let options = CreateSessionOptions::default();
let (_, session) = futures_util::try_join!(
self.0.request::<CreateSessionResponse>(
&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.
Expand Down

0 comments on commit 2395600

Please sign in to comment.