aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore5
-rw-r--r--libdbusmenu-glib/Makefile.am3
-rw-r--r--libdbusmenu-glib/client.c131
-rw-r--r--libdbusmenu-glib/menuitem-proxy.c362
-rw-r--r--libdbusmenu-glib/menuitem-proxy.h74
-rw-r--r--libdbusmenu-glib/server.c19
-rw-r--r--tests/Makefile.am54
-rw-r--r--tests/test-glib-proxy-client.c171
-rw-r--r--tests/test-glib-proxy-proxy.c80
-rw-r--r--tests/test-glib-proxy-server.c126
-rw-r--r--tests/test-glib-proxy.h142
11 files changed, 1166 insertions, 1 deletions
diff --git a/.bzrignore b/.bzrignore
index 0066799..c9483e7 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -59,3 +59,8 @@ tests/test-glib-objects-test
tests/test-glib-objects.xml
tools/testapp/dbusmenu-testapp
libdbusmenu-glib/libdbusmenu_glib_la-client-menuitem.lo
+libdbusmenu-glib/libdbusmenu_glib_la-menuitem-proxy.lo
+tests/test-glib-proxy-client
+tests/test-glib-proxy-server
+tests/test-glib-proxy-proxy
+tests/test-glib-proxy
diff --git a/libdbusmenu-glib/Makefile.am b/libdbusmenu-glib/Makefile.am
index 998af50..65ebf4c 100644
--- a/libdbusmenu-glib/Makefile.am
+++ b/libdbusmenu-glib/Makefile.am
@@ -12,6 +12,7 @@ libdbusmenu_glibincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-glib/
libdbusmenu_glibinclude_HEADERS = \
menuitem.h \
+ menuitem-proxy.h \
server.h \
client.h
@@ -23,6 +24,8 @@ libdbusmenu_glib_la_SOURCES = \
menuitem-marshal.h \
menuitem-marshal.c \
menuitem-private.h \
+ menuitem-proxy.h \
+ menuitem-proxy.c \
server.h \
server.c \
server-marshal.h \
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
index 071654e..4735794 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -75,6 +75,8 @@ struct _DbusmenuClientPrivate
DBusGProxy * dbusproxy;
GHashTable * type_handlers;
+
+ GArray * delayed_properties;
};
typedef struct _newItemPropData newItemPropData;
@@ -85,6 +87,21 @@ struct _newItemPropData
DbusmenuMenuitem * parent;
};
+typedef struct _propertyDelay propertyDelay;
+struct _propertyDelay
+{
+ guint revision;
+ GArray * entries;
+};
+
+typedef struct _propertyDelayValue propertyDelayValue;
+struct _propertyDelayValue
+{
+ gint id;
+ gchar * name;
+ GValue value;
+};
+
#define DBUSMENU_CLIENT_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate))
@@ -208,6 +225,8 @@ dbusmenu_client_init (DbusmenuClient *self)
priv->type_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
+ priv->delayed_properties = g_array_new(FALSE, TRUE, sizeof(propertyDelay));
+
return;
}
@@ -255,6 +274,23 @@ dbusmenu_client_finalize (GObject *object)
g_hash_table_destroy(priv->type_handlers);
}
+ if (priv->delayed_properties) {
+ gint i;
+ for (i = 0; i < priv->delayed_properties->len; i++) {
+ propertyDelay * delay = &g_array_index(priv->delayed_properties, propertyDelay, i);
+ gint j;
+ for (j = 0; j < delay->entries->len; j++) {
+ propertyDelayValue * value = &g_array_index(delay->entries, propertyDelayValue, j);
+ g_free(value->name);
+ g_value_unset(&value->value);
+ }
+ g_array_free(delay->entries, TRUE);
+ delay->entries = NULL;
+ }
+ g_array_free(priv->delayed_properties, TRUE);
+ priv->delayed_properties = NULL;
+ }
+
G_OBJECT_CLASS (dbusmenu_client_parent_class)->finalize (object);
return;
}
@@ -319,6 +355,49 @@ layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient *
return;
}
+/* Add an entry to the set of entries that are delayed until the
+ layout has been updated to this revision */
+static void
+delay_prop_update (guint revision, GArray * delayarray, gint id, gchar * prop, GValue * value)
+{
+ propertyDelay * delay = NULL;
+ gint i;
+
+ /* First look for something with this revision number. This
+ array should be really short, probably not more than an entry or
+ two so there is no reason to optimize this. */
+ for (i = 0; i < delayarray->len; i++) {
+ propertyDelay * localdelay = &g_array_index(delayarray, propertyDelay, i);
+ if (localdelay->revision == revision) {
+ delay = localdelay;
+ break;
+ }
+ }
+
+ /* If we don't have any entires for this revision number then we
+ need to create a new one with it's own array of entires. */
+ if (delay == NULL) {
+ propertyDelay localdelay = {0};
+ localdelay.revision = revision;
+ localdelay.entries = g_array_new(FALSE, TRUE, sizeof(propertyDelayValue));
+
+ g_array_append_val(delayarray, localdelay);
+ delay = &g_array_index(delayarray, propertyDelay, delayarray->len - 1);
+ }
+
+ /* Build the actual entry and tack it on the end of the array
+ of entries */
+ propertyDelayValue delayvalue = {0};
+ delayvalue.id = id;
+ delayvalue.name = g_strdup(prop);
+
+ g_value_init(&delayvalue.value, G_VALUE_TYPE(value));
+ g_value_copy(value, &delayvalue.value);
+
+ g_array_append_val(delay->entries, delayvalue);
+ return;
+}
+
/* Signal from the server that a property has changed
on one of our menuitems */
static void
@@ -333,7 +412,16 @@ id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, D
#endif
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- g_return_if_fail(priv->root != NULL);
+
+ /* If we're not on the right revision, we need to cache the property
+ changes as it could be that the menuitems don't exist yet. */
+ if (priv->root == NULL || priv->my_revision != priv->current_revision) {
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Delaying prop update until rev %d for id %d property %s", priv->current_revision, id, property);
+ #endif
+ delay_prop_update(priv->current_revision, priv->delayed_properties, id, property, value);
+ return;
+ }
DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
g_return_if_fail(menuitem != NULL);
@@ -810,16 +898,19 @@ update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, vo
DbusmenuClient * client = DBUSMENU_CLIENT(data);
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ /* Check to make sure this isn't an issue */
if (error != NULL) {
g_warning("Getting layout failed on client %s object %s: %s", priv->dbus_name, priv->dbus_object, error->message);
return;
}
+ /* Try to take in the layout that we got */
if (!parse_layout(client, xml)) {
g_warning("Unable to parse layout!");
return;
}
+ /* Success, so we need to update our local variables */
priv->my_revision = rev;
/* g_debug("Root is now: 0x%X", (unsigned int)priv->root); */
priv->layoutcall = NULL;
@@ -828,6 +919,44 @@ update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, vo
#endif
g_signal_emit(G_OBJECT(client), signals[LAYOUT_UPDATED], 0, TRUE);
+ /* Apply the delayed properties that were queued up while
+ we were waiting on this layout update. */
+ if (G_LIKELY(priv->delayed_properties != NULL)) {
+ gint i;
+ for (i = 0; i < priv->delayed_properties->len; i++) {
+ propertyDelay * delay = &g_array_index(priv->delayed_properties, propertyDelay, i);
+ if (delay->revision > priv->my_revision) {
+ /* Check to see if this is for future revisions, which
+ is possible if there is a ton of updates. */
+ break;
+ }
+
+ gint j;
+ for (j = 0; j < delay->entries->len; j++) {
+ propertyDelayValue * value = &g_array_index(delay->entries, propertyDelayValue, j);
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, value->id);
+ if (mi != NULL) {
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Applying delayed property id %d property %s", value->id, value->name);
+ #endif
+ dbusmenu_menuitem_property_set_value(mi, value->name, &value->value);
+ }
+ g_free(value->name);
+ g_value_unset(&value->value);
+ }
+ g_array_free(delay->entries, TRUE);
+
+ /* We're removing the entry and moving the index down one
+ to ensure that we adjust for the shift in the array. The
+ reality is that i is always 0. You understood this loop
+ until you got here, didn't you :) */
+ g_array_remove_index(priv->delayed_properties, i);
+ i--;
+ }
+ }
+
+ /* Check to see if we got another update in the time this
+ one was issued. */
if (priv->my_revision < priv->current_revision) {
update_layout(client);
}
diff --git a/libdbusmenu-glib/menuitem-proxy.c b/libdbusmenu-glib/menuitem-proxy.c
new file mode 100644
index 0000000..2dd5ada
--- /dev/null
+++ b/libdbusmenu-glib/menuitem-proxy.c
@@ -0,0 +1,362 @@
+/*
+An object to ferry over properties and signals between two different
+dbusmenu instances. Useful for services.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, as published by
+the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
+PURPOSE. See the applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "menuitem-proxy.h"
+
+typedef struct _DbusmenuMenuitemProxyPrivate DbusmenuMenuitemProxyPrivate;
+struct _DbusmenuMenuitemProxyPrivate {
+ DbusmenuMenuitem * mi;
+ gulong sig_property_changed;
+ gulong sig_child_added;
+ gulong sig_child_removed;
+ gulong sig_child_moved;
+};
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_MENU_ITEM
+};
+
+#define PROP_MENU_ITEM_S "menu-item"
+
+#define DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyPrivate))
+
+static void dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass);
+static void dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self);
+static void dbusmenu_menuitem_proxy_dispose (GObject *object);
+static void dbusmenu_menuitem_proxy_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);
+static void handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp);
+static void add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi);
+static void remove_menuitem (DbusmenuMenuitemProxy * pmi);
+
+G_DEFINE_TYPE (DbusmenuMenuitemProxy, dbusmenu_menuitem_proxy, DBUSMENU_TYPE_MENUITEM);
+
+static void
+dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (DbusmenuMenuitemProxyPrivate));
+
+ object_class->dispose = dbusmenu_menuitem_proxy_dispose;
+ object_class->finalize = dbusmenu_menuitem_proxy_finalize;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ DbusmenuMenuitemClass * miclass = DBUSMENU_MENUITEM_CLASS(klass);
+
+ miclass->handle_event = handle_event;
+
+ g_object_class_install_property (object_class, PROP_MENU_ITEM,
+ g_param_spec_object(PROP_MENU_ITEM_S, "The Menuitem we're proxying",
+ "An instance of the DbusmenuMenuitem class that this menuitem will mimic.",
+ DBUSMENU_TYPE_MENUITEM,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ return;
+}
+
+static void
+dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self)
+{
+ DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(self);
+
+ priv->mi = NULL;
+
+ priv->sig_property_changed = 0;
+ priv->sig_child_added = 0;
+ priv->sig_child_removed = 0;
+ priv->sig_child_moved = 0;
+
+ return;
+}
+
+/* Remove references to objects */
+static void
+dbusmenu_menuitem_proxy_dispose (GObject *object)
+{
+ remove_menuitem(DBUSMENU_MENUITEM_PROXY(object));
+
+ G_OBJECT_CLASS (dbusmenu_menuitem_proxy_parent_class)->dispose (object);
+ return;
+}
+
+/* Free any memory that we've allocated */
+static void
+dbusmenu_menuitem_proxy_finalize (GObject *object)
+{
+
+ G_OBJECT_CLASS (dbusmenu_menuitem_proxy_parent_class)->finalize (object);
+ return;
+}
+
+/* Set a property using the generic GObject interface */
+static void
+set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
+{
+ switch (id) {
+ case PROP_MENU_ITEM: {
+ GObject * lobj = g_value_get_object(value);
+ add_menuitem(DBUSMENU_MENUITEM_PROXY(obj), DBUSMENU_MENUITEM(lobj));
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Get a property using the generic GObject interface */
+static void
+get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
+{
+ DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(obj);
+
+ switch (id) {
+ case PROP_MENU_ITEM:
+ g_value_set_object(value, priv->mi);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Takes the event and passes it along to the item that we're
+ playing proxy for. */
+static void
+handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp)
+{
+ g_return_if_fail(DBUSMENU_IS_MENUITEM_PROXY(mi));
+ DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(mi);
+ g_return_if_fail(priv->mi != NULL);
+ return dbusmenu_menuitem_handle_event(priv->mi, name, value, timestamp);
+}
+
+/* Watches a property change and makes sure to put that value
+ into our property list. */
+static void
+proxy_item_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * value, gpointer user_data)
+{
+ DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
+ dbusmenu_menuitem_property_set_value(DBUSMENU_MENUITEM(pmi), property, value);
+ return;
+}
+
+/* Looks for a child getting added and wraps it and places it
+ in our list of children. */
+static void
+proxy_item_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint position, gpointer user_data)
+{
+ DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
+ DbusmenuMenuitemProxy * child_pmi = dbusmenu_menuitem_proxy_new(child);
+ dbusmenu_menuitem_child_add_position(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(child_pmi), position);
+ return;
+}
+
+/* Find the wrapper for this child and remove it as well. */
+static void
+proxy_item_child_removed (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, gpointer user_data)
+{
+ DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
+ GList * children = dbusmenu_menuitem_get_children(DBUSMENU_MENUITEM(pmi));
+ DbusmenuMenuitemProxy * finalpmi = NULL;
+ GList * childitem;
+
+ for (childitem = children; childitem != NULL; childitem = g_list_next(childitem)) {
+ DbusmenuMenuitemProxy * childpmi = (DbusmenuMenuitemProxy *)childitem->data;
+ DbusmenuMenuitem * childmi = dbusmenu_menuitem_proxy_get_wrapped(childpmi);
+ if (childmi == child) {
+ finalpmi = childpmi;
+ break;
+ }
+ }
+
+ if (finalpmi != NULL) {
+ dbusmenu_menuitem_child_delete(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(finalpmi));
+ }
+
+ return;
+}
+
+/* Find the wrapper for the item and move it in our child list */
+static void
+proxy_item_child_moved (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint newpos, guint oldpos, gpointer user_data)
+{
+ DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
+ GList * children = dbusmenu_menuitem_get_children(DBUSMENU_MENUITEM(pmi));
+ DbusmenuMenuitemProxy * finalpmi = NULL;
+ GList * childitem;
+
+ for (childitem = children; childitem != NULL; childitem = g_list_next(childitem)) {
+ DbusmenuMenuitemProxy * childpmi = (DbusmenuMenuitemProxy *)childitem->data;
+ DbusmenuMenuitem * childmi = dbusmenu_menuitem_proxy_get_wrapped(childpmi);
+ if (childmi == child) {
+ finalpmi = childpmi;
+ break;
+ }
+ }
+
+ if (finalpmi != NULL) {
+ dbusmenu_menuitem_child_reorder(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(finalpmi), newpos);
+ }
+
+ return;
+}
+
+/* Making g_object_unref into a GFunc */
+static void
+func_g_object_unref (gpointer data, gpointer user_data)
+{
+ return g_object_unref(G_OBJECT(data));
+}
+
+/* References all of the things we need for talking to this menuitem
+ including signals and other data. If the menuitem already has
+ properties we need to signal that they've changed for us. */
+static void
+add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi)
+{
+ /* Put it in private */
+ DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi);
+ if (priv->mi != NULL) {
+ remove_menuitem(pmi);
+ }
+ priv->mi = mi;
+ g_object_ref(G_OBJECT(priv->mi));
+
+ /* Attach signals */
+ priv->sig_property_changed = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(proxy_item_property_changed), pmi);
+ priv->sig_child_added = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(proxy_item_child_added), pmi);
+ priv->sig_child_removed = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(proxy_item_child_removed), pmi);
+ priv->sig_child_moved = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(proxy_item_child_moved), pmi);
+
+ /* Grab (cache) Properties */
+ GList * props = dbusmenu_menuitem_properties_list(priv->mi);
+ GList * prop;
+ for (prop = props; prop != NULL; prop = g_list_next(prop)) {
+ gchar * prop_name = (gchar *)prop->data;
+ dbusmenu_menuitem_property_set_value(DBUSMENU_MENUITEM(pmi), prop_name, dbusmenu_menuitem_property_get_value(priv->mi, prop_name));
+ }
+ g_list_free(props);
+
+ /* Go through children and wrap them */
+ GList * children = dbusmenu_menuitem_get_children(priv->mi);
+ GList * child;
+ for (child = children; child != NULL; child = g_list_next(child)) {
+ DbusmenuMenuitemProxy * child_pmi = dbusmenu_menuitem_proxy_new(DBUSMENU_MENUITEM(child->data));
+ dbusmenu_menuitem_child_append(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(child_pmi));
+ }
+
+ return;
+}
+
+/* Removes the menuitem from being our proxy. Typically this isn't
+ done until this object is destroyed, but who knows?!? */
+static void
+remove_menuitem (DbusmenuMenuitemProxy * pmi)
+{
+ DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi);
+ if (priv->mi == NULL) {
+ return;
+ }
+
+ /* Remove signals */
+ if (priv->sig_property_changed != 0) {
+ g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_property_changed);
+ }
+ if (priv->sig_child_added != 0) {
+ g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_added);
+ }
+ if (priv->sig_child_removed != 0) {
+ g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_removed);
+ }
+ if (priv->sig_child_moved != 0) {
+ g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_moved);
+ }
+
+ /* Unref */
+ g_object_unref(G_OBJECT(priv->mi));
+ priv->mi = NULL;
+
+ /* Remove our own children */
+ GList * children = dbusmenu_menuitem_take_children(DBUSMENU_MENUITEM(pmi));
+ g_list_foreach(children, func_g_object_unref, NULL);
+ g_list_free(children);
+
+ return;
+}
+
+/**
+ dbusmenu_menuitem_proxy_new:
+ @mi: The #DbusmenuMenuitem to proxy
+
+ Builds a new #DbusmenuMenuitemProxy object that proxies
+ all of the values for @mi.
+
+ Return value: A new #DbusmenuMenuitemProxy object.
+*/
+DbusmenuMenuitemProxy *
+dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi)
+{
+ DbusmenuMenuitemProxy * pmi = g_object_new(DBUSMENU_TYPE_MENUITEM_PROXY,
+ PROP_MENU_ITEM_S, mi,
+ NULL);
+
+ return pmi;
+}
+
+/**
+ dbusmenu_menuitem_proxy_get_wrapped:
+ @pmi: #DbusmenuMenuitemProxy to look into
+
+ Accesses the private variable of which #DbusmenuMenuitem
+ we are doing the proxying for.
+
+ Return value: A #DbusmenuMenuitem object or a #NULL if we
+ don't have one or there is an error.
+*/
+DbusmenuMenuitem *
+dbusmenu_menuitem_proxy_get_wrapped (DbusmenuMenuitemProxy * pmi)
+{
+ g_return_val_if_fail(DBUSMENU_MENUITEM_PROXY(pmi), NULL);
+ DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi);
+ return priv->mi;
+}
diff --git a/libdbusmenu-glib/menuitem-proxy.h b/libdbusmenu-glib/menuitem-proxy.h
new file mode 100644
index 0000000..56c4941
--- /dev/null
+++ b/libdbusmenu-glib/menuitem-proxy.h
@@ -0,0 +1,74 @@
+/*
+An object to ferry over properties and signals between two different
+dbusmenu instances. Useful for services.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, as published by
+the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
+PURPOSE. See the applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifndef __DBUSMENU_MENUITEM_PROXY_H__
+#define __DBUSMENU_MENUITEM_PROXY_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include "menuitem.h"
+
+G_BEGIN_DECLS
+
+#define DBUSMENU_TYPE_MENUITEM_PROXY (dbusmenu_menuitem_proxy_get_type ())
+#define DBUSMENU_MENUITEM_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxy))
+#define DBUSMENU_MENUITEM_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyClass))
+#define DBUSMENU_IS_MENUITEM_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_TYPE_MENUITEM_PROXY))
+#define DBUSMENU_IS_MENUITEM_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_MENUITEM_PROXY))
+#define DBUSMENU_MENUITEM_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyClass))
+
+typedef struct _DbusmenuMenuitemProxy DbusmenuMenuitemProxy;
+typedef struct _DbusmenuMenuitemProxyClass DbusmenuMenuitemProxyClass;
+
+/**
+ DbusmenuMenuitemProxyClass:
+ @parent_class: The Class of #DbusmeneMenuitem
+
+ Functions and signal slots for #DbusmenuMenuitemProxy.
+*/
+struct _DbusmenuMenuitemProxyClass {
+ DbusmenuMenuitemClass parent_class;
+};
+
+/**
+ DbusmeneMenuitemProxy:
+ @parent: The instance of #DbusmenuMenuitem
+
+ Public instance data for a #DbusmenuMenuitemProxy.
+*/
+struct _DbusmenuMenuitemProxy {
+ DbusmenuMenuitem parent;
+};
+
+GType dbusmenu_menuitem_proxy_get_type (void);
+DbusmenuMenuitemProxy * dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi);
+DbusmenuMenuitem * dbusmenu_menuitem_proxy_get_wrapped (DbusmenuMenuitemProxy * pmi);
+
+G_END_DECLS
+
+#endif
diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c
index 55d339f..f6dddf1 100644
--- a/libdbusmenu-glib/server.c
+++ b/libdbusmenu-glib/server.c
@@ -301,10 +301,29 @@ menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * val
return;
}
+/* Adds the signals for this entry to the list and looks at
+ the children of this entry to add the signals we need
+ as well. We like signals. */
+static void
+added_check_children (gpointer data, gpointer user_data)
+{
+ DbusmenuMenuitem * mi = (DbusmenuMenuitem *)data;
+ DbusmenuServer * server = (DbusmenuServer *)user_data;
+
+ menuitem_signals_create(mi, server);
+ g_list_foreach(dbusmenu_menuitem_get_children(mi), added_check_children, server);
+
+ return;
+}
+
+/* Callback for when a child is added. We need to connect everything
+ up and signal that the layout has changed. */
static void
menuitem_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint pos, DbusmenuServer * server)
{
menuitem_signals_create(child, server);
+ g_list_foreach(dbusmenu_menuitem_get_children(child), added_check_children, server);
+
/* TODO: We probably need to group the layout update signals to make the number more reasonble. */
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
priv->layout_revision++;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 746ac23..cfa1399 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -5,6 +5,7 @@ TESTS = \
test-glib-objects-test \
test-glib-layout \
test-glib-properties \
+ test-glib-proxy \
test-glib-simple-items \
test-gtk-label \
test-gtk-reorder
@@ -16,6 +17,9 @@ check_PROGRAMS = \
test-glib-layout-server \
test-glib-properties-client \
test-glib-properties-server \
+ test-glib-proxy-client \
+ test-glib-proxy-server \
+ test-glib-proxy-proxy \
test-gtk-label-client \
test-gtk-label-server \
test-glib-simple-items \
@@ -127,6 +131,56 @@ test_glib_properties_client_LDADD = \
../libdbusmenu-glib/libdbusmenu-glib.la \
$(DBUSMENUGLIB_LIBS)
+######################
+# Test Glib Proxy
+######################
+
+test-glib-proxy: test-glib-proxy-client test-glib-proxy-server test-glib-proxy-proxy Makefile.am
+ @echo "#!/bin/bash" > $@
+ @echo $(DBUS_RUNNER) --task ./test-glib-proxy-client --task-name Client --task ./test-glib-proxy-server --task-name Server --ignore-return \\ >> $@
+ @echo --task ./test-glib-proxy-proxy --parameter test.proxy.first_proxy --parameter test.proxy.second_proxy --task-name Proxy01 --ignore-return \\ >> $@
+ @echo --task ./test-glib-proxy-proxy --parameter test.proxy.second_proxy --parameter test.proxy.third_proxy --task-name Proxy02 --ignore-return \\ >> $@
+ @echo --task ./test-glib-proxy-proxy --parameter test.proxy.third_proxy --parameter test.proxy.fourth_proxy --task-name Proxy03 --ignore-return \\ >> $@
+ @echo --task ./test-glib-proxy-proxy --parameter test.proxy.fourth_proxy --parameter test.proxy.last_proxy --task-name Proxy04 --ignore-return \\ >> $@
+ @echo --task ./test-glib-proxy-proxy --parameter test.proxy.last_proxy --parameter test.proxy.server --task-name Proxy05 --ignore-return >> $@
+ @chmod +x $@
+
+test_glib_proxy_server_SOURCES = \
+ test-glib-proxy.h \
+ test-glib-proxy-server.c
+
+test_glib_proxy_server_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_glib_proxy_server_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS)
+
+test_glib_proxy_client_SOURCES = \
+ test-glib-proxy.h \
+ test-glib-proxy-client.c
+
+test_glib_proxy_client_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_glib_proxy_client_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS)
+
+test_glib_proxy_proxy_SOURCES = \
+ test-glib-proxy.h \
+ test-glib-proxy-proxy.c
+
+test_glib_proxy_proxy_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_glib_proxy_proxy_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS)
+
#########################
# Test Glib Simple Items
#########################
diff --git a/tests/test-glib-proxy-client.c b/tests/test-glib-proxy-client.c
new file mode 100644
index 0000000..50ad5d3
--- /dev/null
+++ b/tests/test-glib-proxy-client.c
@@ -0,0 +1,171 @@
+/*
+A test for libdbusmenu to ensure its quality.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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 this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <glib.h>
+
+#include <libdbusmenu-glib/client.h>
+#include <libdbusmenu-glib/menuitem.h>
+
+#include "test-glib-proxy.h"
+
+static guint layouton = -1;
+static GMainLoop * mainloop = NULL;
+static gboolean passed = TRUE;
+static guint death_timer = 0;
+
+static gboolean
+verify_props (DbusmenuMenuitem * mi, gchar ** properties)
+{
+ if (properties == NULL) {
+ return TRUE;
+ }
+
+ /* Verify they're all there and correct */
+ guint i;
+ for (i = 0; properties[i] != NULL; i += 2) {
+ const gchar * value = dbusmenu_menuitem_property_get(mi, properties[i]);
+ if (g_strcmp0(value, properties[i + 1])) {
+ g_debug("\tFailed as property '%s' should be '%s' and is '%s'", properties[i], properties[i+1], value);
+ return FALSE;
+ }
+ }
+
+ /* Verify that we don't have any extras */
+ // GList * props = dbusmenu_menuitem_properties_list(mi);
+
+ return TRUE;
+}
+
+static gboolean
+verify_root_to_layout(DbusmenuMenuitem * mi, proplayout_t * layout)
+{
+ g_debug("Verifying ID: %d", layout->id);
+
+ if (!verify_props(mi, layout->properties)) {
+ g_debug("\tFailed as unable to verify properties.");
+ return FALSE;
+ }
+
+ GList * children = dbusmenu_menuitem_get_children(mi);
+
+ if (children == NULL && layout->submenu == NULL) {
+ g_debug("\tPassed: %d", layout->id);
+ return TRUE;
+ }
+ if (children == NULL || layout->submenu == NULL) {
+ if (children == NULL) {
+ g_debug("\tFailed as there are no children but we have submenus");
+ } else {
+ g_debug("\tFailed as we have children but no submenu");
+ }
+ return FALSE;
+ }
+
+ guint i = 0;
+ for (i = 0; children != NULL && layout->submenu[i].id != -1; children = g_list_next(children), i++) {
+ if (!verify_root_to_layout(DBUSMENU_MENUITEM(children->data), &layout->submenu[i])) {
+ return FALSE;
+ }
+ }
+
+ if (children == NULL && layout->submenu[i].id == -1) {
+ g_debug("\tPassed: %d", layout->id);
+ return TRUE;
+ }
+
+ if (children != NULL) {
+ g_debug("\tFailed as there are still children but no submenus. (ID: %d)", layout->id);
+ } else {
+ g_debug("\tFailed as there are still submenus but no children. (ID: %d)", layout->id);
+ }
+ return FALSE;
+}
+
+static gboolean
+timer_func (gpointer data)
+{
+ g_debug("Death timer. Oops. Got to: %d", layouton);
+ passed = FALSE;
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+static gboolean layout_verify_timer (gpointer data);
+
+static void
+layout_updated (DbusmenuClient * client, gpointer data)
+{
+ g_debug("Layout Updated");
+ if (dbusmenu_client_get_root(client) == NULL) {
+ g_debug("\tIgnored, no root");
+ return;
+ }
+ layouton++;
+ g_timeout_add (1500, layout_verify_timer, client);
+ return;
+}
+
+static gboolean
+layout_verify_timer (gpointer data)
+{
+ g_debug("Verifing Layout: %d", layouton);
+ DbusmenuMenuitem * menuroot = dbusmenu_client_get_root(DBUSMENU_CLIENT(data));
+ proplayout_t * layout = &layouts[layouton];
+
+ if (!verify_root_to_layout(menuroot, layout)) {
+ g_debug("FAILED LAYOUT: %d", layouton);
+ passed = FALSE;
+ } else {
+ /* Extend our death */
+ g_source_remove(death_timer);
+ death_timer = g_timeout_add_seconds(10, timer_func, data);
+ }
+
+ if (layouts[layouton+1].id == -1) {
+ g_main_loop_quit(mainloop);
+ }
+
+ return FALSE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ DbusmenuClient * client = dbusmenu_client_new("test.proxy.first_proxy", "/org/test");
+ g_signal_connect(G_OBJECT(client), DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED, G_CALLBACK(layout_updated), NULL);
+
+ death_timer = g_timeout_add_seconds(10, timer_func, client);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_object_unref(G_OBJECT(client));
+
+ if (passed) {
+ g_debug("Quiting");
+ return 0;
+ } else {
+ g_debug("Quiting as we're a failure");
+ return 1;
+ }
+}
diff --git a/tests/test-glib-proxy-proxy.c b/tests/test-glib-proxy-proxy.c
new file mode 100644
index 0000000..1059cfd
--- /dev/null
+++ b/tests/test-glib-proxy-proxy.c
@@ -0,0 +1,80 @@
+#include <glib.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus-glib-bindings.h>
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-glib/menuitem-proxy.h>
+#include <libdbusmenu-glib/server.h>
+#include <libdbusmenu-glib/client.h>
+
+#include "test-glib-proxy.h"
+
+static DbusmenuServer * server = NULL;
+static DbusmenuClient * client = NULL;
+static GMainLoop * mainloop = NULL;
+
+void
+root_changed (DbusmenuClient * client, DbusmenuMenuitem * newroot, gpointer user_data)
+{
+ g_debug("New root: %X", (guint)newroot);
+
+ if (newroot == NULL) {
+ g_debug("Root removed, exiting");
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ DbusmenuMenuitemProxy * pmi = dbusmenu_menuitem_proxy_new(newroot);
+ dbusmenu_server_set_root(server, DBUSMENU_MENUITEM(pmi));
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ if (argc != 3) {
+ g_error ("Need two params");
+ return 1;
+ }
+
+ gchar * whoami = argv[1];
+ gchar * myproxy = argv[2];
+
+ g_debug("I am '%s' and I'm proxying '%s'", whoami, myproxy);
+
+ GError * error = NULL;
+ DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
+
+ g_debug("DBus ID: %s", dbus_connection_get_server_id(dbus_g_connection_get_connection(connection)));
+
+ DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+ guint nameret = 0;
+
+ if (!org_freedesktop_DBus_request_name(bus_proxy, whoami, 0, &nameret, &error)) {
+ g_error("Unable to call to request name");
+ return 1;
+ }
+
+ if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ g_error("Unable to get name");
+ return 1;
+ }
+
+ server = dbusmenu_server_new("/org/test");
+ client = dbusmenu_client_new(myproxy, "/org/test");
+
+ g_signal_connect(client, DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED, G_CALLBACK(root_changed), server);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_object_unref(G_OBJECT(server));
+ g_debug("Quiting");
+
+ return 0;
+}
diff --git a/tests/test-glib-proxy-server.c b/tests/test-glib-proxy-server.c
new file mode 100644
index 0000000..cba8ec7
--- /dev/null
+++ b/tests/test-glib-proxy-server.c
@@ -0,0 +1,126 @@
+/*
+A test for libdbusmenu to ensure its quality.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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 this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus-glib-bindings.h>
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-glib/server.h>
+
+#include "test-glib-proxy.h"
+
+static void
+set_props (DbusmenuMenuitem * mi, gchar ** props)
+{
+ if (props == NULL) return;
+
+ guint i;
+ for (i = 0; props[i] != NULL; i += 2) {
+ dbusmenu_menuitem_property_set(mi, props[i], props[i+1]);
+ }
+
+ return;
+}
+
+static DbusmenuMenuitem *
+layout2menuitem (proplayout_t * layout)
+{
+ if (layout == NULL || layout->id == -1) return NULL;
+
+ DbusmenuMenuitem * local = dbusmenu_menuitem_new();
+ set_props(local, layout->properties);
+
+ if (layout->submenu != NULL) {
+ guint count;
+ for (count = 0; layout->submenu[count].id != -1; count++) {
+ DbusmenuMenuitem * child = layout2menuitem(&layout->submenu[count]);
+ if (child != NULL) {
+ dbusmenu_menuitem_child_append(local, child);
+ }
+ }
+ }
+
+ /* g_debug("Layout to menu return: 0x%X", (unsigned int)local); */
+ return local;
+}
+
+static guint layouton = 0;
+static DbusmenuServer * server = NULL;
+static GMainLoop * mainloop = NULL;
+
+static gboolean
+timer_func (gpointer data)
+{
+ if (layouts[layouton].id == -1) {
+ g_main_loop_quit(mainloop);
+ return FALSE;
+ }
+ g_debug("Updating to Layout %d", layouton);
+
+ DbusmenuMenuitem * mi = layout2menuitem(&layouts[layouton]);
+ dbusmenu_server_set_root(server, mi);
+ g_object_unref(G_OBJECT(mi));
+ layouton++;
+
+ return TRUE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ GError * error = NULL;
+ DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
+
+ g_debug("DBus ID: %s", dbus_connection_get_server_id(dbus_g_connection_get_connection(connection)));
+
+ DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+ guint nameret = 0;
+
+ if (!org_freedesktop_DBus_request_name(bus_proxy, "test.proxy.server", 0, &nameret, &error)) {
+ g_error("Unable to call to request name");
+ return 1;
+ }
+
+ if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ g_error("Unable to get name");
+ return 1;
+ }
+
+ server = dbusmenu_server_new("/org/test");
+
+ timer_func(NULL);
+ g_timeout_add(2500, timer_func, NULL);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ g_object_unref(G_OBJECT(server));
+ g_debug("Quiting");
+
+ return 0;
+}
+
diff --git a/tests/test-glib-proxy.h b/tests/test-glib-proxy.h
new file mode 100644
index 0000000..bc12df6
--- /dev/null
+++ b/tests/test-glib-proxy.h
@@ -0,0 +1,142 @@
+/*
+A test for libdbusmenu to ensure its quality.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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 this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include <glib.h>
+
+typedef struct _proplayout_t proplayout_t;
+struct _proplayout_t {
+ gint id;
+ gchar ** properties;
+ proplayout_t * submenu;
+};
+
+gchar * props1[] = {"property1", "value1", "property2", "value2", NULL};
+gchar * props2[] = {"property00", "value00", "property01", "value01", "property02", "value02", "property03", "value03", "property04", "value04",
+ "property05", "value05", "property06", "value06", "property07", "value07", "property08", "value08", "property09", "value09",
+ "property10", "value10", "property11", "value11", "property12", "value12", "property13", "value13", "property14", "value14",
+ "property15", "value15", "property16", "value16", "property17", "value17", "property18", "value18", "property19", "value19",
+ "property20", "value20", "property21", "value21", "property22", "value22", "property23", "value23", "property24", "value24",
+ "property25", "value25", "property26", "value26", "property27", "value27", "property28", "value28", "property29", "value29",
+ "property30", "value30", "property31", "value31", "property32", "value32", "property33", "value33", "property34", "value34",
+ "property35", "value35", "property36", "value36", "property37", "value37", "property38", "value38", "property39", "value39",
+ "property40", "value40", "property41", "value41", "property42", "value42", "property43", "value43", "property44", "value44",
+ "property45", "value45", "property46", "value46", "property47", "value47", "property48", "value48", "property49", "value49",
+ "property50", "value50", "property51", "value51", "property52", "value52", "property53", "value53", "property54", "value54",
+ "property55", "value55", "property56", "value56", "property57", "value57", "property58", "value58", "property59", "value59",
+ "property60", "value60", "property61", "value61", "property62", "value62", "property63", "value63", "property64", "value64",
+ "property65", "value65", "property66", "value66", "property67", "value67", "property68", "value68", "property69", "value69",
+ "property70", "value70", "property71", "value71", "property72", "value72", "property73", "value73", "property74", "value74",
+ "property75", "value75", "property76", "value76", "property77", "value77", "property78", "value78", "property79", "value79",
+ "property80", "value80", "property81", "value81", "property82", "value82", "property83", "value83", "property84", "value84",
+ "property85", "value85", "property86", "value86", "property87", "value87", "property88", "value88", "property89", "value89",
+ "property90", "value90", "property91", "value91", "property92", "value92", "property93", "value93", "property94", "value94",
+ "property95", "value95", "property96", "value96", "property97", "value97", "property98", "value98", "property99", "value99",
+ NULL};
+gchar * props3[] = {"property name that is really long and will ensure that we can really have long property names, which could be important at some point.",
+ "And a property name that is really long should have a value that is really long, because well, that's an important part of the yin and yang of software testing.",
+ NULL};
+gchar * props4[] = {"icon-name", "network-status", "label", "Look at network", "right-column", "10:32", NULL};
+
+
+proplayout_t submenu_4_1[] = {
+ {id: 10, properties: props2, submenu: NULL},
+ {id: 11, properties: props2, submenu: NULL},
+ {id: 12, properties: props2, submenu: NULL},
+ {id: 13, properties: props2, submenu: NULL},
+ {id: 14, properties: props2, submenu: NULL},
+ {id: 15, properties: props2, submenu: NULL},
+ {id: 16, properties: props2, submenu: NULL},
+ {id: 17, properties: props2, submenu: NULL},
+ {id: 18, properties: props2, submenu: NULL},
+ {id: 19, properties: props2, submenu: NULL},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_4_2[] = {
+ {id: 20, properties: props2, submenu: NULL},
+ {id: 21, properties: props2, submenu: NULL},
+ {id: 22, properties: props2, submenu: NULL},
+ {id: 23, properties: props2, submenu: NULL},
+ {id: 24, properties: props2, submenu: NULL},
+ {id: 25, properties: props2, submenu: NULL},
+ {id: 26, properties: props2, submenu: NULL},
+ {id: 27, properties: props2, submenu: NULL},
+ {id: 28, properties: props2, submenu: NULL},
+ {id: 29, properties: props2, submenu: NULL},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_4_3[] = {
+ {id: 30, properties: props2, submenu: NULL},
+ {id: 31, properties: props2, submenu: NULL},
+ {id: 32, properties: props2, submenu: NULL},
+ {id: 33, properties: props2, submenu: NULL},
+ {id: 34, properties: props2, submenu: NULL},
+ {id: 35, properties: props2, submenu: NULL},
+ {id: 36, properties: props2, submenu: NULL},
+ {id: 37, properties: props2, submenu: NULL},
+ {id: 38, properties: props2, submenu: NULL},
+ {id: 39, properties: props2, submenu: NULL},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_4_0[] = {
+ {id: 1, properties: props2, submenu: submenu_4_1},
+ {id: 2, properties: props2, submenu: submenu_4_2},
+ {id: 3, properties: props2, submenu: submenu_4_3},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_5_5[] = {
+ {id: 205, properties: props3, submenu: NULL},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_5_4[] = {
+ {id: 204, properties: props3, submenu: submenu_5_5},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_5_3[] = {
+ {id: 203, properties: props3, submenu: submenu_5_4},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_5_2[] = {
+ {id: 202, properties: props3, submenu: submenu_5_3},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t submenu_5_1[] = {
+ {id: 201, properties: props3, submenu: submenu_5_2},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+
+proplayout_t layouts[] = {
+ {id: 1, properties: props1, submenu: NULL},
+ {id: 10, properties: props2, submenu: submenu_4_1},
+ {id: 20, properties: props3, submenu: submenu_4_2},
+ {id: 100, properties: props2, submenu: submenu_4_0},
+ {id: 200, properties: props3, submenu: submenu_5_1},
+ {id: -1, properties: NULL, submenu: NULL}
+};
+