aboutsummaryrefslogtreecommitdiff
path: root/libdbusmenu-glib/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdbusmenu-glib/client.c')
-rw-r--r--libdbusmenu-glib/client.c583
1 files changed, 528 insertions, 55 deletions
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
index fa233a4..dae1dd7 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -41,6 +41,11 @@ License version 3 and version 2.1 along with this program. If not, see
#include "client-menuitem.h"
#include "dbusmenu-client.h"
#include "server-marshal.h"
+#include "client-marshal.h"
+
+/* How many property requests should we queue before
+ sending the message on dbus */
+#define MAX_PROPERTIES_TO_QUEUE 100
/* Properties */
enum {
@@ -54,12 +59,13 @@ enum {
LAYOUT_UPDATED,
ROOT_CHANGED,
NEW_MENUITEM,
+ ITEM_ACTIVATE,
+ EVENT_RESULT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
-typedef struct _DbusmenuClientPrivate DbusmenuClientPrivate;
struct _DbusmenuClientPrivate
{
DbusmenuMenuitem * root;
@@ -78,6 +84,10 @@ struct _DbusmenuClientPrivate
DBusGProxy * dbusproxy;
GHashTable * type_handlers;
+
+ GArray * delayed_property_list;
+ GArray * delayed_property_listeners;
+ gint delayed_idle;
};
typedef struct _newItemPropData newItemPropData;
@@ -88,8 +98,25 @@ 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;
+ org_ayatana_dbusmenu_get_properties_reply callback;
+ gpointer user_data;
+ gboolean replied;
+};
+
+typedef struct _event_data_t event_data_t;
+struct _event_data_t {
+ DbusmenuClient * client;
+ DbusmenuMenuitem * menuitem;
+ gchar * event;
+ GValue data;
+ guint timestamp;
+};
+
+
+#define DBUSMENU_CLIENT_GET_PRIVATE(o) (DBUSMENU_CLIENT(o)->priv)
/* GObject Stuff */
static void dbusmenu_client_class_init (DbusmenuClientClass *klass);
@@ -109,6 +136,9 @@ 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 void update_layout (DbusmenuClient * client);
static void menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data);
+static void get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, org_ayatana_dbusmenu_get_properties_reply callback, gpointer user_data);
+static GQuark error_domain (void);
+static void item_activated (DBusGProxy * proxy, gint id, guint timestamp, DbusmenuClient * client);
/* Build a type */
G_DEFINE_TYPE (DbusmenuClient, dbusmenu_client, G_TYPE_OBJECT);
@@ -173,6 +203,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_POINTER_UINT_POINTER,
+ G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, G_TYPE_POINTER, 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",
@@ -191,6 +256,8 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass)
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;
@@ -211,6 +278,10 @@ dbusmenu_client_init (DbusmenuClient *self)
priv->type_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
+ 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;
}
@@ -219,6 +290,44 @@ 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(priv->menuproxy, 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);
priv->layoutcall = NULL;
@@ -310,6 +419,253 @@ 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 (DBusGProxy *proxy, GPtrArray *OUT_properties, GError *error, gpointer userdata)
+{
+ GArray * listeners = (GArray *)userdata;
+ int i;
+
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Get properties callback: %d", OUT_properties->len);
+ #endif
+
+ 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(proxy, NULL, error, listener->user_data);
+ }
+ g_array_free(listeners, TRUE);
+ return;
+ }
+
+ /* Callback all the folks we can find */
+ for (i = 0; i < OUT_properties->len; i++) {
+ GValueArray * varray = (GValueArray *)g_ptr_array_index(OUT_properties, i);
+
+ if (varray->n_values != 2) {
+ g_warning("Value Array is %d entries long but we expected 2.", varray->n_values);
+ continue;
+ }
+
+ GValue * vid = g_value_array_get_nth(varray, 0);
+ GValue * vproperties = g_value_array_get_nth(varray, 1);
+
+ if (G_VALUE_TYPE(vid) != G_TYPE_INT) {
+ g_warning("ID Entry not holding an int: %s", G_VALUE_TYPE_NAME(vid));
+ }
+ if (G_VALUE_TYPE(vproperties) != dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) {
+ g_warning("Properties Entry not holding an a{sv}: %s", G_VALUE_TYPE_NAME(vproperties));
+ }
+
+ gint id = g_value_get_int(vid);
+ GHashTable * properties = g_value_get_boxed(vproperties);
+
+ 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(proxy, properties, NULL, listener->user_data);
+ listener->replied = TRUE;
+ } else {
+ g_warning("Odd, we've already replied to the listener on ID %d", id);
+ }
+ }
+
+ /* 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) {
+ if (localerror == NULL) {
+ g_set_error_literal(&localerror, error_domain(), 0, "Error getting properties for ID");
+ }
+ listener->callback(proxy, NULL, localerror, listener->user_data);
+ }
+ }
+ if (localerror != NULL) {
+ g_error_free(localerror);
+ }
+
+ /* Clean up */
+ g_array_free(listeners, TRUE);
+
+ 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)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(user_data);
+ //org_ayatana_dbusmenu_get_properties_async(priv->menuproxy, id, properties, callback, user_data);
+
+ if (priv->delayed_property_listeners->len == 0) {
+ g_warning("Odd, idle func got no listeners.");
+ return FALSE;
+ }
+
+ /* Build up an ID list to pass */
+ GArray * idlist = g_array_new(FALSE, FALSE, sizeof(gint));
+ gint i;
+ for (i = 0; i < priv->delayed_property_listeners->len; i++) {
+ g_array_append_val(idlist, g_array_index(priv->delayed_property_listeners, properties_listener_t, i).id);
+ }
+
+ org_ayatana_dbusmenu_get_group_properties_async(priv->menuproxy, idlist, (const gchar **)priv->delayed_property_list->data, get_properties_callback, priv->delayed_property_listeners);
+
+ /* Free ID List */
+ g_array_free(idlist, TRUE);
+
+ /* 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);
+
+ dbus_g_connection_flush(priv->session_bus);
+
+ 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, org_ayatana_dbusmenu_get_properties_reply 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(priv->menuproxy, 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 (DBusGProxy * 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)
@@ -367,10 +723,9 @@ 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;
}
@@ -550,6 +905,10 @@ build_proxies (DbusmenuClient * client)
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);
+ dbus_g_object_register_marshaller(_dbusmenu_server_marshal_VOID__INT_UINT, G_TYPE_NONE, G_TYPE_INT, G_TYPE_UINT, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(priv->menuproxy, "ItemActivationRequested", G_TYPE_INT, G_TYPE_UINT, G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal(priv->menuproxy, "ItemActivationRequested", G_CALLBACK(item_activated), client, NULL);
+
update_layout(client);
return;
@@ -561,6 +920,9 @@ build_proxies (DbusmenuClient * client)
static gint
parse_node_get_id (xmlNodePtr node)
{
+ if (node == NULL) {
+ return -1;
+ }
if (node->type != XML_ELEMENT_NODE) {
return -1;
}
@@ -652,16 +1014,19 @@ menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties,
static void
menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * 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);
+ g_free(data);
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);
@@ -701,10 +1066,19 @@ menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GEr
static void
menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
{
+ event_data_t * edata = (event_data_t *)userdata;
+
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->data, edata->timestamp, error, TRUE);
+
+ g_value_unset(&edata->data);
+ g_free(edata->event);
+ g_object_unref(edata->menuitem);
+ g_free(edata);
+
return;
}
@@ -713,8 +1087,39 @@ menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
void
dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name, const GValue * value, 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 (value == NULL) {
+ GValue internalval = {0};
+ g_value_init(&internalval, G_TYPE_INT);
+ g_value_set_int(&internalval, 0);
+ value = &internalval;
+ }
+
+ 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);
+ g_value_init(&edata->data, G_VALUE_TYPE(value));
+ g_value_copy(value, &edata->data);
+ edata->timestamp = timestamp;
+
+ DBusGAsyncData *stuff;
+ stuff = g_slice_new (DBusGAsyncData);
+ stuff->cb = G_CALLBACK (menuitem_call_cb);
+ stuff->userdata = edata;
+ dbus_g_proxy_begin_call_with_timeout (priv->menuproxy, "Event", org_ayatana_dbusmenu_event_async_callback, stuff, _dbus_glib_async_data_free, 1000, G_TYPE_INT, id, G_TYPE_STRING, name, G_TYPE_VALUE, value, G_TYPE_UINT, timestamp, G_TYPE_INVALID);
+
return;
}
@@ -770,11 +1175,51 @@ dbusmenu_client_send_about_to_show(DbusmenuClient * client, gint id, void (*cb)(
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)
{
+ /* First verify and figure out what we've got */
gint id = parse_node_get_id(node);
if (id < 0) {
return NULL;
@@ -782,56 +1227,31 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it
#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);
- }
-
- /* 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);
- }
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(id == dbusmenu_menuitem_get_id(item), NULL);
+ /* Some variables */
xmlNodePtr children;
guint position;
GList * oldchildren = g_list_copy(dbusmenu_menuitem_get_children(item));
/* g_debug("Starting old children: %d", g_list_length(oldchildren)); */
+ /* Go through all the XML Nodes and make sure that we have menuitems
+ to cover those XML nodes. */
for (children = node->children, position = 0; children != NULL; children = children->next, position++) {
/* g_debug("Looking at child: %d", position); */
gint childid = parse_node_get_id(children);
if (childid < 0) {
+ /* Don't increment the position when there isn't a valid
+ node in the XML tree. It's probably a comment. */
+ position--;
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);
@@ -842,20 +1262,26 @@ 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);
}
}
- /* 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);
@@ -866,6 +1292,42 @@ 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 (dbusmenu_menuitem_get_id(parent) == 0) {
+ get_properties_flush(client);
+ }
+
+ /* now it's time to recurse down the tree. */
+ children = node->children;
+ GList * childmis = dbusmenu_menuitem_get_children(item);
+ while (children != NULL && childmis != NULL) {
+ gint xmlid = parse_node_get_id(children);
+ /* 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) {
+ children = children->next;
+ 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, children, DBUSMENU_MENUITEM(childmis->data), item, proxy);
+
+ children = children->next;
+ childmis = g_list_next(childmis);
+ }
+ if (children != NULL) {
+ g_warning("Sync failed, now we've got extra XML nodes.");
+ }
+ if (childmis != NULL) {
+ g_warning("Sync failed, now we've got extra menu items.");
+ }
+
return item;
}
@@ -882,12 +1344,23 @@ parse_layout (DbusmenuClient * client, const gchar * layout)
xmlDocPtr xmldoc;
- xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 16*1024), "dbusmenu.xml", NULL, 0);
+ /* No one should need more characters than this! */
+ xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 1024*1024), "dbusmenu.xml", NULL, 0);
xmlNodePtr root = xmlDocGetRootElement(xmldoc);
+ if (root == NULL) {
+ g_warning("Unable to get root node of menu XML");
+ }
+
DbusmenuMenuitem * oldroot = priv->root;
+ 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, root, priv->root, NULL, priv->menuproxy);
xmlFreeDoc(xmldoc);