From 580571c137929b3cd989ce0ab32135e3bef9d8be Mon Sep 17 00:00:00 2001 From: nozwock <57829219+nozwock@users.noreply.github.com> Date: Sat, 22 Nov 2025 11:21:29 +0530 Subject: [PATCH 1/4] Expose DBus API with DeviceVisibility property --- Cargo.lock | 7 +++++ Cargo.toml | 1 + data/meson.build | 11 +++++++ meson.build | 4 ++- src/config.rs.in | 3 ++ src/dbus.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/window.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 src/dbus.rs diff --git a/Cargo.lock b/Cargo.lock index cbcd922d..a42f7ca8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -503,6 +503,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-str" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4d34b8f066904ed7cfa4a6f9ee96c3214aa998cb44b69ca20bd2054f47402ed" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2132,6 +2138,7 @@ dependencies = [ "ashpd", "async-channel", "better_default", + "const-str", "dirs", "formatx", "fs-err", diff --git a/Cargo.toml b/Cargo.toml index 4e2ae37a..e0aa8e03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,3 +43,4 @@ ashpd = { version = "0.12", default-features = false, features = [ futures-timer = "3.0.3" tokio-util = "0.7.15" tracing-appender = "0.2.3" +const-str = "0.7.0" diff --git a/data/meson.build b/data/meson.build index e1a3127f..f9e02028 100644 --- a/data/meson.build +++ b/data/meson.build @@ -99,3 +99,14 @@ configure_file( install: true, install_dir: datadir / 'dbus-1' / 'services' ) + +service_conf = configuration_data() +service_conf.set('app-id', api_id) +service_conf.set('bindir', bindir) +configure_file( + input: '@0@.service.in'.format(base_id), + output: '@0@.service'.format(api_id), + configuration: service_conf, + install: true, + install_dir: datadir / 'dbus-1' / 'services' +) diff --git a/meson.build b/meson.build index f79439b2..60f7dbb2 100644 --- a/meson.build +++ b/meson.build @@ -48,6 +48,8 @@ else application_id = base_id endif +api_id = application_id + '.Api' + meson.add_dist_script( 'build-aux/dist-vendor.sh', meson.project_build_root() / 'meson-dist' / meson.project_name() @@ -70,4 +72,4 @@ gnome.post_install( gtk_update_icon_cache: true, glib_compile_schemas: true, update_desktop_database: true, -) +) \ No newline at end of file diff --git a/src/config.rs.in b/src/config.rs.in index f4bbb430..67d0832e 100644 --- a/src/config.rs.in +++ b/src/config.rs.in @@ -6,3 +6,6 @@ pub const PKGDATADIR: &str = @PKGDATADIR@; pub const PROFILE: &str = @PROFILE@; pub const RESOURCES_FILE: &str = concat!(@PKGDATADIR@, "/resources.gresource"); pub const VERSION: &str = @VERSION@; + +pub const DBUS_API_NAME: &str = const_str::concat!(APP_ID, ".Api"); +pub const DBUS_API_PATH: &str = const_str::concat!("/", const_str::replace!(APP_ID, ".", "/")); diff --git a/src/dbus.rs b/src/dbus.rs new file mode 100644 index 00000000..0221aef2 --- /dev/null +++ b/src/dbus.rs @@ -0,0 +1,80 @@ +use std::sync::{Arc, LazyLock}; + +use tokio::sync::{ + Mutex, + mpsc::{Receiver, Sender, channel}, +}; +use zbus::{Connection, object_server::InterfaceRef}; + +use crate::config::{DBUS_API_NAME, DBUS_API_PATH}; + +#[derive(Debug)] +pub struct Packet { + pub visibility: bool, + visibility_tx: Arc>>, + pub visibility_rx: Arc>>, +} + +#[zbus::interface(name = "io.github.nozwock.Packet1")] +impl Packet { + #[zbus(property)] + pub async fn device_visibility(&self) -> bool { + self.visibility + } + + /// Also sends the param to the channel associated with `visibility_tx`. + #[zbus(property)] + pub async fn set_device_visibility(&mut self, visibility: bool) { + self.visibility = visibility; + _ = self + .visibility_tx + .lock() + .await + .send(visibility) + .await + .inspect_err(|err| tracing::warn!(%err)); + } +} + +static CONNECTION: LazyLock>> = LazyLock::new(|| Mutex::new(None)); + +pub async fn get_connection() -> Option { + CONNECTION.lock().await.as_ref().cloned() +} + +pub async fn create_connection(visibility: bool) -> anyhow::Result { + let mut conn_guard = CONNECTION.lock().await; + + if let Some(conn) = conn_guard.as_ref() { + Ok(conn.clone()) + } else { + let (tx, rx) = channel::(1); + let conn = zbus::connection::Builder::session()? + .name(DBUS_API_NAME)? + .serve_at( + DBUS_API_PATH, + Packet { + visibility: visibility, + visibility_tx: Arc::new(Mutex::new(tx)), + visibility_rx: Arc::new(Mutex::new(rx)), + }, + )? + .build() + .await?; + *conn_guard = Some(conn.clone()); + + Ok(conn) + } +} + +/// # Panics +/// Panics if `CONNECTION` is `None`. +pub async fn packet_iface() -> InterfaceRef { + get_connection() + .await + .expect("Session should be created before getting iface") + .object_server() + .interface::<_, Packet>(DBUS_API_PATH) + .await + .expect("Interface should be on the object path") +} diff --git a/src/main.rs b/src/main.rs index 3f9bec41..ecee691c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod application; #[rustfmt::skip] mod config; mod constants; +mod dbus; mod ext; mod monitors; mod objects; diff --git a/src/window.rs b/src/window.rs index 8f832a95..d0eedcf0 100644 --- a/src/window.rs +++ b/src/window.rs @@ -19,6 +19,7 @@ use tokio_util::sync::CancellationToken; use crate::application::PacketApplication; use crate::config::{APP_ID, PROFILE}; use crate::constants::packet_log_path; +use crate::dbus; use crate::ext::MessageExt; use crate::objects::{self, SendRequestState}; use crate::objects::{TransferState, UserAction}; @@ -171,6 +172,7 @@ mod imp { pub is_mdns_discovery_on: Rc>, pub looping_async_tasks: RefCell>, + pub dbus_async_tasks: RefCell>, pub is_background_allowed: Cell, pub should_quit: Cell, @@ -216,6 +218,7 @@ mod imp { obj.setup_notification_actions_monitor(); obj.setup_rqs_service(); obj.request_background_at_start(); + obj.setup_dbus_api(); } } @@ -265,9 +268,16 @@ mod imp { // Abort all looping tasks before closing tracing::info!( - count = self.looping_async_tasks.borrow().len(), + rqs_tasks = self.looping_async_tasks.borrow().len(), + dbus_tasks = self.dbus_async_tasks.borrow().len(), "Cancelling looping tasks" ); + while let Some(join_handle) = self.dbus_async_tasks.borrow_mut().pop() { + match join_handle { + LoopingTaskHandle::Tokio(join_handle) => join_handle.abort(), + LoopingTaskHandle::Glib(join_handle) => join_handle.abort(), + } + } while let Some(join_handle) = self.looping_async_tasks.borrow_mut().pop() { match join_handle { LoopingTaskHandle::Tokio(join_handle) => join_handle.abort(), @@ -1849,6 +1859,70 @@ impl PacketApplicationWindow { handle } + fn setup_dbus_api(&self) { + let obj = self.clone(); + + let inner = async move || -> anyhow::Result<()> { + let imp = obj.imp(); + + _ = dbus::create_connection(imp.settings.boolean("device-visibility")).await?; + + let handle = glib::spawn_future_local(clone!( + #[weak] + imp, + async move { + // IMPORTANT: Keep notice of get() and get_mut() so that it doesn't lead to deadlock + let mut visibility_rx = { + let iface_ref = dbus::packet_iface().await; + iface_ref + .get() + .await + .visibility_rx + .clone() + .lock_owned() + .await + }; + + while let Some(extern_visibility) = visibility_rx.recv().await { + _ = imp + .settings + .set_boolean("device-visibility", extern_visibility) + .inspect_err(|err| tracing::warn!(%err)); + } + } + )); + imp.dbus_async_tasks + .borrow_mut() + .push(LoopingTaskHandle::Glib(handle)); + + imp.settings + .connect_changed(Some("device-visibility"), move |settings, key| { + let key = key.to_string(); + glib::spawn_future_local(clone!( + #[weak] + settings, + async move { + let iface_ref = dbus::packet_iface().await; + let mut iface = iface_ref.get_mut().await; + iface.visibility = settings.boolean(&key); + _ = iface + .device_visibility_changed(iface_ref.signal_emitter()) + .await + .inspect_err(|err| tracing::warn!(%err)); + } + )); + }); + + Ok(()) + }; + + glib::spawn_future_local(async move { + _ = inner() + .await + .inspect_err(|err| tracing::error!(%err, "Failed to setup DBus API")); + }); + } + fn setup_connection_monitors(&self) { let imp = self.imp(); From f533e5c224f963af57fa8b7218659e72d04340b6 Mon Sep 17 00:00:00 2001 From: nozwock <57829219+nozwock@users.noreply.github.com> Date: Sat, 22 Nov 2025 12:33:45 +0530 Subject: [PATCH 2/4] Fix crash on visibility change when RQS is not setup An edge case with the exposed DBus API. --- src/window.rs | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/window.rs b/src/window.rs index d0eedcf0..d1b017b1 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1505,6 +1505,24 @@ impl PacketApplicationWindow { )); } + async fn on_visibility_changed(&self, visibility: bool) { + let imp = self.imp(); + + self.bottom_bar_status_indicator_ui_update(visibility); + + if let Some(rqs) = imp.rqs.lock().await.as_mut() { + let visibility = if visibility { + rqs_lib::Visibility::Visible + } else { + rqs_lib::Visibility::Invisible + }; + + rqs.change_visibility(visibility); + } else { + tracing::warn!("Couldn't set device visibility due RQS not being set"); + } + } + fn bottom_bar_status_indicator_ui_update(&self, is_visible: bool) { let imp = self.imp(); @@ -1588,23 +1606,15 @@ impl PacketApplicationWindow { #[weak] imp, move |obj| { - imp.obj() - .bottom_bar_status_indicator_ui_update(obj.is_active()); - - let visibility = if obj.is_active() { - rqs_lib::Visibility::Visible - } else { - rqs_lib::Visibility::Invisible - }; - - glib::spawn_future_local(async move { - imp.rqs - .lock() - .await - .as_mut() - .unwrap() - .change_visibility(visibility); - }); + glib::spawn_future_local(clone!( + #[weak] + imp, + #[weak] + obj, + async move { + imp.obj().on_visibility_changed(obj.is_active()).await; + } + )); } )); } From 751d54908cd20ca67c4faf2df22f50d1177e3fac Mon Sep 17 00:00:00 2001 From: nozwock <57829219+nozwock@users.noreply.github.com> Date: Sat, 22 Nov 2025 12:53:30 +0530 Subject: [PATCH 3/4] Expose DeviceName property to DBus API --- src/dbus.rs | 14 ++++++++++++-- src/window.rs | 52 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/dbus.rs b/src/dbus.rs index 0221aef2..c0666183 100644 --- a/src/dbus.rs +++ b/src/dbus.rs @@ -10,6 +10,7 @@ use crate::config::{DBUS_API_NAME, DBUS_API_PATH}; #[derive(Debug)] pub struct Packet { + pub device_name: String, pub visibility: bool, visibility_tx: Arc>>, pub visibility_rx: Arc>>, @@ -17,6 +18,11 @@ pub struct Packet { #[zbus::interface(name = "io.github.nozwock.Packet1")] impl Packet { + #[zbus(property)] + pub async fn device_name(&self) -> &str { + &self.device_name + } + #[zbus(property)] pub async fn device_visibility(&self) -> bool { self.visibility @@ -42,7 +48,10 @@ pub async fn get_connection() -> Option { CONNECTION.lock().await.as_ref().cloned() } -pub async fn create_connection(visibility: bool) -> anyhow::Result { +pub async fn create_connection( + device_name: String, + visibility: bool, +) -> anyhow::Result { let mut conn_guard = CONNECTION.lock().await; if let Some(conn) = conn_guard.as_ref() { @@ -54,7 +63,8 @@ pub async fn create_connection(visibility: bool) -> anyhow::Result { .serve_at( DBUS_API_PATH, Packet { - visibility: visibility, + device_name, + visibility, visibility_tx: Arc::new(Mutex::new(tx)), visibility_rx: Arc::new(Mutex::new(rx)), }, diff --git a/src/window.rs b/src/window.rs index d1b017b1..174d9b5f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1875,7 +1875,11 @@ impl PacketApplicationWindow { let inner = async move || -> anyhow::Result<()> { let imp = obj.imp(); - _ = dbus::create_connection(imp.settings.boolean("device-visibility")).await?; + _ = dbus::create_connection( + obj.get_device_name_state().as_str().to_string(), + imp.settings.boolean("device-visibility"), + ) + .await?; let handle = glib::spawn_future_local(clone!( #[weak] @@ -1906,21 +1910,37 @@ impl PacketApplicationWindow { .push(LoopingTaskHandle::Glib(handle)); imp.settings - .connect_changed(Some("device-visibility"), move |settings, key| { - let key = key.to_string(); - glib::spawn_future_local(clone!( - #[weak] - settings, - async move { - let iface_ref = dbus::packet_iface().await; - let mut iface = iface_ref.get_mut().await; - iface.visibility = settings.boolean(&key); - _ = iface - .device_visibility_changed(iface_ref.signal_emitter()) - .await - .inspect_err(|err| tracing::warn!(%err)); - } - )); + .connect_changed(None, move |settings, key| match key { + "device-visibility" | "device-name" => { + let key = key.to_string(); + glib::spawn_future_local(clone!( + #[weak] + settings, + async move { + let iface_ref = dbus::packet_iface().await; + let mut iface = iface_ref.get_mut().await; + match key.as_str() { + "device-name" => { + iface.device_name = + settings.string(&key).as_str().to_string(); + _ = iface + .device_name_changed(iface_ref.signal_emitter()) + .await + .inspect_err(|err| tracing::warn!(%err)); + } + "device-visibility" => { + iface.visibility = settings.boolean(&key); + _ = iface + .device_visibility_changed(iface_ref.signal_emitter()) + .await + .inspect_err(|err| tracing::warn!(%err)); + } + _ => {} + } + } + )); + } + _ => {} }); Ok(()) From 3849ac72cc10f60ef13cc31bb77bd371ca0443e3 Mon Sep 17 00:00:00 2001 From: nozwock <57829219+nozwock@users.noreply.github.com> Date: Tue, 25 Nov 2025 04:21:03 +0530 Subject: [PATCH 4/4] Open app in background when querying on `{APP_ID}.Api` service This is done by passing the `--background` flag to the executable in the service file. Before this, using the API would start up the app in foreground and show the window, something that is quite disruptive. --- data/io.github.nozwock.Packet.Api.service.in | 3 +++ data/meson.build | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 data/io.github.nozwock.Packet.Api.service.in diff --git a/data/io.github.nozwock.Packet.Api.service.in b/data/io.github.nozwock.Packet.Api.service.in new file mode 100644 index 00000000..2343aee9 --- /dev/null +++ b/data/io.github.nozwock.Packet.Api.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=@app-id@ +Exec=@bindir@/packet --background diff --git a/data/meson.build b/data/meson.build index f9e02028..35b3f1da 100644 --- a/data/meson.build +++ b/data/meson.build @@ -104,7 +104,7 @@ service_conf = configuration_data() service_conf.set('app-id', api_id) service_conf.set('bindir', bindir) configure_file( - input: '@0@.service.in'.format(base_id), + input: '@0@.service.in'.format(base_id + '.Api'), output: '@0@.service'.format(api_id), configuration: service_conf, install: true,