← Back to team overview

ayatana-commits team mailing list archive

[Merge] lp:~ted/dbusmenu/group-prop-change-signals into lp:dbusmenu

 

Ted Gould has proposed merging lp:~ted/dbusmenu/group-prop-change-signals into lp:dbusmenu.

Requested reviews:
  DBus Menu Team (dbusmenu-team)

For more details, see:
https://code.launchpad.net/~ted/dbusmenu/group-prop-change-signals/+merge/49849

Makes it so that the property update signals are all aggregated into a single larger message so that we don't send as much dbus traffic.

This also includes the no-xml.  They got merged together and I don't know how to get LP to take them back appart.  Here's the message from that one:

Removes the XML from the GetLayout function and instead replaces it with a recursive DBus data type. Can get large.

Some notes:
* This is the basic feature, things like the properties aren't currently used
* There is no default handling so there is only a stub there for now, that's a future branch
-- 
https://code.launchpad.net/~ted/dbusmenu/group-prop-change-signals/+merge/49849
Your team ayatana-commits is subscribed to branch lp:dbusmenu.
=== modified file 'configure.ac'
--- configure.ac	2011-02-10 20:15:31 +0000
+++ configure.ac	2011-02-15 18:02:06 +0000
@@ -42,11 +42,9 @@
 ###########################
 
 GLIB_REQUIRED_VERSION=2.26
-XML_REQUIRED_VERSION=2.6
 
 PKG_CHECK_MODULES(DBUSMENUGLIB, glib-2.0 >= $GLIB_REQUIRED_VERSION
-                                gio-2.0 >= $GLIB_REQUIRED_VERSION
-                                libxml-2.0 >= $XML_REQUIRED_VERSION)
+                                gio-2.0 >= $GLIB_REQUIRED_VERSION)
 
 AC_SUBST(DBUSMENUGLIB_CFLAGS)
 AC_SUBST(DBUSMENUGLIB_LIBS)
@@ -65,16 +63,14 @@
   [with_gtk=2])
 AS_IF([test "x$with_gtk" = x3],
         [PKG_CHECK_MODULES(DBUSMENUGTK,  gtk+-3.0 >= $GTK3_REQUIRED_VERSION
-                                         glib-2.0 >= $GLIB_REQUIRED_VERSION
-                                         libxml-2.0 >= $XML_REQUIRED_VERSION)
+                                         glib-2.0 >= $GLIB_REQUIRED_VERSION)
          AC_SUBST(DBUSMENUGTK_CFLAGS)
          AC_SUBST(DBUSMENUGTK_LIBS)
          AC_DEFINE(HAVE_GTK3, 1, [whether gtk3 is available])
         ],
       [test "x$with_gtk" = x2],
         [PKG_CHECK_MODULES(DBUSMENUGTK,  gtk+-2.0 >= $GTK_REQUIRED_VERSION
-                                         glib-2.0 >= $GLIB_REQUIRED_VERSION
-                                         libxml-2.0 >= $XML_REQUIRED_VERSION)
+                                         glib-2.0 >= $GLIB_REQUIRED_VERSION)
          AC_SUBST(DBUSMENUGTK_CFLAGS)
          AC_SUBST(DBUSMENUGTK_LIBS)
         ],

=== modified file 'libdbusmenu-glib/client.c'
--- libdbusmenu-glib/client.c	2011-01-31 16:48:21 +0000
+++ libdbusmenu-glib/client.c	2011-02-15 18:02:06 +0000
@@ -32,9 +32,6 @@
 
 #include <gio/gio.h>
 
-#include <libxml/parser.h>
-#include <libxml/tree.h>
-
 #include "client.h"
 #include "menuitem.h"
 #include "menuitem-private.h"
@@ -151,9 +148,8 @@
 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, GDBusProxy * proxy);
-static gint parse_layout (DbusmenuClient * client, const gchar * layout);
+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 (GVariant * properties, GError * error, gpointer data);
@@ -1030,11 +1026,60 @@
 {
 	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);
@@ -1054,40 +1099,6 @@
 	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)
-{
-	if (node == NULL) {
-		return -1;
-	}
-	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;
-		}
-	}
-
-	g_warning("Unable to find an ID on the node");
-	return -1;
-}
-
 /* This is the callback for the properties on a menu item.  There
    should be all of them in the Hash, and they we use foreach to
    copy them into the menuitem.
@@ -1265,7 +1276,7 @@
 	edata->event = g_strdup(name);
 	edata->timestamp = timestamp;
 	edata->variant = variant;
-	g_variant_ref(variant);
+	g_variant_ref_sink(variant);
 
 	g_dbus_proxy_call(priv->menuproxy,
 	                  "Event",
@@ -1390,10 +1401,14 @@
 /* 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, GDBusProxy * proxy)
+parse_layout_xml(DbusmenuClient * client, GVariant * layout, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, GDBusProxy * proxy)
 {
+	if (layout == NULL) {
+		return NULL;
+	}
+
 	/* First verify and figure out what we've got */
-	gint id = parse_node_get_id(node);
+	gint id = g_variant_get_int32(g_variant_get_child_value(layout, 0));
 	if (id < 0) {
 		return NULL;
 	}
@@ -1405,20 +1420,26 @@
 	g_return_val_if_fail(id == dbusmenu_menuitem_get_id(item), NULL);
 
 	/* Some variables */
-	xmlNodePtr children;
-	guint position;
+	GVariantIter children;
+	g_variant_iter_init(&children, g_variant_get_child_value(layout, 2));
+	GVariant * child;
+
+	guint position = 0;
 	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++) {
+	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. */
-			position--;
 			continue;
 		}
 		DbusmenuMenuitem * childmi = NULL;
@@ -1451,6 +1472,8 @@
 			dbusmenu_menuitem_child_reorder(item, childmi, position);
 			parse_layout_update(childmi, client);
 		}
+
+		position++;
 	}
 
 	/* Remove any children that are no longer used by this version of
@@ -1473,14 +1496,20 @@
 	}
 
 	/* now it's time to recurse down the tree. */
-	children = node->children;
+	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 (children != NULL && childmis != NULL) {
-		gint xmlid = parse_node_get_id(children);
+	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) {
-			children = children->next;
+			child = g_variant_iter_next_value(&children);
 			continue;
 		}
 
@@ -1489,13 +1518,14 @@
 		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);
+		parse_layout_xml(client, child, DBUSMENU_MENUITEM(childmis->data), item, proxy);
 
-		children = children->next;
+		child = g_variant_iter_next_value(&children);
 		childmis = g_list_next(childmis);
 	}
-	if (children != NULL) {
-		g_warning("Sync failed, now we've got extra XML nodes.");
+
+	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.");
@@ -1507,7 +1537,7 @@
 /* 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");
@@ -1515,17 +1545,6 @@
 
 	DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
 
-	xmlDocPtr xmldoc;
-
-	/* 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) {
@@ -1534,11 +1553,10 @@
 		parse_layout_update(priv->root, client);
 	}
 
-	priv->root = parse_layout_xml(client, root, priv->root, NULL, priv->menuproxy);
-	xmlFreeDoc(xmldoc);
+	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) {
@@ -1579,14 +1597,10 @@
 		goto out;
 	}
 
-	guint rev;
-	gchar * xml;
-
-	g_variant_get(params, "(us)", &rev, &xml);
-	g_variant_unref(params);
-
-	guint parseable = parse_layout(client, xml);
-	g_free(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!");
@@ -1612,6 +1626,10 @@
 		priv->layoutcall = NULL;
 	}
 
+	if (params != NULL) {
+		g_variant_unref(params);
+	}
+
 	g_object_unref(G_OBJECT(client));
 	return;
 }
@@ -1639,10 +1657,20 @@
 
 	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",
-	                  g_variant_new("(i)", 0), /* root */
+	                  args,
 	                  G_DBUS_CALL_FLAGS_NONE,
 	                  -1,   /* timeout */
 	                  priv->layoutcall, /* cancellable */

=== modified file 'libdbusmenu-glib/dbus-menu.xml'
--- libdbusmenu-glib/dbus-menu.xml	2011-01-13 15:53:15 +0000
+++ libdbusmenu-glib/dbus-menu.xml	2011-02-15 18:02:06 +0000
@@ -174,34 +174,38 @@
 <!-- Functions -->
 
 		<method name="GetLayout">
-			<dox:d><![CDATA[
-			Provides an XML representation of the menu hierarchy
-
-			XML syntax:
-
-			@verbatim
-<menu id="0"> # Root container
-	<menu id="1"> # First level menu, for example "File"
-		<menu id="2"/> ~ Second level menu, for example "Open"
-		<menu id="3"/>
-		...
-	</menu>
-	<menu id="4"> # Another first level menu, say "Edit"
-		...
-	</menu>
-	...
-</menu>
-			@endverbatim
-			]]></dox:d>
+			<dox:d>
+			  Provides the layout and propertiers that are attached to the entries
+			  that are in the layout.  It only gives the items that are children
+			  of the item that is specified in @parentId.  It will return all of the
+			  properties or specific ones depending of the value in @propertyNames.
+
+			  The format is recursive, where the second 'v' is in the same format
+			  as the original 'a(ia(sv)a(v))'.  If the @recursive flag is set to
+			  less than one then the second array will have zero entries.
+			</dox:d>
 			<arg type="i" name="parentId" direction="in">
 				<dox:d>The ID of the parent node for the layout.  For
 				grabbing the layout from the root node use zero.</dox:d>
 			</arg>
+			<arg type="i" name="recurse" direction="in">
+				<dox:d>
+				  The amount of levels of recursion to use.  -1, as value would
+				  deliver all the items under the @parentId.
+				</dox:d>
+			</arg>
+			<arg type="as" name="propertyNames" direction="in" >
+				<dox:d>
+					The list of item properties we are
+					interested in.  If there are no entries in the list all of
+					the properties will be sent.
+				</dox:d>
+			</arg>
 			<arg type="u" name="revision" direction="out">
 				<dox:d>The revision number of the layout.  For matching
 				with layoutUpdated signals.</dox:d>
 			</arg>
-			<arg type="s" name="layout" direction="out">
+			<arg type="(ia{sv}av)" name="layout" direction="out">
 				<dox:d>The layout as an XML string of IDs.</dox:d>
 			</arg>
 		</method>
@@ -236,33 +240,21 @@
 			</arg>
 		</method>
 
-		<method name="GetChildren">
-			<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
-			<arg type="i" name="id" direction="in" />
-			<arg type="as" name="propertyNames" direction="in" />
-			<arg type="a(ia{sv})" name="properties" direction="out" />
-		</method>
-
 		<method name="GetProperty">
-			<arg type="i" name="id" direction="in" />
-			<arg type="s" name="name" direction="in" />
-			<arg type="v" name="value" direction="out" />
-		</method>
-
-		<method name="GetProperties">
 			<dox:d>
-			Returns multiple properties in one call. This is more efficient than
-			GetProperty.
-
+			  Get a signal property on a single item.  This is not useful if you're
+			  going to implement this interface, it should only be used if you're
+			  debugging via a commandline tool.
 			</dox:d>
-			<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
-			<arg type="i" name="id" direction="in" >
-				<dox:d>The item whose properties we want to retrieve.</dox:d>
-			</arg>
-			<arg type="as" name="propertyNames" direction="in" >
-				<dox:d>List of string name of the properties we want.  If the list contains no entries, all properties are sent.</dox:d>
-			</arg>
-			<arg type="a{sv}" name="properties" direction="out" />
+			<arg type="i" name="id" direction="in">
+				<dox:d>the id of the item which received the event</dox:d>
+			</arg>
+			<arg type="s" name="name" direction="in">
+				<dox:d>the name of the property to get</dox:d>
+			</arg>
+			<arg type="v" name="value" direction="out">
+				<dox:d>the value of the property</dox:d>
+			</arg>
 		</method>
 
 		<method name="Event">
@@ -309,25 +301,16 @@
 		</method>
 
 <!-- Signals -->
-		<signal name="ItemPropertyUpdated">
-			<dox:d>
-			Triggered by the application to notify the applet that the property @a property
-			from item @a id has changed to @a value.
-			</dox:d>
-			<arg type="i" name="id" direction="out" />
-			<arg type="s" name="prop" direction="out" />
-			<arg type="v" name="value" direction="out" />
-		</signal>
-
-		<signal name="ItemUpdated">
-			<dox:d>
-				Triggered by the application to notify the applet that all properties of item
-			</dox:d>
-			<arg type="i" name="id" direction="out" >
-				<dox:d>id which should be considered outdated</dox:d>
-			</arg>
-		</signal>
-
+		<signal name="ItemsPropertiesUpdated">
+			<dox:d>
+			Triggered when there are lots of property updates across many items
+			so they all get grouped into a single dbus message.  The format is
+			the ID of the item with a hashtable of names and values for those
+			properties.
+			</dox:d>
+			<arg type="a(ia(sv))" name="props" direction="out" />
+			<arg type="a(ia(s))"  name="props_removed" direction="out" />
+		</signal>
 		<signal name="LayoutUpdated">
 			<dox:d>
 			Triggered by the application to notify display of a layout update, up to

=== modified file 'libdbusmenu-glib/menuitem-private.h'
--- libdbusmenu-glib/menuitem-private.h	2010-10-14 21:00:38 +0000
+++ libdbusmenu-glib/menuitem-private.h	2011-02-15 18:02:06 +0000
@@ -33,10 +33,11 @@
 
 G_BEGIN_DECLS
 
-void dbusmenu_menuitem_buildxml (DbusmenuMenuitem * mi, GPtrArray * array);
+GVariant * dbusmenu_menuitem_build_variant (DbusmenuMenuitem * mi, const gchar ** properties, gint recurse);
 gboolean dbusmenu_menuitem_realized (DbusmenuMenuitem * mi);
 void dbusmenu_menuitem_set_realized (DbusmenuMenuitem * mi);
-GVariant * dbusmenu_menuitem_properties_variant (DbusmenuMenuitem * mi);
+GVariant * dbusmenu_menuitem_properties_variant (DbusmenuMenuitem * mi, const gchar ** properties);
+gboolean dbusmenu_menuitem_property_is_default (DbusmenuMenuitem * mi, const gchar * property);
 
 G_END_DECLS
 

=== modified file 'libdbusmenu-glib/menuitem.c'
--- libdbusmenu-glib/menuitem.c	2011-02-04 17:00:07 +0000
+++ libdbusmenu-glib/menuitem.c	2011-02-15 18:02:06 +0000
@@ -1009,7 +1009,7 @@
 
 	if (value != NULL) {
 		gchar * lprop = g_strdup(property);
-		g_variant_ref(value);
+		g_variant_ref_sink(value);
 
 		if (currentval == NULL || !g_variant_equal((GVariant*)currentval, value)) {
 			g_hash_table_replace(priv->properties, lprop, value);
@@ -1213,7 +1213,7 @@
 	GHashTable * table = (GHashTable *)in_data;
 	gchar * key = (gchar *)in_key;
 	GVariant * value = (GVariant *)in_value;
-	g_variant_ref(value);
+	g_variant_ref_sink(value);
 	g_hash_table_insert(table, g_strdup(key), value);
 	return;
 }
@@ -1250,7 +1250,9 @@
 static void
 variant_helper (gpointer in_key, gpointer in_value, gpointer user_data)
 {
-	g_variant_builder_add((GVariantBuilder *)user_data, "{sv}", in_key, in_value);
+	GVariant * value = g_variant_new_dict_entry(g_variant_new_string((gchar *)in_key),
+	                                            g_variant_new_variant((GVariant *)in_value));
+	g_variant_builder_add_value((GVariantBuilder *)user_data, value);
 	return;
 }
 
@@ -1264,7 +1266,7 @@
 	Return Value: A GVariant of type "a{sv}" or NULL on error.
 */
 GVariant *
-dbusmenu_menuitem_properties_variant (DbusmenuMenuitem * mi)
+dbusmenu_menuitem_properties_variant (DbusmenuMenuitem * mi, const gchar ** properties)
 {
 	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
 
@@ -1274,7 +1276,7 @@
 
 	if (g_hash_table_size(priv->properties) > 0) {
 		GVariantBuilder builder;
-		g_variant_builder_init(&builder, g_variant_type_new("a{sv}"));
+		g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
 
 		g_hash_table_foreach(priv->properties, variant_helper, &builder);
 
@@ -1322,7 +1324,7 @@
 
 
 /**
-	dbusmenu_menuitem_buildxml:
+	dbusmenu_menuitem_buildvariant:
 	@mi: #DbusmenuMenuitem to represent in XML
 	@array: (element-type utf8): A list of string that will be turned into an XML file
 
@@ -1332,28 +1334,50 @@
 	start tag and one that is a closing tag.  It will allow it's
 	children to place their own tags in the array in between those two.
 */
-void
-dbusmenu_menuitem_buildxml (DbusmenuMenuitem * mi, GPtrArray * array)
+GVariant *
+dbusmenu_menuitem_build_variant (DbusmenuMenuitem * mi, const gchar ** properties, gint recurse)
 {
-	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
 
 	gint id = 0;
 	if (!dbusmenu_menuitem_get_root(mi)) {
 		id = dbusmenu_menuitem_get_id(mi);
 	}
 
+	/* This is the tuple that'll build up being a representation of
+	   this entry */
+	GVariantBuilder tupleb;
+	g_variant_builder_init(&tupleb, G_VARIANT_TYPE_TUPLE);
+
+	/* Add our ID */
+	g_variant_builder_add_value(&tupleb, g_variant_new_int32(id));
+
+	/* Figure out the properties */
+	GVariant * props = dbusmenu_menuitem_properties_variant(mi, properties);
+	if (props != NULL) {
+		g_variant_builder_add_value(&tupleb, props);
+	} else {
+		g_variant_builder_add_value(&tupleb, g_variant_parse(G_VARIANT_TYPE("a{sv}"), "[ ]", NULL, NULL, NULL));
+	}
+
+	/* Pillage the children */
 	GList * children = dbusmenu_menuitem_get_children(mi);
-	if (children == NULL) {
-		g_ptr_array_add(array, g_strdup_printf("<menu id=\"%d\"/>", id));
+	if (children == NULL && recurse != 0) {
+		g_variant_builder_add_value(&tupleb, g_variant_new_array(G_VARIANT_TYPE_VARIANT, NULL, 0));
 	} else {
-		g_ptr_array_add(array, g_strdup_printf("<menu id=\"%d\">", id));
+		GVariantBuilder childrenbuilder;
+		g_variant_builder_init(&childrenbuilder, G_VARIANT_TYPE_ARRAY);
+
 		for ( ; children != NULL; children = children->next) {
-			dbusmenu_menuitem_buildxml(DBUSMENU_MENUITEM(children->data), array);
+			GVariant * child = dbusmenu_menuitem_build_variant(DBUSMENU_MENUITEM(children->data), properties, recurse - 1);
+
+			g_variant_builder_add_value(&childrenbuilder, g_variant_new_variant(child));
 		}
-		g_ptr_array_add(array, g_strdup("</menu>"));
+
+		g_variant_builder_add_value(&tupleb, g_variant_builder_end(&childrenbuilder));
 	}
 
-	return;
+	return g_variant_builder_end(&tupleb);
 }
 
 typedef struct {
@@ -1473,3 +1497,13 @@
 
 	return;
 }
+
+/* Checks to see if the value of this property is unique or just the
+   default value. */
+/* TODO: Implement this */
+gboolean
+dbusmenu_menuitem_property_is_default (DbusmenuMenuitem * mi, const gchar * property)
+{
+	/* No defaults system yet */
+	return FALSE;
+}

=== modified file 'libdbusmenu-glib/menuitem.h'
--- libdbusmenu-glib/menuitem.h	2011-01-07 05:25:02 +0000
+++ libdbusmenu-glib/menuitem.h	2011-02-15 18:02:06 +0000
@@ -111,14 +111,15 @@
 typedef void (*dbusmenu_menuitem_about_to_show_cb) (DbusmenuMenuitem * mi, gpointer user_data);
 
 /**
- * dbusmenu_menuitem_buildxml_slot_t:
+ * dbusmenu_menuitem_buildvariant_slot_t:
  * @mi: (in): Menu item that should be built from
- * @stringarray: (inout) (transfer none) (array) (element-type utf8): An array of strings that can be combined into an XML file.
  * 
  * This is the function that is called to represent this menu item
- * as an XML fragment.  Should call it's own children.
+ * as a variant.  Should call it's own children.
+ *
+ * Return value: (transfer full) A variant representing this item and it's children
  */
-typedef void (*dbusmenu_menuitem_buildxml_slot_t) (DbusmenuMenuitem * mi, GPtrArray* stringarray);
+typedef GVariant * (*dbusmenu_menuitem_buildvariant_slot_t) (DbusmenuMenuitem * mi, gchar ** properties);
 
 /**
  * DbusmenuMenuitemClass:
@@ -155,7 +156,7 @@
 	void (*realized) (void);
 
 	/* Virtual functions */
-	dbusmenu_menuitem_buildxml_slot_t buildxml;
+	dbusmenu_menuitem_buildvariant_slot_t buildvariant;
 	void (*handle_event) (DbusmenuMenuitem * mi, const gchar * name, GVariant * value, guint timestamp);
 	void (*send_about_to_show) (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data);
 

=== modified file 'libdbusmenu-glib/server.c'
--- libdbusmenu-glib/server.c	2011-01-31 14:10:49 +0000
+++ libdbusmenu-glib/server.c	2011-02-15 18:02:06 +0000
@@ -54,6 +54,9 @@
 	GDBusConnection * bus;
 	GCancellable * bus_lookup;
 	guint dbus_registration;
+
+	GArray * prop_array;
+	guint property_idle;
 };
 
 #define DBUSMENU_SERVER_GET_PRIVATE(o) (DBUSMENU_SERVER(o)->priv)
@@ -156,6 +159,7 @@
 static void       menuitem_signals_remove     (DbusmenuMenuitem * mi,
                                                gpointer data);
 static GQuark     error_quark                 (void);
+static void       prop_array_teardown         (GArray * prop_array);
 static void       bus_get_layout              (DbusmenuServer * server,
                                                GVariant * params,
                                                GDBusMethodInvocation * invocation);
@@ -354,6 +358,17 @@
 
 	if (priv->layout_idle != 0) {
 		g_source_remove(priv->layout_idle);
+		priv->layout_idle = 0;
+	}
+	
+	if (priv->property_idle != 0) {
+		g_source_remove(priv->property_idle);
+		priv->property_idle = 0;
+	}
+
+	if (priv->prop_array != NULL) {
+		prop_array_teardown(priv->prop_array);
+		priv->prop_array = NULL;
 	}
 
 	if (priv->root != NULL) {
@@ -418,6 +433,15 @@
 		if (priv->root != NULL) {
 			dbusmenu_menuitem_foreach(priv->root, menuitem_signals_remove, obj);
 			dbusmenu_menuitem_set_root(priv->root, FALSE);
+
+			GList * properties = dbusmenu_menuitem_properties_list(priv->root);
+			GList * iter;
+			for (iter = properties; iter != NULL; iter = g_list_next(iter)) {
+				gchar * property = (gchar *)iter->data;
+				menuitem_property_changed(priv->root, property, NULL, DBUSMENU_SERVER(obj));
+			}
+			g_list_free(properties);
+
 			g_object_unref(G_OBJECT(priv->root));
 			priv->root = NULL;
 		}
@@ -426,6 +450,14 @@
 			g_object_ref(G_OBJECT(priv->root));
 			dbusmenu_menuitem_set_root(priv->root, TRUE);
 			dbusmenu_menuitem_foreach(priv->root, menuitem_signals_create, obj);
+
+			GList * properties = dbusmenu_menuitem_properties_list(priv->root);
+			GList * iter;
+			for (iter = properties; iter != NULL; iter = g_list_next(iter)) {
+				gchar * property = (gchar *)iter->data;
+				menuitem_property_changed(priv->root, property, dbusmenu_menuitem_property_get_variant(priv->root, property), DBUSMENU_SERVER(obj));
+			}
+			g_list_free(properties);
 		} else {
 			g_debug("Setting root node to NULL");
 		}
@@ -440,17 +472,6 @@
 }
 
 static void
-xmlarray_foreach_free (gpointer arrayentry, gpointer userdata)
-{
-	if (arrayentry != NULL) {
-		/* g_debug("Freeing pointer: %s", (gchar *)arrayentry); */
-		g_free(arrayentry);
-	}
-
-	return;
-}
-
-static void
 get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
 {
 	DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(obj);
@@ -633,22 +654,264 @@
 	return;
 }
 
+typedef struct _prop_idle_item_t prop_idle_item_t;
+struct _prop_idle_item_t {
+	gint id;
+	GArray * array;
+};
+
+typedef struct _prop_idle_prop_t prop_idle_prop_t;
+struct _prop_idle_prop_t {
+	gchar * property;
+	GVariant * variant;
+};
+
+/* Takes appart our data structure so we don't leak any
+   memory or references. */
+static void
+prop_array_teardown (GArray * prop_array)
+{
+	int i, j;
+
+	for (i = 0; i < prop_array->len; i++) {
+		prop_idle_item_t * iitem = &g_array_index(prop_array, prop_idle_item_t, i);
+		
+		for (j = 0; j < iitem->array->len; j++) {
+			prop_idle_prop_t * iprop = &g_array_index(iitem->array, prop_idle_prop_t, j);
+
+			g_free(iprop->property);
+
+			if (iprop->variant != NULL) {
+				g_variant_unref(iprop->variant);
+			}
+		}
+
+		g_array_free(iitem->array, TRUE);
+	}
+
+	g_array_free(prop_array, TRUE);
+
+	return;
+}
+
+/* Works in the idle to send a set of property updates so that they'll
+   all update in a single dbus message. */
+static gboolean
+menuitem_property_idle (gpointer user_data)
+{
+	DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(user_data);
+
+	/* Source will get removed as we return */
+	priv->property_idle = 0;
+
+	/* If there are no items, let's just not signal */
+	if (priv->prop_array == NULL) {
+		return FALSE;
+	}
+
+	int i, j;
+	GVariantBuilder itembuilder;
+	gboolean item_init = FALSE;
+
+	GVariantBuilder removeitembuilder;
+	gboolean removeitem_init = FALSE;
+
+	for (i = 0; i < priv->prop_array->len; i++) {
+		prop_idle_item_t * iitem = &g_array_index(priv->prop_array, prop_idle_item_t, i);
+
+		GVariantBuilder dictbuilder;
+		gboolean dictinit = FALSE;
+
+		GVariantBuilder removedictbuilder;
+		gboolean removedictinit = FALSE;
+		
+		/* Go throught each item and see if it should go in the removal list
+		   or the additive list. */
+		for (j = 0; j < iitem->array->len; j++) {
+			prop_idle_prop_t * iprop = &g_array_index(iitem->array, prop_idle_prop_t, j);
+
+			if (iprop->variant != NULL) {
+				if (!dictinit) {
+					g_variant_builder_init(&dictbuilder, G_VARIANT_TYPE_DICTIONARY);
+					dictinit = TRUE;
+				}
+
+				GVariant * entry = g_variant_new_dict_entry(g_variant_new_string(iprop->property),
+				                                            g_variant_new_variant(iprop->variant));
+
+				g_variant_builder_add_value(&dictbuilder, entry);
+			} else {
+				if (!removedictinit) {
+					g_variant_builder_init(&removedictbuilder, G_VARIANT_TYPE_ARRAY);
+					removedictinit = TRUE;
+				}
+
+				g_variant_builder_add_value(&removedictbuilder, g_variant_new_string(iprop->property));
+			}
+		}
+
+		/* If we've got new values that are real values we need to add that
+		   to the list of items to send the value of */
+		if (dictinit) {
+			GVariantBuilder tuplebuilder;
+			g_variant_builder_init(&tuplebuilder, G_VARIANT_TYPE_TUPLE);
+
+			g_variant_builder_add_value(&tuplebuilder, g_variant_new_int32(iitem->id));
+			g_variant_builder_add_value(&tuplebuilder, g_variant_builder_end(&dictbuilder));
+
+			if (!item_init) {
+				g_variant_builder_init(&itembuilder, G_VARIANT_TYPE_ARRAY);
+				item_init = TRUE;
+			}
+
+			g_variant_builder_add_value(&itembuilder, g_variant_builder_end(&tuplebuilder));
+		}
+
+		/* If we've got properties that have been removed then we need to add
+		   them to the list of removed items */
+		if (removedictinit) {
+			GVariantBuilder tuplebuilder;
+			g_variant_builder_init(&tuplebuilder, G_VARIANT_TYPE_TUPLE);
+
+			g_variant_builder_add_value(&tuplebuilder, g_variant_new_int32(iitem->id));
+			g_variant_builder_add_value(&tuplebuilder, g_variant_builder_end(&removedictbuilder));
+
+			if (!removeitem_init) {
+				g_variant_builder_init(&removeitembuilder, G_VARIANT_TYPE_ARRAY);
+				removeitem_init = TRUE;
+			}
+
+			g_variant_builder_add_value(&removeitembuilder, g_variant_builder_end(&tuplebuilder));
+		}
+	}
+
+	GVariant * megadata[2];
+
+	if (item_init) {
+		megadata[0] = g_variant_builder_end(&itembuilder);
+	} else {
+		GError * error = NULL;
+		megadata[0] = g_variant_parse(G_VARIANT_TYPE("a(ia{sv})"), "[ ]", NULL, NULL, &error);
+
+		if (error != NULL) {
+			g_warning("Unable to parse '[ ]' as a 'a(ia{sv})': %s", error->message);
+			g_error_free(error);
+		}
+	}
+
+	if (removeitem_init) {
+		megadata[1] = g_variant_builder_end(&removeitembuilder);
+	} else {
+		GError * error = NULL;
+		megadata[1] = g_variant_parse(G_VARIANT_TYPE("a(ia(s))"), "[ ]", NULL, NULL, &error);
+
+		if (error != NULL) {
+			g_warning("Unable to parse '[ ]' as a 'a(ia(s))': %s", error->message);
+			g_error_free(error);
+		}
+	}
+
+	if (priv->dbusobject != NULL && priv->bus != NULL) {
+		g_dbus_connection_emit_signal(priv->bus,
+		                              NULL,
+		                              priv->dbusobject,
+		                              DBUSMENU_INTERFACE,
+		                              "ItemPropertiesUpdated",
+		                              g_variant_new_tuple(megadata, 2),
+		                              NULL);
+	} else {
+		g_variant_unref(megadata[0]);
+		g_variant_unref(megadata[1]);
+	}
+
+	/* Clean everything up */
+	prop_array_teardown(priv->prop_array);
+	priv->prop_array = NULL;
+
+	return FALSE;
+}
+
 static void 
 menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, GVariant * variant, DbusmenuServer * server)
 {
+	int i;
+	gint item_id;
+
 	DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
 
-	g_signal_emit(G_OBJECT(server), signals[ID_PROP_UPDATE], 0, dbusmenu_menuitem_get_id(mi), property, variant, TRUE);
-
-	if (priv->dbusobject != NULL && priv->bus != NULL) {
-		g_dbus_connection_emit_signal(priv->bus,
-		                              NULL,
-		                              priv->dbusobject,
-		                              DBUSMENU_INTERFACE,
-		                              "ItemPropertyUpdated",
-		                              g_variant_new("(isv)", dbusmenu_menuitem_get_id(mi), property, variant),
-		                              NULL);
-	}
+	item_id = dbusmenu_menuitem_get_id(mi);
+
+	g_signal_emit(G_OBJECT(server), signals[ID_PROP_UPDATE], 0, item_id, property, variant, TRUE);
+
+	/* See if we have a property array, if not, we need to
+	   build one of these suckers */
+	if (priv->prop_array == NULL) {
+		priv->prop_array = g_array_new(FALSE, FALSE, sizeof(prop_idle_item_t));
+	}
+
+	/* Look to see if we already have this item in the list
+	   and use it if so */
+	prop_idle_item_t * item = NULL;
+	for (i = 0; i < priv->prop_array->len; i++) {
+		prop_idle_item_t * iitem = &g_array_index(priv->prop_array, prop_idle_item_t, i);
+		if (iitem->id == item_id) {
+			item = iitem;
+			break;
+		}
+	}
+
+	GArray * properties = NULL;
+	/* If not, we'll need to build ourselves one */
+	if (item == NULL) {
+		prop_idle_item_t myitem;
+		myitem.id = item_id;
+		myitem.array = g_array_new(FALSE, FALSE, sizeof(prop_idle_prop_t));
+
+		g_array_append_val(priv->prop_array, myitem);
+		properties = myitem.array;
+	} else {
+		properties = item->array;
+	}
+
+	/* Check to see if this property is in the list */
+	prop_idle_prop_t * prop = NULL;
+	for (i = 0; i < properties->len; i++) {
+		prop_idle_prop_t * iprop = &g_array_index(properties, prop_idle_prop_t, i);
+		if (g_strcmp0(iprop->property, property) == 0) {
+			prop = iprop;
+			break;
+		}
+	}
+
+	/* If it's the default value we want to treat it like a clearing
+	   of the value so that it doesn't get sent over dbus and waste
+	   bandwidth */
+	if (dbusmenu_menuitem_property_is_default(mi, property)) {
+		variant = NULL;
+	}
+
+	/* If so, we need to swap the value */
+	if (prop != NULL) {
+		g_variant_unref(prop->variant);
+		prop->variant = variant;
+	} else {
+	/* else we need to add it */
+		prop_idle_prop_t myprop;
+		myprop.property = g_strdup(property);
+		myprop.variant = variant;
+
+		g_array_append_val(properties, myprop);
+	}
+	if (variant != NULL) {
+		g_variant_ref_sink(variant);
+	}
+
+	/* Check to see if the idle is already queued, and queue it
+	   if not. */
+	if (priv->property_idle == 0) {
+		priv->property_idle = g_idle_add(menuitem_property_idle, server);
+	}
+
 	return;
 }
 
@@ -757,26 +1020,28 @@
 {
 	DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
 
-	gint parent = 0;
-	g_variant_get(params, "(i)", &parent);
+	/* Input */
+	gint parent = g_variant_get_int32(g_variant_get_child_value(params, 0));
+	gint recurse = g_variant_get_int32(g_variant_get_child_value(params, 1));
+	const gchar ** props = g_variant_get_strv(g_variant_get_child_value(params, 2), NULL);
 
+	/* Output */
 	guint revision = priv->layout_revision;
-	GPtrArray * xmlarray = g_ptr_array_new();
-
-	if (parent == 0) {
-		if (priv->root == NULL) {
-			/* g_debug("Getting layout without root node!"); */
-			g_ptr_array_add(xmlarray, g_strdup("<menu id=\"0\"/>"));
+	GVariant * items = NULL;
+
+	if (priv->root != NULL) {
+		items = dbusmenu_menuitem_build_variant(priv->root, props, recurse);
+	}
+
+	/* What happens if we don't have anything? */
+	if (items == NULL) {
+		if (parent == 0) {
+			/* We should always have a root, so we'll make up one for
+			   right now. */
+			items = g_variant_parse(G_VARIANT_TYPE("(ia{sv}av)"), "(0, [], [])", NULL, NULL, NULL);
 		} else {
-			dbusmenu_menuitem_buildxml(priv->root, xmlarray);
-		}
-	} else {
-		DbusmenuMenuitem * item = NULL;
-		if (priv->root != NULL) {
-			item = dbusmenu_menuitem_find_id(priv->root, parent);
-		}
-
-		if (item == NULL) {
+			/* If we were looking for a specific ID that's an error that
+			   we should send back, so let's do that. */
 			g_dbus_method_invocation_return_error(invocation,
 				                                  error_quark(),
 				                                  INVALID_MENUITEM_ID,
@@ -784,23 +1049,19 @@
 				                                  parent);
 			return;
 		}
-		dbusmenu_menuitem_buildxml(item, xmlarray);
 	}
-	g_ptr_array_add(xmlarray, NULL);
-
-	/* build string */
-	gchar * layout = g_strjoinv("", (gchar **)xmlarray->pdata);
-
-	g_ptr_array_foreach(xmlarray, xmlarray_foreach_free, NULL);
-	g_ptr_array_free(xmlarray, TRUE);
-
+
+	/* Build the final variant tuple */
+	GVariantBuilder tuplebuilder;
+	g_variant_builder_init(&tuplebuilder, G_VARIANT_TYPE_TUPLE);
+
+	g_variant_builder_add_value(&tuplebuilder, g_variant_new_uint32(revision));
+	g_variant_builder_add_value(&tuplebuilder, items);
+
+	GVariant * retval = g_variant_builder_end(&tuplebuilder);
+	// g_debug("Sending layout type: %s", g_variant_get_type_string(retval));
 	g_dbus_method_invocation_return_value(invocation,
-	                                      g_variant_new("(us)",
-	                                                    revision,
-	                                                    layout));
-
-	g_free(layout);
-
+	                                      retval);
 	return;
 }
 
@@ -874,7 +1135,7 @@
 		return;
 	}
 
-	GVariant * dict = dbusmenu_menuitem_properties_variant(mi);
+	GVariant * dict = dbusmenu_menuitem_properties_variant(mi, NULL);
 
 	g_dbus_method_invocation_return_value(invocation, g_variant_new("(a{sv})", dict));
 
@@ -922,7 +1183,7 @@
 		GVariantBuilder wbuilder;
 		g_variant_builder_init(&wbuilder, G_VARIANT_TYPE_TUPLE);
 		g_variant_builder_add(&wbuilder, "i", id);
-		GVariant * props = dbusmenu_menuitem_properties_variant(mi);
+		GVariant * props = dbusmenu_menuitem_properties_variant(mi, NULL);
 
 		if (props == NULL) {
 			GError * error = NULL;
@@ -982,7 +1243,7 @@
 	gint id = dbusmenu_menuitem_get_id(mi);
 	g_variant_builder_add_value(&tuple, g_variant_new_int32(id));
 
-	GVariant * props = dbusmenu_menuitem_properties_variant(mi);
+	GVariant * props = dbusmenu_menuitem_properties_variant(mi, NULL);
 	g_variant_builder_add_value(&tuple, props);
 
 	g_variant_builder_add_value(builder, g_variant_builder_end(&tuple));
@@ -1105,7 +1366,7 @@
 		event_data->variant = g_variant_get_variant(event_data->variant);
 	}
 
-	g_variant_ref(event_data->variant);
+	g_variant_ref_sink(event_data->variant);
 
 	g_timeout_add(0, event_local_handler, event_data);
 

=== modified file 'tests/json-loader.c'
--- tests/json-loader.c	2010-12-02 03:35:46 +0000
+++ tests/json-loader.c	2011-02-15 18:02:06 +0000
@@ -109,7 +109,6 @@
 
 		if (variant != NULL) {
 			dbusmenu_menuitem_property_set_variant(mi, member, variant);
-			g_variant_unref(variant);
 		}
 	}
 

=== modified file 'tests/test-glib-layout-client.c'
--- tests/test-glib-layout-client.c	2010-02-05 17:54:42 +0000
+++ tests/test-glib-layout-client.c	2011-02-15 18:02:06 +0000
@@ -81,6 +81,11 @@
 	g_debug("Layout Updated");
 
 	DbusmenuMenuitem * menuroot = dbusmenu_client_get_root(client);
+	if (menuroot == NULL) {
+		g_debug("Root NULL, waiting");
+		return;
+	}
+
 	layout_t * layout = &layouts[layouton];
 	
 	if (!verify_root_to_layout(menuroot, layout)) {


Follow ups