gio/
dbus_connection.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{boxed::Box as Box_, future::Future, marker::PhantomData, num::NonZeroU32};
4
5use crate::{
6    ffi, ActionGroup, DBusConnection, DBusInterfaceInfo, DBusMessage, DBusMethodInvocation,
7    DBusSignalFlags, MenuModel,
8};
9use glib::{prelude::*, translate::*};
10
11pub trait DBusMethodCall: Sized {
12    fn parse_call(
13        obj_path: &str,
14        interface: Option<&str>,
15        method: &str,
16        params: glib::Variant,
17    ) -> Result<Self, glib::Error>;
18}
19
20// rustdoc-stripper-ignore-next
21/// Handle method invocations.
22pub struct MethodCallBuilder<'a, T> {
23    registration: RegistrationBuilder<'a>,
24    capture_type: PhantomData<T>,
25}
26
27impl<'a, T: DBusMethodCall> MethodCallBuilder<'a, T> {
28    // rustdoc-stripper-ignore-next
29    /// Handle invocation of a parsed method call.
30    ///
31    /// For each DBus method call parse the call, and then invoke the given closure
32    /// with
33    ///
34    /// 1. the DBus connection object,
35    /// 2. the name of the sender of the method call,
36    /// 3. the parsed call, and
37    /// 4. the method invocation object.
38    ///
39    /// The closure **must** return a value through the invocation object in all
40    /// code paths, using any of its `return_` functions, such as
41    /// [`DBusMethodInvocation::return_result`] or
42    /// [`DBusMethodInvocation::return_future_local`], to finish the call.
43    ///
44    /// If direct access to the invocation object is not needed,
45    /// [`invoke_and_return`] and [`invoke_and_return_future_local`] provide a
46    /// safer interface where the callback returns a result directly.
47    pub fn invoke<F>(self, f: F) -> RegistrationBuilder<'a>
48    where
49        F: Fn(DBusConnection, Option<&str>, T, DBusMethodInvocation) + 'static,
50    {
51        self.registration.method_call(
52            move |connection, sender, obj_path, interface, method, params, invocation| {
53                match T::parse_call(obj_path, interface, method, params) {
54                    Ok(call) => f(connection, sender, call, invocation),
55                    Err(error) => invocation.return_gerror(error),
56                }
57            },
58        )
59    }
60
61    // rustdoc-stripper-ignore-next
62    /// Handle invocation of a parsed method call.
63    ///
64    /// For each DBus method call parse the call, and then invoke the given closure
65    /// with
66    ///
67    /// 1. the DBus connection object,
68    /// 2. the name of the sender of the method call, and
69    /// 3. the parsed call.
70    ///
71    /// The return value of the closure is then returned on the method call.
72    /// If the returned variant value is not a tuple, it is automatically wrapped
73    /// in a single element tuple, as DBus methods must always return tuples.
74    /// See [`DBusMethodInvocation::return_result`] for details.
75    pub fn invoke_and_return<F>(self, f: F) -> RegistrationBuilder<'a>
76    where
77        F: Fn(DBusConnection, Option<&str>, T) -> Result<Option<glib::Variant>, glib::Error>
78            + 'static,
79    {
80        self.invoke(move |connection, sender, call, invocation| {
81            invocation.return_result(f(connection, sender, call))
82        })
83    }
84
85    // rustdoc-stripper-ignore-next
86    /// Handle an async invocation of a parsed method call.
87    ///
88    /// For each DBus method call parse the call, and then invoke the given closure
89    /// with
90    ///
91    /// 1. the DBus connection object,
92    /// 2. the name of the sender of the method call, and
93    /// 3. the parsed call.
94    ///
95    /// The output of the future is then returned on the method call.
96    /// If the returned variant value is not a tuple, it is automatically wrapped
97    /// in a single element tuple, as DBus methods must always return tuples.
98    /// See [`DBusMethodInvocation::return_future_local`] for details.
99    pub fn invoke_and_return_future_local<F, Fut>(self, f: F) -> RegistrationBuilder<'a>
100    where
101        F: Fn(DBusConnection, Option<&str>, T) -> Fut + 'static,
102        Fut: Future<Output = Result<Option<glib::Variant>, glib::Error>> + 'static,
103    {
104        self.invoke(move |connection, sender, call, invocation| {
105            invocation.return_future_local(f(connection, sender, call));
106        })
107    }
108}
109
110#[derive(Debug, Eq, PartialEq)]
111pub struct RegistrationId(NonZeroU32);
112#[derive(Debug, Eq, PartialEq)]
113pub struct WatcherId(NonZeroU32);
114#[derive(Debug, Eq, PartialEq)]
115pub struct ActionGroupExportId(NonZeroU32);
116#[derive(Debug, Eq, PartialEq)]
117pub struct MenuModelExportId(NonZeroU32);
118#[derive(Debug, Eq, PartialEq)]
119pub struct FilterId(NonZeroU32);
120#[derive(Debug, Eq, PartialEq)]
121pub struct SignalSubscriptionId(NonZeroU32);
122
123// rustdoc-stripper-ignore-next
124/// Build a registered DBus object, by handling different parts of DBus.
125#[must_use = "The builder must be built to be used"]
126pub struct RegistrationBuilder<'a> {
127    connection: &'a DBusConnection,
128    object_path: &'a str,
129    interface_info: &'a DBusInterfaceInfo,
130    #[allow(clippy::type_complexity)]
131    method_call: Option<
132        Box_<
133            dyn Fn(
134                DBusConnection,
135                Option<&str>,
136                &str,
137                Option<&str>,
138                &str,
139                glib::Variant,
140                DBusMethodInvocation,
141            ),
142        >,
143    >,
144    #[allow(clippy::type_complexity)]
145    get_property:
146        Option<Box_<dyn Fn(DBusConnection, Option<&str>, &str, &str, &str) -> glib::Variant>>,
147    #[allow(clippy::type_complexity)]
148    set_property:
149        Option<Box_<dyn Fn(DBusConnection, Option<&str>, &str, &str, &str, glib::Variant) -> bool>>,
150}
151
152impl<'a> RegistrationBuilder<'a> {
153    pub fn method_call<
154        F: Fn(
155                DBusConnection,
156                Option<&str>,
157                &str,
158                Option<&str>,
159                &str,
160                glib::Variant,
161                DBusMethodInvocation,
162            ) + 'static,
163    >(
164        mut self,
165        f: F,
166    ) -> Self {
167        self.method_call = Some(Box_::new(f));
168        self
169    }
170
171    // rustdoc-stripper-ignore-next
172    /// Handle method calls on this object.
173    ///
174    /// Return a builder for method calls which parses method names and
175    /// parameters with the given [`DBusMethodCall`] and then allows to dispatch
176    /// the parsed call either synchronously or asynchronously.
177    pub fn typed_method_call<T: DBusMethodCall>(self) -> MethodCallBuilder<'a, T> {
178        MethodCallBuilder {
179            registration: self,
180            capture_type: Default::default(),
181        }
182    }
183
184    #[doc(alias = "get_property")]
185    pub fn property<
186        F: Fn(DBusConnection, Option<&str>, &str, &str, &str) -> glib::Variant + 'static,
187    >(
188        mut self,
189        f: F,
190    ) -> Self {
191        self.get_property = Some(Box_::new(f));
192        self
193    }
194
195    pub fn set_property<
196        F: Fn(DBusConnection, Option<&str>, &str, &str, &str, glib::Variant) -> bool + 'static,
197    >(
198        mut self,
199        f: F,
200    ) -> Self {
201        self.set_property = Some(Box_::new(f));
202        self
203    }
204
205    pub fn build(self) -> Result<RegistrationId, glib::Error> {
206        unsafe {
207            let mut error = std::ptr::null_mut();
208            let id = ffi::g_dbus_connection_register_object_with_closures(
209                self.connection.to_glib_none().0,
210                self.object_path.to_glib_none().0,
211                self.interface_info.to_glib_none().0,
212                self.method_call
213                    .map(|f| {
214                        glib::Closure::new_local(move |args| {
215                            let conn = args[0].get::<DBusConnection>().unwrap();
216                            let sender = args[1].get::<Option<&str>>().unwrap();
217                            let object_path = args[2].get::<&str>().unwrap();
218                            let interface_name = args[3].get::<Option<&str>>().unwrap();
219                            let method_name = args[4].get::<&str>().unwrap();
220                            let parameters = args[5].get::<glib::Variant>().unwrap();
221
222                            // Work around GLib memory leak: Assume that the invocation is passed
223                            // as `transfer full` into the closure.
224                            //
225                            // This workaround is not going to break with future versions of
226                            // GLib as fixing the bug was considered a breaking API change.
227                            //
228                            // See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4427
229                            let invocation = from_glib_full(glib::gobject_ffi::g_value_get_object(
230                                args[6].as_ptr(),
231                            )
232                                as *mut ffi::GDBusMethodInvocation);
233
234                            f(
235                                conn,
236                                sender,
237                                object_path,
238                                interface_name,
239                                method_name,
240                                parameters,
241                                invocation,
242                            );
243                            None
244                        })
245                    })
246                    .to_glib_none()
247                    .0,
248                self.get_property
249                    .map(|f| {
250                        glib::Closure::new_local(move |args| {
251                            let conn = args[0].get::<DBusConnection>().unwrap();
252                            let sender = args[1].get::<Option<&str>>().unwrap();
253                            let object_path = args[2].get::<&str>().unwrap();
254                            let interface_name = args[3].get::<&str>().unwrap();
255                            let property_name = args[4].get::<&str>().unwrap();
256                            let result =
257                                f(conn, sender, object_path, interface_name, property_name);
258                            Some(result.to_value())
259                        })
260                    })
261                    .to_glib_none()
262                    .0,
263                self.set_property
264                    .map(|f| {
265                        glib::Closure::new_local(move |args| {
266                            let conn = args[0].get::<DBusConnection>().unwrap();
267                            let sender = args[1].get::<Option<&str>>().unwrap();
268                            let object_path = args[2].get::<&str>().unwrap();
269                            let interface_name = args[3].get::<&str>().unwrap();
270                            let property_name = args[4].get::<&str>().unwrap();
271                            let value = args[5].get::<glib::Variant>().unwrap();
272                            let result = f(
273                                conn,
274                                sender,
275                                object_path,
276                                interface_name,
277                                property_name,
278                                value,
279                            );
280                            Some(result.to_value())
281                        })
282                    })
283                    .to_glib_none()
284                    .0,
285                &mut error,
286            );
287
288            if error.is_null() {
289                Ok(RegistrationId(NonZeroU32::new_unchecked(id)))
290            } else {
291                Err(from_glib_full(error))
292            }
293        }
294    }
295}
296
297impl DBusConnection {
298    #[doc(alias = "g_dbus_connection_register_object")]
299    #[doc(alias = "g_dbus_connection_register_object_with_closures")]
300    pub fn register_object<'a>(
301        &'a self,
302        object_path: &'a str,
303        interface_info: &'a DBusInterfaceInfo,
304    ) -> RegistrationBuilder<'a> {
305        RegistrationBuilder {
306            connection: self,
307            object_path,
308            interface_info,
309            method_call: None,
310            get_property: None,
311            set_property: None,
312        }
313    }
314
315    #[doc(alias = "g_dbus_connection_unregister_object")]
316    pub fn unregister_object(
317        &self,
318        registration_id: RegistrationId,
319    ) -> Result<(), glib::error::BoolError> {
320        unsafe {
321            glib::result_from_gboolean!(
322                ffi::g_dbus_connection_unregister_object(
323                    self.to_glib_none().0,
324                    registration_id.0.into()
325                ),
326                "Failed to unregister D-Bus object"
327            )
328        }
329    }
330
331    #[doc(alias = "g_dbus_connection_export_action_group")]
332    pub fn export_action_group<P: IsA<ActionGroup>>(
333        &self,
334        object_path: &str,
335        action_group: &P,
336    ) -> Result<ActionGroupExportId, glib::Error> {
337        unsafe {
338            let mut error = std::ptr::null_mut();
339            let id = ffi::g_dbus_connection_export_action_group(
340                self.to_glib_none().0,
341                object_path.to_glib_none().0,
342                action_group.as_ref().to_glib_none().0,
343                &mut error,
344            );
345            if error.is_null() {
346                Ok(ActionGroupExportId(NonZeroU32::new_unchecked(id)))
347            } else {
348                Err(from_glib_full(error))
349            }
350        }
351    }
352
353    #[doc(alias = "g_dbus_connection_unexport_action_group")]
354    pub fn unexport_action_group(&self, export_id: ActionGroupExportId) {
355        unsafe {
356            ffi::g_dbus_connection_unexport_action_group(self.to_glib_none().0, export_id.0.into());
357        }
358    }
359
360    #[doc(alias = "g_dbus_connection_export_menu_model")]
361    pub fn export_menu_model<P: IsA<MenuModel>>(
362        &self,
363        object_path: &str,
364        menu: &P,
365    ) -> Result<MenuModelExportId, glib::Error> {
366        unsafe {
367            let mut error = std::ptr::null_mut();
368            let id = ffi::g_dbus_connection_export_menu_model(
369                self.to_glib_none().0,
370                object_path.to_glib_none().0,
371                menu.as_ref().to_glib_none().0,
372                &mut error,
373            );
374            if error.is_null() {
375                Ok(MenuModelExportId(NonZeroU32::new_unchecked(id)))
376            } else {
377                Err(from_glib_full(error))
378            }
379        }
380    }
381
382    #[doc(alias = "g_dbus_connection_unexport_menu_model")]
383    pub fn unexport_menu_model(&self, export_id: MenuModelExportId) {
384        unsafe {
385            ffi::g_dbus_connection_unexport_menu_model(self.to_glib_none().0, export_id.0.into());
386        }
387    }
388
389    #[doc(alias = "g_dbus_connection_add_filter")]
390    pub fn add_filter<
391        P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
392    >(
393        &self,
394        filter_function: P,
395    ) -> FilterId {
396        let filter_function_data: Box_<P> = Box_::new(filter_function);
397        unsafe extern "C" fn filter_function_func<
398            P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
399        >(
400            connection: *mut ffi::GDBusConnection,
401            message: *mut ffi::GDBusMessage,
402            incoming: glib::ffi::gboolean,
403            user_data: glib::ffi::gpointer,
404        ) -> *mut ffi::GDBusMessage {
405            let connection = from_glib_borrow(connection);
406            let message = from_glib_full(message);
407            let incoming = from_glib(incoming);
408            let callback: &P = &*(user_data as *mut _);
409            let res = (*callback)(&connection, &message, incoming);
410            res.into_glib_ptr()
411        }
412        let filter_function = Some(filter_function_func::<P> as _);
413        unsafe extern "C" fn user_data_free_func_func<
414            P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
415        >(
416            data: glib::ffi::gpointer,
417        ) {
418            let _callback: Box_<P> = Box_::from_raw(data as *mut _);
419        }
420        let destroy_call3 = Some(user_data_free_func_func::<P> as _);
421        let super_callback0: Box_<P> = filter_function_data;
422        unsafe {
423            let id = ffi::g_dbus_connection_add_filter(
424                self.to_glib_none().0,
425                filter_function,
426                Box_::into_raw(super_callback0) as *mut _,
427                destroy_call3,
428            );
429            FilterId(NonZeroU32::new_unchecked(id))
430        }
431    }
432
433    #[doc(alias = "g_dbus_connection_remove_filter")]
434    pub fn remove_filter(&self, filter_id: FilterId) {
435        unsafe {
436            ffi::g_dbus_connection_remove_filter(self.to_glib_none().0, filter_id.0.into());
437        }
438    }
439
440    #[doc(alias = "g_dbus_connection_signal_subscribe")]
441    #[allow(clippy::too_many_arguments)]
442    pub fn signal_subscribe<
443        P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
444    >(
445        &self,
446        sender: Option<&str>,
447        interface_name: Option<&str>,
448        member: Option<&str>,
449        object_path: Option<&str>,
450        arg0: Option<&str>,
451        flags: DBusSignalFlags,
452        callback: P,
453    ) -> SignalSubscriptionId {
454        let callback_data: Box_<P> = Box_::new(callback);
455        unsafe extern "C" fn callback_func<
456            P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
457        >(
458            connection: *mut ffi::GDBusConnection,
459            sender_name: *const libc::c_char,
460            object_path: *const libc::c_char,
461            interface_name: *const libc::c_char,
462            signal_name: *const libc::c_char,
463            parameters: *mut glib::ffi::GVariant,
464            user_data: glib::ffi::gpointer,
465        ) {
466            let connection = from_glib_borrow(connection);
467            let sender_name: Borrowed<glib::GString> = from_glib_borrow(sender_name);
468            let object_path: Borrowed<glib::GString> = from_glib_borrow(object_path);
469            let interface_name: Borrowed<glib::GString> = from_glib_borrow(interface_name);
470            let signal_name: Borrowed<glib::GString> = from_glib_borrow(signal_name);
471            let parameters = from_glib_borrow(parameters);
472            let callback: &P = &*(user_data as *mut _);
473            (*callback)(
474                &connection,
475                sender_name.as_str(),
476                object_path.as_str(),
477                interface_name.as_str(),
478                signal_name.as_str(),
479                &parameters,
480            );
481        }
482        let callback = Some(callback_func::<P> as _);
483        unsafe extern "C" fn user_data_free_func_func<
484            P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
485        >(
486            data: glib::ffi::gpointer,
487        ) {
488            let _callback: Box_<P> = Box_::from_raw(data as *mut _);
489        }
490        let destroy_call9 = Some(user_data_free_func_func::<P> as _);
491        let super_callback0: Box_<P> = callback_data;
492        unsafe {
493            let id = ffi::g_dbus_connection_signal_subscribe(
494                self.to_glib_none().0,
495                sender.to_glib_none().0,
496                interface_name.to_glib_none().0,
497                member.to_glib_none().0,
498                object_path.to_glib_none().0,
499                arg0.to_glib_none().0,
500                flags.into_glib(),
501                callback,
502                Box_::into_raw(super_callback0) as *mut _,
503                destroy_call9,
504            );
505            SignalSubscriptionId(NonZeroU32::new_unchecked(id))
506        }
507    }
508
509    #[doc(alias = "g_dbus_connection_signal_unsubscribe")]
510    pub fn signal_unsubscribe(&self, subscription_id: SignalSubscriptionId) {
511        unsafe {
512            ffi::g_dbus_connection_signal_unsubscribe(
513                self.to_glib_none().0,
514                subscription_id.0.into(),
515            );
516        }
517    }
518}