/* * 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 #include #include /** * 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). * * * Registering a message type * * 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"); * * * * Connecting a callback * * 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); * * * * * Sending a message * * GeditMessageBus *bus = gedit_message_bus_get_default (); * * gedit_message_bus_send (bus, * "/plugins/example", "method", * "arg1", "Hello World", * NULL); * * */ 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: */