1 #include "xed-message-bus.h"
2 
3 #include <string.h>
4 #include <stdarg.h>
5 #include <gobject/gvaluecollector.h>
6 
7 /**
8  * XedMessageCallback:
9  * @bus: the #XedMessageBus on which the message was sent
10  * @message: the #XedMessage which was sent
11  * @userdata: the supplied user data when connecting the callback
12  *
13  * Callback signature used for connecting callback functions to be called
14  * when a message is received (see xed_message_bus_connect()).
15  *
16  */
17 
18 /**
19  * SECTION:xed-message-bus
20  * @short_description: internal message communication bus
21  * @include: xed/xed-message-bus.h
22  *
23  * xed has a communication bus very similar to DBus. Its primary use is to
24  * allow easy communication between plugins, but it can also be used to expose
25  * xed functionality to external applications by providing DBus bindings for
26  * the internal xed message bus.
27  *
28  * There are two different communication busses available. The default bus
29  * (see xed_message_bus_get_default()) is an application wide communication
30  * bus. In addition, each #XedWindow has a separate, private bus
31  * (see xed_window_get_message_bus()). This makes it easier for plugins to
32  * communicate to other plugins in the same window.
33  *
34  * The concept of the message bus is very simple. You can register a message
35  * type on the bus, specified as a Method at a specific Object Path with a
36  * certain set of Method Arguments. You can then connect callback functions
37  * for this message type on the bus. Whenever a message with the Object Path
38  * and Method for which callbacks are connected is sent over the bus, the
39  * callbacks are called. There is no distinction between Methods and Signals
40  * (signals are simply messages where sender and receiver have switched places).
41  *
42  * <example>
43  * <title>Registering a message type</title>
44  * <programlisting>
45  * XedMessageBus *bus = xed_message_bus_get_default ();
46  *
47  * // Register 'method' at '/plugins/example' with one required
48  * // string argument 'arg1'
49  * XedMessageType *message_type = xed_message_bus_register ("/plugins/example", "method",
50  *                                                              0,
51  *                                                              "arg1", G_TYPE_STRING,
52  *                                                              NULL);
53  * </programlisting>
54  * </example>
55  * <example>
56  * <title>Connecting a callback</title>
57  * <programlisting>
58  * static void
59  * example_method_cb (XedMessageBus *bus,
60  *                    XedMessage    *message,
61  *                    gpointer         userdata)
62  * {
63  *  gchar *arg1 = NULL;
64  *
65  *  xed_message_get (message, "arg1", &arg1, NULL);
66  *  g_message ("Evoked /plugins/example.method with: %s", arg1);
67  *  g_free (arg1);
68  * }
69  *
70  * XedMessageBus *bus = xed_message_bus_get_default ();
71  *
72  * guint id = xed_message_bus_connect (bus,
73  *                                       "/plugins/example", "method",
74  *                                       example_method_cb,
75  *                                       NULL,
76  *                                       NULL);
77  *
78  * </programlisting>
79  * </example>
80  * <example>
81  * <title>Sending a message</title>
82  * <programlisting>
83  * XedMessageBus *bus = xed_message_bus_get_default ();
84  *
85  * xed_message_bus_send (bus,
86  *                         "/plugins/example", "method",
87  *                         "arg1", "Hello World",
88  *                         NULL);
89  * </programlisting>
90  * </example>
91  */
92 
93 typedef struct
94 {
95     gchar *object_path;
96     gchar *method;
97 
98     GList *listeners;
99 } Message;
100 
101 typedef struct
102 {
103     guint    id;
104     gboolean blocked;
105 
106     GDestroyNotify     destroy_data;
107     XedMessageCallback callback;
108     gpointer           userdata;
109 } Listener;
110 
111 typedef struct
112 {
113     Message *message;
114     GList   *listener;
115 } IdMap;
116 
117 struct _XedMessageBusPrivate
118 {
119     GHashTable *messages;
120     GHashTable *idmap;
121 
122     GList *message_queue;
123     guint  idle_id;
124 
125     guint next_id;
126 
127     GHashTable *types; /* mapping from identifier to XedMessageType */
128 };
129 
130 /* signals */
131 enum
132 {
133     DISPATCH,
134     REGISTERED,
135     UNREGISTERED,
136     LAST_SIGNAL
137 };
138 
139 static guint message_bus_signals[LAST_SIGNAL] = { 0 };
140 
141 static void xed_message_bus_dispatch_real (XedMessageBus *bus,
142                                            XedMessage    *message);
143 
G_DEFINE_TYPE_WITH_PRIVATE(XedMessageBus,xed_message_bus,G_TYPE_OBJECT)144 G_DEFINE_TYPE_WITH_PRIVATE (XedMessageBus, xed_message_bus, G_TYPE_OBJECT)
145 
146 static void
147 listener_free (Listener *listener)
148 {
149     if (listener->destroy_data)
150     {
151         listener->destroy_data (listener->userdata);
152     }
153 
154     g_free (listener);
155 }
156 
157 static void
message_free(Message * message)158 message_free (Message *message)
159 {
160     g_free (message->method);
161     g_free (message->object_path);
162 
163     g_list_foreach (message->listeners, (GFunc)listener_free, NULL);
164     g_list_free (message->listeners);
165 
166     g_free (message);
167 }
168 
169 static void
message_queue_free(GList * queue)170 message_queue_free (GList *queue)
171 {
172     g_list_foreach (queue, (GFunc)g_object_unref, NULL);
173     g_list_free (queue);
174 }
175 
176 static void
xed_message_bus_finalize(GObject * object)177 xed_message_bus_finalize (GObject *object)
178 {
179     XedMessageBus *bus = XED_MESSAGE_BUS (object);
180 
181     if (bus->priv->idle_id != 0)
182     {
183         g_source_remove (bus->priv->idle_id);
184     }
185 
186     message_queue_free (bus->priv->message_queue);
187 
188     g_hash_table_destroy (bus->priv->messages);
189     g_hash_table_destroy (bus->priv->idmap);
190     g_hash_table_destroy (bus->priv->types);
191 
192     G_OBJECT_CLASS (xed_message_bus_parent_class)->finalize (object);
193 }
194 
195 static void
xed_message_bus_class_init(XedMessageBusClass * klass)196 xed_message_bus_class_init (XedMessageBusClass *klass)
197 {
198     GObjectClass *object_class = G_OBJECT_CLASS (klass);
199 
200     object_class->finalize = xed_message_bus_finalize;
201 
202     klass->dispatch = xed_message_bus_dispatch_real;
203 
204     /**
205      * XedMessageBus::dispatch:
206      * @bus: a #XedMessageBus
207      * @message: the #XedMessage to dispatch
208      *
209      * The "dispatch" signal is emitted when a message is to be dispatched.
210      * The message is dispatched in the default handler of this signal.
211      * Primary use of this signal is to customize the dispatch of a message
212      * (for instance to automatically dispatch all messages over DBus).
213      *2
214      */
215     message_bus_signals[DISPATCH] =
216         g_signal_new ("dispatch",
217                       G_OBJECT_CLASS_TYPE (object_class),
218                       G_SIGNAL_RUN_LAST,
219                       G_STRUCT_OFFSET (XedMessageBusClass, dispatch),
220                       NULL, NULL,
221                       g_cclosure_marshal_VOID__OBJECT,
222                       G_TYPE_NONE,
223                       1,
224                       XED_TYPE_MESSAGE);
225 
226     /**
227      * XedMessageBus::registered:
228      * @bus: a #XedMessageBus
229      * @message_type: the registered #XedMessageType
230      *
231      * The "registered" signal is emitted when a message has been registered
232      * on the bus.
233      *
234      */
235     message_bus_signals[REGISTERED] =
236         g_signal_new ("registered",
237                       G_OBJECT_CLASS_TYPE (object_class),
238                       G_SIGNAL_RUN_LAST,
239                       G_STRUCT_OFFSET (XedMessageBusClass, registered),
240                       NULL, NULL,
241                       g_cclosure_marshal_VOID__BOXED,
242                       G_TYPE_NONE,
243                       1,
244                       XED_TYPE_MESSAGE_TYPE);
245 
246     /**
247      * XedMessageBus::unregistered:
248      * @bus: a #XedMessageBus
249      * @message_type: the unregistered #XedMessageType
250      *
251      * The "unregistered" signal is emitted when a message has been
252      * unregistered from the bus.
253      *
254      */
255     message_bus_signals[UNREGISTERED] =
256         g_signal_new ("unregistered",
257                       G_OBJECT_CLASS_TYPE (object_class),
258                       G_SIGNAL_RUN_LAST,
259                       G_STRUCT_OFFSET (XedMessageBusClass, unregistered),
260                       NULL, NULL,
261                       g_cclosure_marshal_VOID__BOXED,
262                       G_TYPE_NONE,
263                       1,
264                       XED_TYPE_MESSAGE_TYPE);
265 }
266 
267 static Message *
message_new(XedMessageBus * bus,const gchar * object_path,const gchar * method)268 message_new (XedMessageBus *bus,
269              const gchar   *object_path,
270              const gchar   *method)
271 {
272     Message *message = g_new (Message, 1);
273 
274     message->object_path = g_strdup (object_path);
275     message->method = g_strdup (method);
276     message->listeners = NULL;
277 
278     g_hash_table_insert (bus->priv->messages, xed_message_type_identifier (object_path, method), message);
279     return message;
280 }
281 
282 static Message *
lookup_message(XedMessageBus * bus,const gchar * object_path,const gchar * method,gboolean create)283 lookup_message (XedMessageBus *bus,
284                 const gchar   *object_path,
285                 const gchar   *method,
286                 gboolean       create)
287 {
288     gchar *identifier;
289     Message *message;
290 
291     identifier = xed_message_type_identifier (object_path, method);
292     message = (Message *)g_hash_table_lookup (bus->priv->messages, identifier);
293     g_free (identifier);
294 
295     if (!message && !create)
296     {
297         return NULL;
298     }
299 
300     if (!message)
301     {
302         message = message_new (bus, object_path, method);
303     }
304 
305     return message;
306 }
307 
308 static guint
add_listener(XedMessageBus * bus,Message * message,XedMessageCallback callback,gpointer userdata,GDestroyNotify destroy_data)309 add_listener (XedMessageBus      *bus,
310               Message            *message,
311               XedMessageCallback  callback,
312               gpointer            userdata,
313               GDestroyNotify      destroy_data)
314 {
315     Listener *listener;
316     IdMap *idmap;
317 
318     listener = g_new (Listener, 1);
319     listener->id = ++bus->priv->next_id;
320     listener->callback = callback;
321     listener->userdata = userdata;
322     listener->blocked = FALSE;
323     listener->destroy_data = destroy_data;
324 
325     message->listeners = g_list_append (message->listeners, listener);
326 
327     idmap = g_new (IdMap, 1);
328     idmap->message = message;
329     idmap->listener = g_list_last (message->listeners);
330 
331     g_hash_table_insert (bus->priv->idmap, GINT_TO_POINTER (listener->id), idmap);
332     return listener->id;
333 }
334 
335 static void
remove_listener(XedMessageBus * bus,Message * message,GList * listener)336 remove_listener (XedMessageBus *bus,
337                  Message       *message,
338                  GList         *listener)
339 {
340     Listener *lst;
341 
342     lst = (Listener *)listener->data;
343 
344     /* remove from idmap */
345     g_hash_table_remove (bus->priv->idmap, GINT_TO_POINTER (lst->id));
346     listener_free (lst);
347 
348     /* remove from list of listeners */
349     message->listeners = g_list_delete_link (message->listeners, listener);
350 
351     if (!message->listeners)
352     {
353         /* remove message because it does not have any listeners */
354         g_hash_table_remove (bus->priv->messages, message);
355     }
356 }
357 
358 static void
block_listener(XedMessageBus * bus,Message * message,GList * listener)359 block_listener (XedMessageBus *bus,
360                 Message       *message,
361                 GList         *listener)
362 {
363     Listener *lst;
364 
365     lst = (Listener *)listener->data;
366     lst->blocked = TRUE;
367 }
368 
369 static void
unblock_listener(XedMessageBus * bus,Message * message,GList * listener)370 unblock_listener (XedMessageBus *bus,
371                   Message       *message,
372                   GList         *listener)
373 {
374     Listener *lst;
375 
376     lst = (Listener *)listener->data;
377     lst->blocked = FALSE;
378 }
379 
380 static void
dispatch_message_real(XedMessageBus * bus,Message * msg,XedMessage * message)381 dispatch_message_real (XedMessageBus *bus,
382                        Message       *msg,
383                        XedMessage    *message)
384 {
385     GList *item;
386 
387     for (item = msg->listeners; item; item = item->next)
388     {
389         Listener *listener = (Listener *)item->data;
390 
391         if (!listener->blocked)
392         {
393             listener->callback (bus, message, listener->userdata);
394         }
395     }
396 }
397 
398 static void
xed_message_bus_dispatch_real(XedMessageBus * bus,XedMessage * message)399 xed_message_bus_dispatch_real (XedMessageBus *bus,
400                                XedMessage    *message)
401 {
402     const gchar *object_path;
403     const gchar *method;
404     Message *msg;
405 
406     object_path = xed_message_get_object_path (message);
407     method = xed_message_get_method (message);
408 
409     msg = lookup_message (bus, object_path, method, FALSE);
410 
411     if (msg)
412     {
413         dispatch_message_real (bus, msg, message);
414     }
415 }
416 
417 static void
dispatch_message(XedMessageBus * bus,XedMessage * message)418 dispatch_message (XedMessageBus *bus,
419                   XedMessage    *message)
420 {
421     g_signal_emit (bus, message_bus_signals[DISPATCH], 0, message);
422 }
423 
424 static gboolean
idle_dispatch(XedMessageBus * bus)425 idle_dispatch (XedMessageBus *bus)
426 {
427     GList *list;
428     GList *item;
429 
430     /* make sure to set idle_id to 0 first so that any new async messages
431        will be queued properly */
432     bus->priv->idle_id = 0;
433 
434     /* reverse queue to get correct delivery order */
435     list = g_list_reverse (bus->priv->message_queue);
436     bus->priv->message_queue = NULL;
437 
438     for (item = list; item; item = item->next)
439     {
440         XedMessage *msg = XED_MESSAGE (item->data);
441 
442         dispatch_message (bus, msg);
443     }
444 
445     message_queue_free (list);
446     return FALSE;
447 }
448 
449 typedef void (*MatchCallback) (XedMessageBus *, Message *, GList *);
450 
451 static void
process_by_id(XedMessageBus * bus,guint id,MatchCallback processor)452 process_by_id (XedMessageBus *bus,
453                guint          id,
454                MatchCallback  processor)
455 {
456     IdMap *idmap;
457 
458     idmap = (IdMap *)g_hash_table_lookup (bus->priv->idmap, GINT_TO_POINTER (id));
459 
460     if (idmap == NULL)
461     {
462         g_warning ("No handler registered with id `%d'", id);
463         return;
464     }
465 
466     processor (bus, idmap->message, idmap->listener);
467 }
468 
469 static void
process_by_match(XedMessageBus * bus,const gchar * object_path,const gchar * method,XedMessageCallback callback,gpointer userdata,MatchCallback processor)470 process_by_match (XedMessageBus      *bus,
471                   const gchar        *object_path,
472                   const gchar        *method,
473                   XedMessageCallback  callback,
474                   gpointer            userdata,
475                   MatchCallback       processor)
476 {
477     Message *message;
478     GList *item;
479 
480     message = lookup_message (bus, object_path, method, FALSE);
481 
482     if (!message)
483     {
484         g_warning ("No such handler registered for %s.%s", object_path, method);
485         return;
486     }
487 
488     for (item = message->listeners; item; item = item->next)
489     {
490         Listener *listener = (Listener *)item->data;
491 
492         if (listener->callback == callback && listener->userdata == userdata)
493         {
494             processor (bus, message, item);
495             return;
496         }
497     }
498 
499     g_warning ("No such handler registered for %s.%s", object_path, method);
500 }
501 
502 static void
xed_message_bus_init(XedMessageBus * self)503 xed_message_bus_init (XedMessageBus *self)
504 {
505     self->priv = xed_message_bus_get_instance_private (self);
506 
507     self->priv->messages = g_hash_table_new_full (g_str_hash,
508                                                   g_str_equal,
509                                                   (GDestroyNotify)g_free,
510                                                   (GDestroyNotify)message_free);
511 
512     self->priv->idmap = g_hash_table_new_full (g_direct_hash,
513                                                g_direct_equal,
514                                                NULL,
515                                                (GDestroyNotify)g_free);
516 
517     self->priv->types = g_hash_table_new_full (g_str_hash,
518                                                g_str_equal,
519                                                (GDestroyNotify)g_free,
520                                                (GDestroyNotify)xed_message_type_unref);
521 }
522 
523 /**
524  * xed_message_bus_get_default:
525  *
526  * Get the default application #XedMessageBus.
527  *
528  * Return value: (transfer none): the default #XedMessageBus
529  *
530  */
531 XedMessageBus *
xed_message_bus_get_default(void)532 xed_message_bus_get_default (void)
533 {
534     static XedMessageBus *default_bus = NULL;
535 
536     if (G_UNLIKELY (default_bus == NULL))
537     {
538         default_bus = g_object_new (XED_TYPE_MESSAGE_BUS, NULL);
539         g_object_add_weak_pointer (G_OBJECT (default_bus), (gpointer) &default_bus);
540     }
541 
542     return default_bus;
543 }
544 
545 /**
546  * xed_message_bus_new:
547  *
548  * Create a new message bus. Use xed_message_bus_get_default() to get the
549  * default, application wide, message bus. Creating a new bus is useful for
550  * associating a specific bus with for instance a #XedWindow.
551  *
552  * Return value: a new #XedMessageBus
553  *
554  */
555 XedMessageBus *
xed_message_bus_new(void)556 xed_message_bus_new (void)
557 {
558     return XED_MESSAGE_BUS (g_object_new (XED_TYPE_MESSAGE_BUS, NULL));
559 }
560 
561 /**
562  * xed_message_bus_lookup:
563  * @bus: a #XedMessageBus
564  * @object_path: the object path
565  * @method: the method
566  *
567  * Get the registered #XedMessageType for @method at @object_path. The
568  * returned #XedMessageType is owned by the bus and should not be unreffed.
569  *
570  * Return value: the registered #XedMessageType or %NULL if no message type
571  *               is registered for @method at @object_path
572  *
573  */
574 XedMessageType *
xed_message_bus_lookup(XedMessageBus * bus,const gchar * object_path,const gchar * method)575 xed_message_bus_lookup (XedMessageBus *bus,
576                         const gchar   *object_path,
577                         const gchar   *method)
578 {
579     gchar *identifier;
580     XedMessageType *message_type;
581 
582     g_return_val_if_fail (XED_IS_MESSAGE_BUS (bus), NULL);
583     g_return_val_if_fail (object_path != NULL, NULL);
584     g_return_val_if_fail (method != NULL, NULL);
585 
586     identifier = xed_message_type_identifier (object_path, method);
587     message_type = XED_MESSAGE_TYPE (g_hash_table_lookup (bus->priv->types, identifier));
588 
589     g_free (identifier);
590     return message_type;
591 }
592 
593 /**
594  * xed_message_bus_register:
595  * @bus: a #XedMessageBus
596  * @object_path: the object path
597  * @method: the method to register
598  * @num_optional: the number of optional arguments
599  * @...: NULL terminated list of key/gtype method argument pairs
600  *
601  * Register a message on the bus. A message must be registered on the bus before
602  * it can be send. This function registers the type arguments for @method at
603  * @object_path. The arguments are specified with the variable arguments which
604  * should contain pairs of const gchar *key and GType terminated by %NULL. The
605  * last @num_optional arguments are registered as optional (and are thus not
606  * required when sending a message).
607  *
608  * This function emits a #XedMessageBus::registered signal.
609  *
610  * Return value: the registered #XedMessageType. The returned reference is
611  *               owned by the bus. If you want to keep it alive after
612  *               unregistering, use xed_message_type_ref().
613  *
614  */
615 XedMessageType *
xed_message_bus_register(XedMessageBus * bus,const gchar * object_path,const gchar * method,guint num_optional,...)616 xed_message_bus_register (XedMessageBus *bus,
617                           const gchar   *object_path,
618                           const gchar   *method,
619                           guint          num_optional,
620                                          ...)
621 {
622     gchar *identifier;
623     va_list var_args;
624     XedMessageType *message_type;
625 
626     g_return_val_if_fail (XED_IS_MESSAGE_BUS (bus), NULL);
627     g_return_val_if_fail (xed_message_type_is_valid_object_path (object_path), NULL);
628 
629     if (xed_message_bus_is_registered (bus, object_path, method))
630     {
631         g_warning ("Message type for '%s.%s' is already registered", object_path, method);
632         return NULL;
633     }
634 
635     identifier = xed_message_type_identifier (object_path, method);
636 
637     va_start (var_args, num_optional);
638     message_type = xed_message_type_new_valist (object_path, method, num_optional, var_args);
639     va_end (var_args);
640 
641     if (message_type)
642     {
643         g_hash_table_insert (bus->priv->types, identifier, message_type);
644         g_signal_emit (bus, message_bus_signals[REGISTERED], 0, message_type);
645     }
646     else
647     {
648         g_free (identifier);
649     }
650 
651     return message_type;
652 }
653 
654 static void
xed_message_bus_unregister_real(XedMessageBus * bus,XedMessageType * message_type,gboolean remove_from_store)655 xed_message_bus_unregister_real (XedMessageBus  *bus,
656                                  XedMessageType *message_type,
657                                  gboolean        remove_from_store)
658 {
659     gchar *identifier;
660 
661     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
662 
663     identifier = xed_message_type_identifier (xed_message_type_get_object_path (message_type),
664                                               xed_message_type_get_method (message_type));
665 
666     /* Keep message type alive for signal emission */
667     xed_message_type_ref (message_type);
668 
669     if (!remove_from_store || g_hash_table_remove (bus->priv->types, identifier))
670     {
671         g_signal_emit (bus, message_bus_signals[UNREGISTERED], 0, message_type);
672     }
673 
674     xed_message_type_unref (message_type);
675     g_free (identifier);
676 }
677 
678 /**
679  * xed_message_bus_unregister:
680  * @bus: a #XedMessageBus
681  * @message_type: the #XedMessageType to unregister
682  *
683  * Unregisters a previously registered message type. This is especially useful
684  * for plugins which should unregister message types when they are deactivated.
685  *
686  * This function emits the #XedMessageBus::unregistered signal.
687  *
688  */
689 void
xed_message_bus_unregister(XedMessageBus * bus,XedMessageType * message_type)690 xed_message_bus_unregister (XedMessageBus  *bus,
691                             XedMessageType *message_type)
692 {
693     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
694     xed_message_bus_unregister_real (bus, message_type, TRUE);
695 }
696 
697 typedef struct
698 {
699     XedMessageBus *bus;
700     const gchar   *object_path;
701 } UnregisterInfo;
702 
703 static gboolean
unregister_each(const gchar * identifier,XedMessageType * message_type,UnregisterInfo * info)704 unregister_each (const gchar    *identifier,
705                  XedMessageType *message_type,
706                  UnregisterInfo *info)
707 {
708     if (strcmp (xed_message_type_get_object_path (message_type),
709             info->object_path) == 0)
710     {
711         xed_message_bus_unregister_real (info->bus, message_type, FALSE);
712         return TRUE;
713     }
714 
715     return FALSE;
716 }
717 
718 /**
719  * xed_message_bus_unregister_all:
720  * @bus: a #XedMessageBus
721  * @object_path: the object path
722  *
723  * Unregisters all message types for @object_path. This is especially useful for
724  * plugins which should unregister message types when they are deactivated.
725  *
726  * This function emits the #XedMessageBus::unregistered signal for all
727  * unregistered message types.
728  *
729  */
730 void
xed_message_bus_unregister_all(XedMessageBus * bus,const gchar * object_path)731 xed_message_bus_unregister_all (XedMessageBus *bus,
732                                 const gchar     *object_path)
733 {
734     UnregisterInfo info = {bus, object_path};
735 
736     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
737     g_return_if_fail (object_path != NULL);
738 
739     g_hash_table_foreach_remove (bus->priv->types, (GHRFunc)unregister_each, &info);
740 }
741 
742 /**
743  * xed_message_bus_is_registered:
744  * @bus: a #XedMessageBus
745  * @object_path: the object path
746  * @method: the method
747  *
748  * Check whether a message type @method at @object_path is registered on the
749  * bus.
750  *
751  * Return value: %TRUE if the @method at @object_path is a registered message
752  *               type on the bus
753  *
754  */
755 gboolean
xed_message_bus_is_registered(XedMessageBus * bus,const gchar * object_path,const gchar * method)756 xed_message_bus_is_registered (XedMessageBus *bus,
757                                const gchar   *object_path,
758                                const gchar   *method)
759 {
760     gchar *identifier;
761     gboolean ret;
762 
763     g_return_val_if_fail (XED_IS_MESSAGE_BUS (bus), FALSE);
764     g_return_val_if_fail (object_path != NULL, FALSE);
765     g_return_val_if_fail (method != NULL, FALSE);
766 
767     identifier = xed_message_type_identifier (object_path, method);
768     ret = g_hash_table_lookup (bus->priv->types, identifier) != NULL;
769 
770     g_free(identifier);
771     return ret;
772 }
773 
774 typedef struct
775 {
776     XedMessageBusForeach func;
777     gpointer             userdata;
778 } ForeachInfo;
779 
780 static void
foreach_type(const gchar * key,XedMessageType * message_type,ForeachInfo * info)781 foreach_type (const gchar    *key,
782               XedMessageType *message_type,
783               ForeachInfo    *info)
784 {
785     xed_message_type_ref (message_type);
786     info->func (message_type, info->userdata);
787     xed_message_type_unref (message_type);
788 }
789 
790 /**
791  * xed_message_bus_foreach:
792  * @bus: the #XedMessageBus
793  * @func: (scope call): the callback function
794  * @userdata: the user data to supply to the callback function
795  *
796  * Calls @func for each message type registered on the bus
797  *
798  */
799 void
xed_message_bus_foreach(XedMessageBus * bus,XedMessageBusForeach func,gpointer userdata)800 xed_message_bus_foreach (XedMessageBus        *bus,
801                          XedMessageBusForeach  func,
802                          gpointer              userdata)
803 {
804     ForeachInfo info = {func, userdata};
805 
806     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
807     g_return_if_fail (func != NULL);
808 
809     g_hash_table_foreach (bus->priv->types, (GHFunc)foreach_type, &info);
810 }
811 
812 /**
813  * xed_message_bus_connect:
814  * @bus: a #XedMessageBus
815  * @object_path: the object path
816  * @method: the method
817  * @callback: function to be called when message @method at @object_path is sent
818  * @userdata: userdata to use for the callback
819  * @destroy_data: function to evoke with @userdata as argument when @userdata
820  *                needs to be freed
821  *
822  * Connect a callback handler to be evoked when message @method at @object_path
823  * is sent over the bus.
824  *
825  * Return value: the callback identifier
826  *
827  */
828 guint
xed_message_bus_connect(XedMessageBus * bus,const gchar * object_path,const gchar * method,XedMessageCallback callback,gpointer userdata,GDestroyNotify destroy_data)829 xed_message_bus_connect (XedMessageBus      *bus,
830                          const gchar        *object_path,
831                          const gchar        *method,
832                          XedMessageCallback  callback,
833                          gpointer            userdata,
834                          GDestroyNotify      destroy_data)
835 {
836     Message *message;
837 
838     g_return_val_if_fail (XED_IS_MESSAGE_BUS (bus), 0);
839     g_return_val_if_fail (object_path != NULL, 0);
840     g_return_val_if_fail (method != NULL, 0);
841     g_return_val_if_fail (callback != NULL, 0);
842 
843     /* lookup the message and create if it does not exist yet */
844     message = lookup_message (bus, object_path, method, TRUE);
845 
846     return add_listener (bus, message, callback, userdata, destroy_data);
847 }
848 
849 /**
850  * xed_message_bus_disconnect:
851  * @bus: a #XedMessageBus
852  * @id: the callback id as returned by xed_message_bus_connect()
853  *
854  * Disconnects a previously connected message callback.
855  *
856  */
857 void
xed_message_bus_disconnect(XedMessageBus * bus,guint id)858 xed_message_bus_disconnect (XedMessageBus *bus,
859                             guint          id)
860 {
861     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
862 
863     process_by_id (bus, id, remove_listener);
864 }
865 
866 /**
867  * xed_message_bus_disconnect_by_func:
868  * @bus: a #XedMessageBus
869  * @object_path: the object path
870  * @method: the method
871  * @callback: (scope call): the connected callback
872  * @userdata: the userdata with which the callback was connected
873  *
874  * Disconnects a previously connected message callback by matching the
875  * provided callback function and userdata. See also
876  * xed_message_bus_disconnect().
877  *
878  */
879 void
xed_message_bus_disconnect_by_func(XedMessageBus * bus,const gchar * object_path,const gchar * method,XedMessageCallback callback,gpointer userdata)880 xed_message_bus_disconnect_by_func (XedMessageBus      *bus,
881                                     const gchar        *object_path,
882                                     const gchar        *method,
883                                     XedMessageCallback  callback,
884                                     gpointer            userdata)
885 {
886     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
887 
888     process_by_match (bus, object_path, method, callback, userdata, remove_listener);
889 }
890 
891 /**
892  * xed_message_bus_block:
893  * @bus: a #XedMessageBus
894  * @id: the callback id
895  *
896  * Blocks evoking the callback specified by @id. Unblock the callback by
897  * using xed_message_bus_unblock().
898  *
899  */
900 void
xed_message_bus_block(XedMessageBus * bus,guint id)901 xed_message_bus_block (XedMessageBus *bus,
902                        guint          id)
903 {
904     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
905 
906     process_by_id (bus, id, block_listener);
907 }
908 
909 /**
910  * xed_message_bus_block_by_func:
911  * @bus: a #XedMessageBus
912  * @object_path: the object path
913  * @method: the method
914  * @callback: (scope call): the callback to block
915  * @userdata: the userdata with which the callback was connected
916  *
917  * Blocks evoking the callback that matches provided @callback and @userdata.
918  * Unblock the callback using xed_message_bus_unblock_by_func().
919  *
920  */
921 void
xed_message_bus_block_by_func(XedMessageBus * bus,const gchar * object_path,const gchar * method,XedMessageCallback callback,gpointer userdata)922 xed_message_bus_block_by_func (XedMessageBus      *bus,
923                                const gchar        *object_path,
924                                const gchar        *method,
925                                XedMessageCallback  callback,
926                                gpointer            userdata)
927 {
928     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
929 
930     process_by_match (bus, object_path, method, callback, userdata, block_listener);
931 }
932 
933 /**
934  * xed_message_bus_unblock:
935  * @bus: a #XedMessageBus
936  * @id: the callback id
937  *
938  * Unblocks the callback specified by @id.
939  *
940  */
941 void
xed_message_bus_unblock(XedMessageBus * bus,guint id)942 xed_message_bus_unblock (XedMessageBus *bus,
943                          guint          id)
944 {
945     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
946 
947     process_by_id (bus, id, unblock_listener);
948 }
949 
950 /**
951  * xed_message_bus_unblock_by_func:
952  * @bus: a #XedMessageBus
953  * @object_path: the object path
954  * @method: the method
955  * @callback: (scope call): the callback to block
956  * @userdata: the userdata with which the callback was connected
957  *
958  * Unblocks the callback that matches provided @callback and @userdata.
959  *
960  */
961 void
xed_message_bus_unblock_by_func(XedMessageBus * bus,const gchar * object_path,const gchar * method,XedMessageCallback callback,gpointer userdata)962 xed_message_bus_unblock_by_func (XedMessageBus      *bus,
963                                  const gchar        *object_path,
964                                  const gchar        *method,
965                                  XedMessageCallback  callback,
966                                  gpointer            userdata)
967 {
968     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
969 
970     process_by_match (bus, object_path, method, callback, userdata, unblock_listener);
971 }
972 
973 static gboolean
validate_message(XedMessage * message)974 validate_message (XedMessage *message)
975 {
976     if (!xed_message_validate (message))
977     {
978         g_warning ("Message '%s.%s' is invalid", xed_message_get_object_path (message),
979                    xed_message_get_method (message));
980         return FALSE;
981     }
982 
983     return TRUE;
984 }
985 
986 static void
send_message_real(XedMessageBus * bus,XedMessage * message)987 send_message_real (XedMessageBus *bus,
988                    XedMessage    *message)
989 {
990     if (!validate_message (message))
991     {
992         return;
993     }
994 
995     bus->priv->message_queue = g_list_prepend (bus->priv->message_queue, g_object_ref (message));
996 
997     if (bus->priv->idle_id == 0)
998     {
999         bus->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH,
1000                                               (GSourceFunc)idle_dispatch,
1001                                               bus,
1002                                               NULL);
1003     }
1004 }
1005 
1006 /**
1007  * xed_message_bus_send_message:
1008  * @bus: a #XedMessageBus
1009  * @message: the message to send
1010  *
1011  * This sends the provided @message asynchronously over the bus. To send
1012  * a message synchronously, use xed_message_bus_send_message_sync(). The
1013  * convenience function xed_message_bus_send() can be used to easily send
1014  * a message without constructing the message object explicitly first.
1015  *
1016  */
1017 void
xed_message_bus_send_message(XedMessageBus * bus,XedMessage * message)1018 xed_message_bus_send_message (XedMessageBus *bus,
1019                               XedMessage    *message)
1020 {
1021     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
1022     g_return_if_fail (XED_IS_MESSAGE (message));
1023 
1024     send_message_real (bus, message);
1025 }
1026 
1027 static void
send_message_sync_real(XedMessageBus * bus,XedMessage * message)1028 send_message_sync_real (XedMessageBus *bus,
1029                         XedMessage    *message)
1030 {
1031     if (!validate_message (message))
1032     {
1033         return;
1034     }
1035 
1036     dispatch_message (bus, message);
1037 }
1038 
1039 /**
1040  * xed_message_bus_send_message_sync:
1041  * @bus: a #XedMessageBus
1042  * @message: the message to send
1043  *
1044  * This sends the provided @message synchronously over the bus. To send
1045  * a message asynchronously, use xed_message_bus_send_message(). The
1046  * convenience function xed_message_bus_send_sync() can be used to easily send
1047  * a message without constructing the message object explicitly first.
1048  *
1049  */
1050 void
xed_message_bus_send_message_sync(XedMessageBus * bus,XedMessage * message)1051 xed_message_bus_send_message_sync (XedMessageBus *bus,
1052                                    XedMessage    *message)
1053 {
1054     g_return_if_fail (XED_IS_MESSAGE_BUS (bus));
1055     g_return_if_fail (XED_IS_MESSAGE (message));
1056 
1057     send_message_sync_real (bus, message);
1058 }
1059 
1060 static XedMessage *
create_message(XedMessageBus * bus,const gchar * object_path,const gchar * method,va_list var_args)1061 create_message (XedMessageBus *bus,
1062                 const gchar   *object_path,
1063                 const gchar   *method,
1064                 va_list        var_args)
1065 {
1066     XedMessageType *message_type;
1067 
1068     message_type = xed_message_bus_lookup (bus, object_path, method);
1069 
1070     if (!message_type)
1071     {
1072         g_warning ("Could not find message type for '%s.%s'", object_path, method);
1073         return NULL;
1074     }
1075 
1076     return xed_message_type_instantiate_valist (message_type, var_args);
1077 }
1078 
1079 /**
1080  * xed_message_bus_send:
1081  * @bus: a #XedMessageBus
1082  * @object_path: the object path
1083  * @method: the method
1084  * @...: NULL terminated list of key/value pairs
1085  *
1086  * This provides a convenient way to quickly send a message @method at
1087  * @object_path asynchronously over the bus. The variable argument list
1088  * specifies key (string) value pairs used to construct the message arguments.
1089  * To send a message synchronously use xed_message_bus_send_sync().
1090  *
1091  */
1092 void
xed_message_bus_send(XedMessageBus * bus,const gchar * object_path,const gchar * method,...)1093 xed_message_bus_send (XedMessageBus *bus,
1094                       const gchar   *object_path,
1095                       const gchar   *method,
1096                       ...)
1097 {
1098     va_list var_args;
1099     XedMessage *message;
1100 
1101     va_start (var_args, method);
1102 
1103     message = create_message (bus, object_path, method, var_args);
1104 
1105     if (message)
1106     {
1107         send_message_real (bus, message);
1108         g_object_unref (message);
1109     }
1110     else
1111     {
1112         g_warning ("Could not instantiate message");
1113     }
1114 
1115     va_end (var_args);
1116 }
1117 
1118 /**
1119  * xed_message_bus_send_sync:
1120  * @bus: a #XedMessageBus
1121  * @object_path: the object path
1122  * @method: the method
1123  * @...: NULL terminated list of key/value pairs
1124  *
1125  * This provides a convenient way to quickly send a message @method at
1126  * @object_path synchronously over the bus. The variable argument list
1127  * specifies key (string) value pairs used to construct the message
1128  * arguments. To send a message asynchronously use xed_message_bus_send().
1129  *
1130  * Return value: (transfer full): the constructed #XedMessage. The caller owns a reference
1131  *               to the #XedMessage and should call g_object_unref() when
1132  *               it is no longer needed
1133  */
1134 XedMessage *
xed_message_bus_send_sync(XedMessageBus * bus,const gchar * object_path,const gchar * method,...)1135 xed_message_bus_send_sync (XedMessageBus *bus,
1136                            const gchar     *object_path,
1137                            const gchar     *method,
1138                            ...)
1139 {
1140     va_list var_args;
1141     XedMessage *message;
1142 
1143     va_start (var_args, method);
1144     message = create_message (bus, object_path, method, var_args);
1145 
1146     if (message)
1147         send_message_sync_real (bus, message);
1148 
1149     va_end (var_args);
1150 
1151     return message;
1152 }
1153 
1154 // ex:ts=8:noet:
1155