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 futures_channel::mpsc;
10use futures_core::{FusedStream, Stream};
11use glib::{prelude::*, translate::*, variant::VariantTypeMismatchError, WeakRef};
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 if let Some(connection) = self.0.upgrade() {
186 #[allow(deprecated)]
187 connection.signal_unsubscribe(id);
188 }
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:
311 Option<Box_<dyn Fn(DBusConnection, Option<&str>, &str, &str, &str) -> glib::Variant>>,
312 #[allow(clippy::type_complexity)]
313 set_property:
314 Option<Box_<dyn Fn(DBusConnection, Option<&str>, &str, &str, &str, glib::Variant) -> bool>>,
315}
316
317impl<'a> RegistrationBuilder<'a> {
318 pub fn method_call<
319 F: Fn(
320 DBusConnection,
321 Option<&str>,
322 &str,
323 Option<&str>,
324 &str,
325 glib::Variant,
326 DBusMethodInvocation,
327 ) + 'static,
328 >(
329 mut self,
330 f: F,
331 ) -> Self {
332 self.method_call = Some(Box_::new(f));
333 self
334 }
335
336 // rustdoc-stripper-ignore-next
337 /// Handle method calls on this object.
338 ///
339 /// Return a builder for method calls which parses method names and
340 /// parameters with the given [`DBusMethodCall`] and then allows to dispatch
341 /// the parsed call either synchronously or asynchronously.
342 pub fn typed_method_call<T: DBusMethodCall>(self) -> MethodCallBuilder<'a, T> {
343 MethodCallBuilder {
344 registration: self,
345 capture_type: Default::default(),
346 }
347 }
348
349 #[doc(alias = "get_property")]
350 pub fn property<
351 F: Fn(DBusConnection, Option<&str>, &str, &str, &str) -> glib::Variant + 'static,
352 >(
353 mut self,
354 f: F,
355 ) -> Self {
356 self.get_property = Some(Box_::new(f));
357 self
358 }
359
360 pub fn set_property<
361 F: Fn(DBusConnection, Option<&str>, &str, &str, &str, glib::Variant) -> bool + 'static,
362 >(
363 mut self,
364 f: F,
365 ) -> Self {
366 self.set_property = Some(Box_::new(f));
367 self
368 }
369
370 pub fn build(self) -> Result<RegistrationId, glib::Error> {
371 unsafe {
372 let mut error = std::ptr::null_mut();
373 let id = ffi::g_dbus_connection_register_object_with_closures(
374 self.connection.to_glib_none().0,
375 self.object_path.to_glib_none().0,
376 self.interface_info.to_glib_none().0,
377 self.method_call
378 .map(|f| {
379 glib::Closure::new_local(move |args| {
380 let conn = args[0].get::<DBusConnection>().unwrap();
381 let sender = args[1].get::<Option<&str>>().unwrap();
382 let object_path = args[2].get::<&str>().unwrap();
383 let interface_name = args[3].get::<Option<&str>>().unwrap();
384 let method_name = args[4].get::<&str>().unwrap();
385 let parameters = args[5].get::<glib::Variant>().unwrap();
386
387 // Work around GLib memory leak: Assume that the invocation is passed
388 // as `transfer full` into the closure.
389 //
390 // This workaround is not going to break with future versions of
391 // GLib as fixing the bug was considered a breaking API change.
392 //
393 // See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4427
394 let invocation = from_glib_full(glib::gobject_ffi::g_value_get_object(
395 args[6].as_ptr(),
396 )
397 as *mut ffi::GDBusMethodInvocation);
398
399 f(
400 conn,
401 sender,
402 object_path,
403 interface_name,
404 method_name,
405 parameters,
406 invocation,
407 );
408 None
409 })
410 })
411 .to_glib_none()
412 .0,
413 self.get_property
414 .map(|f| {
415 glib::Closure::new_local(move |args| {
416 let conn = args[0].get::<DBusConnection>().unwrap();
417 let sender = args[1].get::<Option<&str>>().unwrap();
418 let object_path = args[2].get::<&str>().unwrap();
419 let interface_name = args[3].get::<&str>().unwrap();
420 let property_name = args[4].get::<&str>().unwrap();
421 let result =
422 f(conn, sender, object_path, interface_name, property_name);
423 Some(result.to_value())
424 })
425 })
426 .to_glib_none()
427 .0,
428 self.set_property
429 .map(|f| {
430 glib::Closure::new_local(move |args| {
431 let conn = args[0].get::<DBusConnection>().unwrap();
432 let sender = args[1].get::<Option<&str>>().unwrap();
433 let object_path = args[2].get::<&str>().unwrap();
434 let interface_name = args[3].get::<&str>().unwrap();
435 let property_name = args[4].get::<&str>().unwrap();
436 let value = args[5].get::<glib::Variant>().unwrap();
437 let result = f(
438 conn,
439 sender,
440 object_path,
441 interface_name,
442 property_name,
443 value,
444 );
445 Some(result.to_value())
446 })
447 })
448 .to_glib_none()
449 .0,
450 &mut error,
451 );
452
453 if error.is_null() {
454 Ok(RegistrationId(NonZeroU32::new_unchecked(id)))
455 } else {
456 Err(from_glib_full(error))
457 }
458 }
459 }
460}
461
462impl DBusConnection {
463 /// Registers callbacks for exported objects at @object_path with the
464 /// D-Bus interface that is described in @interface_info.
465 ///
466 /// Calls to functions in @vtable (and @user_data_free_func) will happen
467 /// in the thread-default main context
468 /// (see [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()])
469 /// of the thread you are calling this method from.
470 ///
471 /// Note that all #GVariant values passed to functions in @vtable will match
472 /// the signature given in @interface_info - if a remote caller passes
473 /// incorrect values, the `org.freedesktop.DBus.Error.InvalidArgs`
474 /// is returned to the remote caller.
475 ///
476 /// Additionally, if the remote caller attempts to invoke methods or
477 /// access properties not mentioned in @interface_info the
478 /// `org.freedesktop.DBus.Error.UnknownMethod` resp.
479 /// `org.freedesktop.DBus.Error.InvalidArgs` errors
480 /// are returned to the caller.
481 ///
482 /// It is considered a programming error if the
483 /// #GDBusInterfaceGetPropertyFunc function in @vtable returns a
484 /// #GVariant of incorrect type.
485 ///
486 /// If an existing callback is already registered at @object_path and
487 /// @interface_name, then @error is set to [`IOErrorEnum::Exists`][crate::IOErrorEnum::Exists].
488 ///
489 /// GDBus automatically implements the standard D-Bus interfaces
490 /// org.freedesktop.DBus.Properties, org.freedesktop.DBus.Introspectable
491 /// and org.freedesktop.Peer, so you don't have to implement those for the
492 /// objects you export. You can implement org.freedesktop.DBus.Properties
493 /// yourself, e.g. to handle getting and setting of properties asynchronously.
494 ///
495 /// Note that the reference count on @interface_info will be
496 /// incremented by 1 (unless allocated statically, e.g. if the
497 /// reference count is -1, see g_dbus_interface_info_ref()) for as long
498 /// as the object is exported. Also note that @vtable will be copied.
499 ///
500 /// See this [server][`DBusConnection`][crate::DBusConnection]#an-example-d-bus-server]
501 /// for an example of how to use this method.
502 /// ## `object_path`
503 /// the object path to register at
504 /// ## `interface_info`
505 /// introspection data for the interface
506 /// ## `vtable`
507 /// a #GDBusInterfaceVTable to call into or [`None`]
508 ///
509 /// # Returns
510 ///
511 /// 0 if @error is set, otherwise a registration id (never 0)
512 /// that can be used with g_dbus_connection_unregister_object()
513 #[doc(alias = "g_dbus_connection_register_object")]
514 #[doc(alias = "g_dbus_connection_register_object_with_closures")]
515 pub fn register_object<'a>(
516 &'a self,
517 object_path: &'a str,
518 interface_info: &'a DBusInterfaceInfo,
519 ) -> RegistrationBuilder<'a> {
520 RegistrationBuilder {
521 connection: self,
522 object_path,
523 interface_info,
524 method_call: None,
525 get_property: None,
526 set_property: None,
527 }
528 }
529
530 /// Unregisters an object.
531 /// ## `registration_id`
532 /// a registration id obtained from
533 /// g_dbus_connection_register_object()
534 ///
535 /// # Returns
536 ///
537 /// [`true`] if the object was unregistered, [`false`] otherwise
538 #[doc(alias = "g_dbus_connection_unregister_object")]
539 pub fn unregister_object(
540 &self,
541 registration_id: RegistrationId,
542 ) -> Result<(), glib::error::BoolError> {
543 unsafe {
544 glib::result_from_gboolean!(
545 ffi::g_dbus_connection_unregister_object(
546 self.to_glib_none().0,
547 registration_id.0.into()
548 ),
549 "Failed to unregister D-Bus object"
550 )
551 }
552 }
553
554 /// Exports @action_group on @self at @object_path.
555 ///
556 /// The implemented D-Bus API should be considered private. It is
557 /// subject to change in the future.
558 ///
559 /// A given object path can only have one action group exported on it.
560 /// If this constraint is violated, the export will fail and 0 will be
561 /// returned (with @error set accordingly).
562 ///
563 /// You can unexport the action group using
564 /// [`unexport_action_group()`][Self::unexport_action_group()] with the return value of
565 /// this function.
566 ///
567 /// The thread default main context is taken at the time of this call.
568 /// All incoming action activations and state change requests are
569 /// reported from this context. Any changes on the action group that
570 /// cause it to emit signals must also come from this same context.
571 /// Since incoming action activations and state change requests are
572 /// rather likely to cause changes on the action group, this effectively
573 /// limits a given action group to being exported from only one main
574 /// context.
575 /// ## `object_path`
576 /// a D-Bus object path
577 /// ## `action_group`
578 /// an action group
579 ///
580 /// # Returns
581 ///
582 /// the ID of the export (never zero), or 0 in case of failure
583 #[doc(alias = "g_dbus_connection_export_action_group")]
584 pub fn export_action_group<P: IsA<ActionGroup>>(
585 &self,
586 object_path: &str,
587 action_group: &P,
588 ) -> Result<ActionGroupExportId, glib::Error> {
589 unsafe {
590 let mut error = std::ptr::null_mut();
591 let id = ffi::g_dbus_connection_export_action_group(
592 self.to_glib_none().0,
593 object_path.to_glib_none().0,
594 action_group.as_ref().to_glib_none().0,
595 &mut error,
596 );
597 if error.is_null() {
598 Ok(ActionGroupExportId(NonZeroU32::new_unchecked(id)))
599 } else {
600 Err(from_glib_full(error))
601 }
602 }
603 }
604
605 /// Reverses the effect of a previous call to
606 /// [`export_action_group()`][Self::export_action_group()].
607 ///
608 /// It is an error to call this function with an ID that wasn’t returned from
609 /// [`export_action_group()`][Self::export_action_group()] or to call it with the same
610 /// ID more than once.
611 /// ## `export_id`
612 /// the ID from [`export_action_group()`][Self::export_action_group()]
613 #[doc(alias = "g_dbus_connection_unexport_action_group")]
614 pub fn unexport_action_group(&self, export_id: ActionGroupExportId) {
615 unsafe {
616 ffi::g_dbus_connection_unexport_action_group(self.to_glib_none().0, export_id.0.into());
617 }
618 }
619
620 /// Exports @menu on @self at @object_path.
621 ///
622 /// The implemented D-Bus API should be considered private.
623 /// It is subject to change in the future.
624 ///
625 /// An object path can only have one menu model exported on it. If this
626 /// constraint is violated, the export will fail and 0 will be
627 /// returned (with @error set accordingly).
628 ///
629 /// Exporting menus with sections containing more than
630 /// `G_MENU_EXPORTER_MAX_SECTION_SIZE` items is not supported and results in
631 /// undefined behavior.
632 ///
633 /// You can unexport the menu model using
634 /// g_dbus_connection_unexport_menu_model() with the return value of
635 /// this function.
636 /// ## `object_path`
637 /// a D-Bus object path
638 /// ## `menu`
639 /// a #GMenuModel
640 ///
641 /// # Returns
642 ///
643 /// the ID of the export (never zero), or 0 in case of failure
644 #[doc(alias = "g_dbus_connection_export_menu_model")]
645 pub fn export_menu_model<P: IsA<MenuModel>>(
646 &self,
647 object_path: &str,
648 menu: &P,
649 ) -> Result<MenuModelExportId, glib::Error> {
650 unsafe {
651 let mut error = std::ptr::null_mut();
652 let id = ffi::g_dbus_connection_export_menu_model(
653 self.to_glib_none().0,
654 object_path.to_glib_none().0,
655 menu.as_ref().to_glib_none().0,
656 &mut error,
657 );
658 if error.is_null() {
659 Ok(MenuModelExportId(NonZeroU32::new_unchecked(id)))
660 } else {
661 Err(from_glib_full(error))
662 }
663 }
664 }
665
666 /// Reverses the effect of a previous call to
667 /// g_dbus_connection_export_menu_model().
668 ///
669 /// It is an error to call this function with an ID that wasn't returned
670 /// from g_dbus_connection_export_menu_model() or to call it with the
671 /// same ID more than once.
672 /// ## `export_id`
673 /// the ID from g_dbus_connection_export_menu_model()
674 #[doc(alias = "g_dbus_connection_unexport_menu_model")]
675 pub fn unexport_menu_model(&self, export_id: MenuModelExportId) {
676 unsafe {
677 ffi::g_dbus_connection_unexport_menu_model(self.to_glib_none().0, export_id.0.into());
678 }
679 }
680
681 /// Adds a message filter. Filters are handlers that are run on all
682 /// incoming and outgoing messages, prior to standard dispatch. Filters
683 /// are run in the order that they were added. The same handler can be
684 /// added as a filter more than once, in which case it will be run more
685 /// than once. Filters added during a filter callback won't be run on
686 /// the message being processed. Filter functions are allowed to modify
687 /// and even drop messages.
688 ///
689 /// Note that filters are run in a dedicated message handling thread so
690 /// they can't block and, generally, can't do anything but signal a
691 /// worker thread. Also note that filters are rarely needed - use API
692 /// such as g_dbus_connection_send_message_with_reply(),
693 /// g_dbus_connection_signal_subscribe() or g_dbus_connection_call() instead.
694 ///
695 /// If a filter consumes an incoming message the message is not
696 /// dispatched anywhere else - not even the standard dispatch machinery
697 /// (that API such as g_dbus_connection_signal_subscribe() and
698 /// g_dbus_connection_send_message_with_reply() relies on) will see the
699 /// message. Similarly, if a filter consumes an outgoing message, the
700 /// message will not be sent to the other peer.
701 ///
702 /// If @user_data_free_func is non-[`None`], it will be called (in the
703 /// thread-default main context of the thread you are calling this
704 /// method from) at some point after @user_data is no longer
705 /// needed. (It is not guaranteed to be called synchronously when the
706 /// filter is removed, and may be called after @self has been
707 /// destroyed.)
708 /// ## `filter_function`
709 /// a filter function
710 ///
711 /// # Returns
712 ///
713 /// a filter identifier that can be used with
714 /// g_dbus_connection_remove_filter()
715 #[doc(alias = "g_dbus_connection_add_filter")]
716 pub fn add_filter<
717 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
718 >(
719 &self,
720 filter_function: P,
721 ) -> FilterId {
722 let filter_function_data: Box_<P> = Box_::new(filter_function);
723 unsafe extern "C" fn filter_function_func<
724 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
725 >(
726 connection: *mut ffi::GDBusConnection,
727 message: *mut ffi::GDBusMessage,
728 incoming: glib::ffi::gboolean,
729 user_data: glib::ffi::gpointer,
730 ) -> *mut ffi::GDBusMessage {
731 let connection = from_glib_borrow(connection);
732 let message = from_glib_full(message);
733 let incoming = from_glib(incoming);
734 let callback: &P = &*(user_data as *mut _);
735 let res = (*callback)(&connection, &message, incoming);
736 res.into_glib_ptr()
737 }
738 let filter_function = Some(filter_function_func::<P> as _);
739 unsafe extern "C" fn user_data_free_func_func<
740 P: Fn(&DBusConnection, &DBusMessage, bool) -> Option<DBusMessage> + 'static,
741 >(
742 data: glib::ffi::gpointer,
743 ) {
744 let _callback: Box_<P> = Box_::from_raw(data as *mut _);
745 }
746 let destroy_call3 = Some(user_data_free_func_func::<P> as _);
747 let super_callback0: Box_<P> = filter_function_data;
748 unsafe {
749 let id = ffi::g_dbus_connection_add_filter(
750 self.to_glib_none().0,
751 filter_function,
752 Box_::into_raw(super_callback0) as *mut _,
753 destroy_call3,
754 );
755 FilterId(NonZeroU32::new_unchecked(id))
756 }
757 }
758
759 /// Removes a filter.
760 ///
761 /// Note that since filters run in a different thread, there is a race
762 /// condition where it is possible that the filter will be running even
763 /// after calling g_dbus_connection_remove_filter(), so you cannot just
764 /// free data that the filter might be using. Instead, you should pass
765 /// a #GDestroyNotify to g_dbus_connection_add_filter(), which will be
766 /// called when it is guaranteed that the data is no longer needed.
767 /// ## `filter_id`
768 /// an identifier obtained from g_dbus_connection_add_filter()
769 #[doc(alias = "g_dbus_connection_remove_filter")]
770 pub fn remove_filter(&self, filter_id: FilterId) {
771 unsafe {
772 ffi::g_dbus_connection_remove_filter(self.to_glib_none().0, filter_id.0.into());
773 }
774 }
775
776 // rustdoc-stripper-ignore-next
777 /// Subscribe to a D-Bus signal.
778 ///
779 /// See [`Self::signal_subscribe`] for arguments.
780 ///
781 /// Return a signal subscription which keeps a reference to this D-Bus
782 /// connection and unsubscribes from the signal when dropped.
783 ///
784 /// To avoid reference cycles you may wish to downgrade the returned
785 /// subscription to a weak one with [`SignalSubscription::downgrade`].
786 #[must_use]
787 pub fn subscribe_to_signal<P: Fn(DBusSignalRef) + 'static>(
788 &self,
789 sender: Option<&str>,
790 interface_name: Option<&str>,
791 member: Option<&str>,
792 object_path: Option<&str>,
793 arg0: Option<&str>,
794 flags: DBusSignalFlags,
795 callback: P,
796 ) -> SignalSubscription {
797 #[allow(deprecated)]
798 let id = self.signal_subscribe(
799 sender,
800 interface_name,
801 member,
802 object_path,
803 arg0,
804 flags,
805 move |connection, sender_name, object_path, interface_name, signal_name, parameters| {
806 callback(DBusSignalRef {
807 connection,
808 sender_name,
809 object_path,
810 interface_name,
811 signal_name,
812 parameters,
813 });
814 },
815 );
816 SignalSubscription(self.clone(), Some(id))
817 }
818
819 /// Subscribes to signals on @self and invokes @callback whenever
820 /// the signal is received. Note that @callback will be invoked in the
821 /// thread-default main context (see [`glib::MainContext::push_thread_default()`][crate::glib::MainContext::push_thread_default()])
822 /// of the thread you are calling this method from.
823 ///
824 /// If @self is not a message bus connection, @sender must be
825 /// [`None`].
826 ///
827 /// If @sender is a well-known name note that @callback is invoked with
828 /// the unique name for the owner of @sender, not the well-known name
829 /// as one would expect. This is because the message bus rewrites the
830 /// name. As such, to avoid certain race conditions, users should be
831 /// tracking the name owner of the well-known name and use that when
832 /// processing the received signal.
833 ///
834 /// If one of [`DBusSignalFlags::MATCH_ARG0_NAMESPACE`][crate::DBusSignalFlags::MATCH_ARG0_NAMESPACE] or
835 /// [`DBusSignalFlags::MATCH_ARG0_PATH`][crate::DBusSignalFlags::MATCH_ARG0_PATH] are given, @arg0 is
836 /// interpreted as part of a namespace or path. The first argument
837 /// of a signal is matched against that part as specified by D-Bus.
838 ///
839 /// If @user_data_free_func is non-[`None`], it will be called (in the
840 /// thread-default main context of the thread you are calling this
841 /// method from) at some point after @user_data is no longer
842 /// needed. (It is not guaranteed to be called synchronously when the
843 /// signal is unsubscribed from, and may be called after @self
844 /// has been destroyed.)
845 ///
846 /// As @callback is potentially invoked in a different thread from where it’s
847 /// emitted, it’s possible for this to happen after
848 /// g_dbus_connection_signal_unsubscribe() has been called in another thread.
849 /// Due to this, @user_data should have a strong reference which is freed with
850 /// @user_data_free_func, rather than pointing to data whose lifecycle is tied
851 /// to the signal subscription. For example, if a #GObject is used to store the
852 /// subscription ID from g_dbus_connection_signal_subscribe(), a strong reference
853 /// to that #GObject must be passed to @user_data, and g_object_unref() passed to
854 /// @user_data_free_func. You are responsible for breaking the resulting
855 /// reference count cycle by explicitly unsubscribing from the signal when
856 /// dropping the last external reference to the #GObject. Alternatively, a weak
857 /// reference may be used.
858 ///
859 /// It is guaranteed that if you unsubscribe from a signal using
860 /// g_dbus_connection_signal_unsubscribe() from the same thread which made the
861 /// corresponding g_dbus_connection_signal_subscribe() call, @callback will not
862 /// be invoked after g_dbus_connection_signal_unsubscribe() returns.
863 ///
864 /// The returned subscription identifier is an opaque value which is guaranteed
865 /// to never be zero.
866 ///
867 /// This function can never fail.
868 /// ## `sender`
869 /// sender name to match on (unique or well-known name)
870 /// or [`None`] to listen from all senders
871 /// ## `interface_name`
872 /// D-Bus interface name to match on or [`None`] to
873 /// match on all interfaces
874 /// ## `member`
875 /// D-Bus signal name to match on or [`None`] to match on
876 /// all signals
877 /// ## `object_path`
878 /// object path to match on or [`None`] to match on
879 /// all object paths
880 /// ## `arg0`
881 /// contents of first string argument to match on or [`None`]
882 /// to match on all kinds of arguments
883 /// ## `flags`
884 /// #GDBusSignalFlags describing how arg0 is used in subscribing to the
885 /// signal
886 /// ## `callback`
887 /// callback to invoke when there is a signal matching the requested data
888 ///
889 /// # Returns
890 ///
891 /// a subscription identifier that can be used with g_dbus_connection_signal_unsubscribe()
892 #[doc(alias = "g_dbus_connection_signal_subscribe")]
893 #[allow(clippy::too_many_arguments)]
894 #[deprecated(note = "Prefer subscribe_to_signal")]
895 pub fn signal_subscribe<
896 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
897 >(
898 &self,
899 sender: Option<&str>,
900 interface_name: Option<&str>,
901 member: Option<&str>,
902 object_path: Option<&str>,
903 arg0: Option<&str>,
904 flags: DBusSignalFlags,
905 callback: P,
906 ) -> SignalSubscriptionId {
907 let callback_data: Box_<P> = Box_::new(callback);
908 unsafe extern "C" fn callback_func<
909 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
910 >(
911 connection: *mut ffi::GDBusConnection,
912 sender_name: *const libc::c_char,
913 object_path: *const libc::c_char,
914 interface_name: *const libc::c_char,
915 signal_name: *const libc::c_char,
916 parameters: *mut glib::ffi::GVariant,
917 user_data: glib::ffi::gpointer,
918 ) {
919 let connection = from_glib_borrow(connection);
920 let sender_name: Borrowed<glib::GString> = from_glib_borrow(sender_name);
921 let object_path: Borrowed<glib::GString> = from_glib_borrow(object_path);
922 let interface_name: Borrowed<glib::GString> = from_glib_borrow(interface_name);
923 let signal_name: Borrowed<glib::GString> = from_glib_borrow(signal_name);
924 let parameters = from_glib_borrow(parameters);
925 let callback: &P = &*(user_data as *mut _);
926 (*callback)(
927 &connection,
928 sender_name.as_str(),
929 object_path.as_str(),
930 interface_name.as_str(),
931 signal_name.as_str(),
932 ¶meters,
933 );
934 }
935 let callback = Some(callback_func::<P> as _);
936 unsafe extern "C" fn user_data_free_func_func<
937 P: Fn(&DBusConnection, &str, &str, &str, &str, &glib::Variant) + 'static,
938 >(
939 data: glib::ffi::gpointer,
940 ) {
941 let _callback: Box_<P> = Box_::from_raw(data as *mut _);
942 }
943 let destroy_call9 = Some(user_data_free_func_func::<P> as _);
944 let super_callback0: Box_<P> = callback_data;
945 unsafe {
946 let id = ffi::g_dbus_connection_signal_subscribe(
947 self.to_glib_none().0,
948 sender.to_glib_none().0,
949 interface_name.to_glib_none().0,
950 member.to_glib_none().0,
951 object_path.to_glib_none().0,
952 arg0.to_glib_none().0,
953 flags.into_glib(),
954 callback,
955 Box_::into_raw(super_callback0) as *mut _,
956 destroy_call9,
957 );
958 SignalSubscriptionId(NonZeroU32::new_unchecked(id))
959 }
960 }
961
962 /// Unsubscribes from signals.
963 ///
964 /// Note that there may still be D-Bus traffic to process (relating to this
965 /// signal subscription) in the current thread-default #GMainContext after this
966 /// function has returned. You should continue to iterate the #GMainContext
967 /// until the #GDestroyNotify function passed to
968 /// g_dbus_connection_signal_subscribe() is called, in order to avoid memory
969 /// leaks through callbacks queued on the #GMainContext after it’s stopped being
970 /// iterated.
971 /// Alternatively, any idle source with a priority lower than `G_PRIORITY_DEFAULT`
972 /// that was scheduled after unsubscription, also indicates that all resources
973 /// of this subscription are released.
974 /// ## `subscription_id`
975 /// a subscription id obtained from
976 /// g_dbus_connection_signal_subscribe()
977 #[doc(alias = "g_dbus_connection_signal_unsubscribe")]
978 #[deprecated(note = "Prefer subscribe_to_signal")]
979 pub fn signal_unsubscribe(&self, subscription_id: SignalSubscriptionId) {
980 unsafe {
981 ffi::g_dbus_connection_signal_unsubscribe(
982 self.to_glib_none().0,
983 subscription_id.0.into(),
984 );
985 }
986 }
987
988 // rustdoc-stripper-ignore-next
989 /// Subscribe to a D-Bus signal and receive signal emissions as a stream.
990 ///
991 /// See [`Self::signal_subscribe`] for arguments. `map_signal` maps the
992 /// received signal to the stream's element.
993 ///
994 /// The returned stream holds a strong reference to this D-Bus connection,
995 /// and unsubscribes from the signal when dropped. To avoid reference cycles
996 /// you may wish to downgrade the returned stream to hold only weak
997 /// reference to the connection using [`SubscribedSignalStream::downgrade`].
998 ///
999 /// After invoking `map_signal` the stream threads incoming signals through
1000 /// an unbounded channel. Hence, memory consumption will keep increasing
1001 /// as long as the stream consumer does not keep up with signal emissions.
1002 /// If you need to perform expensive processing in response to signals it's
1003 /// therefore recommended to insert an extra buffering and if the buffer
1004 /// overruns, either fail drop the entire stream, or drop individual signal
1005 /// emissions until the buffer has space again.
1006 pub fn receive_signal<T: 'static, F: Fn(DBusSignalRef) -> T + 'static>(
1007 &self,
1008 sender: Option<&str>,
1009 interface_name: Option<&str>,
1010 member: Option<&str>,
1011 object_path: Option<&str>,
1012 arg0: Option<&str>,
1013 flags: DBusSignalFlags,
1014 map_signal: F,
1015 ) -> SubscribedSignalStream<SignalSubscription, impl Stream<Item = T> + use<T, F>> {
1016 let (tx, rx) = mpsc::unbounded();
1017 let subscription = self.subscribe_to_signal(
1018 sender,
1019 interface_name,
1020 member,
1021 object_path,
1022 arg0,
1023 flags,
1024 move |signal| {
1025 // Just ignore send errors: if the receiver is dropped, the
1026 // signal subscription is dropped too, so the callback won't
1027 // be invoked anymore.
1028 let _ = tx.unbounded_send(map_signal(signal));
1029 },
1030 );
1031 SubscribedSignalStream {
1032 subscription,
1033 stream: rx,
1034 }
1035 }
1036
1037 // rustdoc-stripper-ignore-next
1038 /// Subscribe to a D-Bus signal and receive signal parameters as a stream.
1039 ///
1040 /// Like [`Self::receive_signal`] (which see for more information), but
1041 /// automatically decodes the emitted signal parameters to type `T`.
1042 /// If decoding fails the corresponding variant type error is sent
1043 /// downstream.
1044 pub fn receive_signal_parameters<T>(
1045 &self,
1046 sender: Option<&str>,
1047 interface_name: Option<&str>,
1048 member: Option<&str>,
1049 object_path: Option<&str>,
1050 arg0: Option<&str>,
1051 flags: DBusSignalFlags,
1052 ) -> SubscribedSignalStream<
1053 SignalSubscription,
1054 impl Stream<Item = Result<T, VariantTypeMismatchError>> + use<T>,
1055 >
1056 where
1057 T: FromVariant + 'static,
1058 {
1059 self.receive_signal(
1060 sender,
1061 interface_name,
1062 member,
1063 object_path,
1064 arg0,
1065 flags,
1066 |signal| signal.parameters.try_get(),
1067 )
1068 }
1069}