diff options
-rw-r--r-- | configure.ac | 16 | ||||
-rw-r--r-- | libdbusmenu-glib/dbus-menu.xml | 14 | ||||
-rw-r--r-- | libdbusmenu-glib/server-marshal.list | 1 | ||||
-rw-r--r-- | libdbusmenu-glib/server.c | 81 | ||||
-rw-r--r-- | libdbusmenu-glib/server.h | 5 | ||||
-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 |
11 files changed, 608 insertions, 34 deletions
diff --git a/configure.ac b/configure.ac index 6c2400c..d8211b1 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.8, 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.8, [-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-glib/dbus-menu.xml b/libdbusmenu-glib/dbus-menu.xml index 53b67de..9e8013c 100644 --- a/libdbusmenu-glib/dbus-menu.xml +++ b/libdbusmenu-glib/dbus-menu.xml @@ -344,6 +344,20 @@ License version 3 and version 2.1 along with this program. If not, see </dox:d> </arg> </signal> + <signal name="ItemActivationRequested"> + <dox:d> + The server is requesting that all clients displaying this + menu open it to the user. This would be for things like + hotkeys that when the user presses them the menu should + open and display itself to the user. + </dox:d> + <arg type="i" name="id" direction="out" > + <dox:d>ID of the menu that should be activated</dox:d> + </arg> + <arg type="u" name="timestamp" direction="out" > + <dox:d>The time that the event occured</dox:d> + </arg> + </signal> <!-- End of interesting stuff --> diff --git a/libdbusmenu-glib/server-marshal.list b/libdbusmenu-glib/server-marshal.list index 1689a05..0d68c4e 100644 --- a/libdbusmenu-glib/server-marshal.list +++ b/libdbusmenu-glib/server-marshal.list @@ -1,2 +1,3 @@ VOID: INT, STRING, POINTER VOID: UINT, INT +VOID: INT, UINT diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c index 08a6631..d1b4888 100644 --- a/libdbusmenu-glib/server.c +++ b/libdbusmenu-glib/server.c @@ -48,6 +48,8 @@ static void _gvalue_array_append_hashtable(GValueArray *array, GHashTable * dict #include "dbusmenu-server.h" +static void layout_update_signal (DbusmenuServer * server); + #define DBUSMENU_VERSION_NUMBER 2 /* Privates, I'll show you mine... */ @@ -58,6 +60,7 @@ struct _DbusmenuServerPrivate DbusmenuMenuitem * root; gchar * dbusobject; gint layout_revision; + guint layout_idle; }; #define DBUSMENU_SERVER_GET_PRIVATE(o) \ @@ -68,6 +71,7 @@ enum { ID_PROP_UPDATE, ID_UPDATE, LAYOUT_UPDATED, + ITEM_ACTIVATION, LAST_SIGNAL }; @@ -168,6 +172,22 @@ dbusmenu_server_class_init (DbusmenuServerClass *class) NULL, NULL, _dbusmenu_server_marshal_VOID__UINT_INT, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_INT); + /** + DbusmenuServer::item-activation-requested: + @arg0: The #DbusmenuServer emitting the signal. + @arg1: The ID of the parent for this update. + @arg2: The timestamp of when the event happened + + This is signaled when a menuitem under this server + sends it's activate signal. + */ + signals[ITEM_ACTIVATION] = g_signal_new(DBUSMENU_SERVER_SIGNAL_ITEM_ACTIVATION, + G_TYPE_FROM_CLASS(class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(DbusmenuServerClass, item_activation), + NULL, NULL, + _dbusmenu_server_marshal_VOID__INT_UINT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_UINT); g_object_class_install_property (object_class, PROP_DBUS_OBJECT, @@ -199,6 +219,7 @@ dbusmenu_server_init (DbusmenuServer *self) priv->root = NULL; priv->dbusobject = NULL; priv->layout_revision = 1; + priv->layout_idle = 0; return; } @@ -208,6 +229,10 @@ dbusmenu_server_dispose (GObject *object) { DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(object); + if (priv->layout_idle != 0) { + g_source_remove(priv->layout_idle); + } + if (priv->root != NULL) { dbusmenu_menuitem_foreach(priv->root, menuitem_signals_remove, object); g_object_unref(priv->root); @@ -260,8 +285,7 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) } else { g_debug("Setting root node to NULL"); } - priv->layout_revision++; - g_signal_emit(obj, signals[LAYOUT_UPDATED], 0, priv->layout_revision, 0, TRUE); + layout_update_signal(DBUSMENU_SERVER(obj)); break; default: g_return_if_reached(); @@ -305,6 +329,35 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) return; } +/* Handle actually signalling in the idle loop. This way we collect all + the updates. */ +static gboolean +layout_update_idle (gpointer user_data) +{ + DbusmenuServer * server = DBUSMENU_SERVER(user_data); + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + + g_signal_emit(G_OBJECT(server), signals[LAYOUT_UPDATED], 0, priv->layout_revision, 0, TRUE); + + priv->layout_idle = 0; + + return FALSE; +} + +/* Signals that the layout has been updated */ +static void +layout_update_signal (DbusmenuServer * server) +{ + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + priv->layout_revision++; + + if (priv->layout_idle == 0) { + priv->layout_idle = g_idle_add(layout_update_idle, server); + } + + return; +} + static void menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * value, DbusmenuServer * server) { @@ -335,10 +388,7 @@ menuitem_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint menuitem_signals_create(child, server); g_list_foreach(dbusmenu_menuitem_get_children(child), added_check_children, server); - /* TODO: We probably need to group the layout update signals to make the number more reasonble. */ - DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); - priv->layout_revision++; - g_signal_emit(G_OBJECT(server), signals[LAYOUT_UPDATED], 0, priv->layout_revision, 0, TRUE); + layout_update_signal(server); return; } @@ -346,19 +396,23 @@ 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. */ - DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); - priv->layout_revision++; - g_signal_emit(G_OBJECT(server), signals[LAYOUT_UPDATED], 0, priv->layout_revision, 0, TRUE); + layout_update_signal(server); return; } static void menuitem_child_moved (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint newpos, guint oldpos, DbusmenuServer * server) { - DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); - priv->layout_revision++; - g_signal_emit(G_OBJECT(server), signals[LAYOUT_UPDATED], 0, priv->layout_revision, 0, TRUE); + layout_update_signal(server); + return; +} + +/* Called when a menu item emits its activated signal so it + gets passed across the bus. */ +static void +menuitem_activated (DbusmenuMenuitem * mi, guint timestamp, DbusmenuServer * server) +{ + g_signal_emit(G_OBJECT(server), signals[ITEM_ACTIVATION], 0, dbusmenu_menuitem_get_id(mi), timestamp, TRUE); return; } @@ -371,6 +425,7 @@ menuitem_signals_create (DbusmenuMenuitem * mi, gpointer 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_CHILD_MOVED, G_CALLBACK(menuitem_child_moved), data); g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menuitem_property_changed), data); + g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(menuitem_activated), data); return; } diff --git a/libdbusmenu-glib/server.h b/libdbusmenu-glib/server.h index f4e3527..a9bf213 100644 --- a/libdbusmenu-glib/server.h +++ b/libdbusmenu-glib/server.h @@ -46,6 +46,7 @@ G_BEGIN_DECLS #define DBUSMENU_SERVER_SIGNAL_ID_PROP_UPDATE "item-property-updated" #define DBUSMENU_SERVER_SIGNAL_ID_UPDATE "item-updated" #define DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATED "layout-updated" +#define DBUSMENU_SERVER_SIGNAL_ITEM_ACTIVATION "item-activation-requested" #define DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATE DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATED #define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object" @@ -58,10 +59,10 @@ G_BEGIN_DECLS @id_prop_update: Slot for #DbusmenuServer::id-prop-update. @id_update: Slot for #DbusmenuServer::id-update. @layout_updated: Slot for #DbusmenuServer::layout-update. + @item_activation_requested: Slot for #DbusmenuServer::item-activation-requested. @dbusmenu_server_reserved1: Reserved for future use. @dbusmenu_server_reserved2: Reserved for future use. @dbusmenu_server_reserved3: Reserved for future use. - @dbusmenu_server_reserved4: Reserved for future use. The class implementing the virtual functions for #DbusmenuServer. */ @@ -73,12 +74,12 @@ struct _DbusmenuServerClass { void (*id_prop_update)(gint id, gchar * property, gchar * value); void (*id_update)(gint id); void (*layout_updated)(gint revision); + void (*item_activation)(gint id, guint timestamp); /* Reserved */ void (*dbusmenu_server_reserved1)(void); void (*dbusmenu_server_reserved2)(void); void (*dbusmenu_server_reserved3)(void); - void (*dbusmenu_server_reserved4)(void); }; /** 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); |