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 /// Registers callbacks for exported objects at @object_path with the
299 /// D-Bus interface that is described in @interface_info.
300 ///
301 /// Calls to functions in @vtable (and @user_data_free_func) will happen
302 /// in the thread-default main context
303 /// (see [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()])
304 /// of the thread you are calling this method from.
305 ///
306 /// Note that all #GVariant values passed to functions in @vtable will match
307 /// the signature given in @interface_info - if a remote caller passes
308 /// incorrect values, the `org.freedesktop.DBus.Error.InvalidArgs`
309 /// is returned to the remote caller.
310 ///
311 /// Additionally, if the remote caller attempts to invoke methods or
312 /// access properties not mentioned in @interface_info the
313 /// `org.freedesktop.DBus.Error.UnknownMethod` resp.
314 /// `org.freedesktop.DBus.Error.InvalidArgs` errors
315 /// are returned to the caller.
316 ///
317 /// It is considered a programming error if the
318 /// #GDBusInterfaceGetPropertyFunc function in @vtable returns a
319 /// #GVariant of incorrect type.
320 ///
321 /// If an existing callback is already registered at @object_path and
322 /// @interface_name, then @error is set to [`IOErrorEnum::Exists`][crate::IOErrorEnum::Exists].
323 ///
324 /// GDBus automatically implements the standard D-Bus interfaces
325 /// org.freedesktop.DBus.Properties, org.freedesktop.DBus.Introspectable
326 /// and org.freedesktop.Peer, so you don't have to implement those for the
327 /// objects you export. You can implement org.freedesktop.DBus.Properties
328 /// yourself, e.g. to handle getting and setting of properties asynchronously.
329 ///
330 /// Note that the reference count on @interface_info will be
331 /// incremented by 1 (unless allocated statically, e.g. if the
332 /// reference count is -1, see g_dbus_interface_info_ref()) for as long
333 /// as the object is exported. Also note that @vtable will be copied.
334 ///
335 /// See this [server][`DBusConnection`][crate::DBusConnection]#an-example-d-bus-server]
336 /// for an example of how to use this method.
337 /// ## `object_path`
338 /// the object path to register at
339 /// ## `interface_info`
340 /// introspection data for the interface
341 /// ## `vtable`
342 /// a #GDBusInterfaceVTable to call into or [`None`]
343 ///
344 /// # Returns
345 ///
346 /// 0 if @error is set, otherwise a registration id (never 0)
347 /// that can be used with g_dbus_connection_unregister_object()
348 #[doc(alias = "g_dbus_connection_register_object")]
349 #[doc(alias = "g_dbus_connection_register_object_with_closures")]
350 pub fn register_object<'a>(
351 &'a self,
352 object_path: &'a str,
353 interface_info: &'a DBusInterfaceInfo,
354 ) -> RegistrationBuilder<'a> {
355 RegistrationBuilder {
356 connection: self,
357 object_path,
358 interface_info,
359 method_call: None,
360 get_property: None,
361 set_property: None,
362 }
363 }
364
365 /// Unregisters an object.
366 /// ## `registration_id`
367 /// a registration id obtained from
368 /// g_dbus_connection_register_object()
369 ///
370 /// # Returns
371 ///
372 /// [`true`] if the object was unregistered, [`false`] otherwise
373 #[doc(alias = "g_dbus_connection_unregister_object")]
374 pub fn unregister_object(
375 &self,
376 registration_id: RegistrationId,
377 ) -> Result<(), glib::error::BoolError> {
378 unsafe {
379 glib::result_from_gboolean!(
380 ffi::g_dbus_connection_unregister_object(
381 self.to_glib_none().0,
382 registration_id.0.into()
383 ),
384 "Failed to unregister D-Bus object"
385 )
386 }
387 }
388
389 /// Exports @action_group on @self at @object_path.
390 ///
391 /// The implemented D-Bus API should be considered private. It is
392 /// subject to change in the future.
393 ///
394 /// A given object path can only have one action group exported on it.
395 /// If this constraint is violated, the export will fail and 0 will be
396 /// returned (with @error set accordingly).
397 ///
398 /// You can unexport the action group using
399 /// [`unexport_action_group()`][Self::unexport_action_group()] with the return value of
400 /// this function.
401 ///
402 /// The thread default main context is taken at the time of this call.
403 /// All incoming action activations and state change requests are
404 /// reported from this context. Any changes on the action group that
405 /// cause it to emit signals must also come from this same context.
406 /// Since incoming action activations and state change requests are
407 /// rather likely to cause changes on the action group, this effectively
408 /// limits a given action group to being exported from only one main
409 /// context.
410 /// ## `object_path`
411 /// a D-Bus object path
412 /// ## `action_group`
413 /// an action group
414 ///
415 /// # Returns
416 ///
417 /// the ID of the export (never zero), or 0 in case of failure
418 #[doc(alias = "g_dbus_connection_export_action_group")]
419 pub fn export_action_group<P: IsA<ActionGroup>>(
420 &self,
421 object_path: &str,
422 action_group: &P,
423 ) -> Result<ActionGroupExportId, glib::Error> {
424 unsafe {
425 let mut error = std::ptr::null_mut();
426 let id = ffi::g_dbus_connection_export_action_group(
427 self.to_glib_none().0,
428 object_path.to_glib_none().0,
429 action_group.as_ref().to_glib_none().0,
430 &mut error,
431 );
432 if error.is_null() {
433 Ok(ActionGroupExportId(NonZeroU32::new_unchecked(id)))
434 } else {
435 Err(from_glib_full(error))
436 }
437 }
438 }
439
440 /// Reverses the effect of a previous call to
441 /// [`export_action_group()`][Self::export_action_group()].
442 ///
443 /// It is an error to call this function with an ID that wasn’t returned from
444 /// [`export_action_group()`][Self::export_action_group()] or to call it with the same
445 /// ID more than once.
446 /// ## `export_id`
447 /// the ID from [`export_action_group()`][Self::export_action_group()]
448 #[doc(alias = "g_dbus_connection_unexport_action_group")]
449 pub fn unexport_action_group(&self, export_id: ActionGroupExportId) {
450 unsafe {
451 ffi::g_dbus_connection_unexport_action_group(self.to_glib_none().0, export_id.0.into());
452 }
453 }
454
455 /// Exports @menu on @self at @object_path.
456 ///
457 /// The implemented D-Bus API should be considered private.
458 /// It is subject to change in the future.
459 ///
460 /// An object path can only have one menu model exported on it. If this
461 /// constraint is violated, the export will fail and 0 will be
462 /// returned (with @error set accordingly).
463 ///
464 /// Exporting menus with sections containing more than
465 /// `G_MENU_EXPORTER_MAX_SECTION_SIZE` items is not supported and results in
466 /// undefined behavior.
467 ///
468 /// You can unexport the menu model using
469 /// g_dbus_connection_unexport_menu_model() with the return value of
470 /// this function.
471 /// ## `object_path`
472 /// a D-Bus object path
473 /// ## `menu`
474 /// a #GMenuModel
475 ///
476 /// # Returns
477 ///
478 /// the ID of the export (never zero), or 0 in case of failure
479 #[doc(alias = "g_dbus_connection_export_menu_model")]
480 pub fn export_menu_model<P: IsA<MenuModel>>(
481 &self,
482 object_path: &str,
483 menu: &P,
484 ) -> Result<MenuModelExportId, glib::Error> {
485 unsafe {
486 let mut error = std::ptr::null_mut();
487 let id = ffi::g_dbus_connection_export_menu_model(
488 self.to_glib_none().0,
489 object_path.to_glib_none().0,
490 menu.as_ref().to_glib_none().0,
491 &mut error,
492 );
493 if error.is_null() {
494 Ok(MenuModelExportId(NonZeroU32::new_unchecked(id)))
495 } else {
496 Err(from_glib_full(error))
497 }
498 }
499 }
500
501 /// Reverses the effect of a previous call to
502 /// g_dbus_connection_export_menu_model().
503 ///
504 /// It is an error to call this function with an ID that wasn't returned
505 /// from g_dbus_connection_export_menu_model() or to call it with the
506 /// same ID more than once.
507 /// ## `export_id`
508 /// the ID from g_dbus_connection_export_menu_model()
509 #[doc(alias = "g_dbus_connection_unexport_menu_model")]
510 pub fn unexport_menu_model(&self, export_id: MenuModelExportId) {
511 unsafe {
512 ffi::g_dbus_connection_unexport_menu_model(self.to_glib_none().0, export_id.0.into());
513 }
514 }
515
516 /// Adds a message filter. Filters are handlers that are run on all
517 /// incoming and outgoing messages, prior to standard dispatch. Filters
518 /// are run in the order that they were added. The same handler can be
519 /// added as a filter more than once, in which case it will be run more
520 /// than once. Filters added during a filter callback won't be run on
521 /// the message being processed. Filter functions are allowed to modify
522 /// and even drop messages.
523 ///
524 /// Note that filters are run in a dedicated message handling thread so
525 /// they can't block and, generally, can't do anything but signal a
526 /// worker thread. Also note that filters are rarely needed - use API
527 /// such as g_dbus_connection_send_message_with_reply(),
528 /// g_dbus_connection_signal_subscribe() or g_dbus_connection_call() instead.
529 ///
530 /// If a filter consumes an incoming message the message is not
531 /// dispatched anywhere else - not even the standard dispatch machinery
532 /// (that API such as g_dbus_connection_signal_subscribe() and
533 /// g_dbus_connection_send_message_with_reply() relies on) will see the
534 /// message. Similarly, if a filter consumes an outgoing message, the
535 /// message will not be sent to the other peer.
536 ///
537 /// If @user_data_free_func is non-[`None`], it will be called (in the
538 /// thread-default main context of the thread you are calling this
539 /// method from) at some point after @user_data is no longer
540 /// needed. (It is not guaranteed to be called synchronously when the
541 /// filter is removed, and may be called after @self has been
542 /// destroyed.)
543 /// ## `filter_function`
544 /// a filter function
545 ///
546 /// # Returns
547 ///
548 /// a filter identifier that can be used with
549 /// g_dbus_connection_remove_filter()
550 #[doc(alias = "g_dbus_connection_add_filter")]
551 pub fn add_filter<
552 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
553 >(
554 &self,
555 filter_function: P,
556 ) -> FilterId {
557 let filter_function_data: Box_<P> = Box_::new(filter_function);
558 unsafe extern "C" fn filter_function_func<
559 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
560 >(
561 connection: *mut ffi::GDBusConnection,
562 message: *mut ffi::GDBusMessage,
563 incoming: glib::ffi::gboolean,
564 user_data: glib::ffi::gpointer,
565 ) -> *mut ffi::GDBusMessage {
566 let connection = from_glib_borrow(connection);
567 let message = from_glib_full(message);
568 let incoming = from_glib(incoming);
569 let callback: &P = &*(user_data as *mut _);
570 let res = (*callback)(&connection, &message, incoming);
571 res.into_glib_ptr()
572 }
573 let filter_function = Some(filter_function_func::<P> as _);
574 unsafe extern "C" fn user_data_free_func_func<
575 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
576 >(
577 data: glib::ffi::gpointer,
578 ) {
579 let _callback: Box_<P> = Box_::from_raw(data as *mut _);
580 }
581 let destroy_call3 = Some(user_data_free_func_func::<P> as _);
582 let super_callback0: Box_<P> = filter_function_data;
583 unsafe {
584 let id = ffi::g_dbus_connection_add_filter(
585 self.to_glib_none().0,
586 filter_function,
587 Box_::into_raw(super_callback0) as *mut _,
588 destroy_call3,
589 );
590 FilterId(NonZeroU32::new_unchecked(id))
591 }
592 }
593
594 /// Removes a filter.
595 ///
596 /// Note that since filters run in a different thread, there is a race
597 /// condition where it is possible that the filter will be running even
598 /// after calling g_dbus_connection_remove_filter(), so you cannot just
599 /// free data that the filter might be using. Instead, you should pass
600 /// a #GDestroyNotify to g_dbus_connection_add_filter(), which will be
601 /// called when it is guaranteed that the data is no longer needed.
602 /// ## `filter_id`
603 /// an identifier obtained from g_dbus_connection_add_filter()
604 #[doc(alias = "g_dbus_connection_remove_filter")]
605 pub fn remove_filter(&self, filter_id: FilterId) {
606 unsafe {
607 ffi::g_dbus_connection_remove_filter(self.to_glib_none().0, filter_id.0.into());
608 }
609 }
610
611 /// Subscribes to signals on @self and invokes @callback whenever
612 /// the signal is received. Note that @callback will be invoked in the
613 /// thread-default main context (see [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()])
614 /// of the thread you are calling this method from.
615 ///
616 /// If @self is not a message bus connection, @sender must be
617 /// [`None`].
618 ///
619 /// If @sender is a well-known name note that @callback is invoked with
620 /// the unique name for the owner of @sender, not the well-known name
621 /// as one would expect. This is because the message bus rewrites the
622 /// name. As such, to avoid certain race conditions, users should be
623 /// tracking the name owner of the well-known name and use that when
624 /// processing the received signal.
625 ///
626 /// If one of [`DBusSignalFlags::MATCH_ARG0_NAMESPACE`][crate::DBusSignalFlags::MATCH_ARG0_NAMESPACE] or
627 /// [`DBusSignalFlags::MATCH_ARG0_PATH`][crate::DBusSignalFlags::MATCH_ARG0_PATH] are given, @arg0 is
628 /// interpreted as part of a namespace or path. The first argument
629 /// of a signal is matched against that part as specified by D-Bus.
630 ///
631 /// If @user_data_free_func is non-[`None`], it will be called (in the
632 /// thread-default main context of the thread you are calling this
633 /// method from) at some point after @user_data is no longer
634 /// needed. (It is not guaranteed to be called synchronously when the
635 /// signal is unsubscribed from, and may be called after @self
636 /// has been destroyed.)
637 ///
638 /// As @callback is potentially invoked in a different thread from where it’s
639 /// emitted, it’s possible for this to happen after
640 /// g_dbus_connection_signal_unsubscribe() has been called in another thread.
641 /// Due to this, @user_data should have a strong reference which is freed with
642 /// @user_data_free_func, rather than pointing to data whose lifecycle is tied
643 /// to the signal subscription. For example, if a #GObject is used to store the
644 /// subscription ID from g_dbus_connection_signal_subscribe(), a strong reference
645 /// to that #GObject must be passed to @user_data, and g_object_unref() passed to
646 /// @user_data_free_func. You are responsible for breaking the resulting
647 /// reference count cycle by explicitly unsubscribing from the signal when
648 /// dropping the last external reference to the #GObject. Alternatively, a weak
649 /// reference may be used.
650 ///
651 /// It is guaranteed that if you unsubscribe from a signal using
652 /// g_dbus_connection_signal_unsubscribe() from the same thread which made the
653 /// corresponding g_dbus_connection_signal_subscribe() call, @callback will not
654 /// be invoked after g_dbus_connection_signal_unsubscribe() returns.
655 ///
656 /// The returned subscription identifier is an opaque value which is guaranteed
657 /// to never be zero.
658 ///
659 /// This function can never fail.
660 /// ## `sender`
661 /// sender name to match on (unique or well-known name)
662 /// or [`None`] to listen from all senders
663 /// ## `interface_name`
664 /// D-Bus interface name to match on or [`None`] to
665 /// match on all interfaces
666 /// ## `member`
667 /// D-Bus signal name to match on or [`None`] to match on
668 /// all signals
669 /// ## `object_path`
670 /// object path to match on or [`None`] to match on
671 /// all object paths
672 /// ## `arg0`
673 /// contents of first string argument to match on or [`None`]
674 /// to match on all kinds of arguments
675 /// ## `flags`
676 /// #GDBusSignalFlags describing how arg0 is used in subscribing to the
677 /// signal
678 /// ## `callback`
679 /// callback to invoke when there is a signal matching the requested data
680 ///
681 /// # Returns
682 ///
683 /// a subscription identifier that can be used with g_dbus_connection_signal_unsubscribe()
684 #[doc(alias = "g_dbus_connection_signal_subscribe")]
685 #[allow(clippy::too_many_arguments)]
686 pub fn signal_subscribe<
687 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
688 >(
689 &self,
690 sender: Option<&str>,
691 interface_name: Option<&str>,
692 member: Option<&str>,
693 object_path: Option<&str>,
694 arg0: Option<&str>,
695 flags: DBusSignalFlags,
696 callback: P,
697 ) -> SignalSubscriptionId {
698 let callback_data: Box_<P> = Box_::new(callback);
699 unsafe extern "C" fn callback_func<
700 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
701 >(
702 connection: *mut ffi::GDBusConnection,
703 sender_name: *const libc::c_char,
704 object_path: *const libc::c_char,
705 interface_name: *const libc::c_char,
706 signal_name: *const libc::c_char,
707 parameters: *mut glib::ffi::GVariant,
708 user_data: glib::ffi::gpointer,
709 ) {
710 let connection = from_glib_borrow(connection);
711 let sender_name: Borrowed<glib::GString> = from_glib_borrow(sender_name);
712 let object_path: Borrowed<glib::GString> = from_glib_borrow(object_path);
713 let interface_name: Borrowed<glib::GString> = from_glib_borrow(interface_name);
714 let signal_name: Borrowed<glib::GString> = from_glib_borrow(signal_name);
715 let parameters = from_glib_borrow(parameters);
716 let callback: &P = &*(user_data as *mut _);
717 (*callback)(
718 &connection,
719 sender_name.as_str(),
720 object_path.as_str(),
721 interface_name.as_str(),
722 signal_name.as_str(),
723 ¶meters,
724 );
725 }
726 let callback = Some(callback_func::<P> as _);
727 unsafe extern "C" fn user_data_free_func_func<
728 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
729 >(
730 data: glib::ffi::gpointer,
731 ) {
732 let _callback: Box_<P> = Box_::from_raw(data as *mut _);
733 }
734 let destroy_call9 = Some(user_data_free_func_func::<P> as _);
735 let super_callback0: Box_<P> = callback_data;
736 unsafe {
737 let id = ffi::g_dbus_connection_signal_subscribe(
738 self.to_glib_none().0,
739 sender.to_glib_none().0,
740 interface_name.to_glib_none().0,
741 member.to_glib_none().0,
742 object_path.to_glib_none().0,
743 arg0.to_glib_none().0,
744 flags.into_glib(),
745 callback,
746 Box_::into_raw(super_callback0) as *mut _,
747 destroy_call9,
748 );
749 SignalSubscriptionId(NonZeroU32::new_unchecked(id))
750 }
751 }
752
753 /// Unsubscribes from signals.
754 ///
755 /// Note that there may still be D-Bus traffic to process (relating to this
756 /// signal subscription) in the current thread-default #GMainContext after this
757 /// function has returned. You should continue to iterate the #GMainContext
758 /// until the #GDestroyNotify function passed to
759 /// g_dbus_connection_signal_subscribe() is called, in order to avoid memory
760 /// leaks through callbacks queued on the #GMainContext after it’s stopped being
761 /// iterated.
762 /// Alternatively, any idle source with a priority lower than `G_PRIORITY_DEFAULT`
763 /// that was scheduled after unsubscription, also indicates that all resources
764 /// of this subscription are released.
765 /// ## `subscription_id`
766 /// a subscription id obtained from
767 /// g_dbus_connection_signal_subscribe()
768 #[doc(alias = "g_dbus_connection_signal_unsubscribe")]
769 pub fn signal_unsubscribe(&self, subscription_id: SignalSubscriptionId) {
770 unsafe {
771 ffi::g_dbus_connection_signal_unsubscribe(
772 self.to_glib_none().0,
773 subscription_id.0.into(),
774 );
775 }
776 }
777}