aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore5
-rw-r--r--Makefile.am2
-rw-r--r--libdbusmenu-glib/Makefile.am20
-rw-r--r--libdbusmenu-glib/client.c83
-rw-r--r--libdbusmenu-glib/dbus-menu.xml7
-rw-r--r--libdbusmenu-glib/menuitem-marshal.list3
-rw-r--r--libdbusmenu-glib/menuitem.c324
-rw-r--r--libdbusmenu-glib/menuitem.h51
-rw-r--r--libdbusmenu-glib/server.c195
-rw-r--r--tests/Makefile.am38
-rw-r--r--tests/glib-server-nomenu.c1
-rw-r--r--tests/test-glib-properties-client.c175
-rw-r--r--tests/test-glib-properties-server.c85
-rw-r--r--tests/test-glib-properties.h95
14 files changed, 1025 insertions, 59 deletions
diff --git a/.bzrignore b/.bzrignore
index 36595be..8c4e180 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -28,3 +28,8 @@ libdbusmenu_glib_la-server-marshal.lo
glib-server-nomenu
test-glib-layout-server
test-glib-layout-client
+menuitem-marshal.c
+menuitem-marshal.h
+libdbusmenu_glib_la-menuitem-marshal.lo
+test-glib-properties-client
+test-glib-properties-server
diff --git a/Makefile.am b/Makefile.am
index 6f3f6f1..2e22cf9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = libdbusmenu-glib libdbusmenu-gtk libdbusmenu-qt tests
+SUBDIRS = libdbusmenu-glib libdbusmenu-gtk libdbusmenu-qt tests po
diff --git a/libdbusmenu-glib/Makefile.am b/libdbusmenu-glib/Makefile.am
index b273555..e74b9ab 100644
--- a/libdbusmenu-glib/Makefile.am
+++ b/libdbusmenu-glib/Makefile.am
@@ -1,7 +1,9 @@
EXTRA_DIST = \
dbusmenu-glib.pc.in \
- dbus-menu.xml
+ dbus-menu.xml \
+ menuitem-marshal.list \
+ server-marshal.list
lib_LTLIBRARIES = \
libdbusmenu-glib.la
@@ -18,6 +20,8 @@ libdbusmenu_glib_la_SOURCES = \
dbusmenu-client.h \
menuitem.h \
menuitem.c \
+ menuitem-marshal.h \
+ menuitem-marshal.c \
server.h \
server.c \
server-marshal.h \
@@ -31,7 +35,7 @@ libdbusmenu_glib_la_LDFLAGS = \
-export-symbols-regex "^[^_].*"
libdbusmenu_glib_la_CFLAGS = \
- $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror -DG_DISABLE_DEPRECATED -DG_LOG_DOMAIN="\"LIBDBUSMENU-GLIB\""
libdbusmenu_glib_la_LIBADD = \
$(DBUSMENUGLIB_LIBS)
@@ -42,6 +46,8 @@ pkgconfigdir = $(libdir)/pkgconfig
BUILT_SOURCES = \
dbusmenu-client.h \
dbusmenu-server.h \
+ menuitem-marshal.h \
+ menuitem-marshal.c \
server-marshal.h \
server-marshal.c
@@ -69,3 +75,13 @@ server-marshal.c: $(srcdir)/server-marshal.list
--prefix=_dbusmenu_server_marshal $(srcdir)/server-marshal.list \
> server-marshal.c
+menuitem-marshal.h: $(srcdir)/menuitem-marshal.list
+ glib-genmarshal --header \
+ --prefix=_dbusmenu_menuitem_marshal $(srcdir)/menuitem-marshal.list \
+ > menuitem-marshal.h
+
+menuitem-marshal.c: $(srcdir)/menuitem-marshal.list
+ glib-genmarshal --body \
+ --prefix=_dbusmenu_menuitem_marshal $(srcdir)/menuitem-marshal.list \
+ > menuitem-marshal.c
+
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
index b0b1157..6094eca 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -35,6 +35,7 @@ License version 3 and version 2.1 along with this program. If not, see
#include "client.h"
#include "dbusmenu-client.h"
+#include "server-marshal.h"
/* Properties */
enum {
@@ -77,12 +78,15 @@ static void set_property (GObject * obj, guint id, const GValue * value, GParamS
static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
/* Private Funcs */
static void layout_update (DBusGProxy * proxy, DbusmenuClient * client);
+static void id_prop_update (DBusGProxy * proxy, guint id, gchar * property, gchar * value, DbusmenuClient * client);
+static void id_update (DBusGProxy * proxy, guint id, DbusmenuClient * client);
static void build_proxies (DbusmenuClient * client);
static guint parse_node_get_id (xmlNodePtr node);
-static DbusmenuMenuitem * parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent);
+static DbusmenuMenuitem * parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy);
static void parse_layout (DbusmenuClient * client, const gchar * layout);
static void update_layout_cb (DBusGProxy * proxy, DBusGProxyCall * call, void * data);
static void update_layout (DbusmenuClient * client);
+static void menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data);
/* Build a type */
G_DEFINE_TYPE (DbusmenuClient, dbusmenu_client, G_TYPE_OBJECT);
@@ -238,6 +242,36 @@ layout_update (DBusGProxy * proxy, DbusmenuClient * client)
return;
}
+/* Signal from the server that a property has changed
+ on one of our menuitems */
+static void
+id_prop_update (DBusGProxy * proxy, guint id, gchar * property, gchar * value, DbusmenuClient * client)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ g_return_if_fail(priv->root != NULL);
+
+ DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
+ g_return_if_fail(menuitem != NULL);
+
+ dbusmenu_menuitem_property_set(menuitem, property, value);
+ return;
+}
+
+/* Oh, lots of updates now. That silly server, they want
+ to change all kinds of stuff! */
+static void
+id_update (DBusGProxy * proxy, guint id, DbusmenuClient * client)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ g_return_if_fail(priv->root != NULL);
+
+ DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
+ g_return_if_fail(menuitem != NULL);
+
+ org_freedesktop_dbusmenu_get_properties_async(proxy, id, menuitem_get_properties_cb, menuitem);
+ return;
+}
+
/* When we have a name and an object, build the two proxies and get the
first version of the layout */
static void
@@ -281,6 +315,13 @@ build_proxies (DbusmenuClient * client)
dbus_g_proxy_add_signal(priv->menuproxy, "LayoutUpdate", G_TYPE_INVALID);
dbus_g_proxy_connect_signal(priv->menuproxy, "LayoutUpdate", G_CALLBACK(layout_update), client, NULL);
+ dbus_g_object_register_marshaller(_dbusmenu_server_marshal_VOID__UINT_STRING_STRING, G_TYPE_NONE, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(priv->menuproxy, "IdPropUpdate", G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal(priv->menuproxy, "IdPropUpdate", G_CALLBACK(id_prop_update), client, NULL);
+
+ dbus_g_proxy_add_signal(priv->menuproxy, "IdUpdate", G_TYPE_UINT, G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal(priv->menuproxy, "IdUpdate", G_CALLBACK(id_update), client, NULL);
+
return;
}
@@ -301,7 +342,7 @@ parse_node_get_id (xmlNodePtr node)
if (g_strcmp0((gchar *)attrib->name, "id") == 0) {
if (attrib->children != NULL) {
guint id = (guint)g_ascii_strtoull((gchar *)attrib->children->content, NULL, 10);
- g_debug ("Found ID: %d", id);
+ /* g_debug ("Found ID: %d", id); */
return id;
}
break;
@@ -312,13 +353,35 @@ parse_node_get_id (xmlNodePtr node)
return 0;
}
+/* A small helper that calls _property_set on each hash table
+ entry in the properties hash. */
+static void
+get_properties_helper (gpointer key, gpointer value, gpointer data)
+{
+ dbusmenu_menuitem_property_set((DbusmenuMenuitem *)data, (gchar *)key, (gchar *)value);
+ return;
+}
+
+/* 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.
+ This isn't the most efficient way. We can optimize this by
+ somehow removing the foreach. But that is for later. */
+static void
+menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+{
+ g_hash_table_foreach(properties, get_properties_helper, data);
+ g_hash_table_destroy(properties);
+ return;
+}
+
/* Parse recursively through the XML and make it into
objects as need be */
static DbusmenuMenuitem *
-parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent)
+parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy)
{
guint id = parse_node_get_id(node);
- g_debug("Looking at node with id: %d", id);
+ /* g_debug("Looking at node with id: %d", id); */
if (item == NULL || dbusmenu_menuitem_get_id(item) != id || id == 0) {
if (item != NULL) {
if (parent != NULL) {
@@ -334,6 +397,8 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa
/* Build a new item */
item = dbusmenu_menuitem_new_with_id(id);
+ /* Get the properties queued up for this item */
+ org_freedesktop_dbusmenu_get_properties_async(proxy, id, menuitem_get_properties_cb, item);
}
xmlNodePtr children;
@@ -341,7 +406,7 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa
GList * oldchildren = dbusmenu_menuitem_take_children(item);
for (children = node->children, position = 0; children != NULL; children = children->next, position++) {
- g_debug("Looking at child: %d", position);
+ /* g_debug("Looking at child: %d", position); */
guint childid = parse_node_get_id(children);
DbusmenuMenuitem * childmi = NULL;
@@ -355,7 +420,7 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa
}
}
- childmi = parse_layout_xml(children, childmi, item);
+ childmi = parse_layout_xml(children, childmi, item, proxy);
dbusmenu_menuitem_child_add_position(item, childmi, position);
}
@@ -382,7 +447,7 @@ parse_layout (DbusmenuClient * client, const gchar * layout)
xmlNodePtr root = xmlDocGetRootElement(xmldoc);
- priv->root = parse_layout_xml(root, priv->root, NULL);
+ priv->root = parse_layout_xml(root, 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);
}
@@ -409,11 +474,11 @@ update_layout_cb (DBusGProxy * proxy, DBusGProxyCall * call, void * data)
}
const gchar * xml = g_value_get_string(&value);
- g_debug("Got layout string: %s", xml);
+ /* g_debug("Got layout string: %s", xml); */
parse_layout(client, xml);
priv->layoutcall = NULL;
- g_debug("Root is now: 0x%X", (unsigned int)priv->root);
+ /* g_debug("Root is now: 0x%X", (unsigned int)priv->root); */
g_signal_emit(G_OBJECT(client), signals[LAYOUT_UPDATED], 0, TRUE);
return;
diff --git a/libdbusmenu-glib/dbus-menu.xml b/libdbusmenu-glib/dbus-menu.xml
index 7c41ac2..51c529b 100644
--- a/libdbusmenu-glib/dbus-menu.xml
+++ b/libdbusmenu-glib/dbus-menu.xml
@@ -41,16 +41,11 @@ License version 3 and version 2.1 along with this program. If not, see
</method>
<method name="GetProperties">
<arg type="u" name="id" direction="in" />
- <arg type="as" name="property" direction="in" />
- <arg type="a(ss)" name="value" direction="out" />
+ <arg type="a{ss}" name="properties" direction="out" />
</method>
<method name="Call">
<arg type="u" name="id" direction="in" />
</method>
- <method name="ListProperties">
- <arg type="u" name="id" direction="in" />
- <arg type="as" name="properties" direction="out" />
- </method>
<!-- Signals -->
<signal name="IdPropUpdate">
diff --git a/libdbusmenu-glib/menuitem-marshal.list b/libdbusmenu-glib/menuitem-marshal.list
new file mode 100644
index 0000000..fc0318f
--- /dev/null
+++ b/libdbusmenu-glib/menuitem-marshal.list
@@ -0,0 +1,3 @@
+VOID: STRING, STRING
+VOID: OBJECT
+VOID: VOID
diff --git a/libdbusmenu-glib/menuitem.c b/libdbusmenu-glib/menuitem.c
index 0894d2c..9506cad 100644
--- a/libdbusmenu-glib/menuitem.c
+++ b/libdbusmenu-glib/menuitem.c
@@ -30,6 +30,7 @@ License version 3 and version 2.1 along with this program. If not, see
#include "config.h"
#endif
#include "menuitem.h"
+#include "menuitem-marshal.h"
/* Private */
/**
@@ -37,6 +38,7 @@ License version 3 and version 2.1 along with this program. If not, see
@id: The ID of this menu item
@children: A list of #DbusmenuMenuitem objects that are
children to this one.
+ @properties: All of the properties on this menu item.
These are the little secrets that we don't want getting
out of data that we have. They can still be gotten using
@@ -47,8 +49,20 @@ struct _DbusmenuMenuitemPrivate
{
guint id;
GList * children;
+ GHashTable * properties;
};
+/* Signals */
+enum {
+ PROPERTY_CHANGED,
+ ITEM_ACTIVATED,
+ CHILD_ADDED,
+ CHILD_REMOVED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
/* Properties */
enum {
PROP_0,
@@ -81,6 +95,69 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass)
object_class->set_property = set_property;
object_class->get_property = get_property;
+ /**
+ DbusmenuMenuitem::property-changed:
+ @arg0: The #DbusmenuMenuitem object.
+ @arg1: The name of the property that changed
+ @arg2: The new value of the property
+
+ Emitted everytime a property on a menuitem is either
+ updated or added.
+ */
+ signals[PROPERTY_CHANGED] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(DbusmenuMenuitemClass, property_changed),
+ NULL, NULL,
+ _dbusmenu_menuitem_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+ /**
+ DbusmenuMenuitem::item-activated:
+ @arg0: The #DbusmenuMenuitem object.
+
+ Emitted on the objects on the server side when
+ they are signaled on the client side.
+ */
+ signals[ITEM_ACTIVATED] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(DbusmenuMenuitemClass, item_activated),
+ NULL, NULL,
+ _dbusmenu_menuitem_marshal_VOID__VOID,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+ /**
+ DbusmenuMenuitem::child-added:
+ @arg0: The #DbusmenuMenuitem which is the parent.
+ @arg1: The #DbusmenuMenuitem which is the child.
+
+ Signaled when the child menuitem has been added to
+ the parent.
+ */
+ signals[CHILD_ADDED] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_added),
+ NULL, NULL,
+ _dbusmenu_menuitem_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 2, G_TYPE_OBJECT);
+ /**
+ DbusmenuMenuitem::child-removed:
+ @arg0: The #DbusmenuMenuitem which was the parent.
+ @arg1: The #DbusmenuMenuitem which was the child.
+
+ Signaled when the child menuitem has been requested to
+ be removed from the parent. This signal is called when
+ it has been removed from the list but not yet had
+ #g_object_unref called on it.
+ */
+ signals[CHILD_REMOVED] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_removed),
+ NULL, NULL,
+ _dbusmenu_menuitem_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 2, G_TYPE_OBJECT);
+
g_object_class_install_property (object_class, PROP_ID,
g_param_spec_uint("id", "ID for the menu item",
"This is a unique indentifier for the menu item.",
@@ -99,6 +176,8 @@ dbusmenu_menuitem_init (DbusmenuMenuitem *self)
priv->id = 0;
priv->children = NULL;
+
+ priv->properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
return;
}
@@ -114,6 +193,12 @@ dbusmenu_menuitem_dispose (GObject *object)
static void
dbusmenu_menuitem_finalize (GObject *object)
{
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(object);
+
+ if (priv->properties != NULL) {
+ g_hash_table_destroy(priv->properties);
+ priv->properties = NULL;
+ }
G_OBJECT_CLASS (dbusmenu_menuitem_parent_class)->finalize (object);
return;
@@ -181,7 +266,7 @@ DbusmenuMenuitem *
dbusmenu_menuitem_new_with_id (guint id)
{
DbusmenuMenuitem * mi = g_object_new(DBUSMENU_TYPE_MENUITEM, "id", id, NULL);
- g_debug("New Menuitem id %d goal id %d", dbusmenu_menuitem_get_id(mi), id);
+ /* g_debug("New Menuitem id %d goal id %d", dbusmenu_menuitem_get_id(mi), id); */
return mi;
}
@@ -223,6 +308,15 @@ dbusmenu_menuitem_get_children (DbusmenuMenuitem * mi)
return priv->children;
}
+/* For all the taken children we need to signal
+ that they were removed */
+static void
+take_children_signal (gpointer data, gpointer user_data)
+{
+ g_signal_emit(G_OBJECT(user_data), signals[CHILD_REMOVED], 0, DBUSMENU_MENUITEM(data), TRUE);
+ return;
+}
+
/**
dbusmenu_menuitem_take_children:
@mi: The #DbusmenMenuitem to take the children from.
@@ -243,6 +337,7 @@ dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi)
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
GList * children = priv->children;
priv->children = NULL;
+ g_list_foreach(children, take_children_signal, mi);
return children;
}
@@ -294,6 +389,7 @@ dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
priv->children = g_list_append(priv->children, child);
+ g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, TRUE);
return TRUE;
}
@@ -316,6 +412,7 @@ dbusmenu_menuitem_child_delete (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
priv->children = g_list_remove(priv->children, child);
+ g_signal_emit(G_OBJECT(mi), signals[CHILD_REMOVED], 0, child, TRUE);
return TRUE;
}
@@ -339,6 +436,7 @@ dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem *
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
priv->children = g_list_insert(priv->children, child, position);
+ g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, TRUE);
return TRUE;
}
@@ -371,25 +469,192 @@ dbusmenu_menuitem_child_find (DbusmenuMenuitem * mi, guint id)
return NULL;
}
+typedef struct {
+ DbusmenuMenuitem * mi;
+ guint id;
+} find_id_t;
+
+/* Basically the heart of the find_id that matches the
+ API of GFunc. Unfortunately, this goes through all the
+ children, but it rejects them quickly. */
+static void
+find_id_helper (gpointer in_mi, gpointer in_find_id)
+{
+ DbusmenuMenuitem * mi = (DbusmenuMenuitem *)in_mi;
+ find_id_t * find_id = (find_id_t *)in_find_id;
+
+ if (find_id->mi != NULL) return;
+ if (find_id->id == dbusmenu_menuitem_get_id(mi)) {
+ find_id->mi = mi;
+ return;
+ }
+
+ g_list_foreach(dbusmenu_menuitem_get_children(mi), find_id_helper, in_find_id);
+ return;
+}
+
+/**
+ dbusmenu_menuitem_find_id:
+ @mi: #DbusmenuMenuitem at the top of the tree to search
+ @id: ID of the #DbusmenuMenuitem to search for
+
+ This function searchs the whole tree of children that
+ are attached to @mi. This could be quite a few nodes, all
+ the way down the tree. It is a depth first search.
+
+ Return value: The #DbusmenuMenuitem with the ID of @id
+ or #NULL if there isn't such a menu item in the tree
+ represented by @mi.
+*/
+DbusmenuMenuitem *
+dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, guint id)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
+ find_id_t find_id = {mi: NULL, id: id};
+ find_id_helper(mi, &find_id);
+ return find_id.mi;
+}
+
+/**
+ dbusmenu_menuitem_property_set:
+ @mi: The #DbusmenuMenuitem to set the property on.
+ @property: Name of the property to set.
+ @value: The value of the property.
+
+ Takes the pair of @property and @value and places them as a
+ property on @mi. If a property already exists by that name,
+ then the value is set to the new value. If not, the property
+ is added. If the value is changed or the property was previously
+ unset then the signal #DbusmenuMenuitem::prop-changed will be
+ emitted by this function.
+
+ Return value: A boolean representing if the property value was set.
+*/
gboolean
dbusmenu_menuitem_property_set (DbusmenuMenuitem * mi, const gchar * property, const gchar * value)
{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
+ g_return_val_if_fail(property != NULL, FALSE);
+
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
+ /* g_debug("Setting a property. ID: %d Prop: %s Value: %s", priv->id, property, value); */
+
+ gpointer lookup = g_hash_table_lookup(priv->properties, property);
+ if (g_strcmp0((gchar *)lookup, value) == 0) {
+ /* The value is the same as the value currently in the
+ table so we don't really care. Just say everything's okay */
+ return TRUE;
+ }
+
+ gchar * lprop = g_strdup(property);
+ gchar * lval = g_strdup(value);
+
+ g_hash_table_insert(priv->properties, lprop, lval);
+ g_signal_emit(G_OBJECT(mi), signals[PROPERTY_CHANGED], 0, property, value, TRUE);
- return FALSE;
+ return TRUE;
}
+/**
+ dbusmenu_menuitem_property_get:
+ @mi: The #DbusmenuMenuitem to look for the property on.
+ @property: The property to grab.
+
+ Look up a property on @mi and return the value of it if
+ it exits. #NULL will be returned if the property doesn't
+ exist.
+
+ Return value: A string with the value of the property
+ that shouldn't be free'd. Or #NULL if the property
+ is not set.
+*/
const gchar *
dbusmenu_menuitem_property_get (DbusmenuMenuitem * mi, const gchar * property)
{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
+ g_return_val_if_fail(property != NULL, NULL);
- return NULL;
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
+
+ return (const gchar *)g_hash_table_lookup(priv->properties, property);
}
+/**
+ dbusmenu_menuitem_property_exit:
+ @mi: The #DbusmenuMenuitem to look for the property on.
+ @property: The property to look for.
+
+ Checkes to see if a particular property exists on @mi and
+ returns #TRUE if so.
+
+ Return value: A boolean checking to see if the property is available
+*/
gboolean
dbusmenu_menuitem_property_exist (DbusmenuMenuitem * mi, const gchar * property)
{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
+ g_return_val_if_fail(property != NULL, FALSE);
+
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
+
+ gpointer value = g_hash_table_lookup(priv->properties, property);
- return FALSE;
+ return value != NULL;
+}
+
+/**
+ dbusmenu_menuitem_properties_list:
+ @mi: #DbusmenuMenuitem to list the properties on
+
+ This functiong gets a list of the names of all the properties
+ that are set on this menu item. This data on the list is owned
+ by the menuitem but the list is not and should be freed using
+ g_list_free() when the calling function is done with it.
+
+ Return value: A list of strings or NULL if there are none.
+*/
+GList *
+dbusmenu_menuitem_properties_list (DbusmenuMenuitem * mi)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
+
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
+ return g_hash_table_get_keys(priv->properties);
+}
+
+static void
+copy_helper (gpointer in_key, gpointer in_value, gpointer in_data)
+{
+ GHashTable * table = (GHashTable *)in_data;
+ g_hash_table_insert(table, in_key, in_value);
+ return;
+}
+
+/**
+ dbusmenu_menuitem_properties_copy:
+ @mi: #DbusmenuMenuitem that we're interested in the properties of
+
+ This function takes the properties of a #DbusmenuMenuitem
+ and puts them into a #GHashTable that is referenced by the
+ key of a string and has the value of a string. The hash
+ table may not have any entries if there aren't any or there
+ is an error in processing. It is the caller's responsibility
+ to destroy the created #GHashTable.
+
+ Return value: A brand new #GHashTable that contains all of the
+ properties that are on this #DbusmenuMenuitem @mi.
+*/
+GHashTable *
+dbusmenu_menuitem_properties_copy (DbusmenuMenuitem * mi)
+{
+ GHashTable * ret = g_hash_table_new(g_str_hash, g_str_equal);
+
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), ret);
+
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
+ g_hash_table_foreach(priv->properties, copy_helper, ret);
+
+ return ret;
}
/**
@@ -422,3 +687,54 @@ dbusmenu_menuitem_buildxml (DbusmenuMenuitem * mi, GPtrArray * array)
return;
}
+typedef struct {
+ void (*func) (DbusmenuMenuitem * mi, gpointer data);
+ gpointer data;
+} foreach_struct_t;
+
+static void
+foreach_helper (gpointer data, gpointer user_data)
+{
+ dbusmenu_menuitem_foreach(DBUSMENU_MENUITEM(data), ((foreach_struct_t *)user_data)->func, ((foreach_struct_t *)user_data)->data);
+ return;
+}
+
+/**
+ dbusmenu_menuitem_foreach:
+ @mi: The #DbusmenItem to start from
+ @func: Function to call on every node in the tree
+ @data: User data to pass to the function
+
+ This calls the function @func on this menu item and all
+ of the children of this item. And their children. And
+ their children. And... you get the point. It will get
+ called on the whole tree.
+*/
+void
+dbusmenu_menuitem_foreach (DbusmenuMenuitem * mi, void (*func) (DbusmenuMenuitem * mi, gpointer data), gpointer data)
+{
+ g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+ g_return_if_fail(func != NULL);
+
+ func(mi, data);
+ GList * children = dbusmenu_menuitem_get_children(mi);
+ foreach_struct_t foreach_data = {func: func, data: data};
+ g_list_foreach(children, foreach_helper, &foreach_data);
+ return;
+}
+
+/**
+ dbusmenu_menuitem_activate:
+ @mi: The #DbusmenuMenuitem to send the signal on.
+
+ Emits the #DbusmenuMenuitem::item-activate signal on this
+ menu item. Called by server objects when they get the
+ appropriate DBus signals from the client.
+*/
+void
+dbusmenu_menuitem_activate (DbusmenuMenuitem * mi)
+{
+ g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+ g_signal_emit(G_OBJECT(mi), signals[ITEM_ACTIVATED], 0, TRUE);
+ return;
+}
diff --git a/libdbusmenu-glib/menuitem.h b/libdbusmenu-glib/menuitem.h
index a604e7a..3f3e97c 100644
--- a/libdbusmenu-glib/menuitem.h
+++ b/libdbusmenu-glib/menuitem.h
@@ -44,11 +44,31 @@ G_BEGIN_DECLS
#define DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED "property-changed"
#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated"
+#define DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED "child-added"
+#define DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED "child-removed"
+
+/**
+ DbusmenuMenuitem:
+
+ This is the #GObject based object that represents a menu
+ item. It gets created the same on both the client and
+ the server side and libdbusmenu-glib does the work of making
+ this object model appear on both sides of DBus. Simple
+ really, though through updates and people coming on and off
+ the bus it can lead to lots of fun complex scenarios.
+*/
+typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
+struct _DbusmenuMenuitem
+{
+ GObject parent;
+};
/**
DbusmenuMenuitemClass:
@property_changed: Slot for #DbusmenuMenuitem::property-changed.
@item_activated: Slot for #DbusmenuMenuitem::item-activated.
+ @child_added: Slot for #DbusmenuMenuitem::child-added.
+ @child_removed: Slot for #DbusmenuMenuitem::child-removed.
@buildxml: Virtual function that appends the strings required
to represent this menu item in the menu XML file.
@reserved1: Reserved for future use.
@@ -62,8 +82,10 @@ struct _DbusmenuMenuitemClass
GObjectClass parent_class;
/* Signals */
- void (*property_changed) (gchar * property);
+ void (*property_changed) (gchar * property, gchar * value);
void (*item_activated) (void);
+ void (*child_added) (DbusmenuMenuitem * child);
+ void (*child_removed) (DbusmenuMenuitem * child);
/* Virtual functions */
void (*buildxml) (GPtrArray * stringarray);
@@ -74,42 +96,31 @@ struct _DbusmenuMenuitemClass
void (*reserved4) (void);
};
-/**
- DbusmenuMenuitem:
-
- This is the #GObject based object that represents a menu
- item. It gets created the same on both the client and
- the server side and libdbusmenu-glib does the work of making
- this object model appear on both sides of DBus. Simple
- really, though through updates and people coming on and off
- the bus it can lead to lots of fun complex scenarios.
-*/
-typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
-struct _DbusmenuMenuitem
-{
- GObject parent;
-};
-
GType dbusmenu_menuitem_get_type (void);
-DbusmenuMenuitem * dbusmenu_menuitem_new (void);
-DbusmenuMenuitem * dbusmenu_menuitem_new_with_id (guint id);
+DbusmenuMenuitem * dbusmenu_menuitem_new (void) G_GNUC_WARN_UNUSED_RESULT;
+DbusmenuMenuitem * dbusmenu_menuitem_new_with_id (guint id) G_GNUC_WARN_UNUSED_RESULT;
guint dbusmenu_menuitem_get_id (DbusmenuMenuitem * mi);
GList * dbusmenu_menuitem_get_children (DbusmenuMenuitem * mi);
-GList * dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi);
+GList * dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi) G_GNUC_WARN_UNUSED_RESULT;
guint dbusmenu_menuitem_get_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent);
gboolean dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child);
gboolean dbusmenu_menuitem_child_delete (DbusmenuMenuitem * mi, DbusmenuMenuitem * child);
gboolean dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position);
DbusmenuMenuitem * dbusmenu_menuitem_child_find (DbusmenuMenuitem * mi, guint id);
+DbusmenuMenuitem * dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, guint id);
gboolean dbusmenu_menuitem_property_set (DbusmenuMenuitem * mi, const gchar * property, const gchar * value);
const gchar * dbusmenu_menuitem_property_get (DbusmenuMenuitem * mi, const gchar * property);
gboolean dbusmenu_menuitem_property_exist (DbusmenuMenuitem * mi, const gchar * property);
+GList * dbusmenu_menuitem_properties_list (DbusmenuMenuitem * mi) G_GNUC_WARN_UNUSED_RESULT;
+GHashTable * dbusmenu_menuitem_properties_copy (DbusmenuMenuitem * mi);
void dbusmenu_menuitem_buildxml (DbusmenuMenuitem * mi, GPtrArray * array);
+void dbusmenu_menuitem_foreach (DbusmenuMenuitem * mi, void (*func) (DbusmenuMenuitem * mi, gpointer data), gpointer data);
+void dbusmenu_menuitem_activate (DbusmenuMenuitem * mi);
/**
SECTION:menuitem
diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c
index 3db6db0..562b2d7 100644
--- a/libdbusmenu-glib/server.c
+++ b/libdbusmenu-glib/server.c
@@ -34,10 +34,9 @@ License version 3 and version 2.1 along with this program. If not, see
#include "server-marshal.h"
/* DBus Prototypes */
-static gboolean _dbusmenu_server_get_property (void);
-static gboolean _dbusmenu_server_get_properties (void);
-static gboolean _dbusmenu_server_call (void);
-static gboolean _dbusmenu_server_list_properties (void);
+static gboolean _dbusmenu_server_get_property (DbusmenuServer * server, guint id, gchar * property, gchar ** value, GError ** error);
+static gboolean _dbusmenu_server_get_properties (DbusmenuServer * server, guint id, GHashTable ** dict, GError ** error);
+static gboolean _dbusmenu_server_call (DbusmenuServer * server, guint id, GError ** error);
#include "dbusmenu-server.h"
@@ -71,6 +70,14 @@ enum {
PROP_LAYOUT
};
+/* Errors */
+enum {
+ INVALID_MENUITEM_ID,
+ INVALID_PROPERTY_NAME,
+ UNKNOWN_DBUS_ERROR,
+ LAST_ERROR
+};
+
/* Prototype */
static void dbusmenu_server_class_init (DbusmenuServerClass *class);
static void dbusmenu_server_init (DbusmenuServer *self);
@@ -78,6 +85,12 @@ static void dbusmenu_server_dispose (GObject *object);
static void dbusmenu_server_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 menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, gchar * value, DbusmenuServer * server);
+static void menuitem_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, DbusmenuServer * server);
+static void menuitem_child_removed (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, DbusmenuServer * server);
+static void menuitem_signals_create (DbusmenuMenuitem * mi, gpointer data);
+static void menuitem_signals_remove (DbusmenuMenuitem * mi, gpointer data);
+static GQuark error_quark (void);
G_DEFINE_TYPE (DbusmenuServer, dbusmenu_server, G_TYPE_OBJECT);
@@ -93,6 +106,16 @@ dbusmenu_server_class_init (DbusmenuServerClass *class)
object_class->set_property = set_property;
object_class->get_property = get_property;
+ /**
+ DbusmenuServer::id-prop-update:
+ @arg0: The #DbusmenuServer emitting the signal.
+ @arg1: The ID of the #DbusmenuMenuitem changing a property.
+ @arg2: The property being changed.
+ @arg3: The value of the property being changed.
+
+ This signal is emitted when a menuitem updates or
+ adds a property.
+ */
signals[ID_PROP_UPDATE] = g_signal_new(DBUSMENU_SERVER_SIGNAL_ID_PROP_UPDATE,
G_TYPE_FROM_CLASS(class),
G_SIGNAL_RUN_LAST,
@@ -100,6 +123,15 @@ dbusmenu_server_class_init (DbusmenuServerClass *class)
NULL, NULL,
_dbusmenu_server_marshal_VOID__UINT_STRING_STRING,
G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
+ /**
+ DbusmenuServer::id-update:
+ @arg0: The #DbusmenuServer emitting the signal.
+ @arg1: ID of the #DbusmenuMenuitem changing.
+
+ The purpose of this signal is to show major change in
+ a menuitem to the point that #DbusmenuServer::id-prop-update
+ seems a little insubstantive.
+ */
signals[ID_UPDATE] = g_signal_new(DBUSMENU_SERVER_SIGNAL_ID_UPDATE,
G_TYPE_FROM_CLASS(class),
G_SIGNAL_RUN_LAST,
@@ -107,6 +139,13 @@ dbusmenu_server_class_init (DbusmenuServerClass *class)
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
+ /**
+ DbusmenuServer::layout-update:
+ @arg0: The #DbusmenuServer emitting the signal.
+
+ This signal is emitted any time the layout of the
+ menuitems under this server is changed.
+ */
signals[LAYOUT_UPDATE] = g_signal_new(DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATE,
G_TYPE_FROM_CLASS(class),
G_SIGNAL_RUN_LAST,
@@ -151,6 +190,13 @@ dbusmenu_server_init (DbusmenuServer *self)
static void
dbusmenu_server_dispose (GObject *object)
{
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(object);
+
+ if (priv->root != NULL) {
+ dbusmenu_menuitem_foreach(priv->root, menuitem_signals_remove, object);
+ g_object_unref(priv->root);
+ }
+
G_OBJECT_CLASS (dbusmenu_server_parent_class)->dispose (object);
return;
}
@@ -178,12 +224,14 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
break;
case PROP_ROOT_NODE:
if (priv->root != NULL) {
+ dbusmenu_menuitem_foreach(priv->root, menuitem_signals_remove, obj);
g_object_unref(G_OBJECT(priv->root));
priv->root = NULL;
}
priv->root = DBUSMENU_MENUITEM(g_value_get_object(value));
if (priv->root != NULL) {
g_object_ref(G_OBJECT(priv->root));
+ dbusmenu_menuitem_foreach(priv->root, menuitem_signals_create, obj);
} else {
g_debug("Setting root node to NULL");
}
@@ -226,7 +274,7 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
case PROP_LAYOUT: {
GPtrArray * xmlarray = g_ptr_array_new();
if (priv->root == NULL) {
- g_debug("Getting layout without root node!");
+ /* g_debug("Getting layout without root node!"); */
g_ptr_array_add(xmlarray, g_strdup("<menu />"));
} else {
dbusmenu_menuitem_buildxml(priv->root, xmlarray);
@@ -236,7 +284,7 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
/* build string */
gchar * finalstring = g_strjoinv("", (gchar **)xmlarray->pdata);
g_value_take_string(value, finalstring);
- g_debug("Final string: %s", finalstring);
+ /* g_debug("Final string: %s", finalstring); */
g_ptr_array_foreach(xmlarray, xmlarray_foreach_free, NULL);
g_ptr_array_free(xmlarray, TRUE);
@@ -250,32 +298,149 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
return;
}
-/* DBus interface */
-static gboolean
-_dbusmenu_server_get_property (void)
+static void
+menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, gchar * value, DbusmenuServer * server)
{
+ g_signal_emit(G_OBJECT(server), signals[ID_PROP_UPDATE], 0, dbusmenu_menuitem_get_id(mi), property, value, TRUE);
+ return;
+}
- return TRUE;
+static void
+menuitem_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, DbusmenuServer * server)
+{
+ menuitem_signals_create(child, server);
+ /* TODO: We probably need to group the layout update signals to make the number more reasonble. */
+ g_signal_emit(G_OBJECT(server), signals[LAYOUT_UPDATE], 0, TRUE);
+ return;
}
-static gboolean
-_dbusmenu_server_get_properties (void)
+static void
+menuitem_child_removed (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, DbusmenuServer * server)
{
+ menuitem_signals_remove(child, server);
+ /* TODO: We probably need to group the layout update signals to make the number more reasonble. */
+ g_signal_emit(G_OBJECT(server), signals[LAYOUT_UPDATE], 0, TRUE);
+ return;
+}
+
+/* Connects all the signals that we're interested in
+ coming from a menuitem */
+static void
+menuitem_signals_create (DbusmenuMenuitem * mi, gpointer data)
+{
+ g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(menuitem_child_added), data);
+ g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(menuitem_child_removed), data);
+ g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menuitem_property_changed), data);
+ return;
+}
+
+/* Removes all the signals that we're interested in
+ coming from a menuitem */
+static void
+menuitem_signals_remove (DbusmenuMenuitem * mi, gpointer data)
+{
+ g_signal_handlers_disconnect_by_func(G_OBJECT(mi), G_CALLBACK(menuitem_child_added), data);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(mi), G_CALLBACK(menuitem_child_removed), data);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(mi), G_CALLBACK(menuitem_property_changed), data);
+ return;
+}
+
+static GQuark
+error_quark (void)
+{
+ static GQuark quark = 0;
+ if (quark == 0) {
+ quark = g_quark_from_static_string (G_LOG_DOMAIN);
+ }
+ return quark;
+}
+
+/* DBus interface */
+static gboolean
+_dbusmenu_server_get_property (DbusmenuServer * server, guint id, gchar * property, gchar ** value, GError ** error)
+{
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
+
+ if (mi == NULL) {
+ if (error != NULL) {
+ g_set_error(error,
+ error_quark(),
+ INVALID_MENUITEM_ID,
+ "The ID supplied %d does not refer to a menu item we have",
+ id);
+ }
+ return FALSE;
+ }
+
+ const gchar * prop = dbusmenu_menuitem_property_get(mi, property);
+ if (prop == NULL) {
+ if (error != NULL) {
+ g_set_error(error,
+ error_quark(),
+ INVALID_PROPERTY_NAME,
+ "The property '%s' does not exist on menuitem with ID of %d",
+ property,
+ id);
+ }
+ return FALSE;
+ }
+
+ if (value == NULL) {
+ if (error != NULL) {
+ g_set_error(error,
+ error_quark(),
+ UNKNOWN_DBUS_ERROR,
+ "Uhm, yeah. We didn't get anywhere to put the value, that's really weird. Seems impossible really.");
+ }
+ return FALSE;
+ }
+
+ *value = g_strdup(prop);
return TRUE;
}
static gboolean
-_dbusmenu_server_call (void)
+_dbusmenu_server_get_properties (DbusmenuServer * server, guint id, GHashTable ** dict, GError ** error)
{
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
+
+ if (mi == NULL) {
+ if (error != NULL) {
+ g_set_error(error,
+ error_quark(),
+ INVALID_MENUITEM_ID,
+ "The ID supplied %d does not refer to a menu item we have",
+ id);
+ }
+ return FALSE;
+ }
+
+ *dict = dbusmenu_menuitem_properties_copy(mi);
return TRUE;
}
static gboolean
-_dbusmenu_server_list_properties (void)
+_dbusmenu_server_call (DbusmenuServer * server, guint id, GError ** error)
{
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
+
+ if (mi == NULL) {
+ if (error != NULL) {
+ g_set_error(error,
+ error_quark(),
+ INVALID_MENUITEM_ID,
+ "The ID supplied %d does not refer to a menu item we have",
+ id);
+ }
+ return FALSE;
+ }
+ dbusmenu_menuitem_activate(mi);
return TRUE;
}
@@ -320,7 +485,7 @@ dbusmenu_server_set_root (DbusmenuServer * self, DbusmenuMenuitem * root)
g_return_if_fail(DBUSMENU_IS_SERVER(self));
g_return_if_fail(DBUSMENU_IS_MENUITEM(root));
- g_debug("Setting root object: 0x%X", (unsigned int)root);
+ /* g_debug("Setting root object: 0x%X", (unsigned int)root); */
GValue rootvalue = {0};
g_value_init(&rootvalue, G_TYPE_OBJECT);
g_value_set_object(&rootvalue, root);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ca0bd77..1f21141 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,12 +2,14 @@ check: tests
DBUS_RUNNER=dbus-test-runner --dbus-config /usr/share/dbus-test-runner/session.conf
-tests: test-glib-layout
+tests: test-glib-layout test-glib-properties
libexec_PROGRAMS = \
glib-server-nomenu \
test-glib-layout-client \
- test-glib-layout-server
+ test-glib-layout-server \
+ test-glib-properties-client \
+ test-glib-properties-server
glib_server_nomenu_SOURCES = \
glib-server-nomenu.c
@@ -20,10 +22,13 @@ glib_server_nomenu_LDADD = \
../libdbusmenu-glib/libdbusmenu-glib.la \
$(DBUSMENUGLIB_LIBS)
+
+
test-glib-layout: test-glib-layout-client test-glib-layout-server
$(DBUS_RUNNER) --task ./test-glib-layout-client --task-name Client --task ./test-glib-layout-server --task-name Server --ignore-return
test_glib_layout_server_SOURCES = \
+ test-glib-layout.h \
test-glib-layout-server.c
test_glib_layout_server_CFLAGS = \
@@ -35,6 +40,7 @@ test_glib_layout_server_LDADD = \
$(DBUSMENUGLIB_LIBS)
test_glib_layout_client_SOURCES = \
+ test-glib-layout.h \
test-glib-layout-client.c
test_glib_layout_client_CFLAGS = \
@@ -47,6 +53,34 @@ test_glib_layout_client_LDADD = \
+test-glib-properties: test-glib-properties-client test-glib-properties-server
+ $(DBUS_RUNNER) --task ./test-glib-properties-client --task-name Client --task ./test-glib-properties-server --task-name Server --ignore-return
+
+test_glib_properties_server_SOURCES = \
+ test-glib-properties.h \
+ test-glib-properties-server.c
+
+test_glib_properties_server_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_glib_properties_server_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS)
+
+test_glib_properties_client_SOURCES = \
+ test-glib-properties.h \
+ test-glib-properties-client.c
+
+test_glib_properties_client_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_glib_properties_client_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS)
+
+
examplesdir = $(docdir)/examples/
diff --git a/tests/glib-server-nomenu.c b/tests/glib-server-nomenu.c
index e5ea159..fb2c61e 100644
--- a/tests/glib-server-nomenu.c
+++ b/tests/glib-server-nomenu.c
@@ -31,6 +31,7 @@ main (int argc, char ** argv)
DbusmenuServer * server = dbusmenu_server_new("/org/test");
DbusmenuMenuitem * menuitem = dbusmenu_menuitem_new();
+ dbusmenu_menuitem_property_set(menuitem, "test", "test");
dbusmenu_server_set_root(server, menuitem);
g_main_loop_run(g_main_loop_new(NULL, FALSE));
diff --git a/tests/test-glib-properties-client.c b/tests/test-glib-properties-client.c
new file mode 100644
index 0000000..4439788
--- /dev/null
+++ b/tests/test-glib-properties-client.c
@@ -0,0 +1,175 @@
+/*
+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-properties.h"
+
+static guint layouton = 0;
+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 (layout->id != dbusmenu_menuitem_get_id(mi)) {
+ g_debug("\tFailed as ID %d is not equal to %d", layout->id, dbusmenu_menuitem_get_id(mi));
+ return FALSE;
+ }
+
+ 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 != 0; 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 == 0) {
+ 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");
+ g_timeout_add (250, layout_verify_timer, client);
+ return;
+}
+
+static gboolean
+layout_verify_timer (gpointer data)
+{
+ 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);
+ }
+
+ layouton++;
+
+ if (layouts[layouton].id == 0) {
+ g_main_loop_quit(mainloop);
+ }
+
+ return FALSE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ /* Make sure the server starts up and all that */
+ g_usleep(500000);
+
+ DbusmenuClient * client = dbusmenu_client_new(":1.0", "/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 0;
+ }
+}
diff --git a/tests/test-glib-properties-server.c b/tests/test-glib-properties-server.c
new file mode 100644
index 0000000..477f951
--- /dev/null
+++ b/tests/test-glib-properties-server.c
@@ -0,0 +1,85 @@
+#include <glib.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-glib/server.h>
+
+#include "test-glib-properties.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 == 0) return NULL;
+
+ DbusmenuMenuitem * local = dbusmenu_menuitem_new_with_id(layout->id);
+ set_props(local, layout->properties);
+
+ if (layout->submenu != NULL) {
+ guint count;
+ for (count = 0; layout->submenu[count].id != 0; 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 == 0) {
+ g_main_loop_quit(mainloop);
+ return FALSE;
+ }
+ g_debug("Updating to Layout %d", layouton);
+
+ dbusmenu_server_set_root(server, layout2menuitem(&layouts[layouton]));
+ layouton++;
+
+ return TRUE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ g_debug("DBus ID: %s", dbus_connection_get_server_id(dbus_g_connection_get_connection(dbus_g_bus_get(DBUS_BUS_SESSION, NULL))));
+
+ 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_debug("Quiting");
+
+ return 0;
+}
+
diff --git a/tests/test-glib-properties.h b/tests/test-glib-properties.h
new file mode 100644
index 0000000..3ab7ee7
--- /dev/null
+++ b/tests/test-glib-properties.h
@@ -0,0 +1,95 @@
+
+#include <glib.h>
+
+typedef struct _proplayout_t proplayout_t;
+struct _proplayout_t {
+ guint 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: 0, 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: 0, 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: 0, 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: 0, properties: NULL, submenu: NULL}
+};
+
+proplayout_t layouts[] = {
+ {id: 1, properties: props1, submenu: NULL},
+ {id: 10, properties: props2, submenu: NULL},
+ {id: 20, properties: props3, submenu: NULL},
+ {id: 100, properties: props2, submenu: submenu_4_0},
+ {id: 0, properties: NULL, submenu: NULL}
+};
+