← Back to team overview

ayatana-commits team mailing list archive

[Branch ~dbusmenu-team/dbusmenu/trunk] Rev 116: Adding support for accelerators.

 

Merge authors:
  Cody Russell (bratsche)
  Ted Gould (ted)
Related merge proposals:
  https://code.launchpad.net/~bratsche/dbusmenu/accel-keys/+merge/28104
  proposed by: Cody Russell (bratsche)
------------------------------------------------------------
revno: 116 [merge]
committer: Ted Gould <ted@xxxxxxxx>
branch nick: trunk
timestamp: Mon 2010-06-21 14:58:34 -0500
message:
  Adding support for accelerators.
added:
  tests/test-gtk-objects.c
  tests/test-gtk-objects.jpg
  tests/test-gtk-shortcut-client.c
  tests/test-gtk-shortcut-server.c
modified:
  .bzrignore
  libdbusmenu-glib/menuitem.h
  libdbusmenu-gtk/client.c
  libdbusmenu-gtk/client.h
  libdbusmenu-gtk/genericmenuitem.c
  libdbusmenu-gtk/menuitem.c
  libdbusmenu-gtk/menuitem.h
  tests/Makefile.am
  tests/run-xvfb.sh


--
lp:dbusmenu
https://code.launchpad.net/~dbusmenu-team/dbusmenu/trunk

Your team ayatana-commits is subscribed to branch lp:dbusmenu.
To unsubscribe from this branch go to https://code.launchpad.net/~dbusmenu-team/dbusmenu/trunk/+edit-subscription
=== modified file '.bzrignore'
--- .bzrignore	2010-06-08 18:12:32 +0000
+++ .bzrignore	2010-06-21 19:47:08 +0000
@@ -71,6 +71,12 @@
 libdbusmenu-gtk/DbusmenuGtk-0.2.tmp.gir
 libdbusmenu-gtk/DbusmenuGtk-0.2.typelib
 libdbusmenu-gtk/DbusmenuGtk-0.2.vapi
+tests/test-gtk-objects
+tests/test-gtk-objects-test
+tests/test-gtk-objects.xml
+tests/test-gtk-shortcut
+tests/test-gtk-shortcut-client
+tests/test-gtk-shortcut-server
 tests/test-glib-submenu
 tests/test-glib-submenu-client
 tests/test-glib-submenu-server

=== modified file 'libdbusmenu-glib/menuitem.h'
--- libdbusmenu-glib/menuitem.h	2010-06-17 19:58:28 +0000
+++ libdbusmenu-glib/menuitem.h	2010-06-21 19:47:08 +0000
@@ -58,6 +58,7 @@
 #define DBUSMENU_MENUITEM_PROP_ICON_DATA             "icon-data"
 #define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE           "toggle-type"
 #define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE          "toggle-state"
+#define DBUSMENU_MENUITEM_PROP_SHORTCUT              "shortcut"
 #define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY         "children-display"
 
 #define DBUSMENU_MENUITEM_TOGGLE_CHECK               "checkmark"
@@ -69,6 +70,11 @@
 
 #define DBUSMENU_MENUITEM_ICON_NAME_BLANK            "blank-icon"
 
+#define DBUSMENU_MENUITEM_SHORTCUT_CONTROL           "Control"
+#define DBUSMENU_MENUITEM_SHORTCUT_ALT               "Alt"
+#define DBUSMENU_MENUITEM_SHORTCUT_SHIFT             "Shift"
+#define DBUSMENU_MENUITEM_SHORTCUT_SUPER             "Super"
+
 #define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU      "submenu"
 
 /**

=== modified file 'libdbusmenu-gtk/client.c'
--- libdbusmenu-gtk/client.c	2010-05-26 22:18:29 +0000
+++ libdbusmenu-gtk/client.c	2010-06-21 19:37:18 +0000
@@ -36,6 +36,15 @@
 #include "menuitem.h"
 #include "genericmenuitem.h"
 
+/* Private */
+typedef struct _DbusmenuGtkClientPrivate DbusmenuGtkClientPrivate;
+struct _DbusmenuGtkClientPrivate {
+	GtkAccelGroup * agroup;
+};
+
+#define DBUSMENU_GTKCLIENT_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClientPrivate))
+
 /* Prototypes */
 static void dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass);
 static void dbusmenu_gtkclient_init       (DbusmenuGtkClient *self);
@@ -62,6 +71,8 @@
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+	g_type_class_add_private (klass, sizeof (DbusmenuGtkClientPrivate));
+
 	object_class->dispose = dbusmenu_gtkclient_dispose;
 	object_class->finalize = dbusmenu_gtkclient_finalize;
 
@@ -73,6 +84,10 @@
 static void
 dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
 {
+	DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(self);
+
+	priv->agroup = NULL;
+
 	dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(self), DBUSMENU_CLIENT_TYPES_DEFAULT,   new_item_normal);
 	dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(self), DBUSMENU_CLIENT_TYPES_SEPARATOR, new_item_seperator);
 
@@ -85,6 +100,12 @@
 static void
 dbusmenu_gtkclient_dispose (GObject *object)
 {
+	DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(object);
+
+	if (priv->agroup != NULL) {
+		g_object_unref(priv->agroup);
+		priv->agroup = NULL;
+	}
 
 	G_OBJECT_CLASS (dbusmenu_gtkclient_parent_class)->dispose (object);
 	return;
@@ -99,6 +120,151 @@
 	return;
 }
 
+/* Structure for passing data to swap_agroup */
+typedef struct _swap_agroup_t swap_agroup_t;
+struct _swap_agroup_t {
+	DbusmenuGtkClient * client;
+	GtkAccelGroup * old_agroup;
+	GtkAccelGroup * new_agroup;
+};
+
+/* Looks at the old version of the accelerator group and
+   the new one and makes the state proper. */
+static gboolean
+do_swap_agroup (DbusmenuMenuitem * mi, gpointer userdata) {
+        swap_agroup_t * data = (swap_agroup_t *)userdata;
+
+	/* If we don't have a shortcut we don't care */
+	if (!dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_SHORTCUT)) {
+		return FALSE;
+	}
+
+	guint key = 0;
+	GdkModifierType modifiers = 0;
+
+	dbusmenu_menuitem_property_get_shortcut(mi, &key, &modifiers);
+
+	g_debug("Setting shortcut on '%s': %d %X", dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_LABEL), key, modifiers);
+
+	if (key == 0) {
+		return FALSE;
+	}
+
+	GtkMenuItem * gmi = dbusmenu_gtkclient_menuitem_get(data->client, mi);
+	if (gmi == NULL) {
+		return FALSE;
+	}
+
+	const gchar * accel_path = gtk_menu_item_get_accel_path(gmi);
+
+	if (accel_path != NULL) {
+		gtk_accel_map_change_entry(accel_path, key, modifiers, TRUE /* replace */);
+	} else {
+		gchar * accel_path = g_strdup_printf("<Appmenus>/Generated/%X/%d", GPOINTER_TO_UINT(data->client), dbusmenu_menuitem_get_id(mi));
+
+		gtk_accel_map_add_entry(accel_path, key, modifiers);
+		gtk_widget_set_accel_path(GTK_WIDGET(gmi), accel_path, data->new_agroup);
+		g_free(accel_path);
+	}
+
+	GtkMenu * submenu = dbusmenu_gtkclient_menuitem_get_submenu(data->client, mi);
+	if (submenu != NULL) {
+		gtk_menu_set_accel_group(submenu, data->new_agroup);
+	}
+
+	return TRUE;
+}
+
+static void
+swap_agroup (DbusmenuMenuitem *mi, gpointer userdata) {
+        do_swap_agroup (mi, userdata);
+
+        return;  /* See what I did here, Ted? :)  */
+}
+
+/* Refresh the shortcut for an entry */
+static void
+refresh_shortcut (DbusmenuGtkClient * client, DbusmenuMenuitem * mi)
+{
+	g_return_if_fail(DBUSMENU_IS_GTKCLIENT(client));
+	g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+
+	DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client);
+
+	swap_agroup_t data;
+	data.client = client;
+	data.old_agroup = priv->agroup;
+	data.new_agroup = priv->agroup;
+
+	if (do_swap_agroup(mi, &data)) {
+                guint key;
+                GdkModifierType mod;
+                GtkMenuItem *gmi = dbusmenu_gtkclient_menuitem_get (client, mi);
+
+                dbusmenu_menuitem_property_get_shortcut (mi, &key, &mod);
+
+                gtk_widget_add_accelerator (GTK_WIDGET (gmi), "activate", priv->agroup, key, mod, GTK_ACCEL_VISIBLE);
+        }
+
+        return;
+}
+
+
+/**
+	dbusmenu_gtkclient_set_accel_group:
+	@client: To set the group on
+	@agroup: The new acceleration group
+
+	Sets the acceleration group for the menu items with accelerators
+	on this client.
+*/
+void
+dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup * agroup)
+{
+	g_return_if_fail(DBUSMENU_IS_GTKCLIENT(client));
+	g_return_if_fail(GTK_IS_ACCEL_GROUP(agroup));
+
+	DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client);
+
+	DbusmenuMenuitem * root = dbusmenu_client_get_root(DBUSMENU_CLIENT(client));
+	if (root != NULL) {
+		swap_agroup_t data;
+		data.client = client;
+		data.old_agroup = priv->agroup;
+		data.new_agroup = agroup;
+
+		dbusmenu_menuitem_foreach(root, swap_agroup, &data);
+	}
+
+	if (priv->agroup != NULL) {
+		g_object_unref(priv->agroup);
+		priv->agroup = NULL;
+	}
+
+	priv->agroup = agroup;
+
+	return;
+}
+
+/**
+	dbusmenu_gtkclient_get_accel_group:
+	@client: Client to query for an accelerator group
+
+	Gets the accel group for this client.
+
+	Return value: Either a valid group or #NULL on error or
+		none set.
+*/
+GtkAccelGroup *
+dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client)
+{
+	g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), NULL);
+
+	DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client);
+
+	return priv->agroup;
+}
+
 /* Internal Functions */
 
 static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem";
@@ -225,6 +391,17 @@
 	return;
 }
 
+/* Special handler for the shortcut changing as we need to have the
+   client for that one to get the accel group. */
+static void
+menu_shortcut_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, DbusmenuGtkClient * client)
+{
+	if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_SHORTCUT)) {
+		refresh_shortcut(client, mi);
+	}
+	return;
+}
+
 /* Call back that happens when the DbusmenuMenuitem
    is destroyed.  We're making sure to clean up everything
    else down the pipe. */
@@ -291,6 +468,7 @@
 
 	/* DbusmenuMenuitem signals */
 	g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_change_cb), gmi);
+	g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_shortcut_change_cb), client);
 	g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(delete_child), client);
 	g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED,   G_CALLBACK(move_child),   client);
 
@@ -305,6 +483,7 @@
 	process_sensitive(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_ENABLED));
 	process_toggle_type(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE));
 	process_toggle_state(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE));
+	refresh_shortcut(client, item);
 
 	/* Oh, we're a child, let's deal with that */
 	if (parent != NULL) {

=== modified file 'libdbusmenu-gtk/client.h'
--- libdbusmenu-gtk/client.h	2010-05-26 22:18:29 +0000
+++ libdbusmenu-gtk/client.h	2010-06-14 21:10:54 +0000
@@ -79,6 +79,9 @@
 GtkMenuItem * dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem * item);
 GtkMenu *     dbusmenu_gtkclient_menuitem_get_submenu (DbusmenuGtkClient * client, DbusmenuMenuitem * item);
 
+void  dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup * agroup);
+GtkAccelGroup * dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client);
+
 void dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * item, GtkMenuItem * gmi, DbusmenuMenuitem * parent);
 
 /**

=== modified file 'libdbusmenu-gtk/genericmenuitem.c'
--- libdbusmenu-gtk/genericmenuitem.c	2010-01-12 06:19:14 +0000
+++ libdbusmenu-gtk/genericmenuitem.c	2010-06-15 18:50:52 +0000
@@ -158,6 +158,8 @@
 static void
 set_label (GtkMenuItem * menu_item, const gchar * label)
 {
+	if (label == NULL) return;
+
 	GtkWidget * child = gtk_bin_get_child(GTK_BIN(menu_item));
 	GtkLabel * labelw = NULL;
 	gboolean suppress_update = FALSE;
@@ -191,9 +193,10 @@
 	   update the one that we already have. */
 	if (labelw == NULL) {
 		/* Build it */
-		labelw = GTK_LABEL(gtk_label_new(label));
+		labelw = GTK_LABEL(gtk_accel_label_new(label));
 		gtk_label_set_use_underline(GTK_LABEL(labelw), TRUE);
 		gtk_misc_set_alignment(GTK_MISC(labelw), 0.0, 0.5);
+		gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(labelw), GTK_WIDGET(menu_item));
 		gtk_widget_show(GTK_WIDGET(labelw));
 
 		/* Check to see if it needs to be in the bin for this

=== modified file 'libdbusmenu-gtk/menuitem.c'
--- libdbusmenu-gtk/menuitem.c	2009-09-03 20:32:17 +0000
+++ libdbusmenu-gtk/menuitem.c	2010-06-15 04:48:51 +0000
@@ -27,6 +27,8 @@
 */
 
 #include "menuitem.h"
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
 
 /**
 	dbusmenu_menuitem_property_set_image:
@@ -128,3 +130,209 @@
 	return icon;
 }
 
+/**
+	dbusmenu_menuitem_property_set_shortcut_string:
+	@menuitem: The #DbusmenuMenuitem to set the shortcut on
+	@shortcut: String describing the shortcut
+
+	This function takes a GTK shortcut string as defined in
+	#gtk_accelerator_parse and turns that into the information
+	required to send it over DBusmenu.
+
+	Return value: Whether it was successful at setting the property.
+*/
+gboolean
+dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, const gchar * shortcut)
+{
+	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE);
+	g_return_val_if_fail(shortcut != NULL, FALSE);
+
+	guint key = 0;
+	GdkModifierType modifier = 0;
+
+	gtk_accelerator_parse(shortcut, &key, &modifier);
+
+	if (key == 0) {
+		g_warning("Unable to parse shortcut string '%s'", shortcut);
+		return FALSE;
+	}
+
+	return dbusmenu_menuitem_property_set_shortcut(menuitem, key, modifier);
+}
+
+/* Append strings to an g_value_array */
+static void
+_g_value_array_append_string (GValueArray * array, const gchar * string)
+{
+	GValue value = {0};
+	g_value_init(&value, G_TYPE_STRING);
+	g_value_set_string(&value, string);
+	g_value_array_append(array, &value);
+	return;
+}
+
+/**
+	dbusmenu_menuitem_property_set_shortcut:
+	@menuitem: The #DbusmenuMenuitem to set the shortcut on
+	@key: The keycode of the key to send
+	@modifier: A bitmask of modifiers used to activate the item
+
+	Takes the modifer described by @key and @modifier and places that into
+	the format sending across Dbus for shortcuts.
+
+	Return value: Whether it was successful at setting the property.
+*/
+gboolean
+dbusmenu_menuitem_property_set_shortcut (DbusmenuMenuitem * menuitem, guint key, GdkModifierType modifier)
+{
+	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE);
+	g_return_val_if_fail(gtk_accelerator_valid(key, modifier), FALSE);
+
+	GValueArray * array = g_value_array_new(4); /* Four seems like the max we'd need, plus it's still small */
+
+	if (modifier & GDK_CONTROL_MASK) {
+		_g_value_array_append_string(array, DBUSMENU_MENUITEM_SHORTCUT_CONTROL);
+	}
+	if (modifier & GDK_MOD1_MASK) {
+		_g_value_array_append_string(array, DBUSMENU_MENUITEM_SHORTCUT_ALT);
+	}
+	if (modifier & GDK_SHIFT_MASK) {
+		_g_value_array_append_string(array, DBUSMENU_MENUITEM_SHORTCUT_SHIFT);
+	}
+	if (modifier & GDK_SUPER_MASK) {
+		_g_value_array_append_string(array, DBUSMENU_MENUITEM_SHORTCUT_SUPER);
+	}
+
+	_g_value_array_append_string(array, gdk_keyval_name(key));
+
+	GValueArray * wrapper = g_value_array_new(1);
+	GValue wrap_val = {0};
+	g_value_init(&wrap_val, G_TYPE_VALUE_ARRAY);
+	g_value_set_boxed(&wrap_val, array);
+	g_value_array_append(wrapper, &wrap_val);
+
+	GValue value = {0};
+	g_value_init(&value, G_TYPE_VALUE_ARRAY);
+	g_value_set_boxed(&value, wrapper);
+
+	dbusmenu_menuitem_property_set_value(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT, &value);
+
+	return TRUE;
+}
+
+/* Look at the closures in an accel group and find
+   the one that matches the one we've been passed */
+static gboolean
+find_closure (GtkAccelKey * key, GClosure * closure, gpointer user_data)
+{
+	return closure == user_data;
+}
+
+/**
+	dbusmenu_menuitem_property_set_shortcut_menuitem:
+	@menuitem: The #DbusmenuMenuitem to set the shortcut on
+	@gmi: A menu item to steal the shortcut off of
+
+	Takes the shortcut that is installed on a menu item and calls
+	#dbusmenu_menuitem_property_set_shortcut with it.  It also sets
+	up listeners to watch it change.
+
+	Return value: Whether it was successful at setting the property.
+*/
+gboolean
+dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, const GtkMenuItem * gmi)
+{
+	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE);
+	g_return_val_if_fail(GTK_IS_MENU_ITEM(gmi), FALSE);
+
+	GClosure * closure = NULL;
+	GList * clist;
+
+	clist = gtk_widget_list_accel_closures(GTK_WIDGET(gmi));
+	if (clist == NULL) {
+		g_warning("Menuitem does not have any closures.");
+		return FALSE;
+	}
+
+	closure = (GClosure *)clist->data;
+	g_list_free(clist);
+
+	GtkAccelGroup * group = gtk_accel_group_from_accel_closure(closure);
+	
+	/* Seriously, if this returns NULL something is seriously
+	   wrong in GTK. */
+	g_return_val_if_fail(group != NULL, FALSE);
+
+	GtkAccelKey * key = gtk_accel_group_find(group, find_closure, closure);
+	/* Again, not much we can do except complain loudly. */
+	g_return_val_if_fail(key != NULL, FALSE);
+
+	return dbusmenu_menuitem_property_set_shortcut(menuitem, key->accel_key, key->accel_mods);
+}
+
+/**
+	dbusmenu_menuitem_property_get_shortcut:
+	@menuitem: The #DbusmenuMenuitem to get the shortcut off
+	@key: Location to put the key value
+	@modifier: Location to put the modifier mask
+
+	This function gets a GTK shortcut as a key and a mask
+	for use to set the accelerators.
+*/
+void
+dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifier)
+{
+	*key = 0;
+	*modifier = 0;
+
+	g_return_if_fail(DBUSMENU_IS_MENUITEM(menuitem));
+
+	const GValue * wrapper = dbusmenu_menuitem_property_get_value(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT);
+	if (wrapper == NULL) {
+		return;
+	}
+
+	GValueArray * wrapperarray = (GValueArray *)g_value_get_boxed(wrapper);
+	if (wrapperarray->n_values == 0) {
+		return;
+	}
+
+	if (wrapperarray->n_values != 1) {
+		g_warning("Shortcut is more than one entry.  Which we don't currently support.  Taking the first.");
+	}
+
+	GValue * ventryarray = g_value_array_get_nth(wrapperarray, 0);
+	GValueArray * entryarray = (GValueArray *)g_value_get_boxed(ventryarray);
+	if (entryarray->n_values == 0) {
+		/* Seems a little odd, but really, we're saying that it isn't a
+		   shortcut, so I'm comfortable with exiting silently. */
+		return;
+	}
+
+	/* Parse through modifiers */
+	int i;
+	for (i = 0; i < entryarray->n_values - 1; i++) {
+		if (g_strcmp0(g_value_get_string(g_value_array_get_nth(entryarray, i)), DBUSMENU_MENUITEM_SHORTCUT_CONTROL) == 0) {
+			*modifier |= GDK_CONTROL_MASK;
+			continue;
+		}
+		if (g_strcmp0(g_value_get_string(g_value_array_get_nth(entryarray, i)), DBUSMENU_MENUITEM_SHORTCUT_ALT) == 0) {
+			*modifier |= GDK_MOD1_MASK;
+			continue;
+		}
+		if (g_strcmp0(g_value_get_string(g_value_array_get_nth(entryarray, i)), DBUSMENU_MENUITEM_SHORTCUT_SHIFT) == 0) {
+			*modifier |= GDK_SHIFT_MASK;
+			continue;
+		}
+		if (g_strcmp0(g_value_get_string(g_value_array_get_nth(entryarray, i)), DBUSMENU_MENUITEM_SHORTCUT_SUPER) == 0) {
+			*modifier |= GDK_SUPER_MASK;
+			continue;
+		}
+	}
+
+	GdkModifierType tempmod;
+
+	gtk_accelerator_parse(g_value_get_string(g_value_array_get_nth(entryarray, entryarray->n_values - 1)), key, &tempmod);
+
+	return;
+}

=== modified file 'libdbusmenu-gtk/menuitem.h'
--- libdbusmenu-gtk/menuitem.h	2009-09-02 18:29:01 +0000
+++ libdbusmenu-gtk/menuitem.h	2010-06-14 15:58:21 +0000
@@ -32,8 +32,15 @@
 #include <glib.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <libdbusmenu-glib/menuitem.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
 
 gboolean dbusmenu_menuitem_property_set_image (DbusmenuMenuitem * menuitem, const gchar * property, const GdkPixbuf * data);
 GdkPixbuf * dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar * property);
 
+gboolean dbusmenu_menuitem_property_set_shortcut (DbusmenuMenuitem * menuitem, guint key, GdkModifierType modifier);
+gboolean dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, const gchar * shortcut);
+gboolean dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, const GtkMenuItem * gmi);
+void dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifiers);
+
 #endif

=== modified file 'tests/Makefile.am'
--- tests/Makefile.am	2010-06-08 18:12:12 +0000
+++ tests/Makefile.am	2010-06-21 19:47:08 +0000
@@ -7,8 +7,10 @@
 	test-glib-properties \
 	test-glib-proxy \
 	test-glib-simple-items \
+	test-gtk-objects-test \
 	test-glib-submenu \
 	test-gtk-label \
+	test-gtk-shortcut \
 	test-gtk-reorder
 
 check_PROGRAMS = \
@@ -21,10 +23,13 @@
 	test-glib-proxy-client \
 	test-glib-proxy-server \
 	test-glib-proxy-proxy \
+	test-gtk-objects \
 	test-glib-submenu-client \
 	test-glib-submenu-server \
 	test-gtk-label-client \
 	test-gtk-label-server \
+	test-gtk-shortcut-client \
+	test-gtk-shortcut-server \
 	test-glib-simple-items \
 	test-gtk-reorder-server
 
@@ -119,7 +124,7 @@
 
 test-glib-objects-test: test-glib-objects Makefile.am
 	@echo "#!/bin/bash" > $@
-	@echo $(DBUS_RUNNER) --task gtester --parameter --verbose --parameter -k --parameter -o --parameter $(OBJECT_XML_REPORT) --parameter ./test-glib-objects >> $@
+	@echo $(DBUS_RUNNER) --task gtester --task-name test --parameter --verbose --parameter -k --parameter -o --parameter $(OBJECT_XML_REPORT) --parameter ./test-glib-objects >> $@
 	@chmod +x $@
 
 test_glib_objects_SOURCES = \
@@ -231,6 +236,34 @@
 	../libdbusmenu-glib/libdbusmenu-glib.la \
 	$(DBUSMENUGLIB_LIBS)
 
+######################
+# Test GTK Object
+######################
+
+GTK_OBJECT_XML_REPORT = test-gtk-objects.xml
+
+test-gtk-objects-test: test-gtk-objects Makefile.am
+	@echo "#!/bin/bash" > $@
+	@echo $(XVFB_RUN) >> $@
+	@echo $(DBUS_RUNNER) --task gtester --task-name test --parameter --verbose --parameter -k --parameter -o --parameter $(GTK_OBJECT_XML_REPORT) --parameter ./test-gtk-objects >> $@
+	@chmod +x $@
+
+test_gtk_objects_SOURCES = \
+	test-gtk-objects.c
+
+test_gtk_objects_CFLAGS = \
+	-I $(srcdir)/.. \
+	$(DBUSMENUGLIB_CFLAGS) \
+	$(DBUSMENUGTK_CFLAGS) \
+	-DSRCDIR="\"$(srcdir)\"" \
+	-Wall -Werror
+
+test_gtk_objects_LDADD = \
+	../libdbusmenu-glib/libdbusmenu-glib.la \
+	../libdbusmenu-gtk/libdbusmenu-gtk.la \
+	$(DBUSMENUGLIB_LIBS) \
+	$(DBUSMENUGTK_LIBS)
+
 #########################
 # Test GTK Label
 #########################
@@ -272,6 +305,46 @@
 	$(DBUSMENUTESTS_LIBS)
 
 #########################
+# Test GTK Shortcut
+#########################
+
+test-gtk-shortcut: test-gtk-shortcut-client test-gtk-shortcut-server Makefile.am
+	@echo "#!/bin/bash" > $@
+	@echo $(XVFB_RUN) >> $@
+	@echo $(DBUS_RUNNER) --task ./test-gtk-shortcut-client --task-name Client --task ./test-gtk-shortcut-server --task-name Server --ignore-return >> $@
+	@chmod +x $@
+
+test_gtk_shortcut_server_SOURCES = \
+	test-gtk-shortcut-server.c
+
+test_gtk_shortcut_server_CFLAGS = \
+	-I $(srcdir)/.. \
+	$(DBUSMENUGTK_CFLAGS) \
+	$(DBUSMENUTESTS_CFLAGS) \
+	$(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_gtk_shortcut_server_LDADD = \
+	../libdbusmenu-glib/libdbusmenu-glib.la \
+	../libdbusmenu-gtk/libdbusmenu-gtk.la \
+	$(DBUSMENUGTK_LIBS) \
+	$(DBUSMENUTESTS_LIBS)
+
+test_gtk_shortcut_client_SOURCES = \
+	test-gtk-shortcut-client.c
+
+test_gtk_shortcut_client_CFLAGS = \
+	-I $(srcdir)/.. \
+	$(DBUSMENUGTK_CFLAGS) \
+	$(DBUSMENUTESTS_CFLAGS) \
+	$(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_gtk_shortcut_client_LDADD = \
+	../libdbusmenu-glib/libdbusmenu-glib.la \
+	../libdbusmenu-gtk/libdbusmenu-gtk.la \
+	$(DBUSMENUGTK_LIBS) \
+	$(DBUSMENUTESTS_LIBS)
+
+#########################
 # Test GTK Reorder
 #########################
 
@@ -329,6 +402,7 @@
 	$(examples_DATA) \
 	run-xvfb.sh \
 	$(json_DATA) \
+	test-gtk-objects.jpg \
 	dbusmenu-gtk/dbusMenuTest \
 	dbusmenu-gtk/mago_tests/dbusmenu.xml \
 	dbusmenu-gtk/mago_tests/dbusmenu.py \
@@ -357,5 +431,6 @@
 
 DISTCLEANFILES = \
 	$(TESTS) \
-	$(OBJECT_XML_REPORT)
+	$(OBJECT_XML_REPORT) \
+	$(GTK_OBJECT_XML_REPORT)
 

=== modified file 'tests/run-xvfb.sh'
--- tests/run-xvfb.sh	2009-11-20 00:03:49 +0000
+++ tests/run-xvfb.sh	2010-06-15 04:57:38 +0000
@@ -1,4 +1,4 @@
-if [ "$DISPLAY" == "" ]; then
+if [ "x$DISPLAY" == "x" ]; then
 Xvfb -ac -noreset -screen 0 800x600x16 -help 2>/dev/null 1>&2
 XID=`for id in 101 102 103 104 105 106 107 197 199 211 223 227 293 307 308 309 310 311 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 4703 4721 4723 4729 4733 4751 9973 9974 9975 9976 9977 9978 9979 9980 9981 9982 9983 9984 9985 9986 9987 9988 9989 9990 9991 9992 9993 9994 9995 9996 9997 9998 9999 ; do test -e /tmp/.X$id-lock || { echo $id; exit 0; }; done; exit 1`
 { Xvfb -ac -noreset -screen 0 800x600x16 :$XID -screen 0 800x600x16 -nolisten tcp -auth /dev/null >/dev/null 2>&1 & trap "kill -15 $! " 0 HUP INT QUIT TRAP USR1 PIPE TERM ; } || { echo "Gtk+Tests:ERROR: Failed to start Xvfb environment for X11 target tests."; exit 1; }

=== added file 'tests/test-gtk-objects.c'
--- tests/test-gtk-objects.c	1970-01-01 00:00:00 +0000
+++ tests/test-gtk-objects.c	2010-06-14 19:52:26 +0000
@@ -0,0 +1,145 @@
+/*
+Testing for the various objects just by themselves.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+    Ted Gould <ted@xxxxxxxxxxxxx>
+
+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 <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-gtk/menuitem.h>
+#include <gdk/gdkkeysyms.h>
+
+#define TEST_IMAGE  SRCDIR "/" "test-gtk-objects.jpg"
+
+/* Building the basic menu item, make sure we didn't break
+   any core GObject stuff */
+static void
+test_object_menuitem (void)
+{
+	/* Build a menu item */
+	DbusmenuMenuitem * item = dbusmenu_menuitem_new();
+
+	/* Test to make sure it's a happy object */
+	g_assert(item != NULL);
+	g_assert(G_IS_OBJECT(item));
+	g_assert(DBUSMENU_IS_MENUITEM(item));
+
+	/* Set up a check to make sure it gets destroyed on unref */
+	g_object_add_weak_pointer(G_OBJECT(item), (gpointer *)&item);
+	g_object_unref(item);
+
+	/* Did it go away? */
+	g_assert(item == NULL);
+
+	return;
+}
+
+/* Setting and getting a pixbuf */
+static void
+test_object_prop_pixbuf (void)
+{
+	const gchar * prop_name = "image-test";
+
+	/* Build a menu item */
+	DbusmenuMenuitem * item = dbusmenu_menuitem_new();
+
+	/* Test to make sure it's a happy object */
+	g_assert(item != NULL);
+	g_assert(G_IS_OBJECT(item));
+	g_assert(DBUSMENU_IS_MENUITEM(item));
+
+	/* Load our image */
+	GdkPixbuf * pixbuf = gdk_pixbuf_new_from_file(TEST_IMAGE, NULL);
+	g_assert(pixbuf != NULL);
+
+	/* Set the property */
+	gboolean success = dbusmenu_menuitem_property_set_image(item, prop_name, pixbuf);
+	g_assert(success);
+	g_object_unref(pixbuf);
+
+	/* Check to see if it's set */
+	const GValue * val = dbusmenu_menuitem_property_get_value(item, prop_name);
+	g_assert(val != NULL);
+
+	/* Get the pixbuf back! */
+	GdkPixbuf * newpixbuf = dbusmenu_menuitem_property_get_image(item, prop_name);
+	g_assert(newpixbuf != NULL);
+	g_object_unref(newpixbuf);
+
+	g_object_unref(item);
+
+	return;
+}
+
+/* Setting and getting a shortcut */
+static void
+test_object_prop_shortcut (void)
+{
+	/* Build a menu item */
+	DbusmenuMenuitem * item = dbusmenu_menuitem_new();
+
+	/* Test to make sure it's a happy object */
+	g_assert(item != NULL);
+	g_assert(G_IS_OBJECT(item));
+	g_assert(DBUSMENU_IS_MENUITEM(item));
+
+	guint key = GDK_c;
+	GdkModifierType modifier = GDK_CONTROL_MASK;
+
+	/* Set a shortcut */
+	gboolean success = dbusmenu_menuitem_property_set_shortcut(item, key, modifier);
+	g_assert(success);
+
+	/* Check for value */
+	const GValue * val = dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_SHORTCUT);
+	g_assert(val != NULL);
+
+	/* Check to see if we love it */
+	guint newkey = 0;
+	GdkModifierType newmodifier = 0;
+	dbusmenu_menuitem_property_get_shortcut(item, &newkey, &newmodifier);
+
+	g_assert(key == newkey);
+	g_assert(newmodifier == modifier);
+
+	g_object_unref(item);
+
+	return;
+}
+
+/* Build the test suite */
+static void
+test_gtk_objects_suite (void)
+{
+	g_test_add_func ("/dbusmenu/gtk/objects/menuitem/base",          test_object_menuitem);
+	g_test_add_func ("/dbusmenu/gtk/objects/menuitem/prop_pixbuf",   test_object_prop_pixbuf);
+	g_test_add_func ("/dbusmenu/gtk/objects/menuitem/prop_shortcut", test_object_prop_shortcut);
+	return;
+}
+
+gint
+main (gint argc, gchar * argv[])
+{
+	gtk_init(&argc, &argv);
+
+	g_test_init(&argc, &argv, NULL);
+
+	/* Test suites */
+	test_gtk_objects_suite();
+
+	return g_test_run ();
+}

=== added file 'tests/test-gtk-objects.jpg'
Binary files tests/test-gtk-objects.jpg	1970-01-01 00:00:00 +0000 and tests/test-gtk-objects.jpg	2010-06-14 18:29:49 +0000 differ
=== added file 'tests/test-gtk-shortcut-client.c'
--- tests/test-gtk-shortcut-client.c	1970-01-01 00:00:00 +0000
+++ tests/test-gtk-shortcut-client.c	2010-06-15 19:21:44 +0000
@@ -0,0 +1,76 @@
+/*
+A test for libdbusmenu to ensure its quality.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+    Ted Gould <ted@xxxxxxxxxxxxx>
+
+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 <gtk/gtk.h>
+#include <libdbusmenu-gtk/menu.h>
+#include <libdbusmenu-gtk/client.h>
+
+static GMainLoop * mainloop = NULL;
+static gboolean passed = TRUE;
+static guint death_timer = 0;
+
+static gboolean
+timer_func (gpointer data)
+{
+	passed = TRUE;
+	g_main_loop_quit(mainloop);
+	return FALSE;
+}
+
+int
+main (int argc, char ** argv)
+{
+	gtk_init(&argc, &argv);
+
+	g_debug("Building Window");
+	GtkWidget * window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	GtkWidget * menubar = gtk_menu_bar_new();
+	GtkWidget * menuitem = gtk_menu_item_new_with_label("Test");
+
+	DbusmenuGtkMenu * dmenu = dbusmenu_gtkmenu_new ("glib.label.test", "/org/test");
+	DbusmenuGtkClient * dclient = dbusmenu_gtkmenu_get_client(dmenu);
+
+	GtkAccelGroup * agroup = gtk_accel_group_new();
+	dbusmenu_gtkclient_set_accel_group(dclient, agroup);
+
+	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), GTK_WIDGET(dmenu));
+	gtk_widget_show(menuitem);
+	gtk_menu_bar_append(menubar, menuitem);
+	gtk_widget_show(menubar);
+	gtk_container_add(GTK_CONTAINER(window), menubar);
+	gtk_window_set_title(GTK_WINDOW(window), "libdbusmenu-gtk test");
+	gtk_window_add_accel_group(GTK_WINDOW(window), agroup);
+	gtk_widget_show(window);
+
+	death_timer = g_timeout_add_seconds(10, timer_func, window);
+
+	g_debug("Entering Mainloop");
+	mainloop = g_main_loop_new(NULL, FALSE);
+	g_main_loop_run(mainloop);
+
+	if (passed) {
+		g_debug("Quiting");
+		return 0;
+	} else {
+		g_debug("Quiting as we're a failure");
+		return 1;
+	}
+}

=== added file 'tests/test-gtk-shortcut-server.c'
--- tests/test-gtk-shortcut-server.c	1970-01-01 00:00:00 +0000
+++ tests/test-gtk-shortcut-server.c	2010-06-15 19:42:35 +0000
@@ -0,0 +1,99 @@
+/*
+A test for libdbusmenu to ensure its quality.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+    Ted Gould <ted@xxxxxxxxxxxxx>
+
+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 <gdk/gdkkeysyms.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus-glib-bindings.h>
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-glib/server.h>
+#include <libdbusmenu-gtk/menuitem.h>
+
+GMainLoop * mainloop = NULL;
+DbusmenuServer * server = NULL;
+
+gboolean
+timer_func (gpointer userdata)
+{
+	g_main_loop_quit(mainloop);
+	return FALSE;
+}
+
+void
+build_menu (void)
+{
+	DbusmenuMenuitem * item;
+
+	DbusmenuMenuitem * root = dbusmenu_menuitem_new();
+
+	item = dbusmenu_menuitem_new();
+	dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_LABEL, "Control-L");
+	dbusmenu_menuitem_property_set_shortcut(item, GDK_l, GDK_CONTROL_MASK);
+	dbusmenu_menuitem_child_append(root, item);
+	g_object_unref(item);
+
+
+	dbusmenu_server_set_root(server, root);
+	g_object_unref(root);
+
+	return;
+}
+
+int
+main (int argc, char ** argv)
+{
+	GError * error = NULL;
+
+	g_type_init();
+
+	DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
+	g_debug("DBus ID: %s", dbus_connection_get_server_id(dbus_g_connection_get_connection(dbus_g_bus_get(DBUS_BUS_SESSION, NULL))));
+
+	DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+	guint nameret = 0;
+
+	if (!org_freedesktop_DBus_request_name(bus_proxy, "glib.label.test", 0, &nameret, &error)) {
+		g_error("Unable to call to request name");
+		return 1;
+	}
+
+	if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+		g_error("Unable to get name");
+		return 1;
+	}
+
+	server = dbusmenu_server_new("/org/test");
+	build_menu();
+
+	g_timeout_add_seconds(10, timer_func, NULL);
+
+	mainloop = g_main_loop_new(NULL, FALSE);
+	g_main_loop_run(mainloop);
+
+	g_debug("Quiting");
+
+	return 0;
+}
+