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 ActionGroup, DBusConnection, DBusInterfaceInfo, DBusMessage, DBusMethodInvocation,
7 DBusPropertyInfoFlags, DBusSignalFlags, MenuModel, ffi,
8};
9use futures_channel::mpsc;
10use futures_core::{FusedStream, Stream};
11use glib::{WeakRef, prelude::*, translate::*, variant::VariantTypeMismatchError};
12use pin_project_lite::pin_project;
13
14pub trait DBusMethodCall: Sized {
15 fn parse_call(
16 obj_path: &str,
17 interface: Option<&str>,
18 method: &str,
19 params: glib::Variant,
20 ) -> Result<Self, glib::Error>;
21}
22
23// rustdoc-stripper-ignore-next
24/// Handle method invocations.
25pub struct MethodCallBuilder<'a, T> {
26 registration: RegistrationBuilder<'a>,
27 capture_type: PhantomData<T>,
28}
29
30impl<'a, T: DBusMethodCall> MethodCallBuilder<'a, T> {
31 // rustdoc-stripper-ignore-next
32 /// Handle invocation of a parsed method call.
33 ///
34 /// For each DBus method call parse the call, and then invoke the given closure
35 /// with
36 ///
37 /// 1. the DBus connection object,
38 /// 2. the name of the sender of the method call,
39 /// 3. the parsed call, and
40 /// 4. the method invocation object.
41 ///
42 /// The closure **must** return a value through the invocation object in all
43 /// code paths, using any of its `return_` functions, such as
44 /// [`DBusMethodInvocation::return_result`] or
45 /// [`DBusMethodInvocation::return_future_local`], to finish the call.
46 ///
47 /// If direct access to the invocation object is not needed,
48 /// [`invoke_and_return`] and [`invoke_and_return_future_local`] provide a
49 /// safer interface where the callback returns a result directly.
50 pub fn invoke<F>(self, f: F) -> RegistrationBuilder<'a>
51 where
52 F: Fn(DBusConnection, Option<&str>, T, DBusMethodInvocation) + 'static,
53 {
54 self.registration.method_call(
55 move |connection, sender, obj_path, interface, method, params, invocation| {
56 match T::parse_call(obj_path, interface, method, params) {
57 Ok(call) => f(connection, sender, call, invocation),
58 Err(error) => invocation.return_gerror(error),
59 }
60 },
61 )
62 }
63
64 // rustdoc-stripper-ignore-next
65 /// Handle invocation of a parsed method call.
66 ///
67 /// For each DBus method call parse the call, and then invoke the given closure
68 /// with
69 ///
70 /// 1. the DBus connection object,
71 /// 2. the name of the sender of the method call, and
72 /// 3. the parsed call.
73 ///
74 /// The return value of the closure is then returned on the method call.
75 /// If the returned variant value is not a tuple, it is automatically wrapped
76 /// in a single element tuple, as DBus methods must always return tuples.
77 /// See [`DBusMethodInvocation::return_result`] for details.
78 pub fn invoke_and_return<F>(self, f: F) -> RegistrationBuilder<'a>
79 where
80 F: Fn(DBusConnection, Option<&str>, T) -> Result<Option<glib::Variant>, glib::Error>
81 + 'static,
82 {
83 self.invoke(move |connection, sender, call, invocation| {
84 invocation.return_result(f(connection, sender, call))
85 })
86 }
87
88 // rustdoc-stripper-ignore-next
89 /// Handle an async invocation of a parsed method call.
90 ///
91 /// For each DBus method call parse the call, and then invoke the given closure
92 /// with
93 ///
94 /// 1. the DBus connection object,
95 /// 2. the name of the sender of the method call, and
96 /// 3. the parsed call.
97 ///
98 /// The output of the future is then returned on the method call.
99 /// If the returned variant value is not a tuple, it is automatically wrapped
100 /// in a single element tuple, as DBus methods must always return tuples.
101 /// See [`DBusMethodInvocation::return_future_local`] for details.
102 pub fn invoke_and_return_future_local<F, Fut>(self, f: F) -> RegistrationBuilder<'a>
103 where
104 F: Fn(DBusConnection, Option<&str>, T) -> Fut + 'static,
105 Fut: Future<Output = Result<Option<glib::Variant>, glib::Error>> + 'static,
106 {
107 self.invoke(move |connection, sender, call, invocation| {
108 invocation.return_future_local(f(connection, sender, call));
109 })
110 }
111}
112
113#[derive(Debug, Eq, PartialEq)]
114pub struct RegistrationId(NonZeroU32);
115#[derive(Debug, Eq, PartialEq)]
116pub struct WatcherId(NonZeroU32);
117#[derive(Debug, Eq, PartialEq)]
118pub struct ActionGroupExportId(NonZeroU32);
119#[derive(Debug, Eq, PartialEq)]
120pub struct MenuModelExportId(NonZeroU32);
121#[derive(Debug, Eq, PartialEq)]
122pub struct FilterId(NonZeroU32);
123
124#[derive(Debug, Eq, PartialEq)]
125pub struct SignalSubscriptionId(NonZeroU32);
126
127// rustdoc-stripper-ignore-next
128/// A strong subscription to a D-Bus signal.
129///
130/// Keep a reference to a D-Bus connection to maintain a subscription on a
131/// D-Bus signal even if the connection has no other strong reference.
132///
133/// When dropped, unsubscribes from signal on the connection, and then drop the
134/// reference on the connection. If no other strong reference on the connection
135/// exists the connection is closed and destroyed.
136#[derive(Debug)]
137pub struct SignalSubscription(DBusConnection, Option<SignalSubscriptionId>);
138
139impl SignalSubscription {
140 // rustdoc-stripper-ignore-next
141 /// Downgrade this signal subscription to a weak one.
142 #[must_use]
143 pub fn downgrade(mut self) -> WeakSignalSubscription {
144 WeakSignalSubscription(self.0.downgrade(), self.1.take())
145 }
146}
147
148impl Drop for SignalSubscription {
149 fn drop(&mut self) {
150 if let Some(id) = self.1.take() {
151 #[allow(deprecated)]
152 self.0.signal_unsubscribe(id);
153 }
154 }
155}
156
157// rustdoc-stripper-ignore-next
158/// A weak subscription to a D-Bus signal.
159///
160/// Like [`SignalSubscription`] but hold only a weak reference to the D-Bus
161/// connection the signal is subscribed on, i.e. maintain the subscription on
162/// the D-Bus signal only as long as some strong reference exists on the
163/// corresponding D-Bus connection.
164///
165/// When dropped, unsubscribes from signal on the connection if it still exists,
166/// and then drop the reference on the connection. If no other strong reference
167/// on the connection exists the connection is closed and destroyed.
168#[derive(Debug)]
169pub struct WeakSignalSubscription(WeakRef<DBusConnection>, Option<SignalSubscriptionId>);
170
171impl WeakSignalSubscription {
172 // rustdoc-stripper-ignore-next
173 /// Upgrade this signal subscription to a strong one.
174 #[must_use]
175 pub fn upgrade(mut self) -> Option<SignalSubscription> {
176 self.0
177 .upgrade()
178 .map(|c| SignalSubscription(c, self.1.take()))
179 }
180}
181
182impl Drop for WeakSignalSubscription {
183 fn drop(&mut self) {
184 if let Some(id) = self.1.take()
185 && let Some(connection) = self.0.upgrade()
186 {
187 #[allow(deprecated)]
188 connection.signal_unsubscribe(id);
189 }
190 }
191}
192
193// rustdoc-stripper-ignore-next
194/// An emitted D-Bus signal.
195#[derive(Debug, Copy, Clone)]
196pub struct DBusSignalRef<'a> {
197 // rustdoc-stripper-ignore-next
198 /// The connection the signal was emitted on.
199 pub connection: &'a DBusConnection,
200 // rustdoc-stripper-ignore-next
201 /// The bus name of the sender which emitted the signal.
202 pub sender_name: &'a str,
203 // rustdoc-stripper-ignore-next
204 /// The path of the object on `sender` the signal was emitted from.
205 pub object_path: &'a str,
206 // rustdoc-stripper-ignore-next
207 /// The interface the signal belongs to.
208 pub interface_name: &'a str,
209 // rustdoc-stripper-ignore-next
210 /// The name of the emitted signal.
211 pub signal_name: &'a str,
212 // rustdoc-stripper-ignore-next
213 /// Parameters the signal was emitted with.
214 pub parameters: &'a glib::Variant,
215}
216
217pin_project! {
218 // rustdoc-stripper-ignore-next
219 /// A subscribed stream.
220 ///
221 /// A stream which wraps an inner stream of type `S` while holding on to a
222 /// subscription handle `H` to keep a subscription alive.
223 #[derive(Debug)]
224 #[must_use = "streams do nothing unless polled"]
225 pub struct SubscribedSignalStream<H, S> {
226 #[pin]
227 stream: S,
228 subscription: H,
229 }
230}
231
232impl<S> SubscribedSignalStream<SignalSubscription, S> {
233 // rustdoc-stripper-ignore-next
234 /// Downgrade the inner signal subscription to a weak one.
235 ///
236 /// See [`SignalSubscription::downgrade`] and [`WeakSignalSubscription`].
237 pub fn downgrade(self) -> SubscribedSignalStream<WeakSignalSubscription, S> {
238 SubscribedSignalStream {
239 subscription: self.subscription.downgrade(),
240 stream: self.stream,
241 }
242 }
243}
244
245impl<S> SubscribedSignalStream<WeakSignalSubscription, S> {
246 // rustdoc-stripper-ignore-next
247 /// Upgrade the inner signal subscription to a strong one.
248 ///
249 /// See [`WeakSignalSubscription::upgrade`] and [`SignalSubscription`].
250 pub fn downgrade(self) -> Option<SubscribedSignalStream<SignalSubscription, S>> {
251 self.subscription
252 .upgrade()
253 .map(|subscription| SubscribedSignalStream {
254 subscription,
255 stream: self.stream,
256 })
257 }
258}
259
260impl<H, S> Stream for SubscribedSignalStream<H, S>
261where
262 S: Stream,
263{
264 type Item = S::Item;
265
266 fn poll_next(
267 self: std::pin::Pin<&mut Self>,
268 cx: &mut std::task::Context<'_>,
269 ) -> std::task::Poll<Option<Self::Item>> {
270 let this = self.project();
271 this.stream.poll_next(cx)
272 }
273
274 fn size_hint(&self) -> (usize, Option<usize>) {
275 self.stream.size_hint()
276 }
277}
278
279impl<H, S> FusedStream for SubscribedSignalStream<H, S>
280where
281 S: FusedStream,
282{
283 fn is_terminated(&self) -> bool {
284 self.stream.is_terminated()
285 }
286}
287
288// rustdoc-stripper-ignore-next
289/// Build a registered DBus object, by handling different parts of DBus.
290#[must_use = "The builder must be built to be used"]
291pub struct RegistrationBuilder<'a> {
292 connection: &'a DBusConnection,
293 object_path: &'a str,
294 interface_info: &'a DBusInterfaceInfo,
295 #[allow(clippy::type_complexity)]
296 method_call: Option<
297 Box_<
298 dyn Fn(
299 DBusConnection,
300 Option<&str>,
301 &str,
302 Option<&str>,
303 &str,
304 glib::Variant,
305 DBusMethodInvocation,
306 ),
307 >,
308 >,
309 #[allow(clippy::type_complexity)]
310 get_property: Option<
311 Box_<
312 dyn Fn(
313 DBusConnection,
314 Option<&str>,
315 &str,
316 &str,
317 &str,
318 ) -> Result<glib::Variant, glib::Error>,
319 >,
320 >,
321 #[allow(clippy::type_complexity)]
322 set_property: Option<
323 Box_<
324 dyn Fn(
325 DBusConnection,
326 Option<&str>,
327 &str,
328 &str,
329 &str,
330 glib::Variant,
331 ) -> Result<(), glib::Error>,
332 >,
333 >,
334}
335
336impl<'a> RegistrationBuilder<'a> {
337 pub fn method_call<
338 F: Fn(
339 DBusConnection,
340 Option<&str>,
341 &str,
342 Option<&str>,
343 &str,
344 glib::Variant,
345 DBusMethodInvocation,
346 ) + 'static,
347 >(
348 mut self,
349 f: F,
350 ) -> Self {
351 self.method_call = Some(Box_::new(f));
352 self
353 }
354
355 // rustdoc-stripper-ignore-next
356 /// Handle method calls on this object.
357 ///
358 /// Return a builder for method calls which parses method names and
359 /// parameters with the given [`DBusMethodCall`] and then allows to dispatch
360 /// the parsed call either synchronously or asynchronously.
361 pub fn typed_method_call<T: DBusMethodCall>(self) -> MethodCallBuilder<'a, T> {
362 MethodCallBuilder {
363 registration: self,
364 capture_type: Default::default(),
365 }
366 }
367
368 #[doc(alias = "get_property")]
369 pub fn property<
370 F: Fn(DBusConnection, Option<&str>, &str, &str, &str) -> Result<glib::Variant, glib::Error>
371 + 'static,
372 >(
373 mut self,
374 f: F,
375 ) -> Self {
376 self.get_property = Some(Box_::new(f));
377 self
378 }
379
380 pub fn set_property<
381 F: Fn(
382 DBusConnection,
383 Option<&str>,
384 &str,
385 &str,
386 &str,
387 glib::Variant,
388 ) -> Result<(), glib::Error>
389 + 'static,
390 >(
391 mut self,
392 f: F,
393 ) -> Self {
394 self.set_property = Some(Box_::new(f));
395 self
396 }
397
398 pub fn build(self) -> Result<RegistrationId, glib::Error> {
399 const PROPERTIES_INTERFACE: &str = "org.freedesktop.DBus.Properties";
400 unsafe {
401 let mut error = std::ptr::null_mut();
402 let interface_info = self.interface_info.clone();
403 // We handle get_/set_property ourselves because the closure versions can't return errors.
404 let id = ffi::g_dbus_connection_register_object_with_closures(
405 self.connection.to_glib_none().0,
406 self.object_path.to_glib_none().0,
407 self.interface_info.to_glib_none().0,
408 self.method_call
409 .map(move |f| {
410 glib::Closure::new_local(move |args| {
411 let conn = args[0].get::<DBusConnection>().unwrap();
412 let sender = args[1].get::<Option<&str>>().unwrap();
413 let object_path = args[2].get::<&str>().unwrap();
414 let interface_name = args[3].get::<Option<&str>>().unwrap();
415 let method_name = args[4].get::<&str>().unwrap();
416 let parameters = args[5].get::<glib::Variant>().unwrap();
417
418 // Work around GLib memory leak: Assume that the invocation is passed
419 // as `transfer full` into the closure.
420 //
421 // This workaround is not going to break with future versions of
422 // GLib as fixing the bug was considered a breaking API change.
423 //
424 // See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4427
425 let invocation = from_glib_full(glib::gobject_ffi::g_value_get_object(
426 args[6].as_ptr(),
427 )
428 as *mut ffi::GDBusMethodInvocation);
429
430 if interface_name == Some(PROPERTIES_INTERFACE)
431 && method_name == "Get"
432 && let Some(get_property) = self.get_property.as_deref()
433 {
434 handle_get_property(
435 conn,
436 sender,
437 object_path,
438 parameters,
439 invocation,
440 get_property,
441 );
442 } else if interface_name == Some(PROPERTIES_INTERFACE)
443 && method_name == "Set"
444 && let Some(set_property) = self.set_property.as_deref()
445 {
446 handle_set_property(
447 conn,
448 sender,
449 object_path,
450 parameters,
451 invocation,
452 set_property,
453 );
454 } else if interface_name == Some(PROPERTIES_INTERFACE)
455 && method_name == "GetAll"
456 && let Some(get_property) = self.get_property.as_deref()
457 {
458 handle_get_all_properties(
459 conn,
460 sender,
461 object_path,
462 &interface_info,
463 invocation,
464 get_property,
465 );
466 } else {
467 f(
468 conn,
469 sender,
470 object_path,
471 interface_name,
472 method_name,
473 parameters,
474 invocation,
475 );
476 }
477
478 None
479 })
480 })
481 .to_glib_none()
482 .0,
483 std::ptr::null_mut(),
484 std::ptr::null_mut(),
485 &mut error,
486 );
487
488 if error.is_null() {
489 Ok(RegistrationId(NonZeroU32::new_unchecked(id)))
490 } else {
491 Err(from_glib_full(error))
492 }
493 }
494 }
495}
496
497fn handle_get_property(
498 connection: DBusConnection,
499 sender: Option<&str>,
500 object_path: &str,
501 parameters: glib::Variant,
502 invocation: DBusMethodInvocation,
503 get_property_func: &dyn Fn(
504 DBusConnection,
505 Option<&str>,
506 &str,
507 &str,
508 &str,
509 ) -> Result<glib::Variant, glib::Error>,
510) {
511 let (interface_name, property_name): (String, String) = parameters
512 .get()
513 .expect("parameters are guaranteed to have correct types by gdbus");
514 let result = get_property_func(
515 connection,
516 sender,
517 object_path,
518 &interface_name,
519 &property_name,
520 );
521 invocation.return_result(result.map(|variant| Some(variant.to_variant())));
522}
523
524fn handle_set_property(
525 connection: DBusConnection,
526 sender: Option<&str>,
527 object_path: &str,
528 parameters: glib::Variant,
529 invocation: DBusMethodInvocation,
530 set_property_func: &dyn Fn(
531 DBusConnection,
532 Option<&str>,
533 &str,
534 &str,
535 &str,
536 glib::Variant,
537 ) -> Result<(), glib::Error>,
538) {
539 let (interface_name, property_name, value): (String, String, _) = parameters
540 .get()
541 .expect("parameters are guaranteed to have correct types by gdbus");
542 let result = set_property_func(
543 connection,
544 sender,
545 object_path,
546 &interface_name,
547 &property_name,
548 value,
549 )
550 .map(|_| None);
551 invocation.return_result(result);
552}
553
554// Re-implementation of `invoke_get_all_properties_in_idle_cb` in gdbusconnection.c
555fn handle_get_all_properties(
556 connection: DBusConnection,
557 sender: Option<&str>,
558 object_path: &str,
559 interface_info: &DBusInterfaceInfo,
560 invocation: DBusMethodInvocation,
561 get_property_func: &dyn Fn(
562 DBusConnection,
563 Option<&str>,
564 &str,
565 &str,
566 &str,
567 ) -> Result<glib::Variant, glib::Error>,
568) {
569 // Interface name is already validated by gdbus
570
571 let interface_name = interface_info.name();
572 let readable_properties = interface_info
573 .properties()
574 .filter(|p| p.flags().contains(DBusPropertyInfoFlags::READABLE));
575
576 let property_values = glib::VariantDict::default();
577 for property in readable_properties {
578 let property_name = property.name();
579 if let Ok(value) = get_property_func(
580 connection.clone(),
581 sender,
582 object_path,
583 interface_name,
584 property_name,
585 ) {
586 property_values.insert(property_name, value);
587 }
588 }
589
590 invocation.return_value(Some(&(property_values,).to_variant()));
591}
592
593impl DBusConnection {
594 /// Registers callbacks for exported objects at @object_path with the
595 /// D-Bus interface that is described in @interface_info.
596 ///
597 /// Calls to functions in @vtable (and @user_data_free_func) will happen
598 /// in the thread-default main context
599 /// (see [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()])
600 /// of the thread you are calling this method from.
601 ///
602 /// Note that all #GVariant values passed to functions in @vtable will match
603 /// the signature given in @interface_info - if a remote caller passes
604 /// incorrect values, the `org.freedesktop.DBus.Error.InvalidArgs`
605 /// is returned to the remote caller.
606 ///
607 /// Additionally, if the remote caller attempts to invoke methods or
608 /// access properties not mentioned in @interface_info the
609 /// `org.freedesktop.DBus.Error.UnknownMethod` resp.
610 /// `org.freedesktop.DBus.Error.InvalidArgs` errors
611 /// are returned to the caller.
612 ///
613 /// It is considered a programming error if the
614 /// #GDBusInterfaceGetPropertyFunc function in @vtable returns a
615 /// #GVariant of incorrect type.
616 ///
617 /// If an existing callback is already registered at @object_path and
618 /// @interface_name, then @error is set to [`IOErrorEnum::Exists`][crate::IOErrorEnum::Exists].
619 ///
620 /// GDBus automatically implements the standard D-Bus interfaces
621 /// org.freedesktop.DBus.Properties, org.freedesktop.DBus.Introspectable
622 /// and org.freedesktop.Peer, so you don't have to implement those for the
623 /// objects you export. You can implement org.freedesktop.DBus.Properties
624 /// yourself, e.g. to handle getting and setting of properties asynchronously.
625 ///
626 /// Note that the reference count on @interface_info will be
627 /// incremented by 1 (unless allocated statically, e.g. if the
628 /// reference count is -1, see g_dbus_interface_info_ref()) for as long
629 /// as the object is exported. Also note that @vtable will be copied.
630 ///
631 /// See this [server][`DBusConnection`][crate::DBusConnection]#an-example-d-bus-server]
632 /// for an example of how to use this method.
633 /// ## `object_path`
634 /// the object path to register at
635 /// ## `interface_info`
636 /// introspection data for the interface
637 /// ## `vtable`
638 /// a #GDBusInterfaceVTable to call into or [`None`]
639 ///
640 /// # Returns
641 ///
642 /// 0 if @error is set, otherwise a registration id (never 0)
643 /// that can be used with g_dbus_connection_unregister_object()
644 #[doc(alias = "g_dbus_connection_register_object")]
645 #[doc(alias = "g_dbus_connection_register_object_with_closures")]
646 pub fn register_object<'a>(
647 &'a self,
648 object_path: &'a str,
649 interface_info: &'a DBusInterfaceInfo,
650 ) -> RegistrationBuilder<'a> {
651 RegistrationBuilder {
652 connection: self,
653 object_path,
654 interface_info,
655 method_call: None,
656 get_property: None,
657 set_property: None,
658 }
659 }
660
661 /// Unregisters an object.
662 /// ## `registration_id`
663 /// a registration id obtained from
664 /// g_dbus_connection_register_object()
665 ///
666 /// # Returns
667 ///
668 /// [`true`] if the object was unregistered, [`false`] otherwise
669 #[doc(alias = "g_dbus_connection_unregister_object")]
670 pub fn unregister_object(
671 &self,
672 registration_id: RegistrationId,
673 ) -> Result<(), glib::error::BoolError> {
674 unsafe {
675 glib::result_from_gboolean!(
676 ffi::g_dbus_connection_unregister_object(
677 self.to_glib_none().0,
678 registration_id.0.into()
679 ),
680 "Failed to unregister D-Bus object"
681 )
682 }
683 }
684
685 /// Exports @action_group on @self at @object_path.
686 ///
687 /// The implemented D-Bus API should be considered private. It is
688 /// subject to change in the future.
689 ///
690 /// A given object path can only have one action group exported on it.
691 /// If this constraint is violated, the export will fail and 0 will be
692 /// returned (with @error set accordingly).
693 ///
694 /// You can unexport the action group using
695 /// [`unexport_action_group()`][Self::unexport_action_group()] with the return value of
696 /// this function.
697 ///
698 /// The thread default main context is taken at the time of this call.
699 /// All incoming action activations and state change requests are
700 /// reported from this context. Any changes on the action group that
701 /// cause it to emit signals must also come from this same context.
702 /// Since incoming action activations and state change requests are
703 /// rather likely to cause changes on the action group, this effectively
704 /// limits a given action group to being exported from only one main
705 /// context.
706 /// ## `object_path`
707 /// a D-Bus object path
708 /// ## `action_group`
709 /// an action group
710 ///
711 /// # Returns
712 ///
713 /// the ID of the export (never zero), or 0 in case of failure
714 #[doc(alias = "g_dbus_connection_export_action_group")]
715 pub fn export_action_group<P: IsA<ActionGroup>>(
716 &self,
717 object_path: &str,
718 action_group: &P,
719 ) -> Result<ActionGroupExportId, glib::Error> {
720 unsafe {
721 let mut error = std::ptr::null_mut();
722 let id = ffi::g_dbus_connection_export_action_group(
723 self.to_glib_none().0,
724 object_path.to_glib_none().0,
725 action_group.as_ref().to_glib_none().0,
726 &mut error,
727 );
728 if error.is_null() {
729 Ok(ActionGroupExportId(NonZeroU32::new_unchecked(id)))
730 } else {
731 Err(from_glib_full(error))
732 }
733 }
734 }
735
736 /// Reverses the effect of a previous call to
737 /// [`export_action_group()`][Self::export_action_group()].
738 ///
739 /// It is an error to call this function with an ID that wasn’t returned from
740 /// [`export_action_group()`][Self::export_action_group()] or to call it with the same
741 /// ID more than once.
742 /// ## `export_id`
743 /// the ID from [`export_action_group()`][Self::export_action_group()]
744 #[doc(alias = "g_dbus_connection_unexport_action_group")]
745 pub fn unexport_action_group(&self, export_id: ActionGroupExportId) {
746 unsafe {
747 ffi::g_dbus_connection_unexport_action_group(self.to_glib_none().0, export_id.0.into());
748 }
749 }
750
751 /// Exports @menu on @self at @object_path.
752 ///
753 /// The implemented D-Bus API should be considered private.
754 /// It is subject to change in the future.
755 ///
756 /// An object path can only have one menu model exported on it. If this
757 /// constraint is violated, the export will fail and 0 will be
758 /// returned (with @error set accordingly).
759 ///
760 /// Exporting menus with sections containing more than
761 /// `G_MENU_EXPORTER_MAX_SECTION_SIZE` items is not supported and results in
762 /// undefined behavior.
763 ///
764 /// You can unexport the menu model using
765 /// g_dbus_connection_unexport_menu_model() with the return value of
766 /// this function.
767 /// ## `object_path`
768 /// a D-Bus object path
769 /// ## `menu`
770 /// a #GMenuModel
771 ///
772 /// # Returns
773 ///
774 /// the ID of the export (never zero), or 0 in case of failure
775 #[doc(alias = "g_dbus_connection_export_menu_model")]
776 pub fn export_menu_model<P: IsA<MenuModel>>(
777 &self,
778 object_path: &str,
779 menu: &P,
780 ) -> Result<MenuModelExportId, glib::Error> {
781 unsafe {
782 let mut error = std::ptr::null_mut();
783 let id = ffi::g_dbus_connection_export_menu_model(
784 self.to_glib_none().0,
785 object_path.to_glib_none().0,
786 menu.as_ref().to_glib_none().0,
787 &mut error,
788 );
789 if error.is_null() {
790 Ok(MenuModelExportId(NonZeroU32::new_unchecked(id)))
791 } else {
792 Err(from_glib_full(error))
793 }
794 }
795 }
796
797 /// Reverses the effect of a previous call to
798 /// g_dbus_connection_export_menu_model().
799 ///
800 /// It is an error to call this function with an ID that wasn't returned
801 /// from g_dbus_connection_export_menu_model() or to call it with the
802 /// same ID more than once.
803 /// ## `export_id`
804 /// the ID from g_dbus_connection_export_menu_model()
805 #[doc(alias = "g_dbus_connection_unexport_menu_model")]
806 pub fn unexport_menu_model(&self, export_id: MenuModelExportId) {
807 unsafe {
808 ffi::g_dbus_connection_unexport_menu_model(self.to_glib_none().0, export_id.0.into());
809 }
810 }
811
812 /// Adds a message filter. Filters are handlers that are run on all
813 /// incoming and outgoing messages, prior to standard dispatch. Filters
814 /// are run in the order that they were added. The same handler can be
815 /// added as a filter more than once, in which case it will be run more
816 /// than once. Filters added during a filter callback won't be run on
817 /// the message being processed. Filter functions are allowed to modify
818 /// and even drop messages.
819 ///
820 /// Note that filters are run in a dedicated message handling thread so
821 /// they can't block and, generally, can't do anything but signal a
822 /// worker thread. Also note that filters are rarely needed - use API
823 /// such as g_dbus_connection_send_message_with_reply(),
824 /// g_dbus_connection_signal_subscribe() or g_dbus_connection_call() instead.
825 ///
826 /// If a filter consumes an incoming message the message is not
827 /// dispatched anywhere else - not even the standard dispatch machinery
828 /// (that API such as g_dbus_connection_signal_subscribe() and
829 /// g_dbus_connection_send_message_with_reply() relies on) will see the
830 /// message. Similarly, if a filter consumes an outgoing message, the
831 /// message will not be sent to the other peer.
832 ///
833 /// If @user_data_free_func is non-[`None`], it will be called (in the
834 /// thread-default main context of the thread you are calling this
835 /// method from) at some point after @user_data is no longer
836 /// needed. (It is not guaranteed to be called synchronously when the
837 /// filter is removed, and may be called after @self has been
838 /// destroyed.)
839 /// ## `filter_function`
840 /// a filter function
841 ///
842 /// # Returns
843 ///
844 /// a filter identifier that can be used with
845 /// g_dbus_connection_remove_filter()
846 #[doc(alias = "g_dbus_connection_add_filter")]
847 pub fn add_filter<
848 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
849 >(
850 &self,
851 filter_function: P,
852 ) -> FilterId {
853 let filter_function_data: Box_<P> = Box_::new(filter_function);
854 unsafe extern "C" fn filter_function_func<
855 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
856 >(
857 connection: *mut ffi::GDBusConnection,
858 message: *mut ffi::GDBusMessage,
859 incoming: glib::ffi::gboolean,
860 user_data: glib::ffi::gpointer,
861 ) -> *mut ffi::GDBusMessage {
862 unsafe {
863 let connection = from_glib_borrow(connection);
864 let message = from_glib_full(message);
865 let incoming = from_glib(incoming);
866 let callback: &P = &*(user_data as *mut _);
867 let res = (*callback)(&connection, &message, incoming);
868 res.into_glib_ptr()
869 }
870 }
871 let filter_function = Some(filter_function_func::<P> as _);
872 unsafe extern "C" fn user_data_free_func_func<
873 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
874 >(
875 data: glib::ffi::gpointer,
876 ) {
877 unsafe {
878 let _callback: Box_<P> = Box_::from_raw(data as *mut _);
879 }
880 }
881 let destroy_call3 = Some(user_data_free_func_func::<P> as _);
882 let super_callback0: Box_<P> = filter_function_data;
883 unsafe {
884 let id = ffi::g_dbus_connection_add_filter(
885 self.to_glib_none().0,
886 filter_function,
887 Box_::into_raw(super_callback0) as *mut _,
888 destroy_call3,
889 );
890 FilterId(NonZeroU32::new_unchecked(id))
891 }
892 }
893
894 /// Removes a filter.
895 ///
896 /// Note that since filters run in a different thread, there is a race
897 /// condition where it is possible that the filter will be running even
898 /// after calling g_dbus_connection_remove_filter(), so you cannot just
899 /// free data that the filter might be using. Instead, you should pass
900 /// a #GDestroyNotify to g_dbus_connection_add_filter(), which will be
901 /// called when it is guaranteed that the data is no longer needed.
902 /// ## `filter_id`
903 /// an identifier obtained from g_dbus_connection_add_filter()
904 #[doc(alias = "g_dbus_connection_remove_filter")]
905 pub fn remove_filter(&self, filter_id: FilterId) {
906 unsafe {
907 ffi::g_dbus_connection_remove_filter(self.to_glib_none().0, filter_id.0.into());
908 }
909 }
910
911 // rustdoc-stripper-ignore-next
912 /// Subscribe to a D-Bus signal.
913 ///
914 /// See [`Self::signal_subscribe`] for arguments.
915 ///
916 /// Return a signal subscription which keeps a reference to this D-Bus
917 /// connection and unsubscribes from the signal when dropped.
918 ///
919 /// To avoid reference cycles you may wish to downgrade the returned
920 /// subscription to a weak one with [`SignalSubscription::downgrade`].
921 #[must_use]
922 pub fn subscribe_to_signal<P: Fn(DBusSignalRef) + 'static>(
923 &self,
924 sender: Option<&str>,
925 interface_name: Option<&str>,
926 member: Option<&str>,
927 object_path: Option<&str>,
928 arg0: Option<&str>,
929 flags: DBusSignalFlags,
930 callback: P,
931 ) -> SignalSubscription {
932 #[allow(deprecated)]
933 let id = self.signal_subscribe(
934 sender,
935 interface_name,
936 member,
937 object_path,
938 arg0,
939 flags,
940 move |connection, sender_name, object_path, interface_name, signal_name, parameters| {
941 callback(DBusSignalRef {
942 connection,
943 sender_name,
944 object_path,
945 interface_name,
946 signal_name,
947 parameters,
948 });
949 },
950 );
951 SignalSubscription(self.clone(), Some(id))
952 }
953
954 /// Subscribes to signals on @self and invokes @callback whenever
955 /// the signal is received. Note that @callback will be invoked in the
956 /// thread-default main context (see [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()])
957 /// of the thread you are calling this method from.
958 ///
959 /// If @self is not a message bus connection, @sender must be
960 /// [`None`].
961 ///
962 /// If @sender is a well-known name note that @callback is invoked with
963 /// the unique name for the owner of @sender, not the well-known name
964 /// as one would expect. This is because the message bus rewrites the
965 /// name. As such, to avoid certain race conditions, users should be
966 /// tracking the name owner of the well-known name and use that when
967 /// processing the received signal.
968 ///
969 /// If one of [`DBusSignalFlags::MATCH_ARG0_NAMESPACE`][crate::DBusSignalFlags::MATCH_ARG0_NAMESPACE] or
970 /// [`DBusSignalFlags::MATCH_ARG0_PATH`][crate::DBusSignalFlags::MATCH_ARG0_PATH] are given, @arg0 is
971 /// interpreted as part of a namespace or path. The first argument
972 /// of a signal is matched against that part as specified by D-Bus.
973 ///
974 /// If @user_data_free_func is non-[`None`], it will be called (in the
975 /// thread-default main context of the thread you are calling this
976 /// method from) at some point after @user_data is no longer
977 /// needed. (It is not guaranteed to be called synchronously when the
978 /// signal is unsubscribed from, and may be called after @self
979 /// has been destroyed.)
980 ///
981 /// As @callback is potentially invoked in a different thread from where it’s
982 /// emitted, it’s possible for this to happen after
983 /// g_dbus_connection_signal_unsubscribe() has been called in another thread.
984 /// Due to this, @user_data should have a strong reference which is freed with
985 /// @user_data_free_func, rather than pointing to data whose lifecycle is tied
986 /// to the signal subscription. For example, if a #GObject is used to store the
987 /// subscription ID from g_dbus_connection_signal_subscribe(), a strong reference
988 /// to that #GObject must be passed to @user_data, and g_object_unref() passed to
989 /// @user_data_free_func. You are responsible for breaking the resulting
990 /// reference count cycle by explicitly unsubscribing from the signal when
991 /// dropping the last external reference to the #GObject. Alternatively, a weak
992 /// reference may be used.
993 ///
994 /// It is guaranteed that if you unsubscribe from a signal using
995 /// g_dbus_connection_signal_unsubscribe() from the same thread which made the
996 /// corresponding g_dbus_connection_signal_subscribe() call, @callback will not
997 /// be invoked after g_dbus_connection_signal_unsubscribe() returns.
998 ///
999 /// The returned subscription identifier is an opaque value which is guaranteed
1000 /// to never be zero.
1001 ///
1002 /// This function can never fail.
1003 /// ## `sender`
1004 /// sender name to match on (unique or well-known name)
1005 /// or [`None`] to listen from all senders
1006 /// ## `interface_name`
1007 /// D-Bus interface name to match on or [`None`] to
1008 /// match on all interfaces
1009 /// ## `member`
1010 /// D-Bus signal name to match on or [`None`] to match on
1011 /// all signals
1012 /// ## `object_path`
1013 /// object path to match on or [`None`] to match on
1014 /// all object paths
1015 /// ## `arg0`
1016 /// contents of first string argument to match on or [`None`]
1017 /// to match on all kinds of arguments
1018 /// ## `flags`
1019 /// #GDBusSignalFlags describing how arg0 is used in subscribing to the
1020 /// signal
1021 /// ## `callback`
1022 /// callback to invoke when there is a signal matching the requested data
1023 ///
1024 /// # Returns
1025 ///
1026 /// a subscription identifier that can be used with g_dbus_connection_signal_unsubscribe()
1027 #[doc(alias = "g_dbus_connection_signal_subscribe")]
1028 #[allow(clippy::too_many_arguments)]
1029 #[deprecated(note = "Prefer subscribe_to_signal")]
1030 pub fn signal_subscribe<
1031 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
1032 >(
1033 &self,
1034 sender: Option<&str>,
1035 interface_name: Option<&str>,
1036 member: Option<&str>,
1037 object_path: Option<&str>,
1038 arg0: Option<&str>,
1039 flags: DBusSignalFlags,
1040 callback: P,
1041 ) -> SignalSubscriptionId {
1042 let callback_data: Box_<P> = Box_::new(callback);
1043 unsafe extern "C" fn callback_func<
1044 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
1045 >(
1046 connection: *mut ffi::GDBusConnection,
1047 sender_name: *const libc::c_char,
1048 object_path: *const libc::c_char,
1049 interface_name: *const libc::c_char,
1050 signal_name: *const libc::c_char,
1051 parameters: *mut glib::ffi::GVariant,
1052 user_data: glib::ffi::gpointer,
1053 ) {
1054 unsafe {
1055 let connection = from_glib_borrow(connection);
1056 let sender_name: Borrowed<glib::GString> = from_glib_borrow(sender_name);
1057 let object_path: Borrowed<glib::GString> = from_glib_borrow(object_path);
1058 let interface_name: Borrowed<glib::GString> = from_glib_borrow(interface_name);
1059 let signal_name: Borrowed<glib::GString> = from_glib_borrow(signal_name);
1060 let parameters = from_glib_borrow(parameters);
1061 let callback: &P = &*(user_data as *mut _);
1062 (*callback)(
1063 &connection,
1064 sender_name.as_str(),
1065 object_path.as_str(),
1066 interface_name.as_str(),
1067 signal_name.as_str(),
1068 ¶meters,
1069 );
1070 }
1071 }
1072 let callback = Some(callback_func::<P> as _);
1073 unsafe extern "C" fn user_data_free_func_func<
1074 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
1075 >(
1076 data: glib::ffi::gpointer,
1077 ) {
1078 unsafe {
1079 let _callback: Box_<P> = Box_::from_raw(data as *mut _);
1080 }
1081 }
1082 let destroy_call9 = Some(user_data_free_func_func::<P> as _);
1083 let super_callback0: Box_<P> = callback_data;
1084 unsafe {
1085 let id = ffi::g_dbus_connection_signal_subscribe(
1086 self.to_glib_none().0,
1087 sender.to_glib_none().0,
1088 interface_name.to_glib_none().0,
1089 member.to_glib_none().0,
1090 object_path.to_glib_none().0,
1091 arg0.to_glib_none().0,
1092 flags.into_glib(),
1093 callback,
1094 Box_::into_raw(super_callback0) as *mut _,
1095 destroy_call9,
1096 );
1097 SignalSubscriptionId(NonZeroU32::new_unchecked(id))
1098 }
1099 }
1100
1101 /// Unsubscribes from signals.
1102 ///
1103 /// Note that there may still be D-Bus traffic to process (relating to this
1104 /// signal subscription) in the current thread-default #GMainContext after this
1105 /// function has returned. You should continue to iterate the #GMainContext
1106 /// until the #GDestroyNotify function passed to
1107 /// g_dbus_connection_signal_subscribe() is called, in order to avoid memory
1108 /// leaks through callbacks queued on the #GMainContext after it’s stopped being
1109 /// iterated.
1110 /// Alternatively, any idle source with a priority lower than `G_PRIORITY_DEFAULT`
1111 /// that was scheduled after unsubscription, also indicates that all resources
1112 /// of this subscription are released.
1113 /// ## `subscription_id`
1114 /// a subscription id obtained from
1115 /// g_dbus_connection_signal_subscribe()
1116 #[doc(alias = "g_dbus_connection_signal_unsubscribe")]
1117 #[deprecated(note = "Prefer subscribe_to_signal")]
1118 pub fn signal_unsubscribe(&self, subscription_id: SignalSubscriptionId) {
1119 unsafe {
1120 ffi::g_dbus_connection_signal_unsubscribe(
1121 self.to_glib_none().0,
1122 subscription_id.0.into(),
1123 );
1124 }
1125 }
1126
1127 // rustdoc-stripper-ignore-next
1128 /// Subscribe to a D-Bus signal and receive signal emissions as a stream.
1129 ///
1130 /// See [`Self::signal_subscribe`] for arguments. `map_signal` maps the
1131 /// received signal to the stream's element.
1132 ///
1133 /// The returned stream holds a strong reference to this D-Bus connection,
1134 /// and unsubscribes from the signal when dropped. To avoid reference cycles
1135 /// you may wish to downgrade the returned stream to hold only weak
1136 /// reference to the connection using [`SubscribedSignalStream::downgrade`].
1137 ///
1138 /// After invoking `map_signal` the stream threads incoming signals through
1139 /// an unbounded channel. Hence, memory consumption will keep increasing
1140 /// as long as the stream consumer does not keep up with signal emissions.
1141 /// If you need to perform expensive processing in response to signals it's
1142 /// therefore recommended to insert an extra buffering and if the buffer
1143 /// overruns, either fail drop the entire stream, or drop individual signal
1144 /// emissions until the buffer has space again.
1145 pub fn receive_signal<T: 'static, F: Fn(DBusSignalRef) -> T + 'static>(
1146 &self,
1147 sender: Option<&str>,
1148 interface_name: Option<&str>,
1149 member: Option<&str>,
1150 object_path: Option<&str>,
1151 arg0: Option<&str>,
1152 flags: DBusSignalFlags,
1153 map_signal: F,
1154 ) -> SubscribedSignalStream<SignalSubscription, impl Stream<Item = T> + use<T, F>> {
1155 let (tx, rx) = mpsc::unbounded();
1156 let subscription = self.subscribe_to_signal(
1157 sender,
1158 interface_name,
1159 member,
1160 object_path,
1161 arg0,
1162 flags,
1163 move |signal| {
1164 // Just ignore send errors: if the receiver is dropped, the
1165 // signal subscription is dropped too, so the callback won't
1166 // be invoked anymore.
1167 let _ = tx.unbounded_send(map_signal(signal));
1168 },
1169 );
1170 SubscribedSignalStream {
1171 subscription,
1172 stream: rx,
1173 }
1174 }
1175
1176 // rustdoc-stripper-ignore-next
1177 /// Subscribe to a D-Bus signal and receive signal parameters as a stream.
1178 ///
1179 /// Like [`Self::receive_signal`] (which see for more information), but
1180 /// automatically decodes the emitted signal parameters to type `T`.
1181 /// If decoding fails the corresponding variant type error is sent
1182 /// downstream.
1183 pub fn receive_signal_parameters<T>(
1184 &self,
1185 sender: Option<&str>,
1186 interface_name: Option<&str>,
1187 member: Option<&str>,
1188 object_path: Option<&str>,
1189 arg0: Option<&str>,
1190 flags: DBusSignalFlags,
1191 ) -> SubscribedSignalStream<
1192 SignalSubscription,
1193 impl Stream<Item = Result<T, VariantTypeMismatchError>> + use<T>,
1194 >
1195 where
1196 T: FromVariant + 'static,
1197 {
1198 self.receive_signal(
1199 sender,
1200 interface_name,
1201 member,
1202 object_path,
1203 arg0,
1204 flags,
1205 |signal| signal.parameters.try_get(),
1206 )
1207 }
1208}