aboutsummaryrefslogtreecommitdiff
path: root/libdbusmenu-glib/client.c
diff options
context:
space:
mode:
authorTed Gould <ted@gould.cx>2011-02-21 11:22:46 -0600
committerTed Gould <ted@gould.cx>2011-02-21 11:22:46 -0600
commit2f92a8662f5a84f9ef35dd847539f573f60edc5f (patch)
tree9c00fda471a88fcc331c858bc2de66076f7de9ef /libdbusmenu-glib/client.c
parent71020d95b0eb9f583523628261d26336a5dceb39 (diff)
parent34248e4fe3aeef17be5fe9f2339057fbaee81ad2 (diff)
downloadlibdbusmenu-2f92a8662f5a84f9ef35dd847539f573f60edc5f.tar.gz
libdbusmenu-2f92a8662f5a84f9ef35dd847539f573f60edc5f.tar.bz2
libdbusmenu-2f92a8662f5a84f9ef35dd847539f573f60edc5f.zip
Another update to trunk
Diffstat (limited to 'libdbusmenu-glib/client.c')
-rw-r--r--libdbusmenu-glib/client.c1368
1 files changed, 1076 insertions, 292 deletions
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
index c0d3b7a..0848294 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -30,15 +30,19 @@ License version 3 and version 2.1 along with this program. If not, see
#include "config.h"
#endif
-#include <libxml/parser.h>
-#include <libxml/tree.h>
+#include <gio/gio.h>
#include "client.h"
#include "menuitem.h"
#include "menuitem-private.h"
#include "client-menuitem.h"
-#include "dbusmenu-client.h"
#include "server-marshal.h"
+#include "client-marshal.h"
+#include "dbus-menu-clean.xml.h"
+
+/* How many property requests should we queue before
+ sending the message on dbus */
+#define MAX_PROPERTIES_TO_QUEUE 100
/* Properties */
enum {
@@ -52,12 +56,15 @@ enum {
LAYOUT_UPDATED,
ROOT_CHANGED,
NEW_MENUITEM,
+ ITEM_ACTIVATE,
+ EVENT_RESULT,
LAST_SIGNAL
};
+typedef void (*properties_func) (GVariant * properties, GError * error, gpointer user_data);
+
static guint signals[LAST_SIGNAL] = { 0 };
-typedef struct _DbusmenuClientPrivate DbusmenuClientPrivate;
struct _DbusmenuClientPrivate
{
DbusmenuMenuitem * root;
@@ -65,17 +72,24 @@ struct _DbusmenuClientPrivate
gchar * dbus_object;
gchar * dbus_name;
- DBusGConnection * session_bus;
- DBusGProxy * menuproxy;
- DBusGProxy * propproxy;
- DBusGProxyCall * layoutcall;
+ GDBusConnection * session_bus;
+ GCancellable * session_bus_cancel;
+
+ GDBusProxy * menuproxy;
+ GCancellable * menuproxy_cancel;
+
+ GCancellable * layoutcall;
gint current_revision;
gint my_revision;
- DBusGProxy * dbusproxy;
+ guint dbusproxy;
GHashTable * type_handlers;
+
+ GArray * delayed_property_list;
+ GArray * delayed_property_listeners;
+ gint delayed_idle;
};
typedef struct _newItemPropData newItemPropData;
@@ -86,8 +100,41 @@ struct _newItemPropData
DbusmenuMenuitem * parent;
};
-#define DBUSMENU_CLIENT_GET_PRIVATE(o) \
-(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate))
+typedef struct _properties_listener_t properties_listener_t;
+struct _properties_listener_t {
+ gint id;
+ properties_func callback;
+ gpointer user_data;
+ gboolean replied;
+};
+
+typedef struct _event_data_t event_data_t;
+struct _event_data_t {
+ DbusmenuClient * client;
+ DbusmenuMenuitem * menuitem;
+ gchar * event;
+ GVariant * variant;
+ guint timestamp;
+};
+
+typedef struct _type_handler_t type_handler_t;
+struct _type_handler_t {
+ DbusmenuClient * client;
+ DbusmenuClientTypeHandler cb;
+ DbusmenuClientTypeDestroyHandler destroy_cb;
+ gpointer user_data;
+ gchar * type;
+};
+
+typedef struct _properties_callback_t properties_callback_t;
+struct _properties_callback_t {
+ DbusmenuClient * client;
+ GArray * listeners;
+};
+
+
+#define DBUSMENU_CLIENT_GET_PRIVATE(o) (DBUSMENU_CLIENT(o)->priv)
+#define DBUSMENU_INTERFACE "com.canonical.dbusmenu"
/* GObject Stuff */
static void dbusmenu_client_class_init (DbusmenuClientClass *klass);
@@ -97,16 +144,26 @@ static void dbusmenu_client_finalize (GObject *object);
static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec);
static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
/* Private Funcs */
-static void layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient * client);
-static void id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, DbusmenuClient * client);
-static void id_update (DBusGProxy * proxy, gint id, DbusmenuClient * client);
+static void layout_update (GDBusProxy * proxy, guint revision, gint parent, DbusmenuClient * client);
+static void id_prop_update (GDBusProxy * proxy, gint id, gchar * property, GVariant * value, DbusmenuClient * client);
+static void id_update (GDBusProxy * proxy, gint id, DbusmenuClient * client);
static void build_proxies (DbusmenuClient * client);
-static gint parse_node_get_id (xmlNodePtr node);
-static DbusmenuMenuitem * parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy);
-static gint parse_layout (DbusmenuClient * client, const gchar * layout);
-static void update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * in_error, void * data);
+static DbusmenuMenuitem * parse_layout_xml(DbusmenuClient * client, GVariant * layout, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, GDBusProxy * proxy);
+static gint parse_layout (DbusmenuClient * client, GVariant * layout);
+static void update_layout_cb (GObject * proxy, GAsyncResult * res, gpointer data);
static void update_layout (DbusmenuClient * client);
-static void menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data);
+static void menuitem_get_properties_cb (GVariant * properties, GError * error, gpointer data);
+static void get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, properties_func callback, gpointer user_data);
+static GQuark error_domain (void);
+static void item_activated (GDBusProxy * proxy, gint id, guint timestamp, DbusmenuClient * client);
+static void menuproxy_build_cb (GObject * object, GAsyncResult * res, gpointer user_data);
+static void menuproxy_name_changed_cb (GObject * object, GParamSpec * pspec, gpointer user_data);
+static void menuproxy_signal_cb (GDBusProxy * proxy, gchar * sender, gchar * signal, GVariant * params, gpointer user_data);
+static void type_handler_destroy (gpointer user_data);
+
+/* Globals */
+static GDBusNodeInfo * dbusmenu_node_info = NULL;
+static GDBusInterfaceInfo * dbusmenu_interface_info = NULL;
/* Build a type */
G_DEFINE_TYPE (DbusmenuClient, dbusmenu_client, G_TYPE_OBJECT);
@@ -171,6 +228,41 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass)
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
+ /**
+ DbusmenuClient::item-activate:
+ @arg0: The #DbusmenuClient object
+ @arg1: The #DbusmenuMenuitem activated
+ @arg2: A timestamp that the event happened at
+
+ Signaled when the server wants to activate an item in
+ order to display the menu.
+ */
+ signals[ITEM_ACTIVATE] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE,
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DbusmenuClientClass, item_activate),
+ NULL, NULL,
+ _dbusmenu_client_marshal_VOID__OBJECT_UINT,
+ G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT);
+ /**
+ DbusmenuClient::event-error:
+ @arg0: The #DbusmenuClient object
+ @arg1: The #DbusmenuMenuitem sent an event
+ @arg2: The ID of the event sent
+ @arg3: The data sent along with the event
+ @arg4: A timestamp that the event happened at
+ @arg5: Possibly the error in sending the event (or NULL)
+
+ Signal sent to show that there was an error in sending the event
+ to the server.
+ */
+ signals[EVENT_RESULT] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT,
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DbusmenuClientClass, event_result),
+ NULL, NULL,
+ _dbusmenu_client_marshal_VOID__OBJECT_STRING_VARIANT_UINT_POINTER,
+ G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, G_TYPE_VARIANT, G_TYPE_UINT, G_TYPE_POINTER);
g_object_class_install_property (object_class, PROP_DBUSOBJECT,
g_param_spec_string(DBUSMENU_CLIENT_PROP_DBUS_OBJECT, "DBus Object we represent",
@@ -183,12 +275,32 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass)
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+ if (dbusmenu_node_info == NULL) {
+ GError * error = NULL;
+
+ dbusmenu_node_info = g_dbus_node_info_new_for_xml(dbus_menu_clean_xml, &error);
+ if (error != NULL) {
+ g_error("Unable to parse DBusmenu Interface description: %s", error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (dbusmenu_interface_info == NULL) {
+ dbusmenu_interface_info = g_dbus_node_info_lookup_interface(dbusmenu_node_info, DBUSMENU_INTERFACE);
+
+ if (dbusmenu_interface_info == NULL) {
+ g_error("Unable to find interface '" DBUSMENU_INTERFACE "'");
+ }
+ }
+
return;
}
static void
dbusmenu_client_init (DbusmenuClient *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate);
+
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(self);
priv->root = NULL;
@@ -197,17 +309,24 @@ dbusmenu_client_init (DbusmenuClient *self)
priv->dbus_name = NULL;
priv->session_bus = NULL;
+ priv->session_bus_cancel = NULL;
+
priv->menuproxy = NULL;
- priv->propproxy = NULL;
+ priv->menuproxy_cancel = NULL;
+
priv->layoutcall = NULL;
priv->current_revision = 0;
priv->my_revision = 0;
- priv->dbusproxy = NULL;
+ priv->dbusproxy = 0;
priv->type_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
- g_free, NULL);
+ g_free, type_handler_destroy);
+
+ priv->delayed_idle = 0;
+ priv->delayed_property_list = g_array_new(TRUE, FALSE, sizeof(gchar *));
+ priv->delayed_property_listeners = g_array_new(FALSE, FALSE, sizeof(properties_listener_t));
return;
}
@@ -217,23 +336,78 @@ dbusmenu_client_dispose (GObject *object)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(object);
+ if (priv->delayed_idle != 0) {
+ g_source_remove(priv->delayed_idle);
+ priv->delayed_idle = 0;
+ }
+
+ /* Only used for queueing up a new command, so we can
+ just drop this array. */
+ if (priv->delayed_property_list == NULL) {
+ gchar ** dataregion = (gchar **)g_array_free(priv->delayed_property_list, FALSE);
+ if (dataregion != NULL) {
+ g_strfreev(dataregion);
+ }
+ priv->delayed_property_list = NULL;
+ }
+
+ if (priv->delayed_property_listeners == NULL) {
+ gint i;
+ GError * localerror = NULL;
+
+ /* Making sure all the callbacks get called so that if they had
+ memory in their user_data that needs to be free'd that happens. */
+ for (i = 0; i < priv->delayed_property_listeners->len; i++) {
+ properties_listener_t * listener = &g_array_index(priv->delayed_property_listeners, properties_listener_t, i);
+ if (!listener->replied) {
+ if (localerror == NULL) {
+ g_set_error_literal(&localerror, error_domain(), 0, "DbusmenuClient Shutdown");
+ }
+ listener->callback(NULL, localerror, listener->user_data);
+ }
+ }
+ if (localerror != NULL) {
+ g_error_free(localerror);
+ }
+
+ g_array_free(priv->delayed_property_listeners, TRUE);
+ priv->delayed_property_listeners = NULL;
+ }
+
if (priv->layoutcall != NULL) {
- dbus_g_proxy_cancel_call(priv->menuproxy, priv->layoutcall);
+ g_cancellable_cancel(priv->layoutcall);
+ g_object_unref(priv->layoutcall);
priv->layoutcall = NULL;
}
+
+ /* Bring down the menu proxy, ensure we're not
+ looking for one at the same time. */
+ if (priv->menuproxy_cancel != NULL) {
+ g_cancellable_cancel(priv->menuproxy_cancel);
+ g_object_unref(priv->menuproxy_cancel);
+ priv->menuproxy_cancel = NULL;
+ }
if (priv->menuproxy != NULL) {
g_object_unref(G_OBJECT(priv->menuproxy));
priv->menuproxy = NULL;
}
- if (priv->propproxy != NULL) {
- g_object_unref(G_OBJECT(priv->propproxy));
- priv->propproxy = NULL;
+
+ if (priv->dbusproxy != 0) {
+ g_bus_unwatch_name(priv->dbusproxy);
+ priv->dbusproxy = 0;
+ }
+
+ /* Bring down the session bus, ensure we're not
+ looking for one at the same time. */
+ if (priv->session_bus_cancel != NULL) {
+ g_cancellable_cancel(priv->session_bus_cancel);
+ g_object_unref(priv->session_bus_cancel);
+ priv->session_bus_cancel = NULL;
}
- if (priv->dbusproxy != NULL) {
- g_object_unref(G_OBJECT(priv->dbusproxy));
- priv->dbusproxy = NULL;
+ if (priv->session_bus != NULL) {
+ g_object_unref(priv->session_bus);
+ priv->session_bus = NULL;
}
- priv->session_bus = NULL;
if (priv->root != NULL) {
g_object_unref(G_OBJECT(priv->root));
@@ -308,9 +482,276 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
/* Internal funcs */
+static GQuark
+error_domain (void)
+{
+ static GQuark error = 0;
+ if (error == 0) {
+ error = g_quark_from_static_string(G_LOG_DOMAIN "-CLIENT");
+ }
+ return error;
+}
+
+/* Quick little function to search through the listeners and find
+ one that matches an ID */
+static properties_listener_t *
+find_listener (GArray * listeners, guint index, gint id)
+{
+ if (index >= listeners->len) {
+ return NULL;
+ }
+
+ properties_listener_t * retval = &g_array_index(listeners, properties_listener_t, index);
+ if (retval->id == id) {
+ return retval;
+ }
+
+ return find_listener(listeners, index + 1, id);
+}
+
+/* Call back from getting the group properties, now we need
+ to unwind and call the various functions. */
+static void
+get_properties_callback (GObject *obj, GAsyncResult * res, gpointer user_data)
+{
+ properties_callback_t * cbdata = (properties_callback_t *)user_data;
+ GArray * listeners = cbdata->listeners;
+ int i;
+ GError * error = NULL;
+ GVariant * params = NULL;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(obj), res, &error);
+
+ if (error != NULL) {
+ /* If we get an error, all our callbacks need to hear about it. */
+ g_warning("Group Properties error: %s", error->message);
+ for (i = 0; i < listeners->len; i++) {
+ properties_listener_t * listener = &g_array_index(listeners, properties_listener_t, i);
+ listener->callback(NULL, error, listener->user_data);
+ }
+ g_error_free(error);
+ goto out;
+ }
+
+ /* Callback all the folks we can find */
+ GVariantIter * iter = g_variant_iter_new(g_variant_get_child_value(params, 0));
+ GVariant * child;
+ while ((child = g_variant_iter_next_value(iter)) != NULL) {
+ if (g_strcmp0(g_variant_get_type_string(child), "(ia{sv})") != 0) {
+ g_warning("Properties return signature is not '(ia{sv})' it is '%s'", g_variant_get_type_string(child));
+ continue;
+ }
+
+ gint id = g_variant_get_int32(g_variant_get_child_value(child, 0));
+ GVariant * properties = g_variant_get_child_value(child, 1);
+
+ properties_listener_t * listener = find_listener(listeners, 0, id);
+ if (listener == NULL) {
+ g_warning("Unable to find listener for ID %d", id);
+ continue;
+ }
+
+ if (!listener->replied) {
+ listener->callback(properties, NULL, listener->user_data);
+ listener->replied = TRUE;
+ } else {
+ g_warning("Odd, we've already replied to the listener on ID %d", id);
+ }
+ }
+ g_variant_iter_free(iter);
+ g_variant_unref(params);
+
+ /* Provide errors for those who we can't */
+ GError * localerror = NULL;
+ for (i = 0; i < listeners->len; i++) {
+ properties_listener_t * listener = &g_array_index(listeners, properties_listener_t, i);
+ if (!listener->replied) {
+ g_warning("Generating properties error for: %d", listener->id);
+ if (localerror == NULL) {
+ g_set_error_literal(&localerror, error_domain(), 0, "Error getting properties for ID");
+ }
+ listener->callback(NULL, localerror, listener->user_data);
+ }
+ }
+ if (localerror != NULL) {
+ g_error_free(localerror);
+ }
+
+out:
+ /* Clean up */
+ g_array_free(listeners, TRUE);
+ g_object_unref(cbdata->client);
+ g_free(user_data);
+
+ return;
+}
+
+/* Idle handler to send out all of our property requests as one big
+ lovely property request. */
+static gboolean
+get_properties_idle (gpointer user_data)
+{
+ properties_callback_t * cbdata = NULL;
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(user_data);
+ g_return_val_if_fail(priv->menuproxy != NULL, TRUE);
+
+ if (priv->delayed_property_listeners->len == 0) {
+ g_warning("Odd, idle func got no listeners.");
+ return FALSE;
+ }
+
+ /* Build up an ID list to pass */
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ gint i;
+ for (i = 0; i < priv->delayed_property_listeners->len; i++) {
+ g_variant_builder_add(&builder, "i", g_array_index(priv->delayed_property_listeners, properties_listener_t, i).id);
+ }
+
+ GVariant * variant_ids = g_variant_builder_end(&builder);
+
+ /* Build up a prop list to pass */
+ g_variant_builder_init(&builder, g_variant_type_new("as"));
+ /* TODO: need to use delayed property list here */
+ GVariant * variant_props = g_variant_builder_end(&builder);
+
+ /* Combine them into a value for the parameter */
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value(&builder, variant_ids);
+ g_variant_builder_add_value(&builder, variant_props);
+ GVariant * variant_params = g_variant_builder_end(&builder);
+
+ cbdata = g_new(properties_callback_t, 1);
+ cbdata->listeners = priv->delayed_property_listeners;
+ cbdata->client = DBUSMENU_CLIENT(user_data);
+ g_object_ref(G_OBJECT(user_data));
+
+ g_dbus_proxy_call(priv->menuproxy,
+ "GetGroupProperties",
+ variant_params,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ NULL, /* cancellable */
+ get_properties_callback,
+ cbdata);
+
+ /* Free properties */
+ gchar ** dataregion = (gchar **)g_array_free(priv->delayed_property_list, FALSE);
+ if (dataregion != NULL) {
+ g_strfreev(dataregion);
+ }
+ priv->delayed_property_list = g_array_new(TRUE, FALSE, sizeof(gchar *));
+
+ /* Rebuild the listeners */
+ priv->delayed_property_listeners = g_array_new(FALSE, FALSE, sizeof(properties_listener_t));
+
+ /* Make sure we set for a new idle */
+ priv->delayed_idle = 0;
+
+ return FALSE;
+}
+
+/* Forces a call out to start getting properties with the menu items
+ that we have queued up already. */
+static void
+get_properties_flush (DbusmenuClient * client)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (priv->delayed_idle == 0) {
+ return;
+ }
+
+ g_source_remove(priv->delayed_idle);
+ priv->delayed_idle = 0;
+
+ get_properties_idle(client);
+
+ return;
+}
+
+/* A function to group all the get_properties commands to make them
+ more efficient over dbus. */
+static void
+get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, properties_func callback, gpointer user_data)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ if (find_listener(priv->delayed_property_listeners, 0, id) != NULL) {
+ g_warning("Asking for properties from same ID twice: %d", id);
+ GError * localerror = NULL;
+ g_set_error_literal(&localerror, error_domain(), 0, "ID already queued");
+ callback(NULL, localerror, user_data);
+ g_error_free(localerror);
+ return;
+ }
+
+ if (properties == NULL || properties[0] == NULL) {
+ /* get all case */
+ if (priv->delayed_property_list->len != 0) {
+ /* If there are entries in the list, then we'll need to
+ remove them all, and start over */
+ gchar ** dataregion = (gchar **)g_array_free(priv->delayed_property_list, FALSE);
+ if (dataregion != NULL) {
+ g_strfreev(dataregion);
+ }
+ priv->delayed_property_list = g_array_new(TRUE, FALSE, sizeof(gchar *));
+ }
+ } else {
+ /* there could be a list we care about */
+ /* TODO: No one uses this today */
+ /* TODO: Copy them into the list */
+ }
+
+ properties_listener_t listener = {0};
+ listener.id = id;
+ listener.callback = callback;
+ listener.user_data = user_data;
+ listener.replied = FALSE;
+
+ g_array_append_val(priv->delayed_property_listeners, listener);
+
+ if (priv->delayed_idle == 0) {
+ priv->delayed_idle = g_idle_add(get_properties_idle, client);
+ }
+
+ /* Look at how many proprites we have queued up and
+ make it so that we don't leave too many in one
+ request. */
+ if (priv->delayed_property_listeners->len == MAX_PROPERTIES_TO_QUEUE) {
+ get_properties_flush(client);
+ }
+
+ return;
+}
+
+/* Called when a server item wants to activate the menu */
+static void
+item_activated (GDBusProxy * proxy, gint id, guint timestamp, DbusmenuClient * client)
+{
+ g_return_if_fail(DBUSMENU_IS_CLIENT(client));
+
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (priv->root == NULL) {
+ g_warning("Asked to activate item %d when we don't have a menu structure.", id);
+ return;
+ }
+
+ DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
+ if (menuitem == NULL) {
+ g_warning("Unable to find menu item %d to activate.", id);
+ return;
+ }
+
+ g_signal_emit(G_OBJECT(client), signals[ITEM_ACTIVATE], 0, menuitem, timestamp, TRUE);
+
+ return;
+}
+
/* Annoying little wrapper to make the right function update */
static void
-layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient * client)
+layout_update (GDBusProxy * proxy, guint revision, gint parent, DbusmenuClient * client)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
priv->current_revision = revision;
@@ -323,16 +764,8 @@ layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient *
/* Signal from the server that a property has changed
on one of our menuitems */
static void
-id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, DbusmenuClient * client)
+id_prop_update (GDBusProxy * proxy, gint id, gchar * property, GVariant * value, DbusmenuClient * client)
{
- #ifdef MASSIVEDEBUGGING
- GValue valstr = {0};
- g_value_init(&valstr, G_TYPE_STRING);
- g_value_transform(value, &valstr);
- g_debug("Property change sent to client for item %d property %s value %s", id, property, g_utf8_strlen(g_value_get_string(&valstr), 50) < 25 ? g_value_get_string(&valstr) : "<too long>");
- g_value_unset(&valstr);
- #endif
-
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
g_return_if_fail(priv->root != NULL);
@@ -345,7 +778,7 @@ id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, D
return;
}
- dbusmenu_menuitem_property_set_value(menuitem, property, value);
+ dbusmenu_menuitem_property_set_variant(menuitem, property, value);
return;
}
@@ -353,7 +786,7 @@ id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, D
/* Oh, lots of updates now. That silly server, they want
to change all kinds of stuff! */
static void
-id_update (DBusGProxy * proxy, gint id, DbusmenuClient * client)
+id_update (GDBusProxy * proxy, gint id, DbusmenuClient * client)
{
#ifdef MASSIVEDEBUGGING
g_debug("Client side ID update: %d", id);
@@ -365,32 +798,19 @@ id_update (DBusGProxy * proxy, gint id, DbusmenuClient * client)
DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
g_return_if_fail(menuitem != NULL);
- gchar * properties[1] = {NULL}; /* This gets them all */
g_debug("Getting properties");
g_object_ref(menuitem);
- org_ayatana_dbusmenu_get_properties_async(proxy, id, (const gchar **)properties, menuitem_get_properties_cb, menuitem);
+ get_properties_globber(client, id, NULL, menuitem_get_properties_cb, menuitem);
return;
}
/* Watches to see if our DBus savior comes onto the bus */
static void
-dbus_owner_change (DBusGProxy * proxy, const gchar * name, const gchar * prev, const gchar * new, DbusmenuClient * client)
+dbus_owner_change (GDBusConnection * connection, const gchar * name, const gchar * owner, gpointer user_data)
{
- DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- /* g_debug("Owner change: %s %s %s", name, prev, new); */
-
- if (!(new[0] != '\0' && prev[0] == '\0')) {
- /* If it's not someone new getting on the bus, sorry we
- simply just don't care. It's not that your service isn't
- important to someone, just not us. You'll find the right
- process someday, there's lots of processes out there. */
- return;
- }
+ g_return_if_fail(DBUSMENU_IS_CLIENT(user_data));
- if (g_strcmp0(name, priv->dbus_name)) {
- /* Again, someone else's service. */
- return;
- }
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
/* Woot! A service for us to love and to hold for ever
and ever and ever! */
@@ -403,28 +823,22 @@ static void
build_dbus_proxy (DbusmenuClient * client)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- GError * error = NULL;
- if (priv->dbusproxy != NULL) {
+ if (priv->dbusproxy != 0) {
return;
}
- priv->dbusproxy = dbus_g_proxy_new_for_name_owner (priv->session_bus,
- DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS,
- DBUS_INTERFACE_DBUS,
- &error);
- if (error != NULL) {
- g_debug("Oh, that's bad. That's really bad. We can't get a proxy to DBus itself? Seriously? Here's all I know: %s", error->message);
- g_error_free(error);
- return;
- }
+ priv->dbusproxy = g_bus_watch_name_on_connection(priv->session_bus,
+ priv->dbus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ dbus_owner_change,
+ NULL,
+ client,
+ NULL);
- dbus_g_proxy_add_signal(priv->dbusproxy, "NameOwnerChanged",
- G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
- G_TYPE_INVALID);
- dbus_g_proxy_connect_signal(priv->dbusproxy, "NameOwnerChanged",
- G_CALLBACK(dbus_owner_change), client, NULL);
+ /* Now let's check to make sure we're not in some race
+ condition case. */
+ /* TODO: Not sure how to check for names in GDBus */
return;
}
@@ -448,7 +862,11 @@ proxy_destroyed (GObject * gobj_proxy, gpointer userdata)
}
if ((gpointer)priv->menuproxy == (gpointer)gobj_proxy) {
- priv->layoutcall = NULL;
+ if (priv->layoutcall != NULL) {
+ g_cancellable_cancel(priv->layoutcall);
+ g_object_unref(priv->layoutcall);
+ priv->layoutcall = NULL;
+ }
}
priv->current_revision = 0;
@@ -458,112 +876,226 @@ proxy_destroyed (GObject * gobj_proxy, gpointer userdata)
return;
}
+/* Respond to us getting the session bus (hopefully) or handle
+ the error if not */
+void
+session_bus_cb (GObject * object, GAsyncResult * res, gpointer user_data)
+{
+ GError * error = NULL;
+
+ /* NOTE: We're not using any other variables before checking
+ the result because they could be destroyed and thus invalid */
+ GDBusConnection * bus = g_bus_get_finish(res, &error);
+ if (error != NULL) {
+ g_warning("Unable to get session bus: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ /* If this wasn't cancelled, we should be good */
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ priv->session_bus = bus;
+
+ if (priv->session_bus_cancel != NULL) {
+ g_object_unref(priv->session_bus_cancel);
+ priv->session_bus_cancel = NULL;
+ }
+
+ /* Retry to build the proxies now that we have a bus */
+ build_proxies(DBUSMENU_CLIENT(user_data));
+
+ return;
+}
+
/* When we have a name and an object, build the two proxies and get the
first version of the layout */
static void
build_proxies (DbusmenuClient * client)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- GError * error = NULL;
g_return_if_fail(priv->dbus_object != NULL);
g_return_if_fail(priv->dbus_name != NULL);
- priv->session_bus = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
- if (error != NULL) {
- g_error("Unable to get session bus: %s", error->message);
- g_error_free(error);
- build_dbus_proxy(client);
+ if (priv->session_bus == NULL) {
+ /* We don't have the session bus yet, that's okay, but
+ we need to handle that. */
+
+ /* If we're already running we don't need to look again. */
+ if (priv->session_bus_cancel == NULL) {
+ priv->session_bus_cancel = g_cancellable_new();
+
+ /* Async get the session bus */
+ g_bus_get(G_BUS_TYPE_SESSION, priv->session_bus_cancel, session_bus_cb, client);
+ }
+
+ /* This function exists, it'll be called again when we get
+ the session bus so this condition will be ignored */
return;
}
- priv->propproxy = dbus_g_proxy_new_for_name_owner(priv->session_bus,
- priv->dbus_name,
- priv->dbus_object,
- DBUS_INTERFACE_PROPERTIES,
- &error);
- if (error != NULL) {
- g_warning("Unable to get property proxy for %s on %s: %s", priv->dbus_name, priv->dbus_object, error->message);
- g_error_free(error);
- build_dbus_proxy(client);
- return;
+ /* Build us a menu proxy */
+ if (priv->menuproxy == NULL) {
+
+ /* Check to see if we're already building one */
+ if (priv->menuproxy_cancel == NULL) {
+ priv->menuproxy_cancel = g_cancellable_new();
+
+ g_dbus_proxy_new(priv->session_bus,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ dbusmenu_interface_info,
+ priv->dbus_name,
+ priv->dbus_object,
+ DBUSMENU_INTERFACE,
+ priv->menuproxy_cancel,
+ menuproxy_build_cb,
+ client);
+ }
}
- g_object_add_weak_pointer(G_OBJECT(priv->propproxy), (gpointer *)&priv->propproxy);
- g_signal_connect(G_OBJECT(priv->propproxy), "destroy", G_CALLBACK(proxy_destroyed), client);
- priv->menuproxy = dbus_g_proxy_new_for_name_owner(priv->session_bus,
- priv->dbus_name,
- priv->dbus_object,
- "org.ayatana.dbusmenu",
- &error);
+ return;
+}
+
+/* Callback when we know if the menu proxy can be created or
+ not and do something with it! */
+static void
+menuproxy_build_cb (GObject * object, GAsyncResult * res, gpointer user_data)
+{
+ GError * error = NULL;
+
+ /* NOTE: We're not using any other variables before checking
+ the result because they could be destroyed and thus invalid */
+ GDBusProxy * proxy = g_dbus_proxy_new_finish(res, &error);
if (error != NULL) {
- g_warning("Unable to get dbusmenu proxy for %s on %s: %s", priv->dbus_name, priv->dbus_object, error->message);
+ g_warning("Unable to get menu proxy: %s", error->message);
g_error_free(error);
- build_dbus_proxy(client);
return;
}
- g_object_add_weak_pointer(G_OBJECT(priv->menuproxy), (gpointer *)&priv->menuproxy);
- g_signal_connect(G_OBJECT(priv->menuproxy), "destroy", G_CALLBACK(proxy_destroyed), client);
- /* If we get here, we don't need the DBus proxy */
- if (priv->dbusproxy != NULL) {
- g_object_unref(G_OBJECT(priv->dbusproxy));
- priv->dbusproxy = NULL;
- }
+ /* If this wasn't cancelled, we should be good */
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ priv->menuproxy = proxy;
- dbus_g_object_register_marshaller(_dbusmenu_server_marshal_VOID__UINT_INT, G_TYPE_NONE, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
- dbus_g_proxy_add_signal(priv->menuproxy, "LayoutUpdated", G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
- dbus_g_proxy_connect_signal(priv->menuproxy, "LayoutUpdated", G_CALLBACK(layout_update), client, NULL);
+ if (priv->menuproxy_cancel != NULL) {
+ g_object_unref(priv->menuproxy_cancel);
+ priv->menuproxy_cancel = NULL;
+ }
- dbus_g_object_register_marshaller(_dbusmenu_server_marshal_VOID__INT_STRING_POINTER, G_TYPE_NONE, G_TYPE_INT, G_TYPE_STRING, G_TYPE_VALUE, G_TYPE_INVALID);
- dbus_g_proxy_add_signal(priv->menuproxy, "ItemPropertyUpdated", G_TYPE_INT, G_TYPE_STRING, G_TYPE_VALUE, G_TYPE_INVALID);
- dbus_g_proxy_connect_signal(priv->menuproxy, "ItemPropertyUpdated", G_CALLBACK(id_prop_update), client, NULL);
+ /* If we get here, we don't need the DBus proxy */
+ if (priv->dbusproxy != 0) {
+ g_bus_unwatch_name(priv->dbusproxy);
+ priv->dbusproxy = 0;
+ }
- dbus_g_proxy_add_signal(priv->menuproxy, "ItemUpdated", G_TYPE_INT, G_TYPE_INVALID);
- dbus_g_proxy_connect_signal(priv->menuproxy, "ItemUpdated", G_CALLBACK(id_update), client, NULL);
+ g_signal_connect(priv->menuproxy, "g-signal", G_CALLBACK(menuproxy_signal_cb), client);
+ g_signal_connect(priv->menuproxy, "notify::g-name-owner", G_CALLBACK(menuproxy_name_changed_cb), client);
- update_layout(client);
+ gchar * name_owner = g_dbus_proxy_get_name_owner(priv->menuproxy);
+ if (name_owner != NULL) {
+ update_layout(client);
+ g_free(name_owner);
+ }
return;
}
-/* Get the ID attribute of the node, parse it and
- return it. Also we're checking to ensure the node
- is a 'menu' here. */
-static gint
-parse_node_get_id (xmlNodePtr node)
+/* Handle the case where we change owners */
+static void
+menuproxy_name_changed_cb (GObject * object, GParamSpec * pspec, gpointer user_data)
{
- if (node->type != XML_ELEMENT_NODE) {
- return -1;
- }
- if (g_strcmp0((gchar *)node->name, "menu") != 0) {
- /* This kills some nodes early */
- g_warning("XML Node is not 'menu' it is '%s'", node->name);
- return -1;
- }
-
- xmlAttrPtr attrib;
- for (attrib = node->properties; attrib != NULL; attrib = attrib->next) {
- if (g_strcmp0((gchar *)attrib->name, "id") == 0) {
- if (attrib->children != NULL) {
- gint id = (guint)g_ascii_strtoll((gchar *)attrib->children->content, NULL, 10);
- /* g_debug ("Found ID: %d", id); */
- return id;
- }
- break;
- }
+ GDBusProxy * proxy = G_DBUS_PROXY(object);
+
+ gchar * owner = g_dbus_proxy_get_name_owner(proxy);
+
+ if (owner == NULL) {
+ /* Oh, no! We lost our owner! */
+ proxy_destroyed(G_OBJECT(proxy), user_data);
+ } else {
+ g_free(owner);
+ update_layout(DBUSMENU_CLIENT(user_data));
}
- g_warning("Unable to find an ID on the node");
- return -1;
+ return;
}
-/* A small helper that calls _property_set on each hash table
- entry in the properties hash. */
+/* Handle the signals out of the proxy */
static void
-get_properties_helper (gpointer key, gpointer value, gpointer data)
+menuproxy_signal_cb (GDBusProxy * proxy, gchar * sender, gchar * signal, GVariant * params, gpointer user_data)
{
- dbusmenu_menuitem_property_set_value((DbusmenuMenuitem *)data, (gchar *)key, (GValue *)value);
+ g_return_if_fail(DBUSMENU_IS_CLIENT(user_data));
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (g_strcmp0(signal, "LayoutUpdated") == 0) {
+ guint revision; gint parent;
+ g_variant_get(params, "(ui)", &revision, &parent);
+ layout_update(proxy, revision, parent, client);
+ } else if (priv->root == NULL) {
+ /* Drop out here, all the rest of these really need to have a root
+ node so we can just ignore them if there isn't one. */
+ } else if (g_strcmp0(signal, "ItemPropertiesUpdated") == 0) {
+ /* Remove before adding just incase there is a duplicate, against the
+ rules, but we can handle it so let's do it. */
+ GVariantIter ritems;
+ g_variant_iter_init(&ritems, g_variant_get_child_value(params, 1));
+
+ GVariant * ritem;
+ while ((ritem = g_variant_iter_next_value(&ritems)) != NULL) {
+ gint id = g_variant_get_int32(g_variant_get_child_value(ritem, 0));
+ DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
+
+ if (menuitem == NULL) {
+ continue;
+ }
+
+ GVariantIter properties;
+ g_variant_iter_init(&properties, g_variant_get_child_value(ritem, 1));
+ gchar * property;
+
+ while (g_variant_iter_next(&properties, "s", &property)) {
+ g_debug("Removing property '%s' on %d", property, id);
+ dbusmenu_menuitem_property_remove(menuitem, property);
+ }
+ }
+
+ GVariantIter items;
+ g_variant_iter_init(&items, g_variant_get_child_value(params, 0));
+
+ GVariant * item;
+ while ((item = g_variant_iter_next_value(&items)) != NULL) {
+ gint id = g_variant_get_int32(g_variant_get_child_value(item, 0));
+ GVariantIter properties;
+ g_variant_iter_init(&properties, g_variant_get_child_value(item, 1));
+ gchar * property;
+ GVariant * value;
+
+ while (g_variant_iter_next(&properties, "{sv}", &property, &value)) {
+ GVariant * internalvalue = value;
+ if (G_LIKELY(g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT))) {
+ /* Unboxing if needed */
+ internalvalue = g_variant_get_variant(value);
+ }
+ id_prop_update(proxy, id, property, internalvalue, client);
+ }
+ }
+ } else if (g_strcmp0(signal, "ItemPropertyUpdated") == 0) {
+ gint id; gchar * property; GVariant * value;
+ g_variant_get(params, "(isv)", &id, &property, &value);
+ id_prop_update(proxy, id, property, value, client);
+ } else if (g_strcmp0(signal, "ItemUpdated") == 0) {
+ gint id;
+ g_variant_get(params, "(i)", &id);
+ id_update(proxy, id, client);
+ } else if (g_strcmp0(signal, "ItemActivationRequested") == 0) {
+ gint id; guint timestamp;
+ g_variant_get(params, "(iu)", &id, &timestamp);
+ item_activated(proxy, id, timestamp, client);
+ } else {
+ g_warning("Received signal '%s' from menu proxy that is unknown", signal);
+ }
+
return;
}
@@ -573,17 +1105,32 @@ get_properties_helper (gpointer key, gpointer value, gpointer data)
This isn't the most efficient way. We can optimize this by
somehow removing the foreach. But that is for later. */
static void
-menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+menuitem_get_properties_cb (GVariant * properties, GError * error, gpointer data)
{
g_return_if_fail(DBUSMENU_IS_MENUITEM(data));
+ DbusmenuMenuitem * item = DBUSMENU_MENUITEM(data);
+
if (error != NULL) {
g_warning("Error getting properties on a menuitem: %s", error->message);
g_object_unref(data);
return;
}
- g_hash_table_foreach(properties, get_properties_helper, data);
- g_hash_table_destroy(properties);
+
+ GVariantIter * iter = g_variant_iter_new(properties);
+ gchar * key;
+ GVariant * value;
+
+ while (g_variant_iter_next(iter, "{sv}", &key, &value)) {
+ dbusmenu_menuitem_property_set_variant(item, key, value);
+
+ g_variant_unref(value);
+ g_free(key);
+ }
+
+ g_variant_iter_free(iter);
+
g_object_unref(data);
+
return;
}
@@ -591,7 +1138,7 @@ menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError
is getting recycled with the update, but we think might have prop
changes. */
static void
-menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+menuitem_get_properties_replace_cb (GVariant * properties, GError * error, gpointer data)
{
g_return_if_fail(DBUSMENU_IS_MENUITEM(data));
gboolean have_error = FALSE;
@@ -604,14 +1151,14 @@ menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties,
GList * current_props = NULL;
for (current_props = dbusmenu_menuitem_properties_list(DBUSMENU_MENUITEM(data));
- current_props != NULL ; current_props = g_list_next(current_props)) {
- if (have_error || g_hash_table_lookup(properties, current_props->data) == NULL) {
- dbusmenu_menuitem_property_remove(DBUSMENU_MENUITEM(data), (const gchar *)current_props->data);
- }
+ current_props != NULL && have_error == FALSE;
+ current_props = g_list_next(current_props)) {
+ dbusmenu_menuitem_property_remove(DBUSMENU_MENUITEM(data), (const gchar *)current_props->data);
}
+ g_list_free(current_props);
if (!have_error) {
- menuitem_get_properties_cb(proxy, properties, error, data);
+ menuitem_get_properties_cb(properties, error, data);
} else {
g_object_unref(data);
}
@@ -622,35 +1169,37 @@ menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties,
/* This is a different get properites call back that also sends
new signals. It basically is a small wrapper around the original. */
static void
-menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+menuitem_get_properties_new_cb (GVariant * properties, GError * error, gpointer data)
{
+ g_return_if_fail(data != NULL);
+ newItemPropData * propdata = (newItemPropData *)data;
+
if (error != NULL) {
g_warning("Error getting properties on a new menuitem: %s", error->message);
- g_object_unref(data);
+ g_object_unref(propdata->item);
return;
}
- g_return_if_fail(data != NULL);
- newItemPropData * propdata = (newItemPropData *)data;
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(propdata->client);
+ /* Extra ref as get_properties will unref once itself */
g_object_ref(propdata->item);
- menuitem_get_properties_cb (proxy, properties, error, propdata->item);
+ menuitem_get_properties_cb (properties, error, propdata->item);
gboolean handled = FALSE;
const gchar * type;
- DbusmenuClientTypeHandler newfunc = NULL;
+ type_handler_t * th = NULL;
type = dbusmenu_menuitem_property_get(propdata->item, DBUSMENU_MENUITEM_PROP_TYPE);
if (type != NULL) {
- newfunc = g_hash_table_lookup(priv->type_handlers, type);
+ th = (type_handler_t *)g_hash_table_lookup(priv->type_handlers, type);
} else {
- newfunc = g_hash_table_lookup(priv->type_handlers, DBUSMENU_CLIENT_TYPES_DEFAULT);
+ th = (type_handler_t *)g_hash_table_lookup(priv->type_handlers, DBUSMENU_CLIENT_TYPES_DEFAULT);
}
- if (newfunc != NULL) {
- handled = newfunc(propdata->item, propdata->parent, propdata->client);
+ if (th != NULL && th->cb != NULL) {
+ handled = th->cb(propdata->item, propdata->parent, propdata->client, th->user_data);
}
#ifdef MASSIVEDEBUGGING
@@ -671,10 +1220,30 @@ menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GEr
/* Respond to the call function to make sure that the other side
got it, or print a warning. */
static void
-menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
+menuitem_call_cb (GObject * proxy, GAsyncResult * res, gpointer userdata)
{
+ GError * error = NULL;
+ event_data_t * edata = (event_data_t *)userdata;
+ GVariant * params;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error);
+
if (error != NULL) {
- g_warning("Unable to call menu item %d: %s", GPOINTER_TO_INT(userdata), error->message);
+ g_warning("Unable to call event '%s' on menu item %d: %s", edata->event, dbusmenu_menuitem_get_id(edata->menuitem), error->message);
+ }
+
+ g_signal_emit(edata->client, signals[EVENT_RESULT], 0, edata->menuitem, edata->event, edata->variant, edata->timestamp, error, TRUE);
+
+ g_variant_unref(edata->variant);
+ g_free(edata->event);
+ g_object_unref(edata->menuitem);
+ g_free(edata);
+
+ if (G_UNLIKELY(error != NULL)) {
+ g_error_free(error);
+ }
+ if (G_LIKELY(params != NULL)) {
+ g_variant_unref(params);
}
return;
@@ -683,10 +1252,41 @@ menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
/* Sends the event over DBus to the server on the other side
of the bus. */
void
-dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name, const GValue * value, guint timestamp)
+dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name, GVariant * variant, guint timestamp)
{
+ g_return_if_fail(DBUSMENU_IS_CLIENT(client));
+ g_return_if_fail(id >= 0);
+ g_return_if_fail(name != NULL);
+
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- org_ayatana_dbusmenu_event_async (priv->menuproxy, id, name, value, timestamp, menuitem_call_cb, GINT_TO_POINTER(id));
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
+ if (mi == NULL) {
+ g_warning("Asked to activate a menuitem %d that we don't know about", id);
+ return;
+ }
+
+ if (variant == NULL) {
+ variant = g_variant_new_int32(0);
+ }
+
+ event_data_t * edata = g_new0(event_data_t, 1);
+ edata->client = client;
+ edata->menuitem = mi;
+ g_object_ref(edata->menuitem);
+ edata->event = g_strdup(name);
+ edata->timestamp = timestamp;
+ edata->variant = variant;
+ g_variant_ref_sink(variant);
+
+ g_dbus_proxy_call(priv->menuproxy,
+ "Event",
+ g_variant_new("(isvu)", id, name, variant, timestamp),
+ G_DBUS_CALL_FLAGS_NONE,
+ 1000, /* timeout */
+ NULL, /* cancellable */
+ menuitem_call_cb,
+ edata);
+
return;
}
@@ -700,14 +1300,24 @@ struct _about_to_show_t {
/* Reports errors and responds to update request that were a result
of sending the about to show signal. */
static void
-about_to_show_cb (DBusGProxy * proxy, gboolean need_update, GError * error, gpointer userdata)
+about_to_show_cb (GObject * proxy, GAsyncResult * res, gpointer userdata)
{
+ gboolean need_update = FALSE;
+ GError * error = NULL;
about_to_show_t * data = (about_to_show_t *)userdata;
+ GVariant * params = NULL;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error);
if (error != NULL) {
g_warning("Unable to send about_to_show: %s", error->message);
/* Note: we're just ensuring only the callback gets called */
need_update = FALSE;
+ g_error_free(error);
+ error = NULL;
+ } else {
+ g_variant_get(params, "(b)", &need_update);
+ g_variant_unref(params);
}
/* If we need to update, do that first. */
@@ -738,72 +1348,104 @@ dbusmenu_client_send_about_to_show(DbusmenuClient * client, gint id, void (*cb)(
data->cb_data = cb_data;
g_object_ref(client);
- org_ayatana_dbusmenu_about_to_show_async (priv->menuproxy, id, about_to_show_cb, data);
+ g_dbus_proxy_call(priv->menuproxy,
+ "AboutToShow",
+ g_variant_new("(i)", id),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ NULL, /* cancellable */
+ about_to_show_cb,
+ data);
+ return;
+}
+
+/* Builds a new child with property requests and everything
+ else to clean up the code a bit */
+static DbusmenuMenuitem *
+parse_layout_new_child (gint id, DbusmenuClient * client, DbusmenuMenuitem * parent)
+{
+ DbusmenuMenuitem * item = NULL;
+
+ /* Build a new item */
+ item = DBUSMENU_MENUITEM(dbusmenu_client_menuitem_new(id, client));
+ if (parent == NULL) {
+ dbusmenu_menuitem_set_root(item, TRUE);
+ }
+
+ /* Get the properties queued up for this item */
+ /* Not happy allocating about this, but I need these :( */
+ newItemPropData * propdata = g_new0(newItemPropData, 1);
+ if (propdata != NULL) {
+ propdata->client = client;
+ propdata->item = item;
+ propdata->parent = parent;
+
+ g_object_ref(item);
+ get_properties_globber(client, id, NULL, menuitem_get_properties_new_cb, propdata);
+ } else {
+ g_warning("Unable to allocate memory to get properties for menuitem. This menuitem will never be realized.");
+ }
+
+ return item;
+}
+
+/* Refresh the properties on this item */
+static void
+parse_layout_update (DbusmenuMenuitem * item, DbusmenuClient * client)
+{
+ g_object_ref(item);
+ get_properties_globber(client, dbusmenu_menuitem_get_id(item), NULL, menuitem_get_properties_replace_cb, item);
return;
}
/* Parse recursively through the XML and make it into
objects as need be */
static DbusmenuMenuitem *
-parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy)
+parse_layout_xml(DbusmenuClient * client, GVariant * layout, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, GDBusProxy * proxy)
{
- gint id = parse_node_get_id(node);
+ if (layout == NULL) {
+ return NULL;
+ }
+
+ /* First verify and figure out what we've got */
+ gint id = g_variant_get_int32(g_variant_get_child_value(layout, 0));
if (id < 0) {
return NULL;
}
#ifdef MASSIVEDEBUGGING
g_debug("Client looking at node with id: %d", id);
#endif
- /* If we don't have any item, or the IDs don't match */
- if (item == NULL || dbusmenu_menuitem_get_id(item) != id) {
- if (item != NULL) {
- if (parent != NULL) {
- dbusmenu_menuitem_child_delete(parent, item);
- }
- item = NULL;
- }
- /* Build a new item */
- item = DBUSMENU_MENUITEM(dbusmenu_client_menuitem_new(id, client));
- if (parent == NULL) {
- dbusmenu_menuitem_set_root(item, TRUE);
- }
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(id == dbusmenu_menuitem_get_id(item), NULL);
- /* Get the properties queued up for this item */
- /* Not happy about this, but I need these :( */
- newItemPropData * propdata = g_new0(newItemPropData, 1);
- if (propdata != NULL) {
- propdata->client = client;
- propdata->item = item;
- propdata->parent = parent;
-
- gchar * properties[1] = {NULL}; /* This gets them all */
- g_object_ref(item);
- org_ayatana_dbusmenu_get_properties_async(proxy, id, (const gchar **)properties, menuitem_get_properties_new_cb, propdata);
- } else {
- g_warning("Unable to allocate memory to get properties for menuitem. This menuitem will never be realized.");
- }
- } else {
- /* Refresh the properties */
- /* XXX: We shouldn't need to get the properties everytime we reuse an entry */
- gchar * properties[1] = {NULL}; /* This gets them all */
- g_object_ref(item);
- org_ayatana_dbusmenu_get_properties_async(proxy, id, (const gchar **)properties, menuitem_get_properties_replace_cb, item);
- }
+ /* Some variables */
+ GVariantIter children;
+ g_variant_iter_init(&children, g_variant_get_child_value(layout, 2));
+ GVariant * child;
- xmlNodePtr children;
- guint position;
+ guint position = 0;
GList * oldchildren = g_list_copy(dbusmenu_menuitem_get_children(item));
/* g_debug("Starting old children: %d", g_list_length(oldchildren)); */
- for (children = node->children, position = 0; children != NULL; children = children->next, position++) {
+ /* Go through all the XML Nodes and make sure that we have menuitems
+ to cover those XML nodes. */
+ while ((child = g_variant_iter_next_value(&children)) != NULL) {
/* g_debug("Looking at child: %d", position); */
- gint childid = parse_node_get_id(children);
+ if (g_variant_is_of_type(child, G_VARIANT_TYPE_VARIANT)) {
+ child = g_variant_get_variant(child);
+ }
+
+ gint childid = g_variant_get_int32(g_variant_get_child_value(child, 0));
if (childid < 0) {
+ /* Don't increment the position when there isn't a valid
+ node in the XML tree. It's probably a comment. */
continue;
}
DbusmenuMenuitem * childmi = NULL;
+ /* First see if we can recycle a node that we've already built
+ on this menu item */
GList * childsearch = NULL;
for (childsearch = oldchildren; childsearch != NULL; childsearch = g_list_next(childsearch)) {
DbusmenuMenuitem * cs_mi = DBUSMENU_MENUITEM(childsearch->data);
@@ -814,20 +1456,28 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it
}
}
- DbusmenuMenuitem * newchildmi = parse_layout_xml(client, children, childmi, item, proxy);
-
- if (newchildmi != childmi) {
- if (childmi != NULL) {
- dbusmenu_menuitem_child_delete(item, childmi);
- }
- dbusmenu_menuitem_child_add_position(item, newchildmi, position);
- g_object_unref(newchildmi);
+ if (childmi == NULL) {
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Building new menu item %d at position %d", childid, position);
+ #endif
+ /* If we can't recycle, then we build a new one */
+ childmi = parse_layout_new_child(childid, client, item);
+ dbusmenu_menuitem_child_add_position(item, childmi, position);
+ g_object_unref(childmi);
} else {
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Recycling menu item %d at position %d", childid, position);
+ #endif
+ /* If we can recycle, make sure it's in the right place */
dbusmenu_menuitem_child_reorder(item, childmi, position);
+ parse_layout_update(childmi, client);
}
+
+ position++;
}
- /* g_debug("Stopping old children: %d", g_list_length(oldchildren)); */
+ /* Remove any children that are no longer used by this version of
+ the layout. */
GList * oldchildleft = NULL;
for (oldchildleft = oldchildren; oldchildleft != NULL; oldchildleft = g_list_next(oldchildleft)) {
DbusmenuMenuitem * oldmi = DBUSMENU_MENUITEM(oldchildleft->data);
@@ -838,13 +1488,56 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it
}
g_list_free(oldchildren);
+ /* We've got everything built up at this node and reconcilled */
+
+ /* Flush the properties requests if this is the first level */
+ if (parent != NULL && dbusmenu_menuitem_get_id(parent) == 0) {
+ get_properties_flush(client);
+ }
+
+ /* now it's time to recurse down the tree. */
+ g_variant_iter_init(&children, g_variant_get_child_value(layout, 2));
+
+ child = g_variant_iter_next_value(&children);
+ GList * childmis = dbusmenu_menuitem_get_children(item);
+ while (child != NULL && childmis != NULL) {
+ if (g_variant_is_of_type(child, G_VARIANT_TYPE_VARIANT)) {
+ child = g_variant_get_variant(child);
+ }
+
+ gint xmlid = g_variant_get_int32(g_variant_get_child_value(child, 0));
+ /* If this isn't a valid menu item we need to move on
+ until we have one. This avoids things like comments. */
+ if (xmlid < 0) {
+ child = g_variant_iter_next_value(&children);
+ continue;
+ }
+
+ #ifdef MASSIVEDEBUGGING
+ gint miid = dbusmenu_menuitem_get_id(DBUSMENU_MENUITEM(childmis->data));
+ g_debug("Recursing parse_layout_xml. XML ID: %d MI ID: %d", xmlid, miid);
+ #endif
+
+ parse_layout_xml(client, child, DBUSMENU_MENUITEM(childmis->data), item, proxy);
+
+ child = g_variant_iter_next_value(&children);
+ childmis = g_list_next(childmis);
+ }
+
+ if (child != NULL) {
+ g_warning("Sync failed, now we've got extra layout nodes.");
+ }
+ if (childmis != NULL) {
+ g_warning("Sync failed, now we've got extra menu items.");
+ }
+
return item;
}
/* Take the layout passed to us over DBus and turn it into
a set of beautiful objects */
static gint
-parse_layout (DbusmenuClient * client, const gchar * layout)
+parse_layout (DbusmenuClient * client, GVariant * layout)
{
#ifdef MASSIVEDEBUGGING
g_debug("Client Parsing a new layout");
@@ -852,19 +1545,18 @@ parse_layout (DbusmenuClient * client, const gchar * layout)
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- xmlDocPtr xmldoc;
-
- xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 16*1024), "dbusmenu.xml", NULL, 0);
-
- xmlNodePtr root = xmlDocGetRootElement(xmldoc);
-
DbusmenuMenuitem * oldroot = priv->root;
- priv->root = parse_layout_xml(client, root, priv->root, NULL, priv->menuproxy);
- xmlFreeDoc(xmldoc);
+ if (priv->root == NULL) {
+ priv->root = parse_layout_new_child(0, client, NULL);
+ } else {
+ parse_layout_update(priv->root, client);
+ }
+
+ priv->root = parse_layout_xml(client, layout, priv->root, NULL, priv->menuproxy);
if (priv->root == NULL) {
- g_warning("Unable to parse layout on client %s object %s: %s", priv->dbus_name, priv->dbus_object, layout);
+ g_warning("Unable to parse layout on client %s object %s: %s", priv->dbus_name, priv->dbus_object, g_variant_print(layout, TRUE));
}
if (priv->root != oldroot) {
@@ -889,24 +1581,34 @@ parse_layout (DbusmenuClient * client, const gchar * layout)
/* When the layout property returns, here's where we take care of that. */
static void
-update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, void * data)
+update_layout_cb (GObject * proxy, GAsyncResult * res, gpointer data)
{
DbusmenuClient * client = DBUSMENU_CLIENT(data);
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ GError * error = NULL;
+ GVariant * params = NULL;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error);
+
if (error != NULL) {
- g_warning("Getting layout failed on client %s object %s: %s", priv->dbus_name, priv->dbus_object, error->message);
- return;
+ g_warning("Getting layout failed: %s", error->message);
+ g_error_free(error);
+ goto out;
}
- if (!parse_layout(client, xml)) {
+ guint rev = g_variant_get_uint32(g_variant_get_child_value(params, 0));
+ GVariant * layout = g_variant_get_child_value(params, 1);
+
+ guint parseable = parse_layout(client, layout);
+
+ if (parseable == 0) {
g_warning("Unable to parse layout!");
- return;
+ goto out;
}
priv->my_revision = rev;
/* g_debug("Root is now: 0x%X", (unsigned int)priv->root); */
- priv->layoutcall = NULL;
#ifdef MASSIVEDEBUGGING
g_debug("Client signaling layout has changed.");
#endif
@@ -918,6 +1620,17 @@ update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, vo
update_layout(client);
}
+out:
+ if (priv->layoutcall != NULL) {
+ g_object_unref(priv->layoutcall);
+ priv->layoutcall = NULL;
+ }
+
+ if (params != NULL) {
+ g_variant_unref(params);
+ }
+
+ g_object_unref(G_OBJECT(client));
return;
}
@@ -932,31 +1645,54 @@ update_layout (DbusmenuClient * client)
return;
}
+ gchar * name_owner = g_dbus_proxy_get_name_owner(priv->menuproxy);
+ if (name_owner == NULL) {
+ return;
+ }
+ g_free(name_owner);
+
if (priv->layoutcall != NULL) {
return;
}
- priv->layoutcall = org_ayatana_dbusmenu_get_layout_async(priv->menuproxy,
- 0, /* Parent is the root */
- update_layout_cb,
- client);
+ priv->layoutcall = g_cancellable_new();
+
+ GVariantBuilder tupleb;
+ g_variant_builder_init(&tupleb, G_VARIANT_TYPE_TUPLE);
+
+ g_variant_builder_add_value(&tupleb, g_variant_new_int32(0)); // root
+ g_variant_builder_add_value(&tupleb, g_variant_new_int32(-1)); // recurse
+ g_variant_builder_add_value(&tupleb, g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0)); // props
+
+ GVariant * args = g_variant_builder_end(&tupleb);
+ // g_debug("Args (type: %s): %s", g_variant_get_type_string(args), g_variant_print(args, TRUE));
+
+ g_object_ref(G_OBJECT(client));
+ g_dbus_proxy_call(priv->menuproxy,
+ "GetLayout",
+ args,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ priv->layoutcall, /* cancellable */
+ update_layout_cb,
+ client);
return;
}
/* Public API */
/**
- dbusmenu_client_new:
- @name: The DBus name for the server to connect to
- @object: The object on the server to monitor
-
- This function creates a new client that connects to a specific
- server on DBus. That server is at a specific location sharing
- a known object. The interface is assumed by the code to be
- the DBus menu interface. The newly created client will start
- sending out events as it syncs up with the server.
-
- Return value: A brand new #DbusmenuClient
+ * dbusmenu_client_new:
+ * @name: The DBus name for the server to connect to
+ * @object: The object on the server to monitor
+ *
+ * This function creates a new client that connects to a specific
+ * server on DBus. That server is at a specific location sharing
+ * a known object. The interface is assumed by the code to be
+ * the DBus menu interface. The newly created client will start
+ * sending out events as it syncs up with the server.
+ *
+ * Return value: A brand new #DbusmenuClient
*/
DbusmenuClient *
dbusmenu_client_new (const gchar * name, const gchar * object)
@@ -970,21 +1706,21 @@ dbusmenu_client_new (const gchar * name, const gchar * object)
}
/**
- dbusmenu_client_get_root:
- @client: The #DbusmenuClient to get the root node from
-
- Grabs the root node for the specified client @client. This
- function may block. It will block if there is currently a
- call to update the layout, it will block on that layout
- updated and then return the newly updated layout. Chances
- are that this update is in the queue for the mainloop as
- it would have been requested some time ago, but in theory
- it could block longer.
-
- Return value: A #DbusmenuMenuitem representing the root of
- menu on the server. If there is no server or there is
- an error receiving its layout it'll return #NULL.
-*/
+ * dbusmenu_client_get_root:
+ * @client: The #DbusmenuClient to get the root node from
+ *
+ * Grabs the root node for the specified client @client. This
+ * function may block. It will block if there is currently a
+ * call to update the layout, it will block on that layout
+ * updated and then return the newly updated layout. Chances
+ * are that this update is in the queue for the mainloop as
+ * it would have been requested some time ago, but in theory
+ * it could block longer.
+ *
+ * Return value: (transfer none): A #DbusmenuMenuitem representing the root of
+ * menu on the server. If there is no server or there is
+ * an error receiving its layout it'll return #NULL.
+ */
DbusmenuMenuitem *
dbusmenu_client_get_root (DbusmenuClient * client)
{
@@ -992,10 +1728,6 @@ dbusmenu_client_get_root (DbusmenuClient * client)
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- if (priv->propproxy == NULL) {
- return NULL;
- }
-
#ifdef MASSIVEDEBUGGING
g_debug("Client get root: %X", (guint)priv->root);
#endif
@@ -1003,30 +1735,74 @@ dbusmenu_client_get_root (DbusmenuClient * client)
return priv->root;
}
+/* Remove the type handler when we're all done with it */
+static void
+type_handler_destroy (gpointer user_data)
+{
+ type_handler_t * th = (type_handler_t *)user_data;
+ if (th->destroy_cb != NULL) {
+ th->destroy_cb(th->client, th->type, th->user_data);
+ }
+ g_free(th->type);
+ g_free(th);
+ return;
+}
+
/**
- dbusmenu_client_add_type_handler:
- @client: Client where we're getting types coming in
- @type: A text string that will be matched with the 'type'
- property on incoming menu items
- @newfunc: The function that will be executed with those new
- items when they come in.
-
- This function connects into the type handling of the #DbusmenuClient.
- Every new menuitem that comes in immediately gets asked for it's
- properties. When we get those properties we check the 'type'
- property and look to see if it matches a handler that is known
- by the client. If so, the @newfunc function is executed on that
- #DbusmenuMenuitem. If not, then the DbusmenuClient::new-menuitem
- signal is sent.
-
- In the future the known types will be sent to the server so that it
- can make choices about the menu item types availble.
-
- Return value: If registering the new type was successful.
+ * dbusmenu_client_add_type_handler:
+ * @client: Client where we're getting types coming in
+ * @type: A text string that will be matched with the 'type'
+ * property on incoming menu items
+ * @newfunc: The function that will be executed with those new
+ * items when they come in.
+ *
+ * This function connects into the type handling of the #DbusmenuClient.
+ * Every new menuitem that comes in immediately gets asked for it's
+ * properties. When we get those properties we check the 'type'
+ * property and look to see if it matches a handler that is known
+ * by the client. If so, the @newfunc function is executed on that
+ * #DbusmenuMenuitem. If not, then the DbusmenuClient::new-menuitem
+ * signal is sent.
+ *
+ * In the future the known types will be sent to the server so that it
+ * can make choices about the menu item types availble.
+ *
+ * Return value: If registering the new type was successful.
*/
gboolean
dbusmenu_client_add_type_handler (DbusmenuClient * client, const gchar * type, DbusmenuClientTypeHandler newfunc)
{
+ return dbusmenu_client_add_type_handler_full(client, type, newfunc, NULL, NULL);
+}
+
+/**
+ * dbusmenu_client_add_type_handler_full:
+ * @client: Client where we're getting types coming in
+ * @type: A text string that will be matched with the 'type'
+ * property on incoming menu items
+ * @newfunc: The function that will be executed with those new
+ * items when they come in.
+ * @user_data: Data passed to @newfunc when it is called
+ * @destroy_func: A function that is called when the type handler is
+ * removed (usually on client destruction) which will free
+ * the resources in @user_data.
+ *
+ * This function connects into the type handling of the #DbusmenuClient.
+ * Every new menuitem that comes in immediately gets asked for it's
+ * properties. When we get those properties we check the 'type'
+ * property and look to see if it matches a handler that is known
+ * by the client. If so, the @newfunc function is executed on that
+ * #DbusmenuMenuitem. If not, then the DbusmenuClient::new-menuitem
+ * signal is sent.
+ *
+ * In the future the known types will be sent to the server so that it
+ * can make choices about the menu item types availble.
+ *
+ * Return value: If registering the new type was successful.
+*/
+gboolean
+dbusmenu_client_add_type_handler_full (DbusmenuClient * client, const gchar * type, DbusmenuClientTypeHandler newfunc, gpointer user_data, DbusmenuClientTypeDestroyHandler destroy_func)
+{
g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), FALSE);
g_return_val_if_fail(type != NULL, FALSE);
@@ -1047,6 +1823,14 @@ dbusmenu_client_add_type_handler (DbusmenuClient * client, const gchar * type, D
return FALSE;
}
- g_hash_table_insert(priv->type_handlers, g_strdup(type), newfunc);
+ type_handler_t * th = g_new0(type_handler_t, 1);
+ th->client = client;
+ th->cb = newfunc;
+ th->destroy_cb = destroy_func;
+ th->user_data = user_data;
+ th->type = g_strdup(type);
+
+ g_hash_table_insert(priv->type_handlers, g_strdup(type), th);
return TRUE;
}
+