diff options
author | Ted Gould <ted@canonical.com> | 2009-06-05 15:08:56 -0500 |
---|---|---|
committer | Ted Gould <ted@canonical.com> | 2009-06-05 15:08:56 -0500 |
commit | ec5c382624da19a1a83d1e75d21f74778df01f38 (patch) | |
tree | ab9fcd33632bf90eb6374408a6d4bb2e4a9050e3 | |
parent | 3d0e0276fd7856831dcc845a24a252ad304b3bad (diff) | |
parent | 3c9734543b84ecd412ffd214ac0560b11858a00a (diff) | |
download | libdbusmenu-ec5c382624da19a1a83d1e75d21f74778df01f38.tar.gz libdbusmenu-ec5c382624da19a1a83d1e75d21f74778df01f38.tar.bz2 libdbusmenu-ec5c382624da19a1a83d1e75d21f74778df01f38.zip |
Merging in work on properties. Cody reviewed in merge request 6707.
-rw-r--r-- | .bzrignore | 5 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | libdbusmenu-glib/Makefile.am | 20 | ||||
-rw-r--r-- | libdbusmenu-glib/client.c | 83 | ||||
-rw-r--r-- | libdbusmenu-glib/dbus-menu.xml | 7 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-marshal.list | 3 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem.c | 324 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem.h | 51 | ||||
-rw-r--r-- | libdbusmenu-glib/server.c | 195 | ||||
-rw-r--r-- | tests/Makefile.am | 38 | ||||
-rw-r--r-- | tests/glib-server-nomenu.c | 1 | ||||
-rw-r--r-- | tests/test-glib-properties-client.c | 175 | ||||
-rw-r--r-- | tests/test-glib-properties-server.c | 85 | ||||
-rw-r--r-- | tests/test-glib-properties.h | 95 |
14 files changed, 1025 insertions, 59 deletions
@@ -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} +}; + |