aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore133
-rw-r--r--Makefile.am26
-rwxr-xr-xautogen.sh3
-rw-r--r--configure.ac25
-rw-r--r--libdbusmenu-glib/Makefile.am15
-rw-r--r--libdbusmenu-glib/client-marshal.list2
-rw-r--r--libdbusmenu-glib/client-menuitem.c6
-rw-r--r--libdbusmenu-glib/client.c583
-rw-r--r--libdbusmenu-glib/client.h17
-rw-r--r--libdbusmenu-glib/dbus-menu.xml14
-rw-r--r--libdbusmenu-glib/menuitem-proxy.c6
-rw-r--r--libdbusmenu-glib/menuitem-proxy.h18
-rw-r--r--libdbusmenu-glib/menuitem.c68
-rw-r--r--libdbusmenu-glib/menuitem.h33
-rw-r--r--libdbusmenu-glib/server-marshal.list1
-rw-r--r--libdbusmenu-glib/server.c195
-rw-r--r--libdbusmenu-glib/server.h33
-rw-r--r--libdbusmenu-gtk/client.c248
-rw-r--r--libdbusmenu-gtk/client.h14
-rw-r--r--libdbusmenu-gtk/genericmenuitem.c5
-rw-r--r--libdbusmenu-gtk/menu.c6
-rw-r--r--libdbusmenu-gtk/menu.h11
-rw-r--r--libdbusmenu-gtk/menuitem.c248
-rw-r--r--libdbusmenu-gtk/menuitem.h7
-rw-r--r--tests/Makefile.am249
-rw-r--r--tests/dbusmenu-jsonloader.pc.in14
-rw-r--r--tests/json-loader.c220
-rw-r--r--tests/json-loader.h31
-rw-r--r--tests/run-xvfb.sh2
-rw-r--r--tests/test-glib-events-client.c140
-rw-r--r--tests/test-glib-events-server.c102
-rw-r--r--tests/test-glib-properties-client.c2
-rw-r--r--tests/test-gtk-label-client.c2
-rw-r--r--tests/test-gtk-label-server.c71
-rw-r--r--tests/test-gtk-objects.c145
-rw-r--r--tests/test-gtk-objects.jpgbin0 -> 14376 bytes
-rw-r--r--tests/test-gtk-shortcut-client.c76
-rw-r--r--tests/test-gtk-shortcut-server.c99
-rw-r--r--tests/test-gtk-submenu-client.c150
-rw-r--r--tests/test-gtk-submenu-server.c113
-rw-r--r--tests/test-json-01.json4023
-rw-r--r--tests/test-json-client.c75
-rw-r--r--tests/test-json-server.c81
-rw-r--r--tools/Makefile.am7
-rw-r--r--tools/dbusmenu-dumper.c343
45 files changed, 7432 insertions, 230 deletions
diff --git a/.bzrignore b/.bzrignore
index 1e0108e..c1772dc 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -49,8 +49,8 @@ tests/test-gtk-reorder-server.c
tests/test-gtk-reorder-server
tests/test-gtk-reorder
tools/dbusmenu-dumper
-libdbusmenu-[0-9].[0-9].[0-9].tar.gz
-libdbusmenu-[0-9].[0-9].[0-9].tar.gz.asc
+libdbusmenu-[0-9]*.[0-9]*.[0-9]*.tar.gz
+libdbusmenu-[0-9]*.[0-9]*.[0-9]*.tar.gz.asc
tests/test-mago
tests/*.bustle
libdbusmenu-gtk/libdbusmenu_gtk_la-genericmenuitem.lo
@@ -71,6 +71,135 @@ libdbusmenu-gtk/DbusmenuGtk-0.2.gir
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
+docs/libdbusmenu-glib/reference/html-build.stamp
+docs/libdbusmenu-glib/reference/html.stamp
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-decl-list.txt
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-decl-list.txt.bak
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-decl.txt
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-decl.txt.bak
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-overrides.txt
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-undeclared.txt
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-undocumented.txt
+docs/libdbusmenu-glib/reference/libdbusmenu-glib-unused.txt
+docs/libdbusmenu-glib/reference/libdbusmenu-glib.args
+docs/libdbusmenu-glib/reference/libdbusmenu-glib.hierarchy
+docs/libdbusmenu-glib/reference/libdbusmenu-glib.interfaces
+docs/libdbusmenu-glib/reference/libdbusmenu-glib.prerequisites
+docs/libdbusmenu-glib/reference/libdbusmenu-glib.signals
+docs/libdbusmenu-glib/reference/scan-build.stamp
+docs/libdbusmenu-glib/reference/sgml-build.stamp
+docs/libdbusmenu-glib/reference/sgml.stamp
+docs/libdbusmenu-glib/reference/tmpl-build.stamp
+docs/libdbusmenu-glib/reference/tmpl.stamp
+docs/libdbusmenu-glib/reference/version.xml
+docs/libdbusmenu-glib/reference/xml
+docs/libdbusmenu-glib/reference/html/annotation-glossary.html
+docs/libdbusmenu-glib/reference/html/api-index-full.html
+docs/libdbusmenu-glib/reference/html/ch01.html
+docs/libdbusmenu-glib/reference/html/home.png
+docs/libdbusmenu-glib/reference/html/index.html
+docs/libdbusmenu-glib/reference/html/index.sgml
+docs/libdbusmenu-glib/reference/html/left.png
+docs/libdbusmenu-glib/reference/html/libdbusmenu-glib-DbusmenuClient.html
+docs/libdbusmenu-glib/reference/html/libdbusmenu-glib-DbusmenuClientMenuitem.html
+docs/libdbusmenu-glib/reference/html/libdbusmenu-glib-DbusmenuMenuitem.html
+docs/libdbusmenu-glib/reference/html/libdbusmenu-glib-DbusmenuMenuitemProxy.html
+docs/libdbusmenu-glib/reference/html/libdbusmenu-glib-DbusmenuServer.html
+docs/libdbusmenu-glib/reference/html/libdbusmenu-glib.devhelp
+docs/libdbusmenu-glib/reference/html/libdbusmenu-glib.devhelp2
+docs/libdbusmenu-glib/reference/html/object-tree.html
+docs/libdbusmenu-glib/reference/html/right.png
+docs/libdbusmenu-glib/reference/html/style.css
+docs/libdbusmenu-glib/reference/html/up.png
+docs/libdbusmenu-glib/reference/tmpl/client-menuitem.sgml
+docs/libdbusmenu-glib/reference/tmpl/client-menuitem.sgml.bak
+docs/libdbusmenu-glib/reference/tmpl/client.sgml
+docs/libdbusmenu-glib/reference/tmpl/client.sgml.bak
+docs/libdbusmenu-glib/reference/tmpl/libdbusmenu-glib-unused.sgml
+docs/libdbusmenu-glib/reference/tmpl/menuitem-proxy.sgml
+docs/libdbusmenu-glib/reference/tmpl/menuitem-proxy.sgml.bak
+docs/libdbusmenu-glib/reference/tmpl/menuitem.sgml
+docs/libdbusmenu-glib/reference/tmpl/menuitem.sgml.bak
+docs/libdbusmenu-glib/reference/tmpl/server.sgml
+docs/libdbusmenu-glib/reference/tmpl/server.sgml.bak
+docs/libdbusmenu-gtk/reference/html-build.stamp
+docs/libdbusmenu-gtk/reference/html.stamp
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-decl-list.txt
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-decl-list.txt.bak
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-decl.txt
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-decl.txt.bak
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-overrides.txt
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-sections.txt
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-undeclared.txt
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-undocumented.txt
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-unused.txt
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk.args
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk.hierarchy
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk.interfaces
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk.prerequisites
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk.signals
+docs/libdbusmenu-gtk/reference/libdbusmenu-gtk.types
+docs/libdbusmenu-gtk/reference/scan-build.stamp
+docs/libdbusmenu-gtk/reference/sgml-build.stamp
+docs/libdbusmenu-gtk/reference/sgml.stamp
+docs/libdbusmenu-gtk/reference/tmpl-build.stamp
+docs/libdbusmenu-gtk/reference/tmpl.stamp
+docs/libdbusmenu-gtk/reference/version.xml
+docs/libdbusmenu-gtk/reference/xml
+docs/libdbusmenu-gtk/reference/html/Genericmenuitem.html
+docs/libdbusmenu-gtk/reference/html/api-index-full.html
+docs/libdbusmenu-gtk/reference/html/ch01.html
+docs/libdbusmenu-gtk/reference/html/home.png
+docs/libdbusmenu-gtk/reference/html/index.html
+docs/libdbusmenu-gtk/reference/html/index.sgml
+docs/libdbusmenu-gtk/reference/html/left.png
+docs/libdbusmenu-gtk/reference/html/libdbusmenu-gtk-DbusmenuGtkClient.html
+docs/libdbusmenu-gtk/reference/html/libdbusmenu-gtk-DbusmenuGtkMenu.html
+docs/libdbusmenu-gtk/reference/html/libdbusmenu-gtk-menuitem.html
+docs/libdbusmenu-gtk/reference/html/libdbusmenu-gtk.devhelp
+docs/libdbusmenu-gtk/reference/html/libdbusmenu-gtk.devhelp2
+docs/libdbusmenu-gtk/reference/html/object-tree.html
+docs/libdbusmenu-gtk/reference/html/right.png
+docs/libdbusmenu-gtk/reference/html/style.css
+docs/libdbusmenu-gtk/reference/html/up.png
+docs/libdbusmenu-gtk/reference/tmpl/client.sgml
+docs/libdbusmenu-gtk/reference/tmpl/client.sgml.bak
+docs/libdbusmenu-gtk/reference/tmpl/genericmenuitem.sgml
+docs/libdbusmenu-gtk/reference/tmpl/genericmenuitem.sgml.bak
+docs/libdbusmenu-gtk/reference/tmpl/libdbusmenu-gtk-unused.sgml
+docs/libdbusmenu-gtk/reference/tmpl/menu.sgml
+docs/libdbusmenu-gtk/reference/tmpl/menu.sgml.bak
+docs/libdbusmenu-gtk/reference/tmpl/menuitem.sgml
+docs/libdbusmenu-gtk/reference/tmpl/menuitem.sgml.bak
+gtk-doc.make
+m4/gtk-doc.m4
+tests/dbusmenu-jsonloader.pc
+tests/libdbusmenu-jsonloader.la
+tests/libdbusmenu_jsonloader_la-json-loader.lo
+tests/test-json-server
+tests/test-json-client
+tests/test-json
+libdbusmenu-glib/client-marshal.c
+libdbusmenu-glib/client-marshal.h
+libdbusmenu-glib/libdbusmenu_glib_la-client-marshal.lo
+tests/test-glib-events
+tests/test-glib-events-client
+tests/test-glib-events-server
+libdbusmenu-gtk/DbusmenuGtk3-0.2.gir
+libdbusmenu-gtk/DbusmenuGtk3-0.2.tmp.gir
+libdbusmenu-gtk/DbusmenuGtk3-0.2.typelib
+libdbusmenu-gtk/dbusmenu-gtk3.pc
+libdbusmenu-gtk/libdbusmenu-gtk3.la
+libdbusmenu-gtk/libdbusmenu_gtk3_la-client.lo
+libdbusmenu-gtk/libdbusmenu_gtk3_la-genericmenuitem.lo
+libdbusmenu-gtk/libdbusmenu_gtk3_la-menu.lo
+libdbusmenu-gtk/libdbusmenu_gtk3_la-menuitem.lo
diff --git a/Makefile.am b/Makefile.am
index 3853d2a..b5fff3e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -12,3 +12,29 @@ SUBDIRS = \
po
DISTCHECK_CONFIGURE_FLAGS = --enable-introspection --enable-gtk-doc
+
+dist-hook:
+ @if test -d "$(top_srcdir)/.bzr"; \
+ then \
+ echo Creating ChangeLog && \
+ ( cd "$(top_srcdir)" && \
+ echo '# Generated by Makefile. Do not edit.'; echo; \
+ $(top_srcdir)/missing --run bzr log --gnu-changelog ) > ChangeLog.tmp \
+ && mv -f ChangeLog.tmp $(top_distdir)/ChangeLog \
+ || (rm -f ChangeLog.tmp; \
+ echo Failed to generate ChangeLog >&2 ); \
+ else \
+ echo Failed to generate ChangeLog: not a branch >&2; \
+ fi
+ @if test -d "$(top_srcdir)/.bzr"; \
+ then \
+ echo Creating AUTHORS && \
+ ( cd "$(top_srcdir)" && \
+ echo '# Generated by Makefile. Do not edit.'; echo; \
+ $(top_srcdir)/missing --run bzr log --long --levels=0 | grep -e "^\s*author:" -e "^\s*committer:" | cut -d ":" -f 2 | cut -d "<" -f 1 | sort -u) > AUTHORS.tmp \
+ && mv -f AUTHORS.tmp $(top_distdir)/AUTHORS \
+ || (rm -f AUTHORS.tmp; \
+ echo Failed to generate AUTHORS >&2 ); \
+ else \
+ echo Failed to generate AUTHORS: not a branch >&2; \
+ fi
diff --git a/autogen.sh b/autogen.sh
index 93174f5..61260e2 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -9,5 +9,4 @@ which gnome-autogen.sh || {
USE_GNOME2_MACROS=1 \
USE_COMMON_DOC_BUILD=yes \
-gnome-autogen.sh --enable-gtk-doc
-
+gnome-autogen.sh --enable-gtk-doc $@
diff --git a/configure.ac b/configure.ac
index a525612..2bd8042 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,11 @@
-AC_INIT(libdbusmenu, 0.3.2, ted@canonical.com)
+AC_INIT(libdbusmenu, 0.3.16, ted@canonical.com)
AC_COPYRIGHT([Copyright 2009,2010 Canonical])
AC_PREREQ(2.62)
AM_CONFIG_HEADER(config.h)
-AM_INIT_AUTOMAKE(libdbusmenu, 0.3.2, [-Wno-portability])
+AM_INIT_AUTOMAKE(libdbusmenu, 0.3.16, [-Wno-portability])
AM_MAINTAINER_MODE
@@ -77,12 +77,26 @@ AS_IF([test "x$with_gtk" = x3],
AM_CONDITIONAL(USE_GTK3, [test "x$with_gtk" = x3])
###########################
+# 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
###########################
JSON_GLIB_REQUIRED_VERSION=0.6.0
+GIO_UNIX_REQUIRED_VERSION=2.24
-PKG_CHECK_MODULES(DBUSMENUTESTS, json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION)
+PKG_CHECK_MODULES(DBUSMENUTESTS, json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION
+ gio-unix-2.0 >= $GIO_UNIX_REQUIRED_VERSION)
AC_SUBST(DBUSMENUTESTS_CFLAGS)
AC_SUBST(DBUSMENUTESTS_LIBS)
@@ -103,8 +117,8 @@ AC_PATH_PROG([VALA_API_GEN], [vapigen])
# Lib versioning
###########################
-LIBDBUSMENU_CURRENT=1
-LIBDBUSMENU_REVISION=7
+LIBDBUSMENU_CURRENT=2
+LIBDBUSMENU_REVISION=0
LIBDBUSMENU_AGE=0
AC_SUBST(LIBDBUSMENU_CURRENT)
@@ -146,6 +160,7 @@ libdbusmenu-gtk/dbusmenu-gtk3.pc
tools/Makefile
tools/testapp/Makefile
tests/Makefile
+tests/dbusmenu-jsonloader.pc
docs/Makefile
docs/libdbusmenu-glib/Makefile
docs/libdbusmenu-glib/reference/Makefile
diff --git a/libdbusmenu-glib/Makefile.am b/libdbusmenu-glib/Makefile.am
index 3df1513..0a6513f 100644
--- a/libdbusmenu-glib/Makefile.am
+++ b/libdbusmenu-glib/Makefile.am
@@ -4,6 +4,7 @@ CLEANFILES =
EXTRA_DIST = \
dbusmenu-glib.pc.in \
dbus-menu.xml \
+ client-marshal.list \
menuitem-marshal.list \
server-marshal.list
@@ -32,6 +33,8 @@ libdbusmenu_glib_la_SOURCES = \
server.c \
server-marshal.h \
server-marshal.c \
+ client-marshal.h \
+ client-marshal.c \
client-menuitem.h \
client-menuitem.c \
client.h \
@@ -54,6 +57,8 @@ pkgconfigdir = $(libdir)/pkgconfig
BUILT_SOURCES = \
dbusmenu-client.h \
dbusmenu-server.h \
+ client-marshal.h \
+ client-marshal.c \
menuitem-marshal.h \
menuitem-marshal.c \
server-marshal.h \
@@ -73,6 +78,16 @@ dbusmenu-client.h: dbus-menu.xml
--output=dbusmenu-client.h \
$(srcdir)/dbus-menu.xml
+client-marshal.h: $(srcdir)/client-marshal.list
+ glib-genmarshal --header \
+ --prefix=_dbusmenu_client_marshal $(srcdir)/client-marshal.list \
+ > client-marshal.h
+
+client-marshal.c: $(srcdir)/client-marshal.list
+ glib-genmarshal --body \
+ --prefix=_dbusmenu_client_marshal $(srcdir)/client-marshal.list \
+ > client-marshal.c
+
server-marshal.h: $(srcdir)/server-marshal.list
glib-genmarshal --header \
--prefix=_dbusmenu_server_marshal $(srcdir)/server-marshal.list \
diff --git a/libdbusmenu-glib/client-marshal.list b/libdbusmenu-glib/client-marshal.list
new file mode 100644
index 0000000..2e14491
--- /dev/null
+++ b/libdbusmenu-glib/client-marshal.list
@@ -0,0 +1,2 @@
+VOID: OBJECT, UINT
+VOID: OBJECT, STRING, POINTER, UINT, POINTER
diff --git a/libdbusmenu-glib/client-menuitem.c b/libdbusmenu-glib/client-menuitem.c
index 979cf79..9c21065 100644
--- a/libdbusmenu-glib/client-menuitem.c
+++ b/libdbusmenu-glib/client-menuitem.c
@@ -46,7 +46,7 @@ static void dbusmenu_client_menuitem_init (DbusmenuClientMenuitem *self);
static void dbusmenu_client_menuitem_dispose (GObject *object);
static void dbusmenu_client_menuitem_finalize (GObject *object);
static void handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp);
-static void send_about_to_show (DbusmenuMenuitem * mi, dbusmenu_menuitem_about_to_show_cb cb, gpointer cb_data);
+static void send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data);
G_DEFINE_TYPE (DbusmenuClientMenuitem, dbusmenu_client_menuitem, DBUSMENU_TYPE_MENUITEM);
@@ -112,7 +112,7 @@ handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, g
typedef struct _about_to_show_t about_to_show_t;
struct _about_to_show_t {
DbusmenuMenuitem * mi;
- dbusmenu_menuitem_about_to_show_cb cb;
+ void (*cb) (DbusmenuMenuitem * mi, gpointer user_data);
gpointer cb_data;
};
@@ -131,7 +131,7 @@ about_to_show_cb (gpointer user_data)
/* Passes the about to show signal on through the client. */
static void
-send_about_to_show (DbusmenuMenuitem * mi, dbusmenu_menuitem_about_to_show_cb cb, gpointer cb_data)
+send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data)
{
DbusmenuClientMenuitemPrivate * priv = DBUSMENU_CLIENT_MENUITEM_GET_PRIVATE(mi);
if (cb == NULL) {
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
index fa233a4..dae1dd7 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -41,6 +41,11 @@ License version 3 and version 2.1 along with this program. If not, see
#include "client-menuitem.h"
#include "dbusmenu-client.h"
#include "server-marshal.h"
+#include "client-marshal.h"
+
+/* How many property requests should we queue before
+ sending the message on dbus */
+#define MAX_PROPERTIES_TO_QUEUE 100
/* Properties */
enum {
@@ -54,12 +59,13 @@ enum {
LAYOUT_UPDATED,
ROOT_CHANGED,
NEW_MENUITEM,
+ ITEM_ACTIVATE,
+ EVENT_RESULT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
-typedef struct _DbusmenuClientPrivate DbusmenuClientPrivate;
struct _DbusmenuClientPrivate
{
DbusmenuMenuitem * root;
@@ -78,6 +84,10 @@ struct _DbusmenuClientPrivate
DBusGProxy * dbusproxy;
GHashTable * type_handlers;
+
+ GArray * delayed_property_list;
+ GArray * delayed_property_listeners;
+ gint delayed_idle;
};
typedef struct _newItemPropData newItemPropData;
@@ -88,8 +98,25 @@ struct _newItemPropData
DbusmenuMenuitem * parent;
};
-#define DBUSMENU_CLIENT_GET_PRIVATE(o) \
-(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate))
+typedef struct _properties_listener_t properties_listener_t;
+struct _properties_listener_t {
+ gint id;
+ org_ayatana_dbusmenu_get_properties_reply callback;
+ gpointer user_data;
+ gboolean replied;
+};
+
+typedef struct _event_data_t event_data_t;
+struct _event_data_t {
+ DbusmenuClient * client;
+ DbusmenuMenuitem * menuitem;
+ gchar * event;
+ GValue data;
+ guint timestamp;
+};
+
+
+#define DBUSMENU_CLIENT_GET_PRIVATE(o) (DBUSMENU_CLIENT(o)->priv)
/* GObject Stuff */
static void dbusmenu_client_class_init (DbusmenuClientClass *klass);
@@ -109,6 +136,9 @@ static gint parse_layout (DbusmenuClient * client, const gchar * layout);
static void update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * in_error, void * data);
static void update_layout (DbusmenuClient * client);
static void menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data);
+static void get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, org_ayatana_dbusmenu_get_properties_reply callback, gpointer user_data);
+static GQuark error_domain (void);
+static void item_activated (DBusGProxy * proxy, gint id, guint timestamp, DbusmenuClient * client);
/* Build a type */
G_DEFINE_TYPE (DbusmenuClient, dbusmenu_client, G_TYPE_OBJECT);
@@ -173,6 +203,41 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass)
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
+ /**
+ DbusmenuClient::item-activate:
+ @arg0: The #DbusmenuClient object
+ @arg1: The #DbusmenuMenuitem activated
+ @arg2: A timestamp that the event happened at
+
+ Signaled when the server wants to activate an item in
+ order to display the menu.
+ */
+ signals[ITEM_ACTIVATE] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE,
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DbusmenuClientClass, item_activate),
+ NULL, NULL,
+ _dbusmenu_client_marshal_VOID__OBJECT_UINT,
+ G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT);
+ /**
+ DbusmenuClient::event-error:
+ @arg0: The #DbusmenuClient object
+ @arg1: The #DbusmenuMenuitem sent an event
+ @arg2: The ID of the event sent
+ @arg3: The data sent along with the event
+ @arg4: A timestamp that the event happened at
+ @arg5: Possibly the error in sending the event (or NULL)
+
+ Signal sent to show that there was an error in sending the event
+ to the server.
+ */
+ signals[EVENT_RESULT] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT,
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DbusmenuClientClass, event_result),
+ NULL, NULL,
+ _dbusmenu_client_marshal_VOID__OBJECT_STRING_POINTER_UINT_POINTER,
+ G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_POINTER);
g_object_class_install_property (object_class, PROP_DBUSOBJECT,
g_param_spec_string(DBUSMENU_CLIENT_PROP_DBUS_OBJECT, "DBus Object we represent",
@@ -191,6 +256,8 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass)
static void
dbusmenu_client_init (DbusmenuClient *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate);
+
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(self);
priv->root = NULL;
@@ -211,6 +278,10 @@ dbusmenu_client_init (DbusmenuClient *self)
priv->type_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
+ priv->delayed_idle = 0;
+ priv->delayed_property_list = g_array_new(TRUE, FALSE, sizeof(gchar *));
+ priv->delayed_property_listeners = g_array_new(FALSE, FALSE, sizeof(properties_listener_t));
+
return;
}
@@ -219,6 +290,44 @@ dbusmenu_client_dispose (GObject *object)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(object);
+ if (priv->delayed_idle != 0) {
+ g_source_remove(priv->delayed_idle);
+ priv->delayed_idle = 0;
+ }
+
+ /* Only used for queueing up a new command, so we can
+ just drop this array. */
+ if (priv->delayed_property_list == NULL) {
+ gchar ** dataregion = (gchar **)g_array_free(priv->delayed_property_list, FALSE);
+ if (dataregion != NULL) {
+ g_strfreev(dataregion);
+ }
+ priv->delayed_property_list = NULL;
+ }
+
+ if (priv->delayed_property_listeners == NULL) {
+ gint i;
+ GError * localerror = NULL;
+
+ /* Making sure all the callbacks get called so that if they had
+ memory in their user_data that needs to be free'd that happens. */
+ for (i = 0; i < priv->delayed_property_listeners->len; i++) {
+ properties_listener_t * listener = &g_array_index(priv->delayed_property_listeners, properties_listener_t, i);
+ if (!listener->replied) {
+ if (localerror == NULL) {
+ g_set_error_literal(&localerror, error_domain(), 0, "DbusmenuClient Shutdown");
+ }
+ listener->callback(priv->menuproxy, NULL, localerror, listener->user_data);
+ }
+ }
+ if (localerror != NULL) {
+ g_error_free(localerror);
+ }
+
+ g_array_free(priv->delayed_property_listeners, TRUE);
+ priv->delayed_property_listeners = NULL;
+ }
+
if (priv->layoutcall != NULL) {
dbus_g_proxy_cancel_call(priv->menuproxy, priv->layoutcall);
priv->layoutcall = NULL;
@@ -310,6 +419,253 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
/* Internal funcs */
+static GQuark
+error_domain (void)
+{
+ static GQuark error = 0;
+ if (error == 0) {
+ error = g_quark_from_static_string(G_LOG_DOMAIN "-CLIENT");
+ }
+ return error;
+}
+
+/* Quick little function to search through the listeners and find
+ one that matches an ID */
+static properties_listener_t *
+find_listener (GArray * listeners, guint index, gint id)
+{
+ if (index >= listeners->len) {
+ return NULL;
+ }
+
+ properties_listener_t * retval = &g_array_index(listeners, properties_listener_t, index);
+ if (retval->id == id) {
+ return retval;
+ }
+
+ return find_listener(listeners, index + 1, id);
+}
+
+/* Call back from getting the group properties, now we need
+ to unwind and call the various functions. */
+static void
+get_properties_callback (DBusGProxy *proxy, GPtrArray *OUT_properties, GError *error, gpointer userdata)
+{
+ GArray * listeners = (GArray *)userdata;
+ int i;
+
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Get properties callback: %d", OUT_properties->len);
+ #endif
+
+ if (error != NULL) {
+ /* If we get an error, all our callbacks need to hear about it. */
+ g_warning("Group Properties error: %s", error->message);
+ for (i = 0; i < listeners->len; i++) {
+ properties_listener_t * listener = &g_array_index(listeners, properties_listener_t, i);
+ listener->callback(proxy, NULL, error, listener->user_data);
+ }
+ g_array_free(listeners, TRUE);
+ return;
+ }
+
+ /* Callback all the folks we can find */
+ for (i = 0; i < OUT_properties->len; i++) {
+ GValueArray * varray = (GValueArray *)g_ptr_array_index(OUT_properties, i);
+
+ if (varray->n_values != 2) {
+ g_warning("Value Array is %d entries long but we expected 2.", varray->n_values);
+ continue;
+ }
+
+ GValue * vid = g_value_array_get_nth(varray, 0);
+ GValue * vproperties = g_value_array_get_nth(varray, 1);
+
+ if (G_VALUE_TYPE(vid) != G_TYPE_INT) {
+ g_warning("ID Entry not holding an int: %s", G_VALUE_TYPE_NAME(vid));
+ }
+ if (G_VALUE_TYPE(vproperties) != dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) {
+ g_warning("Properties Entry not holding an a{sv}: %s", G_VALUE_TYPE_NAME(vproperties));
+ }
+
+ gint id = g_value_get_int(vid);
+ GHashTable * properties = g_value_get_boxed(vproperties);
+
+ properties_listener_t * listener = find_listener(listeners, 0, id);
+ if (listener == NULL) {
+ g_warning("Unable to find listener for ID %d", id);
+ continue;
+ }
+
+ if (!listener->replied) {
+ listener->callback(proxy, properties, NULL, listener->user_data);
+ listener->replied = TRUE;
+ } else {
+ g_warning("Odd, we've already replied to the listener on ID %d", id);
+ }
+ }
+
+ /* Provide errors for those who we can't */
+ GError * localerror = NULL;
+ for (i = 0; i < listeners->len; i++) {
+ properties_listener_t * listener = &g_array_index(listeners, properties_listener_t, i);
+ if (!listener->replied) {
+ if (localerror == NULL) {
+ g_set_error_literal(&localerror, error_domain(), 0, "Error getting properties for ID");
+ }
+ listener->callback(proxy, NULL, localerror, listener->user_data);
+ }
+ }
+ if (localerror != NULL) {
+ g_error_free(localerror);
+ }
+
+ /* Clean up */
+ g_array_free(listeners, TRUE);
+
+ return;
+}
+
+/* Idle handler to send out all of our property requests as one big
+ lovely property request. */
+static gboolean
+get_properties_idle (gpointer user_data)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(user_data);
+ //org_ayatana_dbusmenu_get_properties_async(priv->menuproxy, id, properties, callback, user_data);
+
+ if (priv->delayed_property_listeners->len == 0) {
+ g_warning("Odd, idle func got no listeners.");
+ return FALSE;
+ }
+
+ /* Build up an ID list to pass */
+ GArray * idlist = g_array_new(FALSE, FALSE, sizeof(gint));
+ gint i;
+ for (i = 0; i < priv->delayed_property_listeners->len; i++) {
+ g_array_append_val(idlist, g_array_index(priv->delayed_property_listeners, properties_listener_t, i).id);
+ }
+
+ org_ayatana_dbusmenu_get_group_properties_async(priv->menuproxy, idlist, (const gchar **)priv->delayed_property_list->data, get_properties_callback, priv->delayed_property_listeners);
+
+ /* Free ID List */
+ g_array_free(idlist, TRUE);
+
+ /* Free properties */
+ gchar ** dataregion = (gchar **)g_array_free(priv->delayed_property_list, FALSE);
+ if (dataregion != NULL) {
+ g_strfreev(dataregion);
+ }
+ priv->delayed_property_list = g_array_new(TRUE, FALSE, sizeof(gchar *));
+
+ /* Rebuild the listeners */
+ priv->delayed_property_listeners = g_array_new(FALSE, FALSE, sizeof(properties_listener_t));
+
+ /* Make sure we set for a new idle */
+ priv->delayed_idle = 0;
+
+ return FALSE;
+}
+
+/* Forces a call out to start getting properties with the menu items
+ that we have queued up already. */
+static void
+get_properties_flush (DbusmenuClient * client)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (priv->delayed_idle == 0) {
+ return;
+ }
+
+ g_source_remove(priv->delayed_idle);
+ priv->delayed_idle = 0;
+
+ get_properties_idle(client);
+
+ dbus_g_connection_flush(priv->session_bus);
+
+ return;
+}
+
+/* A function to group all the get_properties commands to make them
+ more efficient over dbus. */
+static void
+get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, org_ayatana_dbusmenu_get_properties_reply callback, gpointer user_data)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ if (find_listener(priv->delayed_property_listeners, 0, id) != NULL) {
+ g_warning("Asking for properties from same ID twice: %d", id);
+ GError * localerror = NULL;
+ g_set_error_literal(&localerror, error_domain(), 0, "ID already queued");
+ callback(priv->menuproxy, NULL, localerror, user_data);
+ g_error_free(localerror);
+ return;
+ }
+
+ if (properties == NULL || properties[0] == NULL) {
+ /* get all case */
+ if (priv->delayed_property_list->len != 0) {
+ /* If there are entries in the list, then we'll need to
+ remove them all, and start over */
+ gchar ** dataregion = (gchar **)g_array_free(priv->delayed_property_list, FALSE);
+ if (dataregion != NULL) {
+ g_strfreev(dataregion);
+ }
+ priv->delayed_property_list = g_array_new(TRUE, FALSE, sizeof(gchar *));
+ }
+ } else {
+ /* there could be a list we care about */
+ /* TODO: No one uses this today */
+ /* TODO: Copy them into the list */
+ }
+
+ properties_listener_t listener = {0};
+ listener.id = id;
+ listener.callback = callback;
+ listener.user_data = user_data;
+ listener.replied = FALSE;
+
+ g_array_append_val(priv->delayed_property_listeners, listener);
+
+ if (priv->delayed_idle == 0) {
+ priv->delayed_idle = g_idle_add(get_properties_idle, client);
+ }
+
+ /* Look at how many proprites we have queued up and
+ make it so that we don't leave too many in one
+ request. */
+ if (priv->delayed_property_listeners->len == MAX_PROPERTIES_TO_QUEUE) {
+ get_properties_flush(client);
+ }
+
+ return;
+}
+
+/* Called when a server item wants to activate the menu */
+static void
+item_activated (DBusGProxy * proxy, gint id, guint timestamp, DbusmenuClient * client)
+{
+ g_return_if_fail(DBUSMENU_IS_CLIENT(client));
+
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (priv->root == NULL) {
+ g_warning("Asked to activate item %d when we don't have a menu structure.", id);
+ return;
+ }
+
+ DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
+ if (menuitem == NULL) {
+ g_warning("Unable to find menu item %d to activate.", id);
+ return;
+ }
+
+ g_signal_emit(G_OBJECT(client), signals[ITEM_ACTIVATE], 0, menuitem, timestamp, TRUE);
+
+ return;
+}
+
/* Annoying little wrapper to make the right function update */
static void
layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient * client)
@@ -367,10 +723,9 @@ id_update (DBusGProxy * proxy, gint id, DbusmenuClient * client)
DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
g_return_if_fail(menuitem != NULL);
- gchar * properties[1] = {NULL}; /* This gets them all */
g_debug("Getting properties");
g_object_ref(menuitem);
- org_ayatana_dbusmenu_get_properties_async(proxy, id, (const gchar **)properties, menuitem_get_properties_cb, menuitem);
+ get_properties_globber(client, id, NULL, menuitem_get_properties_cb, menuitem);
return;
}
@@ -550,6 +905,10 @@ build_proxies (DbusmenuClient * client)
dbus_g_proxy_add_signal(priv->menuproxy, "ItemUpdated", G_TYPE_INT, G_TYPE_INVALID);
dbus_g_proxy_connect_signal(priv->menuproxy, "ItemUpdated", G_CALLBACK(id_update), client, NULL);
+ dbus_g_object_register_marshaller(_dbusmenu_server_marshal_VOID__INT_UINT, G_TYPE_NONE, G_TYPE_INT, G_TYPE_UINT, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(priv->menuproxy, "ItemActivationRequested", G_TYPE_INT, G_TYPE_UINT, G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal(priv->menuproxy, "ItemActivationRequested", G_CALLBACK(item_activated), client, NULL);
+
update_layout(client);
return;
@@ -561,6 +920,9 @@ build_proxies (DbusmenuClient * client)
static gint
parse_node_get_id (xmlNodePtr node)
{
+ if (node == NULL) {
+ return -1;
+ }
if (node->type != XML_ELEMENT_NODE) {
return -1;
}
@@ -652,16 +1014,19 @@ menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties,
static void
menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
{
+ g_return_if_fail(data != NULL);
+ newItemPropData * propdata = (newItemPropData *)data;
+
if (error != NULL) {
g_warning("Error getting properties on a new menuitem: %s", error->message);
- g_object_unref(data);
+ g_object_unref(propdata->item);
+ g_free(data);
return;
}
- g_return_if_fail(data != NULL);
- newItemPropData * propdata = (newItemPropData *)data;
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(propdata->client);
+ /* Extra ref as get_properties will unref once itself */
g_object_ref(propdata->item);
menuitem_get_properties_cb (proxy, properties, error, propdata->item);
@@ -701,10 +1066,19 @@ menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GEr
static void
menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
{
+ event_data_t * edata = (event_data_t *)userdata;
+
if (error != NULL) {
- g_warning("Unable to call menu item %d: %s", GPOINTER_TO_INT(userdata), error->message);
+ g_warning("Unable to call event '%s' on menu item %d: %s", edata->event, dbusmenu_menuitem_get_id(edata->menuitem), error->message);
}
+ g_signal_emit(edata->client, signals[EVENT_RESULT], 0, edata->menuitem, edata->event, &edata->data, edata->timestamp, error, TRUE);
+
+ g_value_unset(&edata->data);
+ g_free(edata->event);
+ g_object_unref(edata->menuitem);
+ g_free(edata);
+
return;
}
@@ -713,8 +1087,39 @@ menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
void
dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name, const GValue * value, guint timestamp)
{
+ g_return_if_fail(DBUSMENU_IS_CLIENT(client));
+ g_return_if_fail(id >= 0);
+ g_return_if_fail(name != NULL);
+
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- org_ayatana_dbusmenu_event_async (priv->menuproxy, id, name, value, timestamp, menuitem_call_cb, GINT_TO_POINTER(id));
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
+ if (mi == NULL) {
+ g_warning("Asked to activate a menuitem %d that we don't know about", id);
+ return;
+ }
+
+ if (value == NULL) {
+ GValue internalval = {0};
+ g_value_init(&internalval, G_TYPE_INT);
+ g_value_set_int(&internalval, 0);
+ value = &internalval;
+ }
+
+ event_data_t * edata = g_new0(event_data_t, 1);
+ edata->client = client;
+ edata->menuitem = mi;
+ g_object_ref(edata->menuitem);
+ edata->event = g_strdup(name);
+ g_value_init(&edata->data, G_VALUE_TYPE(value));
+ g_value_copy(value, &edata->data);
+ edata->timestamp = timestamp;
+
+ DBusGAsyncData *stuff;
+ stuff = g_slice_new (DBusGAsyncData);
+ stuff->cb = G_CALLBACK (menuitem_call_cb);
+ stuff->userdata = edata;
+ dbus_g_proxy_begin_call_with_timeout (priv->menuproxy, "Event", org_ayatana_dbusmenu_event_async_callback, stuff, _dbus_glib_async_data_free, 1000, G_TYPE_INT, id, G_TYPE_STRING, name, G_TYPE_VALUE, value, G_TYPE_UINT, timestamp, G_TYPE_INVALID);
+
return;
}
@@ -770,11 +1175,51 @@ dbusmenu_client_send_about_to_show(DbusmenuClient * client, gint id, void (*cb)(
return;
}
+/* Builds a new child with property requests and everything
+ else to clean up the code a bit */
+static DbusmenuMenuitem *
+parse_layout_new_child (gint id, DbusmenuClient * client, DbusmenuMenuitem * parent)
+{
+ DbusmenuMenuitem * item = NULL;
+
+ /* Build a new item */
+ item = DBUSMENU_MENUITEM(dbusmenu_client_menuitem_new(id, client));
+ if (parent == NULL) {
+ dbusmenu_menuitem_set_root(item, TRUE);
+ }
+
+ /* Get the properties queued up for this item */
+ /* Not happy allocating about this, but I need these :( */
+ newItemPropData * propdata = g_new0(newItemPropData, 1);
+ if (propdata != NULL) {
+ propdata->client = client;
+ propdata->item = item;
+ propdata->parent = parent;
+
+ g_object_ref(item);
+ get_properties_globber(client, id, NULL, menuitem_get_properties_new_cb, propdata);
+ } else {
+ g_warning("Unable to allocate memory to get properties for menuitem. This menuitem will never be realized.");
+ }
+
+ return item;
+}
+
+/* Refresh the properties on this item */
+static void
+parse_layout_update (DbusmenuMenuitem * item, DbusmenuClient * client)
+{
+ g_object_ref(item);
+ get_properties_globber(client, dbusmenu_menuitem_get_id(item), NULL, menuitem_get_properties_replace_cb, item);
+ return;
+}
+
/* Parse recursively through the XML and make it into
objects as need be */
static DbusmenuMenuitem *
parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy)
{
+ /* First verify and figure out what we've got */
gint id = parse_node_get_id(node);
if (id < 0) {
return NULL;
@@ -782,56 +1227,31 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it
#ifdef MASSIVEDEBUGGING
g_debug("Client looking at node with id: %d", id);
#endif
- /* If we don't have any item, or the IDs don't match */
- if (item == NULL || dbusmenu_menuitem_get_id(item) != id) {
- if (item != NULL) {
- if (parent != NULL) {
- dbusmenu_menuitem_child_delete(parent, item);
- }
- item = NULL;
- }
- /* Build a new item */
- item = DBUSMENU_MENUITEM(dbusmenu_client_menuitem_new(id, client));
- if (parent == NULL) {
- dbusmenu_menuitem_set_root(item, TRUE);
- }
-
- /* Get the properties queued up for this item */
- /* Not happy about this, but I need these :( */
- newItemPropData * propdata = g_new0(newItemPropData, 1);
- if (propdata != NULL) {
- propdata->client = client;
- propdata->item = item;
- propdata->parent = parent;
-
- gchar * properties[1] = {NULL}; /* This gets them all */
- g_object_ref(item);
- org_ayatana_dbusmenu_get_properties_async(proxy, id, (const gchar **)properties, menuitem_get_properties_new_cb, propdata);
- } else {
- g_warning("Unable to allocate memory to get properties for menuitem. This menuitem will never be realized.");
- }
- } else {
- /* Refresh the properties */
- /* XXX: We shouldn't need to get the properties everytime we reuse an entry */
- gchar * properties[1] = {NULL}; /* This gets them all */
- g_object_ref(item);
- org_ayatana_dbusmenu_get_properties_async(proxy, id, (const gchar **)properties, menuitem_get_properties_replace_cb, item);
- }
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(id == dbusmenu_menuitem_get_id(item), NULL);
+ /* Some variables */
xmlNodePtr children;
guint position;
GList * oldchildren = g_list_copy(dbusmenu_menuitem_get_children(item));
/* g_debug("Starting old children: %d", g_list_length(oldchildren)); */
+ /* Go through all the XML Nodes and make sure that we have menuitems
+ to cover those XML nodes. */
for (children = node->children, position = 0; children != NULL; children = children->next, position++) {
/* g_debug("Looking at child: %d", position); */
gint childid = parse_node_get_id(children);
if (childid < 0) {
+ /* Don't increment the position when there isn't a valid
+ node in the XML tree. It's probably a comment. */
+ position--;
continue;
}
DbusmenuMenuitem * childmi = NULL;
+ /* First see if we can recycle a node that we've already built
+ on this menu item */
GList * childsearch = NULL;
for (childsearch = oldchildren; childsearch != NULL; childsearch = g_list_next(childsearch)) {
DbusmenuMenuitem * cs_mi = DBUSMENU_MENUITEM(childsearch->data);
@@ -842,20 +1262,26 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it
}
}
- DbusmenuMenuitem * newchildmi = parse_layout_xml(client, children, childmi, item, proxy);
-
- if (newchildmi != childmi) {
- if (childmi != NULL) {
- dbusmenu_menuitem_child_delete(item, childmi);
- }
- dbusmenu_menuitem_child_add_position(item, newchildmi, position);
- g_object_unref(newchildmi);
+ if (childmi == NULL) {
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Building new menu item %d at position %d", childid, position);
+ #endif
+ /* If we can't recycle, then we build a new one */
+ childmi = parse_layout_new_child(childid, client, item);
+ dbusmenu_menuitem_child_add_position(item, childmi, position);
+ g_object_unref(childmi);
} else {
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Recycling menu item %d at position %d", childid, position);
+ #endif
+ /* If we can recycle, make sure it's in the right place */
dbusmenu_menuitem_child_reorder(item, childmi, position);
+ parse_layout_update(childmi, client);
}
}
- /* g_debug("Stopping old children: %d", g_list_length(oldchildren)); */
+ /* Remove any children that are no longer used by this version of
+ the layout. */
GList * oldchildleft = NULL;
for (oldchildleft = oldchildren; oldchildleft != NULL; oldchildleft = g_list_next(oldchildleft)) {
DbusmenuMenuitem * oldmi = DBUSMENU_MENUITEM(oldchildleft->data);
@@ -866,6 +1292,42 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it
}
g_list_free(oldchildren);
+ /* We've got everything built up at this node and reconcilled */
+
+ /* Flush the properties requests if this is the first level */
+ if (dbusmenu_menuitem_get_id(parent) == 0) {
+ get_properties_flush(client);
+ }
+
+ /* now it's time to recurse down the tree. */
+ children = node->children;
+ GList * childmis = dbusmenu_menuitem_get_children(item);
+ while (children != NULL && childmis != NULL) {
+ gint xmlid = parse_node_get_id(children);
+ /* If this isn't a valid menu item we need to move on
+ until we have one. This avoids things like comments. */
+ if (xmlid < 0) {
+ children = children->next;
+ continue;
+ }
+
+ #ifdef MASSIVEDEBUGGING
+ gint miid = dbusmenu_menuitem_get_id(DBUSMENU_MENUITEM(childmis->data));
+ g_debug("Recursing parse_layout_xml. XML ID: %d MI ID: %d", xmlid, miid);
+ #endif
+
+ parse_layout_xml(client, children, DBUSMENU_MENUITEM(childmis->data), item, proxy);
+
+ children = children->next;
+ childmis = g_list_next(childmis);
+ }
+ if (children != NULL) {
+ g_warning("Sync failed, now we've got extra XML nodes.");
+ }
+ if (childmis != NULL) {
+ g_warning("Sync failed, now we've got extra menu items.");
+ }
+
return item;
}
@@ -882,12 +1344,23 @@ parse_layout (DbusmenuClient * client, const gchar * layout)
xmlDocPtr xmldoc;
- xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 16*1024), "dbusmenu.xml", NULL, 0);
+ /* No one should need more characters than this! */
+ xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 1024*1024), "dbusmenu.xml", NULL, 0);
xmlNodePtr root = xmlDocGetRootElement(xmldoc);
+ if (root == NULL) {
+ g_warning("Unable to get root node of menu XML");
+ }
+
DbusmenuMenuitem * oldroot = priv->root;
+ if (priv->root == NULL) {
+ priv->root = parse_layout_new_child(0, client, NULL);
+ } else {
+ parse_layout_update(priv->root, client);
+ }
+
priv->root = parse_layout_xml(client, root, priv->root, NULL, priv->menuproxy);
xmlFreeDoc(xmldoc);
diff --git a/libdbusmenu-glib/client.h b/libdbusmenu-glib/client.h
index 2b76f5e..32813d9 100644
--- a/libdbusmenu-glib/client.h
+++ b/libdbusmenu-glib/client.h
@@ -46,6 +46,8 @@ G_BEGIN_DECLS
#define DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED "layout-updated"
#define DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED "root-changed"
#define DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM "new-menuitem"
+#define DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE "item-activate"
+#define DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT "event-result"
#define DBUSMENU_CLIENT_PROP_DBUS_NAME "dbus-name"
#define DBUSMENU_CLIENT_PROP_DBUS_OBJECT "dbus-object"
@@ -54,15 +56,21 @@ G_BEGIN_DECLS
#define DBUSMENU_CLIENT_TYPES_SEPARATOR "separator"
#define DBUSMENU_CLIENT_TYPES_IMAGE "standard"
+typedef struct _DbusmenuClientPrivate DbusmenuClientPrivate;
+
/**
DbusmenuClientClass:
@parent_class: #GObjectClass
@layout_updated: Slot for #DbusmenuClient::layout-updated.
@new_menuitem: Slot for #DbusmenuClient::new-menuitem.
+ @item_activate: Slot for #DbusmenuClient::item-activate.
+ @event_result: Slot for #DbusmenuClient::event-error.
@reserved1: Reserved for future use.
@reserved2: Reserved for future use.
@reserved3: Reserved for future use.
@reserved4: Reserved for future use.
+ @reserved5: Reserved for future use.
+ @reserved6: Reserved for future use.
A simple class that takes all of the information from a
#DbusmenuServer over DBus and makes the same set of
@@ -75,12 +83,16 @@ struct _DbusmenuClientClass {
void (*layout_updated)(void);
void (*root_changed) (DbusmenuMenuitem * newroot);
void (*new_menuitem) (DbusmenuMenuitem * newitem);
+ void (*item_activate) (DbusmenuMenuitem * item, guint timestamp);
+ void (*event_result) (DbusmenuMenuitem * item, gchar * event, GValue * data, guint timestamp, GError * error);
- /* Reserved for future use */
+ /*< Private >*/
void (*reserved1) (void);
void (*reserved2) (void);
void (*reserved3) (void);
void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
};
/**
@@ -93,6 +105,9 @@ struct _DbusmenuClientClass {
typedef struct _DbusmenuClient DbusmenuClient;
struct _DbusmenuClient {
GObject parent;
+
+ /*< Private >*/
+ DbusmenuClientPrivate * priv;
};
typedef gboolean (*DbusmenuClientTypeHandler) (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
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/menuitem-proxy.c b/libdbusmenu-glib/menuitem-proxy.c
index 2dd5ada..7acb541 100644
--- a/libdbusmenu-glib/menuitem-proxy.c
+++ b/libdbusmenu-glib/menuitem-proxy.c
@@ -32,7 +32,6 @@ License version 3 and version 2.1 along with this program. If not, see
#include "menuitem-proxy.h"
-typedef struct _DbusmenuMenuitemProxyPrivate DbusmenuMenuitemProxyPrivate;
struct _DbusmenuMenuitemProxyPrivate {
DbusmenuMenuitem * mi;
gulong sig_property_changed;
@@ -49,8 +48,7 @@ enum {
#define PROP_MENU_ITEM_S "menu-item"
-#define DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(o) \
-(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyPrivate))
+#define DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(o) (DBUSMENU_MENUITEM_PROXY(o)->priv)
static void dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass);
static void dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self);
@@ -92,6 +90,8 @@ dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass)
static void
dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyPrivate);
+
DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(self);
priv->mi = NULL;
diff --git a/libdbusmenu-glib/menuitem-proxy.h b/libdbusmenu-glib/menuitem-proxy.h
index 56c4941..2a22efe 100644
--- a/libdbusmenu-glib/menuitem-proxy.h
+++ b/libdbusmenu-glib/menuitem-proxy.h
@@ -42,17 +42,28 @@ G_BEGIN_DECLS
#define DBUSMENU_IS_MENUITEM_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_MENUITEM_PROXY))
#define DBUSMENU_MENUITEM_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyClass))
-typedef struct _DbusmenuMenuitemProxy DbusmenuMenuitemProxy;
-typedef struct _DbusmenuMenuitemProxyClass DbusmenuMenuitemProxyClass;
+typedef struct _DbusmenuMenuitemProxy DbusmenuMenuitemProxy;
+typedef struct _DbusmenuMenuitemProxyClass DbusmenuMenuitemProxyClass;
+typedef struct _DbusmenuMenuitemProxyPrivate DbusmenuMenuitemProxyPrivate;
/**
DbusmenuMenuitemProxyClass:
@parent_class: The Class of #DbusmeneMenuitem
+ @reserved1: Reserved for future use.
+ @reserved2: Reserved for future use.
+ @reserved3: Reserved for future use.
+ @reserved4: Reserved for future use.
Functions and signal slots for #DbusmenuMenuitemProxy.
*/
struct _DbusmenuMenuitemProxyClass {
DbusmenuMenuitemClass parent_class;
+
+ /*< Private >*/
+ void (*reserved1) (void);
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+ void (*reserved4) (void);
};
/**
@@ -63,6 +74,9 @@ struct _DbusmenuMenuitemProxyClass {
*/
struct _DbusmenuMenuitemProxy {
DbusmenuMenuitem parent;
+
+ /*< Private >*/
+ DbusmenuMenuitemProxyPrivate * priv;
};
GType dbusmenu_menuitem_proxy_get_type (void);
diff --git a/libdbusmenu-glib/menuitem.c b/libdbusmenu-glib/menuitem.c
index 623539c..73b765b 100644
--- a/libdbusmenu-glib/menuitem.c
+++ b/libdbusmenu-glib/menuitem.c
@@ -52,7 +52,6 @@ License version 3 and version 2.1 along with this program. If not, see
out of data that we have. They can still be gotten using
accessor functions, but are protected appropriately.
*/
-typedef struct _DbusmenuMenuitemPrivate DbusmenuMenuitemPrivate;
struct _DbusmenuMenuitemPrivate
{
gint id;
@@ -70,6 +69,7 @@ enum {
CHILD_REMOVED,
CHILD_MOVED,
REALIZED,
+ SHOW_TO_USER,
LAST_SIGNAL
};
@@ -83,8 +83,7 @@ enum {
#define PROP_ID_S "id"
-#define DBUSMENU_MENUITEM_GET_PRIVATE(o) \
-(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_MENUITEM, DbusmenuMenuitemPrivate))
+#define DBUSMENU_MENUITEM_GET_PRIVATE(o) (DBUSMENU_MENUITEM(o)->priv)
/* Prototypes */
static void dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass);
@@ -96,6 +95,7 @@ static void get_property (GObject * obj, guint id, GValue * value, GParamSpec *
static void g_value_transform_STRING_BOOLEAN (const GValue * in, GValue * out);
static void g_value_transform_STRING_INT (const GValue * in, GValue * out);
static void handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp);
+static void send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data);
/* GObject stuff */
G_DEFINE_TYPE (DbusmenuMenuitem, dbusmenu_menuitem, G_TYPE_OBJECT);
@@ -113,6 +113,7 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass)
object_class->get_property = get_property;
klass->handle_event = handle_event;
+ klass->send_about_to_show = send_about_to_show;
/**
DbusmenuMenuitem::property-changed:
@@ -211,6 +212,22 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass)
NULL, NULL,
_dbusmenu_menuitem_marshal_VOID__VOID,
G_TYPE_NONE, 0, G_TYPE_NONE);
+ /**
+ DbusmenuMenuitem::show-to-user:
+ @arg0: The #DbusmenuMenuitem which should be shown.
+ @arg1: Timestamp the event happened at
+
+ Signaled when the application would like the visualization
+ of this menu item shown to the user. This usually requires
+ going over the bus to get it done.
+ */
+ signals[SHOW_TO_USER] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_SHOW_TO_USER,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(DbusmenuMenuitemClass, show_to_user),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT, G_TYPE_NONE);
g_object_class_install_property (object_class, PROP_ID,
g_param_spec_int(PROP_ID_S, "ID for the menu item",
@@ -271,6 +288,8 @@ _g_value_free (gpointer data)
static void
dbusmenu_menuitem_init (DbusmenuMenuitem *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_TYPE_MENUITEM, DbusmenuMenuitemPrivate);
+
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(self);
priv->id = -1;
@@ -370,6 +389,28 @@ handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, g
return;
}
+/* Handles our about to show signal on items that submenus
+ exist. This is sending just activate now, but we should
+ probably consider a special signal in the future if GTK
+ gets more sophisticated about this. */
+static void
+send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data)
+{
+ g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+
+ if (dbusmenu_menuitem_get_children(mi) == NULL) {
+ g_warning("About to Show called on an item wihtout submenus. We're ignoring it.");
+ } else {
+ g_signal_emit(G_OBJECT(mi), signals[ITEM_ACTIVATED], 0, 0 /* timestamp */, TRUE);
+ }
+
+ if (cb != NULL) {
+ cb(mi, cb_data);
+ }
+
+ return;
+}
+
/* Public interface */
/**
@@ -1333,7 +1374,7 @@ dbusmenu_menuitem_handle_event (DbusmenuMenuitem * mi, const gchar * name, const
called if possible.
*/
void
-dbusmenu_menuitem_send_about_to_show (DbusmenuMenuitem * mi, dbusmenu_menuitem_about_to_show_cb cb, gpointer cb_data)
+dbusmenu_menuitem_send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data)
{
g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
#ifdef MASSIVEDEBUGGING
@@ -1349,3 +1390,22 @@ dbusmenu_menuitem_send_about_to_show (DbusmenuMenuitem * mi, dbusmenu_menuitem_a
return;
}
+
+/**
+ dbusmenu_menuitem_show_to_user:
+ @mi: #DbusmenuMenuitem to show
+ @timestamp: The time that the user requested it to be shown
+
+ Signals that this menu item should be shown to the user. If this is
+ server side the server will then take it and send it over the
+ bus.
+*/
+void
+dbusmenu_menuitem_show_to_user (DbusmenuMenuitem * mi, guint timestamp)
+{
+ g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+
+ g_signal_emit(G_OBJECT(mi), signals[SHOW_TO_USER], 0, timestamp, TRUE);
+
+ return;
+}
diff --git a/libdbusmenu-glib/menuitem.h b/libdbusmenu-glib/menuitem.h
index 0d79ebb..ff8d713 100644
--- a/libdbusmenu-glib/menuitem.h
+++ b/libdbusmenu-glib/menuitem.h
@@ -49,6 +49,7 @@ G_BEGIN_DECLS
#define DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED "child-moved"
#define DBUSMENU_MENUITEM_SIGNAL_REALIZED "realized"
#define DBUSMENU_MENUITEM_SIGNAL_REALIZED_ID (g_signal_lookup(DBUSMENU_MENUITEM_SIGNAL_REALIZED, DBUSMENU_TYPE_MENUITEM))
+#define DBUSMENU_MENUITEM_SIGNAL_SHOW_TO_USER "show-to-user"
#define DBUSMENU_MENUITEM_PROP_TYPE "type"
#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible"
@@ -58,6 +59,7 @@ G_BEGIN_DECLS
#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,8 +71,15 @@ G_BEGIN_DECLS
#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"
+typedef struct _DbusmenuMenuitemPrivate DbusmenuMenuitemPrivate;
+
/**
* DbusmenuMenuitem:
*
@@ -85,6 +94,9 @@ typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
struct _DbusmenuMenuitem
{
GObject parent;
+
+ /*< Private >*/
+ DbusmenuMenuitemPrivate * priv;
};
/**
@@ -118,10 +130,14 @@ typedef void (*dbusmenu_menuitem_buildxml_slot_t) (DbusmenuMenuitem * mi, GPtrAr
* @buildxml: Virtual function that appends the strings required to represent this menu item in the menu XML file.
* @handle_event: This function is to override how events are handled by subclasses. Look at #dbusmenu_menuitem_handle_event for lots of good information.
* @send_about_to_show: Virtual function that notifies server that the client is about to show a menu.
+ * @show_to_user: Slot for #DbusmenuMenuitem::show-to-user.
+ *
* @reserved1: Reserved for future use.
* @reserved2: Reserved for future use.
* @reserved3: Reserved for future use.
* @reserved4: Reserved for future use.
+ * @reserved5: Reserved for future use.
+ * @reserved6: Reserved for future use.
*/
typedef struct _DbusmenuMenuitemClass DbusmenuMenuitemClass;
struct _DbusmenuMenuitemClass
@@ -139,12 +155,17 @@ struct _DbusmenuMenuitemClass
/* Virtual functions */
dbusmenu_menuitem_buildxml_slot_t buildxml;
void (*handle_event) (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp);
- void (*send_about_to_show) (DbusmenuMenuitem * mi, dbusmenu_menuitem_about_to_show_cb cb, gpointer cb_data);
+ void (*send_about_to_show) (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data);
+ void (*show_to_user) (DbusmenuMenuitem * mi, guint timestamp, gpointer cb_data);
+
+ /*< Private >*/
void (*reserved1) (void);
- /* void (*reserved2) (void); */
- /* void (*reserved3) (void); */
- /* void (*reserved4) (void); -- realized, realloc when bumping lib version */
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+ void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
};
GType dbusmenu_menuitem_get_type (void);
@@ -184,7 +205,9 @@ gboolean dbusmenu_menuitem_get_root (DbusmenuMenuitem * mi);
void dbusmenu_menuitem_foreach (DbusmenuMenuitem * mi, void (*func) (DbusmenuMenuitem * mi, gpointer data), gpointer data);
void dbusmenu_menuitem_handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp);
-void dbusmenu_menuitem_send_about_to_show (DbusmenuMenuitem * mi, dbusmenu_menuitem_about_to_show_cb cb, gpointer cb_data);
+void dbusmenu_menuitem_send_about_to_show (DbusmenuMenuitem * mi, void (*cb) (DbusmenuMenuitem * mi, gpointer user_data), gpointer cb_data);
+
+void dbusmenu_menuitem_show_to_user (DbusmenuMenuitem * mi, guint timestamp);
/**
* SECTION:menuitem
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 0da66cc..c5cf2fe 100644
--- a/libdbusmenu-glib/server.c
+++ b/libdbusmenu-glib/server.c
@@ -37,34 +37,38 @@ License version 3 and version 2.1 along with this program. If not, see
/* DBus Prototypes */
static gboolean _dbusmenu_server_get_layout (DbusmenuServer * server, gint parent, guint * revision, gchar ** layout, GError ** error);
static gboolean _dbusmenu_server_get_property (DbusmenuServer * server, gint id, gchar * property, gchar ** value, GError ** error);
-static gboolean _dbusmenu_server_get_properties (DbusmenuServer * server, gint id, GPtrArray * properties, GHashTable ** dict, GError ** error);
-static gboolean _dbusmenu_server_get_group_properties (DbusmenuServer * server, GArray * ids, GArray * properties, GHashTable ** values, GError ** error);
+static gboolean _dbusmenu_server_get_properties (DbusmenuServer * server, gint id, gchar ** properties, GHashTable ** dict, GError ** error);
+static gboolean _dbusmenu_server_get_group_properties (DbusmenuServer * server, GArray * ids, gchar ** properties, GPtrArray ** values, GError ** error);
static gboolean _dbusmenu_server_event (DbusmenuServer * server, gint id, gchar * eventid, GValue * data, guint timestamp, GError ** error);
static gboolean _dbusmenu_server_get_children (DbusmenuServer * server, gint id, GPtrArray * properties, GPtrArray ** output, GError ** error);
static gboolean _dbusmenu_server_about_to_show (DbusmenuServer * server, gint id, gboolean * need_update, GError ** error);
+/* DBus Helpers */
+static void _gvalue_array_append_int(GValueArray *array, gint i);
+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... */
-typedef struct _DbusmenuServerPrivate DbusmenuServerPrivate;
-
struct _DbusmenuServerPrivate
{
DbusmenuMenuitem * root;
gchar * dbusobject;
gint layout_revision;
+ guint layout_idle;
};
-#define DBUSMENU_SERVER_GET_PRIVATE(o) \
-(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_SERVER, DbusmenuServerPrivate))
+#define DBUSMENU_SERVER_GET_PRIVATE(o) (DBUSMENU_SERVER(o)->priv)
/* Signals */
enum {
ID_PROP_UPDATE,
ID_UPDATE,
LAYOUT_UPDATED,
+ ITEM_ACTIVATION,
LAST_SIGNAL
};
@@ -165,6 +169,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,
@@ -191,11 +211,14 @@ dbusmenu_server_class_init (DbusmenuServerClass *class)
static void
dbusmenu_server_init (DbusmenuServer *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_TYPE_SERVER, DbusmenuServerPrivate);
+
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(self);
priv->root = NULL;
priv->dbusobject = NULL;
priv->layout_revision = 1;
+ priv->layout_idle = 0;
return;
}
@@ -205,6 +228,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);
@@ -225,15 +252,22 @@ static void
set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(obj);
+ GError * error = NULL;
switch (id) {
case PROP_DBUS_OBJECT:
g_return_if_fail(priv->dbusobject == NULL);
priv->dbusobject = g_value_dup_string(value);
- DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
- dbus_g_connection_register_g_object(connection,
- priv->dbusobject,
- obj);
+ DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+
+ if (connection == NULL || error != NULL) {
+ g_warning("Unable to get session bus: %s", error == NULL ? "No message" : error->message);
+ if (error != NULL) { g_error_free(error); }
+ } else {
+ dbus_g_connection_register_g_object(connection,
+ priv->dbusobject,
+ obj);
+ }
break;
case PROP_ROOT_NODE:
if (priv->root != NULL) {
@@ -250,8 +284,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();
@@ -295,6 +328,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)
{
@@ -325,10 +387,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;
}
@@ -336,19 +395,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_shown (DbusmenuMenuitem * mi, guint timestamp, DbusmenuServer * server)
+{
+ g_signal_emit(G_OBJECT(server), signals[ITEM_ACTIVATION], 0, dbusmenu_menuitem_get_id(mi), timestamp, TRUE);
return;
}
@@ -361,6 +424,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_SHOW_TO_USER, G_CALLBACK(menuitem_shown), data);
return;
}
@@ -473,7 +537,7 @@ _dbusmenu_server_get_property (DbusmenuServer * server, gint id, gchar * propert
}
static gboolean
-_dbusmenu_server_get_properties (DbusmenuServer * server, gint id, GPtrArray * properties, GHashTable ** dict, GError ** error)
+_dbusmenu_server_get_properties (DbusmenuServer * server, gint id, gchar ** properties, GHashTable ** dict, GError ** error)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
@@ -494,18 +558,42 @@ _dbusmenu_server_get_properties (DbusmenuServer * server, gint id, GPtrArray * p
return TRUE;
}
+/* Handles getting a bunch of properties from a variety of menu items
+ to make one mega dbus message */
static gboolean
-_dbusmenu_server_get_group_properties (DbusmenuServer * server, GArray * ids, GArray * properties, GHashTable ** values, GError ** error)
+_dbusmenu_server_get_group_properties (DbusmenuServer * server, GArray * ids, gchar ** properties, GPtrArray ** values, GError ** error)
{
- if (error != NULL) {
- g_set_error(error,
- error_quark(),
- NOT_IMPLEMENTED,
- "The GetGroupProperties function is not implemented, sorry.");
+ /* Build an initial pointer array */
+ *values = g_ptr_array_new();
+
+ /* Go through each ID to get that ID's properties */
+ int idcnt;
+ for (idcnt = 0; idcnt < ids->len; idcnt++) {
+ GHashTable * idprops = NULL;
+ GError * error = NULL;
+ gint id = g_array_index(ids, int, idcnt);
+
+ /* Get the properties for this ID the old fashioned way. */
+ if (!_dbusmenu_server_get_properties(server, id, properties, &idprops, &error)) {
+ g_warning("Error getting the properties from ID %d: %s", id, error->message);
+ g_error_free(error);
+ error = NULL;
+ continue;
+ }
+
+ GValueArray * valarray = g_value_array_new(2);
+
+ _gvalue_array_append_int(valarray, id);
+ _gvalue_array_append_hashtable(valarray, idprops);
+
+ g_ptr_array_add(*values, valarray);
}
- return FALSE;
+
+ return TRUE;
}
+/* Allocate a value on the stack for the int and append
+ it to the array. */
static void
_gvalue_array_append_int(GValueArray *array, gint i)
{
@@ -517,6 +605,8 @@ _gvalue_array_append_int(GValueArray *array, gint i)
g_value_unset(&value);
}
+/* Allocate a value on the stack for the hashtable and append
+ it to the array. */
static void
_gvalue_array_append_hashtable(GValueArray *array, GHashTable * dict)
{
@@ -537,11 +627,15 @@ serialize_menuitem(gpointer data, gpointer user_data)
gint id = dbusmenu_menuitem_get_id(mi);
GHashTable * dict = dbusmenu_menuitem_properties_copy(mi);
- GValueArray * item = g_value_array_new(1);
+ GValueArray * item = g_value_array_new(2);
_gvalue_array_append_int(item, id);
_gvalue_array_append_hashtable(item, dict);
g_ptr_array_add(output, item);
+
+ g_hash_table_unref(dict);
+
+ return;
}
static gboolean
@@ -568,6 +662,33 @@ _dbusmenu_server_get_children (DbusmenuServer * server, gint id, GPtrArray * pro
return TRUE;
}
+/* Structure for holding the event data for the idle function
+ to pick it up. */
+typedef struct _idle_event_t idle_event_t;
+struct _idle_event_t {
+ DbusmenuMenuitem * mi;
+ gchar * eventid;
+ GValue data;
+ guint timestamp;
+};
+
+/* A handler for else where in the main loop so that the dbusmenu
+ event response doesn't get blocked */
+static gboolean
+event_local_handler (gpointer user_data)
+{
+ idle_event_t * data = (idle_event_t *)user_data;
+
+ dbusmenu_menuitem_handle_event(data->mi, data->eventid, &data->data, data->timestamp);
+
+ g_object_unref(data->mi);
+ g_free(data->eventid);
+ g_value_unset(&data->data);
+ g_free(data);
+ return FALSE;
+}
+
+/* Handles the even coming off of DBus */
static gboolean
_dbusmenu_server_event (DbusmenuServer * server, gint id, gchar * eventid, GValue * data, guint timestamp, GError ** error)
{
@@ -585,7 +706,15 @@ _dbusmenu_server_event (DbusmenuServer * server, gint id, gchar * eventid, GValu
return FALSE;
}
- dbusmenu_menuitem_handle_event(mi, eventid, data, timestamp);
+ idle_event_t * event_data = g_new0(idle_event_t, 1);
+ event_data->mi = mi;
+ g_object_ref(event_data->mi);
+ event_data->eventid = g_strdup(eventid);
+ event_data->timestamp = timestamp;
+ g_value_init(&(event_data->data), G_VALUE_TYPE(data));
+ g_value_copy(data, &(event_data->data));
+
+ g_timeout_add(0, event_local_handler, event_data);
return TRUE;
}
diff --git a/libdbusmenu-glib/server.h b/libdbusmenu-glib/server.h
index f4e3527..5668258 100644
--- a/libdbusmenu-glib/server.h
+++ b/libdbusmenu-glib/server.h
@@ -46,22 +46,29 @@ 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"
#define DBUSMENU_SERVER_PROP_ROOT_NODE "root-node"
#define DBUSMENU_SERVER_PROP_VERSION "version"
+typedef struct _DbusmenuServerPrivate DbusmenuServerPrivate;
+
/**
DbusmenuServerClass:
@parent_class: #GObjectClass
@id_prop_update: Slot for #DbusmenuServer::id-prop-update.
@id_update: Slot for #DbusmenuServer::id-update.
@layout_updated: Slot for #DbusmenuServer::layout-update.
- @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.
+ @item_activation_requested: Slot for #DbusmenuServer::item-activation-requested.
+
+ @reserved1: Reserved for future use.
+ @reserved2: Reserved for future use.
+ @reserved3: Reserved for future use.
+ @reserved4: Reserved for future use.
+ @reserved5: Reserved for future use.
+ @reserved6: Reserved for future use.
The class implementing the virtual functions for #DbusmenuServer.
*/
@@ -73,12 +80,15 @@ struct _DbusmenuServerClass {
void (*id_prop_update)(gint id, gchar * property, gchar * value);
void (*id_update)(gint id);
void (*layout_updated)(gint revision);
-
- /* Reserved */
- void (*dbusmenu_server_reserved1)(void);
- void (*dbusmenu_server_reserved2)(void);
- void (*dbusmenu_server_reserved3)(void);
- void (*dbusmenu_server_reserved4)(void);
+ void (*item_activation)(gint id, guint timestamp);
+
+ /*< Private >*/
+ void (*reserved1) (void);
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+ void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
};
/**
@@ -91,6 +101,9 @@ struct _DbusmenuServerClass {
typedef struct _DbusmenuServer DbusmenuServer;
struct _DbusmenuServer {
GObject parent;
+
+ /*< Private >*/
+ DbusmenuServerPrivate * priv;
};
GType dbusmenu_server_get_type (void);
diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c
index 3bd0af6..bba4550 100644
--- a/libdbusmenu-gtk/client.c
+++ b/libdbusmenu-gtk/client.c
@@ -36,6 +36,13 @@ License version 3 and version 2.1 along with this program. If not, see
#include "menuitem.h"
#include "genericmenuitem.h"
+/* Private */
+struct _DbusmenuGtkClientPrivate {
+ GtkAccelGroup * agroup;
+};
+
+#define DBUSMENU_GTKCLIENT_GET_PRIVATE(o) (DBUSMENU_GTKCLIENT(o)->priv)
+
/* Prototypes */
static void dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass);
static void dbusmenu_gtkclient_init (DbusmenuGtkClient *self);
@@ -45,6 +52,7 @@ static void new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpoint
static void new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, DbusmenuGtkClient * gtkclient);
static void delete_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, DbusmenuGtkClient * gtkclient);
static void move_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint new, guint old, DbusmenuGtkClient * gtkclient);
+static void item_activate (DbusmenuClient * client, DbusmenuMenuitem * mi, guint timestamp, gpointer userdata);
static gboolean new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
static gboolean new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
@@ -62,6 +70,8 @@ dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass)
{
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,10 +83,18 @@ dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass)
static void
dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClientPrivate);
+
+ 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);
+ /* TODO: I think these can be handled in the class... */
g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM, G_CALLBACK(new_menuitem), NULL);
+ g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE, G_CALLBACK(item_activate), NULL);
return;
}
@@ -85,6 +103,12 @@ dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
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 +123,155 @@ dbusmenu_gtkclient_finalize (GObject *object)
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);
+
+ if (key == 0) {
+ return FALSE;
+ }
+
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Setting shortcut on '%s': %d %X", dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_LABEL), key, modifiers);
+ #endif
+
+ 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 = 0;
+ GdkModifierType mod = 0;
+ GtkMenuItem *gmi = dbusmenu_gtkclient_menuitem_get (client, mi);
+
+ dbusmenu_menuitem_property_get_shortcut (mi, &key, &mod);
+
+ if (key != 0) {
+ 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 +398,17 @@ menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, GtkMen
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. */
@@ -250,6 +434,63 @@ new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpointer userdata)
return;
}
+/* Goes through the tree of items and ensure's that all the items
+ above us are also displayed. */
+static void
+activate_helper (GtkMenuShell * shell)
+{
+ if (shell == NULL) {
+ return;
+ }
+
+ if (GTK_IS_MENU(shell)) {
+ GtkWidget * attach = gtk_menu_get_attach_widget(GTK_MENU(shell));
+
+ if (attach != NULL) {
+ GtkWidget * parent = gtk_widget_get_parent(GTK_WIDGET(attach));
+
+ if (parent != NULL) {
+ if (GTK_IS_MENU(parent)) {
+ activate_helper(GTK_MENU_SHELL(parent));
+ }
+
+ /* This code is being commented out for GTK 3 because it
+ doesn't expose the right variables. We need to figure
+ this out as menus won't get grabs properly.
+ TODO FIXME HELP ARGHHHHHHHH */
+#if (HAVE_GTK3 == 0)
+ if (!GTK_MENU_SHELL (parent)->active) {
+ gtk_grab_add (parent);
+ GTK_MENU_SHELL (parent)->have_grab = TRUE;
+ GTK_MENU_SHELL (parent)->active = TRUE;
+ }
+#endif
+
+ gtk_menu_shell_select_item(GTK_MENU_SHELL(parent), attach);
+ }
+ }
+ }
+
+ return;
+}
+
+/* Signaled when we should show a menuitem at request of the application
+ that it is in. */
+static void
+item_activate (DbusmenuClient * client, DbusmenuMenuitem * mi, guint timestamp, gpointer userdata)
+{
+ gpointer pmenu = g_object_get_data(G_OBJECT(mi), data_menu);
+ if (pmenu == NULL) {
+ g_warning("Activated menu item doesn't have a menu? ID: %d", dbusmenu_menuitem_get_id(mi));
+ return;
+ }
+
+ activate_helper(GTK_MENU_SHELL(pmenu));
+ gtk_menu_shell_select_first(GTK_MENU_SHELL(pmenu), FALSE);
+
+ return;
+}
+
#ifdef MASSIVEDEBUGGING
static void
destroy_gmi (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
@@ -291,6 +532,7 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem *
/* 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,10 +547,11 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem *
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) {
- 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;
@@ -322,6 +565,7 @@ new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, Dbus
#endif
if (dbusmenu_menuitem_get_root(mi)) { return; }
+ if (g_strcmp0(dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_TYPE), DBUSMENU_CLIENT_TYPES_SEPARATOR) == 0) { return; }
gpointer ann_menu = g_object_get_data(G_OBJECT(mi), data_menu);
GtkMenu * menu = GTK_MENU(ann_menu);
@@ -335,7 +579,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/libdbusmenu-gtk/client.h b/libdbusmenu-gtk/client.h
index 7672bf7..c986a5d 100644
--- a/libdbusmenu-gtk/client.h
+++ b/libdbusmenu-gtk/client.h
@@ -43,6 +43,8 @@ G_BEGIN_DECLS
#define DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED
+typedef struct _DbusmenuGtkClientPrivate DbusmenuGtkClientPrivate;
+
/**
DbusmenuGtkClientClass:
@parent_class: #GtkMenuClass
@@ -50,6 +52,8 @@ G_BEGIN_DECLS
@reserved2: Reserved for future use.
@reserved3: Reserved for future use.
@reserved4: Reserved for future use.
+ @reserved5: Reserved for future use.
+ @reserved6: Reserved for future use.
*/
typedef struct _DbusmenuGtkClientClass DbusmenuGtkClientClass;
struct _DbusmenuGtkClientClass {
@@ -58,11 +62,13 @@ struct _DbusmenuGtkClientClass {
/* Signals */
void (*root_changed) (DbusmenuMenuitem * newroot);
- /* Reserved */
+ /*< Private >*/
void (*reserved1) (void);
void (*reserved2) (void);
void (*reserved3) (void);
void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
};
/**
@@ -72,6 +78,9 @@ struct _DbusmenuGtkClientClass {
typedef struct _DbusmenuGtkClient DbusmenuGtkClient;
struct _DbusmenuGtkClient {
DbusmenuClient parent;
+
+ /*< Private >*/
+ DbusmenuGtkClientPrivate * priv;
};
GType dbusmenu_gtkclient_get_type (void);
@@ -79,6 +88,9 @@ DbusmenuGtkClient * dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_obje
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);
/**
diff --git a/libdbusmenu-gtk/genericmenuitem.c b/libdbusmenu-gtk/genericmenuitem.c
index b357d82..d507487 100644
--- a/libdbusmenu-gtk/genericmenuitem.c
+++ b/libdbusmenu-gtk/genericmenuitem.c
@@ -174,6 +174,8 @@ get_hpadding (GtkWidget * widget)
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;
@@ -207,9 +209,10 @@ set_label (GtkMenuItem * menu_item, const gchar * label)
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
diff --git a/libdbusmenu-gtk/menu.c b/libdbusmenu-gtk/menu.c
index 3e6f2dd..9c4f7f8 100644
--- a/libdbusmenu-gtk/menu.c
+++ b/libdbusmenu-gtk/menu.c
@@ -44,7 +44,6 @@ enum {
};
/* Private */
-typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate;
struct _DbusmenuGtkMenuPrivate {
DbusmenuGtkClient * client;
@@ -52,8 +51,7 @@ struct _DbusmenuGtkMenuPrivate {
gchar * dbus_name;
};
-#define DBUSMENU_GTKMENU_GET_PRIVATE(o) \
-(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuPrivate))
+#define DBUSMENU_GTKMENU_GET_PRIVATE(o) (DBUSMENU_GTKMENU(o)->priv)
/* Prototypes */
static void dbusmenu_gtkmenu_class_init (DbusmenuGtkMenuClass *klass);
@@ -110,6 +108,8 @@ menu_focus_cb(DbusmenuGtkMenu * menu, gpointer userdata)
static void
dbusmenu_gtkmenu_init (DbusmenuGtkMenu *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuPrivate);
+
DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(self);
priv->client = NULL;
diff --git a/libdbusmenu-gtk/menu.h b/libdbusmenu-gtk/menu.h
index 5147d30..896e466 100644
--- a/libdbusmenu-gtk/menu.h
+++ b/libdbusmenu-gtk/menu.h
@@ -42,6 +42,8 @@ G_BEGIN_DECLS
#define DBUSMENU_IS_GTKMENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_GTKMENU_TYPE))
#define DBUSMENU_GTKMENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuClass))
+typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate;
+
/**
DbusmenuGtkMenuClass:
@parent_class: #GtkMenuClass
@@ -49,16 +51,20 @@ G_BEGIN_DECLS
@reserved2: Reserved for future use.
@reserved3: Reserved for future use.
@reserved4: Reserved for future use.
+ @reserved5: Reserved for future use.
+ @reserved6: Reserved for future use.
*/
typedef struct _DbusmenuGtkMenuClass DbusmenuGtkMenuClass;
struct _DbusmenuGtkMenuClass {
GtkMenuClass parent_class;
- /* Reserved */
+ /*< Private >*/
void (*reserved1) (void);
void (*reserved2) (void);
void (*reserved3) (void);
void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
};
/**
@@ -68,6 +74,9 @@ struct _DbusmenuGtkMenuClass {
typedef struct _DbusmenuGtkMenu DbusmenuGtkMenu;
struct _DbusmenuGtkMenu {
GtkMenu parent;
+
+ /*< Private >*/
+ DbusmenuGtkMenuPrivate * priv;
};
GType dbusmenu_gtkmenu_get_type (void);
diff --git a/libdbusmenu-gtk/menuitem.c b/libdbusmenu-gtk/menuitem.c
index 23ff311..adf9180 100644
--- a/libdbusmenu-gtk/menuitem.c
+++ b/libdbusmenu-gtk/menuitem.c
@@ -27,6 +27,9 @@ License version 3 and version 2.1 along with this program. If not, see
*/
#include "menuitem.h"
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <dbus/dbus-gtype-specialized.h>
/**
dbusmenu_menuitem_property_set_image:
@@ -128,3 +131,248 @@ dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar *
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);
+}
+
+/**
+ 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);
+
+ GArray * array = g_array_sized_new(TRUE, TRUE, sizeof(gchar *), 4); /* Four seems like the max we'd need, plus it's still small */
+
+ const gchar * control_val = DBUSMENU_MENUITEM_SHORTCUT_CONTROL;
+ const gchar * alt_val = DBUSMENU_MENUITEM_SHORTCUT_ALT;
+ const gchar * shift_val = DBUSMENU_MENUITEM_SHORTCUT_SHIFT;
+ const gchar * super_val = DBUSMENU_MENUITEM_SHORTCUT_SUPER;
+
+ if (modifier & GDK_CONTROL_MASK) {
+ g_array_append_val(array, control_val);
+ }
+ if (modifier & GDK_MOD1_MASK) {
+ g_array_append_val(array, alt_val);
+ }
+ if (modifier & GDK_SHIFT_MASK) {
+ g_array_append_val(array, shift_val);
+ }
+ if (modifier & GDK_SUPER_MASK) {
+ g_array_append_val(array, super_val);
+ }
+
+ const gchar * keyname = gdk_keyval_name(key);
+ g_array_append_val(array, keyname);
+
+ GType type = dbus_g_type_get_collection("GPtrArray", G_TYPE_STRV);
+ GPtrArray * wrapper = (GPtrArray *)dbus_g_type_specialized_construct(type);
+
+ GValue value = {0,};
+ g_value_init(&value, type);
+ g_value_take_boxed(&value, wrapper);
+
+ DBusGTypeSpecializedAppendContext ctx;
+ dbus_g_type_specialized_init_append(&value, &ctx);
+
+ GValue strval = {0,};
+ g_value_init(&strval, G_TYPE_STRV);
+ g_value_take_boxed(&strval, array->data);
+ g_array_free(array, FALSE);
+
+ dbus_g_type_specialized_collection_append(&ctx, &strval);
+ dbus_g_type_specialized_collection_end_append(&ctx);
+
+ 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;
+ GtkWidget *label = gtk_bin_get_child(GTK_BIN (gmi));
+
+ if (GTK_IS_ACCEL_LABEL (label))
+ {
+ g_object_get (label,
+ "accel-closure", &closure,
+ NULL);
+ }
+
+ if (closure == NULL)
+ return FALSE;
+
+ GtkAccelGroup * group = gtk_accel_group_from_accel_closure(closure);
+
+ /* Apparently this is more common than I thought. */
+ if (group == NULL) {
+ return 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);
+
+ if (!gtk_accelerator_valid (key->accel_key, key->accel_mods))
+ return FALSE;
+
+ return dbusmenu_menuitem_property_set_shortcut(menuitem, key->accel_key, key->accel_mods);
+}
+
+/* A set of typed data for the interator */
+typedef struct _iter_data_t iter_data_t;
+struct _iter_data_t {
+ guint * key;
+ GdkModifierType * modifier;
+};
+
+/* Goes through the wrapper items. In reality we only support one
+ so it checks to see if a key is set first. But, we could possibly,
+ support more in the future. */
+static void
+_wrapper_iterator (const GValue * value, gpointer user_data)
+{
+ iter_data_t * iter_data = (iter_data_t *)user_data;
+
+ if (*iter_data->key != 0) {
+ g_warning("Shortcut is more than one entry. Which we don't currently support. Taking the first.");
+ return;
+ }
+
+ if (!G_VALUE_HOLDS(value, G_TYPE_STRV)) {
+ g_warning("Unexpected shortcut structure. Value array is: %s", G_VALUE_TYPE_NAME(value));
+ return;
+ }
+
+ gchar ** stringarray = (gchar **)g_value_get_boxed(value);
+ if (stringarray == NULL) {
+ return;
+ }
+
+ const gchar * last_string = NULL;
+ int i;
+
+ for (i = 0; stringarray[i] != NULL; i++) {
+ last_string = stringarray[i];
+
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_CONTROL) == 0) {
+ *iter_data->modifier |= GDK_CONTROL_MASK;
+ continue;
+ }
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_ALT) == 0) {
+ *iter_data->modifier |= GDK_MOD1_MASK;
+ continue;
+ }
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_SHIFT) == 0) {
+ *iter_data->modifier |= GDK_SHIFT_MASK;
+ continue;
+ }
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_SUPER) == 0) {
+ *iter_data->modifier |= GDK_SUPER_MASK;
+ continue;
+ }
+ }
+
+ if (last_string != NULL) {
+ GdkModifierType tempmod;
+ gtk_accelerator_parse(last_string, iter_data->key, &tempmod);
+ }
+
+ return;
+}
+
+/**
+ 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;
+ }
+ if (!dbus_g_type_is_collection(G_VALUE_TYPE(wrapper))) {
+ g_warning("Unexpected shortcut structure. Wrapper is: %s", G_VALUE_TYPE_NAME(wrapper));
+ return;
+ }
+
+ iter_data_t iter_data;
+ iter_data.key = key;
+ iter_data.modifier = modifier;
+
+ dbus_g_type_collection_value_iterate(wrapper, _wrapper_iterator, &iter_data);
+
+ return;
+}
+
diff --git a/libdbusmenu-gtk/menuitem.h b/libdbusmenu-gtk/menuitem.h
index ff458de..6960f76 100644
--- a/libdbusmenu-gtk/menuitem.h
+++ b/libdbusmenu-gtk/menuitem.h
@@ -32,8 +32,15 @@ License version 3 and version 2.1 along with this program. If not, see
#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
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f1b50bc..aa79c8f 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,19 +1,28 @@
DBUS_RUNNER=dbus-test-runner
+CLEANFILES=
+
TESTS = \
test-glib-objects-test \
+ test-glib-events \
test-glib-layout \
test-glib-properties \
test-glib-proxy \
test-glib-simple-items \
test-glib-submenu \
+ test-json \
+ test-gtk-objects-test \
test-gtk-label \
- test-gtk-reorder
+ test-gtk-shortcut \
+ test-gtk-reorder \
+ test-gtk-submenu
check_PROGRAMS = \
glib-server-nomenu \
test-glib-objects \
+ test-glib-events-client \
+ test-glib-events-server \
test-glib-layout-client \
test-glib-layout-server \
test-glib-properties-client \
@@ -21,16 +30,60 @@ check_PROGRAMS = \
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
+ test-gtk-reorder-server \
+ test-json-client \
+ test-json-server \
+ test-gtk-submenu-server \
+ test-gtk-submenu-client
XVFB_RUN=". $(srcdir)/run-xvfb.sh"
######################
+# JSON Loader lib
+######################
+
+lib_LTLIBRARIES = libdbusmenu-jsonloader.la
+
+libdbusmenu_jsonloaderincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-jsonloader/
+
+libdbusmenu_jsonloaderinclude_HEADERS = \
+ json-loader.h
+
+libdbusmenu_jsonloader_la_SOURCES = \
+ json-loader.h \
+ json-loader.c
+
+libdbusmenu_jsonloader_la_LDFLAGS = \
+ -version-info $(LIBDBUSMENU_CURRENT):$(LIBDBUSMENU_REVISION):$(LIBDBUSMENU_AGE) \
+ -no-undefined \
+ -export-symbols-regex "^[^_].*"
+
+libdbusmenu_jsonloader_la_CFLAGS = \
+ $(DBUSMENUGLIB_CFLAGS) \
+ $(DBUSMENUTESTS_CFLAGS) \
+ -I $(srcdir)/.. \
+ -Wall \
+ -Werror \
+ -DG_DISABLE_DEPRECATED \
+ -DG_LOG_DOMAIN="\"LIBDBUSMENU-JSONLOADER\""
+
+libdbusmenu_jsonloader_la_LIBADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS) \
+ $(DBUSMENUTESTS_LIBS)
+
+pkgconfig_DATA = dbusmenu-jsonloader.pc
+pkgconfigdir = $(libdir)/pkgconfig
+
+######################
# Test GLib server
######################
@@ -79,6 +132,78 @@ test_glib_layout_client_LDADD = \
$(DBUSMENUGLIB_LIBS)
######################
+# Test Glib Events
+######################
+
+test-glib-events: test-glib-events-client test-glib-events-server Makefile.am
+ @echo "#!/bin/bash" > $@
+ @echo $(DBUS_RUNNER) --task ./test-glib-events-client --task-name Client --task ./test-glib-events-server --task-name Server --ignore-return >> $@
+ @chmod +x $@
+
+test_glib_events_server_SOURCES = \
+ test-glib-events-server.c
+
+test_glib_events_server_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_glib_events_server_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS)
+
+test_glib_events_client_SOURCES = \
+ test-glib-events-client.c
+
+test_glib_events_client_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_glib_events_client_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUGLIB_LIBS)
+
+######################
+# Test JSON
+######################
+
+test-json: test-json-client test-json-server Makefile.am
+ @echo "#!/bin/bash" > $@
+ @echo $(XVFB_RUN) >> $@
+ @echo $(DBUS_RUNNER) --task ./test-json-client --task-name Client --parameter $(top_builddir)/tools/dbusmenu-dumper --parameter test-json-01.output.json --ignore-return --task ./test-json-server --task-name Server --parameter $(srcdir)/test-json-01.json --ignore-return >> $@
+ @echo diff $(srcdir)/test-json-01.json test-json-01.output.json \> /dev/null >> $@
+ @chmod +x $@
+
+CLEANFILES += test-json-01.output.json
+
+test_json_server_SOURCES = \
+ test-json-server.c
+
+test_json_server_CFLAGS = \
+ -I $(srcdir)/.. \
+ -I $(srcdir) \
+ $(DBUSMENUGLIB_CFLAGS) \
+ $(DBUSMENUTESTS_CFLAGS) \
+ -Wall -Werror
+
+test_json_server_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ libdbusmenu-jsonloader.la \
+ $(DBUSMENUTESTS_LIBS) \
+ $(DBUSMENUGLIB_LIBS)
+
+test_json_client_SOURCES = \
+ test-json-client.c
+
+test_json_client_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) -Wall -Werror
+
+test_json_client_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ $(DBUSMENUTESTS_LIBS) \
+ $(DBUSMENUGLIB_LIBS)
+
+######################
# Test Glib Submenu
######################
@@ -119,7 +244,7 @@ OBJECT_XML_REPORT = test-glib-objects.xml
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 +356,34 @@ test_glib_simple_items_LDADD = \
../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
#########################
@@ -253,6 +406,7 @@ test_gtk_label_server_CFLAGS = \
test_gtk_label_server_LDADD = \
../libdbusmenu-glib/libdbusmenu-glib.la \
../libdbusmenu-gtk/libdbusmenu-gtk.la \
+ libdbusmenu-jsonloader.la \
$(DBUSMENUGTK_LIBS) \
$(DBUSMENUTESTS_LIBS)
@@ -272,6 +426,46 @@ test_gtk_label_client_LDADD = \
$(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
#########################
@@ -297,6 +491,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
#########################
@@ -329,6 +563,7 @@ EXTRA_DIST = \
$(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 \
@@ -347,9 +582,10 @@ EXTRA_DIST = \
dbusmenu-gtk/mago_tests/data/several_submenus_recursive.json \
dbusmenu-gtk/mago_tests/data/several_submenus_utf8.json \
dbusmenu-gtk/mago_tests/data/static.json \
- dbusmenu-gtk/mago_tests/data/test-gtk-label.json
+ dbusmenu-gtk/mago_tests/data/test-gtk-label.json \
+ test-json-01.json
-CLEANFILES = \
+CLEANFILES += \
dbusmenu-gtk/mago_tests/dbusmenu.pyc
distclean-local:
@@ -357,5 +593,6 @@ distclean-local:
DISTCLEANFILES = \
$(TESTS) \
- $(OBJECT_XML_REPORT)
+ $(OBJECT_XML_REPORT) \
+ $(GTK_OBJECT_XML_REPORT)
diff --git a/tests/dbusmenu-jsonloader.pc.in b/tests/dbusmenu-jsonloader.pc.in
new file mode 100644
index 0000000..d042132
--- /dev/null
+++ b/tests/dbusmenu-jsonloader.pc.in
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+bindir=@bindir@
+includedir=@includedir@
+
+Cflags: -I${includedir}/libdbusmenu-0.1
+Requires: dbus-glib-1,dbusmenu-glib,json-glib-1.0
+Libs: -L${libdir} -ldbusmenu-jsonloader
+
+Name: libdbusmenu-jsonloader
+Description: A small library to load JSON descriptions of menus. Mostly for testing.
+Version: @VERSION@
+
diff --git a/tests/json-loader.c b/tests/json-loader.c
new file mode 100644
index 0000000..aad4295
--- /dev/null
+++ b/tests/json-loader.c
@@ -0,0 +1,220 @@
+/*
+A loader to turn JSON into dbusmenu menuitems
+
+Copyright 2010 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 "json-loader.h"
+#include <dbus/dbus-gtype-specialized.h>
+
+static GValue *
+node2value (JsonNode * node)
+{
+ if (node == NULL) {
+ return NULL;
+ }
+
+ GValue * value = g_new0(GValue, 1);
+
+ if (JSON_NODE_TYPE(node) == JSON_NODE_VALUE) {
+ json_node_get_value(node, value);
+ return value;
+ }
+
+ if (JSON_NODE_TYPE(node) == JSON_NODE_ARRAY) {
+ JsonArray * array = json_node_get_array(node);
+ JsonNode * first = json_array_get_element(array, 0);
+
+ if (JSON_NODE_TYPE(first) == JSON_NODE_VALUE) {
+ GValue subvalue = {0};
+ json_node_get_value(first, &subvalue);
+
+ if (G_VALUE_TYPE(&subvalue) == G_TYPE_STRING) {
+ GArray * garray = g_array_sized_new(TRUE, TRUE, sizeof(gchar *), json_array_get_length(array));
+ g_value_init(value, G_TYPE_STRV);
+ g_value_take_boxed(value, garray->data);
+
+ int i;
+ for (i = 0; i < json_array_get_length(array); i++) {
+ const gchar * str = json_node_get_string(json_array_get_element(array, i));
+ gchar * dupstr = g_strdup(str);
+ g_array_append_val(garray, dupstr);
+ }
+
+ g_array_free(garray, FALSE);
+ } else {
+ GValueArray * varray = g_value_array_new(json_array_get_length(array));
+ g_value_init(value, G_TYPE_VALUE_ARRAY);
+ g_value_take_boxed(value, varray);
+
+ g_value_array_append(varray, &subvalue);
+ g_value_unset(&subvalue);
+
+ int i;
+ for (i = 1; i < json_array_get_length(array); i++) {
+ json_node_get_value(first, &subvalue);
+ g_value_array_append(varray, &subvalue);
+ g_value_unset(&subvalue);
+ }
+ }
+
+ } else {
+ GValue * subvalue = node2value(first);
+ GType type = dbus_g_type_get_collection("GPtrArray", G_VALUE_TYPE(subvalue));
+ gpointer * wrapper = dbus_g_type_specialized_construct(type);
+
+ g_value_init(value, type);
+ g_value_take_boxed(value, wrapper);
+
+ DBusGTypeSpecializedAppendContext ctx;
+ dbus_g_type_specialized_init_append(value, &ctx);
+
+ dbus_g_type_specialized_collection_append(&ctx, subvalue);
+ int i;
+ for (i = 1; i < json_array_get_length(array); i++) {
+ GValue * subvalue = node2value(node);
+ dbus_g_type_specialized_collection_append(&ctx, subvalue);
+ }
+
+ dbus_g_type_specialized_collection_end_append(&ctx);
+ }
+ }
+
+ if (JSON_NODE_TYPE(node) == JSON_NODE_OBJECT) {
+ JsonObject * obj = json_node_get_object(node);
+
+ GType type = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
+ GHashTable * hash = (GHashTable *)dbus_g_type_specialized_construct(type);
+
+ g_value_init(value, type);
+ g_value_take_boxed(value, hash);
+
+ DBusGTypeSpecializedAppendContext ctx;
+ dbus_g_type_specialized_init_append(value, &ctx);
+
+ GList * members = NULL;
+ for (members = json_object_get_members(obj); members != NULL; members = g_list_next(members)) {
+ const gchar * member = members->data;
+
+ JsonNode * lnode = json_object_get_member(obj, member);
+ GValue * value = node2value(lnode);
+
+ if (value != NULL) {
+ GValue name = {0};
+ g_value_init(&name, G_TYPE_STRING);
+ g_value_set_static_string(&name, member);
+
+ dbus_g_type_specialized_map_append(&ctx, &name, value);
+
+ g_value_unset(&name);
+ g_value_unset(value);
+ g_free(value);
+ }
+ }
+ }
+
+ return value;
+}
+
+static void
+set_props (DbusmenuMenuitem * mi, JsonObject * node)
+{
+ if (node == NULL) return;
+
+ GList * members = NULL;
+ for (members = json_object_get_members(node); members != NULL; members = g_list_next(members)) {
+ const gchar * member = members->data;
+
+ if (!g_strcmp0(member, "id")) { continue; }
+ if (!g_strcmp0(member, "submenu")) { continue; }
+
+ JsonNode * lnode = json_object_get_member(node, member);
+ GValue * value = node2value(lnode);
+
+ if (value != NULL) {
+ dbusmenu_menuitem_property_set_value(mi, member, value);
+ g_value_unset(value);
+ g_free(value);
+ }
+ }
+
+ return;
+}
+
+DbusmenuMenuitem *
+dbusmenu_json_build_from_node (const JsonNode * cnode)
+{
+ JsonNode * node = (JsonNode *)cnode; /* To match the jsonglib API :( */
+
+ if (node == NULL) return NULL;
+ if (JSON_NODE_TYPE(node) != JSON_NODE_OBJECT) return NULL;
+
+ JsonObject * layout = json_node_get_object(node);
+
+ DbusmenuMenuitem * local = NULL;
+ if (json_object_has_member(layout, "id")) {
+ JsonNode * node = json_object_get_member(layout, "id");
+ g_return_val_if_fail(JSON_NODE_TYPE(node) == JSON_NODE_VALUE, NULL);
+ local = dbusmenu_menuitem_new_with_id(json_node_get_int(node));
+ } else {
+ local = dbusmenu_menuitem_new();
+ }
+
+ set_props(local, layout);
+
+ if (json_object_has_member(layout, "submenu")) {
+ JsonNode * node = json_object_get_member(layout, "submenu");
+ g_return_val_if_fail(JSON_NODE_TYPE(node) == JSON_NODE_ARRAY, local);
+ JsonArray * array = json_node_get_array(node);
+ guint count;
+ for (count = 0; count < json_array_get_length(array); count++) {
+ DbusmenuMenuitem * child = dbusmenu_json_build_from_node(json_array_get_element(array, count));
+ if (child != NULL) {
+ dbusmenu_menuitem_child_append(local, child);
+ }
+ }
+ }
+
+ /* g_debug("Layout to menu return: 0x%X", (unsigned int)local); */
+ return local;
+}
+
+DbusmenuMenuitem *
+dbusmenu_json_build_from_file (const gchar * filename)
+{
+ JsonParser * parser = json_parser_new();
+
+ GError * error = NULL;
+ if (!json_parser_load_from_file(parser, filename, &error)) {
+ g_warning("Failed parsing file %s because: %s", filename, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ JsonNode * root_node = json_parser_get_root(parser);
+ if (JSON_NODE_TYPE(root_node) != JSON_NODE_OBJECT) {
+ g_warning("Root node is not an object, fail. It's an: %s", json_node_type_name(root_node));
+ return NULL;
+ }
+
+ DbusmenuMenuitem * mi = dbusmenu_json_build_from_node(root_node);
+
+ g_object_unref(parser);
+
+ return mi;
+}
diff --git a/tests/json-loader.h b/tests/json-loader.h
new file mode 100644
index 0000000..666bb6e
--- /dev/null
+++ b/tests/json-loader.h
@@ -0,0 +1,31 @@
+/*
+A loader to turn JSON into dbusmenu menuitems
+
+Copyright 2010 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/>.
+*/
+
+#ifndef __DBUSMENU_JSON_LOADER_H__
+#define __DBUSMENU_JSON_LOADER_H__
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <json-glib/json-glib.h>
+
+DbusmenuMenuitem * dbusmenu_json_build_from_node (const JsonNode * node);
+DbusmenuMenuitem * dbusmenu_json_build_from_file (const gchar * filename);
+
+#endif /* __DBUSMENU_JSON_LOADER_H__ */
diff --git a/tests/run-xvfb.sh b/tests/run-xvfb.sh
index 3622dbf..3aa05c1 100644
--- a/tests/run-xvfb.sh
+++ b/tests/run-xvfb.sh
@@ -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; }
diff --git a/tests/test-glib-events-client.c b/tests/test-glib-events-client.c
new file mode 100644
index 0000000..97d5caf
--- /dev/null
+++ b/tests/test-glib-events-client.c
@@ -0,0 +1,140 @@
+/*
+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-submenu.h"
+
+#define TIMESTAMP_VALUE 54
+#define DATA_VALUE 32
+#define USER_VALUE 76
+
+static GMainLoop * mainloop = NULL;
+static gboolean passed = TRUE;
+static gboolean first = TRUE;
+
+static void
+event_status (DbusmenuClient * client, DbusmenuMenuitem * item, gchar * name, GValue * data, guint timestamp, GError * error, gpointer user_data)
+{
+ g_debug("Event status: %s", error == NULL ? "Sent" : "Error");
+
+ if (timestamp != TIMESTAMP_VALUE) {
+ g_debug("Timestamp value pass fail got: %d", timestamp);
+ passed = FALSE;
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ if (g_value_get_int(data) != DATA_VALUE) {
+ g_debug("Data value pass fail got: %d", g_value_get_int(data));
+ passed = FALSE;
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ if (GPOINTER_TO_INT(user_data) != USER_VALUE) {
+ g_debug("User value pass fail got: %d", GPOINTER_TO_INT(user_data));
+ passed = FALSE;
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ if (first && error != NULL) {
+ passed = FALSE;
+ g_debug("First signal back failed.");
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ if (!first && error == NULL) {
+ passed = FALSE;
+ g_debug("Second signal didn't fail.");
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ if (!first && error != NULL) {
+ g_debug("Second signal failed: pass.");
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ first = FALSE;
+ dbusmenu_menuitem_handle_event(item, "clicked", data, timestamp);
+ return;
+}
+
+static void
+layout_updated (DbusmenuClient * client, gpointer user_data)
+{
+ g_debug("Layout Updated");
+
+ DbusmenuMenuitem * menuroot = dbusmenu_client_get_root(client);
+ if (menuroot == NULL) {
+ g_debug("Root is NULL?");
+ return;
+ }
+
+ GValue data = {0};
+ g_value_init(&data, G_TYPE_INT);
+ g_value_set_int(&data, DATA_VALUE);
+
+ dbusmenu_menuitem_handle_event(menuroot, "clicked", &data, TIMESTAMP_VALUE);
+
+ return;
+}
+
+static gboolean
+timer_func (gpointer data)
+{
+ g_debug("Death timer. Oops.");
+ passed = FALSE;
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ DbusmenuClient * client = dbusmenu_client_new("org.dbusmenu.test", "/org/test");
+ g_signal_connect(G_OBJECT(client), DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED, G_CALLBACK(layout_updated), NULL);
+ g_signal_connect(G_OBJECT(client), DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT, G_CALLBACK(event_status), GINT_TO_POINTER(USER_VALUE));
+
+ g_timeout_add_seconds(5, 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 1;
+ }
+}
diff --git a/tests/test-glib-events-server.c b/tests/test-glib-events-server.c
new file mode 100644
index 0000000..0d1e0b1
--- /dev/null
+++ b/tests/test-glib-events-server.c
@@ -0,0 +1,102 @@
+/*
+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/server.h>
+#include <libdbusmenu-glib/menuitem.h>
+
+static DbusmenuServer * server = NULL;
+static GMainLoop * mainloop = NULL;
+static gboolean passed = TRUE;
+
+static void
+handle_event (void) {
+ g_debug("Handle event");
+ g_main_loop_quit(mainloop);
+ return;
+}
+
+static gboolean
+timer_func (gpointer data)
+{
+ passed = FALSE;
+ g_debug("Never got a signal");
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+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, "org.dbusmenu.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");
+ DbusmenuMenuitem * menuitem = dbusmenu_menuitem_new();
+ dbusmenu_server_set_root(server, menuitem);
+
+ g_signal_connect(G_OBJECT(menuitem), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(handle_event), NULL);
+
+ g_timeout_add_seconds(3, timer_func, NULL);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ if (passed) {
+ int i;
+
+ for (i = 0; i < 5; i++) {
+ g_debug("Ignoring signals: %d", i);
+ g_usleep(1000 * 1000);
+ }
+ }
+
+ if (passed) {
+ g_debug("Test Passed");
+ return 0;
+ } else {
+ g_debug("Test Failed");
+ return 1;
+ }
+}
diff --git a/tests/test-glib-properties-client.c b/tests/test-glib-properties-client.c
index 434465a..ae7b80b 100644
--- a/tests/test-glib-properties-client.c
+++ b/tests/test-glib-properties-client.c
@@ -121,7 +121,7 @@ static void
layout_updated (DbusmenuClient * client, gpointer data)
{
g_debug("Layout Updated");
- g_timeout_add (250, layout_verify_timer, client);
+ g_timeout_add (500, layout_verify_timer, client);
return;
}
diff --git a/tests/test-gtk-label-client.c b/tests/test-gtk-label-client.c
index 070c278..14eb5bd 100644
--- a/tests/test-gtk-label-client.c
+++ b/tests/test-gtk-label-client.c
@@ -22,7 +22,6 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <gtk/gtk.h>
#include <libdbusmenu-gtk/menu.h>
-static guint layouton = 0;
static GMainLoop * mainloop = NULL;
static gboolean passed = TRUE;
static guint death_timer = 0;
@@ -105,7 +104,6 @@ verify_root_to_layout(DbusmenuMenuitem * mi, proplayout_t * layout)
static gboolean
timer_func (gpointer data)
{
- g_debug("Death timer. Oops. Got to: %d", layouton);
passed = TRUE;
g_main_loop_quit(mainloop);
return FALSE;
diff --git a/tests/test-gtk-label-server.c b/tests/test-gtk-label-server.c
index 32d7a43..32572fc 100644
--- a/tests/test-gtk-label-server.c
+++ b/tests/test-gtk-label-server.c
@@ -30,74 +30,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <libdbusmenu-glib/server.h>
#include <json-glib/json-glib.h>
-
-static void
-menuitem_click(DbusmenuMenuitem * mi, guint32 time, gpointer user_data)
-{
- g_debug("Clicked on: %d @ %d", dbusmenu_menuitem_get_id(mi), time);
- return;
-}
-
-static void
-set_props (DbusmenuMenuitem * mi, JsonObject * node)
-{
- if (node == NULL) return;
-
- GList * members = NULL;
- for (members = json_object_get_members(node); members != NULL; members = g_list_next(members)) {
- const gchar * member = members->data;
-
- if (!g_strcmp0(member, "id")) { continue; }
- if (!g_strcmp0(member, "submenu")) { continue; }
-
- JsonNode * lnode = json_object_get_member(node, member);
- if (JSON_NODE_TYPE(lnode) != JSON_NODE_VALUE) { continue; }
-
- GValue value = {0};
- json_node_get_value(lnode, &value);
- dbusmenu_menuitem_property_set_value(mi, member, &value);
- g_value_unset(&value);
- }
-
- return;
-}
-
-static DbusmenuMenuitem *
-layout2menuitem (JsonNode * inlayout)
-{
- if (inlayout == NULL) return NULL;
- if (JSON_NODE_TYPE(inlayout) != JSON_NODE_OBJECT) return NULL;
-
- JsonObject * layout = json_node_get_object(inlayout);
-
- DbusmenuMenuitem * local = NULL;
- if (json_object_has_member(layout, "id")) {
- JsonNode * node = json_object_get_member(layout, "id");
- g_return_val_if_fail(JSON_NODE_TYPE(node) == JSON_NODE_VALUE, NULL);
- local = dbusmenu_menuitem_new_with_id(json_node_get_int(node));
- } else {
- local = dbusmenu_menuitem_new();
- }
- g_signal_connect(G_OBJECT(local), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(menuitem_click), NULL);
-
- set_props(local, layout);
-
- if (json_object_has_member(layout, "submenu")) {
- JsonNode * node = json_object_get_member(layout, "submenu");
- g_return_val_if_fail(JSON_NODE_TYPE(node) == JSON_NODE_ARRAY, local);
- JsonArray * array = json_node_get_array(node);
- guint count;
- for (count = 0; count < json_array_get_length(array); count++) {
- DbusmenuMenuitem * child = layout2menuitem(json_array_get_element(array, count));
- if (child != NULL) {
- dbusmenu_menuitem_child_append(local, child);
- }
- }
- }
-
- /* g_debug("Layout to menu return: 0x%X", (unsigned int)local); */
- return local;
-}
+#include "json-loader.h"
static JsonArray * root_array = NULL;
static guint layouton = 0;
@@ -114,7 +47,7 @@ timer_func (gpointer data)
}
g_debug("Updating to Layout %d", layouton);
- dbusmenu_server_set_root(server, layout2menuitem(json_array_get_element(root_array, layouton)));
+ dbusmenu_server_set_root(server, dbusmenu_json_build_from_node(json_array_get_element(root_array, layouton)));
layouton++;
return TRUE;
diff --git a/tests/test-gtk-objects.c b/tests/test-gtk-objects.c
new file mode 100644
index 0000000..726f404
--- /dev/null
+++ b/tests/test-gtk-objects.c
@@ -0,0 +1,145 @@
+/*
+Testing for the various objects just by themselves.
+
+Copyright 2010 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 <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 ();
+}
diff --git a/tests/test-gtk-objects.jpg b/tests/test-gtk-objects.jpg
new file mode 100644
index 0000000..478704e
--- /dev/null
+++ b/tests/test-gtk-objects.jpg
Binary files differ
diff --git a/tests/test-gtk-shortcut-client.c b/tests/test-gtk-shortcut-client.c
new file mode 100644
index 0000000..003885c
--- /dev/null
+++ b/tests/test-gtk-shortcut-client.c
@@ -0,0 +1,76 @@
+/*
+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>
+#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;
+ }
+}
diff --git a/tests/test-gtk-shortcut-server.c b/tests/test-gtk-shortcut-server.c
new file mode 100644
index 0000000..3b703a1
--- /dev/null
+++ b/tests/test-gtk-shortcut-server.c
@@ -0,0 +1,99 @@
+/*
+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 <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;
+}
+
diff --git a/tests/test-gtk-submenu-client.c b/tests/test-gtk-submenu-client.c
new file mode 100644
index 0000000..ec46122
--- /dev/null
+++ b/tests/test-gtk-submenu-client.c
@@ -0,0 +1,150 @@
+/*
+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;
+ return FALSE;
+}
+
+gboolean
+finished_func (gpointer user_data)
+{
+ 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(2, timer_func, menuitem);
+ g_timeout_add_seconds(6, finished_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..11cede0
--- /dev/null
+++ b/tests/test-gtk-submenu-server.c
@@ -0,0 +1,113 @@
+/*
+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;
+}
+
+static gboolean
+show_item (gpointer pmi)
+{
+ DbusmenuMenuitem * mi = DBUSMENU_MENUITEM(pmi);
+ g_debug("Showing item");
+
+ dbusmenu_menuitem_show_to_user(mi, 0);
+
+ 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");
+
+ g_timeout_add_seconds(2, show_item, item);
+
+ 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(4, show_item, item);
+
+ g_timeout_add_seconds(6, 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-json-01.json b/tests/test-json-01.json
new file mode 100644
index 0000000..08e9112
--- /dev/null
+++ b/tests/test-json-01.json
@@ -0,0 +1,4023 @@
+{
+ "id": 0,
+ "children-display": "submenu",
+ "submenu": [
+ {
+ "id": 5,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "File",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 6,
+ "enabled": true,
+ "label": "Quit",
+ "shortcut": [["Control", "q"]],
+ "visible": true
+ },
+ {
+ "id": 7,
+ "enabled": true,
+ "label": "Close all",
+ "shortcut": [["Control", "Shift", "w"]],
+ "visible": true
+ },
+ {
+ "id": 8,
+ "enabled": true,
+ "label": "Close",
+ "shortcut": [["Control", "w"]],
+ "visible": true
+ },
+ {
+ "id": 9,
+ "type": "separator"
+ },
+ {
+ "id": 10,
+ "enabled": true,
+ "label": "Send by Email...",
+ "visible": true
+ },
+ {
+ "id": 11,
+ "enabled": true,
+ "label": "Print...",
+ "shortcut": [["Control", "p"]],
+ "visible": true
+ },
+ {
+ "id": 12,
+ "enabled": true,
+ "label": "Page Setup",
+ "visible": true
+ },
+ {
+ "id": 13,
+ "type": "separator"
+ },
+ {
+ "id": 14,
+ "enabled": true,
+ "label": "Revert",
+ "visible": true
+ },
+ {
+ "id": 15,
+ "enabled": true,
+ "label": "Save as Template...",
+ "visible": true
+ },
+ {
+ "id": 16,
+ "enabled": true,
+ "label": "Save a Copy...",
+ "visible": true
+ },
+ {
+ "id": 17,
+ "enabled": true,
+ "label": "Save As...",
+ "shortcut": [["Control", "Shift", "s"]],
+ "visible": true
+ },
+ {
+ "id": 18,
+ "enabled": true,
+ "label": "Save",
+ "shortcut": [["Control", "s"]],
+ "visible": true
+ },
+ {
+ "id": 19,
+ "type": "separator"
+ },
+ {
+ "id": 20,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Open Recent",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 21,
+ "enabled": true,
+ "label": "Document History",
+ "visible": true
+ },
+ {
+ "id": 22,
+ "type": "separator"
+ },
+ {
+ "id": 23,
+ "enabled": true,
+ "label": "giggity.jpg",
+ "shortcut": [["Control", "2"]],
+ "visible": true
+ },
+ {
+ "id": 24,
+ "enabled": true,
+ "label": "Icon Height.svg",
+ "shortcut": [["Control", "1"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 25,
+ "enabled": true,
+ "label": "Open Location...",
+ "visible": true
+ },
+ {
+ "id": 26,
+ "enabled": true,
+ "label": "Open as Layers...",
+ "shortcut": [["Control", "Alt", "o"]],
+ "visible": true
+ },
+ {
+ "id": 27,
+ "enabled": true,
+ "label": "Open...",
+ "shortcut": [["Control", "o"]],
+ "visible": true
+ },
+ {
+ "id": 28,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Create",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 29,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Web Page Themes",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 30,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Classic.Gimp.Org",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 31,
+ "enabled": true,
+ "label": "Tube Sub-Sub-Button Label...",
+ "visible": true
+ },
+ {
+ "id": 32,
+ "enabled": true,
+ "label": "Tube Sub-Button Label...",
+ "visible": true
+ },
+ {
+ "id": 33,
+ "enabled": true,
+ "label": "Tube Button Label...",
+ "visible": true
+ },
+ {
+ "id": 34,
+ "enabled": true,
+ "label": "Small Header...",
+ "visible": true
+ },
+ {
+ "id": 35,
+ "enabled": true,
+ "label": "General Tube Labels...",
+ "visible": true
+ },
+ {
+ "id": 36,
+ "enabled": true,
+ "label": "Big Header...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 37,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Beveled Pattern",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 38,
+ "enabled": true,
+ "label": "Hrule...",
+ "visible": true
+ },
+ {
+ "id": 39,
+ "enabled": true,
+ "label": "Heading...",
+ "visible": true
+ },
+ {
+ "id": 40,
+ "enabled": true,
+ "label": "Button...",
+ "visible": true
+ },
+ {
+ "id": 41,
+ "enabled": true,
+ "label": "Bullet...",
+ "visible": true
+ },
+ {
+ "id": 42,
+ "enabled": true,
+ "label": "Arrow...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 43,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Alien Glow",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 44,
+ "enabled": true,
+ "label": "Hrule...",
+ "visible": true
+ },
+ {
+ "id": 45,
+ "enabled": true,
+ "label": "Button...",
+ "visible": true
+ },
+ {
+ "id": 46,
+ "enabled": true,
+ "label": "Bullet...",
+ "visible": true
+ },
+ {
+ "id": 47,
+ "enabled": true,
+ "label": "Arrow...",
+ "visible": true
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": 48,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Patterns",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 49,
+ "enabled": true,
+ "label": "Truchet...",
+ "visible": true
+ },
+ {
+ "id": 50,
+ "enabled": true,
+ "label": "Swirly...",
+ "visible": true
+ },
+ {
+ "id": 51,
+ "enabled": true,
+ "label": "Swirl-Tile...",
+ "visible": true
+ },
+ {
+ "id": 52,
+ "enabled": true,
+ "label": "Render Map...",
+ "visible": true
+ },
+ {
+ "id": 53,
+ "enabled": true,
+ "label": "Land...",
+ "visible": true
+ },
+ {
+ "id": 54,
+ "enabled": true,
+ "label": "Flatland...",
+ "visible": true
+ },
+ {
+ "id": 55,
+ "enabled": true,
+ "label": "Camouflage...",
+ "visible": true
+ },
+ {
+ "id": 56,
+ "enabled": true,
+ "label": "3D Truchet...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 57,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Logos",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 58,
+ "enabled": true,
+ "label": "Web Title Header...",
+ "visible": true
+ },
+ {
+ "id": 59,
+ "enabled": true,
+ "label": "Textured...",
+ "visible": true
+ },
+ {
+ "id": 60,
+ "enabled": true,
+ "label": "Text Circle...",
+ "visible": true
+ },
+ {
+ "id": 61,
+ "enabled": true,
+ "label": "Starscape...",
+ "visible": true
+ },
+ {
+ "id": 62,
+ "enabled": true,
+ "label": "Speed Text...",
+ "visible": true
+ },
+ {
+ "id": 63,
+ "enabled": true,
+ "label": "SOTA Chrome...",
+ "visible": true
+ },
+ {
+ "id": 64,
+ "enabled": true,
+ "label": "Particle Trace...",
+ "visible": true
+ },
+ {
+ "id": 65,
+ "enabled": true,
+ "label": "Newsprint Text...",
+ "visible": true
+ },
+ {
+ "id": 66,
+ "enabled": true,
+ "label": "Neon...",
+ "visible": true
+ },
+ {
+ "id": 67,
+ "enabled": true,
+ "label": "Imigre-26...",
+ "visible": true
+ },
+ {
+ "id": 68,
+ "enabled": true,
+ "label": "Gradient Bevel...",
+ "visible": true
+ },
+ {
+ "id": 69,
+ "enabled": true,
+ "label": "Glowing Hot...",
+ "visible": true
+ },
+ {
+ "id": 70,
+ "enabled": true,
+ "label": "Glossy...",
+ "visible": true
+ },
+ {
+ "id": 71,
+ "enabled": true,
+ "label": "Frosty...",
+ "visible": true
+ },
+ {
+ "id": 72,
+ "enabled": true,
+ "label": "Crystal...",
+ "visible": true
+ },
+ {
+ "id": 73,
+ "enabled": true,
+ "label": "Cool Metal...",
+ "visible": true
+ },
+ {
+ "id": 74,
+ "enabled": true,
+ "label": "Comic Book...",
+ "visible": true
+ },
+ {
+ "id": 75,
+ "enabled": true,
+ "label": "Chrome...",
+ "visible": true
+ },
+ {
+ "id": 76,
+ "enabled": true,
+ "label": "Chip Away...",
+ "visible": true
+ },
+ {
+ "id": 77,
+ "enabled": true,
+ "label": "Chalk...",
+ "visible": true
+ },
+ {
+ "id": 78,
+ "enabled": true,
+ "label": "Carved...",
+ "visible": true
+ },
+ {
+ "id": 79,
+ "enabled": true,
+ "label": "Bovination...",
+ "visible": true
+ },
+ {
+ "id": 80,
+ "enabled": true,
+ "label": "Blended...",
+ "visible": true
+ },
+ {
+ "id": 81,
+ "enabled": true,
+ "label": "Basic I...",
+ "visible": true
+ },
+ {
+ "id": 82,
+ "enabled": true,
+ "label": "Basic II...",
+ "visible": true
+ },
+ {
+ "id": 83,
+ "enabled": true,
+ "label": "Alien Neon...",
+ "visible": true
+ },
+ {
+ "id": 84,
+ "enabled": true,
+ "label": "Alien Glow...",
+ "visible": true
+ },
+ {
+ "id": 85,
+ "enabled": true,
+ "label": "3D Outline...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 86,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Buttons",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 87,
+ "enabled": true,
+ "label": "Simple Beveled Button...",
+ "visible": true
+ },
+ {
+ "id": 88,
+ "enabled": true,
+ "label": "Round Button...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 89,
+ "type": "separator"
+ },
+ {
+ "id": 90,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "xscanimage",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 91,
+ "enabled": false,
+ "label": "Device dialog...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 92,
+ "enabled": true,
+ "label": "Screenshot...",
+ "visible": true
+ },
+ {
+ "id": 93,
+ "enabled": true,
+ "label": "From Clipboard",
+ "shortcut": [["Control", "Shift", "v"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 94,
+ "enabled": true,
+ "label": "New...",
+ "shortcut": [["Control", "n"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 95,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Edit",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 96,
+ "enabled": true,
+ "label": "Units",
+ "visible": true
+ },
+ {
+ "id": 97,
+ "enabled": true,
+ "label": "Modules",
+ "visible": true
+ },
+ {
+ "id": 98,
+ "enabled": true,
+ "label": "Keyboard Shortcuts",
+ "visible": true
+ },
+ {
+ "id": 99,
+ "enabled": true,
+ "label": "Preferences",
+ "visible": true
+ },
+ {
+ "id": 100,
+ "type": "separator"
+ },
+ {
+ "id": 101,
+ "enabled": false,
+ "label": "Stroke Path...",
+ "visible": true
+ },
+ {
+ "id": 102,
+ "enabled": false,
+ "label": "Stroke Selection...",
+ "visible": true
+ },
+ {
+ "id": 103,
+ "enabled": true,
+ "label": "Fill with Pattern",
+ "shortcut": [["Control", "semicolon"]],
+ "visible": true
+ },
+ {
+ "id": 104,
+ "enabled": true,
+ "label": "Fill with BG Color",
+ "shortcut": [["Control", "period"]],
+ "visible": true
+ },
+ {
+ "id": 105,
+ "enabled": true,
+ "label": "Fill with FG Color",
+ "shortcut": [["Control", "comma"]],
+ "visible": true
+ },
+ {
+ "id": 106,
+ "enabled": true,
+ "label": "Clear",
+ "shortcut": [["Delete"]],
+ "visible": true
+ },
+ {
+ "id": 107,
+ "type": "separator"
+ },
+ {
+ "id": 108,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Buffer",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 109,
+ "enabled": true,
+ "label": "Paste Named...",
+ "visible": true
+ },
+ {
+ "id": 110,
+ "enabled": true,
+ "label": "Copy Visible Named...",
+ "visible": true
+ },
+ {
+ "id": 111,
+ "enabled": true,
+ "label": "Copy Named...",
+ "visible": true
+ },
+ {
+ "id": 112,
+ "enabled": true,
+ "label": "Cut Named...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 113,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Paste as",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 114,
+ "enabled": true,
+ "label": "New Pattern...",
+ "visible": true
+ },
+ {
+ "id": 115,
+ "enabled": true,
+ "label": "New Brush...",
+ "visible": true
+ },
+ {
+ "id": 116,
+ "enabled": true,
+ "label": "New Layer",
+ "visible": true
+ },
+ {
+ "id": 117,
+ "enabled": true,
+ "label": "New Image",
+ "shortcut": [["Control", "Shift", "v"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 118,
+ "enabled": true,
+ "label": "Paste Into",
+ "visible": true
+ },
+ {
+ "id": 119,
+ "enabled": true,
+ "label": "Paste",
+ "shortcut": [["Control", "v"]],
+ "visible": true
+ },
+ {
+ "id": 120,
+ "enabled": true,
+ "label": "Copy Visible",
+ "shortcut": [["Control", "Shift", "c"]],
+ "visible": true
+ },
+ {
+ "id": 121,
+ "enabled": true,
+ "label": "Copy",
+ "shortcut": [["Control", "c"]],
+ "visible": true
+ },
+ {
+ "id": 122,
+ "enabled": true,
+ "label": "Cut",
+ "shortcut": [["Control", "x"]],
+ "visible": true
+ },
+ {
+ "id": 123,
+ "type": "separator"
+ },
+ {
+ "id": 124,
+ "enabled": true,
+ "label": "Undo History",
+ "visible": true
+ },
+ {
+ "id": 3,
+ "enabled": false,
+ "label": "_Fade...",
+ "visible": true
+ },
+ {
+ "id": 2,
+ "enabled": false,
+ "label": "_Redo",
+ "shortcut": [["Control", "y"]],
+ "visible": true
+ },
+ {
+ "id": 1,
+ "enabled": false,
+ "label": "_Undo",
+ "shortcut": [["Control", "z"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 125,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Select",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 126,
+ "enabled": false,
+ "label": "To Path",
+ "visible": true
+ },
+ {
+ "id": 127,
+ "enabled": true,
+ "label": "Save to Channel",
+ "visible": true
+ },
+ {
+ "id": 128,
+ "enabled": true,
+ "label": "Toggle Quick Mask",
+ "shortcut": [["Shift", "q"]],
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 129,
+ "type": "separator"
+ },
+ {
+ "id": 130,
+ "enabled": true,
+ "label": "Distort...",
+ "visible": true
+ },
+ {
+ "id": 131,
+ "enabled": false,
+ "label": "Border...",
+ "visible": true
+ },
+ {
+ "id": 132,
+ "enabled": false,
+ "label": "Grow...",
+ "visible": true
+ },
+ {
+ "id": 133,
+ "enabled": false,
+ "label": "Shrink...",
+ "visible": true
+ },
+ {
+ "id": 134,
+ "enabled": false,
+ "label": "Sharpen",
+ "visible": true
+ },
+ {
+ "id": 135,
+ "enabled": false,
+ "label": "Feather...",
+ "visible": true
+ },
+ {
+ "id": 136,
+ "type": "separator"
+ },
+ {
+ "id": 137,
+ "enabled": true,
+ "label": "Selection Editor",
+ "visible": true
+ },
+ {
+ "id": 138,
+ "enabled": false,
+ "label": "From Path",
+ "shortcut": [["Shift", "v"]],
+ "visible": true
+ },
+ {
+ "id": 139,
+ "enabled": true,
+ "label": "By Color",
+ "shortcut": [["Shift", "o"]],
+ "visible": true
+ },
+ {
+ "id": 140,
+ "enabled": false,
+ "label": "Float",
+ "shortcut": [["Control", "Shift", "l"]],
+ "visible": true
+ },
+ {
+ "id": 141,
+ "enabled": true,
+ "label": "Invert",
+ "shortcut": [["Control", "i"]],
+ "visible": true
+ },
+ {
+ "id": 142,
+ "enabled": false,
+ "label": "None",
+ "shortcut": [["Control", "Shift", "a"]],
+ "visible": true
+ },
+ {
+ "id": 143,
+ "enabled": true,
+ "label": "All",
+ "shortcut": [["Control", "a"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 144,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "View",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 145,
+ "enabled": true,
+ "label": "Show Statusbar",
+ "toggle-state": 1,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 146,
+ "enabled": true,
+ "label": "Show Scrollbars",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 147,
+ "enabled": true,
+ "label": "Show Rulers",
+ "shortcut": [["Control", "Shift", "r"]],
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 148,
+ "enabled": true,
+ "label": "Show Menubar",
+ "toggle-state": 1,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 149,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Padding Color",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 150,
+ "enabled": true,
+ "label": "As in Preferences",
+ "visible": true
+ },
+ {
+ "id": 151,
+ "type": "separator"
+ },
+ {
+ "id": 152,
+ "enabled": true,
+ "label": "Select Custom Color...",
+ "visible": true
+ },
+ {
+ "id": 153,
+ "enabled": true,
+ "label": "Dark Check Color",
+ "visible": true
+ },
+ {
+ "id": 154,
+ "enabled": true,
+ "label": "Light Check Color",
+ "visible": true
+ },
+ {
+ "id": 155,
+ "enabled": true,
+ "label": "From Theme",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 156,
+ "type": "separator"
+ },
+ {
+ "id": 157,
+ "enabled": true,
+ "label": "Snap to Active Path",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 158,
+ "enabled": true,
+ "label": "Snap to Canvas Edges",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 159,
+ "enabled": true,
+ "label": "Snap to Grid",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 160,
+ "enabled": true,
+ "label": "Snap to Guides",
+ "toggle-state": 1,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 161,
+ "type": "separator"
+ },
+ {
+ "id": 162,
+ "enabled": true,
+ "label": "Show Sample Points",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 163,
+ "enabled": true,
+ "label": "Show Grid",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 164,
+ "enabled": true,
+ "label": "Show Guides",
+ "shortcut": [["Control", "Shift", "t"]],
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 165,
+ "enabled": true,
+ "label": "Show Layer Boundary",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 166,
+ "enabled": true,
+ "label": "Show Selection",
+ "shortcut": [["Control", "t"]],
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 167,
+ "type": "separator"
+ },
+ {
+ "id": 168,
+ "enabled": true,
+ "label": "Display Filters...",
+ "visible": true
+ },
+ {
+ "id": 169,
+ "enabled": true,
+ "label": "Navigation Window",
+ "visible": true
+ },
+ {
+ "id": 170,
+ "type": "separator"
+ },
+ {
+ "id": 171,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Fullscreen",
+ "shortcut": [["F11"]],
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 172,
+ "enabled": true,
+ "label": "Open Display...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 173,
+ "enabled": true,
+ "label": "Shrink Wrap",
+ "shortcut": [["Control", "e"]],
+ "visible": true
+ },
+ {
+ "id": 174,
+ "type": "separator"
+ },
+ {
+ "id": 175,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "_Zoom (67%)",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 176,
+ "enabled": true,
+ "label": "Othe_r (67%)...",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 177,
+ "type": "separator"
+ },
+ {
+ "id": 178,
+ "enabled": true,
+ "label": "1:16 (6.25%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 179,
+ "enabled": true,
+ "label": "1:8 (12.5%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 180,
+ "enabled": true,
+ "label": "1:4 (25%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 181,
+ "enabled": true,
+ "label": "1:2 (50%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 182,
+ "enabled": true,
+ "label": "1:1 (100%)",
+ "shortcut": [["1"]],
+ "toggle-state": 1,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 183,
+ "enabled": true,
+ "label": "2:1 (200%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 184,
+ "enabled": true,
+ "label": "4:1 (400%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 185,
+ "enabled": true,
+ "label": "8:1 (800%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 186,
+ "enabled": true,
+ "label": "16:1 (1600%)",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 187,
+ "type": "separator"
+ },
+ {
+ "id": 188,
+ "enabled": true,
+ "label": "Fill Window",
+ "visible": true
+ },
+ {
+ "id": 189,
+ "enabled": true,
+ "label": "Fit Image in Window",
+ "shortcut": [["Control", "Shift", "e"]],
+ "visible": true
+ },
+ {
+ "id": 190,
+ "enabled": true,
+ "label": "Zoom In",
+ "shortcut": [["plus"]],
+ "visible": true
+ },
+ {
+ "id": 191,
+ "enabled": true,
+ "label": "Zoom Out",
+ "shortcut": [["minus"]],
+ "visible": true
+ },
+ {
+ "id": 4,
+ "enabled": true,
+ "label": "Re_vert Zoom (67%)",
+ "shortcut": [["grave"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 192,
+ "enabled": true,
+ "label": "Dot for Dot",
+ "toggle-state": 1,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 193,
+ "enabled": true,
+ "label": "New View",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 194,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Image",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 195,
+ "enabled": true,
+ "label": "Image Properties",
+ "shortcut": [["Alt", "Return"]],
+ "visible": true
+ },
+ {
+ "id": 196,
+ "enabled": true,
+ "label": "Configure Grid...",
+ "visible": true
+ },
+ {
+ "id": 197,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Guides",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 198,
+ "enabled": true,
+ "label": "Remove all Guides",
+ "visible": true
+ },
+ {
+ "id": 199,
+ "enabled": true,
+ "label": "New Guides from Selection",
+ "visible": true
+ },
+ {
+ "id": 200,
+ "enabled": true,
+ "label": "New Guide...",
+ "visible": true
+ },
+ {
+ "id": 201,
+ "enabled": true,
+ "label": "New Guide (by Percent)...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 202,
+ "type": "separator"
+ },
+ {
+ "id": 203,
+ "enabled": true,
+ "label": "Align Visible Layers...",
+ "visible": true
+ },
+ {
+ "id": 204,
+ "enabled": true,
+ "label": "Flatten Image",
+ "visible": true
+ },
+ {
+ "id": 205,
+ "enabled": true,
+ "label": "Merge Visible Layers...",
+ "shortcut": [["Control", "m"]],
+ "visible": true
+ },
+ {
+ "id": 206,
+ "type": "separator"
+ },
+ {
+ "id": 207,
+ "enabled": true,
+ "label": "Zealous Crop",
+ "visible": true
+ },
+ {
+ "id": 208,
+ "enabled": true,
+ "label": "Autocrop Image",
+ "visible": true
+ },
+ {
+ "id": 209,
+ "enabled": false,
+ "label": "Crop to Selection",
+ "visible": true
+ },
+ {
+ "id": 210,
+ "type": "separator"
+ },
+ {
+ "id": 211,
+ "enabled": true,
+ "label": "Scale Image...",
+ "visible": true
+ },
+ {
+ "id": 212,
+ "enabled": true,
+ "label": "Print Size...",
+ "visible": true
+ },
+ {
+ "id": 213,
+ "enabled": false,
+ "label": "Fit Canvas to Selection",
+ "visible": true
+ },
+ {
+ "id": 214,
+ "enabled": true,
+ "label": "Fit Canvas to Layers",
+ "visible": true
+ },
+ {
+ "id": 215,
+ "enabled": true,
+ "label": "Canvas Size...",
+ "visible": true
+ },
+ {
+ "id": 216,
+ "type": "separator"
+ },
+ {
+ "id": 217,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Transform",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 218,
+ "enabled": true,
+ "label": "Guillotine",
+ "visible": true
+ },
+ {
+ "id": 219,
+ "type": "separator"
+ },
+ {
+ "id": 220,
+ "enabled": true,
+ "label": "Rotate 180\302\260",
+ "visible": true
+ },
+ {
+ "id": 221,
+ "enabled": true,
+ "label": "Rotate 90\302\260 counter-clockwise",
+ "visible": true
+ },
+ {
+ "id": 222,
+ "enabled": true,
+ "label": "Rotate 90\302\260 clockwise",
+ "visible": true
+ },
+ {
+ "id": 223,
+ "type": "separator"
+ },
+ {
+ "id": 224,
+ "enabled": true,
+ "label": "Flip Vertically",
+ "visible": true
+ },
+ {
+ "id": 225,
+ "enabled": true,
+ "label": "Flip Horizontally",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 226,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Mode",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 227,
+ "enabled": true,
+ "label": "Convert to Color Profile...",
+ "visible": true
+ },
+ {
+ "id": 228,
+ "enabled": true,
+ "label": "Assign Color Profile...",
+ "visible": true
+ },
+ {
+ "id": 229,
+ "type": "separator"
+ },
+ {
+ "id": 230,
+ "enabled": true,
+ "label": "Indexed...",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 231,
+ "enabled": true,
+ "label": "Grayscale",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 232,
+ "enabled": true,
+ "label": "RGB",
+ "toggle-state": 1,
+ "toggle-type": "checkmark",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 233,
+ "enabled": true,
+ "label": "Duplicate",
+ "shortcut": [["Control", "d"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 234,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Layer",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 235,
+ "enabled": true,
+ "label": "Autocrop Layer",
+ "visible": true
+ },
+ {
+ "id": 236,
+ "enabled": false,
+ "label": "Crop to Selection",
+ "visible": true
+ },
+ {
+ "id": 237,
+ "enabled": true,
+ "label": "Scale Layer...",
+ "visible": true
+ },
+ {
+ "id": 238,
+ "enabled": true,
+ "label": "Layer to Image Size",
+ "visible": true
+ },
+ {
+ "id": 239,
+ "enabled": true,
+ "label": "Layer Boundary Size...",
+ "visible": true
+ },
+ {
+ "id": 240,
+ "type": "separator"
+ },
+ {
+ "id": 241,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Transform",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 242,
+ "enabled": true,
+ "label": "Offset...",
+ "shortcut": [["Control", "Shift", "o"]],
+ "visible": true
+ },
+ {
+ "id": 243,
+ "type": "separator"
+ },
+ {
+ "id": 244,
+ "enabled": true,
+ "label": "Arbitrary Rotation...",
+ "visible": true
+ },
+ {
+ "id": 245,
+ "enabled": true,
+ "label": "Rotate 180\302\260",
+ "visible": true
+ },
+ {
+ "id": 246,
+ "enabled": true,
+ "label": "Rotate 90\302\260 counter-clockwise",
+ "visible": true
+ },
+ {
+ "id": 247,
+ "enabled": true,
+ "label": "Rotate 90\302\260 clockwise",
+ "visible": true
+ },
+ {
+ "id": 248,
+ "type": "separator"
+ },
+ {
+ "id": 249,
+ "enabled": true,
+ "label": "Flip Vertically",
+ "visible": true
+ },
+ {
+ "id": 250,
+ "enabled": true,
+ "label": "Flip Horizontally",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 251,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Transparency",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 252,
+ "enabled": true,
+ "label": "Intersect with Selection",
+ "visible": true
+ },
+ {
+ "id": 253,
+ "enabled": true,
+ "label": "Subtract from Selection",
+ "visible": true
+ },
+ {
+ "id": 254,
+ "enabled": true,
+ "label": "Add to Selection",
+ "visible": true
+ },
+ {
+ "id": 255,
+ "enabled": true,
+ "label": "Alpha to Selection",
+ "visible": true
+ },
+ {
+ "id": 256,
+ "type": "separator"
+ },
+ {
+ "id": 257,
+ "enabled": true,
+ "label": "Threshold Alpha...",
+ "visible": true
+ },
+ {
+ "id": 258,
+ "enabled": true,
+ "label": "Semi-Flatten",
+ "visible": true
+ },
+ {
+ "id": 259,
+ "enabled": true,
+ "label": "Color to Alpha...",
+ "visible": true
+ },
+ {
+ "id": 260,
+ "enabled": true,
+ "label": "Remove Alpha Channel",
+ "visible": true
+ },
+ {
+ "id": 261,
+ "enabled": false,
+ "label": "Add Alpha Channel",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 262,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Mask",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 263,
+ "enabled": false,
+ "label": "Intersect with Selection",
+ "visible": true
+ },
+ {
+ "id": 264,
+ "enabled": false,
+ "label": "Subtract from Selection",
+ "visible": true
+ },
+ {
+ "id": 265,
+ "enabled": false,
+ "label": "Add to Selection",
+ "visible": true
+ },
+ {
+ "id": 266,
+ "enabled": false,
+ "label": "Mask to Selection",
+ "visible": true
+ },
+ {
+ "id": 267,
+ "type": "separator"
+ },
+ {
+ "id": 268,
+ "enabled": false,
+ "label": "Disable Layer Mask",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 269,
+ "enabled": false,
+ "label": "Edit Layer Mask",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 270,
+ "enabled": false,
+ "label": "Show Layer Mask",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 271,
+ "type": "separator"
+ },
+ {
+ "id": 272,
+ "enabled": false,
+ "label": "Delete Layer Mask",
+ "visible": true
+ },
+ {
+ "id": 273,
+ "enabled": false,
+ "label": "Apply Layer Mask",
+ "visible": true
+ },
+ {
+ "id": 274,
+ "enabled": true,
+ "label": "Add Layer Mask...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 275,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Stack",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 276,
+ "enabled": true,
+ "label": "Reverse Layer Order",
+ "visible": true
+ },
+ {
+ "id": 277,
+ "type": "separator"
+ },
+ {
+ "id": 278,
+ "enabled": false,
+ "label": "Layer to Bottom",
+ "visible": true
+ },
+ {
+ "id": 279,
+ "enabled": false,
+ "label": "Layer to Top",
+ "visible": true
+ },
+ {
+ "id": 280,
+ "enabled": false,
+ "label": "Lower Layer",
+ "visible": true
+ },
+ {
+ "id": 281,
+ "enabled": false,
+ "label": "Raise Layer",
+ "visible": true
+ },
+ {
+ "id": 282,
+ "type": "separator"
+ },
+ {
+ "id": 283,
+ "enabled": false,
+ "label": "Select Bottom Layer",
+ "shortcut": [["End"]],
+ "visible": true
+ },
+ {
+ "id": 284,
+ "enabled": false,
+ "label": "Select Top Layer",
+ "shortcut": [["Home"]],
+ "visible": true
+ },
+ {
+ "id": 285,
+ "enabled": false,
+ "label": "Select Next Layer",
+ "shortcut": [["Page_Down"]],
+ "visible": true
+ },
+ {
+ "id": 286,
+ "enabled": false,
+ "label": "Select Previous Layer",
+ "shortcut": [["Page_Up"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 287,
+ "children-display": "submenu",
+ "type": "separator",
+ "submenu": [
+ {
+ "id": 288,
+ "enabled": false,
+ "label": "Empty",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 289,
+ "enabled": true,
+ "label": "Delete Layer",
+ "visible": true
+ },
+ {
+ "id": 290,
+ "enabled": false,
+ "label": "Merge Down",
+ "visible": true
+ },
+ {
+ "id": 291,
+ "enabled": false,
+ "label": "Anchor Layer",
+ "shortcut": [["Control", "h"]],
+ "visible": true
+ },
+ {
+ "id": 292,
+ "enabled": true,
+ "label": "Duplicate Layer",
+ "shortcut": [["Control", "Shift", "d"]],
+ "visible": true
+ },
+ {
+ "id": 293,
+ "enabled": true,
+ "label": "New from Visible",
+ "visible": true
+ },
+ {
+ "id": 294,
+ "enabled": true,
+ "label": "New Layer...",
+ "shortcut": [["Control", "Shift", "n"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 295,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Colors",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 296,
+ "enabled": true,
+ "label": "Retinex...",
+ "visible": true
+ },
+ {
+ "id": 297,
+ "enabled": true,
+ "label": "Maximum RGB...",
+ "visible": true
+ },
+ {
+ "id": 298,
+ "enabled": false,
+ "label": "Hot...",
+ "visible": true
+ },
+ {
+ "id": 299,
+ "enabled": true,
+ "label": "Filter Pack...",
+ "visible": true
+ },
+ {
+ "id": 300,
+ "enabled": true,
+ "label": "Color to Alpha...",
+ "visible": true
+ },
+ {
+ "id": 301,
+ "enabled": true,
+ "label": "Colorify...",
+ "visible": true
+ },
+ {
+ "id": 302,
+ "type": "separator"
+ },
+ {
+ "id": 303,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Info",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 304,
+ "enabled": true,
+ "label": "Smooth Palette...",
+ "visible": true
+ },
+ {
+ "id": 305,
+ "enabled": true,
+ "label": "Colorcube Analysis...",
+ "visible": true
+ },
+ {
+ "id": 306,
+ "enabled": true,
+ "label": "Border Average...",
+ "visible": true
+ },
+ {
+ "id": 307,
+ "enabled": true,
+ "label": "Histogram",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 308,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Map",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 309,
+ "enabled": true,
+ "label": "Sample Colorize...",
+ "visible": true
+ },
+ {
+ "id": 310,
+ "enabled": true,
+ "label": "Rotate Colors...",
+ "visible": true
+ },
+ {
+ "id": 311,
+ "enabled": true,
+ "label": "Palette Map",
+ "visible": true
+ },
+ {
+ "id": 312,
+ "enabled": true,
+ "label": "Gradient Map",
+ "visible": true
+ },
+ {
+ "id": 313,
+ "enabled": true,
+ "label": "Color Exchange...",
+ "visible": true
+ },
+ {
+ "id": 314,
+ "enabled": true,
+ "label": "Alien Map...",
+ "visible": true
+ },
+ {
+ "id": 315,
+ "type": "separator"
+ },
+ {
+ "id": 316,
+ "enabled": false,
+ "label": "Set Colormap...",
+ "visible": true
+ },
+ {
+ "id": 317,
+ "enabled": false,
+ "label": "Rearrange Colormap...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 318,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Components",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 319,
+ "enabled": false,
+ "label": "Recompose",
+ "visible": true
+ },
+ {
+ "id": 320,
+ "enabled": true,
+ "label": "Decompose...",
+ "visible": true
+ },
+ {
+ "id": 321,
+ "enabled": false,
+ "label": "Compose...",
+ "visible": true
+ },
+ {
+ "id": 322,
+ "enabled": true,
+ "label": "Channel Mixer...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 323,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Auto",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 324,
+ "enabled": true,
+ "label": "Stretch HSV",
+ "visible": true
+ },
+ {
+ "id": 325,
+ "enabled": true,
+ "label": "Stretch Contrast",
+ "visible": true
+ },
+ {
+ "id": 326,
+ "enabled": true,
+ "label": "Normalize",
+ "visible": true
+ },
+ {
+ "id": 327,
+ "enabled": true,
+ "label": "Color Enhance",
+ "visible": true
+ },
+ {
+ "id": 328,
+ "enabled": true,
+ "label": "White Balance",
+ "visible": true
+ },
+ {
+ "id": 329,
+ "enabled": true,
+ "label": "Equalize",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 330,
+ "type": "separator"
+ },
+ {
+ "id": 331,
+ "enabled": true,
+ "label": "Use GEGL",
+ "toggle-state": 0,
+ "toggle-type": "checkmark",
+ "visible": true
+ },
+ {
+ "id": 332,
+ "type": "separator"
+ },
+ {
+ "id": 333,
+ "enabled": true,
+ "label": "Value Invert",
+ "visible": true
+ },
+ {
+ "id": 334,
+ "enabled": true,
+ "label": "Invert",
+ "visible": true
+ },
+ {
+ "id": 335,
+ "type": "separator"
+ },
+ {
+ "id": 336,
+ "enabled": true,
+ "label": "Desaturate...",
+ "visible": true
+ },
+ {
+ "id": 337,
+ "enabled": true,
+ "label": "Posterize...",
+ "visible": true
+ },
+ {
+ "id": 338,
+ "enabled": true,
+ "label": "Curves...",
+ "visible": true
+ },
+ {
+ "id": 339,
+ "enabled": true,
+ "label": "Levels...",
+ "visible": true
+ },
+ {
+ "id": 340,
+ "enabled": true,
+ "label": "Threshold...",
+ "visible": true
+ },
+ {
+ "id": 341,
+ "enabled": true,
+ "label": "Brightness-Contrast...",
+ "visible": true
+ },
+ {
+ "id": 342,
+ "enabled": true,
+ "label": "Colorize...",
+ "visible": true
+ },
+ {
+ "id": 343,
+ "enabled": true,
+ "label": "Hue-Saturation...",
+ "visible": true
+ },
+ {
+ "id": 344,
+ "enabled": true,
+ "label": "Color Balance...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 345,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Tools",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 346,
+ "enabled": true,
+ "label": "Swap Colors",
+ "shortcut": [["x"]],
+ "visible": true
+ },
+ {
+ "id": 347,
+ "enabled": true,
+ "label": "Default Colors",
+ "shortcut": [["d"]],
+ "visible": true
+ },
+ {
+ "id": 348,
+ "enabled": true,
+ "label": "Toolbox",
+ "shortcut": [["Control", "b"]],
+ "visible": true
+ },
+ {
+ "id": 349,
+ "type": "separator"
+ },
+ {
+ "id": 350,
+ "enabled": true,
+ "label": "GEGL Operation...",
+ "visible": true
+ },
+ {
+ "id": 351,
+ "enabled": true,
+ "label": "Text",
+ "shortcut": [["t"]],
+ "visible": true
+ },
+ {
+ "id": 352,
+ "enabled": true,
+ "label": "Measure",
+ "shortcut": [["Shift", "m"]],
+ "visible": true
+ },
+ {
+ "id": 353,
+ "enabled": true,
+ "label": "Zoom",
+ "shortcut": [["z"]],
+ "visible": true
+ },
+ {
+ "id": 354,
+ "enabled": true,
+ "label": "Color Picker",
+ "shortcut": [["o"]],
+ "visible": true
+ },
+ {
+ "id": 355,
+ "enabled": true,
+ "label": "Paths",
+ "shortcut": [["b"]],
+ "visible": true
+ },
+ {
+ "id": 356,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Color Tools",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 357,
+ "enabled": true,
+ "label": "Desaturate...",
+ "visible": true
+ },
+ {
+ "id": 358,
+ "enabled": true,
+ "label": "Posterize...",
+ "visible": true
+ },
+ {
+ "id": 359,
+ "enabled": true,
+ "label": "Curves...",
+ "visible": true
+ },
+ {
+ "id": 360,
+ "enabled": true,
+ "label": "Levels...",
+ "visible": true
+ },
+ {
+ "id": 361,
+ "enabled": true,
+ "label": "Threshold...",
+ "visible": true
+ },
+ {
+ "id": 362,
+ "enabled": true,
+ "label": "Brightness-Contrast...",
+ "visible": true
+ },
+ {
+ "id": 363,
+ "enabled": true,
+ "label": "Colorize...",
+ "visible": true
+ },
+ {
+ "id": 364,
+ "enabled": true,
+ "label": "Hue-Saturation...",
+ "visible": true
+ },
+ {
+ "id": 365,
+ "enabled": true,
+ "label": "Color Balance...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 366,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Transform Tools",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 367,
+ "enabled": true,
+ "label": "Flip",
+ "shortcut": [["Shift", "f"]],
+ "visible": true
+ },
+ {
+ "id": 368,
+ "enabled": true,
+ "label": "Perspective",
+ "shortcut": [["Shift", "p"]],
+ "visible": true
+ },
+ {
+ "id": 369,
+ "enabled": true,
+ "label": "Shear",
+ "shortcut": [["Shift", "s"]],
+ "visible": true
+ },
+ {
+ "id": 370,
+ "enabled": true,
+ "label": "Scale",
+ "shortcut": [["Shift", "t"]],
+ "visible": true
+ },
+ {
+ "id": 371,
+ "enabled": true,
+ "label": "Rotate",
+ "shortcut": [["Shift", "r"]],
+ "visible": true
+ },
+ {
+ "id": 372,
+ "enabled": true,
+ "label": "Crop",
+ "shortcut": [["Shift", "c"]],
+ "visible": true
+ },
+ {
+ "id": 373,
+ "enabled": true,
+ "label": "Move",
+ "shortcut": [["m"]],
+ "visible": true
+ },
+ {
+ "id": 374,
+ "enabled": true,
+ "label": "Align",
+ "shortcut": [["q"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 375,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Paint Tools",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 376,
+ "enabled": true,
+ "label": "Dodge / Burn",
+ "shortcut": [["Shift", "d"]],
+ "visible": true
+ },
+ {
+ "id": 377,
+ "enabled": true,
+ "label": "Smudge",
+ "shortcut": [["s"]],
+ "visible": true
+ },
+ {
+ "id": 378,
+ "enabled": true,
+ "label": "Blur / Sharpen",
+ "shortcut": [["Shift", "u"]],
+ "visible": true
+ },
+ {
+ "id": 379,
+ "enabled": true,
+ "label": "Perspective Clone",
+ "visible": true
+ },
+ {
+ "id": 380,
+ "enabled": true,
+ "label": "Heal",
+ "shortcut": [["h"]],
+ "visible": true
+ },
+ {
+ "id": 381,
+ "enabled": true,
+ "label": "Clone",
+ "shortcut": [["c"]],
+ "visible": true
+ },
+ {
+ "id": 382,
+ "enabled": true,
+ "label": "Ink",
+ "shortcut": [["k"]],
+ "visible": true
+ },
+ {
+ "id": 383,
+ "enabled": true,
+ "label": "Airbrush",
+ "shortcut": [["a"]],
+ "visible": true
+ },
+ {
+ "id": 384,
+ "enabled": true,
+ "label": "Eraser",
+ "shortcut": [["Shift", "e"]],
+ "visible": true
+ },
+ {
+ "id": 385,
+ "enabled": true,
+ "label": "Paintbrush",
+ "shortcut": [["p"]],
+ "visible": true
+ },
+ {
+ "id": 386,
+ "enabled": true,
+ "label": "Pencil",
+ "shortcut": [["n"]],
+ "visible": true
+ },
+ {
+ "id": 387,
+ "enabled": true,
+ "label": "Blend",
+ "shortcut": [["l"]],
+ "visible": true
+ },
+ {
+ "id": 388,
+ "enabled": true,
+ "label": "Bucket Fill",
+ "shortcut": [["Shift", "b"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 389,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Selection Tools",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 390,
+ "enabled": true,
+ "label": "Intelligent Scissors",
+ "shortcut": [["i"]],
+ "visible": true
+ },
+ {
+ "id": 391,
+ "enabled": true,
+ "label": "By Color Select",
+ "shortcut": [["Shift", "o"]],
+ "visible": true
+ },
+ {
+ "id": 392,
+ "enabled": true,
+ "label": "Fuzzy Select",
+ "shortcut": [["u"]],
+ "visible": true
+ },
+ {
+ "id": 393,
+ "enabled": true,
+ "label": "Foreground Select",
+ "visible": true
+ },
+ {
+ "id": 394,
+ "enabled": true,
+ "label": "Free Select",
+ "shortcut": [["f"]],
+ "visible": true
+ },
+ {
+ "id": 395,
+ "enabled": true,
+ "label": "Ellipse Select",
+ "shortcut": [["e"]],
+ "visible": true
+ },
+ {
+ "id": 396,
+ "enabled": true,
+ "label": "Rectangle Select",
+ "shortcut": [["r"]],
+ "visible": true
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": 397,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Filters",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 398,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Script-Fu",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 399,
+ "enabled": true,
+ "label": "Start Server...",
+ "visible": true
+ },
+ {
+ "id": 400,
+ "enabled": true,
+ "label": "Refresh Scripts",
+ "visible": true
+ },
+ {
+ "id": 401,
+ "enabled": true,
+ "label": "Console",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 402,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Python-Fu",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 403,
+ "enabled": true,
+ "label": "Console",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 404,
+ "type": "separator"
+ },
+ {
+ "id": 405,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Alpha to Logo",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 406,
+ "enabled": true,
+ "label": "Textured...",
+ "visible": true
+ },
+ {
+ "id": 407,
+ "enabled": true,
+ "label": "Particle Trace...",
+ "visible": true
+ },
+ {
+ "id": 408,
+ "enabled": true,
+ "label": "Neon...",
+ "visible": true
+ },
+ {
+ "id": 409,
+ "enabled": true,
+ "label": "Gradient Bevel...",
+ "visible": true
+ },
+ {
+ "id": 410,
+ "enabled": true,
+ "label": "Glowing Hot...",
+ "visible": true
+ },
+ {
+ "id": 411,
+ "enabled": true,
+ "label": "Glossy...",
+ "visible": true
+ },
+ {
+ "id": 412,
+ "enabled": true,
+ "label": "Frosty...",
+ "visible": true
+ },
+ {
+ "id": 413,
+ "enabled": true,
+ "label": "Cool Metal...",
+ "visible": true
+ },
+ {
+ "id": 414,
+ "enabled": true,
+ "label": "Comic Book...",
+ "visible": true
+ },
+ {
+ "id": 415,
+ "enabled": true,
+ "label": "Chrome...",
+ "visible": true
+ },
+ {
+ "id": 416,
+ "enabled": true,
+ "label": "Chip Away...",
+ "visible": true
+ },
+ {
+ "id": 417,
+ "enabled": true,
+ "label": "Chalk...",
+ "visible": true
+ },
+ {
+ "id": 418,
+ "enabled": true,
+ "label": "Bovination...",
+ "visible": true
+ },
+ {
+ "id": 419,
+ "enabled": true,
+ "label": "Blended...",
+ "visible": true
+ },
+ {
+ "id": 420,
+ "enabled": true,
+ "label": "Basic I...",
+ "visible": true
+ },
+ {
+ "id": 421,
+ "enabled": true,
+ "label": "Basic II...",
+ "visible": true
+ },
+ {
+ "id": 422,
+ "enabled": true,
+ "label": "Alien Neon...",
+ "visible": true
+ },
+ {
+ "id": 423,
+ "enabled": true,
+ "label": "Alien Glow...",
+ "visible": true
+ },
+ {
+ "id": 424,
+ "enabled": true,
+ "label": "3D Outline...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 425,
+ "type": "separator"
+ },
+ {
+ "id": 426,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Animation",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 427,
+ "enabled": true,
+ "label": "Unoptimize",
+ "visible": true
+ },
+ {
+ "id": 428,
+ "enabled": true,
+ "label": "Playback...",
+ "visible": true
+ },
+ {
+ "id": 429,
+ "enabled": true,
+ "label": "Optimize (for GIF)",
+ "visible": true
+ },
+ {
+ "id": 430,
+ "enabled": true,
+ "label": "Optimize (Difference)",
+ "visible": true
+ },
+ {
+ "id": 431,
+ "type": "separator"
+ },
+ {
+ "id": 432,
+ "enabled": true,
+ "label": "Waves...",
+ "visible": true
+ },
+ {
+ "id": 433,
+ "enabled": true,
+ "label": "Spinning Globe...",
+ "visible": true
+ },
+ {
+ "id": 434,
+ "enabled": true,
+ "label": "Rippling...",
+ "visible": true
+ },
+ {
+ "id": 435,
+ "enabled": true,
+ "label": "Burn-In...",
+ "visible": true
+ },
+ {
+ "id": 436,
+ "enabled": true,
+ "label": "Blend...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 437,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Web",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 438,
+ "enabled": true,
+ "label": "Slice...",
+ "visible": true
+ },
+ {
+ "id": 439,
+ "enabled": true,
+ "label": "Semi-Flatten",
+ "visible": true
+ },
+ {
+ "id": 440,
+ "enabled": true,
+ "label": "Image Map...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 441,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Render",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 442,
+ "enabled": true,
+ "label": "Spyrogimp...",
+ "visible": true
+ },
+ {
+ "id": 443,
+ "enabled": true,
+ "label": "Sphere Designer...",
+ "visible": true
+ },
+ {
+ "id": 444,
+ "enabled": true,
+ "label": "Line Nova...",
+ "visible": true
+ },
+ {
+ "id": 445,
+ "enabled": true,
+ "label": "Lava...",
+ "visible": true
+ },
+ {
+ "id": 446,
+ "enabled": true,
+ "label": "Gfig...",
+ "visible": true
+ },
+ {
+ "id": 447,
+ "enabled": true,
+ "label": "Fractal Explorer...",
+ "visible": true
+ },
+ {
+ "id": 448,
+ "enabled": true,
+ "label": "Circuit...",
+ "visible": true
+ },
+ {
+ "id": 449,
+ "type": "separator"
+ },
+ {
+ "id": 450,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Pattern",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 451,
+ "enabled": true,
+ "label": "Sinus...",
+ "visible": true
+ },
+ {
+ "id": 452,
+ "enabled": true,
+ "label": "Qbist...",
+ "visible": true
+ },
+ {
+ "id": 453,
+ "enabled": true,
+ "label": "Maze...",
+ "visible": true
+ },
+ {
+ "id": 454,
+ "enabled": true,
+ "label": "Jigsaw...",
+ "visible": true
+ },
+ {
+ "id": 455,
+ "enabled": true,
+ "label": "Grid...",
+ "visible": true
+ },
+ {
+ "id": 456,
+ "enabled": true,
+ "label": "Diffraction Patterns...",
+ "visible": true
+ },
+ {
+ "id": 457,
+ "enabled": true,
+ "label": "CML Explorer...",
+ "visible": true
+ },
+ {
+ "id": 458,
+ "enabled": true,
+ "label": "Checkerboard...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 459,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Nature",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 460,
+ "enabled": true,
+ "label": "IFS Fractal...",
+ "visible": true
+ },
+ {
+ "id": 461,
+ "enabled": true,
+ "label": "Flame...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 462,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Clouds",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 463,
+ "enabled": true,
+ "label": "Solid Noise...",
+ "visible": true
+ },
+ {
+ "id": 464,
+ "enabled": true,
+ "label": "Plasma...",
+ "visible": true
+ },
+ {
+ "id": 465,
+ "enabled": true,
+ "label": "Fog...",
+ "visible": true
+ },
+ {
+ "id": 466,
+ "enabled": true,
+ "label": "Difference Clouds...",
+ "visible": true
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": 467,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Map",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 468,
+ "enabled": true,
+ "label": "Warp...",
+ "visible": true
+ },
+ {
+ "id": 469,
+ "enabled": true,
+ "label": "Tile...",
+ "visible": true
+ },
+ {
+ "id": 470,
+ "enabled": true,
+ "label": "Small Tiles...",
+ "visible": true
+ },
+ {
+ "id": 471,
+ "enabled": true,
+ "label": "Paper Tile...",
+ "visible": true
+ },
+ {
+ "id": 472,
+ "enabled": true,
+ "label": "Map Object...",
+ "visible": true
+ },
+ {
+ "id": 473,
+ "enabled": true,
+ "label": "Make Seamless",
+ "visible": true
+ },
+ {
+ "id": 474,
+ "enabled": true,
+ "label": "Illusion...",
+ "visible": true
+ },
+ {
+ "id": 475,
+ "enabled": true,
+ "label": "Fractal Trace...",
+ "visible": true
+ },
+ {
+ "id": 476,
+ "enabled": true,
+ "label": "Displace...",
+ "visible": true
+ },
+ {
+ "id": 477,
+ "enabled": true,
+ "label": "Bump Map...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 478,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Decor",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 479,
+ "enabled": false,
+ "label": "Stencil Chrome...",
+ "visible": true
+ },
+ {
+ "id": 480,
+ "enabled": false,
+ "label": "Stencil Carve...",
+ "visible": true
+ },
+ {
+ "id": 481,
+ "enabled": false,
+ "label": "Slide...",
+ "visible": true
+ },
+ {
+ "id": 482,
+ "enabled": false,
+ "label": "Round Corners...",
+ "visible": true
+ },
+ {
+ "id": 483,
+ "enabled": true,
+ "label": "Old Photo...",
+ "visible": true
+ },
+ {
+ "id": 484,
+ "enabled": true,
+ "label": "Fuzzy Border...",
+ "visible": true
+ },
+ {
+ "id": 485,
+ "enabled": true,
+ "label": "Coffee Stain...",
+ "visible": true
+ },
+ {
+ "id": 486,
+ "enabled": true,
+ "label": "Add Border...",
+ "visible": true
+ },
+ {
+ "id": 487,
+ "enabled": true,
+ "label": "Add Bevel...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 488,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Artistic",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 489,
+ "enabled": true,
+ "label": "Weave...",
+ "visible": true
+ },
+ {
+ "id": 490,
+ "enabled": true,
+ "label": "Van Gogh (LIC)...",
+ "visible": true
+ },
+ {
+ "id": 491,
+ "enabled": true,
+ "label": "Softglow...",
+ "visible": true
+ },
+ {
+ "id": 492,
+ "enabled": true,
+ "label": "Predator...",
+ "visible": true
+ },
+ {
+ "id": 493,
+ "enabled": true,
+ "label": "Photocopy...",
+ "visible": true
+ },
+ {
+ "id": 494,
+ "enabled": true,
+ "label": "Oilify...",
+ "visible": true
+ },
+ {
+ "id": 495,
+ "enabled": true,
+ "label": "GIMPressionist...",
+ "visible": true
+ },
+ {
+ "id": 496,
+ "enabled": true,
+ "label": "Cubism...",
+ "visible": true
+ },
+ {
+ "id": 497,
+ "enabled": true,
+ "label": "Clothify...",
+ "visible": true
+ },
+ {
+ "id": 498,
+ "enabled": true,
+ "label": "Cartoon...",
+ "visible": true
+ },
+ {
+ "id": 499,
+ "enabled": true,
+ "label": "Apply Canvas...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 500,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Combine",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 501,
+ "enabled": true,
+ "label": "Filmstrip...",
+ "visible": true
+ },
+ {
+ "id": 502,
+ "enabled": true,
+ "label": "Depth Merge...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 503,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Generic",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 504,
+ "enabled": true,
+ "label": "Erode",
+ "visible": true
+ },
+ {
+ "id": 505,
+ "enabled": true,
+ "label": "Dilate",
+ "visible": true
+ },
+ {
+ "id": 506,
+ "enabled": true,
+ "label": "Convolution Matrix...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 507,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Edge-Detect",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 508,
+ "enabled": true,
+ "label": "Sobel...",
+ "visible": true
+ },
+ {
+ "id": 509,
+ "enabled": true,
+ "label": "Neon...",
+ "visible": true
+ },
+ {
+ "id": 510,
+ "enabled": true,
+ "label": "Laplace",
+ "visible": true
+ },
+ {
+ "id": 511,
+ "enabled": true,
+ "label": "Edge...",
+ "visible": true
+ },
+ {
+ "id": 512,
+ "enabled": true,
+ "label": "Difference of Gaussians...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 513,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Noise",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 514,
+ "enabled": true,
+ "label": "Spread...",
+ "visible": true
+ },
+ {
+ "id": 515,
+ "enabled": true,
+ "label": "Slur...",
+ "visible": true
+ },
+ {
+ "id": 516,
+ "enabled": true,
+ "label": "RGB Noise...",
+ "visible": true
+ },
+ {
+ "id": 517,
+ "enabled": true,
+ "label": "Pick...",
+ "visible": true
+ },
+ {
+ "id": 518,
+ "enabled": true,
+ "label": "Hurl...",
+ "visible": true
+ },
+ {
+ "id": 519,
+ "enabled": true,
+ "label": "HSV Noise...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 520,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Light and Shadow",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 521,
+ "enabled": true,
+ "label": "Glass Tile...",
+ "visible": true
+ },
+ {
+ "id": 522,
+ "enabled": true,
+ "label": "Apply Lens...",
+ "visible": true
+ },
+ {
+ "id": 523,
+ "type": "separator"
+ },
+ {
+ "id": 524,
+ "enabled": true,
+ "label": "Xach-Effect...",
+ "visible": true
+ },
+ {
+ "id": 525,
+ "enabled": true,
+ "label": "Perspective...",
+ "visible": true
+ },
+ {
+ "id": 526,
+ "enabled": true,
+ "label": "Drop Shadow...",
+ "visible": true
+ },
+ {
+ "id": 527,
+ "type": "separator"
+ },
+ {
+ "id": 528,
+ "enabled": true,
+ "label": "Supernova...",
+ "visible": true
+ },
+ {
+ "id": 529,
+ "enabled": true,
+ "label": "Sparkle...",
+ "visible": true
+ },
+ {
+ "id": 530,
+ "enabled": true,
+ "label": "Lighting Effects...",
+ "visible": true
+ },
+ {
+ "id": 531,
+ "enabled": true,
+ "label": "Lens Flare...",
+ "visible": true
+ },
+ {
+ "id": 532,
+ "enabled": true,
+ "label": "Gradient Flare...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 533,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Distorts",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 534,
+ "enabled": true,
+ "label": "Wind...",
+ "visible": true
+ },
+ {
+ "id": 535,
+ "enabled": true,
+ "label": "Whirl and Pinch...",
+ "visible": true
+ },
+ {
+ "id": 536,
+ "enabled": true,
+ "label": "Waves...",
+ "visible": true
+ },
+ {
+ "id": 537,
+ "enabled": true,
+ "label": "Video...",
+ "visible": true
+ },
+ {
+ "id": 538,
+ "enabled": true,
+ "label": "Value Propagate...",
+ "visible": true
+ },
+ {
+ "id": 539,
+ "enabled": true,
+ "label": "Shift...",
+ "visible": true
+ },
+ {
+ "id": 540,
+ "enabled": true,
+ "label": "Ripple...",
+ "visible": true
+ },
+ {
+ "id": 541,
+ "enabled": true,
+ "label": "Polar Coordinates...",
+ "visible": true
+ },
+ {
+ "id": 542,
+ "enabled": true,
+ "label": "Pagecurl...",
+ "visible": true
+ },
+ {
+ "id": 543,
+ "enabled": true,
+ "label": "Newsprint...",
+ "visible": true
+ },
+ {
+ "id": 544,
+ "enabled": true,
+ "label": "Mosaic...",
+ "visible": true
+ },
+ {
+ "id": 545,
+ "enabled": true,
+ "label": "Lens Distortion...",
+ "visible": true
+ },
+ {
+ "id": 546,
+ "enabled": true,
+ "label": "IWarp...",
+ "visible": true
+ },
+ {
+ "id": 547,
+ "enabled": true,
+ "label": "Erase Every Other Row...",
+ "visible": true
+ },
+ {
+ "id": 548,
+ "enabled": true,
+ "label": "Engrave...",
+ "visible": true
+ },
+ {
+ "id": 549,
+ "enabled": true,
+ "label": "Emboss...",
+ "visible": true
+ },
+ {
+ "id": 550,
+ "enabled": true,
+ "label": "Curve Bend...",
+ "visible": true
+ },
+ {
+ "id": 551,
+ "enabled": true,
+ "label": "Blinds...",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 552,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Enhance",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 553,
+ "enabled": true,
+ "label": "Unsharp Mask...",
+ "visible": true
+ },
+ {
+ "id": 554,
+ "enabled": true,
+ "label": "Sharpen...",
+ "visible": true
+ },
+ {
+ "id": 555,
+ "enabled": true,
+ "label": "Red Eye Removal...",
+ "visible": true
+ },
+ {
+ "id": 556,
+ "enabled": false,
+ "label": "NL Filter...",
+ "visible": true
+ },
+ {
+ "id": 557,
+ "enabled": true,
+ "label": "Destripe...",
+ "visible": true
+ },
+ {
+ "id": 558,
+ "enabled": true,
+ "label": "Despeckle...",
+ "visible": true
+ },
+ {
+ "id": 559,
+ "enabled": true,
+ "label": "Deinterlace...",
+ "visible": true
+ },
+ {
+ "id": 560,
+ "enabled": true,
+ "label": "Antialias",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 561,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Blur",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 562,
+ "enabled": true,
+ "label": "Tileable Blur...",
+ "visible": true
+ },
+ {
+ "id": 563,
+ "enabled": true,
+ "label": "Selective Gaussian Blur...",
+ "visible": true
+ },
+ {
+ "id": 564,
+ "enabled": true,
+ "label": "Pixelize...",
+ "visible": true
+ },
+ {
+ "id": 565,
+ "enabled": true,
+ "label": "Motion Blur...",
+ "visible": true
+ },
+ {
+ "id": 566,
+ "enabled": true,
+ "label": "Gaussian Blur...",
+ "visible": true
+ },
+ {
+ "id": 567,
+ "enabled": true,
+ "label": "Blur",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 568,
+ "type": "separator"
+ },
+ {
+ "id": 569,
+ "enabled": true,
+ "label": "Reset all Filters",
+ "visible": true
+ },
+ {
+ "id": 570,
+ "children-display": "submenu",
+ "enabled": false,
+ "label": "Re-Show Last",
+ "shortcut": [["Control", "Shift", "f"]],
+ "visible": true,
+ "submenu": [
+ {
+ "id": 571,
+ "enabled": false,
+ "label": "Empty",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 572,
+ "enabled": false,
+ "label": "Repeat Last",
+ "shortcut": [["Control", "f"]],
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 573,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Windows",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 574,
+ "enabled": true,
+ "label": "Toolbox",
+ "shortcut": [["Control", "b"]],
+ "visible": true
+ },
+ {
+ "id": 575,
+ "type": "separator"
+ },
+ {
+ "id": 576,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Dockable Dialogs",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 577,
+ "enabled": true,
+ "label": "Error Console",
+ "visible": true
+ },
+ {
+ "id": 578,
+ "enabled": true,
+ "label": "Tools",
+ "visible": true
+ },
+ {
+ "id": 579,
+ "enabled": true,
+ "label": "Templates",
+ "visible": true
+ },
+ {
+ "id": 580,
+ "enabled": true,
+ "label": "Document History",
+ "visible": true
+ },
+ {
+ "id": 581,
+ "enabled": true,
+ "label": "Images",
+ "visible": true
+ },
+ {
+ "id": 582,
+ "type": "separator"
+ },
+ {
+ "id": 583,
+ "enabled": true,
+ "label": "Buffers",
+ "visible": true
+ },
+ {
+ "id": 584,
+ "enabled": true,
+ "label": "Fonts",
+ "visible": true
+ },
+ {
+ "id": 585,
+ "enabled": true,
+ "label": "Palettes",
+ "visible": true
+ },
+ {
+ "id": 586,
+ "enabled": true,
+ "label": "Gradients",
+ "shortcut": [["Control", "g"]],
+ "visible": true
+ },
+ {
+ "id": 587,
+ "enabled": true,
+ "label": "Patterns",
+ "shortcut": [["Control", "Shift", "p"]],
+ "visible": true
+ },
+ {
+ "id": 588,
+ "enabled": true,
+ "label": "Brushes",
+ "shortcut": [["Control", "Shift", "b"]],
+ "visible": true
+ },
+ {
+ "id": 589,
+ "enabled": true,
+ "label": "Colors",
+ "visible": true
+ },
+ {
+ "id": 590,
+ "type": "separator"
+ },
+ {
+ "id": 591,
+ "enabled": true,
+ "label": "Sample Points",
+ "visible": true
+ },
+ {
+ "id": 592,
+ "enabled": true,
+ "label": "Pointer",
+ "visible": true
+ },
+ {
+ "id": 593,
+ "enabled": true,
+ "label": "Undo History",
+ "visible": true
+ },
+ {
+ "id": 594,
+ "enabled": true,
+ "label": "Navigation",
+ "visible": true
+ },
+ {
+ "id": 595,
+ "enabled": true,
+ "label": "Selection Editor",
+ "visible": true
+ },
+ {
+ "id": 596,
+ "enabled": true,
+ "label": "Histogram",
+ "visible": true
+ },
+ {
+ "id": 597,
+ "enabled": true,
+ "label": "Colormap",
+ "visible": true
+ },
+ {
+ "id": 598,
+ "enabled": true,
+ "label": "Paths",
+ "visible": true
+ },
+ {
+ "id": 599,
+ "enabled": true,
+ "label": "Channels",
+ "visible": true
+ },
+ {
+ "id": 600,
+ "enabled": true,
+ "label": "Layers",
+ "shortcut": [["Control", "l"]],
+ "visible": true
+ },
+ {
+ "id": 601,
+ "type": "separator"
+ },
+ {
+ "id": 602,
+ "enabled": true,
+ "label": "Device Status",
+ "visible": true
+ },
+ {
+ "id": 603,
+ "enabled": true,
+ "label": "Tool Options",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 604,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Recently Closed Docks",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 605,
+ "enabled": false,
+ "label": "Empty",
+ "visible": true
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": 606,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "Help",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 607,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "User Manual",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 608,
+ "enabled": true,
+ "label": "Working with Digital Camera Photos",
+ "visible": true
+ },
+ {
+ "id": 609,
+ "enabled": true,
+ "label": "Using Paths",
+ "visible": true
+ },
+ {
+ "id": 610,
+ "enabled": true,
+ "label": "Preparing your Images for the Web",
+ "visible": true
+ },
+ {
+ "id": 611,
+ "enabled": true,
+ "label": "How to Use Dialogs",
+ "visible": true
+ },
+ {
+ "id": 612,
+ "enabled": true,
+ "label": "Drawing Simple Objects",
+ "visible": true
+ },
+ {
+ "id": 613,
+ "enabled": true,
+ "label": "Create, Open and Save Files",
+ "visible": true
+ },
+ {
+ "id": 614,
+ "enabled": true,
+ "label": "Basic Concepts",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 615,
+ "children-display": "submenu",
+ "enabled": true,
+ "label": "GIMP Online",
+ "visible": true,
+ "submenu": [
+ {
+ "id": 616,
+ "enabled": true,
+ "label": "User Manual Web Site",
+ "visible": true
+ },
+ {
+ "id": 617,
+ "enabled": true,
+ "label": "Plug-in Registry",
+ "visible": true
+ },
+ {
+ "id": 618,
+ "enabled": true,
+ "label": "Main Web Site",
+ "visible": true
+ },
+ {
+ "id": 619,
+ "enabled": true,
+ "label": "Developer Web Site",
+ "visible": true
+ }
+ ]
+ },
+ {
+ "id": 620,
+ "type": "separator"
+ },
+ {
+ "id": 621,
+ "enabled": true,
+ "label": "Procedure Browser",
+ "visible": true
+ },
+ {
+ "id": 622,
+ "enabled": true,
+ "label": "Plug-In Browser",
+ "visible": true
+ },
+ {
+ "id": 623,
+ "type": "separator"
+ },
+ {
+ "id": 624,
+ "enabled": true,
+ "label": "About",
+ "visible": true
+ },
+ {
+ "id": 625,
+ "enabled": true,
+ "label": "Tip of the Day",
+ "visible": true
+ },
+ {
+ "id": 626,
+ "enabled": true,
+ "label": "Context Help",
+ "shortcut": [["Shift", "F1"]],
+ "visible": true
+ },
+ {
+ "id": 627,
+ "enabled": true,
+ "label": "Help",
+ "shortcut": [["F1"]],
+ "visible": true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/test-json-client.c b/tests/test-json-client.c
new file mode 100644
index 0000000..f9da55e
--- /dev/null
+++ b/tests/test-json-client.c
@@ -0,0 +1,75 @@
+/*
+Test to check the json-loader and dbusmenu-dumper
+
+Copyright 2010 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 <gio/gio.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+GMainLoop * mainloop = NULL;
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+ g_debug("Wait for friends");
+
+ GError * error = NULL;
+ DBusGConnection * session_bus = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+ if (error != NULL) {
+ g_error("Unable to get session bus: %s", error->message);
+ return 1;
+ }
+
+ DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(session_bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
+
+ gboolean has_owner = FALSE;
+ gint owner_count = 0;
+ while (!has_owner && owner_count < 10000) {
+ org_freedesktop_DBus_name_has_owner(bus_proxy, "org.dbusmenu.test", &has_owner, NULL);
+ owner_count++;
+ }
+
+ if (owner_count == 10000) {
+ g_error("Unable to get name owner after 10000 tries");
+ return 1;
+ }
+
+ g_usleep(500000);
+
+ g_debug("Initing");
+
+ gchar * command = g_strdup_printf("%s --dbus-name=org.dbusmenu.test --dbus-object=/org/test", argv[1]);
+ g_debug("Executing: %s", command);
+
+ gchar * output;
+ g_spawn_command_line_sync(command, &output, NULL, NULL, NULL);
+
+ GFile * ofile = g_file_new_for_commandline_arg(argv[2]);
+ if (ofile != NULL) {
+ g_file_replace_contents(ofile, output, g_utf8_strlen(output, -1), NULL, FALSE, 0, NULL, NULL, NULL);
+ }
+
+ g_debug("Exiting");
+
+ return 0;
+}
diff --git a/tests/test-json-server.c b/tests/test-json-server.c
new file mode 100644
index 0000000..fe9507a
--- /dev/null
+++ b/tests/test-json-server.c
@@ -0,0 +1,81 @@
+/*
+Test to check the json-loader and dbusmenu-dumper
+
+Copyright 2010 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/server.h>
+#include <libdbusmenu-glib/menuitem.h>
+
+#include "json-loader.h"
+
+static GMainLoop * mainloop = NULL;
+
+static gboolean
+timer_func (gpointer data)
+{
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+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, "org.dbusmenu.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_json_build_from_file(argv[1]);
+ g_return_val_if_fail(root!=NULL, 1);
+
+ dbusmenu_server_set_root(server, root);
+
+ g_timeout_add(10000, 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 55d631e..3256f7e 100644
--- a/tools/dbusmenu-dumper.c
+++ b/tools/dbusmenu-dumper.c
@@ -21,26 +21,140 @@ 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;
+static gchar * value2string (const GValue * value, int depth);
+
+static gchar *
+strv_dumper(const GValue * value)
+{
+ gchar ** strv = (gchar **)g_value_get_boxed(value);
+
+ gchar * joined = g_strjoinv("\", \"", strv);
+ gchar * retval = g_strdup_printf("[\"%s\"]", joined);
+ g_free(joined);
+ return retval;
+}
+
+typedef struct _collection_iterator_t collection_iterator_t;
+struct _collection_iterator_t {
+ gchar * space;
+ GPtrArray * array;
+ gboolean first;
+ int depth;
+};
+
+static void
+collection_iterate (const GValue * value, gpointer user_data)
+{
+ collection_iterator_t * iter = (collection_iterator_t *)user_data;
+
+ gchar * str = value2string(value, iter->depth);
+ gchar * retval = NULL;
+
+ if (iter->first) {
+ iter->first = FALSE;
+ retval = g_strdup_printf("\n%s%s", iter->space, str);
+ } else {
+ retval = g_strdup_printf(",\n%s%s", iter->space, str);
+ }
+
+ g_ptr_array_add(iter->array, retval);
+ g_free(str);
+
+ return;
+}
+
+static gchar *
+collection_dumper (const GValue * value, int depth)
+{
+ gchar * space = g_strnfill(depth, ' ');
+ GPtrArray * array = g_ptr_array_new_with_free_func(g_free);
+
+ g_ptr_array_add(array, g_strdup("["));
+
+ collection_iterator_t iter;
+ iter.space = space;
+ iter.array = array;
+ iter.first = TRUE;
+ iter.depth = depth + 2;
+
+ dbus_g_type_collection_value_iterate(value, collection_iterate, &iter);
+
+ g_ptr_array_add(array, g_strdup_printf("\n%s]", space));
+
+ g_free(space);
+
+ gchar * retstr = NULL;
+ if (array->len == 3) {
+ retstr = g_strdup_printf("[%s]", ((gchar *)array->pdata[1]) + depth + 1/*for newline*/);
+ } else {
+ retstr = g_strjoinv(NULL, (gchar **)array->pdata);
+ }
+
+ g_ptr_array_free(array, TRUE);
+
+ return retstr;
+}
+
+static gchar *
+value2string (const GValue * value, int depth)
+{
+ gchar * str = NULL;
+
+ if (value == NULL) {
+ return g_strdup("(null)");
+ }
+
+ if (dbus_g_type_is_collection(G_VALUE_TYPE(value))) {
+ str = collection_dumper(value, depth);
+ } else if (G_VALUE_TYPE(value) == G_TYPE_STRV) {
+ str = strv_dumper(value);
+ } else if (G_VALUE_TYPE(value) == G_TYPE_BOOLEAN) {
+ if (g_value_get_boolean(value)) {
+ str = g_strdup("true");
+ } else {
+ str = g_strdup("false");
+ }
+ } else {
+ str = g_strdup_value_contents(value);
+ }
+
+ return str;
+}
+
+static gint
+list_str_cmp (gconstpointer a, gconstpointer b)
+{
+ return g_strcmp0((gchar *)a, (gchar *)b);
+}
+
static void
print_menuitem (DbusmenuMenuitem * item, int depth)
{
gchar * space = g_strnfill(depth, ' ');
g_print("%s\"id\": %d", space, dbusmenu_menuitem_get_id(item));
- GList * properties = dbusmenu_menuitem_properties_list(item);
+ GList * properties_raw = dbusmenu_menuitem_properties_list(item);
+ GList * properties = g_list_sort(properties_raw, list_str_cmp);
GList * property;
for (property = properties; property != NULL; property = g_list_next(property)) {
- GValue value = {0};
- g_value_init(&value, G_TYPE_STRING);
- g_value_transform(dbusmenu_menuitem_property_get_value(item, (gchar *)property->data), &value);
- g_print(",\n%s\"%s\": \"%s\"", space, (gchar *)property->data, g_value_get_string(&value));
- g_value_unset(&value);
+ const GValue * value = dbusmenu_menuitem_property_get_value(item, (gchar *)property->data);
+ gchar * str = value2string(value, depth + g_utf8_strlen((gchar *)property->data, -1) + 2 /*quotes*/ + 2 /*: */);
+ g_print(",\n%s\"%s\": %s", space, (gchar *)property->data, str);
+ g_free(str);
}
g_list_free(properties);
@@ -90,11 +204,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) {
@@ -127,7 +421,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
@@ -147,16 +442,32 @@ 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;
+ }
+ if (!init_dbus_vars_from_window(window)) {
+ g_printerr("ERROR: could not find a menu for the pointed window\n");
+ return 1;
+ }
+ } 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);