diff options
Diffstat (limited to 'gedit/gedit-message-bus.c')
-rw-r--r-- | gedit/gedit-message-bus.c | 1259 |
1 files changed, 1259 insertions, 0 deletions
diff --git a/gedit/gedit-message-bus.c b/gedit/gedit-message-bus.c new file mode 100644 index 0000000..a5ed195 --- /dev/null +++ b/gedit/gedit-message-bus.c @@ -0,0 +1,1259 @@ +/* + * gedit-message-bus.h + * This file is part of gedit + * + * Copyright (C) 2008-2010 - Jesse van den Kieboom + * + * gedit is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gedit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gedit; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "gedit-message-bus.h" + +#include <string.h> +#include <stdarg.h> +#include <gobject/gvaluecollector.h> + +/** + * GeditMessageCallback: + * @bus: the #GeditMessageBus on which the message was sent + * @message: the #GeditMessage which was sent + * @user_data: the supplied user data when connecting the callback + * + * Callback signature used for connecting callback functions to be called + * when a message is received (see gedit_message_bus_connect()). + * + */ + +/** + * SECTION:gedit-message-bus + * @short_description: internal message communication bus + * @include: gedit/gedit-message-bus.h + * + * gedit has a communication bus very similar to DBus. Its primary use is to + * allow easy communication between plugins, but it can also be used to expose + * gedit functionality to external applications by providing DBus bindings for + * the internal gedit message bus. + * + * There are two different communication busses available. The default bus + * (see gedit_message_bus_get_default()) is an application wide communication + * bus. In addition, each #GeditWindow has a separate, private bus + * (see gedit_window_get_message_bus()). This makes it easier for plugins to + * communicate to other plugins in the same window. + * + * The concept of the message bus is very simple. You can register a message + * type on the bus, specified as a Method at a specific Object Path with a + * certain set of Method Arguments. You can then connect callback functions + * for this message type on the bus. Whenever a message with the Object Path + * and Method for which callbacks are connected is sent over the bus, the + * callbacks are called. There is no distinction between Methods and Signals + * (signals are simply messages where sender and receiver have switched places). + * + * <example> + * <title>Registering a message type</title> + * <programlisting> + * GeditMessageBus *bus = gedit_message_bus_get_default (); + * + * // Register 'method' at '/plugins/example' with one required + * // string argument 'arg1' + * gedit_message_bus_register (bus, EXAMPLE_TYPE_METHOD_MESSAGE, + * "/plugins/example", "method"); + * </programlisting> + * </example> + * <example> + * <title>Connecting a callback</title> + * <programlisting> + * static void + * example_method_cb (GeditMessageBus *bus, + * GeditMessage *message, + * gpointer user_data) + * { + * gchar *arg1 = NULL; + * + * gedit_message_get (message, "arg1", &arg1, NULL); + * g_message ("Evoked /plugins/example.method with: %s", arg1); + * g_free (arg1); + * } + * + * GeditMessageBus *bus = gedit_message_bus_get_default (); + * + * guint id = gedit_message_bus_connect (bus, + * "/plugins/example", "method", + * example_method_cb, + * NULL, + * NULL); + * + * </programlisting> + * </example> + * <example> + * <title>Sending a message</title> + * <programlisting> + * GeditMessageBus *bus = gedit_message_bus_get_default (); + * + * gedit_message_bus_send (bus, + * "/plugins/example", "method", + * "arg1", "Hello World", + * NULL); + * </programlisting> + * </example> + */ + +typedef struct +{ + gchar *object_path; + gchar *method; + + gchar *identifier; +} MessageIdentifier; + +typedef struct +{ + MessageIdentifier *identifier; + + GList *listeners; +} Message; + +typedef struct +{ + guint id; + gboolean blocked; + + GDestroyNotify destroy_data; + GeditMessageCallback callback; + gpointer user_data; +} Listener; + +typedef struct +{ + Message *message; + GList *listener; +} IdMap; + +struct _GeditMessageBusPrivate +{ + GHashTable *messages; + GHashTable *idmap; + + GList *message_queue; + guint idle_id; + + guint next_id; + + GHashTable *types; /* mapping from identifier to GeditMessageType */ +}; + +/* signals */ +enum +{ + DISPATCH, + REGISTERED, + UNREGISTERED, + LAST_SIGNAL +}; + +static guint message_bus_signals[LAST_SIGNAL]; + +static void gedit_message_bus_dispatch_real (GeditMessageBus *bus, + GeditMessage *message); + +G_DEFINE_TYPE_WITH_PRIVATE (GeditMessageBus, gedit_message_bus, G_TYPE_OBJECT) + +static MessageIdentifier * +message_identifier_new (const gchar *object_path, + const gchar *method) +{ + MessageIdentifier *ret; + + ret = g_slice_new (MessageIdentifier); + + ret->object_path = g_strdup (object_path); + ret->method = g_strdup (method); + + ret->identifier = gedit_message_type_identifier (object_path, method); + + return ret; +} + +static void +message_identifier_free (MessageIdentifier *identifier) +{ + g_free (identifier->object_path); + g_free (identifier->method); + g_free (identifier->identifier); + + g_slice_free (MessageIdentifier, identifier); +} + +static guint +message_identifier_hash (gconstpointer id) +{ + return g_str_hash (((MessageIdentifier *)id)->identifier); +} + +static gboolean +message_identifier_equal (gconstpointer id1, + gconstpointer id2) +{ + return g_str_equal (((MessageIdentifier *)id1)->identifier, + ((MessageIdentifier *)id2)->identifier); +} + +static void +listener_free (Listener *listener) +{ + if (listener->destroy_data) + { + listener->destroy_data (listener->user_data); + } + + g_slice_free (Listener, listener); +} + +static void +message_free (Message *message) +{ + message_identifier_free (message->identifier); + + g_list_free_full (message->listeners, (GDestroyNotify) listener_free); + g_slice_free (Message, message); +} + +static void +message_queue_free (GList *queue) +{ + g_list_free_full (queue, g_object_unref); +} + +static void +gedit_message_bus_finalize (GObject *object) +{ + GeditMessageBus *bus = GEDIT_MESSAGE_BUS (object); + + if (bus->priv->idle_id != 0) + { + g_source_remove (bus->priv->idle_id); + } + + message_queue_free (bus->priv->message_queue); + + g_hash_table_destroy (bus->priv->messages); + g_hash_table_destroy (bus->priv->idmap); + g_hash_table_destroy (bus->priv->types); + + G_OBJECT_CLASS (gedit_message_bus_parent_class)->finalize (object); +} + +static void +gedit_message_bus_class_init (GeditMessageBusClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_message_bus_finalize; + + klass->dispatch = gedit_message_bus_dispatch_real; + + /** + * GeditMessageBus::dispatch: + * @bus: a #GeditMessageBus + * @message: the #GeditMessage to dispatch + * + * The "dispatch" signal is emitted when a message is to be dispatched. + * The message is dispatched in the default handler of this signal. + * Primary use of this signal is to customize the dispatch of a message + * (for instance to automatically dispatch all messages over DBus). + * + */ + message_bus_signals[DISPATCH] = + g_signal_new ("dispatch", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditMessageBusClass, dispatch), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + GEDIT_TYPE_MESSAGE); + + /** + * GeditMessageBus::registered: + * @bus: a #GeditMessageBus + * @object_path: the registered object path. + * @method: the registered method + * + * The "registered" signal is emitted when a message has been registered + * on the bus. + * + */ + message_bus_signals[REGISTERED] = + g_signal_new ("registered", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditMessageBusClass, registered), + NULL, NULL, NULL, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); + + /** + * GeditMessageBus::unregistered: + * @bus: a #GeditMessageBus + * @object_path: the unregistered object path. + * @method: the unregistered method + * + * The "unregistered" signal is emitted when a message has been + * unregistered from the bus. + * + */ + message_bus_signals[UNREGISTERED] = + g_signal_new ("unregistered", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditMessageBusClass, unregistered), + NULL, NULL, NULL, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_STRING); +} + +static Message * +message_new (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method) +{ + Message *message = g_slice_new (Message); + + message->identifier = message_identifier_new (object_path, method); + message->listeners = NULL; + + g_hash_table_insert (bus->priv->messages, + message->identifier, + message); + + return message; +} + +static Message * +lookup_message (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + gboolean create) +{ + MessageIdentifier *identifier; + Message *message; + + identifier = message_identifier_new (object_path, method); + message = g_hash_table_lookup (bus->priv->messages, identifier); + message_identifier_free (identifier); + + if (!message && !create) + { + return NULL; + } + + if (!message) + { + message = message_new (bus, object_path, method); + } + + return message; +} + +static guint +add_listener (GeditMessageBus *bus, + Message *message, + GeditMessageCallback callback, + gpointer user_data, + GDestroyNotify destroy_data) +{ + Listener *listener; + IdMap *idmap; + + listener = g_slice_new (Listener); + listener->id = ++bus->priv->next_id; + listener->callback = callback; + listener->user_data = user_data; + listener->blocked = FALSE; + listener->destroy_data = destroy_data; + + message->listeners = g_list_append (message->listeners, listener); + + idmap = g_new (IdMap, 1); + idmap->message = message; + idmap->listener = g_list_last (message->listeners); + + g_hash_table_insert (bus->priv->idmap, GINT_TO_POINTER (listener->id), idmap); + + return listener->id; +} + +static void +remove_listener (GeditMessageBus *bus, + Message *message, + GList *listener) +{ + Listener *lst; + + lst = (Listener *)listener->data; + + /* remove from idmap */ + g_hash_table_remove (bus->priv->idmap, GINT_TO_POINTER (lst->id)); + listener_free (lst); + + /* remove from list of listeners */ + message->listeners = g_list_delete_link (message->listeners, listener); + + if (!message->listeners) + { + /* remove message because it does not have any listeners */ + g_hash_table_remove (bus->priv->messages, message->identifier); + } +} + +static void +block_listener (GeditMessageBus *bus, + Message *message, + GList *listener) +{ + Listener *lst; + + lst = listener->data; + lst->blocked = TRUE; +} + +static void +unblock_listener (GeditMessageBus *bus, + Message *message, + GList *listener) +{ + Listener *lst; + + lst = listener->data; + lst->blocked = FALSE; +} + +static void +dispatch_message_real (GeditMessageBus *bus, + Message *msg, + GeditMessage *message) +{ + GList *item; + + for (item = msg->listeners; item; item = item->next) + { + Listener *listener = (Listener *)item->data; + + if (!listener->blocked) + { + listener->callback (bus, message, listener->user_data); + } + } +} + +static void +gedit_message_bus_dispatch_real (GeditMessageBus *bus, + GeditMessage *message) +{ + const gchar *object_path; + const gchar *method; + Message *msg; + + object_path = gedit_message_get_object_path (message); + method = gedit_message_get_method (message); + + g_return_if_fail (object_path != NULL); + g_return_if_fail (method != NULL); + + msg = lookup_message (bus, object_path, method, FALSE); + + if (msg) + { + dispatch_message_real (bus, msg, message); + } +} + +static void +dispatch_message (GeditMessageBus *bus, + GeditMessage *message) +{ + g_signal_emit (bus, message_bus_signals[DISPATCH], 0, message); +} + +static gboolean +idle_dispatch (GeditMessageBus *bus) +{ + GList *list; + GList *item; + + /* make sure to set idle_id to 0 first so that any new async messages + will be queued properly */ + bus->priv->idle_id = 0; + + /* reverse queue to get correct delivery order */ + list = g_list_reverse (bus->priv->message_queue); + bus->priv->message_queue = NULL; + + for (item = list; item; item = item->next) + { + GeditMessage *msg = GEDIT_MESSAGE (item->data); + + dispatch_message (bus, msg); + } + + message_queue_free (list); + return FALSE; +} + +typedef void (*MatchCallback) (GeditMessageBus *, Message *, GList *); + +static void +process_by_id (GeditMessageBus *bus, + guint id, + MatchCallback processor) +{ + IdMap *idmap; + + idmap = (IdMap *)g_hash_table_lookup (bus->priv->idmap, GINT_TO_POINTER (id)); + + if (idmap == NULL) + { + g_warning ("No handler registered with id `%d'", id); + return; + } + + processor (bus, idmap->message, idmap->listener); +} + +static void +process_by_match (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer user_data, + MatchCallback processor) +{ + Message *message; + GList *item; + + message = lookup_message (bus, object_path, method, FALSE); + + if (!message) + { + g_warning ("No such handler registered for %s.%s", object_path, method); + return; + } + + for (item = message->listeners; item; item = item->next) + { + Listener *listener = (Listener *)item->data; + + if (listener->callback == callback && + listener->user_data == user_data) + { + processor (bus, message, item); + return; + } + } + + g_warning ("No such handler registered for %s.%s", object_path, method); +} + +static void +free_type (gpointer data) +{ + g_slice_free (GType, data); +} + +static void +gedit_message_bus_init (GeditMessageBus *self) +{ + self->priv = gedit_message_bus_get_instance_private (self); + + self->priv->messages = g_hash_table_new_full (message_identifier_hash, + message_identifier_equal, + NULL, + (GDestroyNotify) message_free); + + self->priv->idmap = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify) g_free); + + self->priv->types = g_hash_table_new_full (message_identifier_hash, + message_identifier_equal, + (GDestroyNotify) message_identifier_free, + (GDestroyNotify) free_type); +} + +/** + * gedit_message_bus_get_default: + * + * Get the default application #GeditMessageBus. + * + * Return value: (transfer none): the default #GeditMessageBus + * + */ +GeditMessageBus * +gedit_message_bus_get_default (void) +{ + static GeditMessageBus *default_bus = NULL; + + if (G_UNLIKELY (default_bus == NULL)) + { + default_bus = g_object_new (GEDIT_TYPE_MESSAGE_BUS, NULL); + + g_object_add_weak_pointer (G_OBJECT (default_bus), + (gpointer) &default_bus); + } + + return default_bus; +} + +/** + * gedit_message_bus_new: + * + * Create a new message bus. Use gedit_message_bus_get_default() to get the + * default, application wide, message bus. Creating a new bus is useful for + * associating a specific bus with for instance a #GeditWindow. + * + * Return value: a new #GeditMessageBus + * + */ +GeditMessageBus * +gedit_message_bus_new (void) +{ + return GEDIT_MESSAGE_BUS (g_object_new (GEDIT_TYPE_MESSAGE_BUS, NULL)); +} + +/** + * gedit_message_bus_lookup: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * + * Get the registered #GeditMessageType for @method at @object_path. The + * returned #GeditMessageType is owned by the bus and should not be unreffed. + * + * Return value: the registered #GeditMessageType or %NULL if no message type + * is registered for @method at @object_path + * + */ +GType +gedit_message_bus_lookup (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method) +{ + MessageIdentifier *identifier; + GType *message_type; + + g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), G_TYPE_INVALID); + g_return_val_if_fail (object_path != NULL, G_TYPE_INVALID); + g_return_val_if_fail (method != NULL, G_TYPE_INVALID); + + identifier = message_identifier_new (object_path, method); + message_type = g_hash_table_lookup (bus->priv->types, identifier); + message_identifier_free (identifier); + + if (!message_type) + { + return G_TYPE_INVALID; + } + else + { + return *message_type; + } +} + +/** + * gedit_message_bus_register: + * @bus: a #GeditMessageBus + * @message_type: the message type + * @object_path: the object path + * @method: the method to register + * + * Register a message on the bus. A message must be registered on the bus before + * it can be send. This function registers the type for @method at + * @object_path. + * + * This function emits a #GeditMessageBus::registered signal. + * + */ +void +gedit_message_bus_register (GeditMessageBus *bus, + GType message_type, + const gchar *object_path, + const gchar *method) +{ + MessageIdentifier *identifier; + GType *ntype; + + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (gedit_message_is_valid_object_path (object_path)); + g_return_if_fail (g_type_is_a (message_type, GEDIT_TYPE_MESSAGE)); + + if (gedit_message_bus_is_registered (bus, object_path, method)) + { + g_warning ("Message type for '%s.%s' is already registered", + object_path, + method); + } + + identifier = message_identifier_new (object_path, method); + ntype = g_slice_new (GType); + + *ntype = message_type; + + g_hash_table_insert (bus->priv->types, + identifier, + ntype); + + g_signal_emit (bus, + message_bus_signals[REGISTERED], + 0, + object_path, + method); +} + +static void +gedit_message_bus_unregister_real (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + gboolean remove_from_store) +{ + MessageIdentifier *identifier; + + identifier = message_identifier_new (object_path, method); + + if (!remove_from_store || g_hash_table_remove (bus->priv->types, + identifier)) + { + g_signal_emit (bus, + message_bus_signals[UNREGISTERED], + 0, + object_path, + method); + } + + message_identifier_free (identifier); +} + +/** + * gedit_message_bus_unregister: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * + * Unregisters a previously registered message type. This is especially useful + * for plugins which should unregister message types when they are deactivated. + * + * This function emits the #GeditMessageBus::unregistered signal. + * + */ +void +gedit_message_bus_unregister (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (object_path != NULL); + g_return_if_fail (method != NULL); + + gedit_message_bus_unregister_real (bus, + object_path, + method, + TRUE); +} + +typedef struct +{ + GeditMessageBus *bus; + const gchar *object_path; +} UnregisterInfo; + +static gboolean +unregister_each (MessageIdentifier *identifier, + GType *gtype, + UnregisterInfo *info) +{ + if (g_strcmp0 (identifier->object_path, info->object_path) == 0) + { + gedit_message_bus_unregister_real (info->bus, + identifier->object_path, + identifier->method, + FALSE); + + return TRUE; + } + + return FALSE; +} + +/** + * gedit_message_bus_unregister_all: + * @bus: a #GeditMessageBus + * @object_path: the object path + * + * Unregisters all message types for @object_path. This is especially useful for + * plugins which should unregister message types when they are deactivated. + * + * This function emits the #GeditMessageBus::unregistered signal for all + * unregistered message types. + * + */ +void +gedit_message_bus_unregister_all (GeditMessageBus *bus, + const gchar *object_path) +{ + UnregisterInfo info = {bus, object_path}; + + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (object_path != NULL); + + g_hash_table_foreach_remove (bus->priv->types, + (GHRFunc)unregister_each, + &info); +} + +/** + * gedit_message_bus_is_registered: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * + * Check whether a message type @method at @object_path is registered on the + * bus. + * + * Return value: %TRUE if the @method at @object_path is a registered message + * type on the bus + * + */ +gboolean +gedit_message_bus_is_registered (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method) +{ + MessageIdentifier *identifier; + gboolean ret; + + g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), FALSE); + g_return_val_if_fail (object_path != NULL, FALSE); + g_return_val_if_fail (method != NULL, FALSE); + + identifier = message_identifier_new (object_path, method); + ret = g_hash_table_lookup (bus->priv->types, identifier) != NULL; + message_identifier_free (identifier); + + return ret; +} + +typedef struct +{ + GeditMessageBusForeach func; + gpointer user_data; +} ForeachInfo; + +static void +foreach_type (MessageIdentifier *identifier, + GType *message_type, + ForeachInfo *info) +{ + info->func (identifier->object_path, + identifier->method, + info->user_data); +} + +/** + * gedit_message_bus_foreach: + * @bus: the #GeditMessageBus + * @func: (scope call): the callback function + * @user_data: the user data to supply to the callback function + * + * Calls @func for each message type registered on the bus + * + */ +void +gedit_message_bus_foreach (GeditMessageBus *bus, + GeditMessageBusForeach func, + gpointer user_data) +{ + ForeachInfo info = {func, user_data}; + + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (func != NULL); + + g_hash_table_foreach (bus->priv->types, (GHFunc)foreach_type, &info); +} + +/** + * gedit_message_bus_connect: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * @callback: function to be called when message @method at @object_path is sent + * @user_data: (allow-none): user_data to use for the callback + * @destroy_data: (allow-none): function to evoke with @user_data as argument when @user_data + * needs to be freed + * + * Connect a callback handler to be evoked when message @method at @object_path + * is sent over the bus. + * + * Return value: the callback identifier + * + */ +guint +gedit_message_bus_connect (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer user_data, + GDestroyNotify destroy_data) +{ + Message *message; + + g_return_val_if_fail (GEDIT_IS_MESSAGE_BUS (bus), 0); + g_return_val_if_fail (object_path != NULL, 0); + g_return_val_if_fail (method != NULL, 0); + g_return_val_if_fail (callback != NULL, 0); + + /* lookup the message and create if it does not exist yet */ + message = lookup_message (bus, object_path, method, TRUE); + + return add_listener (bus, message, callback, user_data, destroy_data); +} + +/** + * gedit_message_bus_disconnect: + * @bus: a #GeditMessageBus + * @id: the callback id as returned by gedit_message_bus_connect() + * + * Disconnects a previously connected message callback. + * + */ +void +gedit_message_bus_disconnect (GeditMessageBus *bus, + guint id) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_id (bus, id, remove_listener); +} + +/** + * gedit_message_bus_disconnect_by_func: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * @callback: (scope call): the connected callback + * @user_data: the user_data with which the callback was connected + * + * Disconnects a previously connected message callback by matching the + * provided callback function and user_data. See also + * gedit_message_bus_disconnect(). + * + */ +void +gedit_message_bus_disconnect_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer user_data) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_match (bus, + object_path, + method, + callback, + user_data, + remove_listener); +} + +/** + * gedit_message_bus_block: + * @bus: a #GeditMessageBus + * @id: the callback id + * + * Blocks evoking the callback specified by @id. Unblock the callback by + * using gedit_message_bus_unblock(). + * + */ +void +gedit_message_bus_block (GeditMessageBus *bus, + guint id) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_id (bus, id, block_listener); +} + +/** + * gedit_message_bus_block_by_func: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * @callback: (scope call): the callback to block + * @user_data: the user_data with which the callback was connected + * + * Blocks evoking the callback that matches provided @callback and @user_data. + * Unblock the callback using gedit_message_bus_unblock_by_func(). + * + */ +void +gedit_message_bus_block_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer user_data) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_match (bus, + object_path, + method, + callback, + user_data, + block_listener); +} + +/** + * gedit_message_bus_unblock: + * @bus: a #GeditMessageBus + * @id: the callback id + * + * Unblocks the callback specified by @id. + * + */ +void +gedit_message_bus_unblock (GeditMessageBus *bus, + guint id) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_id (bus, id, unblock_listener); +} + +/** + * gedit_message_bus_unblock_by_func: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * @callback: (scope call): the callback to block + * @user_data: the user_data with which the callback was connected + * + * Unblocks the callback that matches provided @callback and @user_data. + * + */ +void +gedit_message_bus_unblock_by_func (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + GeditMessageCallback callback, + gpointer user_data) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + + process_by_match (bus, + object_path, + method, + callback, + user_data, + unblock_listener); +} + +static void +send_message_real (GeditMessageBus *bus, + GeditMessage *message) +{ + bus->priv->message_queue = g_list_prepend (bus->priv->message_queue, + g_object_ref (message)); + + if (bus->priv->idle_id == 0) + { + bus->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH, + (GSourceFunc)idle_dispatch, + bus, + NULL); + } +} + +/** + * gedit_message_bus_send_message: + * @bus: a #GeditMessageBus + * @message: the message to send + * + * This sends the provided @message asynchronously over the bus. To send + * a message synchronously, use gedit_message_bus_send_message_sync(). The + * convenience function gedit_message_bus_send() can be used to easily send + * a message without constructing the message object explicitly first. + * + */ +void +gedit_message_bus_send_message (GeditMessageBus *bus, + GeditMessage *message) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (GEDIT_IS_MESSAGE (message)); + + send_message_real (bus, message); +} + +/** + * gedit_message_bus_send_message_sync: + * @bus: a #GeditMessageBus + * @message: the message to send + * + * This sends the provided @message synchronously over the bus. To send + * a message asynchronously, use gedit_message_bus_send_message(). The + * convenience function gedit_message_bus_send_sync() can be used to easily send + * a message without constructing the message object explicitly first. + * + */ +void +gedit_message_bus_send_message_sync (GeditMessageBus *bus, + GeditMessage *message) +{ + g_return_if_fail (GEDIT_IS_MESSAGE_BUS (bus)); + g_return_if_fail (GEDIT_IS_MESSAGE (message)); + + dispatch_message (bus, message); +} + +static GeditMessage * +create_message (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + const gchar *first_property, + va_list var_args) +{ + GType message_type; + GeditMessage *msg; + + message_type = gedit_message_bus_lookup (bus, object_path, method); + + if (message_type == G_TYPE_INVALID) + { + g_warning ("Could not find message type for '%s.%s'", + object_path, + method); + + return NULL; + } + + msg = GEDIT_MESSAGE (g_object_new_valist (message_type, + first_property, + var_args)); + + if (msg) + { + g_object_set (msg, + "object_path", + object_path, + "method", + method, + NULL); + } + + return msg; +} + +/** + * gedit_message_bus_send: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * @first_property: the first property + * @...: NULL terminated list of key/value pairs + * + * This provides a convenient way to quickly send a message @method at + * @object_path asynchronously over the bus. The variable argument list + * specifies key (string) value pairs used to construct the message arguments. + * To send a message synchronously use gedit_message_bus_send_sync(). + */ +void +gedit_message_bus_send (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + const gchar *first_property, + ...) +{ + va_list var_args; + GeditMessage *message; + + va_start (var_args, first_property); + + message = create_message (bus, + object_path, + method, + first_property, + var_args); + + if (message) + { + send_message_real (bus, message); + g_object_unref (message); + } + else + { + g_warning ("Could not instantiate message"); + } + + va_end (var_args); +} + +/** + * gedit_message_bus_send_sync: + * @bus: a #GeditMessageBus + * @object_path: the object path + * @method: the method + * @first_property: the first property + * @...: (allow-none): %NULL terminated list of key/value pairs + * + * This provides a convenient way to quickly send a message @method at + * @object_path synchronously over the bus. The variable argument list + * specifies key (string) value pairs used to construct the message + * arguments. To send a message asynchronously use gedit_message_bus_send(). + * + * Return value: (allow-none) (transfer full): the constructed #GeditMessage. + * The caller owns a reference to the #GeditMessage and should + * call g_object_unref() when it is no longer needed. + */ +GeditMessage * +gedit_message_bus_send_sync (GeditMessageBus *bus, + const gchar *object_path, + const gchar *method, + const gchar *first_property, + ...) +{ + va_list var_args; + GeditMessage *message; + + va_start (var_args, first_property); + message = create_message (bus, + object_path, + method, + first_property, + var_args); + + if (message) + { + dispatch_message (bus, message); + } + + va_end (var_args); + + return message; +} + +/* ex:set ts=8 noet: */ |