diff options
-rw-r--r-- | configure.ac | 16 | ||||
-rw-r--r-- | libdbusmenu-gtk/client.c | 4 | ||||
-rw-r--r-- | tests/Makefile.am | 48 | ||||
-rw-r--r-- | tests/test-gtk-submenu-client.c | 143 | ||||
-rw-r--r-- | tests/test-gtk-submenu-server.c | 98 | ||||
-rw-r--r-- | tools/Makefile.am | 7 | ||||
-rw-r--r-- | tools/dbusmenu-dumper.c | 225 |
7 files changed, 522 insertions, 19 deletions
diff --git a/configure.ac b/configure.ac index 6c2400c..d54dc41 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ -AC_INIT(libdbusmenu, 0.3.6, ted@canonical.com) +AC_INIT(libdbusmenu, 0.3.7, ted@canonical.com) AC_COPYRIGHT([Copyright 2009,2010 Canonical]) AC_PREREQ(2.62) AM_CONFIG_HEADER(config.h) -AM_INIT_AUTOMAKE(libdbusmenu, 0.3.6, [-Wno-portability]) +AM_INIT_AUTOMAKE(libdbusmenu, 0.3.7, [-Wno-portability]) AM_MAINTAINER_MODE @@ -58,6 +58,18 @@ AC_SUBST(DBUSMENUGTK_CFLAGS) AC_SUBST(DBUSMENUGTK_LIBS) ########################### +# Dependencies - dumper +########################### + +X11_REQUIRED_VERSION=1.3 + +PKG_CHECK_MODULES(DBUSMENUDUMPER, gtk+-2.0 >= $GTK_REQUIRED_VERSION + x11 >= $X11_REQUIRED_VERSION) + +AC_SUBST(DBUSMENUDUMPER_CFLAGS) +AC_SUBST(DBUSMENUDUMPER_LIBS) + +########################### # Dependencies - Testing ########################### diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c index b406697..b5b509f 100644 --- a/libdbusmenu-gtk/client.c +++ b/libdbusmenu-gtk/client.c @@ -491,7 +491,7 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * /* Oh, we're a child, let's deal with that */ if (parent != NULL) { - new_child(parent, item, dbusmenu_menuitem_get_position_realized(item, parent), DBUSMENU_GTKCLIENT(client)); + new_child(parent, item, dbusmenu_menuitem_get_position(item, parent), DBUSMENU_GTKCLIENT(client)); } return; @@ -519,7 +519,7 @@ new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, Dbus } GtkMenuItem * childmi = dbusmenu_gtkclient_menuitem_get(gtkclient, child); - gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), dbusmenu_menuitem_get_position_realized(child, mi)); + gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), position); gtk_widget_show(GTK_WIDGET(menu)); return; diff --git a/tests/Makefile.am b/tests/Makefile.am index 63857a2..839305f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -10,11 +10,11 @@ TESTS = \ test-glib-proxy \ test-glib-simple-items \ test-glib-submenu \ - test-json \ test-gtk-objects-test \ test-gtk-label \ test-gtk-shortcut \ - test-gtk-reorder + test-gtk-reorder \ + test-gtk-submenu check_PROGRAMS = \ glib-server-nomenu \ @@ -36,7 +36,9 @@ check_PROGRAMS = \ test-glib-simple-items \ test-gtk-reorder-server \ test-json-client \ - test-json-server + test-json-server \ + test-gtk-submenu-server \ + test-gtk-submenu-client XVFB_RUN=". $(srcdir)/run-xvfb.sh" @@ -454,6 +456,46 @@ test_gtk_reorder_server_LDADD = \ $(DBUSMENUTESTS_LIBS) ######################### +# Test GTK Submenu +######################### + +test-gtk-submenu: test-gtk-submenu-client test-gtk-submenu-server Makefile.am + @echo "#!/bin/bash" > $@ + @echo $(XVFB_RUN) >> $@ + @echo $(DBUS_RUNNER) --task ./test-gtk-submenu-client --task-name Client --task ./test-gtk-submenu-server --task-name Server --ignore-return >> $@ + @chmod +x $@ + +test_gtk_submenu_server_SOURCES = \ + test-gtk-submenu-server.c + +test_gtk_submenu_server_CFLAGS = \ + -I $(srcdir)/.. \ + $(DBUSMENUGTK_CFLAGS) \ + $(DBUSMENUTESTS_CFLAGS) \ + $(DBUSMENUGLIB_CFLAGS) -Wall -Werror + +test_gtk_submenu_server_LDADD = \ + ../libdbusmenu-glib/libdbusmenu-glib.la \ + ../libdbusmenu-gtk/libdbusmenu-gtk.la \ + $(DBUSMENUGTK_LIBS) \ + $(DBUSMENUTESTS_LIBS) + +test_gtk_submenu_client_SOURCES = \ + test-gtk-submenu-client.c + +test_gtk_submenu_client_CFLAGS = \ + -I $(srcdir)/.. \ + $(DBUSMENUGTK_CFLAGS) \ + $(DBUSMENUTESTS_CFLAGS) \ + $(DBUSMENUGLIB_CFLAGS) -Wall -Werror + +test_gtk_submenu_client_LDADD = \ + ../libdbusmenu-glib/libdbusmenu-glib.la \ + ../libdbusmenu-gtk/libdbusmenu-gtk.la \ + $(DBUSMENUGTK_LIBS) \ + $(DBUSMENUTESTS_LIBS) + +######################### # Test Mago ######################### diff --git a/tests/test-gtk-submenu-client.c b/tests/test-gtk-submenu-client.c new file mode 100644 index 0000000..2e1ef7a --- /dev/null +++ b/tests/test-gtk-submenu-client.c @@ -0,0 +1,143 @@ +/* +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 <gtk/gtk.h> +#include <libdbusmenu-gtk/menu.h> + +static GMainLoop * mainloop = NULL; +static gboolean passed = TRUE; + +static gboolean check_menu_content(GtkMenu * menu, char ** content) +{ + GList * child = gtk_container_get_children(GTK_CONTAINER(menu)); + char ** expected = content; + for (; child != NULL; child = g_list_next(child), ++expected) { + if (*expected == NULL) { + g_warning("Too many gtk items"); + return FALSE; + } + const char * label = gtk_menu_item_get_label(GTK_MENU_ITEM(child->data)); + if (g_strcmp0(label, *expected) != 0) { + g_warning("Expected '%s', got '%s'", *expected, label); + return FALSE; + } + } + if (*expected != NULL) { + g_warning("Not enough gtk items"); + return FALSE; + } + return TRUE; +} + +static void +abort_test(const char * message) +{ + if (message) { + g_warning("%s", message); + } + passed = FALSE; + g_main_loop_quit(mainloop); +} + +static gboolean +timer_func (gpointer data) +{ + static char * root_content[] = { "Folder 1", "Folder 2", NULL }; + static char * folder1_content[] = { "1.1", "1.2", "1.3", NULL }; + static char * folder2_content[] = { "2.1", "2.2", "2.3", NULL }; + + GtkMenuItem * root_item = GTK_MENU_ITEM(data); + GtkMenu * menu = GTK_MENU(gtk_menu_item_get_submenu(root_item)); + + /* Root */ + if (!check_menu_content(menu, root_content)) { + abort_test("Checking root content failed"); + return FALSE; + } + + /* Folder 1 */ + GList * child = gtk_container_get_children(GTK_CONTAINER(menu)); + GtkMenuItem * item = GTK_MENU_ITEM(child->data); + GtkMenu * folder_menu = GTK_MENU(gtk_menu_item_get_submenu(item)); + if (!folder_menu) { + abort_test("Folder 1 has no menu"); + return FALSE; + } + + if (!check_menu_content(folder_menu, folder1_content)) { + abort_test("Checking folder1 content failed"); + return FALSE; + } + + /* Folder 2 */ + child = g_list_next(child); + item = GTK_MENU_ITEM(child->data); + folder_menu = GTK_MENU(gtk_menu_item_get_submenu(item)); + if (!folder_menu) { + abort_test("Folder 2 has no menu"); + return FALSE; + } + + if (!check_menu_content(folder_menu, folder2_content)) { + abort_test("Checking folder2 content failed"); + return FALSE; + } + + passed = TRUE; + g_main_loop_quit(mainloop); + return FALSE; +} + +int +main (int argc, char ** argv) +{ + gtk_init(&argc, &argv); + + g_debug("Client Initialized. Waiting."); + /* Make sure the server starts up and all that */ + g_usleep(500000); + + 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"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), GTK_WIDGET(dbusmenu_gtkmenu_new ("glib.label.test", "/org/test"))); + 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_widget_show(window); + + g_timeout_add_seconds(1, timer_func, menuitem); + + 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; + } +} diff --git a/tests/test-gtk-submenu-server.c b/tests/test-gtk-submenu-server.c new file mode 100644 index 0000000..ba3993e --- /dev/null +++ b/tests/test-gtk-submenu-server.c @@ -0,0 +1,98 @@ +/* +A test for libdbusmenu to ensure its quality. + +Copyright 2009 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <glib.h> + +#include <dbus/dbus.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include <dbus/dbus-glib-bindings.h> + +#include <libdbusmenu-glib/menuitem.h> +#include <libdbusmenu-glib/server.h> + +static GMainLoop *mainloop = NULL; + +static gboolean +timer_func (gpointer data) +{ + g_main_loop_quit (mainloop); + + return FALSE; +} + +DbusmenuMenuitem * +add_item(DbusmenuMenuitem * parent, const char * label) +{ + DbusmenuMenuitem * item = dbusmenu_menuitem_new(); + dbusmenu_menuitem_property_set(item, "label", label); + dbusmenu_menuitem_child_append(parent, item); + return item; +} + +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; + } + + DbusmenuServer * server = dbusmenu_server_new("/org/test"); + DbusmenuMenuitem * root = dbusmenu_menuitem_new(); + dbusmenu_server_set_root(server, root); + + DbusmenuMenuitem * item; + item = add_item(root, "Folder 1"); + add_item(item, "1.1"); + add_item(item, "1.2"); + add_item(item, "1.3"); + + item = add_item(root, "Folder 2"); + add_item(item, "2.1"); + add_item(item, "2.2"); + add_item(item, "2.3"); + + g_timeout_add_seconds(3, timer_func, NULL); + + mainloop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(mainloop); + + g_debug("Quiting"); + + return 0; +} + diff --git a/tools/Makefile.am b/tools/Makefile.am index 77d6eef..48993f1 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -10,11 +10,14 @@ dbusmenu_dumper_SOURCES = \ dbusmenu_dumper_CFLAGS = \ -I $(srcdir)/.. \ - $(DBUSMENUGLIB_CFLAGS) -Wall -Werror + $(DBUSMENUGLIB_CFLAGS) \ + $(DBUSMENUDUMPER_CFLAGS) \ + -Wall -Werror dbusmenu_dumper_LDADD = \ ../libdbusmenu-glib/libdbusmenu-glib.la \ - $(DBUSMENUGLIB_LIBS) + $(DBUSMENUGLIB_LIBS) \ + $(DBUSMENUDUMPER_LIBS) doc_DATA = README.dbusmenu-bench diff --git a/tools/dbusmenu-dumper.c b/tools/dbusmenu-dumper.c index 6ce9655..40ab1e1 100644 --- a/tools/dbusmenu-dumper.c +++ b/tools/dbusmenu-dumper.c @@ -21,11 +21,17 @@ with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <glib.h> +#include <dbus/dbus-glib.h> + +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> #include <libdbusmenu-glib/client.h> #include <libdbusmenu-glib/menuitem.h> #include <dbus/dbus-gtype-specialized.h> +#include <X11/Xlib.h> static GMainLoop * mainloop = NULL; @@ -191,11 +197,191 @@ new_root_cb (DbusmenuClient * client, DbusmenuMenuitem * newroot) return; } +/* Window clicking ***************************************************/ +static GdkFilterReturn +click_filter (GdkXEvent *gdk_xevent, + GdkEvent *event, + gpointer data); + +static Window +find_real_window (Window w, int depth) +{ + if (depth > 5) { + return None; + } + /*static*/ Atom wm_state = XInternAtom(gdk_display, "WM_STATE", False); + Atom type; + int format; + unsigned long nitems, after; + unsigned char* prop; + if (XGetWindowProperty(gdk_display, w, wm_state, 0, 0, False, AnyPropertyType, + &type, &format, &nitems, &after, &prop) == Success) { + if (prop != NULL) { + XFree(prop); + } + if (type != None) { + return w; + } + } + Window root, parent; + Window* children; + unsigned int nchildren; + Window ret = None; + if (XQueryTree(gdk_display, w, &root, &parent, &children, &nchildren) != 0) { + unsigned int i; + for(i = 0; i < nchildren && ret == None; ++i) { + ret = find_real_window(children[ i ], depth + 1); + } + if (children != NULL) { + XFree(children); + } + } + return ret; +} + +static Window +get_window_under_cursor (void) +{ + Window root; + Window child; + uint mask; + int rootX, rootY, winX, winY; + XQueryPointer(gdk_display, gdk_x11_get_default_root_xwindow(), &root, &child, &rootX, &rootY, &winX, &winY, &mask); + if (child == None) { + return None; + } + return find_real_window(child, 0); +} + +static void +uninstall_click_filter (void) +{ + GdkWindow *root; + + root = gdk_get_default_root_window (); + gdk_window_remove_filter (root, (GdkFilterFunc) click_filter, NULL); + + gdk_pointer_ungrab (GDK_CURRENT_TIME); + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + + gtk_main_quit (); +} + +static GdkFilterReturn +click_filter (GdkXEvent *gdk_xevent, + GdkEvent *event, + gpointer data) + +{ + XEvent *xevent = (XEvent *) gdk_xevent; + gboolean *success = (gboolean *)data; + + switch (xevent->type) { + case ButtonPress: + uninstall_click_filter(); + *success = TRUE; + return GDK_FILTER_REMOVE; + case KeyPress: + if (xevent->xkey.keycode == XKeysymToKeycode(gdk_display, XK_Escape)) { + uninstall_click_filter(); + *success = FALSE; + return GDK_FILTER_REMOVE; + } + break; + default: + break; + } + + return GDK_FILTER_CONTINUE; +} + +static gboolean +install_click_filter (gpointer data) +{ + GdkGrabStatus status; + GdkCursor *cross; + GdkWindow *root; + + root = gdk_get_default_root_window(); + + gdk_window_add_filter(root, (GdkFilterFunc) click_filter, data); + + cross = gdk_cursor_new(GDK_CROSS); + status = gdk_pointer_grab(root, FALSE, GDK_BUTTON_PRESS_MASK, + NULL, cross, GDK_CURRENT_TIME); + gdk_cursor_unref(cross); + + if (status != GDK_GRAB_SUCCESS) { + g_warning("Pointer grab failed.\n"); + uninstall_click_filter(); + return FALSE; + } + + status = gdk_keyboard_grab(root, FALSE, GDK_CURRENT_TIME); + if (status != GDK_GRAB_SUCCESS) { + g_warning("Keyboard grab failed.\n"); + uninstall_click_filter(); + return FALSE; + } + + gdk_flush(); + return FALSE; +} + +static gboolean +wait_for_click (void) +{ + gboolean success; + g_idle_add (install_click_filter, (gpointer)(&success)); + gtk_main (); + return success; +} static gchar * dbusname = NULL; static gchar * dbusobject = NULL; static gboolean +init_dbus_vars_from_window(Window window) +{ + DBusGConnection *connection; + GError *error; + DBusGProxy *proxy; + + error = NULL; + connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error); + if (connection == NULL) { + g_printerr("Failed to open connection to bus: %s\n", error->message); + g_error_free(error); + return FALSE; + } + + proxy = dbus_g_proxy_new_for_name (connection, + "org.ayatana.AppMenu.Registrar", + "/org/ayatana/AppMenu/Registrar", + "org.ayatana.AppMenu.Registrar"); + + error = NULL; + if (!dbus_g_proxy_call (proxy, "GetMenuForWindow", &error, + G_TYPE_UINT, window, G_TYPE_INVALID, + G_TYPE_STRING, &dbusname, DBUS_TYPE_G_OBJECT_PATH, &dbusobject, G_TYPE_INVALID)) + { + g_printerr("ERROR: %s\n", error->message); + g_error_free(error); + g_object_unref(proxy); + return FALSE; + } + + if (!g_strcmp0(dbusobject, "/")) { + return FALSE; + } + + g_object_unref (proxy); + + return TRUE; +} + +/* Option parser *****************************************************/ +static gboolean option_dbusname (const gchar * arg, const gchar * value, gpointer data, GError ** error) { if (dbusname != NULL) { @@ -228,7 +414,8 @@ usage (void) static GOptionEntry general_options[] = { {"dbus-name", 'd', 0, G_OPTION_ARG_CALLBACK, option_dbusname, "The name of the program to connect to (i.e. org.test.bob", "dbusname"}, - {"dbus-object", 'o', 0, G_OPTION_ARG_CALLBACK, option_dbusobject, "The path to the Dbus object (i.e /org/test/bob/alvin)", "dbusobject"} + {"dbus-object", 'o', 0, G_OPTION_ARG_CALLBACK, option_dbusobject, "The path to the Dbus object (i.e /org/test/bob/alvin)", "dbusobject"}, + {NULL} }; int @@ -248,16 +435,34 @@ main (int argc, char ** argv) return 1; } - if (dbusname == NULL) { - g_printerr("ERROR: dbus-name not specified\n"); - usage(); - return 1; - } + if (dbusname == NULL && dbusobject == NULL) { + gtk_init(&argc, &argv); + if (!wait_for_click()) { + return 1; + } + Window window = get_window_under_cursor(); + if (window == None) { + g_printerr("ERROR: could not get the id for the pointed window\n"); + return 1; + } + g_debug("window: %u", (unsigned int)window); + if (!init_dbus_vars_from_window(window)) { + g_printerr("ERROR: could not find a menu for the pointed window\n"); + return 1; + } + g_debug("dbusname: %s, dbusobject: %s", dbusname, dbusobject); + } else { + if (dbusname == NULL) { + g_printerr("ERROR: dbus-name not specified\n"); + usage(); + return 1; + } - if (dbusobject == NULL) { - g_printerr("ERROR: dbus-object not specified\n"); - usage(); - return 1; + if (dbusobject == NULL) { + g_printerr("ERROR: dbus-object not specified\n"); + usage(); + return 1; + } } DbusmenuClient * client = dbusmenu_client_new (dbusname, dbusobject); |