aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTed Gould <ted@gould.cx>2011-02-21 11:22:46 -0600
committerTed Gould <ted@gould.cx>2011-02-21 11:22:46 -0600
commit2f92a8662f5a84f9ef35dd847539f573f60edc5f (patch)
tree9c00fda471a88fcc331c858bc2de66076f7de9ef
parent71020d95b0eb9f583523628261d26336a5dceb39 (diff)
parent34248e4fe3aeef17be5fe9f2339057fbaee81ad2 (diff)
downloadlibdbusmenu-2f92a8662f5a84f9ef35dd847539f573f60edc5f.tar.gz
libdbusmenu-2f92a8662f5a84f9ef35dd847539f573f60edc5f.tar.bz2
libdbusmenu-2f92a8662f5a84f9ef35dd847539f573f60edc5f.zip
Another update to trunk
-rw-r--r--.bzrignore157
-rw-r--r--Makefile.am31
-rwxr-xr-xautogen.sh3
-rw-r--r--configure.ac86
-rw-r--r--docs/libdbusmenu-glib/reference/tmpl/dummy.sgml0
-rw-r--r--docs/libdbusmenu-gtk/reference/Makefile.am13
-rw-r--r--docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-docs.sgml2
-rw-r--r--docs/libdbusmenu-gtk/reference/tmpl/dummy.sgml0
-rw-r--r--libdbusmenu-glib/Makefile.am100
-rw-r--r--libdbusmenu-glib/clean-namespaces.xslt14
-rw-r--r--libdbusmenu-glib/client-marshal.list2
-rw-r--r--libdbusmenu-glib/client-menuitem.c12
-rw-r--r--libdbusmenu-glib/client.c1368
-rw-r--r--libdbusmenu-glib/client.h48
-rw-r--r--libdbusmenu-glib/dbus-menu.xml119
-rw-r--r--libdbusmenu-glib/dbusmenu-glib-0.4.pc.in (renamed from libdbusmenu-glib/dbusmenu-glib.pc.in)4
-rw-r--r--libdbusmenu-glib/dbusmenu-glib.h37
-rw-r--r--libdbusmenu-glib/menuitem-marshal.list2
-rw-r--r--libdbusmenu-glib/menuitem-private.h4
-rw-r--r--libdbusmenu-glib/menuitem-proxy.c52
-rw-r--r--libdbusmenu-glib/menuitem-proxy.h18
-rw-r--r--libdbusmenu-glib/menuitem.c1025
-rw-r--r--libdbusmenu-glib/menuitem.h59
-rw-r--r--libdbusmenu-glib/server-marshal.list3
-rw-r--r--libdbusmenu-glib/server.c1176
-rw-r--r--libdbusmenu-glib/server.h33
-rw-r--r--libdbusmenu-gtk/Makefile.am107
-rw-r--r--libdbusmenu-gtk/client.c434
-rw-r--r--libdbusmenu-gtk/client.h14
-rw-r--r--libdbusmenu-gtk/dbusmenu-gtk-0.4.pc.in (renamed from libdbusmenu-gtk/dbusmenu-gtk.pc.in)4
-rw-r--r--libdbusmenu-gtk/dbusmenu-gtk.h41
-rw-r--r--libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in14
-rw-r--r--libdbusmenu-gtk/genericmenuitem.c116
-rw-r--r--libdbusmenu-gtk/menu.c83
-rw-r--r--libdbusmenu-gtk/menu.h11
-rw-r--r--libdbusmenu-gtk/menuitem.c227
-rw-r--r--libdbusmenu-gtk/menuitem.h15
-rw-r--r--libdbusmenu-gtk/parser.c887
-rw-r--r--libdbusmenu-gtk/parser.h37
-rw-r--r--libdbusmenu-gtk/serializablemenuitem.c288
-rw-r--r--libdbusmenu-gtk/serializablemenuitem.h115
-rw-r--r--tests/Makefile.am311
-rw-r--r--tests/dbusmenu-jsonloader-0.4.pc.in14
-rw-r--r--tests/json-loader.c179
-rw-r--r--tests/json-loader.h31
-rw-r--r--tests/run-xvfb.sh2
-rw-r--r--tests/test-glib-events-client.c138
-rw-r--r--tests/test-glib-events-server.c103
-rw-r--r--tests/test-glib-layout-client.c5
-rw-r--r--tests/test-glib-layout-server.c53
-rw-r--r--tests/test-glib-objects.c70
-rw-r--r--tests/test-glib-properties-client.c2
-rw-r--r--tests/test-glib-properties-server.c7
-rw-r--r--tests/test-glib-proxy-client.c7
-rw-r--r--tests/test-glib-proxy-proxy.c52
-rw-r--r--tests/test-glib-proxy-server.c51
-rw-r--r--tests/test-glib-simple-items.c3
-rwxr-xr-xtests/test-glib-simple-items.py35
-rw-r--r--tests/test-glib-submenu-server.c53
-rw-r--r--tests/test-gtk-label-client.c2
-rw-r--r--tests/test-gtk-label-server.c123
-rw-r--r--tests/test-gtk-objects.c145
-rw-r--r--tests/test-gtk-objects.jpgbin0 -> 14376 bytes
-rw-r--r--tests/test-gtk-parser.c115
-rw-r--r--tests/test-gtk-reorder-server.c55
-rw-r--r--tests/test-gtk-shortcut-client.c76
-rwxr-xr-xtests/test-gtk-shortcut-client.py52
-rw-r--r--tests/test-gtk-shortcut-server.c100
-rw-r--r--tests/test-gtk-submenu-client.c150
-rw-r--r--tests/test-gtk-submenu-server.c114
-rw-r--r--tests/test-json-01.json4023
-rw-r--r--tests/test-json-client.c79
-rw-r--r--tests/test-json-server.c88
-rw-r--r--tools/Makefile.am7
-rwxr-xr-xtools/dbusmenu-bench2
-rw-r--r--tools/dbusmenu-dumper.c251
-rw-r--r--tools/testapp/Makefile.am8
-rw-r--r--tools/testapp/main.c49
78 files changed, 11686 insertions, 1630 deletions
diff --git a/.bzrignore b/.bzrignore
index 1e0108e..39be2d5 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,159 @@ 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-0.4.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-glib/dbus-menu.xml.c
+libdbusmenu-glib/dbus-menu.xml.h
+libdbusmenu-glib/libdbusmenu_glib_la-dbus-menu.xml.lo
+libdbusmenu-glib/dbus-menu-clean.xml
+libdbusmenu-glib/dbus-menu-clean.xml.c
+libdbusmenu-glib/dbus-menu-clean.xml.h
+libdbusmenu-glib/libdbusmenu_glib_la-dbus-menu-clean.xml.lo
+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
+libdbusmenu-glib/Dbusmenu-Glib-0.4.gir
+libdbusmenu-glib/Dbusmenu-Glib-0.4.typelib
+libdbusmenu-glib/Dbusmenu-Glib-0.4.vapi
+libdbusmenu-glib/dbusmenu-glib-0.4.pc
+libdbusmenu-gtk/DbusmenuGtk-0.4.gir
+libdbusmenu-gtk/DbusmenuGtk-0.4.tmp.gir
+libdbusmenu-gtk/DbusmenuGtk-0.4.typelib
+libdbusmenu-gtk/DbusmenuGtk-0.4.vapi
+libdbusmenu-gtk/dbusmenu-gtk-0.4.pc
+libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc
+libdbusmenu-gtk/libdbusmenu_gtk_la-serializablemenuitem.lo
+docs/libdbusmenu-gtk/reference/html/DbusmenuGtkSerializableMenuItem.html
+docs/libdbusmenu-gtk/reference/tmpl/serializablemenuitem.sgml
+libdbusmenu-gtk/libdbusmenu_gtk_la-parser.lo
+test-gtk-parser
+test-gtk-parser-test
+test-gtk-parser.xml
diff --git a/Makefile.am b/Makefile.am
index 3853d2a..c2c0980 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -12,3 +12,34 @@ SUBDIRS = \
po
DISTCHECK_CONFIGURE_FLAGS = --enable-introspection --enable-gtk-doc
+
+## Can't disable deprecations yet, working on that, but
+## we want to get there.
+#
+# DISTCHECK_CONFIGURE_FLAGS = --enable-introspection --enable-gtk-doc --disable-deprecations
+
+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 938fe84..a1af560 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.98, 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.98, [-Wno-portability])
AM_MAINTAINER_MODE
@@ -21,6 +21,15 @@ AC_CONFIG_MACRO_DIR([m4])
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
+AC_ARG_ENABLE([deprecations],
+ [AS_HELP_STRING([--enable-deprecations],
+ [allow deprecated API usage @<:@default=yes@:>@])],
+ [],
+ [enable_deprecations=yes])
+AS_IF([test "x$enable_deprecations" = xno],
+ [CFLAGS="$CFLAGS -DG_DISABLE_DEPRECATED -DGDK_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED -DGSEAL_ENABLE -DGTK_DISABLE_SINGLE_INCLUDES"]
+)
+
###########################
# GTK Doc
###########################
@@ -32,13 +41,10 @@ GNOME_DOC_INIT
# Dependencies - GLib
###########################
-GLIB_REQUIRED_VERSION=2.18
-DBUS_REQUIRED_VERSION=0.76
-XML_REQUIRED_VERSION=2.6
+GLIB_REQUIRED_VERSION=2.26
PKG_CHECK_MODULES(DBUSMENUGLIB, glib-2.0 >= $GLIB_REQUIRED_VERSION
- dbus-glib-1 >= $DBUS_REQUIRED_VERSION
- libxml-2.0 >= $XML_REQUIRED_VERSION)
+ gio-2.0 >= $GLIB_REQUIRED_VERSION)
AC_SUBST(DBUSMENUGLIB_CFLAGS)
AC_SUBST(DBUSMENUGLIB_LIBS)
@@ -48,22 +54,51 @@ AC_SUBST(DBUSMENUGLIB_LIBS)
###########################
GTK_REQUIRED_VERSION=2.16
+GTK3_REQUIRED_VERSION=2.91
+
+AC_ARG_WITH([gtk],
+ [AS_HELP_STRING([--with-gtk],
+ [Which version of gtk to use @<:@default=2@:>@])],
+ [],
+ [with_gtk=2])
+AS_IF([test "x$with_gtk" = x3],
+ [PKG_CHECK_MODULES(DBUSMENUGTK, gtk+-3.0 >= $GTK3_REQUIRED_VERSION
+ glib-2.0 >= $GLIB_REQUIRED_VERSION)
+ AC_SUBST(DBUSMENUGTK_CFLAGS)
+ AC_SUBST(DBUSMENUGTK_LIBS)
+ AC_DEFINE(HAVE_GTK3, 1, [whether gtk3 is available])
+ ],
+ [test "x$with_gtk" = x2],
+ [PKG_CHECK_MODULES(DBUSMENUGTK, gtk+-2.0 >= $GTK_REQUIRED_VERSION
+ glib-2.0 >= $GLIB_REQUIRED_VERSION)
+ AC_SUBST(DBUSMENUGTK_CFLAGS)
+ AC_SUBST(DBUSMENUGTK_LIBS)
+ ],
+ [AC_MSG_FAILURE([Value for --with-gtk was neither 2 nor 3])]
+)
+AM_CONDITIONAL(USE_GTK3, [test "x$with_gtk" = x3])
+
+###########################
+# Dependencies - dumper
+###########################
+
+X11_REQUIRED_VERSION=1.3
-PKG_CHECK_MODULES(DBUSMENUGTK, gtk+-2.0 >= $GTK_REQUIRED_VERSION
- glib-2.0 >= $GLIB_REQUIRED_VERSION
- dbus-glib-1 >= $DBUS_REQUIRED_VERSION
- libxml-2.0 >= $XML_REQUIRED_VERSION)
+PKG_CHECK_MODULES(DBUSMENUDUMPER, gtk+-2.0 >= $GTK_REQUIRED_VERSION
+ x11 >= $X11_REQUIRED_VERSION)
-AC_SUBST(DBUSMENUGTK_CFLAGS)
-AC_SUBST(DBUSMENUGTK_LIBS)
+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)
@@ -74,6 +109,12 @@ AC_SUBST(DBUSMENUTESTS_LIBS)
GOBJECT_INTROSPECTION_CHECK([0.6.7])
+PKG_CHECK_EXISTS([gobject-introspection-1.0 >= 0.10],
+ introspection_ten=yes,
+ introspection_ten=no)
+
+AM_CONDITIONAL(INTROSPECTION_TEN, [test "x$introspection_ten" = "xyes"])
+
###########################
# Vala API Generation
###########################
@@ -81,11 +122,17 @@ GOBJECT_INTROSPECTION_CHECK([0.6.7])
AC_PATH_PROG([VALA_API_GEN], [vapigen])
###########################
+# XSLT Processor
+###########################
+
+AC_PATH_PROG([XSLT_PROC], [xsltproc])
+
+###########################
# Lib versioning
###########################
-LIBDBUSMENU_CURRENT=1
-LIBDBUSMENU_REVISION=7
+LIBDBUSMENU_CURRENT=3
+LIBDBUSMENU_REVISION=6
LIBDBUSMENU_AGE=0
AC_SUBST(LIBDBUSMENU_CURRENT)
@@ -120,12 +167,14 @@ AC_OUTPUT([
Makefile
po/Makefile.in
libdbusmenu-glib/Makefile
-libdbusmenu-glib/dbusmenu-glib.pc
+libdbusmenu-glib/dbusmenu-glib-0.4.pc
libdbusmenu-gtk/Makefile
-libdbusmenu-gtk/dbusmenu-gtk.pc
+libdbusmenu-gtk/dbusmenu-gtk-0.4.pc
+libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc
tools/Makefile
tools/testapp/Makefile
tests/Makefile
+tests/dbusmenu-jsonloader-0.4.pc
docs/Makefile
docs/libdbusmenu-glib/Makefile
docs/libdbusmenu-glib/reference/Makefile
@@ -145,5 +194,6 @@ libdbusmenu Configuration:
Prefix: $prefix
Massive Debugging: $with_massivedebugging
+ GTK+ Version: $with_gtk
])
diff --git a/docs/libdbusmenu-glib/reference/tmpl/dummy.sgml b/docs/libdbusmenu-glib/reference/tmpl/dummy.sgml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/libdbusmenu-glib/reference/tmpl/dummy.sgml
diff --git a/docs/libdbusmenu-gtk/reference/Makefile.am b/docs/libdbusmenu-gtk/reference/Makefile.am
index 21d95bd..be9f69c 100644
--- a/docs/libdbusmenu-gtk/reference/Makefile.am
+++ b/docs/libdbusmenu-gtk/reference/Makefile.am
@@ -1,5 +1,12 @@
+
## Process this file with automake to produce Makefile.in
+if USE_GTK3
+VER=3
+else
+VER=
+endif
+
# We require automake 1.6 at least.
AUTOMAKE_OPTIONS = 1.6
@@ -9,7 +16,7 @@ AUTOMAKE_OPTIONS = 1.6
# of using the various options.
# The name of the module, e.g. 'glib'.
-DOC_MODULE=libdbusmenu-gtk
+DOC_MODULE=libdbusmenu-gtk$(VER)
# The top-level SGML file. You can change this if you want to.
DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml
@@ -47,7 +54,7 @@ CFILE_GLOB=$(top_srcdir)/libdbusmenu-gtk/*.c
# Header files to ignore when scanning.
# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
-IGNORE_HFILES=
+IGNORE_HFILES=genericmenuitem.h
# Images to copy into HTML directory.
# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
@@ -68,7 +75,7 @@ expand_content_files=
# e.g. INCLUDES=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
INCLUDES=-I$(top_srcdir) $(DBUSMENUGLIB_CFLAGS) $(DBUSMENUGTK_CFLAGS)
-GTKDOC_LIBS=$(top_builddir)/libdbusmenu-gtk/libdbusmenu-gtk.la
+GTKDOC_LIBS=$(top_builddir)/libdbusmenu-gtk/libdbusmenu-gtk$(VER).la $(DBUSMENUGLIB_LIBS) $(DBUSMENUGTK_LIBS)
# This includes the standard gtk-doc make rules, copied by gtkdocize.
include $(top_srcdir)/gtk-doc.local.make
diff --git a/docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-docs.sgml b/docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-docs.sgml
index ae9ab07..ec6b82f 100644
--- a/docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-docs.sgml
+++ b/docs/libdbusmenu-gtk/reference/libdbusmenu-gtk-docs.sgml
@@ -13,8 +13,8 @@
<title>API</title>
<xi:include href="xml/menu.xml"/>
<xi:include href="xml/client.xml"/>
- <xi:include href="xml/genericmenuitem.xml"/>
<xi:include href="xml/menuitem.xml"/>
+ <xi:include href="xml/serializablemenuitem.xml"/>
</chapter>
<chapter id="object-tree">
diff --git a/docs/libdbusmenu-gtk/reference/tmpl/dummy.sgml b/docs/libdbusmenu-gtk/reference/tmpl/dummy.sgml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/libdbusmenu-gtk/reference/tmpl/dummy.sgml
diff --git a/libdbusmenu-glib/Makefile.am b/libdbusmenu-glib/Makefile.am
index 3df1513..92de502 100644
--- a/libdbusmenu-glib/Makefile.am
+++ b/libdbusmenu-glib/Makefile.am
@@ -2,25 +2,28 @@
CLEANFILES =
EXTRA_DIST = \
- dbusmenu-glib.pc.in \
+ clean-namespaces.xslt \
+ dbusmenu-glib-0.4.pc.in \
dbus-menu.xml \
+ client-marshal.list \
menuitem-marshal.list \
server-marshal.list
lib_LTLIBRARIES = \
libdbusmenu-glib.la
-libdbusmenu_glibincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-glib/
+libdbusmenu_glibincludedir=$(includedir)/libdbusmenu-0.4/libdbusmenu-glib/
libdbusmenu_glibinclude_HEADERS = \
+ dbusmenu-glib.h \
menuitem.h \
menuitem-proxy.h \
server.h \
client.h
libdbusmenu_glib_la_SOURCES = \
- dbusmenu-server.h \
- dbusmenu-client.h \
+ dbus-menu-clean.xml.h \
+ dbus-menu-clean.xml.c \
menuitem.h \
menuitem.c \
menuitem-marshal.h \
@@ -32,6 +35,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 \
@@ -48,30 +53,43 @@ libdbusmenu_glib_la_CFLAGS = \
libdbusmenu_glib_la_LIBADD = \
$(DBUSMENUGLIB_LIBS)
-pkgconfig_DATA = dbusmenu-glib.pc
+pkgconfig_DATA = dbusmenu-glib-0.4.pc
pkgconfigdir = $(libdir)/pkgconfig
+%.xml.h: %.xml
+ echo "extern const char * $(subst -,_,$(subst .,_,$(basename $(notdir $@))));" > $@
+
+%.xml.c: %.xml
+ echo "const char * $(subst -,_,$(subst .,_,$(basename $(notdir $@)))) = " > $@
+ sed -e "s:\":\\\\\":g" -e s:^:\": -e s:\$$:\\\\n\": $< >> $@
+ echo ";" >> $@
+
+dbus-menu-clean.xml: dbus-menu.xml
+ $(XSLT_PROC) $(srcdir)/clean-namespaces.xslt $< > $@ || (rm -f $@ && /bin/false)
+
+CLEANFILES += dbus-menu-clean.xml
+
BUILT_SOURCES = \
- dbusmenu-client.h \
- dbusmenu-server.h \
+ dbus-menu-clean.xml.c \
+ dbus-menu-clean.xml.h \
+ client-marshal.h \
+ client-marshal.c \
menuitem-marshal.h \
menuitem-marshal.c \
server-marshal.h \
server-marshal.c
-dbusmenu-server.h: dbus-menu.xml
- dbus-binding-tool \
- --prefix=_dbusmenu_server \
- --mode=glib-server \
- --output=dbusmenu-server.h \
- $(srcdir)/dbus-menu.xml
+CLEANFILES += $(BUILT_SOURCES)
+
+client-marshal.h: $(srcdir)/client-marshal.list
+ glib-genmarshal --header \
+ --prefix=_dbusmenu_client_marshal $(srcdir)/client-marshal.list \
+ > client-marshal.h
-dbusmenu-client.h: dbus-menu.xml
- dbus-binding-tool \
- --prefix=_dbusmenu_client \
- --mode=glib-client \
- --output=dbusmenu-client.h \
- $(srcdir)/dbus-menu.xml
+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 \
@@ -99,25 +117,39 @@ menuitem-marshal.c: $(srcdir)/menuitem-marshal.list
-include $(INTROSPECTION_MAKEFILE)
INTROSPECTION_GIRS =
-INTROSPECTION_SCANNER_ARGS = \
- --add-include-path=$(srcdir) \
- $(addprefix --c-include=libdbusmenu-glib/, $(introspection_sources))
+
+if INTROSPECTION_TEN
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) \
+ --warn-all \
+ --add-include-path=$(srcdir) \
+ $(addprefix --c-include=libdbusmenu-glib/, $(libdbusmenu_glibinclude_HEADERS)) \
+ --symbol-prefix=dbusmenu \
+ --identifier-prefix=Dbusmenu
+else
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) \
+ --warn-all \
+ --add-include-path=$(srcdir) \
+ $(addprefix --c-include=libdbusmenu-glib/, $(libdbusmenu_glibinclude_HEADERS))
+endif
+
INTROSPECTION_COMPILER_ARGS = --includedir=$(builddir)
if HAVE_INTROSPECTION
-introspection_sources = $(libdbusmenu_glibinclude_HEADERS)
+introspection_sources = $(libdbusmenu_glibinclude_HEADERS) $(libdbusmenu_glib_la_SOURCES)
-Dbusmenu_Glib-0.2.gir: libdbusmenu-glib.la
-Dbusmenu_Glib_0_2_gir_INCLUDES = \
+Dbusmenu-0.4.gir: libdbusmenu-glib.la
+Dbusmenu_0_4_gir_INCLUDES = \
GObject-2.0
-Dbusmenu_Glib_0_2_gir_CFLAGS = $(DBUSMENUGLIB_CFLAGS)
-Dbusmenu_Glib_0_2_gir_LIBS = libdbusmenu-glib.la
-Dbusmenu_Glib_0_2_gir_FILES = $(addprefix $(srcdir)/, $(introspection_sources))
-Dbusmenu_Glib_0_2_gir_NAMESPACE = Dbusmenu
-Dbusmenu_Glib_0_2_gir_VERSION = Glib-0.2
+Dbusmenu_0_4_gir_CFLAGS = $(DBUSMENUGLIB_CFLAGS) -I$(top_srcdir)
+Dbusmenu_0_4_gir_LIBS = libdbusmenu-glib.la
+Dbusmenu_0_4_gir_FILES = $(addprefix $(srcdir)/, $(introspection_sources))
+Dbusmenu_0_4_gir_NAMESPACE = Dbusmenu
+Dbusmenu_0_4_gir_VERSION = 0.4
+Dbusmenu_0_4_gir_EXPORT_PACKAGES = dbusmenu-glib-0.4
+Dbusmenu_0_4_gir_SCANNER_FLAGS = $(INTROSPECTION_SCANNER_ARGS)
-INTROSPECTION_GIRS += Dbusmenu-Glib-0.2.gir
+INTROSPECTION_GIRS += Dbusmenu-0.4.gir
girdir = $(datadir)/gir-1.0
gir_DATA = $(INTROSPECTION_GIRS)
@@ -136,10 +168,10 @@ endif
if HAVE_INTROSPECTION
vapidir = $(datadir)/vala/vapi
-vapi_DATA = Dbusmenu-Glib-0.2.vapi
+vapi_DATA = Dbusmenu-0.4.vapi
-Dbusmenu-Glib-0.2.vapi: Dbusmenu-Glib-0.2.gir
- $(VALA_API_GEN) --library=Dbusmenu-Glib-0.2 $<
+Dbusmenu-0.4.vapi: Dbusmenu-0.4.gir
+ $(VALA_API_GEN) --library=Dbusmenu-0.4 $<
CLEANFILES += $(vapi_DATA)
diff --git a/libdbusmenu-glib/clean-namespaces.xslt b/libdbusmenu-glib/clean-namespaces.xslt
new file mode 100644
index 0000000..8c0c521
--- /dev/null
+++ b/libdbusmenu-glib/clean-namespaces.xslt
@@ -0,0 +1,14 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dox="http://www.canonical.com/dbus/dox.dtd">
+ <xsl:template match="*|@*">
+ <xsl:copy>
+ <xsl:apply-templates select="*|@*" />
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="@dox:*|dox:*"/>
+ <xsl:template match="*">
+ <xsl:element name="{local-name()}">
+ <xsl:apply-templates select="@* | node()"/>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet>
+
diff --git a/libdbusmenu-glib/client-marshal.list b/libdbusmenu-glib/client-marshal.list
new file mode 100644
index 0000000..866dfa8
--- /dev/null
+++ b/libdbusmenu-glib/client-marshal.list
@@ -0,0 +1,2 @@
+VOID: OBJECT, UINT
+VOID: OBJECT, STRING, VARIANT, UINT, POINTER
diff --git a/libdbusmenu-glib/client-menuitem.c b/libdbusmenu-glib/client-menuitem.c
index 979cf79..0f14b85 100644
--- a/libdbusmenu-glib/client-menuitem.c
+++ b/libdbusmenu-glib/client-menuitem.c
@@ -45,8 +45,8 @@ static void dbusmenu_client_menuitem_class_init (DbusmenuClientMenuitemClass *kl
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 handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * value, guint timestamp);
+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);
@@ -102,17 +102,17 @@ dbusmenu_client_menuitem_new (gint id, DbusmenuClient * client)
/* Passes the event signal on through the client. */
static void
-handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp)
+handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp)
{
DbusmenuClientMenuitemPrivate * priv = DBUSMENU_CLIENT_MENUITEM_GET_PRIVATE(mi);
- dbusmenu_client_send_event(priv->client, dbusmenu_menuitem_get_id(mi), name, value, timestamp);
+ dbusmenu_client_send_event(priv->client, dbusmenu_menuitem_get_id(mi), name, variant, timestamp);
return;
}
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 c0d3b7a..0848294 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -30,15 +30,19 @@ License version 3 and version 2.1 along with this program. If not, see
#include "config.h"
#endif
-#include <libxml/parser.h>
-#include <libxml/tree.h>
+#include <gio/gio.h>
#include "client.h"
#include "menuitem.h"
#include "menuitem-private.h"
#include "client-menuitem.h"
-#include "dbusmenu-client.h"
#include "server-marshal.h"
+#include "client-marshal.h"
+#include "dbus-menu-clean.xml.h"
+
+/* How many property requests should we queue before
+ sending the message on dbus */
+#define MAX_PROPERTIES_TO_QUEUE 100
/* Properties */
enum {
@@ -52,12 +56,15 @@ enum {
LAYOUT_UPDATED,
ROOT_CHANGED,
NEW_MENUITEM,
+ ITEM_ACTIVATE,
+ EVENT_RESULT,
LAST_SIGNAL
};
+typedef void (*properties_func) (GVariant * properties, GError * error, gpointer user_data);
+
static guint signals[LAST_SIGNAL] = { 0 };
-typedef struct _DbusmenuClientPrivate DbusmenuClientPrivate;
struct _DbusmenuClientPrivate
{
DbusmenuMenuitem * root;
@@ -65,17 +72,24 @@ struct _DbusmenuClientPrivate
gchar * dbus_object;
gchar * dbus_name;
- DBusGConnection * session_bus;
- DBusGProxy * menuproxy;
- DBusGProxy * propproxy;
- DBusGProxyCall * layoutcall;
+ GDBusConnection * session_bus;
+ GCancellable * session_bus_cancel;
+
+ GDBusProxy * menuproxy;
+ GCancellable * menuproxy_cancel;
+
+ GCancellable * layoutcall;
gint current_revision;
gint my_revision;
- DBusGProxy * dbusproxy;
+ guint dbusproxy;
GHashTable * type_handlers;
+
+ GArray * delayed_property_list;
+ GArray * delayed_property_listeners;
+ gint delayed_idle;
};
typedef struct _newItemPropData newItemPropData;
@@ -86,8 +100,41 @@ 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;
+ properties_func callback;
+ gpointer user_data;
+ gboolean replied;
+};
+
+typedef struct _event_data_t event_data_t;
+struct _event_data_t {
+ DbusmenuClient * client;
+ DbusmenuMenuitem * menuitem;
+ gchar * event;
+ GVariant * variant;
+ guint timestamp;
+};
+
+typedef struct _type_handler_t type_handler_t;
+struct _type_handler_t {
+ DbusmenuClient * client;
+ DbusmenuClientTypeHandler cb;
+ DbusmenuClientTypeDestroyHandler destroy_cb;
+ gpointer user_data;
+ gchar * type;
+};
+
+typedef struct _properties_callback_t properties_callback_t;
+struct _properties_callback_t {
+ DbusmenuClient * client;
+ GArray * listeners;
+};
+
+
+#define DBUSMENU_CLIENT_GET_PRIVATE(o) (DBUSMENU_CLIENT(o)->priv)
+#define DBUSMENU_INTERFACE "com.canonical.dbusmenu"
/* GObject Stuff */
static void dbusmenu_client_class_init (DbusmenuClientClass *klass);
@@ -97,16 +144,26 @@ static void dbusmenu_client_finalize (GObject *object);
static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec);
static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
/* Private Funcs */
-static void layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient * client);
-static void id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, DbusmenuClient * client);
-static void id_update (DBusGProxy * proxy, gint id, DbusmenuClient * client);
+static void layout_update (GDBusProxy * proxy, guint revision, gint parent, DbusmenuClient * client);
+static void id_prop_update (GDBusProxy * proxy, gint id, gchar * property, GVariant * value, DbusmenuClient * client);
+static void id_update (GDBusProxy * proxy, gint id, DbusmenuClient * client);
static void build_proxies (DbusmenuClient * client);
-static gint parse_node_get_id (xmlNodePtr node);
-static DbusmenuMenuitem * parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy);
-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 DbusmenuMenuitem * parse_layout_xml(DbusmenuClient * client, GVariant * layout, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, GDBusProxy * proxy);
+static gint parse_layout (DbusmenuClient * client, GVariant * layout);
+static void update_layout_cb (GObject * proxy, GAsyncResult * res, gpointer data);
static void update_layout (DbusmenuClient * client);
-static void menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data);
+static void menuitem_get_properties_cb (GVariant * properties, GError * error, gpointer data);
+static void get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, properties_func callback, gpointer user_data);
+static GQuark error_domain (void);
+static void item_activated (GDBusProxy * proxy, gint id, guint timestamp, DbusmenuClient * client);
+static void menuproxy_build_cb (GObject * object, GAsyncResult * res, gpointer user_data);
+static void menuproxy_name_changed_cb (GObject * object, GParamSpec * pspec, gpointer user_data);
+static void menuproxy_signal_cb (GDBusProxy * proxy, gchar * sender, gchar * signal, GVariant * params, gpointer user_data);
+static void type_handler_destroy (gpointer user_data);
+
+/* Globals */
+static GDBusNodeInfo * dbusmenu_node_info = NULL;
+static GDBusInterfaceInfo * dbusmenu_interface_info = NULL;
/* Build a type */
G_DEFINE_TYPE (DbusmenuClient, dbusmenu_client, G_TYPE_OBJECT);
@@ -171,6 +228,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_VARIANT_UINT_POINTER,
+ G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, G_TYPE_VARIANT, 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",
@@ -183,12 +275,32 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass)
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+ if (dbusmenu_node_info == NULL) {
+ GError * error = NULL;
+
+ dbusmenu_node_info = g_dbus_node_info_new_for_xml(dbus_menu_clean_xml, &error);
+ if (error != NULL) {
+ g_error("Unable to parse DBusmenu Interface description: %s", error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (dbusmenu_interface_info == NULL) {
+ dbusmenu_interface_info = g_dbus_node_info_lookup_interface(dbusmenu_node_info, DBUSMENU_INTERFACE);
+
+ if (dbusmenu_interface_info == NULL) {
+ g_error("Unable to find interface '" DBUSMENU_INTERFACE "'");
+ }
+ }
+
return;
}
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;
@@ -197,17 +309,24 @@ dbusmenu_client_init (DbusmenuClient *self)
priv->dbus_name = NULL;
priv->session_bus = NULL;
+ priv->session_bus_cancel = NULL;
+
priv->menuproxy = NULL;
- priv->propproxy = NULL;
+ priv->menuproxy_cancel = NULL;
+
priv->layoutcall = NULL;
priv->current_revision = 0;
priv->my_revision = 0;
- priv->dbusproxy = NULL;
+ priv->dbusproxy = 0;
priv->type_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
- g_free, NULL);
+ g_free, type_handler_destroy);
+
+ 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;
}
@@ -217,23 +336,78 @@ 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(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);
+ g_cancellable_cancel(priv->layoutcall);
+ g_object_unref(priv->layoutcall);
priv->layoutcall = NULL;
}
+
+ /* Bring down the menu proxy, ensure we're not
+ looking for one at the same time. */
+ if (priv->menuproxy_cancel != NULL) {
+ g_cancellable_cancel(priv->menuproxy_cancel);
+ g_object_unref(priv->menuproxy_cancel);
+ priv->menuproxy_cancel = NULL;
+ }
if (priv->menuproxy != NULL) {
g_object_unref(G_OBJECT(priv->menuproxy));
priv->menuproxy = NULL;
}
- if (priv->propproxy != NULL) {
- g_object_unref(G_OBJECT(priv->propproxy));
- priv->propproxy = NULL;
+
+ if (priv->dbusproxy != 0) {
+ g_bus_unwatch_name(priv->dbusproxy);
+ priv->dbusproxy = 0;
+ }
+
+ /* Bring down the session bus, ensure we're not
+ looking for one at the same time. */
+ if (priv->session_bus_cancel != NULL) {
+ g_cancellable_cancel(priv->session_bus_cancel);
+ g_object_unref(priv->session_bus_cancel);
+ priv->session_bus_cancel = NULL;
}
- if (priv->dbusproxy != NULL) {
- g_object_unref(G_OBJECT(priv->dbusproxy));
- priv->dbusproxy = NULL;
+ if (priv->session_bus != NULL) {
+ g_object_unref(priv->session_bus);
+ priv->session_bus = NULL;
}
- priv->session_bus = NULL;
if (priv->root != NULL) {
g_object_unref(G_OBJECT(priv->root));
@@ -308,9 +482,276 @@ 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 (GObject *obj, GAsyncResult * res, gpointer user_data)
+{
+ properties_callback_t * cbdata = (properties_callback_t *)user_data;
+ GArray * listeners = cbdata->listeners;
+ int i;
+ GError * error = NULL;
+ GVariant * params = NULL;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(obj), res, &error);
+
+ 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(NULL, error, listener->user_data);
+ }
+ g_error_free(error);
+ goto out;
+ }
+
+ /* Callback all the folks we can find */
+ GVariantIter * iter = g_variant_iter_new(g_variant_get_child_value(params, 0));
+ GVariant * child;
+ while ((child = g_variant_iter_next_value(iter)) != NULL) {
+ if (g_strcmp0(g_variant_get_type_string(child), "(ia{sv})") != 0) {
+ g_warning("Properties return signature is not '(ia{sv})' it is '%s'", g_variant_get_type_string(child));
+ continue;
+ }
+
+ gint id = g_variant_get_int32(g_variant_get_child_value(child, 0));
+ GVariant * properties = g_variant_get_child_value(child, 1);
+
+ 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(properties, NULL, listener->user_data);
+ listener->replied = TRUE;
+ } else {
+ g_warning("Odd, we've already replied to the listener on ID %d", id);
+ }
+ }
+ g_variant_iter_free(iter);
+ g_variant_unref(params);
+
+ /* 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) {
+ g_warning("Generating properties error for: %d", listener->id);
+ if (localerror == NULL) {
+ g_set_error_literal(&localerror, error_domain(), 0, "Error getting properties for ID");
+ }
+ listener->callback(NULL, localerror, listener->user_data);
+ }
+ }
+ if (localerror != NULL) {
+ g_error_free(localerror);
+ }
+
+out:
+ /* Clean up */
+ g_array_free(listeners, TRUE);
+ g_object_unref(cbdata->client);
+ g_free(user_data);
+
+ 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)
+{
+ properties_callback_t * cbdata = NULL;
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(user_data);
+ g_return_val_if_fail(priv->menuproxy != NULL, TRUE);
+
+ if (priv->delayed_property_listeners->len == 0) {
+ g_warning("Odd, idle func got no listeners.");
+ return FALSE;
+ }
+
+ /* Build up an ID list to pass */
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ gint i;
+ for (i = 0; i < priv->delayed_property_listeners->len; i++) {
+ g_variant_builder_add(&builder, "i", g_array_index(priv->delayed_property_listeners, properties_listener_t, i).id);
+ }
+
+ GVariant * variant_ids = g_variant_builder_end(&builder);
+
+ /* Build up a prop list to pass */
+ g_variant_builder_init(&builder, g_variant_type_new("as"));
+ /* TODO: need to use delayed property list here */
+ GVariant * variant_props = g_variant_builder_end(&builder);
+
+ /* Combine them into a value for the parameter */
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value(&builder, variant_ids);
+ g_variant_builder_add_value(&builder, variant_props);
+ GVariant * variant_params = g_variant_builder_end(&builder);
+
+ cbdata = g_new(properties_callback_t, 1);
+ cbdata->listeners = priv->delayed_property_listeners;
+ cbdata->client = DBUSMENU_CLIENT(user_data);
+ g_object_ref(G_OBJECT(user_data));
+
+ g_dbus_proxy_call(priv->menuproxy,
+ "GetGroupProperties",
+ variant_params,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ NULL, /* cancellable */
+ get_properties_callback,
+ cbdata);
+
+ /* 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);
+
+ 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, properties_func 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(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 (GDBusProxy * 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)
+layout_update (GDBusProxy * proxy, guint revision, gint parent, DbusmenuClient * client)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
priv->current_revision = revision;
@@ -323,16 +764,8 @@ layout_update (DBusGProxy * proxy, guint revision, gint parent, DbusmenuClient *
/* Signal from the server that a property has changed
on one of our menuitems */
static void
-id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, DbusmenuClient * client)
+id_prop_update (GDBusProxy * proxy, gint id, gchar * property, GVariant * value, DbusmenuClient * client)
{
- #ifdef MASSIVEDEBUGGING
- GValue valstr = {0};
- g_value_init(&valstr, G_TYPE_STRING);
- g_value_transform(value, &valstr);
- g_debug("Property change sent to client for item %d property %s value %s", id, property, g_utf8_strlen(g_value_get_string(&valstr), 50) < 25 ? g_value_get_string(&valstr) : "<too long>");
- g_value_unset(&valstr);
- #endif
-
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
g_return_if_fail(priv->root != NULL);
@@ -345,7 +778,7 @@ id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, D
return;
}
- dbusmenu_menuitem_property_set_value(menuitem, property, value);
+ dbusmenu_menuitem_property_set_variant(menuitem, property, value);
return;
}
@@ -353,7 +786,7 @@ id_prop_update (DBusGProxy * proxy, gint id, gchar * property, GValue * value, D
/* Oh, lots of updates now. That silly server, they want
to change all kinds of stuff! */
static void
-id_update (DBusGProxy * proxy, gint id, DbusmenuClient * client)
+id_update (GDBusProxy * proxy, gint id, DbusmenuClient * client)
{
#ifdef MASSIVEDEBUGGING
g_debug("Client side ID update: %d", id);
@@ -365,32 +798,19 @@ 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;
}
/* Watches to see if our DBus savior comes onto the bus */
static void
-dbus_owner_change (DBusGProxy * proxy, const gchar * name, const gchar * prev, const gchar * new, DbusmenuClient * client)
+dbus_owner_change (GDBusConnection * connection, const gchar * name, const gchar * owner, gpointer user_data)
{
- DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- /* g_debug("Owner change: %s %s %s", name, prev, new); */
-
- if (!(new[0] != '\0' && prev[0] == '\0')) {
- /* If it's not someone new getting on the bus, sorry we
- simply just don't care. It's not that your service isn't
- important to someone, just not us. You'll find the right
- process someday, there's lots of processes out there. */
- return;
- }
+ g_return_if_fail(DBUSMENU_IS_CLIENT(user_data));
- if (g_strcmp0(name, priv->dbus_name)) {
- /* Again, someone else's service. */
- return;
- }
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
/* Woot! A service for us to love and to hold for ever
and ever and ever! */
@@ -403,28 +823,22 @@ static void
build_dbus_proxy (DbusmenuClient * client)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- GError * error = NULL;
- if (priv->dbusproxy != NULL) {
+ if (priv->dbusproxy != 0) {
return;
}
- priv->dbusproxy = dbus_g_proxy_new_for_name_owner (priv->session_bus,
- DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS,
- DBUS_INTERFACE_DBUS,
- &error);
- if (error != NULL) {
- g_debug("Oh, that's bad. That's really bad. We can't get a proxy to DBus itself? Seriously? Here's all I know: %s", error->message);
- g_error_free(error);
- return;
- }
+ priv->dbusproxy = g_bus_watch_name_on_connection(priv->session_bus,
+ priv->dbus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ dbus_owner_change,
+ NULL,
+ client,
+ NULL);
- dbus_g_proxy_add_signal(priv->dbusproxy, "NameOwnerChanged",
- G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
- G_TYPE_INVALID);
- dbus_g_proxy_connect_signal(priv->dbusproxy, "NameOwnerChanged",
- G_CALLBACK(dbus_owner_change), client, NULL);
+ /* Now let's check to make sure we're not in some race
+ condition case. */
+ /* TODO: Not sure how to check for names in GDBus */
return;
}
@@ -448,7 +862,11 @@ proxy_destroyed (GObject * gobj_proxy, gpointer userdata)
}
if ((gpointer)priv->menuproxy == (gpointer)gobj_proxy) {
- priv->layoutcall = NULL;
+ if (priv->layoutcall != NULL) {
+ g_cancellable_cancel(priv->layoutcall);
+ g_object_unref(priv->layoutcall);
+ priv->layoutcall = NULL;
+ }
}
priv->current_revision = 0;
@@ -458,112 +876,226 @@ proxy_destroyed (GObject * gobj_proxy, gpointer userdata)
return;
}
+/* Respond to us getting the session bus (hopefully) or handle
+ the error if not */
+void
+session_bus_cb (GObject * object, GAsyncResult * res, gpointer user_data)
+{
+ GError * error = NULL;
+
+ /* NOTE: We're not using any other variables before checking
+ the result because they could be destroyed and thus invalid */
+ GDBusConnection * bus = g_bus_get_finish(res, &error);
+ if (error != NULL) {
+ g_warning("Unable to get session bus: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ /* If this wasn't cancelled, we should be good */
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ priv->session_bus = bus;
+
+ if (priv->session_bus_cancel != NULL) {
+ g_object_unref(priv->session_bus_cancel);
+ priv->session_bus_cancel = NULL;
+ }
+
+ /* Retry to build the proxies now that we have a bus */
+ build_proxies(DBUSMENU_CLIENT(user_data));
+
+ return;
+}
+
/* When we have a name and an object, build the two proxies and get the
first version of the layout */
static void
build_proxies (DbusmenuClient * client)
{
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- GError * error = NULL;
g_return_if_fail(priv->dbus_object != NULL);
g_return_if_fail(priv->dbus_name != NULL);
- priv->session_bus = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
- if (error != NULL) {
- g_error("Unable to get session bus: %s", error->message);
- g_error_free(error);
- build_dbus_proxy(client);
+ if (priv->session_bus == NULL) {
+ /* We don't have the session bus yet, that's okay, but
+ we need to handle that. */
+
+ /* If we're already running we don't need to look again. */
+ if (priv->session_bus_cancel == NULL) {
+ priv->session_bus_cancel = g_cancellable_new();
+
+ /* Async get the session bus */
+ g_bus_get(G_BUS_TYPE_SESSION, priv->session_bus_cancel, session_bus_cb, client);
+ }
+
+ /* This function exists, it'll be called again when we get
+ the session bus so this condition will be ignored */
return;
}
- priv->propproxy = dbus_g_proxy_new_for_name_owner(priv->session_bus,
- priv->dbus_name,
- priv->dbus_object,
- DBUS_INTERFACE_PROPERTIES,
- &error);
- if (error != NULL) {
- g_warning("Unable to get property proxy for %s on %s: %s", priv->dbus_name, priv->dbus_object, error->message);
- g_error_free(error);
- build_dbus_proxy(client);
- return;
+ /* Build us a menu proxy */
+ if (priv->menuproxy == NULL) {
+
+ /* Check to see if we're already building one */
+ if (priv->menuproxy_cancel == NULL) {
+ priv->menuproxy_cancel = g_cancellable_new();
+
+ g_dbus_proxy_new(priv->session_bus,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ dbusmenu_interface_info,
+ priv->dbus_name,
+ priv->dbus_object,
+ DBUSMENU_INTERFACE,
+ priv->menuproxy_cancel,
+ menuproxy_build_cb,
+ client);
+ }
}
- g_object_add_weak_pointer(G_OBJECT(priv->propproxy), (gpointer *)&priv->propproxy);
- g_signal_connect(G_OBJECT(priv->propproxy), "destroy", G_CALLBACK(proxy_destroyed), client);
- priv->menuproxy = dbus_g_proxy_new_for_name_owner(priv->session_bus,
- priv->dbus_name,
- priv->dbus_object,
- "org.ayatana.dbusmenu",
- &error);
+ return;
+}
+
+/* Callback when we know if the menu proxy can be created or
+ not and do something with it! */
+static void
+menuproxy_build_cb (GObject * object, GAsyncResult * res, gpointer user_data)
+{
+ GError * error = NULL;
+
+ /* NOTE: We're not using any other variables before checking
+ the result because they could be destroyed and thus invalid */
+ GDBusProxy * proxy = g_dbus_proxy_new_finish(res, &error);
if (error != NULL) {
- g_warning("Unable to get dbusmenu proxy for %s on %s: %s", priv->dbus_name, priv->dbus_object, error->message);
+ g_warning("Unable to get menu proxy: %s", error->message);
g_error_free(error);
- build_dbus_proxy(client);
return;
}
- g_object_add_weak_pointer(G_OBJECT(priv->menuproxy), (gpointer *)&priv->menuproxy);
- g_signal_connect(G_OBJECT(priv->menuproxy), "destroy", G_CALLBACK(proxy_destroyed), client);
- /* If we get here, we don't need the DBus proxy */
- if (priv->dbusproxy != NULL) {
- g_object_unref(G_OBJECT(priv->dbusproxy));
- priv->dbusproxy = NULL;
- }
+ /* If this wasn't cancelled, we should be good */
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ priv->menuproxy = proxy;
- dbus_g_object_register_marshaller(_dbusmenu_server_marshal_VOID__UINT_INT, G_TYPE_NONE, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
- dbus_g_proxy_add_signal(priv->menuproxy, "LayoutUpdated", G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
- dbus_g_proxy_connect_signal(priv->menuproxy, "LayoutUpdated", G_CALLBACK(layout_update), client, NULL);
+ if (priv->menuproxy_cancel != NULL) {
+ g_object_unref(priv->menuproxy_cancel);
+ priv->menuproxy_cancel = NULL;
+ }
- dbus_g_object_register_marshaller(_dbusmenu_server_marshal_VOID__INT_STRING_POINTER, G_TYPE_NONE, G_TYPE_INT, G_TYPE_STRING, G_TYPE_VALUE, G_TYPE_INVALID);
- dbus_g_proxy_add_signal(priv->menuproxy, "ItemPropertyUpdated", G_TYPE_INT, G_TYPE_STRING, G_TYPE_VALUE, G_TYPE_INVALID);
- dbus_g_proxy_connect_signal(priv->menuproxy, "ItemPropertyUpdated", G_CALLBACK(id_prop_update), client, NULL);
+ /* If we get here, we don't need the DBus proxy */
+ if (priv->dbusproxy != 0) {
+ g_bus_unwatch_name(priv->dbusproxy);
+ priv->dbusproxy = 0;
+ }
- 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);
+ g_signal_connect(priv->menuproxy, "g-signal", G_CALLBACK(menuproxy_signal_cb), client);
+ g_signal_connect(priv->menuproxy, "notify::g-name-owner", G_CALLBACK(menuproxy_name_changed_cb), client);
- update_layout(client);
+ gchar * name_owner = g_dbus_proxy_get_name_owner(priv->menuproxy);
+ if (name_owner != NULL) {
+ update_layout(client);
+ g_free(name_owner);
+ }
return;
}
-/* Get the ID attribute of the node, parse it and
- return it. Also we're checking to ensure the node
- is a 'menu' here. */
-static gint
-parse_node_get_id (xmlNodePtr node)
+/* Handle the case where we change owners */
+static void
+menuproxy_name_changed_cb (GObject * object, GParamSpec * pspec, gpointer user_data)
{
- if (node->type != XML_ELEMENT_NODE) {
- return -1;
- }
- if (g_strcmp0((gchar *)node->name, "menu") != 0) {
- /* This kills some nodes early */
- g_warning("XML Node is not 'menu' it is '%s'", node->name);
- return -1;
- }
-
- xmlAttrPtr attrib;
- for (attrib = node->properties; attrib != NULL; attrib = attrib->next) {
- if (g_strcmp0((gchar *)attrib->name, "id") == 0) {
- if (attrib->children != NULL) {
- gint id = (guint)g_ascii_strtoll((gchar *)attrib->children->content, NULL, 10);
- /* g_debug ("Found ID: %d", id); */
- return id;
- }
- break;
- }
+ GDBusProxy * proxy = G_DBUS_PROXY(object);
+
+ gchar * owner = g_dbus_proxy_get_name_owner(proxy);
+
+ if (owner == NULL) {
+ /* Oh, no! We lost our owner! */
+ proxy_destroyed(G_OBJECT(proxy), user_data);
+ } else {
+ g_free(owner);
+ update_layout(DBUSMENU_CLIENT(user_data));
}
- g_warning("Unable to find an ID on the node");
- return -1;
+ return;
}
-/* A small helper that calls _property_set on each hash table
- entry in the properties hash. */
+/* Handle the signals out of the proxy */
static void
-get_properties_helper (gpointer key, gpointer value, gpointer data)
+menuproxy_signal_cb (GDBusProxy * proxy, gchar * sender, gchar * signal, GVariant * params, gpointer user_data)
{
- dbusmenu_menuitem_property_set_value((DbusmenuMenuitem *)data, (gchar *)key, (GValue *)value);
+ g_return_if_fail(DBUSMENU_IS_CLIENT(user_data));
+ DbusmenuClient * client = DBUSMENU_CLIENT(user_data);
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (g_strcmp0(signal, "LayoutUpdated") == 0) {
+ guint revision; gint parent;
+ g_variant_get(params, "(ui)", &revision, &parent);
+ layout_update(proxy, revision, parent, client);
+ } else if (priv->root == NULL) {
+ /* Drop out here, all the rest of these really need to have a root
+ node so we can just ignore them if there isn't one. */
+ } else if (g_strcmp0(signal, "ItemPropertiesUpdated") == 0) {
+ /* Remove before adding just incase there is a duplicate, against the
+ rules, but we can handle it so let's do it. */
+ GVariantIter ritems;
+ g_variant_iter_init(&ritems, g_variant_get_child_value(params, 1));
+
+ GVariant * ritem;
+ while ((ritem = g_variant_iter_next_value(&ritems)) != NULL) {
+ gint id = g_variant_get_int32(g_variant_get_child_value(ritem, 0));
+ DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id);
+
+ if (menuitem == NULL) {
+ continue;
+ }
+
+ GVariantIter properties;
+ g_variant_iter_init(&properties, g_variant_get_child_value(ritem, 1));
+ gchar * property;
+
+ while (g_variant_iter_next(&properties, "s", &property)) {
+ g_debug("Removing property '%s' on %d", property, id);
+ dbusmenu_menuitem_property_remove(menuitem, property);
+ }
+ }
+
+ GVariantIter items;
+ g_variant_iter_init(&items, g_variant_get_child_value(params, 0));
+
+ GVariant * item;
+ while ((item = g_variant_iter_next_value(&items)) != NULL) {
+ gint id = g_variant_get_int32(g_variant_get_child_value(item, 0));
+ GVariantIter properties;
+ g_variant_iter_init(&properties, g_variant_get_child_value(item, 1));
+ gchar * property;
+ GVariant * value;
+
+ while (g_variant_iter_next(&properties, "{sv}", &property, &value)) {
+ GVariant * internalvalue = value;
+ if (G_LIKELY(g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT))) {
+ /* Unboxing if needed */
+ internalvalue = g_variant_get_variant(value);
+ }
+ id_prop_update(proxy, id, property, internalvalue, client);
+ }
+ }
+ } else if (g_strcmp0(signal, "ItemPropertyUpdated") == 0) {
+ gint id; gchar * property; GVariant * value;
+ g_variant_get(params, "(isv)", &id, &property, &value);
+ id_prop_update(proxy, id, property, value, client);
+ } else if (g_strcmp0(signal, "ItemUpdated") == 0) {
+ gint id;
+ g_variant_get(params, "(i)", &id);
+ id_update(proxy, id, client);
+ } else if (g_strcmp0(signal, "ItemActivationRequested") == 0) {
+ gint id; guint timestamp;
+ g_variant_get(params, "(iu)", &id, &timestamp);
+ item_activated(proxy, id, timestamp, client);
+ } else {
+ g_warning("Received signal '%s' from menu proxy that is unknown", signal);
+ }
+
return;
}
@@ -573,17 +1105,32 @@ get_properties_helper (gpointer key, gpointer value, gpointer data)
This isn't the most efficient way. We can optimize this by
somehow removing the foreach. But that is for later. */
static void
-menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+menuitem_get_properties_cb (GVariant * properties, GError * error, gpointer data)
{
g_return_if_fail(DBUSMENU_IS_MENUITEM(data));
+ DbusmenuMenuitem * item = DBUSMENU_MENUITEM(data);
+
if (error != NULL) {
g_warning("Error getting properties on a menuitem: %s", error->message);
g_object_unref(data);
return;
}
- g_hash_table_foreach(properties, get_properties_helper, data);
- g_hash_table_destroy(properties);
+
+ GVariantIter * iter = g_variant_iter_new(properties);
+ gchar * key;
+ GVariant * value;
+
+ while (g_variant_iter_next(iter, "{sv}", &key, &value)) {
+ dbusmenu_menuitem_property_set_variant(item, key, value);
+
+ g_variant_unref(value);
+ g_free(key);
+ }
+
+ g_variant_iter_free(iter);
+
g_object_unref(data);
+
return;
}
@@ -591,7 +1138,7 @@ menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError
is getting recycled with the update, but we think might have prop
changes. */
static void
-menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+menuitem_get_properties_replace_cb (GVariant * properties, GError * error, gpointer data)
{
g_return_if_fail(DBUSMENU_IS_MENUITEM(data));
gboolean have_error = FALSE;
@@ -604,14 +1151,14 @@ menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties,
GList * current_props = NULL;
for (current_props = dbusmenu_menuitem_properties_list(DBUSMENU_MENUITEM(data));
- current_props != NULL ; current_props = g_list_next(current_props)) {
- if (have_error || g_hash_table_lookup(properties, current_props->data) == NULL) {
- dbusmenu_menuitem_property_remove(DBUSMENU_MENUITEM(data), (const gchar *)current_props->data);
- }
+ current_props != NULL && have_error == FALSE;
+ current_props = g_list_next(current_props)) {
+ dbusmenu_menuitem_property_remove(DBUSMENU_MENUITEM(data), (const gchar *)current_props->data);
}
+ g_list_free(current_props);
if (!have_error) {
- menuitem_get_properties_cb(proxy, properties, error, data);
+ menuitem_get_properties_cb(properties, error, data);
} else {
g_object_unref(data);
}
@@ -622,35 +1169,37 @@ menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties,
/* This is a different get properites call back that also sends
new signals. It basically is a small wrapper around the original. */
static void
-menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+menuitem_get_properties_new_cb (GVariant * 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);
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);
+ menuitem_get_properties_cb (properties, error, propdata->item);
gboolean handled = FALSE;
const gchar * type;
- DbusmenuClientTypeHandler newfunc = NULL;
+ type_handler_t * th = NULL;
type = dbusmenu_menuitem_property_get(propdata->item, DBUSMENU_MENUITEM_PROP_TYPE);
if (type != NULL) {
- newfunc = g_hash_table_lookup(priv->type_handlers, type);
+ th = (type_handler_t *)g_hash_table_lookup(priv->type_handlers, type);
} else {
- newfunc = g_hash_table_lookup(priv->type_handlers, DBUSMENU_CLIENT_TYPES_DEFAULT);
+ th = (type_handler_t *)g_hash_table_lookup(priv->type_handlers, DBUSMENU_CLIENT_TYPES_DEFAULT);
}
- if (newfunc != NULL) {
- handled = newfunc(propdata->item, propdata->parent, propdata->client);
+ if (th != NULL && th->cb != NULL) {
+ handled = th->cb(propdata->item, propdata->parent, propdata->client, th->user_data);
}
#ifdef MASSIVEDEBUGGING
@@ -671,10 +1220,30 @@ menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GEr
/* Respond to the call function to make sure that the other side
got it, or print a warning. */
static void
-menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
+menuitem_call_cb (GObject * proxy, GAsyncResult * res, gpointer userdata)
{
+ GError * error = NULL;
+ event_data_t * edata = (event_data_t *)userdata;
+ GVariant * params;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error);
+
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->variant, edata->timestamp, error, TRUE);
+
+ g_variant_unref(edata->variant);
+ g_free(edata->event);
+ g_object_unref(edata->menuitem);
+ g_free(edata);
+
+ if (G_UNLIKELY(error != NULL)) {
+ g_error_free(error);
+ }
+ if (G_LIKELY(params != NULL)) {
+ g_variant_unref(params);
}
return;
@@ -683,10 +1252,41 @@ menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
/* Sends the event over DBus to the server on the other side
of the bus. */
void
-dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name, const GValue * value, guint timestamp)
+dbusmenu_client_send_event (DbusmenuClient * client, gint id, const gchar * name, GVariant * variant, 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 (variant == NULL) {
+ variant = g_variant_new_int32(0);
+ }
+
+ 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);
+ edata->timestamp = timestamp;
+ edata->variant = variant;
+ g_variant_ref_sink(variant);
+
+ g_dbus_proxy_call(priv->menuproxy,
+ "Event",
+ g_variant_new("(isvu)", id, name, variant, timestamp),
+ G_DBUS_CALL_FLAGS_NONE,
+ 1000, /* timeout */
+ NULL, /* cancellable */
+ menuitem_call_cb,
+ edata);
+
return;
}
@@ -700,14 +1300,24 @@ struct _about_to_show_t {
/* Reports errors and responds to update request that were a result
of sending the about to show signal. */
static void
-about_to_show_cb (DBusGProxy * proxy, gboolean need_update, GError * error, gpointer userdata)
+about_to_show_cb (GObject * proxy, GAsyncResult * res, gpointer userdata)
{
+ gboolean need_update = FALSE;
+ GError * error = NULL;
about_to_show_t * data = (about_to_show_t *)userdata;
+ GVariant * params = NULL;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error);
if (error != NULL) {
g_warning("Unable to send about_to_show: %s", error->message);
/* Note: we're just ensuring only the callback gets called */
need_update = FALSE;
+ g_error_free(error);
+ error = NULL;
+ } else {
+ g_variant_get(params, "(b)", &need_update);
+ g_variant_unref(params);
}
/* If we need to update, do that first. */
@@ -738,72 +1348,104 @@ dbusmenu_client_send_about_to_show(DbusmenuClient * client, gint id, void (*cb)(
data->cb_data = cb_data;
g_object_ref(client);
- org_ayatana_dbusmenu_about_to_show_async (priv->menuproxy, id, about_to_show_cb, data);
+ g_dbus_proxy_call(priv->menuproxy,
+ "AboutToShow",
+ g_variant_new("(i)", id),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ NULL, /* cancellable */
+ about_to_show_cb,
+ data);
+ 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)
+parse_layout_xml(DbusmenuClient * client, GVariant * layout, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, GDBusProxy * proxy)
{
- gint id = parse_node_get_id(node);
+ if (layout == NULL) {
+ return NULL;
+ }
+
+ /* First verify and figure out what we've got */
+ gint id = g_variant_get_int32(g_variant_get_child_value(layout, 0));
if (id < 0) {
return NULL;
}
#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);
- }
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(id == dbusmenu_menuitem_get_id(item), NULL);
- /* 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);
- }
+ /* Some variables */
+ GVariantIter children;
+ g_variant_iter_init(&children, g_variant_get_child_value(layout, 2));
+ GVariant * child;
- xmlNodePtr children;
- guint position;
+ guint position = 0;
GList * oldchildren = g_list_copy(dbusmenu_menuitem_get_children(item));
/* g_debug("Starting old children: %d", g_list_length(oldchildren)); */
- for (children = node->children, position = 0; children != NULL; children = children->next, position++) {
+ /* Go through all the XML Nodes and make sure that we have menuitems
+ to cover those XML nodes. */
+ while ((child = g_variant_iter_next_value(&children)) != NULL) {
/* g_debug("Looking at child: %d", position); */
- gint childid = parse_node_get_id(children);
+ if (g_variant_is_of_type(child, G_VARIANT_TYPE_VARIANT)) {
+ child = g_variant_get_variant(child);
+ }
+
+ gint childid = g_variant_get_int32(g_variant_get_child_value(child, 0));
if (childid < 0) {
+ /* Don't increment the position when there isn't a valid
+ node in the XML tree. It's probably a comment. */
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);
@@ -814,20 +1456,28 @@ 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);
}
+
+ position++;
}
- /* 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);
@@ -838,13 +1488,56 @@ 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 (parent != NULL && dbusmenu_menuitem_get_id(parent) == 0) {
+ get_properties_flush(client);
+ }
+
+ /* now it's time to recurse down the tree. */
+ g_variant_iter_init(&children, g_variant_get_child_value(layout, 2));
+
+ child = g_variant_iter_next_value(&children);
+ GList * childmis = dbusmenu_menuitem_get_children(item);
+ while (child != NULL && childmis != NULL) {
+ if (g_variant_is_of_type(child, G_VARIANT_TYPE_VARIANT)) {
+ child = g_variant_get_variant(child);
+ }
+
+ gint xmlid = g_variant_get_int32(g_variant_get_child_value(child, 0));
+ /* 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) {
+ child = g_variant_iter_next_value(&children);
+ 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, child, DBUSMENU_MENUITEM(childmis->data), item, proxy);
+
+ child = g_variant_iter_next_value(&children);
+ childmis = g_list_next(childmis);
+ }
+
+ if (child != NULL) {
+ g_warning("Sync failed, now we've got extra layout nodes.");
+ }
+ if (childmis != NULL) {
+ g_warning("Sync failed, now we've got extra menu items.");
+ }
+
return item;
}
/* Take the layout passed to us over DBus and turn it into
a set of beautiful objects */
static gint
-parse_layout (DbusmenuClient * client, const gchar * layout)
+parse_layout (DbusmenuClient * client, GVariant * layout)
{
#ifdef MASSIVEDEBUGGING
g_debug("Client Parsing a new layout");
@@ -852,19 +1545,18 @@ parse_layout (DbusmenuClient * client, const gchar * layout)
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- xmlDocPtr xmldoc;
-
- xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 16*1024), "dbusmenu.xml", NULL, 0);
-
- xmlNodePtr root = xmlDocGetRootElement(xmldoc);
-
DbusmenuMenuitem * oldroot = priv->root;
- priv->root = parse_layout_xml(client, root, priv->root, NULL, priv->menuproxy);
- xmlFreeDoc(xmldoc);
+ 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, layout, priv->root, NULL, priv->menuproxy);
if (priv->root == NULL) {
- g_warning("Unable to parse layout on client %s object %s: %s", priv->dbus_name, priv->dbus_object, layout);
+ g_warning("Unable to parse layout on client %s object %s: %s", priv->dbus_name, priv->dbus_object, g_variant_print(layout, TRUE));
}
if (priv->root != oldroot) {
@@ -889,24 +1581,34 @@ parse_layout (DbusmenuClient * client, const gchar * layout)
/* When the layout property returns, here's where we take care of that. */
static void
-update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, void * data)
+update_layout_cb (GObject * proxy, GAsyncResult * res, gpointer data)
{
DbusmenuClient * client = DBUSMENU_CLIENT(data);
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+ GError * error = NULL;
+ GVariant * params = NULL;
+
+ params = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error);
+
if (error != NULL) {
- g_warning("Getting layout failed on client %s object %s: %s", priv->dbus_name, priv->dbus_object, error->message);
- return;
+ g_warning("Getting layout failed: %s", error->message);
+ g_error_free(error);
+ goto out;
}
- if (!parse_layout(client, xml)) {
+ guint rev = g_variant_get_uint32(g_variant_get_child_value(params, 0));
+ GVariant * layout = g_variant_get_child_value(params, 1);
+
+ guint parseable = parse_layout(client, layout);
+
+ if (parseable == 0) {
g_warning("Unable to parse layout!");
- return;
+ goto out;
}
priv->my_revision = rev;
/* g_debug("Root is now: 0x%X", (unsigned int)priv->root); */
- priv->layoutcall = NULL;
#ifdef MASSIVEDEBUGGING
g_debug("Client signaling layout has changed.");
#endif
@@ -918,6 +1620,17 @@ update_layout_cb (DBusGProxy * proxy, guint rev, gchar * xml, GError * error, vo
update_layout(client);
}
+out:
+ if (priv->layoutcall != NULL) {
+ g_object_unref(priv->layoutcall);
+ priv->layoutcall = NULL;
+ }
+
+ if (params != NULL) {
+ g_variant_unref(params);
+ }
+
+ g_object_unref(G_OBJECT(client));
return;
}
@@ -932,31 +1645,54 @@ update_layout (DbusmenuClient * client)
return;
}
+ gchar * name_owner = g_dbus_proxy_get_name_owner(priv->menuproxy);
+ if (name_owner == NULL) {
+ return;
+ }
+ g_free(name_owner);
+
if (priv->layoutcall != NULL) {
return;
}
- priv->layoutcall = org_ayatana_dbusmenu_get_layout_async(priv->menuproxy,
- 0, /* Parent is the root */
- update_layout_cb,
- client);
+ priv->layoutcall = g_cancellable_new();
+
+ GVariantBuilder tupleb;
+ g_variant_builder_init(&tupleb, G_VARIANT_TYPE_TUPLE);
+
+ g_variant_builder_add_value(&tupleb, g_variant_new_int32(0)); // root
+ g_variant_builder_add_value(&tupleb, g_variant_new_int32(-1)); // recurse
+ g_variant_builder_add_value(&tupleb, g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0)); // props
+
+ GVariant * args = g_variant_builder_end(&tupleb);
+ // g_debug("Args (type: %s): %s", g_variant_get_type_string(args), g_variant_print(args, TRUE));
+
+ g_object_ref(G_OBJECT(client));
+ g_dbus_proxy_call(priv->menuproxy,
+ "GetLayout",
+ args,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ priv->layoutcall, /* cancellable */
+ update_layout_cb,
+ client);
return;
}
/* Public API */
/**
- dbusmenu_client_new:
- @name: The DBus name for the server to connect to
- @object: The object on the server to monitor
-
- This function creates a new client that connects to a specific
- server on DBus. That server is at a specific location sharing
- a known object. The interface is assumed by the code to be
- the DBus menu interface. The newly created client will start
- sending out events as it syncs up with the server.
-
- Return value: A brand new #DbusmenuClient
+ * dbusmenu_client_new:
+ * @name: The DBus name for the server to connect to
+ * @object: The object on the server to monitor
+ *
+ * This function creates a new client that connects to a specific
+ * server on DBus. That server is at a specific location sharing
+ * a known object. The interface is assumed by the code to be
+ * the DBus menu interface. The newly created client will start
+ * sending out events as it syncs up with the server.
+ *
+ * Return value: A brand new #DbusmenuClient
*/
DbusmenuClient *
dbusmenu_client_new (const gchar * name, const gchar * object)
@@ -970,21 +1706,21 @@ dbusmenu_client_new (const gchar * name, const gchar * object)
}
/**
- dbusmenu_client_get_root:
- @client: The #DbusmenuClient to get the root node from
-
- Grabs the root node for the specified client @client. This
- function may block. It will block if there is currently a
- call to update the layout, it will block on that layout
- updated and then return the newly updated layout. Chances
- are that this update is in the queue for the mainloop as
- it would have been requested some time ago, but in theory
- it could block longer.
-
- Return value: A #DbusmenuMenuitem representing the root of
- menu on the server. If there is no server or there is
- an error receiving its layout it'll return #NULL.
-*/
+ * dbusmenu_client_get_root:
+ * @client: The #DbusmenuClient to get the root node from
+ *
+ * Grabs the root node for the specified client @client. This
+ * function may block. It will block if there is currently a
+ * call to update the layout, it will block on that layout
+ * updated and then return the newly updated layout. Chances
+ * are that this update is in the queue for the mainloop as
+ * it would have been requested some time ago, but in theory
+ * it could block longer.
+ *
+ * Return value: (transfer none): A #DbusmenuMenuitem representing the root of
+ * menu on the server. If there is no server or there is
+ * an error receiving its layout it'll return #NULL.
+ */
DbusmenuMenuitem *
dbusmenu_client_get_root (DbusmenuClient * client)
{
@@ -992,10 +1728,6 @@ dbusmenu_client_get_root (DbusmenuClient * client)
DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
- if (priv->propproxy == NULL) {
- return NULL;
- }
-
#ifdef MASSIVEDEBUGGING
g_debug("Client get root: %X", (guint)priv->root);
#endif
@@ -1003,30 +1735,74 @@ dbusmenu_client_get_root (DbusmenuClient * client)
return priv->root;
}
+/* Remove the type handler when we're all done with it */
+static void
+type_handler_destroy (gpointer user_data)
+{
+ type_handler_t * th = (type_handler_t *)user_data;
+ if (th->destroy_cb != NULL) {
+ th->destroy_cb(th->client, th->type, th->user_data);
+ }
+ g_free(th->type);
+ g_free(th);
+ return;
+}
+
/**
- dbusmenu_client_add_type_handler:
- @client: Client where we're getting types coming in
- @type: A text string that will be matched with the 'type'
- property on incoming menu items
- @newfunc: The function that will be executed with those new
- items when they come in.
-
- This function connects into the type handling of the #DbusmenuClient.
- Every new menuitem that comes in immediately gets asked for it's
- properties. When we get those properties we check the 'type'
- property and look to see if it matches a handler that is known
- by the client. If so, the @newfunc function is executed on that
- #DbusmenuMenuitem. If not, then the DbusmenuClient::new-menuitem
- signal is sent.
-
- In the future the known types will be sent to the server so that it
- can make choices about the menu item types availble.
-
- Return value: If registering the new type was successful.
+ * dbusmenu_client_add_type_handler:
+ * @client: Client where we're getting types coming in
+ * @type: A text string that will be matched with the 'type'
+ * property on incoming menu items
+ * @newfunc: The function that will be executed with those new
+ * items when they come in.
+ *
+ * This function connects into the type handling of the #DbusmenuClient.
+ * Every new menuitem that comes in immediately gets asked for it's
+ * properties. When we get those properties we check the 'type'
+ * property and look to see if it matches a handler that is known
+ * by the client. If so, the @newfunc function is executed on that
+ * #DbusmenuMenuitem. If not, then the DbusmenuClient::new-menuitem
+ * signal is sent.
+ *
+ * In the future the known types will be sent to the server so that it
+ * can make choices about the menu item types availble.
+ *
+ * Return value: If registering the new type was successful.
*/
gboolean
dbusmenu_client_add_type_handler (DbusmenuClient * client, const gchar * type, DbusmenuClientTypeHandler newfunc)
{
+ return dbusmenu_client_add_type_handler_full(client, type, newfunc, NULL, NULL);
+}
+
+/**
+ * dbusmenu_client_add_type_handler_full:
+ * @client: Client where we're getting types coming in
+ * @type: A text string that will be matched with the 'type'
+ * property on incoming menu items
+ * @newfunc: The function that will be executed with those new
+ * items when they come in.
+ * @user_data: Data passed to @newfunc when it is called
+ * @destroy_func: A function that is called when the type handler is
+ * removed (usually on client destruction) which will free
+ * the resources in @user_data.
+ *
+ * This function connects into the type handling of the #DbusmenuClient.
+ * Every new menuitem that comes in immediately gets asked for it's
+ * properties. When we get those properties we check the 'type'
+ * property and look to see if it matches a handler that is known
+ * by the client. If so, the @newfunc function is executed on that
+ * #DbusmenuMenuitem. If not, then the DbusmenuClient::new-menuitem
+ * signal is sent.
+ *
+ * In the future the known types will be sent to the server so that it
+ * can make choices about the menu item types availble.
+ *
+ * Return value: If registering the new type was successful.
+*/
+gboolean
+dbusmenu_client_add_type_handler_full (DbusmenuClient * client, const gchar * type, DbusmenuClientTypeHandler newfunc, gpointer user_data, DbusmenuClientTypeDestroyHandler destroy_func)
+{
g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), FALSE);
g_return_val_if_fail(type != NULL, FALSE);
@@ -1047,6 +1823,14 @@ dbusmenu_client_add_type_handler (DbusmenuClient * client, const gchar * type, D
return FALSE;
}
- g_hash_table_insert(priv->type_handlers, g_strdup(type), newfunc);
+ type_handler_t * th = g_new0(type_handler_t, 1);
+ th->client = client;
+ th->cb = newfunc;
+ th->destroy_cb = destroy_func;
+ th->user_data = user_data;
+ th->type = g_strdup(type);
+
+ g_hash_table_insert(priv->type_handlers, g_strdup(type), th);
return TRUE;
}
+
diff --git a/libdbusmenu-glib/client.h b/libdbusmenu-glib/client.h
index 2b76f5e..79c0ee2 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, GVariant * 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,9 +105,34 @@ struct _DbusmenuClientClass {
typedef struct _DbusmenuClient DbusmenuClient;
struct _DbusmenuClient {
GObject parent;
+
+ /*< Private >*/
+ DbusmenuClientPrivate * priv;
};
-typedef gboolean (*DbusmenuClientTypeHandler) (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
+/**
+ DbusmenuClientTypeHandler:
+ @newitem: The #DbusmenuMenuitem that was created
+ @parent: The parent of @newitem or #NULL if none
+ @client: A pointer to the #DbusmenuClient
+ @user_data: The data you gave us
+
+ The type handler is called when a dbusmenu item is created
+ with a matching type as setup in #dbusmenu_client_add_type_handler
+*/
+typedef gboolean (*DbusmenuClientTypeHandler) (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data);
+
+/**
+ DbusmenuClientTypeDestroyHandler:
+ @client: A pointer to the #DbusmenuClient
+ @type: The type that this handler was registered with
+ @user_data: The data you gave us
+
+ This handler is called when the type becomes unregistered by the
+ client. This is usally caused by the #DbusmenuClient being destroyed
+ and should free memory or unref objects in @user_data.
+*/
+typedef void (*DbusmenuClientTypeDestroyHandler) (DbusmenuClient * client, const gchar * type, gpointer user_data);
GType dbusmenu_client_get_type (void);
DbusmenuClient * dbusmenu_client_new (const gchar * name,
@@ -104,10 +141,15 @@ DbusmenuMenuitem * dbusmenu_client_get_root (DbusmenuClient * client)
gboolean dbusmenu_client_add_type_handler (DbusmenuClient * client,
const gchar * type,
DbusmenuClientTypeHandler newfunc);
+gboolean dbusmenu_client_add_type_handler_full (DbusmenuClient * client,
+ const gchar * type,
+ DbusmenuClientTypeHandler newfunc,
+ gpointer user_data,
+ DbusmenuClientTypeDestroyHandler destroy_func);
void dbusmenu_client_send_event (DbusmenuClient * client,
gint id,
const gchar * name,
- const GValue * value,
+ GVariant * variant,
guint timestamp);
void dbusmenu_client_send_about_to_show(DbusmenuClient * client,
gint id,
diff --git a/libdbusmenu-glib/dbus-menu.xml b/libdbusmenu-glib/dbus-menu.xml
index 53b67de..da14c63 100644
--- a/libdbusmenu-glib/dbus-menu.xml
+++ b/libdbusmenu-glib/dbus-menu.xml
@@ -28,15 +28,15 @@ You should have received a copy of both the GNU Lesser General Public
License version 3 and version 2.1 along with this program. If not, see
<http://www.gnu.org/licenses/>
-->
-<node name="/" xmlns:dox="http://www.ayatana.org/dbus/dox.dtd">
+<node name="/" xmlns:dox="http://www.canonical.com/dbus/dox.dtd">
<dox:d><![CDATA[
@mainpage
The goal of DBusMenu is to expose menus on DBus.
- Main interface is documented here: @ref org::ayatana::dbusmenu
+ Main interface is documented here: @ref com::canonical::dbusmenu
]]></dox:d>
- <interface name="org.ayatana.dbusmenu">
+ <interface name="com.canonical.dbusmenu">
<dox:d><![CDATA[
A DBus interface to expose menus on DBus.
@@ -174,34 +174,38 @@ License version 3 and version 2.1 along with this program. If not, see
<!-- Functions -->
<method name="GetLayout">
- <dox:d><![CDATA[
- Provides an XML representation of the menu hierarchy
-
- XML syntax:
-
- @verbatim
-<menu id="0"> # Root container
- <menu id="1"> # First level menu, for example "File"
- <menu id="2"/> ~ Second level menu, for example "Open"
- <menu id="3"/>
- ...
- </menu>
- <menu id="4"> # Another first level menu, say "Edit"
- ...
- </menu>
- ...
-</menu>
- @endverbatim
- ]]></dox:d>
+ <dox:d>
+ Provides the layout and propertiers that are attached to the entries
+ that are in the layout. It only gives the items that are children
+ of the item that is specified in @parentId. It will return all of the
+ properties or specific ones depending of the value in @propertyNames.
+
+ The format is recursive, where the second 'v' is in the same format
+ as the original 'a(ia(sv)a(v))'. If the @recursive flag is set to
+ less than one then the second array will have zero entries.
+ </dox:d>
<arg type="i" name="parentId" direction="in">
<dox:d>The ID of the parent node for the layout. For
grabbing the layout from the root node use zero.</dox:d>
</arg>
+ <arg type="i" name="recurse" direction="in">
+ <dox:d>
+ The amount of levels of recursion to use. -1, as value would
+ deliver all the items under the @parentId.
+ </dox:d>
+ </arg>
+ <arg type="as" name="propertyNames" direction="in" >
+ <dox:d>
+ The list of item properties we are
+ interested in. If there are no entries in the list all of
+ the properties will be sent.
+ </dox:d>
+ </arg>
<arg type="u" name="revision" direction="out">
<dox:d>The revision number of the layout. For matching
with layoutUpdated signals.</dox:d>
</arg>
- <arg type="s" name="layout" direction="out">
+ <arg type="(ia{sv}av)" name="layout" direction="out">
<dox:d>The layout as an XML string of IDs.</dox:d>
</arg>
</method>
@@ -236,33 +240,21 @@ License version 3 and version 2.1 along with this program. If not, see
</arg>
</method>
- <method name="GetChildren">
- <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
- <arg type="i" name="id" direction="in" />
- <arg type="as" name="propertyNames" direction="in" />
- <arg type="a(ia{sv})" name="properties" direction="out" />
- </method>
-
<method name="GetProperty">
- <arg type="i" name="id" direction="in" />
- <arg type="s" name="name" direction="in" />
- <arg type="v" name="value" direction="out" />
- </method>
-
- <method name="GetProperties">
<dox:d>
- Returns multiple properties in one call. This is more efficient than
- GetProperty.
-
+ Get a signal property on a single item. This is not useful if you're
+ going to implement this interface, it should only be used if you're
+ debugging via a commandline tool.
</dox:d>
- <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
- <arg type="i" name="id" direction="in" >
- <dox:d>The item whose properties we want to retrieve.</dox:d>
+ <arg type="i" name="id" direction="in">
+ <dox:d>the id of the item which received the event</dox:d>
</arg>
- <arg type="as" name="propertyNames" direction="in" >
- <dox:d>List of string name of the properties we want. If the list contains no entries, all properties are sent.</dox:d>
+ <arg type="s" name="name" direction="in">
+ <dox:d>the name of the property to get</dox:d>
+ </arg>
+ <arg type="v" name="value" direction="out">
+ <dox:d>the value of the property</dox:d>
</arg>
- <arg type="a{sv}" name="properties" direction="out" />
</method>
<method name="Event">
@@ -309,25 +301,16 @@ License version 3 and version 2.1 along with this program. If not, see
</method>
<!-- Signals -->
- <signal name="ItemPropertyUpdated">
- <dox:d>
- Triggered by the application to notify the applet that the property @a property
- from item @a id has changed to @a value.
- </dox:d>
- <arg type="i" name="id" direction="out" />
- <arg type="s" name="prop" direction="out" />
- <arg type="v" name="value" direction="out" />
- </signal>
-
- <signal name="ItemUpdated">
+ <signal name="ItemsPropertiesUpdated">
<dox:d>
- Triggered by the application to notify the applet that all properties of item
+ Triggered when there are lots of property updates across many items
+ so they all get grouped into a single dbus message. The format is
+ the ID of the item with a hashtable of names and values for those
+ properties.
</dox:d>
- <arg type="i" name="id" direction="out" >
- <dox:d>id which should be considered outdated</dox:d>
- </arg>
+ <arg type="a(ia(sv))" name="props" direction="out" />
+ <arg type="a(ia(s))" name="props_removed" direction="out" />
</signal>
-
<signal name="LayoutUpdated">
<dox:d>
Triggered by the application to notify display of a layout update, up to
@@ -344,6 +327,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/dbusmenu-glib.pc.in b/libdbusmenu-glib/dbusmenu-glib-0.4.pc.in
index dacd903..31a1eac 100644
--- a/libdbusmenu-glib/dbusmenu-glib.pc.in
+++ b/libdbusmenu-glib/dbusmenu-glib-0.4.pc.in
@@ -4,8 +4,8 @@ libdir=@libdir@
bindir=@bindir@
includedir=@includedir@
-Cflags: -I${includedir}/libdbusmenu-0.1
-Requires: dbus-glib-1
+Cflags: -I${includedir}/libdbusmenu-0.4
+Requires:
Libs: -L${libdir} -ldbusmenu-glib
Name: libdbusmenu-glib
diff --git a/libdbusmenu-glib/dbusmenu-glib.h b/libdbusmenu-glib/dbusmenu-glib.h
new file mode 100644
index 0000000..9c377ca
--- /dev/null
+++ b/libdbusmenu-glib/dbusmenu-glib.h
@@ -0,0 +1,37 @@
+/*
+A library to communicate a menu object set accross DBus and
+track updates and maintain consistency.
+
+Copyright 2011 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 either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, 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 applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifndef __DBUSMENU_GLIB_H__
+#define __DBUSMENU_GLIB_H__
+
+#include <libdbusmenu-glib/client.h>
+#include <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-glib/menuitem-proxy.h>
+#include <libdbusmenu-glib/server.h>
+
+#endif /* __DBUSMENU_GLIB_H__ */
diff --git a/libdbusmenu-glib/menuitem-marshal.list b/libdbusmenu-glib/menuitem-marshal.list
index 654c91b..e3cb272 100644
--- a/libdbusmenu-glib/menuitem-marshal.list
+++ b/libdbusmenu-glib/menuitem-marshal.list
@@ -1,4 +1,4 @@
-VOID: STRING, POINTER
+VOID: STRING, VARIANT
VOID: OBJECT, UINT, UINT
VOID: OBJECT, UINT
VOID: OBJECT
diff --git a/libdbusmenu-glib/menuitem-private.h b/libdbusmenu-glib/menuitem-private.h
index 3a0c026..89319dc 100644
--- a/libdbusmenu-glib/menuitem-private.h
+++ b/libdbusmenu-glib/menuitem-private.h
@@ -33,9 +33,11 @@ License version 3 and version 2.1 along with this program. If not, see
G_BEGIN_DECLS
-void dbusmenu_menuitem_buildxml (DbusmenuMenuitem * mi, GPtrArray * array);
+GVariant * dbusmenu_menuitem_build_variant (DbusmenuMenuitem * mi, const gchar ** properties, gint recurse);
gboolean dbusmenu_menuitem_realized (DbusmenuMenuitem * mi);
void dbusmenu_menuitem_set_realized (DbusmenuMenuitem * mi);
+GVariant * dbusmenu_menuitem_properties_variant (DbusmenuMenuitem * mi, const gchar ** properties);
+gboolean dbusmenu_menuitem_property_is_default (DbusmenuMenuitem * mi, const gchar * property);
G_END_DECLS
diff --git a/libdbusmenu-glib/menuitem-proxy.c b/libdbusmenu-glib/menuitem-proxy.c
index 2dd5ada..ae6a334 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);
@@ -58,7 +56,7 @@ static void dbusmenu_menuitem_proxy_dispose (GObject *object);
static void dbusmenu_menuitem_proxy_finalize (GObject *object);
static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec);
static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
-static void handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp);
+static void handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp);
static void add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi);
static void remove_menuitem (DbusmenuMenuitemProxy * pmi);
@@ -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;
@@ -162,21 +162,21 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
/* Takes the event and passes it along to the item that we're
playing proxy for. */
static void
-handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp)
+handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp)
{
g_return_if_fail(DBUSMENU_IS_MENUITEM_PROXY(mi));
DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(mi);
g_return_if_fail(priv->mi != NULL);
- return dbusmenu_menuitem_handle_event(priv->mi, name, value, timestamp);
+ return dbusmenu_menuitem_handle_event(priv->mi, name, variant, timestamp);
}
/* Watches a property change and makes sure to put that value
into our property list. */
static void
-proxy_item_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * value, gpointer user_data)
+proxy_item_property_changed (DbusmenuMenuitem * mi, gchar * property, GVariant * variant, gpointer user_data)
{
DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
- dbusmenu_menuitem_property_set_value(DBUSMENU_MENUITEM(pmi), property, value);
+ dbusmenu_menuitem_property_set_variant(DBUSMENU_MENUITEM(pmi), property, variant);
return;
}
@@ -273,7 +273,7 @@ add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi)
GList * prop;
for (prop = props; prop != NULL; prop = g_list_next(prop)) {
gchar * prop_name = (gchar *)prop->data;
- dbusmenu_menuitem_property_set_value(DBUSMENU_MENUITEM(pmi), prop_name, dbusmenu_menuitem_property_get_value(priv->mi, prop_name));
+ dbusmenu_menuitem_property_set_variant(DBUSMENU_MENUITEM(pmi), prop_name, dbusmenu_menuitem_property_get_variant(priv->mi, prop_name));
}
g_list_free(props);
@@ -325,14 +325,14 @@ remove_menuitem (DbusmenuMenuitemProxy * pmi)
}
/**
- dbusmenu_menuitem_proxy_new:
- @mi: The #DbusmenuMenuitem to proxy
-
- Builds a new #DbusmenuMenuitemProxy object that proxies
- all of the values for @mi.
-
- Return value: A new #DbusmenuMenuitemProxy object.
-*/
+ * dbusmenu_menuitem_proxy_new:
+ * @mi: The #DbusmenuMenuitem to proxy
+ *
+ * Builds a new #DbusmenuMenuitemProxy object that proxies
+ * all of the values for @mi.
+ *
+ * Return value: A new #DbusmenuMenuitemProxy object.
+ */
DbusmenuMenuitemProxy *
dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi)
{
@@ -344,15 +344,15 @@ dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi)
}
/**
- dbusmenu_menuitem_proxy_get_wrapped:
- @pmi: #DbusmenuMenuitemProxy to look into
-
- Accesses the private variable of which #DbusmenuMenuitem
- we are doing the proxying for.
-
- Return value: A #DbusmenuMenuitem object or a #NULL if we
- don't have one or there is an error.
-*/
+ * dbusmenu_menuitem_proxy_get_wrapped:
+ * @pmi: #DbusmenuMenuitemProxy to look into
+ *
+ * Accesses the private variable of which #DbusmenuMenuitem
+ * we are doing the proxying for.
+ *
+ * Return value: (transfer none): A #DbusmenuMenuitem object or a #NULL if we
+ * don't have one or there is an error.
+ */
DbusmenuMenuitem *
dbusmenu_menuitem_proxy_get_wrapped (DbusmenuMenuitemProxy * pmi)
{
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..c994130 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,8 @@ enum {
CHILD_REMOVED,
CHILD_MOVED,
REALIZED,
+ SHOW_TO_USER,
+ ABOUT_TO_SHOW,
LAST_SIGNAL
};
@@ -83,8 +84,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);
@@ -95,7 +95,8 @@ static void set_property (GObject * obj, guint id, const GValue * value, GParamS
static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
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 handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, 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 +114,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:
@@ -128,8 +130,8 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass)
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(DbusmenuMenuitemClass, property_changed),
NULL, NULL,
- _dbusmenu_menuitem_marshal_VOID__STRING_POINTER,
- G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER);
+ _dbusmenu_menuitem_marshal_VOID__STRING_VARIANT,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
/**
DbusmenuMenuitem::item-activated:
@arg0: The #DbusmenuMenuitem object.
@@ -211,6 +213,37 @@ 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);
+
+ /**
+ DbusmenuMenuitem::about-to-show:
+ @arg0: The #DbusmenuMenuitem object.
+
+ Emitted when the submenu for this item
+ is about to be shown
+ */
+ signals[ABOUT_TO_SHOW] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(DbusmenuMenuitemClass, about_to_show),
+ NULL, NULL,
+ _dbusmenu_menuitem_marshal_VOID__VOID,
+ G_TYPE_BOOLEAN, 0, G_TYPE_NONE);
g_object_class_install_property (object_class, PROP_ID,
g_param_spec_int(PROP_ID_S, "ID for the menu item",
@@ -254,15 +287,12 @@ g_value_transform_STRING_INT (const GValue * in, GValue * out)
static gint menuitem_next_id = 1;
-/* A small little function to both clear the insides of a
- value as well as the memory it itself uses. */
+/* Make the unref function match the prototype need for the
+ hashtable destructor */
static void
-_g_value_free (gpointer data)
+_g_variant_unref (gpointer data)
{
- if (data == NULL) return;
- GValue * value = (GValue*)data;
- g_value_unset(value);
- g_free(data);
+ g_variant_unref((GVariant *)data);
return;
}
@@ -271,12 +301,14 @@ _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;
priv->children = NULL;
- priv->properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, _g_value_free);
+ priv->properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, _g_variant_unref);
priv->root = FALSE;
priv->realized = FALSE;
@@ -361,7 +393,7 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
/* Handles the activate event if it is sent. */
static void
-handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp)
+handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * value, guint timestamp)
{
if (g_strcmp0(name, "clicked") == 0) {
g_signal_emit(G_OBJECT(mi), signals[ITEM_ACTIVATED], 0, timestamp, TRUE);
@@ -370,15 +402,38 @@ handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, g
return;
}
-/* Public interface */
+/* 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));
-/**
- dbusmenu_menuitem_new:
+ if (dbusmenu_menuitem_get_children(mi) == NULL) {
+ g_warning("About to Show called on an item wihtout submenus. We're ignoring it.");
+ } else {
+ gboolean dummy;
+ g_signal_emit(G_OBJECT(mi), signals[ABOUT_TO_SHOW], 0, &dummy);
+ }
- Create a new #DbusmenuMenuitem with all default values.
+ if (cb != NULL) {
+ cb(mi, cb_data);
+ }
- Return value: A newly allocated #DbusmenuMenuitem.
-*/
+ return;
+}
+
+/* Public interface */
+
+/**
+ * dbusmenu_menuitem_new:
+ *
+ * Create a new #DbusmenuMenuitem with all default values.
+ *
+ * Return value: A newly allocated #DbusmenuMenuitem.
+ */
DbusmenuMenuitem *
dbusmenu_menuitem_new (void)
{
@@ -386,13 +441,13 @@ dbusmenu_menuitem_new (void)
}
/**
- dbusmenu_menuitem_new_with_id:
- @id: ID to use for this menuitem
-
- This creates a blank #DbusmenuMenuitem with a specific ID.
-
- Return value: A newly allocated #DbusmenuMenuitem.
-*/
+ * dbusmenu_menuitem_new_with_id:
+ * @id: ID to use for this menuitem
+ *
+ * This creates a blank #DbusmenuMenuitem with a specific ID.
+ *
+ * Return value: A newly allocated #DbusmenuMenuitem.
+ */
DbusmenuMenuitem *
dbusmenu_menuitem_new_with_id (gint id)
{
@@ -402,13 +457,13 @@ dbusmenu_menuitem_new_with_id (gint id)
}
/**
- dbusmenu_menuitem_get_id:
- @mi: The #DbusmenuMenuitem to query.
-
- Gets the unique ID for @mi.
-
- Return value: The ID of the @mi.
-*/
+ * dbusmenu_menuitem_get_id:
+ * @mi: The #DbusmenuMenuitem to query.
+ *
+ * Gets the unique ID for @mi.
+ *
+ * Return value: The ID of the @mi.
+ */
gint
dbusmenu_menuitem_get_id (DbusmenuMenuitem * mi)
{
@@ -425,17 +480,17 @@ dbusmenu_menuitem_get_id (DbusmenuMenuitem * mi)
}
/**
- dbusmenu_menuitem_realized:
- @mi: #DbusmenuMenuitem to check on
-
- This function returns whether the menuitem has been realized or
- not. This is significant mostly in client implementations that
- can use this additional state to see if the second layers of
- the implementation have been built yet.
-
- Return value: Returns whether or not the menu item has been realized
- yet or not.
-*/
+ * dbusmenu_menuitem_realized:
+ * @mi: #DbusmenuMenuitem to check on
+ *
+ * This function returns whether the menuitem has been realized or
+ * not. This is significant mostly in client implementations that
+ * can use this additional state to see if the second layers of
+ * the implementation have been built yet.
+ *
+ * Return value: Returns whether or not the menu item has been realized
+ * yet or not.
+ */
gboolean
dbusmenu_menuitem_realized (DbusmenuMenuitem * mi)
{
@@ -445,12 +500,12 @@ dbusmenu_menuitem_realized (DbusmenuMenuitem * mi)
}
/**
- dbusmenu_menuitem_set_realized:
- @mi: #DbusmenuMenuitem to realize
-
- Sets the internal variable tracking whether it's been realized and
- signals the DbusmenuMenuitem::realized event.
-*/
+ * dbusmenu_menuitem_set_realized:
+ * @mi: #DbusmenuMenuitem to realize
+ *
+ * Sets the internal variable tracking whether it's been realized and
+ * signals the DbusmenuMenuitem::realized event.
+ */
void
dbusmenu_menuitem_set_realized (DbusmenuMenuitem * mi)
{
@@ -465,15 +520,15 @@ dbusmenu_menuitem_set_realized (DbusmenuMenuitem * mi)
}
/**
- dbusmenu_menuitem_get_children:
- @mi: The #DbusmenuMenuitem to query.
-
- Returns simply the list of children that this menu item
- has. The list is valid until another child related function
- is called, where it might be changed.
-
- Return value: A #GList of pointers to #DbusmenuMenuitem objects.
-*/
+ * dbusmenu_menuitem_get_children:
+ * @mi: The #DbusmenuMenuitem to query.
+ *
+ * Returns simply the list of children that this menu item
+ * has. The list is valid until another child related function
+ * is called, where it might be changed.
+ *
+ * Return value: (transfer none): A #GList of pointers to #DbusmenuMenuitem objects.
+ */
GList *
dbusmenu_menuitem_get_children (DbusmenuMenuitem * mi)
{
@@ -492,22 +547,22 @@ take_children_signal (gpointer data, gpointer user_data)
g_debug("Menuitem %d (%s) signalling child removed %d (%s)", ID(user_data), LABEL(user_data), ID(data), LABEL(data));
#endif
g_signal_emit(G_OBJECT(user_data), signals[CHILD_REMOVED], 0, DBUSMENU_MENUITEM(data), TRUE);
- g_object_unref(G_OBJECT(data));
return;
}
/**
- dbusmenu_menuitem_take_children:
- @mi: The #DbusmenMenuitem to take the children from.
-
- While the name sounds devious that's exactly what this function
- does. It takes the list of children from the @mi and clears the
- internal list. The calling function is now in charge of the ref's
- on the children it has taken. A lot of responsibility involved
- in taking children.
-
- Return value: A #GList of pointers to #DbusmenuMenuitem objects.
-*/
+ * dbusmenu_menuitem_take_children:
+ * @mi: The #DbusmenMenuitem to take the children from.
+ *
+ * While the name sounds devious that's exactly what this function
+ * does. It takes the list of children from the @mi and clears the
+ * internal list. The calling function is now in charge of the ref's
+ * on the children it has taken. A lot of responsibility involved
+ * in taking children.
+ *
+ * Return value: (transfer full) (element-type Dbusmenu.Menuitem):
+ * A #GList of pointers to #DbusmenuMenuitem objects.
+ */
GList *
dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi)
{
@@ -524,16 +579,16 @@ dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi)
}
/**
- dbusmenu_menuitem_get_position:
- @mi: The #DbusmenuMenuitem to find the position of
- @parent: The #DbusmenuMenuitem who's children contain @mi
-
- This function returns the position of the menu item @mi
- in the children of @parent. It will return zero if the
- menu item can't be found.
-
- Return value: The position of @mi in the children of @parent.
-*/
+ * dbusmenu_menuitem_get_position:
+ * @mi: The #DbusmenuMenuitem to find the position of
+ * @parent: The #DbusmenuMenuitem who's children contain @mi
+ *
+ * This function returns the position of the menu item @mi
+ * in the children of @parent. It will return zero if the
+ * menu item can't be found.
+ *
+ * Return value: The position of @mi in the children of @parent.
+ */
guint
dbusmenu_menuitem_get_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent)
{
@@ -563,15 +618,15 @@ dbusmenu_menuitem_get_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent
}
/**
- dbusmenu_menuitem_get_position_realized:
- @mi: The #DbusmenuMenuitem to find the position of
- @parent: The #DbusmenuMenuitem who's children contain @mi
-
- This function is very similar to #dbusmenu_menuitem_get_position
- except that it only counts in the children that have been realized.
-
- Return value: The position of @mi in the realized children of @parent.
-*/
+ * dbusmenu_menuitem_get_position_realized:
+ * @mi: The #DbusmenuMenuitem to find the position of
+ * @parent: The #DbusmenuMenuitem who's children contain @mi
+ *
+ * This function is very similar to #dbusmenu_menuitem_get_position
+ * except that it only counts in the children that have been realized.
+ *
+ * Return value: The position of @mi in the realized children of @parent.
+ */
guint
dbusmenu_menuitem_get_position_realized (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent)
{
@@ -607,15 +662,15 @@ dbusmenu_menuitem_get_position_realized (DbusmenuMenuitem * mi, DbusmenuMenuitem
}
/**
- dbusmenu_menuitem_child_append:
- @mi: The #DbusmenuMenuitem which will become a new parent
- @child: The #DbusmenMenuitem that will be a child
-
- This function adds @child to the list of children on @mi at
- the end of that list.
-
- Return value: Whether the child has been added successfully.
-*/
+ * dbusmenu_menuitem_child_append:
+ * @mi: The #DbusmenuMenuitem which will become a new parent
+ * @child: The #DbusmenMenuitem that will be a child
+ *
+ * This function adds @child to the list of children on @mi at
+ * the end of that list.
+ *
+ * Return value: Whether the child has been added successfully.
+ */
gboolean
dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
{
@@ -639,15 +694,15 @@ dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
}
/**
- dbusmenu_menuitem_child_prepend:
- @mi: The #DbusmenuMenuitem which will become a new parent
- @child: The #DbusmenMenuitem that will be a child
-
- This function adds @child to the list of children on @mi at
- the beginning of that list.
-
- Return value: Whether the child has been added successfully.
-*/
+ * dbusmenu_menuitem_child_prepend:
+ * @mi: The #DbusmenuMenuitem which will become a new parent
+ * @child: The #DbusmenMenuitem that will be a child
+ *
+ * This function adds @child to the list of children on @mi at
+ * the beginning of that list.
+ *
+ * Return value: Whether the child has been added successfully.
+ */
gboolean
dbusmenu_menuitem_child_prepend (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
{
@@ -671,16 +726,16 @@ dbusmenu_menuitem_child_prepend (DbusmenuMenuitem * mi, DbusmenuMenuitem * child
}
/**
- dbusmenu_menuitem_child_delete:
- @mi: The #DbusmenuMenuitem which has @child as a child
- @child: The child #DbusmenuMenuitem that you want to no longer
- be a child of @mi.
-
- This function removes @child from the children list of @mi. It does
- not call #g_object_unref on @child.
-
- Return value: If we were able to delete @child.
-*/
+ * dbusmenu_menuitem_child_delete:
+ * @mi: The #DbusmenuMenuitem which has @child as a child
+ * @child: The child #DbusmenuMenuitem that you want to no longer
+ * be a child of @mi.
+ *
+ * This function removes @child from the children list of @mi. It does
+ * not call #g_object_unref on @child.
+ *
+ * Return value: If we were able to delete @child.
+ */
gboolean
dbusmenu_menuitem_child_delete (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
{
@@ -703,17 +758,17 @@ dbusmenu_menuitem_child_delete (DbusmenuMenuitem * mi, DbusmenuMenuitem * child)
}
/**
- dbusmenu_menuitem_child_add_position:
- @mi: The #DbusmenuMenuitem that we're adding the child @child to.
- @child: The #DbusmenuMenuitem to make a child of @mi.
- @position: Where in @mi object's list of chidren @child should be placed.
-
- Puts @child in the list of children for @mi at the location
- specified in @position. If there is not enough entires available
- then @child will be placed at the end of the list.
-
- Return value: Whether @child was added successfully.
-*/
+ * dbusmenu_menuitem_child_add_position:
+ * @mi: The #DbusmenuMenuitem that we're adding the child @child to.
+ * @child: The #DbusmenuMenuitem to make a child of @mi.
+ * @position: Where in @mi object's list of chidren @child should be placed.
+ *
+ * Puts @child in the list of children for @mi at the location
+ * specified in @position. If there is not enough entires available
+ * then @child will be placed at the end of the list.
+ *
+ * Return value: Whether @child was added successfully.
+ */
gboolean
dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position)
{
@@ -737,17 +792,17 @@ dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem *
}
/**
- dbusmenu_menuitem_child_reorder:
- @base: The #DbusmenuMenuitem that has children needing realignment
- @child: The #DbusmenuMenuitem that is a child needing to be moved
- @position: The position in the list to place it in
-
- This function moves a child on the list of children. It is
- for a child that is already in the list, but simply needs a
- new location.
-
- Return value: Whether the move was successful.
-*/
+ * dbusmenu_menuitem_child_reorder:
+ * @mi: The #DbusmenuMenuitem that has children needing realignment
+ * @child: The #DbusmenuMenuitem that is a child needing to be moved
+ * @position: The position in the list to place it in
+ *
+ * This function moves a child on the list of children. It is
+ * for a child that is already in the list, but simply needs a
+ * new location.
+ *
+ * Return value: Whether the move was successful.
+ */
gboolean
dbusmenu_menuitem_child_reorder(DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position)
{
@@ -777,16 +832,16 @@ dbusmenu_menuitem_child_reorder(DbusmenuMenuitem * mi, DbusmenuMenuitem * child,
}
/**
- dbusmenu_menuitem_child_find:
- @mi: The #DbusmenuMenuitem who's children to look on
- @id: The ID of the child that we're looking for.
-
- Search the children of @mi to find one with the ID of @id.
- If it doesn't exist then we return #NULL.
-
- Return value: The menu item with the ID @id or #NULL if it
- can't be found.
-*/
+ * dbusmenu_menuitem_child_find:
+ * @mi: The #DbusmenuMenuitem who's children to look on
+ * @id: The ID of the child that we're looking for.
+ *
+ * Search the children of @mi to find one with the ID of @id.
+ * If it doesn't exist then we return #NULL.
+ *
+ * Return value: (transfer none): The menu item with the ID @id or #NULL if it
+ * can't be found.
+ */
DbusmenuMenuitem *
dbusmenu_menuitem_child_find (DbusmenuMenuitem * mi, gint id)
{
@@ -830,18 +885,18 @@ find_id_helper (gpointer in_mi, gpointer in_find_id)
}
/**
- dbusmenu_menuitem_find_id:
- @mi: #DbusmenuMenuitem at the top of the tree to search
- @id: ID of the #DbusmenuMenuitem to search for
-
- This function searchs the whole tree of children that
- are attached to @mi. This could be quite a few nodes, all
- the way down the tree. It is a depth first search.
-
- Return value: The #DbusmenuMenuitem with the ID of @id
- or #NULL if there isn't such a menu item in the tree
- represented by @mi.
-*/
+ * dbusmenu_menuitem_find_id:
+ * @mi: #DbusmenuMenuitem at the top of the tree to search
+ * @id: ID of the #DbusmenuMenuitem to search for
+ *
+ * This function searchs the whole tree of children that
+ * are attached to @mi. This could be quite a few nodes, all
+ * the way down the tree. It is a depth first search.
+ *
+ * Return value: (transfer none): The #DbusmenuMenuitem with the ID of @id
+ * or #NULL if there isn't such a menu item in the tree
+ * represented by @mi.
+ */
DbusmenuMenuitem *
dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, gint id)
{
@@ -858,239 +913,244 @@ dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, gint id)
}
/**
- dbusmenu_menuitem_property_set:
- @mi: The #DbusmenuMenuitem to set the property on.
- @property: Name of the property to set.
- @value: The value of the property.
-
- Takes the pair of @property and @value and places them as a
- property on @mi. If a property already exists by that name,
- then the value is set to the new value. If not, the property
- is added. If the value is changed or the property was previously
- unset then the signal #DbusmenuMenuitem::prop-changed will be
- emitted by this function.
-
- Return value: A boolean representing if the property value was set.
-*/
+ * dbusmenu_menuitem_property_set:
+ * @mi: The #DbusmenuMenuitem to set the property on.
+ * @property: Name of the property to set.
+ * @value: The value of the property.
+ *
+ * Takes the pair of @property and @value and places them as a
+ * property on @mi. If a property already exists by that name,
+ * then the value is set to the new value. If not, the property
+ * is added. If the value is changed or the property was previously
+ * unset then the signal #DbusmenuMenuitem::prop-changed will be
+ * emitted by this function.
+ *
+ * Return value: A boolean representing if the property value was set.
+ */
gboolean
dbusmenu_menuitem_property_set (DbusmenuMenuitem * mi, const gchar * property, const gchar * value)
{
- GValue val = {0};
- g_value_init(&val, G_TYPE_STRING);
- g_value_set_static_string(&val, value);
- return dbusmenu_menuitem_property_set_value(mi, property, &val);
+ GVariant * variant = NULL;
+ if (value != NULL) {
+ variant = g_variant_new_string(value);
+ }
+ return dbusmenu_menuitem_property_set_variant(mi, property, variant);
}
/**
- dbusmenu_menuitem_property_set_bool:
- @mi: The #DbusmenuMenuitem to set the property on.
- @property: Name of the property to set.
- @value: The value of the property.
-
- Takes a boolean @value and sets it on @property as a
- property on @mi. If a property already exists by that name,
- then the value is set to the new value. If not, the property
- is added. If the value is changed or the property was previously
- unset then the signal #DbusmenuMenuitem::prop-changed will be
- emitted by this function.
-
- Return value: A boolean representing if the property value was set.
-*/
+ * dbusmenu_menuitem_property_set_bool:
+ * @mi: The #DbusmenuMenuitem to set the property on.
+ * @property: Name of the property to set.
+ * @value: The value of the property.
+ *
+ * Takes a boolean @value and sets it on @property as a
+ * property on @mi. If a property already exists by that name,
+ * then the value is set to the new value. If not, the property
+ * is added. If the value is changed or the property was previously
+ * unset then the signal #DbusmenuMenuitem::prop-changed will be
+ * emitted by this function.
+ *
+ * Return value: A boolean representing if the property value was set.
+ */
gboolean
dbusmenu_menuitem_property_set_bool (DbusmenuMenuitem * mi, const gchar * property, const gboolean value)
{
- GValue val = {0};
- g_value_init(&val, G_TYPE_BOOLEAN);
- g_value_set_boolean(&val, value);
- return dbusmenu_menuitem_property_set_value(mi, property, &val);
+ GVariant * variant = g_variant_new("b", value);
+ return dbusmenu_menuitem_property_set_variant(mi, property, variant);
}
/**
- dbusmenu_menuitem_property_set_int:
- @mi: The #DbusmenuMenuitem to set the property on.
- @property: Name of the property to set.
- @value: The value of the property.
-
- Takes a boolean @value and sets it on @property as a
- property on @mi. If a property already exists by that name,
- then the value is set to the new value. If not, the property
- is added. If the value is changed or the property was previously
- unset then the signal #DbusmenuMenuitem::prop-changed will be
- emitted by this function.
-
- Return value: A boolean representing if the property value was set.
-*/
+ * dbusmenu_menuitem_property_set_int:
+ * @mi: The #DbusmenuMenuitem to set the property on.
+ * @property: Name of the property to set.
+ * @value: The value of the property.
+ *
+ * Takes a boolean @value and sets it on @property as a
+ * property on @mi. If a property already exists by that name,
+ * then the value is set to the new value. If not, the property
+ * is added. If the value is changed or the property was previously
+ * unset then the signal #DbusmenuMenuitem::prop-changed will be
+ * emitted by this function.
+ *
+ * Return value: A boolean representing if the property value was set.
+ */
gboolean
dbusmenu_menuitem_property_set_int (DbusmenuMenuitem * mi, const gchar * property, const gint value)
{
- GValue val = {0};
- g_value_init(&val, G_TYPE_INT);
- g_value_set_int(&val, value);
- return dbusmenu_menuitem_property_set_value(mi, property, &val);
+ GVariant * variant = g_variant_new("i", value);
+ return dbusmenu_menuitem_property_set_variant(mi, property, variant);
}
/**
- dbusmenu_menuitem_property_set:
- @mi: The #DbusmenuMenuitem to set the property on.
- @property: Name of the property to set.
- @value: The value of the property.
-
- Takes the pair of @property and @value and places them as a
- property on @mi. If a property already exists by that name,
- then the value is set to the new value. If not, the property
- is added. If the value is changed or the property was previously
- unset then the signal #DbusmenuMenuitem::prop-changed will be
- emitted by this function.
-
- Return value: A boolean representing if the property value was set.
-*/
+ * dbusmenu_menuitem_property_set_variant:
+ * @mi: The #DbusmenuMenuitem to set the property on.
+ * @property: Name of the property to set.
+ * @value: The value of the property.
+ *
+ * Takes the pair of @property and @value and places them as a
+ * property on @mi. If a property already exists by that name,
+ * then the value is set to the new value. If not, the property
+ * is added. If the value is changed or the property was previously
+ * unset then the signal #DbusmenuMenuitem::prop-changed will be
+ * emitted by this function.
+ *
+ * Return value: A boolean representing if the property value was set.
+ */
gboolean
-dbusmenu_menuitem_property_set_value (DbusmenuMenuitem * mi, const gchar * property, const GValue * value)
+dbusmenu_menuitem_property_set_variant (DbusmenuMenuitem * mi, const gchar * property, GVariant * value)
{
g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE);
g_return_val_if_fail(property != NULL, FALSE);
- g_return_val_if_fail(G_IS_VALUE(value), FALSE);
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
- /* g_debug("Setting a property. ID: %d Prop: %s Value: %s", priv->id, property, value); */
- #if 0
- gpointer lookup = g_hash_table_lookup(priv->properties, property);
- if (g_strcmp0((gchar *)lookup, value) == 0) {
- /* The value is the same as the value currently in the
- table so we don't really care. Just say everything's okay */
- return TRUE;
- }
- #endif
+ gboolean replaced = FALSE;
+ gpointer currentval = g_hash_table_lookup(priv->properties, property);
- gchar * lprop = g_strdup(property);
- GValue * lval = g_new0(GValue, 1);
- g_value_init(lval, G_VALUE_TYPE(value));
- g_value_copy(value, lval);
+ if (value != NULL) {
+ gchar * lprop = g_strdup(property);
+ g_variant_ref_sink(value);
- g_hash_table_replace(priv->properties, lprop, lval);
- #ifdef MASSIVEDEBUGGING
- gchar * valstr = g_strdup_value_contents(lval);
- g_debug("Menuitem %d (%s) signalling property '%s' changed to '%s'", ID(mi), LABEL(mi), property, g_utf8_strlen(valstr, 50) < 25 ? valstr : "<too long>");
- g_free(valstr);
- #endif
+ if (currentval == NULL || !g_variant_equal((GVariant*)currentval, value)) {
+ g_hash_table_replace(priv->properties, lprop, value);
+ replaced = TRUE;
+ }
+ } else {
+ if (currentval != NULL) {
+ g_hash_table_remove(priv->properties, property);
+ replaced = TRUE;
+ }
+ }
- g_signal_emit(G_OBJECT(mi), signals[PROPERTY_CHANGED], 0, lprop, lval, TRUE);
+ /* NOTE: The actual value is invalid at this point
+ becuse it has been unref'd when replaced in the hash
+ table. But the fact that there was a value is
+ the imporant part. */
+ if (currentval == NULL || replaced) {
+ g_signal_emit(G_OBJECT(mi), signals[PROPERTY_CHANGED], 0, property, value, TRUE);
+ }
return TRUE;
}
/**
- dbusmenu_menuitem_property_get:
- @mi: The #DbusmenuMenuitem to look for the property on.
- @property: The property to grab.
-
- Look up a property on @mi and return the value of it if
- it exits. #NULL will be returned if the property doesn't
- exist.
-
- Return value: A string with the value of the property
- that shouldn't be free'd. Or #NULL if the property
- is not set or is not a string.
-*/
+ * dbusmenu_menuitem_property_get:
+ * @mi: The #DbusmenuMenuitem to look for the property on.
+ * @property: The property to grab.
+ *
+ * Look up a property on @mi and return the value of it if
+ * it exits. #NULL will be returned if the property doesn't
+ * exist.
+ *
+ * Return value: (transfer none): A string with the value of the property
+ * that shouldn't be free'd. Or #NULL if the property
+ * is not set or is not a string.
+ */
const gchar *
dbusmenu_menuitem_property_get (DbusmenuMenuitem * mi, const gchar * property)
{
- const GValue * value = dbusmenu_menuitem_property_get_value(mi, property);
- if (value == NULL) return NULL;
- if (G_VALUE_TYPE(value) != G_TYPE_STRING) return NULL;
- return g_value_get_string(value);
+ GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
+ if (variant == NULL) return NULL;
+ if (!g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_STRING)) return NULL;
+ return g_variant_get_string(variant, NULL);
}
/**
- dbusmenu_menuitem_property_get_value:
- @mi: The #DbusmenuMenuitem to look for the property on.
- @property: The property to grab.
-
- Look up a property on @mi and return the value of it if
- it exits. #NULL will be returned if the property doesn't
- exist.
-
- Return value: A GValue for the property.
-*/
-const GValue *
-dbusmenu_menuitem_property_get_value (DbusmenuMenuitem * mi, const gchar * property)
+ * dbusmenu_menuitem_property_get_variant:
+ * @mi: The #DbusmenuMenuitem to look for the property on.
+ * @property: The property to grab.
+ *
+ * Look up a property on @mi and return the value of it if
+ * it exits. #NULL will be returned if the property doesn't
+ * exist.
+ *
+ * Return value: (transfer none): A GVariant for the property.
+ */
+GVariant *
+dbusmenu_menuitem_property_get_variant (DbusmenuMenuitem * mi, const gchar * property)
{
g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
g_return_val_if_fail(property != NULL, NULL);
DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
- return (const GValue *)g_hash_table_lookup(priv->properties, property);
+ return (GVariant *)g_hash_table_lookup(priv->properties, property);
}
/**
- dbusmenu_menuitem_property_get_bool:
- @mi: The #DbusmenuMenuitem to look for the property on.
- @property: The property to grab.
-
- Look up a property on @mi and return the value of it if
- it exits. Returns #FALSE if the property doesn't exist.
-
- Return value: The value of the property or #FALSE.
-*/
+ * dbusmenu_menuitem_property_get_bool:
+ * @mi: The #DbusmenuMenuitem to look for the property on.
+ * @property: The property to grab.
+ *
+ * Look up a property on @mi and return the value of it if
+ * it exits. Returns #FALSE if the property doesn't exist.
+ *
+ * Return value: The value of the property or #FALSE.
+ */
gboolean
dbusmenu_menuitem_property_get_bool (DbusmenuMenuitem * mi, const gchar * property)
{
- const GValue * value = dbusmenu_menuitem_property_get_value(mi, property);
- if (value == NULL) return FALSE;
- if (G_VALUE_TYPE(value) != G_TYPE_BOOLEAN) {
- if (g_value_type_transformable(G_VALUE_TYPE(value), G_TYPE_BOOLEAN)) {
- GValue boolval = {0};
- g_value_init(&boolval, G_TYPE_BOOLEAN);
- g_value_transform(value, &boolval);
- return g_value_get_boolean(&boolval);
+ GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
+ if (variant == NULL) return FALSE;
+
+ if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_BOOLEAN)) {
+ return g_variant_get_boolean(variant);
+ }
+
+ if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_STRING)) {
+ const gchar * string = g_variant_get_string(variant, NULL);
+
+ if (!g_strcmp0(string, "TRUE") || !g_strcmp0(string, "true") || !g_strcmp0(string, "True")) {
+ return TRUE;
} else {
return FALSE;
}
}
- return g_value_get_boolean(value);
+
+ g_warning("Property '%s' has been requested as an boolean but is not one.", property);
+ return FALSE;
}
/**
- dbusmenu_menuitem_property_get_int:
- @mi: The #DbusmenuMenuitem to look for the property on.
- @property: The property to grab.
-
- Look up a property on @mi and return the value of it if
- it exits. Returns zero if the property doesn't exist.
-
- Return value: The value of the property or zero.
-*/
+ * dbusmenu_menuitem_property_get_int:
+ * @mi: The #DbusmenuMenuitem to look for the property on.
+ * @property: The property to grab.
+ *
+ * Look up a property on @mi and return the value of it if
+ * it exits. Returns zero if the property doesn't exist.
+ *
+ * Return value: The value of the property or zero.
+ */
gint
dbusmenu_menuitem_property_get_int (DbusmenuMenuitem * mi, const gchar * property)
{
- const GValue * value = dbusmenu_menuitem_property_get_value(mi, property);
- if (value == NULL) return 0;
- if (G_VALUE_TYPE(value) != G_TYPE_INT) {
- if (g_value_type_transformable(G_VALUE_TYPE(value), G_TYPE_INT)) {
- GValue intval = {0};
- g_value_init(&intval, G_TYPE_INT);
- g_value_transform(value, &intval);
- return g_value_get_int(&intval);
- } else {
- return 0;
- }
+ GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
+ if (variant == NULL) return 0;
+
+ if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_INT32)) {
+ return g_variant_get_int32(variant);
}
- return g_value_get_int(value);
-}
+ if (g_variant_type_equal(g_variant_get_type(variant), G_VARIANT_TYPE_STRING)) {
+ const gchar * string = g_variant_get_string(variant, NULL);
+ return atoi(string);
+ }
-/**
- dbusmenu_menuitem_property_exit:
- @mi: The #DbusmenuMenuitem to look for the property on.
- @property: The property to look for.
+ g_warning("Property '%s' has been requested as an int but is not one.", property);
+ return 0;
+}
- Checkes to see if a particular property exists on @mi and
- returns #TRUE if so.
- Return value: A boolean checking to see if the property is available
-*/
+/**
+ * dbusmenu_menuitem_property_exit:
+ * @mi: The #DbusmenuMenuitem to look for the property on.
+ * @property: The property to look for.
+ *
+ * Checkes to see if a particular property exists on @mi and
+ * returns #TRUE if so.
+ *
+ * Return value: A boolean checking to see if the property is available
+ */
gboolean
dbusmenu_menuitem_property_exist (DbusmenuMenuitem * mi, const gchar * property)
{
@@ -1105,12 +1165,12 @@ dbusmenu_menuitem_property_exist (DbusmenuMenuitem * mi, const gchar * property)
}
/**
- dbusmenu_menuitem_property_remove:
- @mi: The #DbusmenuMenuitem to remove the property on.
- @property: The property to look for.
-
- Removes a property from the menuitem.
-*/
+ * dbusmenu_menuitem_property_remove:
+ * @mi: The #DbusmenuMenuitem to remove the property on.
+ * @property: The property to look for.
+ *
+ * Removes a property from the menuitem.
+ */
void
dbusmenu_menuitem_property_remove (DbusmenuMenuitem * mi, const gchar * property)
{
@@ -1125,15 +1185,16 @@ dbusmenu_menuitem_property_remove (DbusmenuMenuitem * mi, const gchar * property
}
/**
- dbusmenu_menuitem_properties_list:
- @mi: #DbusmenuMenuitem to list the properties on
-
- This functiong gets a list of the names of all the properties
- that are set on this menu item. This data on the list is owned
- by the menuitem but the list is not and should be freed using
- g_list_free() when the calling function is done with it.
-
- Return value: A list of strings or NULL if there are none.
+ * dbusmenu_menuitem_properties_list:
+ * @mi: #DbusmenuMenuitem to list the properties on
+ *
+ * This functiong gets a list of the names of all the properties
+ * that are set on this menu item. This data on the list is owned
+ * by the menuitem but the list is not and should be freed using
+ * g_list_free() when the calling function is done with it.
+ *
+ * Return value: (transfer container): A list of strings or NULL if there are
+ * none.
*/
GList *
dbusmenu_menuitem_properties_list (DbusmenuMenuitem * mi)
@@ -1144,32 +1205,38 @@ dbusmenu_menuitem_properties_list (DbusmenuMenuitem * mi)
return g_hash_table_get_keys(priv->properties);
}
+/* Copy the keys and make references to the variants that are
+ in the new table. They'll be free'd and unref'd when the
+ Hashtable gets destroyed. */
static void
copy_helper (gpointer in_key, gpointer in_value, gpointer in_data)
{
GHashTable * table = (GHashTable *)in_data;
- g_hash_table_insert(table, in_key, in_value);
+ gchar * key = (gchar *)in_key;
+ GVariant * value = (GVariant *)in_value;
+ g_variant_ref_sink(value);
+ g_hash_table_insert(table, g_strdup(key), value);
return;
}
/**
- dbusmenu_menuitem_properties_copy:
- @mi: #DbusmenuMenuitem that we're interested in the properties of
-
- This function takes the properties of a #DbusmenuMenuitem
- and puts them into a #GHashTable that is referenced by the
- key of a string and has the value of a string. The hash
- table may not have any entries if there aren't any or there
- is an error in processing. It is the caller's responsibility
- to destroy the created #GHashTable.
-
- Return value: A brand new #GHashTable that contains all of the
- properties that are on this #DbusmenuMenuitem @mi.
+ * dbusmenu_menuitem_properties_copy:
+ * @mi: #DbusmenuMenuitem that we're interested in the properties of
+ *
+ * This function takes the properties of a #DbusmenuMenuitem
+ * and puts them into a #GHashTable that is referenced by the
+ * key of a string and has the value of a string. The hash
+ * table may not have any entries if there aren't any or there
+ * is an error in processing. It is the caller's responsibility
+ * to destroy the created #GHashTable.
+ *
+ * Return value: (transfer full): A brand new #GHashTable that contains all of
+ * theroperties that are on this #DbusmenuMenuitem @mi.
*/
GHashTable *
dbusmenu_menuitem_properties_copy (DbusmenuMenuitem * mi)
{
- GHashTable * ret = g_hash_table_new(g_str_hash, g_str_equal);
+ GHashTable * ret = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, _g_variant_unref);
g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), ret);
@@ -1179,16 +1246,57 @@ dbusmenu_menuitem_properties_copy (DbusmenuMenuitem * mi)
return ret;
}
+/* Looks at each value in the hashtable and tries to convert it
+ into a variant and add it to our variant builder */
+static void
+variant_helper (gpointer in_key, gpointer in_value, gpointer user_data)
+{
+ GVariant * value = g_variant_new_dict_entry(g_variant_new_string((gchar *)in_key),
+ g_variant_new_variant((GVariant *)in_value));
+ g_variant_builder_add_value((GVariantBuilder *)user_data, value);
+ return;
+}
+
/**
- dbusmenu_menuitem_set_root:
- @mi: #DbusmenuMenuitem to set whether it's root
- @root: Whether @mi is a root node or not
+ * dbusmenu_menuitem_properties_variant:
+ * @mi: #DbusmenuMenuitem to get properties from
+ *
+ * Grabs the properties of the menuitem as a GVariant with the
+ * type "a{sv}".
+ *
+ * Return Value: (transfer full): A GVariant of type "a{sv}" or NULL on error.
+ */
+GVariant *
+dbusmenu_menuitem_properties_variant (DbusmenuMenuitem * mi, const gchar ** properties)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
+
+ DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi);
- This function sets the internal value of whether this is a
- root node or not.
+ GVariant * final_variant = NULL;
- Return value: None
-*/
+ if (g_hash_table_size(priv->properties) > 0) {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ g_hash_table_foreach(priv->properties, variant_helper, &builder);
+
+ final_variant = g_variant_builder_end(&builder);
+ }
+
+ return final_variant;
+}
+
+/**
+ * dbusmenu_menuitem_set_root:
+ * @mi: #DbusmenuMenuitem to set whether it's root
+ * @root: Whether @mi is a root node or not
+ *
+ * This function sets the internal value of whether this is a
+ * root node or not.
+ *
+ * Return value: None
+ */
void
dbusmenu_menuitem_set_root (DbusmenuMenuitem * mi, gboolean root)
{
@@ -1199,14 +1307,14 @@ dbusmenu_menuitem_set_root (DbusmenuMenuitem * mi, gboolean root)
}
/**
- dbusmenu_menuitem_get_root:
- @mi: #DbusmenuMenuitem to see whether it's root
-
- This function returns the internal value of whether this is a
- root node or not.
-
- Return value: #TRUE if this is a root node
-*/
+ * dbusmenu_menuitem_get_root:
+ * @mi: #DbusmenuMenuitem to see whether it's root
+ *
+ * This function returns the internal value of whether this is a
+ * root node or not.
+ *
+ * Return value: #TRUE if this is a root node
+ */
gboolean
dbusmenu_menuitem_get_root (DbusmenuMenuitem * mi)
{
@@ -1217,38 +1325,62 @@ dbusmenu_menuitem_get_root (DbusmenuMenuitem * mi)
/**
- dbusmenu_menuitem_buildxml:
- @mi: #DbusmenuMenuitem to represent in XML
- @array: (element-type utf8): A list of string that will be turned into an XML file
-
- This function will add strings to the array @array. It will put
- at least one entry if this menu item has no children. If it has
- children it will put two for this entry, one representing the
- start tag and one that is a closing tag. It will allow it's
- children to place their own tags in the array in between those two.
+ * dbusmenu_menuitem_buildvariant:
+ * @mi: #DbusmenuMenuitem to represent in a variant
+ * @properties: (element-type utf8): A list of string that will be put into
+ * a variant
+ *
+ * This function will put at least one entry if this menu item has no children.
+ * If it has children it will put two for this entry, one representing the
+ * start tag and one that is a closing tag. It will allow it's
+ * children to place their own tags in the array in between those two.
+ *
+ * Return value: (transfer full): Variant representing @properties
*/
-void
-dbusmenu_menuitem_buildxml (DbusmenuMenuitem * mi, GPtrArray * array)
+GVariant *
+dbusmenu_menuitem_build_variant (DbusmenuMenuitem * mi, const gchar ** properties, gint recurse)
{
- g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL);
gint id = 0;
if (!dbusmenu_menuitem_get_root(mi)) {
id = dbusmenu_menuitem_get_id(mi);
}
+ /* This is the tuple that'll build up being a representation of
+ this entry */
+ GVariantBuilder tupleb;
+ g_variant_builder_init(&tupleb, G_VARIANT_TYPE_TUPLE);
+
+ /* Add our ID */
+ g_variant_builder_add_value(&tupleb, g_variant_new_int32(id));
+
+ /* Figure out the properties */
+ GVariant * props = dbusmenu_menuitem_properties_variant(mi, properties);
+ if (props != NULL) {
+ g_variant_builder_add_value(&tupleb, props);
+ } else {
+ g_variant_builder_add_value(&tupleb, g_variant_parse(G_VARIANT_TYPE("a{sv}"), "[ ]", NULL, NULL, NULL));
+ }
+
+ /* Pillage the children */
GList * children = dbusmenu_menuitem_get_children(mi);
- if (children == NULL) {
- g_ptr_array_add(array, g_strdup_printf("<menu id=\"%d\"/>", id));
+ if (children == NULL && recurse != 0) {
+ g_variant_builder_add_value(&tupleb, g_variant_new_array(G_VARIANT_TYPE_VARIANT, NULL, 0));
} else {
- g_ptr_array_add(array, g_strdup_printf("<menu id=\"%d\">", id));
+ GVariantBuilder childrenbuilder;
+ g_variant_builder_init(&childrenbuilder, G_VARIANT_TYPE_ARRAY);
+
for ( ; children != NULL; children = children->next) {
- dbusmenu_menuitem_buildxml(DBUSMENU_MENUITEM(children->data), array);
+ GVariant * child = dbusmenu_menuitem_build_variant(DBUSMENU_MENUITEM(children->data), properties, recurse - 1);
+
+ g_variant_builder_add_value(&childrenbuilder, g_variant_new_variant(child));
}
- g_ptr_array_add(array, g_strdup("</menu>"));
+
+ g_variant_builder_add_value(&tupleb, g_variant_builder_end(&childrenbuilder));
}
- return;
+ return g_variant_builder_end(&tupleb);
}
typedef struct {
@@ -1264,15 +1396,15 @@ foreach_helper (gpointer data, gpointer user_data)
}
/**
- dbusmenu_menuitem_foreach:
- @mi: The #DbusmenItem to start from
- @func: Function to call on every node in the tree
- @data: (closure): User data to pass to the function
-
- This calls the function @func on this menu item and all
- of the children of this item. And their children. And
- their children. And... you get the point. It will get
- called on the whole tree.
+ * dbusmenu_menuitem_foreach:
+ * @mi: The #DbusmenItem to start from
+ * @func: Function to call on every node in the tree
+ * @data: (closure): User data to pass to the function
+ *
+ * This calls the function @func on this menu item and all
+ * of the children of this item. And their children. And
+ * their children. And... you get the point. It will get
+ * called on the whole tree.
*/
void
dbusmenu_menuitem_foreach (DbusmenuMenuitem * mi, void (*func) (DbusmenuMenuitem * mi, gpointer data), gpointer data)
@@ -1288,26 +1420,26 @@ dbusmenu_menuitem_foreach (DbusmenuMenuitem * mi, void (*func) (DbusmenuMenuitem
}
/**
- dbusmenu_menuitem_handle_event:
- @mi: The #DbusmenuMenuitem to send the signal on.
- @name: The name of the signal
- @value: A value that could be set for the event
- @timestamp: The timestamp of when the event happened
-
- This function is called to create an event. It is likely
- to be overrided by subclasses. The default menu item
- will respond to the activate signal and do:
-
- Emits the #DbusmenuMenuitem::item-activate signal on this
- menu item. Called by server objects when they get the
- appropriate DBus signals from the client.
-
- If you subclass this function you should really think
- about calling the parent function unless you have a good
- reason not to.
+ * dbusmenu_menuitem_handle_event:
+ * @mi: The #DbusmenuMenuitem to send the signal on.
+ * @name: The name of the signal
+ * @variant: A value that could be set for the event
+ * @timestamp: The timestamp of when the event happened
+ *
+ * This function is called to create an event. It is likely
+ * to be overrided by subclasses. The default menu item
+ * will respond to the activate signal and do:
+ *
+ * Emits the #DbusmenuMenuitem::item-activate signal on this
+ * menu item. Called by server objects when they get the
+ * appropriate DBus signals from the client.
+ *
+ * If you subclass this function you should really think
+ * about calling the parent function unless you have a good
+ * reason not to.
*/
void
-dbusmenu_menuitem_handle_event (DbusmenuMenuitem * mi, const gchar * name, const GValue * value, guint timestamp)
+dbusmenu_menuitem_handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp)
{
g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
#ifdef MASSIVEDEBUGGING
@@ -1316,24 +1448,24 @@ dbusmenu_menuitem_handle_event (DbusmenuMenuitem * mi, const gchar * name, const
DbusmenuMenuitemClass * class = DBUSMENU_MENUITEM_GET_CLASS(mi);
if (class->handle_event != NULL) {
- return class->handle_event(mi, name, value, timestamp);
+ return class->handle_event(mi, name, variant, timestamp);
}
return;
}
/**
- dbusmenu_menuitem_send_about_to_show:
- @mi: The #DbusmenuMenuitem to send the signal on.
- @cb: Callback to call when the call has returned.
- @cb_data: (closure): Data to pass to the callback.
-
- This function is used to send the even that the submenu
- of this item is about to be shown. Callers to this event
- should delay showing the menu until their callback is
- called if possible.
-*/
+ * dbusmenu_menuitem_send_about_to_show:
+ * @mi: The #DbusmenuMenuitem to send the signal on.
+ * @cb: Callback to call when the call has returned.
+ * @cb_data: (closure): Data to pass to the callback.
+ *
+ * This function is used to send the even that the submenu
+ * of this item is about to be shown. Callers to this event
+ * should delay showing the menu until their callback is
+ * 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 +1481,32 @@ 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;
+}
+
+/* Checks to see if the value of this property is unique or just the
+ default value. */
+/* TODO: Implement this */
+gboolean
+dbusmenu_menuitem_property_is_default (DbusmenuMenuitem * mi, const gchar * property)
+{
+ /* No defaults system yet */
+ return FALSE;
+}
diff --git a/libdbusmenu-glib/menuitem.h b/libdbusmenu-glib/menuitem.h
index 39d257e..afd6084 100644
--- a/libdbusmenu-glib/menuitem.h
+++ b/libdbusmenu-glib/menuitem.h
@@ -49,6 +49,8 @@ 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_SIGNAL_ABOUT_TO_SHOW "about-to-show"
#define DBUSMENU_MENUITEM_PROP_TYPE "type"
#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible"
@@ -58,7 +60,8 @@ 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_CHILD_DISPLAY "child-display"
+#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut"
+#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display"
#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark"
#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio"
@@ -69,8 +72,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 +95,9 @@ typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
struct _DbusmenuMenuitem
{
GObject parent;
+
+ /*< Private >*/
+ DbusmenuMenuitemPrivate * priv;
};
/**
@@ -98,14 +111,15 @@ struct _DbusmenuMenuitem
typedef void (*dbusmenu_menuitem_about_to_show_cb) (DbusmenuMenuitem * mi, gpointer user_data);
/**
- * dbusmenu_menuitem_buildxml_slot_t:
+ * dbusmenu_menuitem_buildvariant_slot_t:
* @mi: (in): Menu item that should be built from
- * @stringarray: (inout) (transfer none) (array) (element-type utf8): An array of strings that can be combined into an XML file.
*
* This is the function that is called to represent this menu item
- * as an XML fragment. Should call it's own children.
+ * as a variant. Should call it's own children.
+ *
+ * Return value: (transfer full) A variant representing this item and it's children
*/
-typedef void (*dbusmenu_menuitem_buildxml_slot_t) (DbusmenuMenuitem * mi, GPtrArray* stringarray);
+typedef GVariant * (*dbusmenu_menuitem_buildvariant_slot_t) (DbusmenuMenuitem * mi, gchar ** properties);
/**
* DbusmenuMenuitemClass:
@@ -115,13 +129,18 @@ typedef void (*dbusmenu_menuitem_buildxml_slot_t) (DbusmenuMenuitem * mi, GPtrAr
* @child_removed: Slot for #DbusmenuMenuitem::child-removed.
* @child_moved: Slot for #DbusmenuMenuitem::child-moved.
* @realized: Slot for #DbusmenuMenuitem::realized.
+ * @about_to_show: Slot for #DbusmenuMenuitem::about-to-show.
* @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
@@ -129,7 +148,7 @@ struct _DbusmenuMenuitemClass
GObjectClass parent_class;
/* Signals */
- void (*property_changed) (gchar * property, GValue * value);
+ void (*property_changed) (gchar * property, GVariant * value);
void (*item_activated) (guint timestamp);
void (*child_added) (DbusmenuMenuitem * child, guint position);
void (*child_removed) (DbusmenuMenuitem * child);
@@ -137,14 +156,20 @@ struct _DbusmenuMenuitemClass
void (*realized) (void);
/* 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);
+ dbusmenu_menuitem_buildvariant_slot_t buildvariant;
+ void (*handle_event) (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp);
+ 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);
+ gboolean (*about_to_show) (void);
+
+ /*< 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);
@@ -167,11 +192,11 @@ DbusmenuMenuitem * dbusmenu_menuitem_child_find (DbusmenuMenuitem * mi, gint id)
DbusmenuMenuitem * dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, gint id);
gboolean dbusmenu_menuitem_property_set (DbusmenuMenuitem * mi, const gchar * property, const gchar * value);
-gboolean dbusmenu_menuitem_property_set_value (DbusmenuMenuitem * mi, const gchar * property, const GValue * value);
+gboolean dbusmenu_menuitem_property_set_variant (DbusmenuMenuitem * mi, const gchar * property, GVariant * value);
gboolean dbusmenu_menuitem_property_set_bool (DbusmenuMenuitem * mi, const gchar * property, const gboolean value);
gboolean dbusmenu_menuitem_property_set_int (DbusmenuMenuitem * mi, const gchar * property, const gint value);
const gchar * dbusmenu_menuitem_property_get (DbusmenuMenuitem * mi, const gchar * property);
-const GValue * dbusmenu_menuitem_property_get_value (DbusmenuMenuitem * mi, const gchar * property);
+GVariant * dbusmenu_menuitem_property_get_variant (DbusmenuMenuitem * mi, const gchar * property);
gboolean dbusmenu_menuitem_property_get_bool (DbusmenuMenuitem * mi, const gchar * property);
gint dbusmenu_menuitem_property_get_int (DbusmenuMenuitem * mi, const gchar * property);
gboolean dbusmenu_menuitem_property_exist (DbusmenuMenuitem * mi, const gchar * property);
@@ -183,8 +208,10 @@ void dbusmenu_menuitem_set_root (DbusmenuMenuitem * mi, gboolean root);
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_handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp);
+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..08ebf93 100644
--- a/libdbusmenu-glib/server-marshal.list
+++ b/libdbusmenu-glib/server-marshal.list
@@ -1,2 +1,3 @@
-VOID: INT, STRING, POINTER
+VOID: INT, STRING, VARIANT
VOID: UINT, INT
+VOID: INT, UINT
diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c
index 0da66cc..aa39991 100644
--- a/libdbusmenu-glib/server.c
+++ b/libdbusmenu-glib/server.c
@@ -30,41 +30,43 @@ License version 3 and version 2.1 along with this program. If not, see
#include "config.h"
#endif
+#include <gio/gio.h>
+
#include "menuitem-private.h"
#include "server.h"
#include "server-marshal.h"
-/* 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_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);
+#include "dbus-menu-clean.xml.h"
-#include "dbusmenu-server.h"
+static void layout_update_signal (DbusmenuServer * server);
-#define DBUSMENU_VERSION_NUMBER 2
+#define DBUSMENU_VERSION_NUMBER 2
+#define DBUSMENU_INTERFACE "com.canonical.dbusmenu"
/* Privates, I'll show you mine... */
-typedef struct _DbusmenuServerPrivate DbusmenuServerPrivate;
-
struct _DbusmenuServerPrivate
{
DbusmenuMenuitem * root;
gchar * dbusobject;
gint layout_revision;
+ guint layout_idle;
+
+ GDBusConnection * bus;
+ GCancellable * bus_lookup;
+ guint dbus_registration;
+
+ GArray * prop_array;
+ guint property_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
};
@@ -84,22 +86,111 @@ enum {
INVALID_PROPERTY_NAME,
UNKNOWN_DBUS_ERROR,
NOT_IMPLEMENTED,
+ NO_VALID_LAYOUT,
LAST_ERROR
};
+/* Method Table */
+typedef void (*MethodTableFunc) (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation);
+
+typedef struct _method_table_t method_table_t;
+struct _method_table_t {
+ const gchar * interned_name;
+ MethodTableFunc func;
+};
+
+enum {
+ METHOD_GET_LAYOUT = 0,
+ METHOD_GET_GROUP_PROPERTIES,
+ METHOD_GET_CHILDREN,
+ METHOD_GET_PROPERTY,
+ METHOD_GET_PROPERTIES,
+ METHOD_EVENT,
+ METHOD_ABOUT_TO_SHOW,
+ /* Counter, do not remove! */
+ METHOD_COUNT
+};
+
/* Prototype */
-static void dbusmenu_server_class_init (DbusmenuServerClass *class);
-static void dbusmenu_server_init (DbusmenuServer *self);
-static void dbusmenu_server_dispose (GObject *object);
-static void dbusmenu_server_finalize (GObject *object);
-static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec);
-static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
-static void menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * value, DbusmenuServer * server);
-static void menuitem_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint pos, DbusmenuServer * server);
-static void menuitem_child_removed (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, DbusmenuServer * server);
-static void menuitem_signals_create (DbusmenuMenuitem * mi, gpointer data);
-static void menuitem_signals_remove (DbusmenuMenuitem * mi, gpointer data);
-static GQuark error_quark (void);
+static void dbusmenu_server_class_init (DbusmenuServerClass *class);
+static void dbusmenu_server_init (DbusmenuServer *self);
+static void dbusmenu_server_dispose (GObject *object);
+static void dbusmenu_server_finalize (GObject *object);
+static void set_property (GObject * obj,
+ guint id,
+ const GValue * value,
+ GParamSpec * pspec);
+static void get_property (GObject * obj,
+ guint id,
+ GValue * value,
+ GParamSpec * pspec);
+static void register_object (DbusmenuServer * server);
+static void bus_got_cb (GObject * obj,
+ GAsyncResult * result,
+ gpointer user_data);
+static void bus_method_call (GDBusConnection * connection,
+ const gchar * sender,
+ const gchar * path,
+ const gchar * interface,
+ const gchar * method,
+ GVariant * params,
+ GDBusMethodInvocation * invocation,
+ gpointer user_data);
+static GVariant * bus_get_prop (GDBusConnection * connection,
+ const gchar * sender,
+ const gchar * path,
+ const gchar * interface,
+ const gchar * property,
+ GError ** error,
+ gpointer user_data);
+static void menuitem_property_changed (DbusmenuMenuitem * mi,
+ gchar * property,
+ GVariant * variant,
+ DbusmenuServer * server);
+static void menuitem_child_added (DbusmenuMenuitem * parent,
+ DbusmenuMenuitem * child,
+ guint pos,
+ DbusmenuServer * server);
+static void menuitem_child_removed (DbusmenuMenuitem * parent,
+ DbusmenuMenuitem * child,
+ DbusmenuServer * server);
+static void menuitem_signals_create (DbusmenuMenuitem * mi,
+ gpointer data);
+static void menuitem_signals_remove (DbusmenuMenuitem * mi,
+ gpointer data);
+static GQuark error_quark (void);
+static void prop_array_teardown (GArray * prop_array);
+static void bus_get_layout (DbusmenuServer * server,
+ GVariant * params,
+ GDBusMethodInvocation * invocation);
+static void bus_get_group_properties (DbusmenuServer * server,
+ GVariant * params,
+ GDBusMethodInvocation * invocation);
+static void bus_get_children (DbusmenuServer * server,
+ GVariant * params,
+ GDBusMethodInvocation * invocation);
+static void bus_get_property (DbusmenuServer * server,
+ GVariant * params,
+ GDBusMethodInvocation * invocation);
+static void bus_get_properties (DbusmenuServer * server,
+ GVariant * params,
+ GDBusMethodInvocation * invocation);
+static void bus_event (DbusmenuServer * server,
+ GVariant * params,
+ GDBusMethodInvocation * invocation);
+static void bus_about_to_show (DbusmenuServer * server,
+ GVariant * params,
+ GDBusMethodInvocation * invocation);
+
+/* Globals */
+static GDBusNodeInfo * dbusmenu_node_info = NULL;
+static GDBusInterfaceInfo * dbusmenu_interface_info = NULL;
+static const GDBusInterfaceVTable dbusmenu_interface_table = {
+ method_call: bus_method_call,
+ get_property: bus_get_prop,
+ set_property: NULL /* No properties that can be set */
+};
+static method_table_t dbusmenu_method_table[METHOD_COUNT];
G_DEFINE_TYPE (DbusmenuServer, dbusmenu_server, G_TYPE_OBJECT);
@@ -130,8 +221,8 @@ dbusmenu_server_class_init (DbusmenuServerClass *class)
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(DbusmenuServerClass, id_prop_update),
NULL, NULL,
- _dbusmenu_server_marshal_VOID__INT_STRING_POINTER,
- G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_VALUE);
+ _dbusmenu_server_marshal_VOID__INT_STRING_VARIANT,
+ G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_VARIANT);
/**
DbusmenuServer::id-update:
@arg0: The #DbusmenuServer emitting the signal.
@@ -165,12 +256,28 @@ 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,
g_param_spec_string(DBUSMENU_SERVER_PROP_DBUS_OBJECT, "DBus object path",
"The object that represents this set of menus on DBus",
- "/org/ayatana/dbusmenu",
+ "/com/canonical/dbusmenu",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ROOT_NODE,
g_param_spec_object(DBUSMENU_SERVER_PROP_ROOT_NODE, "Root menu node",
@@ -183,7 +290,45 @@ dbusmenu_server_class_init (DbusmenuServerClass *class)
DBUSMENU_VERSION_NUMBER, DBUSMENU_VERSION_NUMBER, DBUSMENU_VERSION_NUMBER,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
- dbus_g_object_type_install_info(DBUSMENU_TYPE_SERVER, &dbus_glib__dbusmenu_server_object_info);
+ if (dbusmenu_node_info == NULL) {
+ GError * error = NULL;
+
+ dbusmenu_node_info = g_dbus_node_info_new_for_xml(dbus_menu_clean_xml, &error);
+ if (error != NULL) {
+ g_error("Unable to parse DBusmenu Interface description: %s", error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (dbusmenu_interface_info == NULL) {
+ dbusmenu_interface_info = g_dbus_node_info_lookup_interface(dbusmenu_node_info, DBUSMENU_INTERFACE);
+
+ if (dbusmenu_interface_info == NULL) {
+ g_error("Unable to find interface '" DBUSMENU_INTERFACE "'");
+ }
+ }
+
+ /* Building our Method table :( */
+ dbusmenu_method_table[METHOD_GET_LAYOUT].interned_name = g_intern_static_string("GetLayout");
+ dbusmenu_method_table[METHOD_GET_LAYOUT].func = bus_get_layout;
+
+ dbusmenu_method_table[METHOD_GET_GROUP_PROPERTIES].interned_name = g_intern_static_string("GetGroupProperties");
+ dbusmenu_method_table[METHOD_GET_GROUP_PROPERTIES].func = bus_get_group_properties;
+
+ dbusmenu_method_table[METHOD_GET_CHILDREN].interned_name = g_intern_static_string("GetChildren");
+ dbusmenu_method_table[METHOD_GET_CHILDREN].func = bus_get_children;
+
+ dbusmenu_method_table[METHOD_GET_PROPERTY].interned_name = g_intern_static_string("GetProperty");
+ dbusmenu_method_table[METHOD_GET_PROPERTY].func = bus_get_property;
+
+ dbusmenu_method_table[METHOD_GET_PROPERTIES].interned_name = g_intern_static_string("GetProperties");
+ dbusmenu_method_table[METHOD_GET_PROPERTIES].func = bus_get_properties;
+
+ dbusmenu_method_table[METHOD_EVENT].interned_name = g_intern_static_string("Event");
+ dbusmenu_method_table[METHOD_EVENT].func = bus_event;
+
+ dbusmenu_method_table[METHOD_ABOUT_TO_SHOW].interned_name = g_intern_static_string("AboutToShow");
+ dbusmenu_method_table[METHOD_ABOUT_TO_SHOW].func = bus_about_to_show;
return;
}
@@ -191,11 +336,17 @@ 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;
+ priv->bus = NULL;
+ priv->bus_lookup = NULL;
+ priv->dbus_registration = 0;
return;
}
@@ -205,11 +356,47 @@ dbusmenu_server_dispose (GObject *object)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(object);
+ if (priv->layout_idle != 0) {
+ g_source_remove(priv->layout_idle);
+ priv->layout_idle = 0;
+ }
+
+ if (priv->property_idle != 0) {
+ g_source_remove(priv->property_idle);
+ priv->property_idle = 0;
+ }
+
+ if (priv->prop_array != NULL) {
+ prop_array_teardown(priv->prop_array);
+ priv->prop_array = NULL;
+ }
+
if (priv->root != NULL) {
dbusmenu_menuitem_foreach(priv->root, menuitem_signals_remove, object);
g_object_unref(priv->root);
}
+ if (priv->dbus_registration != 0) {
+ g_dbus_connection_unregister_object(priv->bus, priv->dbus_registration);
+ priv->dbus_registration = 0;
+ }
+
+ if (priv->bus != NULL) {
+ g_object_unref(priv->bus);
+ priv->bus = NULL;
+ }
+
+ if (priv->bus_lookup != NULL) {
+ if (!g_cancellable_is_cancelled(priv->bus_lookup)) {
+ /* Note, this may case the async function to run at
+ some point in the future. That's okay, it'll get an
+ error, but just FYI */
+ g_cancellable_cancel(priv->bus_lookup);
+ }
+ g_object_unref(priv->bus_lookup);
+ priv->bus_lookup = NULL;
+ }
+
G_OBJECT_CLASS (dbusmenu_server_parent_class)->dispose (object);
return;
}
@@ -230,15 +417,31 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
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);
+
+ if (priv->bus == NULL) {
+ if (priv->bus_lookup == NULL) {
+ priv->bus_lookup = g_cancellable_new();
+ g_return_if_fail(priv->bus_lookup != NULL);
+ }
+
+ g_bus_get(G_BUS_TYPE_SESSION, priv->bus_lookup, bus_got_cb, obj);
+ } else {
+ register_object(DBUSMENU_SERVER(obj));
+ }
break;
case PROP_ROOT_NODE:
if (priv->root != NULL) {
dbusmenu_menuitem_foreach(priv->root, menuitem_signals_remove, obj);
dbusmenu_menuitem_set_root(priv->root, FALSE);
+
+ GList * properties = dbusmenu_menuitem_properties_list(priv->root);
+ GList * iter;
+ for (iter = properties; iter != NULL; iter = g_list_next(iter)) {
+ gchar * property = (gchar *)iter->data;
+ menuitem_property_changed(priv->root, property, NULL, DBUSMENU_SERVER(obj));
+ }
+ g_list_free(properties);
+
g_object_unref(G_OBJECT(priv->root));
priv->root = NULL;
}
@@ -247,11 +450,18 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
g_object_ref(G_OBJECT(priv->root));
dbusmenu_menuitem_set_root(priv->root, TRUE);
dbusmenu_menuitem_foreach(priv->root, menuitem_signals_create, obj);
+
+ GList * properties = dbusmenu_menuitem_properties_list(priv->root);
+ GList * iter;
+ for (iter = properties; iter != NULL; iter = g_list_next(iter)) {
+ gchar * property = (gchar *)iter->data;
+ menuitem_property_changed(priv->root, property, dbusmenu_menuitem_property_get_variant(priv->root, property), DBUSMENU_SERVER(obj));
+ }
+ g_list_free(properties);
} 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();
@@ -262,17 +472,6 @@ set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
}
static void
-xmlarray_foreach_free (gpointer arrayentry, gpointer userdata)
-{
- if (arrayentry != NULL) {
- /* g_debug("Freeing pointer: %s", (gchar *)arrayentry); */
- g_free(arrayentry);
- }
-
- return;
-}
-
-static void
get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(obj);
@@ -295,10 +494,424 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
return;
}
+/* Register the object on the dbus bus */
+static void
+register_object (DbusmenuServer * server)
+{
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+
+ /* Object info */
+ g_return_if_fail(priv->bus != NULL);
+ g_return_if_fail(priv->dbusobject != NULL);
+
+ /* Class info */
+ g_return_if_fail(dbusmenu_node_info != NULL);
+ g_return_if_fail(dbusmenu_interface_info != NULL);
+
+ /* We might block on this in the future, but it'd be nice if
+ we could change the object path. Thinking about it... */
+ if (priv->dbus_registration != 0) {
+ g_dbus_connection_unregister_object(priv->bus, priv->dbus_registration);
+ priv->dbus_registration = 0;
+ }
+
+ GError * error = NULL;
+ priv->dbus_registration = g_dbus_connection_register_object(priv->bus,
+ priv->dbusobject,
+ dbusmenu_interface_info,
+ &dbusmenu_interface_table,
+ server,
+ NULL,
+ &error);
+
+ if (error != NULL) {
+ g_warning("Unable to register object on bus: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ /* If we've got it registered let's tell everyone about it */
+ g_signal_emit(G_OBJECT(server), signals[LAYOUT_UPDATED], 0, priv->layout_revision, 0, TRUE);
+ if (priv->dbusobject != NULL && priv->bus != NULL) {
+ g_dbus_connection_emit_signal(priv->bus,
+ NULL,
+ priv->dbusobject,
+ DBUSMENU_INTERFACE,
+ "LayoutUpdated",
+ g_variant_new("(ui)", priv->layout_revision, 0),
+ NULL);
+ }
+
+ return;
+}
+
+/* Callback from asking GIO to get us the session bus */
+static void
+bus_got_cb (GObject * obj, GAsyncResult * result, gpointer user_data)
+{
+ GError * error = NULL;
+
+ GDBusConnection * bus = g_bus_get_finish(result, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to get session bus: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ /* Note: We're not using the user_data before we check for
+ the error so that in the cancelled case at destruction of
+ the object we don't end up with an invalid object. */
+
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(user_data);
+ priv->bus = bus;
+
+ register_object(DBUSMENU_SERVER(user_data));
+
+ return;
+}
+
+/* Function for the GDBus vtable to handle all method calls and dish
+ them out the appropriate functions */
+static void
+bus_method_call (GDBusConnection * connection, const gchar * sender, const gchar * path, const gchar * interface, const gchar * method, GVariant * params, GDBusMethodInvocation * invocation, gpointer user_data)
+{
+ int i;
+ const gchar * interned_method = g_intern_string(method);
+
+ for (i = 0; i < METHOD_COUNT; i++) {
+ if (dbusmenu_method_table[i].interned_name == interned_method) {
+ if (dbusmenu_method_table[i].func != NULL) {
+ return dbusmenu_method_table[i].func(DBUSMENU_SERVER(user_data), params, invocation);
+ } else {
+ /* If we have a null function we're responding but nothing else. */
+ g_warning("Invalid function call for '%s' with parameters: %s", method, g_variant_print(params, TRUE));
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ return;
+ }
+ }
+ }
+
+ /* We're here because there's an error */
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ NOT_IMPLEMENTED,
+ "Unable to find method '%s'",
+ method);
+ return;
+}
+
+/* For the GDBus vtable but we only have one property so it's pretty
+ simple. */
+static GVariant *
+bus_get_prop (GDBusConnection * connection, const gchar * sender, const gchar * path, const gchar * interface, const gchar * property, GError ** error, gpointer user_data)
+{
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(user_data);
+
+ /* None of these should happen */
+ g_return_val_if_fail(g_strcmp0(interface, DBUSMENU_INTERFACE) == 0, NULL);
+ g_return_val_if_fail(g_strcmp0(path, priv->dbusobject) == 0, NULL);
+ g_return_val_if_fail(g_strcmp0(property, "version") == 0, NULL);
+
+ return g_variant_new_uint32(DBUSMENU_VERSION_NUMBER);
+}
+
+/* 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);
+ if (priv->dbusobject != NULL && priv->bus != NULL) {
+ g_dbus_connection_emit_signal(priv->bus,
+ NULL,
+ priv->dbusobject,
+ DBUSMENU_INTERFACE,
+ "LayoutUpdated",
+ g_variant_new("(ui)", priv->layout_revision, 0),
+ NULL);
+ }
+
+ 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;
+}
+
+typedef struct _prop_idle_item_t prop_idle_item_t;
+struct _prop_idle_item_t {
+ gint id;
+ GArray * array;
+};
+
+typedef struct _prop_idle_prop_t prop_idle_prop_t;
+struct _prop_idle_prop_t {
+ gchar * property;
+ GVariant * variant;
+};
+
+/* Takes appart our data structure so we don't leak any
+ memory or references. */
+static void
+prop_array_teardown (GArray * prop_array)
+{
+ int i, j;
+
+ for (i = 0; i < prop_array->len; i++) {
+ prop_idle_item_t * iitem = &g_array_index(prop_array, prop_idle_item_t, i);
+
+ for (j = 0; j < iitem->array->len; j++) {
+ prop_idle_prop_t * iprop = &g_array_index(iitem->array, prop_idle_prop_t, j);
+
+ g_free(iprop->property);
+
+ if (iprop->variant != NULL) {
+ g_variant_unref(iprop->variant);
+ }
+ }
+
+ g_array_free(iitem->array, TRUE);
+ }
+
+ g_array_free(prop_array, TRUE);
+
+ return;
+}
+
+/* Works in the idle to send a set of property updates so that they'll
+ all update in a single dbus message. */
+static gboolean
+menuitem_property_idle (gpointer user_data)
+{
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(user_data);
+
+ /* Source will get removed as we return */
+ priv->property_idle = 0;
+
+ /* If there are no items, let's just not signal */
+ if (priv->prop_array == NULL) {
+ return FALSE;
+ }
+
+ int i, j;
+ GVariantBuilder itembuilder;
+ gboolean item_init = FALSE;
+
+ GVariantBuilder removeitembuilder;
+ gboolean removeitem_init = FALSE;
+
+ for (i = 0; i < priv->prop_array->len; i++) {
+ prop_idle_item_t * iitem = &g_array_index(priv->prop_array, prop_idle_item_t, i);
+
+ GVariantBuilder dictbuilder;
+ gboolean dictinit = FALSE;
+
+ GVariantBuilder removedictbuilder;
+ gboolean removedictinit = FALSE;
+
+ /* Go throught each item and see if it should go in the removal list
+ or the additive list. */
+ for (j = 0; j < iitem->array->len; j++) {
+ prop_idle_prop_t * iprop = &g_array_index(iitem->array, prop_idle_prop_t, j);
+
+ if (iprop->variant != NULL) {
+ if (!dictinit) {
+ g_variant_builder_init(&dictbuilder, G_VARIANT_TYPE_DICTIONARY);
+ dictinit = TRUE;
+ }
+
+ GVariant * entry = g_variant_new_dict_entry(g_variant_new_string(iprop->property),
+ g_variant_new_variant(iprop->variant));
+
+ g_variant_builder_add_value(&dictbuilder, entry);
+ } else {
+ if (!removedictinit) {
+ g_variant_builder_init(&removedictbuilder, G_VARIANT_TYPE_ARRAY);
+ removedictinit = TRUE;
+ }
+
+ g_variant_builder_add_value(&removedictbuilder, g_variant_new_string(iprop->property));
+ }
+ }
+
+ /* If we've got new values that are real values we need to add that
+ to the list of items to send the value of */
+ if (dictinit) {
+ GVariantBuilder tuplebuilder;
+ g_variant_builder_init(&tuplebuilder, G_VARIANT_TYPE_TUPLE);
+
+ g_variant_builder_add_value(&tuplebuilder, g_variant_new_int32(iitem->id));
+ g_variant_builder_add_value(&tuplebuilder, g_variant_builder_end(&dictbuilder));
+
+ if (!item_init) {
+ g_variant_builder_init(&itembuilder, G_VARIANT_TYPE_ARRAY);
+ item_init = TRUE;
+ }
+
+ g_variant_builder_add_value(&itembuilder, g_variant_builder_end(&tuplebuilder));
+ }
+
+ /* If we've got properties that have been removed then we need to add
+ them to the list of removed items */
+ if (removedictinit) {
+ GVariantBuilder tuplebuilder;
+ g_variant_builder_init(&tuplebuilder, G_VARIANT_TYPE_TUPLE);
+
+ g_variant_builder_add_value(&tuplebuilder, g_variant_new_int32(iitem->id));
+ g_variant_builder_add_value(&tuplebuilder, g_variant_builder_end(&removedictbuilder));
+
+ if (!removeitem_init) {
+ g_variant_builder_init(&removeitembuilder, G_VARIANT_TYPE_ARRAY);
+ removeitem_init = TRUE;
+ }
+
+ g_variant_builder_add_value(&removeitembuilder, g_variant_builder_end(&tuplebuilder));
+ }
+ }
+
+ GVariant * megadata[2];
+
+ if (item_init) {
+ megadata[0] = g_variant_builder_end(&itembuilder);
+ } else {
+ GError * error = NULL;
+ megadata[0] = g_variant_parse(G_VARIANT_TYPE("a(ia{sv})"), "[ ]", NULL, NULL, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to parse '[ ]' as a 'a(ia{sv})': %s", error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (removeitem_init) {
+ megadata[1] = g_variant_builder_end(&removeitembuilder);
+ } else {
+ GError * error = NULL;
+ megadata[1] = g_variant_parse(G_VARIANT_TYPE("a(ia(s))"), "[ ]", NULL, NULL, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to parse '[ ]' as a 'a(ia(s))': %s", error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (priv->dbusobject != NULL && priv->bus != NULL) {
+ g_dbus_connection_emit_signal(priv->bus,
+ NULL,
+ priv->dbusobject,
+ DBUSMENU_INTERFACE,
+ "ItemPropertiesUpdated",
+ g_variant_new_tuple(megadata, 2),
+ NULL);
+ } else {
+ g_variant_unref(megadata[0]);
+ g_variant_unref(megadata[1]);
+ }
+
+ /* Clean everything up */
+ prop_array_teardown(priv->prop_array);
+ priv->prop_array = NULL;
+
+ return FALSE;
+}
+
static void
-menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, GValue * value, DbusmenuServer * server)
+menuitem_property_changed (DbusmenuMenuitem * mi, gchar * property, GVariant * variant, DbusmenuServer * server)
{
- g_signal_emit(G_OBJECT(server), signals[ID_PROP_UPDATE], 0, dbusmenu_menuitem_get_id(mi), property, value, TRUE);
+ int i;
+ gint item_id;
+
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+
+ item_id = dbusmenu_menuitem_get_id(mi);
+
+ g_signal_emit(G_OBJECT(server), signals[ID_PROP_UPDATE], 0, item_id, property, variant, TRUE);
+
+ /* See if we have a property array, if not, we need to
+ build one of these suckers */
+ if (priv->prop_array == NULL) {
+ priv->prop_array = g_array_new(FALSE, FALSE, sizeof(prop_idle_item_t));
+ }
+
+ /* Look to see if we already have this item in the list
+ and use it if so */
+ prop_idle_item_t * item = NULL;
+ for (i = 0; i < priv->prop_array->len; i++) {
+ prop_idle_item_t * iitem = &g_array_index(priv->prop_array, prop_idle_item_t, i);
+ if (iitem->id == item_id) {
+ item = iitem;
+ break;
+ }
+ }
+
+ GArray * properties = NULL;
+ /* If not, we'll need to build ourselves one */
+ if (item == NULL) {
+ prop_idle_item_t myitem;
+ myitem.id = item_id;
+ myitem.array = g_array_new(FALSE, FALSE, sizeof(prop_idle_prop_t));
+
+ g_array_append_val(priv->prop_array, myitem);
+ properties = myitem.array;
+ } else {
+ properties = item->array;
+ }
+
+ /* Check to see if this property is in the list */
+ prop_idle_prop_t * prop = NULL;
+ for (i = 0; i < properties->len; i++) {
+ prop_idle_prop_t * iprop = &g_array_index(properties, prop_idle_prop_t, i);
+ if (g_strcmp0(iprop->property, property) == 0) {
+ prop = iprop;
+ break;
+ }
+ }
+
+ /* If it's the default value we want to treat it like a clearing
+ of the value so that it doesn't get sent over dbus and waste
+ bandwidth */
+ if (dbusmenu_menuitem_property_is_default(mi, property)) {
+ variant = NULL;
+ }
+
+ /* If so, we need to swap the value */
+ if (prop != NULL) {
+ g_variant_unref(prop->variant);
+ prop->variant = variant;
+ } else {
+ /* else we need to add it */
+ prop_idle_prop_t myprop;
+ myprop.property = g_strdup(property);
+ myprop.variant = variant;
+
+ g_array_append_val(properties, myprop);
+ }
+ if (variant != NULL) {
+ g_variant_ref_sink(variant);
+ }
+
+ /* Check to see if the idle is already queued, and queue it
+ if not. */
+ if (priv->property_idle == 0) {
+ priv->property_idle = g_idle_add(menuitem_property_idle, server);
+ }
+
return;
}
@@ -325,10 +938,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 +946,36 @@ 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)
{
+ 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)
+{
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);
+
+ g_signal_emit(G_OBJECT(server), signals[ITEM_ACTIVATION], 0, dbusmenu_menuitem_get_id(mi), timestamp, TRUE);
+
+ if (priv->dbusobject != NULL && priv->bus != NULL) {
+ g_dbus_connection_emit_signal(priv->bus,
+ NULL,
+ priv->dbusobject,
+ DBUSMENU_INTERFACE,
+ "ItemActivationRequested",
+ g_variant_new("(iu)", dbusmenu_menuitem_get_id(mi), timestamp),
+ NULL);
+ }
+
return;
}
@@ -361,6 +988,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;
}
@@ -387,229 +1015,397 @@ error_quark (void)
}
/* DBus interface */
-static gboolean
-_dbusmenu_server_get_layout (DbusmenuServer * server, gint parent, guint * revision, gchar ** layout, GError ** error)
+static void
+bus_get_layout (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
- *revision = priv->layout_revision;
- GPtrArray * xmlarray = g_ptr_array_new();
+ /* Input */
+ gint parent = g_variant_get_int32(g_variant_get_child_value(params, 0));
+ gint recurse = g_variant_get_int32(g_variant_get_child_value(params, 1));
+ const gchar ** props = g_variant_get_strv(g_variant_get_child_value(params, 2), NULL);
+
+ /* Output */
+ guint revision = priv->layout_revision;
+ GVariant * items = NULL;
+
+ if (priv->root != NULL) {
+ items = dbusmenu_menuitem_build_variant(priv->root, props, recurse);
+ }
- if (parent == 0) {
- if (priv->root == NULL) {
- /* g_debug("Getting layout without root node!"); */
- g_ptr_array_add(xmlarray, g_strdup("<menu id=\"0\"/>"));
+ /* What happens if we don't have anything? */
+ if (items == NULL) {
+ if (parent == 0) {
+ /* We should always have a root, so we'll make up one for
+ right now. */
+ items = g_variant_parse(G_VARIANT_TYPE("(ia{sv}av)"), "(0, [], [])", NULL, NULL, NULL);
} else {
- dbusmenu_menuitem_buildxml(priv->root, xmlarray);
- }
- } else {
- DbusmenuMenuitem * item = dbusmenu_menuitem_find_id(priv->root, parent);
- if (item == NULL) {
- if (error != NULL) {
- g_set_error(error,
- error_quark(),
- INVALID_MENUITEM_ID,
- "The ID supplied %d does not refer to a menu item we have",
- parent);
- }
- return FALSE;
+ /* If we were looking for a specific ID that's an error that
+ we should send back, so let's do that. */
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ INVALID_MENUITEM_ID,
+ "The ID supplied %d does not refer to a menu item we have",
+ parent);
+ return;
}
- dbusmenu_menuitem_buildxml(item, xmlarray);
}
- g_ptr_array_add(xmlarray, NULL);
- /* build string */
- *layout = g_strjoinv("", (gchar **)xmlarray->pdata);
+ /* Build the final variant tuple */
+ GVariantBuilder tuplebuilder;
+ g_variant_builder_init(&tuplebuilder, G_VARIANT_TYPE_TUPLE);
- g_ptr_array_foreach(xmlarray, xmlarray_foreach_free, NULL);
- g_ptr_array_free(xmlarray, TRUE);
+ g_variant_builder_add_value(&tuplebuilder, g_variant_new_uint32(revision));
+ g_variant_builder_add_value(&tuplebuilder, items);
- return TRUE;
+ GVariant * retval = g_variant_builder_end(&tuplebuilder);
+ // g_debug("Sending layout type: %s", g_variant_get_type_string(retval));
+ g_dbus_method_invocation_return_value(invocation,
+ retval);
+ return;
}
-static gboolean
-_dbusmenu_server_get_property (DbusmenuServer * server, gint id, gchar * property, gchar ** value, GError ** error)
+/* Get a single property off of a single menuitem */
+static void
+bus_get_property (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+
+ if (priv->root == NULL) {
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ NO_VALID_LAYOUT,
+ "There currently isn't a layout in this server");
+ return;
+ }
+
+ gint id = g_variant_get_int32(g_variant_get_child_value(params, 0));
+ const gchar * property = g_variant_get_string(g_variant_get_child_value(params, 1), NULL);
+
DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
if (mi == NULL) {
- if (error != NULL) {
- g_set_error(error,
+ g_dbus_method_invocation_return_error(invocation,
error_quark(),
INVALID_MENUITEM_ID,
"The ID supplied %d does not refer to a menu item we have",
id);
- }
- return FALSE;
+ return;
}
- const gchar * prop = dbusmenu_menuitem_property_get(mi, property);
- if (prop == NULL) {
- if (error != NULL) {
- g_set_error(error,
+ GVariant * variant = dbusmenu_menuitem_property_get_variant(mi, property);
+ if (variant == NULL) {
+ g_dbus_method_invocation_return_error(invocation,
error_quark(),
INVALID_PROPERTY_NAME,
"The property '%s' does not exist on menuitem with ID of %d",
property,
id);
- }
- return FALSE;
+ return;
}
- if (value == NULL) {
- if (error != NULL) {
- g_set_error(error,
- error_quark(),
- UNKNOWN_DBUS_ERROR,
- "Uhm, yeah. We didn't get anywhere to put the value, that's really weird. Seems impossible really.");
- }
- return FALSE;
- }
-
- *value = g_strdup(prop);
-
- return TRUE;
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(v)", variant));
+ return;
}
-static gboolean
-_dbusmenu_server_get_properties (DbusmenuServer * server, gint id, GPtrArray * properties, GHashTable ** dict, GError ** error)
+/* Get some properties off of a single menuitem */
+static void
+bus_get_properties (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+
+ if (priv->root == NULL) {
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ NO_VALID_LAYOUT,
+ "There currently isn't a layout in this server");
+ return;
+ }
+
+ gint id = g_variant_get_int32(g_variant_get_child_value(params, 0));
+
DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
if (mi == NULL) {
- if (error != NULL) {
- g_set_error(error,
+ g_dbus_method_invocation_return_error(invocation,
error_quark(),
INVALID_MENUITEM_ID,
"The ID supplied %d does not refer to a menu item we have",
id);
- }
- return FALSE;
+ return;
}
- *dict = dbusmenu_menuitem_properties_copy(mi);
+ GVariant * dict = dbusmenu_menuitem_properties_variant(mi, NULL);
- return TRUE;
-}
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(a{sv})", dict));
-static gboolean
-_dbusmenu_server_get_group_properties (DbusmenuServer * server, GArray * ids, GArray * properties, GHashTable ** values, GError ** error)
-{
- if (error != NULL) {
- g_set_error(error,
- error_quark(),
- NOT_IMPLEMENTED,
- "The GetGroupProperties function is not implemented, sorry.");
- }
- return FALSE;
+ return;
}
+/* Handles getting a bunch of properties from a variety of menu items
+ to make one mega dbus message */
static void
-_gvalue_array_append_int(GValueArray *array, gint i)
+bus_get_group_properties (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation)
{
- GValue value = {0};
+ DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
- g_value_init(&value, G_TYPE_INT);
- g_value_set_int(&value, i);
- g_value_array_append(array, &value);
- g_value_unset(&value);
-}
+ if (priv->root == NULL) {
+ GVariant * idlist = g_variant_get_child_value(params, 0);
+ if (g_variant_n_children(idlist) == 1 && g_variant_get_int32(g_variant_get_child_value(idlist, 0)) == 0) {
+ GVariant * final = g_variant_parse(g_variant_type_new("(a(ia{sv}))"), "([(0, {})],)", NULL, NULL, NULL);
+ g_dbus_method_invocation_return_value(invocation, final);
+ return;
+ }
-static void
-_gvalue_array_append_hashtable(GValueArray *array, GHashTable * dict)
-{
- GValue value = {0};
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ NO_VALID_LAYOUT,
+ "There currently isn't a layout in this server");
+ return;
+ }
+
+ GVariantIter ids;
+ g_variant_iter_init(&ids, g_variant_get_child_value(params, 0));
+
+ GVariantBuilder builder;
+ gboolean builder_init = FALSE;
+
+ gint id;
+ while (g_variant_iter_next(&ids, "i", &id)) {
+ DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
+ if (mi == NULL) continue;
+
+ if (!builder_init) {
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+ builder_init = TRUE;
+ }
+
+ GVariantBuilder wbuilder;
+ g_variant_builder_init(&wbuilder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add(&wbuilder, "i", id);
+ GVariant * props = dbusmenu_menuitem_properties_variant(mi, NULL);
+
+ if (props == NULL) {
+ GError * error = NULL;
+ props = g_variant_parse(g_variant_type_new("a{sv}"), "{}", NULL, NULL, &error);
+ if (error != NULL) {
+ g_warning("Unable to parse '{}' as a 'a{sv}': %s", error->message);
+ g_error_free(error);
+ props = NULL;
+ }
+ }
+
+ g_variant_builder_add_value(&wbuilder, props);
+ GVariant * mi_data = g_variant_builder_end(&wbuilder);
+
+ g_variant_builder_add_value(&builder, mi_data);
+ }
+
+ GVariant * ret = NULL;
+
+ if (builder_init) {
+ ret = g_variant_builder_end(&builder);
+ } else {
+ GError * error = NULL;
+ ret = g_variant_parse(g_variant_type_new("a(ia{sv})"), "[]", NULL, NULL, NULL);
+ if (error != NULL) {
+ g_warning("Unable to parse '[]' as a 'a(ia{sv})': %s", error->message);
+ g_error_free(error);
+ ret = NULL;
+ }
+ }
+
+ GVariant * final = NULL;
+ if (ret != NULL) {
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value(&builder, ret);
+ final = g_variant_builder_end(&builder);
+ } else {
+ g_warning("Error building property list, final variant is NULL");
+ }
+
+ g_dbus_method_invocation_return_value(invocation, final);
- g_value_init(&value, dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE));
- g_value_set_boxed(&value, dict);
- g_value_array_append(array, &value);
- g_value_unset(&value);
+ return;
}
+/* Turn a menuitem into an variant and attach it to the
+ VariantBuilder we passed in */
static void
serialize_menuitem(gpointer data, gpointer user_data)
{
DbusmenuMenuitem * mi = DBUSMENU_MENUITEM(data);
- GPtrArray * output = (GPtrArray *)(user_data);
+ GVariantBuilder * builder = (GVariantBuilder *)(user_data);
+ GVariantBuilder tuple;
+
+ g_variant_builder_init(&tuple, G_VARIANT_TYPE_TUPLE);
gint id = dbusmenu_menuitem_get_id(mi);
- GHashTable * dict = dbusmenu_menuitem_properties_copy(mi);
+ g_variant_builder_add_value(&tuple, g_variant_new_int32(id));
+
+ GVariant * props = dbusmenu_menuitem_properties_variant(mi, NULL);
+ g_variant_builder_add_value(&tuple, props);
- GValueArray * item = g_value_array_new(1);
- _gvalue_array_append_int(item, id);
- _gvalue_array_append_hashtable(item, dict);
+ g_variant_builder_add_value(builder, g_variant_builder_end(&tuple));
- g_ptr_array_add(output, item);
+ return;
}
-static gboolean
-_dbusmenu_server_get_children (DbusmenuServer * server, gint id, GPtrArray * properties, GPtrArray ** output, GError ** error)
+/* Gets the children and their properties of the ID that is
+ passed into the function */
+static void
+bus_get_children (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+ gint id = g_variant_get_int32(g_variant_get_child_value(params, 0));
+
+ if (priv->root == NULL) {
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ NO_VALID_LAYOUT,
+ "There currently isn't a layout in this server");
+ return;
+ }
+
DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
if (mi == NULL) {
- if (error != NULL) {
- g_set_error(error,
- error_quark(),
- INVALID_MENUITEM_ID,
- "The ID supplied %d does not refer to a menu item we have",
- id);
- }
- return FALSE;
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ INVALID_MENUITEM_ID,
+ "The ID supplied %d does not refer to a menu item we have",
+ id);
+ return;
}
- *output = g_ptr_array_new();
GList * children = dbusmenu_menuitem_get_children(mi);
- g_list_foreach(children, serialize_menuitem, *output);
+ GVariant * ret = NULL;
+
+ if (children != NULL) {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ g_list_foreach(children, serialize_menuitem, &builder);
- return TRUE;
+ GVariant * end = g_variant_builder_end(&builder);
+ ret = g_variant_new_tuple(&end, 1);
+ } else {
+ GError * error = NULL;
+ ret = g_variant_parse(g_variant_type_new("(a(ia{sv}))"), "([(0, {})],)", NULL, NULL, &error);
+ if (error != NULL) {
+ g_warning("Unable to parse '([(0, {})],)' as a '(a(ia{sv}))': %s", error->message);
+ g_error_free(error);
+ ret = NULL;
+ }
+ }
+
+ g_dbus_method_invocation_return_value(invocation, ret);
+ return;
}
+/* 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;
+ GVariant * variant;
+ guint timestamp;
+};
+
+/* A handler for else where in the main loop so that the dbusmenu
+ event response doesn't get blocked */
static gboolean
-_dbusmenu_server_event (DbusmenuServer * server, gint id, gchar * eventid, GValue * data, guint timestamp, GError ** error)
+event_local_handler (gpointer user_data)
+{
+ idle_event_t * data = (idle_event_t *)user_data;
+
+ dbusmenu_menuitem_handle_event(data->mi, data->eventid, data->variant, data->timestamp);
+
+ g_object_unref(data->mi);
+ g_free(data->eventid);
+ g_variant_unref(data->variant);
+ g_free(data);
+ return FALSE;
+}
+
+/* Handles the events coming off of DBus */
+static void
+bus_event (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+
+ if (priv->root == NULL) {
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ NO_VALID_LAYOUT,
+ "There currently isn't a layout in this server");
+ return;
+ }
+
+ gint id = g_variant_get_int32(g_variant_get_child_value(params, 0));
DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
if (mi == NULL) {
- if (error != NULL) {
- g_set_error(error,
- error_quark(),
- INVALID_MENUITEM_ID,
- "The ID supplied %d does not refer to a menu item we have",
- id);
- }
- return FALSE;
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ INVALID_MENUITEM_ID,
+ "The ID supplied %d does not refer to a menu item we have",
+ id);
+ return;
+ }
+
+ 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(g_variant_get_string(g_variant_get_child_value(params, 1), NULL));
+ event_data->timestamp = g_variant_get_uint32(g_variant_get_child_value(params, 3));
+ event_data->variant = g_variant_get_child_value(params, 2);
+
+ if (g_variant_is_of_type(event_data->variant, G_VARIANT_TYPE_VARIANT)) {
+ event_data->variant = g_variant_get_variant(event_data->variant);
}
- dbusmenu_menuitem_handle_event(mi, eventid, data, timestamp);
- return TRUE;
+ g_variant_ref_sink(event_data->variant);
+
+ g_timeout_add(0, event_local_handler, event_data);
+
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ return;
}
/* Recieve the About To Show function. Pass it to our menu item. */
-static gboolean
-_dbusmenu_server_about_to_show (DbusmenuServer * server, gint id, gboolean * need_update, GError ** error)
+static void
+bus_about_to_show (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation)
{
DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server);
+
+ if (priv->root == NULL) {
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ NO_VALID_LAYOUT,
+ "There currently isn't a layout in this server");
+ return;
+ }
+
+ gint id = g_variant_get_int32(g_variant_get_child_value(params, 0));
DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id);
if (mi == NULL) {
- if (error != NULL) {
- g_set_error(error,
- error_quark(),
- INVALID_MENUITEM_ID,
- "The ID supplied %d does not refer to a menu item we have",
- id);
- }
- return FALSE;
+ g_dbus_method_invocation_return_error(invocation,
+ error_quark(),
+ INVALID_MENUITEM_ID,
+ "The ID supplied %d does not refer to a menu item we have",
+ id);
+ return;
}
+ dbusmenu_menuitem_send_about_to_show(mi, NULL, NULL);
+
/* GTK+ does not support about-to-show concept for now */
- *need_update = FALSE;
- return TRUE;
+ g_dbus_method_invocation_return_value(invocation,
+ g_variant_new("(b)", FALSE));
+ return;
}
/* Public Interface */
@@ -620,7 +1416,7 @@ _dbusmenu_server_about_to_show (DbusmenuServer * server, gint id, gboolean * nee
Creates a new #DbusmenuServer object with a specific object
path on DBus. If @object is set to NULL the default object
- name of "/org/ayatana/dbusmenu" will be used.
+ name of "/com/canonical/dbusmenu" will be used.
Return value: A brand new #DbusmenuServer
*/
@@ -628,7 +1424,7 @@ DbusmenuServer *
dbusmenu_server_new (const gchar * object)
{
if (object == NULL) {
- object = "/org/ayatana/dbusmenu";
+ object = "/com/canonical/dbusmenu";
}
DbusmenuServer * self = g_object_new(DBUSMENU_TYPE_SERVER,
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/Makefile.am b/libdbusmenu-gtk/Makefile.am
index 2be63b7..50a8f2c 100644
--- a/libdbusmenu-gtk/Makefile.am
+++ b/libdbusmenu-gtk/Makefile.am
@@ -1,18 +1,31 @@
CLEANFILES =
-EXTRA_DIST = \
- dbusmenu-gtk.pc.in
+if USE_GTK3
+VER=3
+GTKGIR=Gtk-3.0
+GTKVALA=gtk+-3.0
+lib_LTLIBRARIES = libdbusmenu-gtk3.la
+else
+VER=
+GTKGIR=Gtk-2.0
+GTKVALA=gtk+-2.0
+lib_LTLIBRARIES = libdbusmenu-gtk.la
+endif
-lib_LTLIBRARIES = \
- libdbusmenu-gtk.la
+EXTRA_DIST = \
+ dbusmenu-gtk-0.4.pc.in \
+ dbusmenu-gtk3-0.4.pc.in
-libdbusmenu_gtkincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-gtk/
+libdbusmenu_gtkincludedir=$(includedir)/libdbusmenu-0.4/libdbusmenu-gtk$(VER)/
libdbusmenu_gtkinclude_HEADERS = \
+ dbusmenu-gtk.h \
client.h \
menu.h \
- menuitem.h
+ menuitem.h \
+ parser.h \
+ serializablemenuitem.h
libdbusmenu_gtk_la_SOURCES = \
client.h \
@@ -22,7 +35,11 @@ libdbusmenu_gtk_la_SOURCES = \
menu.h \
menu.c \
menuitem.h \
- menuitem.c
+ menuitem.c \
+ parser.h \
+ parser.c \
+ serializablemenuitem.h \
+ serializablemenuitem.c
libdbusmenu_gtk_la_LDFLAGS = \
-version-info $(LIBDBUSMENU_CURRENT):$(LIBDBUSMENU_REVISION):$(LIBDBUSMENU_AGE) \
@@ -36,7 +53,18 @@ libdbusmenu_gtk_la_LIBADD = \
../libdbusmenu-glib/libdbusmenu-glib.la \
$(DBUSMENUGTK_LIBS)
-pkgconfig_DATA = dbusmenu-gtk.pc
+# We duplicate these here because Automake won't let us use $(VER) on the left hand side.
+# Since we carefully use $(VER) in the right hand side above, we can assign the same values.
+# Only one version of the library is every compiled at the same time, so it is safe to reuse
+# the right hand sides like this.
+libdbusmenu_gtk3includedir = $(libdbusmenu_gtkincludedir)
+libdbusmenu_gtk3include_HEADERS = $(libdbusmenu_gtkinclude_HEADERS)
+libdbusmenu_gtk3_la_SOURCES = $(libdbusmenu_gtk_la_SOURCES)
+libdbusmenu_gtk3_la_LDFLAGS = $(libdbusmenu_gtk_la_LDFLAGS)
+libdbusmenu_gtk3_la_CFLAGS = $(libdbusmenu_gtk_la_CFLAGS)
+libdbusmenu_gtk3_la_LIBADD = $(libdbusmenu_gtk_la_LIBADD)
+
+pkgconfig_DATA = dbusmenu-gtk$(VER)-0.4.pc
pkgconfigdir = $(libdir)/pkgconfig
#########################
@@ -45,26 +73,49 @@ pkgconfigdir = $(libdir)/pkgconfig
-include $(INTROSPECTION_MAKEFILE)
INTROSPECTION_GIRS =
-INTROSPECTION_SCANNER_ARGS = \
- --add-include-path=$(srcdir) \
+
+if INTROSPECTION_TEN
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) \
+ --warn-all \
--add-include-path=$(top_builddir)/libdbusmenu-glib \
- $(addprefix --c-include=libdbusmenu-gtk/, $(introspection_sources))
+ $(addprefix --c-include=libdbusmenu-gtk/, $(libdbusmenu_gtkinclude_HEADERS)) \
+ --symbol-prefix=dbusmenu \
+ --identifier-prefix=DbusmenuGtk
+else
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) \
+ --warn-all \
+ --add-include-path=$(top_builddir)/libdbusmenu-glib \
+ $(addprefix --c-include=libdbusmenu-gtk/, $(libdbusmenu_gtkinclude_HEADERS))
+endif
+
INTROSPECTION_COMPILER_ARGS = --includedir=$(builddir) --includedir=$(top_builddir)/libdbusmenu-glib
if HAVE_INTROSPECTION
-introspection_sources = $(libdbusmenu_gtkinclude_HEADERS)
+introspection_sources = $(filter-out genericmenuitem.%, $(libdbusmenu_gtkinclude_HEADERS) $(libdbusmenu_gtk_la_SOURCES))
-DbusmenuGtk-0.2.gir: libdbusmenu-gtk.la
-DbusmenuGtk_0_2_gir_INCLUDES = \
+DbusmenuGtk$(VER)-0.4.gir: libdbusmenu-gtk$(VER).la
+DbusmenuGtk_0_4_gir_INCLUDES = \
GObject-2.0 \
- Gtk-2.0 \
- Dbusmenu-Glib-0.2
-DbusmenuGtk_0_2_gir_CFLAGS = $(DBUSMENUGTK_CFLAGS) -I$(top_srcdir)
-DbusmenuGtk_0_2_gir_LIBS = libdbusmenu-gtk.la
-DbusmenuGtk_0_2_gir_FILES = $(addprefix $(srcdir)/, $(introspection_sources))
-
-INTROSPECTION_GIRS += DbusmenuGtk-0.2.gir
+ $(GTKGIR) \
+ Dbusmenu-0.4
+DbusmenuGtk_0_4_gir_CFLAGS = $(DBUSMENUGTK_CFLAGS) -I$(top_srcdir)
+DbusmenuGtk_0_4_gir_LIBS = libdbusmenu-gtk$(VER).la
+DbusmenuGtk_0_4_gir_FILES = $(addprefix $(srcdir)/, $(introspection_sources))
+DbusmenuGtk_0_4_gir_NAMESPACE = DbusmenuGtk$(VER)
+DbusmenuGtk_0_4_gir_SCANNERFLAGS = $(INTROSPECTION_SCANNER_ARGS)
+DbusmenuGtk_0_4_gir_EXPORT_PACKAGES = dbusmenu-gtk$(VER)-0.4
+
+# We duplicate these for the same reason as libdbusmenu_gtk3includedir above
+DbusmenuGtk3_0_4_gir_INCLUDES = $(DbusmenuGtk_0_4_gir_INCLUDES)
+DbusmenuGtk3_0_4_gir_CFLAGS = $(DbusmenuGtk_0_4_gir_CFLAGS)
+DbusmenuGtk3_0_4_gir_LIBS = $(DbusmenuGtk_0_4_gir_LIBS)
+DbusmenuGtk3_0_4_gir_FILES = $(DbusmenuGtk_0_4_gir_FILES)
+DbusmenuGtk3_0_4_gir_NAMESPACE = $(DbusmenuGtk_0_4_gir_NAMESPACE)
+DbusmenuGtk3_0_4_gir_SCANNERFLAGS = $(DbusmenuGtk_0_4_gir_SCANNERFLAGS)
+DbusmenuGtk3_0_4_gir_EXPORT_PACKAGES = $(DbusmenuGtk_0_4_gir_EXPORT_PACKAGES)
+
+INTROSPECTION_GIRS += DbusmenuGtk$(VER)-0.4.gir
girdir = $(datadir)/gir-1.0
gir_DATA = $(INTROSPECTION_GIRS)
@@ -83,24 +134,24 @@ endif
if HAVE_INTROSPECTION
vapidir = $(datadir)/vala/vapi
-vapi_DATA = DbusmenuGtk-0.2.vapi
+vapi_DATA = DbusmenuGtk$(VER)-0.4.vapi
-DbusmenuGtk-0.2.vapi: DbusmenuGtk-0.2.tmp.gir Makefile.am
- $(VALA_API_GEN) --library=DbusmenuGtk-0.2 \
+DbusmenuGtk$(VER)-0.4.vapi: DbusmenuGtk$(VER)-0.4.tmp.gir Makefile.am
+ $(VALA_API_GEN) --library=DbusmenuGtk$(VER)-0.4 \
--pkg gdk-pixbuf-2.0 \
- --pkg gtk+-2.0 \
+ --pkg $(GTKVALA) \
--pkg atk \
- --pkg Dbusmenu-Glib-0.2 \
+ --pkg Dbusmenu-0.4 \
--vapidir=$(top_builddir)/libdbusmenu-glib \
$<
-DbusmenuGtk-0.2.tmp.gir: DbusmenuGtk-0.2.gir
+DbusmenuGtk$(VER)-0.4.tmp.gir: DbusmenuGtk$(VER)-0.4.gir
$(SED) \
-e "s|GdkPixbuf.Pixbuf|Gdk.Pixbuf|g" \
-e "s|Atk.ImplementorIface|Atk.Implementor|g" \
$< > $@
-CLEANFILES += $(vapi_DATA) DbusmenuGtk-0.2.tmp.gir
+CLEANFILES += $(vapi_DATA) DbusmenuGtk$(VER)-0.4.tmp.gir
endif
diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c
index 3bd0af6..50978ff 100644
--- a/libdbusmenu-gtk/client.c
+++ b/libdbusmenu-gtk/client.c
@@ -36,6 +36,14 @@ 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)
+#define USE_FALLBACK_PROP "use-fallback"
+
/* Prototypes */
static void dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass);
static void dbusmenu_gtkclient_init (DbusmenuGtkClient *self);
@@ -45,13 +53,14 @@ 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);
+static gboolean new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data);
+static gboolean new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data);
-static void process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value);
-static void process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value);
-static void image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GValue * invalue, gpointer userdata);
+static void process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value);
+static void process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value);
+static void image_property_handle (DbusmenuMenuitem * item, const gchar * property, GVariant * invalue, gpointer userdata);
/* GObject Stuff */
G_DEFINE_TYPE (DbusmenuGtkClient, dbusmenu_gtkclient, DBUSMENU_TYPE_CLIENT);
@@ -62,6 +71,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 +84,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 +104,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 +124,156 @@ 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;
+ g_object_ref(priv->agroup);
+
+ return;
+}
+
+/**
+ * dbusmenu_gtkclient_get_accel_group:
+ * @client: Client to query for an accelerator group
+ *
+ * Gets the accel group for this client.
+ *
+ * Return value: (transfer none): 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";
@@ -110,10 +285,8 @@ static gboolean
menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
{
if (gtk_menu_item_get_submenu(gmi) == NULL) {
- GValue value = {0};
- g_value_init(&value, G_TYPE_INT);
- g_value_set_int(&value, 0);
- dbusmenu_menuitem_handle_event(mi, "clicked", &value, gtk_get_current_event_time());
+ GVariant * variant = g_variant_new("i", 0);
+ dbusmenu_menuitem_handle_event(mi, "clicked", variant, gtk_get_current_event_time());
} else {
/* TODO: We need to stop the display of the submenu
until this callback returns. */
@@ -124,7 +297,7 @@ menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
/* Process the visible property */
static void
-process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value)
+process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value)
{
gboolean val = TRUE;
if (value != NULL) {
@@ -141,7 +314,7 @@ process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value)
/* Process the sensitive property */
static void
-process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value)
+process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value)
{
gboolean val = TRUE;
if (value != NULL) {
@@ -153,26 +326,21 @@ process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * valu
/* Process the sensitive property */
static void
-process_toggle_type (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value)
+process_toggle_type (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * variant)
{
if (!IS_GENERICMENUITEM(gmi)) return;
- if (value == NULL) return;
+ if (variant == NULL) return;
GenericmenuitemCheckType type = GENERICMENUITEM_CHECK_TYPE_NONE;
- GValue strvalue = {0};
- g_value_init(&strvalue, G_TYPE_STRING);
-
- if (value != NULL && g_value_transform(value, &strvalue)) {
- const gchar * strval = g_value_get_string(&strvalue);
+ if (variant != NULL) {
+ const gchar * strval = g_variant_get_string(variant, NULL);
if (!g_strcmp0(strval, DBUSMENU_MENUITEM_TOGGLE_CHECK)) {
type = GENERICMENUITEM_CHECK_TYPE_CHECKBOX;
} else if (!g_strcmp0(strval, DBUSMENU_MENUITEM_TOGGLE_RADIO)) {
type = GENERICMENUITEM_CHECK_TYPE_RADIO;
}
-
- g_value_unset(&strvalue);
}
genericmenuitem_set_check_type(GENERICMENUITEM(gmi), type);
@@ -182,17 +350,14 @@ process_toggle_type (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * va
/* Process the sensitive property */
static void
-process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value)
+process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * variant)
{
if (!IS_GENERICMENUITEM(gmi)) return;
GenericmenuitemState state = GENERICMENUITEM_STATE_UNCHECKED;
- GValue intvalue = {0};
- g_value_init(&intvalue, G_TYPE_INT);
-
- if (value != NULL && g_value_transform(value, &intvalue)) {
- int val = g_value_get_int(&intvalue);
+ if (variant != NULL) {
+ int val = g_variant_get_int32(variant);
if (val == DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED) {
state = GENERICMENUITEM_STATE_CHECKED;
@@ -208,23 +373,34 @@ process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * v
/* Whenever we have a property change on a DbusmenuMenuitem
we need to be responsive to that. */
static void
-menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, GtkMenuItem * gmi)
+menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, GVariant * variant, GtkMenuItem * gmi)
{
if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_LABEL)) {
- gtk_menu_item_set_label(gmi, g_value_get_string(value));
+ gtk_menu_item_set_label(gmi, variant == NULL ? NULL : g_variant_get_string(variant, NULL));
} else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_VISIBLE)) {
- process_visible(mi, gmi, value);
+ process_visible(mi, gmi, variant);
} else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_ENABLED)) {
- process_sensitive(mi, gmi, value);
+ process_sensitive(mi, gmi, variant);
} else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE)) {
- process_toggle_type(mi, gmi, value);
+ process_toggle_type(mi, gmi, variant);
} else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE)) {
- process_toggle_state(mi, gmi, value);
+ process_toggle_state(mi, gmi, variant);
}
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, GVariant * 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 +426,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)
@@ -260,21 +493,21 @@ destroy_gmi (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
#endif
/**
- dbusmenu_gtkclient_newitem_base:
- @client: The client handling everything on this connection
- @item: The #DbusmenuMenuitem to attach the GTK-isms to
- @gmi: A #GtkMenuItem representing the GTK world's view of this menuitem
- @parent: The parent #DbusmenuMenuitem
-
- This function provides some of the basic connectivity for being in
- the GTK world. Things like visibility and sensitivity of the item are
- handled here so that the subclasses don't have to. If you're building
- your on GTK menu item you can use this function to apply those basic
- attributes so that you don't have to deal with them either.
-
- This also handles passing the "activate" signal back to the
- #DbusmenuMenuitem side of thing.
-*/
+ * dbusmenu_gtkclient_newitem_base:
+ * @client: The client handling everything on this connection
+ * @item: The #DbusmenuMenuitem to attach the GTK-isms to
+ * @gmi: A #GtkMenuItem representing the GTK world's view of this menuitem
+ * @parent: The parent #DbusmenuMenuitem
+ *
+ * This function provides some of the basic connectivity for being in
+ * the GTK world. Things like visibility and sensitivity of the item are
+ * handled here so that the subclasses don't have to. If you're building
+ * your on GTK menu item you can use this function to apply those basic
+ * attributes so that you don't have to deal with them either.
+ *
+ * This also handles passing the "activate" signal back to the
+ * #DbusmenuMenuitem side of thing.
+ */
void
dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * item, GtkMenuItem * gmi, DbusmenuMenuitem * parent)
{
@@ -291,6 +524,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);
@@ -301,14 +535,15 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem *
g_object_weak_ref(G_OBJECT(item), destoryed_dbusmenuitem_cb, gmi);
/* Check our set of props to see if any are set already */
- process_visible(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_VISIBLE));
- 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));
+ process_visible(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_VISIBLE));
+ process_sensitive(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_ENABLED));
+ process_toggle_type(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE));
+ process_toggle_state(item, gmi, dbusmenu_menuitem_property_get_variant(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 +557,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 +571,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;
@@ -381,15 +617,15 @@ move_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint new, guint ol
/* Public API */
/**
- dbusmenu_gtkclient_new:
- @dbus_name: Name of the #DbusmenuServer on DBus
- @dbus_name: Name of the object on the #DbusmenuServer
-
- Creates a new #DbusmenuGtkClient object and creates a #DbusmenuClient
- that connects across DBus to a #DbusmenuServer.
-
- Return value: A new #DbusmenuGtkClient sync'd with a server
-*/
+ * dbusmenu_gtkclient_new:
+ * @dbus_name: Name of the #DbusmenuServer on DBus
+ * @dbus_object: Name of the object on the #DbusmenuServer
+ *
+ * Creates a new #DbusmenuGtkClient object and creates a #DbusmenuClient
+ * that connects across DBus to a #DbusmenuServer.
+ *
+ * Return value: A new #DbusmenuGtkClient sync'd with a server
+ */
DbusmenuGtkClient *
dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_object)
{
@@ -400,15 +636,15 @@ dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_object)
}
/**
- dbusmenu_gtkclient_menuitem_get:
- @client: A #DbusmenuGtkClient with the item in it.
- @item: #DbusmenuMenuitem to get associated #GtkMenuItem on.
-
- This grabs the #GtkMenuItem that is associated with the
- #DbusmenuMenuitem.
-
- Return value: The #GtkMenuItem that can be played with.
-*/
+ * dbusmenu_gtkclient_menuitem_get:
+ * @client: A #DbusmenuGtkClient with the item in it.
+ * @item: #DbusmenuMenuitem to get associated #GtkMenuItem on.
+ *
+ * This grabs the #GtkMenuItem that is associated with the
+ * #DbusmenuMenuitem.
+ *
+ * Return value: (transfer none): The #GtkMenuItem that can be played with.
+ */
GtkMenuItem *
dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem * item)
{
@@ -424,13 +660,13 @@ dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem *
}
/**
- dbusmenu_gtkclient_menuitem_get_submenu:
- @client: A #DbusmenuGtkClient with the item in it.
- @item: #DbusmenuMenuitem to get associated #GtkMenu on.
-
- This grabs the submenu associated with the menuitem.
-
- Return value: The #GtkMenu if there is one.
+ * dbusmenu_gtkclient_menuitem_get_submenu:
+ * @client: A #DbusmenuGtkClient with the item in it.
+ * @item: #DbusmenuMenuitem to get associated #GtkMenu on.
+ *
+ * This grabs the submenu associated with the menuitem.
+ *
+ * Return value: (transfer none): The #GtkMenu if there is one.
*/
GtkMenu *
dbusmenu_gtkclient_menuitem_get_submenu (DbusmenuGtkClient * client, DbusmenuMenuitem * item)
@@ -449,7 +685,7 @@ dbusmenu_gtkclient_menuitem_get_submenu (DbusmenuGtkClient * client, DbusmenuMen
/* The base type handler that builds a plain ol'
GtkMenuItem to represent, well, the GtkMenuItem */
static gboolean
-new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client)
+new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data)
{
g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE);
g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE);
@@ -467,11 +703,11 @@ new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, Dbusmenu
image_property_handle(newitem,
DBUSMENU_MENUITEM_PROP_ICON_NAME,
- dbusmenu_menuitem_property_get_value(newitem, DBUSMENU_MENUITEM_PROP_ICON_NAME),
+ dbusmenu_menuitem_property_get_variant(newitem, DBUSMENU_MENUITEM_PROP_ICON_NAME),
client);
image_property_handle(newitem,
DBUSMENU_MENUITEM_PROP_ICON_DATA,
- dbusmenu_menuitem_property_get_value(newitem, DBUSMENU_MENUITEM_PROP_ICON_DATA),
+ dbusmenu_menuitem_property_get_variant(newitem, DBUSMENU_MENUITEM_PROP_ICON_DATA),
client);
g_signal_connect(G_OBJECT(newitem),
DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED,
@@ -484,7 +720,7 @@ new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, Dbusmenu
/* Type handler for the seperators where it builds
a GtkSeparator to act as the GtkMenuItem */
static gboolean
-new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client)
+new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data)
{
g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE);
g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE);
@@ -502,10 +738,33 @@ new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, Dbusm
return TRUE;
}
+/* A little helper so we don't generate a bunch of warnings
+ about being able to set use-fallback */
+static void
+set_use_fallback (GtkWidget * widget)
+{
+ static gboolean checked = FALSE;
+ static gboolean available = FALSE;
+
+ if (!checked) {
+ available = (g_object_class_find_property(G_OBJECT_CLASS(GTK_IMAGE_GET_CLASS(widget)), USE_FALLBACK_PROP) != NULL);
+ if (!available) {
+ g_warning("The '" USE_FALLBACK_PROP "' is not available on GtkImage so icons may not show correctly.");
+ }
+ checked = TRUE;
+ }
+
+ if (available) {
+ g_object_set(G_OBJECT(widget), USE_FALLBACK_PROP, TRUE, NULL);
+ }
+
+ return;
+}
+
/* This handler looks at property changes for items that are
image menu items. */
static void
-image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GValue * invalue, gpointer userdata)
+image_property_handle (DbusmenuMenuitem * item, const gchar * property, GVariant * variant, gpointer userdata)
{
/* We're only looking at these two properties here */
if (g_strcmp0(property, DBUSMENU_MENUITEM_PROP_ICON_NAME) != 0 &&
@@ -514,11 +773,10 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GV
}
const gchar * value = NULL;
-
- if (invalue != NULL && G_VALUE_TYPE(invalue) == G_TYPE_STRING) {
- value = g_value_get_string(invalue);
+ if (variant != NULL) {
+ value = g_variant_get_string(variant, NULL);
}
-
+
if (value == NULL || value[0] == '\0') {
/* This means that we're unsetting a value. */
/* Try to use the other one */
@@ -555,6 +813,7 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GV
gtkimage = NULL;
} else if (g_strcmp0(iconname, DBUSMENU_MENUITEM_ICON_NAME_BLANK) == 0) {
gtkimage = gtk_image_new();
+ set_use_fallback(gtkimage);
} else {
/* Look to see if we want to have an icon with the 'ltr' or
'rtl' depending on what we're doing. */
@@ -573,6 +832,7 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GV
can just convert it to this name. */
if (gtkimage == NULL) {
gtkimage = gtk_image_new_from_icon_name(finaliconname, GTK_ICON_SIZE_MENU);
+ set_use_fallback(gtkimage);
} else {
gtk_image_set_from_icon_name(GTK_IMAGE(gtkimage), finaliconname, GTK_ICON_SIZE_MENU);
}
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/dbusmenu-gtk.pc.in b/libdbusmenu-gtk/dbusmenu-gtk-0.4.pc.in
index df4cb36..8784556 100644
--- a/libdbusmenu-gtk/dbusmenu-gtk.pc.in
+++ b/libdbusmenu-gtk/dbusmenu-gtk-0.4.pc.in
@@ -4,8 +4,8 @@ libdir=@libdir@
bindir=@bindir@
includedir=@includedir@
-Cflags: -I${includedir}/libdbusmenu-0.1
-Requires: dbus-glib-1 dbusmenu-glib
+Cflags: -I${includedir}/libdbusmenu-0.4
+Requires: dbusmenu-glib-0.4
Libs: -L${libdir} -ldbusmenu-gtk
Name: libdbusmenu-gtk
diff --git a/libdbusmenu-gtk/dbusmenu-gtk.h b/libdbusmenu-gtk/dbusmenu-gtk.h
new file mode 100644
index 0000000..f2fe5be
--- /dev/null
+++ b/libdbusmenu-gtk/dbusmenu-gtk.h
@@ -0,0 +1,41 @@
+/*
+A library to communicate a menu object set accross DBus and
+track updates and maintain consistency.
+
+Copyright 2011 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 either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, 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 applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifndef __DBUSMENU_GTK_H__
+#define __DBUSMENU_GTK_H__
+
+/* Start with the glib stuff */
+#include <libdbusmenu-glib/dbusmenu-glib.h>
+
+/* Now get the GTK stuff */
+#include <libdbusmenu-gtk/client.h>
+#include <libdbusmenu-gtk/menu.h>
+#include <libdbusmenu-gtk/menuitem.h>
+#include <libdbusmenu-gtk/serializablemenuitem.h>
+
+#endif /* __DBUSMENU_GLIB_H__ */
diff --git a/libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in b/libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in
new file mode 100644
index 0000000..804b13e
--- /dev/null
+++ b/libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+bindir=@bindir@
+includedir=@includedir@
+
+Cflags: -I${includedir}/libdbusmenu-0.4
+Requires: dbusmenu-glib-0.4
+Libs: -L${libdir} -ldbusmenu-gtk3
+
+Name: libdbusmenu-gtk3
+Description: libdbusmenu-gtk3.
+Version: @VERSION@
+
diff --git a/libdbusmenu-gtk/genericmenuitem.c b/libdbusmenu-gtk/genericmenuitem.c
index 8f40d93..2fd6fba 100644
--- a/libdbusmenu-gtk/genericmenuitem.c
+++ b/libdbusmenu-gtk/genericmenuitem.c
@@ -51,7 +51,6 @@ static void genericmenuitem_class_init (GenericmenuitemClass *klass);
static void genericmenuitem_init (Genericmenuitem *self);
static void genericmenuitem_dispose (GObject *object);
static void genericmenuitem_finalize (GObject *object);
-static void draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area);
static void set_label (GtkMenuItem * menu_item, const gchar * label);
static const gchar * get_label (GtkMenuItem * menu_item);
static void activate (GtkMenuItem * menu_item);
@@ -59,8 +58,14 @@ static void activate (GtkMenuItem * menu_item);
/* GObject stuff */
G_DEFINE_TYPE (Genericmenuitem, genericmenuitem, GTK_TYPE_CHECK_MENU_ITEM);
-/* Globals */
+#if HAVE_GTK3
+static void draw_indicator (GtkCheckMenuItem *check_menu_item, cairo_t *cr);
+static void (*parent_draw_indicator) (GtkCheckMenuItem *check_menu_item, cairo_t *cr) = NULL;
+#else
+static void draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area);
static void (*parent_draw_indicator) (GtkCheckMenuItem *check_menu_item, GdkRectangle *area) = NULL;
+#endif
+static void (*parent_menuitem_activate) (GtkMenuItem * mi) = NULL;
/* Initializing all of the classes. Most notably we're
disabling the drawing of the check early. */
@@ -82,6 +87,7 @@ genericmenuitem_class_init (GenericmenuitemClass *klass)
GtkMenuItemClass * menuitem_class = GTK_MENU_ITEM_CLASS (klass);
menuitem_class->set_label = set_label;
menuitem_class->get_label = get_label;
+ parent_menuitem_activate = menuitem_class->activate;
menuitem_class->activate = activate;
return;
@@ -121,6 +127,17 @@ genericmenuitem_finalize (GObject *object)
/* Checks to see if we should be drawing a little box at
all. If we should be, let's do that, otherwise we're
going suppress the box drawing. */
+#if HAVE_GTK3
+static void
+draw_indicator (GtkCheckMenuItem *check_menu_item, cairo_t *cr)
+{
+ Genericmenuitem * self = GENERICMENUITEM(check_menu_item);
+ if (self->priv->check_type != GENERICMENUITEM_CHECK_TYPE_NONE) {
+ parent_draw_indicator(check_menu_item, cr);
+ }
+ return;
+}
+#else
static void
draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area)
{
@@ -130,6 +147,7 @@ draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area)
}
return;
}
+#endif
/* A small helper to look through the widgets in the
box and find the one that is the label. */
@@ -158,6 +176,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;
@@ -191,9 +211,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
@@ -257,12 +278,12 @@ activate (GtkMenuItem * menu_item)
}
/**
- genericmenuitem_set_check_type:
- @item: #Genericmenuitem to set the type on
- @check_type: Which type of check should be displayed
-
- This function changes the type of the checkmark that
- appears in the left hand gutter for the menuitem.
+ * genericmenuitem_set_check_type:
+ * @item: #Genericmenuitem to set the type on
+ * @check_type: Which type of check should be displayed
+ *
+ * This function changes the type of the checkmark that
+ * appears in the left hand gutter for the menuitem.
*/
void
genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType check_type)
@@ -272,7 +293,6 @@ genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType
}
item->priv->check_type = check_type;
- GValue value = {0};
switch (item->priv->check_type) {
case GENERICMENUITEM_CHECK_TYPE_NONE:
@@ -281,14 +301,10 @@ genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType
check on the item. */
break;
case GENERICMENUITEM_CHECK_TYPE_CHECKBOX:
- g_value_init(&value, G_TYPE_BOOLEAN);
- g_value_set_boolean(&value, FALSE);
- g_object_set_property(G_OBJECT(item), "draw-as-radio", &value);
+ gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), FALSE);
break;
case GENERICMENUITEM_CHECK_TYPE_RADIO:
- g_value_init(&value, G_TYPE_BOOLEAN);
- g_value_set_boolean(&value, TRUE);
- g_object_set_property(G_OBJECT(item), "draw-as-radio", &value);
+ gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
break;
default:
g_warning("Generic Menuitem invalid check type: %d", check_type);
@@ -301,14 +317,14 @@ genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType
}
/**
- genericmenuitem_set_state:
- @item: #Genericmenuitem to set the type on
- @check_type: What is the state of the check
-
- Sets the state of the check in the menu item. It does
- not require, but isn't really useful if the type of
- check that the menuitem is set to #GENERICMENUITEM_CHECK_TYPE_NONE.
-*/
+ * genericmenuitem_set_state:
+ * @item: #Genericmenuitem to set the type on
+ * @check_type: What is the state of the check
+ *
+ * Sets the state of the check in the menu item. It does
+ * not require, but isn't really useful if the type of
+ * check that the menuitem is set to #GENERICMENUITEM_CHECK_TYPE_NONE.
+ */
void
genericmenuitem_set_state (Genericmenuitem * item, GenericmenuitemState state)
{
@@ -319,38 +335,32 @@ genericmenuitem_set_state (Genericmenuitem * item, GenericmenuitemState state)
item->priv->state = state;
GtkCheckMenuItem * check = GTK_CHECK_MENU_ITEM(item);
-
- gboolean old_active = check->active;
- gboolean old_inconsist = check->inconsistent;
+ gboolean goal_active = FALSE;
switch (item->priv->state) {
case GENERICMENUITEM_STATE_UNCHECKED:
- check->active = FALSE;
- check->inconsistent = FALSE;
+ goal_active = FALSE;
+ gtk_check_menu_item_set_inconsistent (check, FALSE);
break;
case GENERICMENUITEM_STATE_CHECKED:
- check->active = TRUE;
- check->inconsistent = FALSE;
+ goal_active = TRUE;
+ gtk_check_menu_item_set_inconsistent (check, FALSE);
break;
case GENERICMENUITEM_STATE_INDETERMINATE:
- check->active = TRUE;
- check->inconsistent = TRUE;
+ goal_active = TRUE;
+ gtk_check_menu_item_set_inconsistent (check, TRUE);
break;
default:
g_warning("Generic Menuitem invalid check state: %d", state);
return;
}
- if (old_active != check->active) {
- g_object_notify(G_OBJECT(item), "active");
- }
-
- if (old_inconsist != check->inconsistent) {
- g_object_notify(G_OBJECT(item), "inconsistent");
+ if (goal_active != gtk_check_menu_item_get_active(check)) {
+ if (parent_menuitem_activate != NULL) {
+ parent_menuitem_activate(GTK_MENU_ITEM(check));
+ }
}
- gtk_widget_queue_draw(GTK_WIDGET(item));
-
return;
}
@@ -367,11 +377,11 @@ set_image_helper (GtkWidget * widget, gpointer data)
}
/**
- genericmenuitem_set_image:
- @item: A #Genericmenuitem
- @image: The image to set as the image of @item
-
- Sets the image of the menu item.
+ * genericmenuitem_set_image:
+ * @item: A #Genericmenuitem
+ * @image: The image to set as the image of @item
+ *
+ * Sets the image of the menu item.
*/
void
genericmenuitem_set_image (Genericmenuitem * menu_item, GtkWidget * image)
@@ -429,13 +439,13 @@ genericmenuitem_set_image (Genericmenuitem * menu_item, GtkWidget * image)
}
/**
- genericmenuitem_get_image:
- @item: A #Genericmenuitem
-
- Returns the image if there is one.
-
- Return value: A pointer to the image of the item or #NULL
- if there isn't one.
+ * genericmenuitem_get_image:
+ * @item: A #Genericmenuitem
+ *
+ * Returns the image if there is one.
+ *
+ * Return value: (transfer none): A pointer to the image of the item or #NULL
+ * if there isn't one.
*/
GtkWidget *
genericmenuitem_get_image (Genericmenuitem * menu_item)
diff --git a/libdbusmenu-gtk/menu.c b/libdbusmenu-gtk/menu.c
index 4fa546b..c2720ac 100644
--- a/libdbusmenu-gtk/menu.c
+++ b/libdbusmenu-gtk/menu.c
@@ -44,16 +44,15 @@ enum {
};
/* Private */
-typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate;
struct _DbusmenuGtkMenuPrivate {
DbusmenuGtkClient * client;
+ DbusmenuMenuitem * root;
gchar * dbus_object;
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);
@@ -65,6 +64,8 @@ static void get_property (GObject * obj, guint id, GValue * value, GParamSpec *
/* Internal */
static void build_client (DbusmenuGtkMenu * self);
static void child_realized (DbusmenuMenuitem * child, gpointer userdata);
+static void remove_child_signals (gpointer data, gpointer user_data);
+static void root_changed (DbusmenuGtkClient * client, DbusmenuMenuitem * newroot, DbusmenuGtkMenu * menu);
/* GObject Stuff */
G_DEFINE_TYPE (DbusmenuGtkMenu, dbusmenu_gtkmenu, GTK_TYPE_MENU);
@@ -110,6 +111,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;
@@ -127,6 +130,12 @@ dbusmenu_gtkmenu_dispose (GObject *object)
{
DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(object);
+ /* Remove signals from the root */
+ if (priv->root != NULL) {
+ /* This will clear the root */
+ root_changed(priv->client, NULL, DBUSMENU_GTKMENU(object));
+ }
+
if (priv->client != NULL) {
g_object_unref(G_OBJECT(priv->client));
priv->client = NULL;
@@ -237,7 +246,7 @@ root_child_added (DbusmenuMenuitem * root, DbusmenuMenuitem * child, guint posit
GtkMenuItem * mi = dbusmenu_gtkclient_menuitem_get(priv->client, child);
if (mi != NULL) {
GtkWidget * item = GTK_WIDGET(mi);
- gtk_menu_insert(GTK_MENU(menu), item, dbusmenu_menuitem_get_position_realized(child, root));
+ gtk_menu_shell_insert(GTK_MENU_SHELL(menu), item, dbusmenu_menuitem_get_position_realized(child, root));
#ifdef MASSIVEDEBUGGING
menu_pos_t menu_pos;
menu_pos.mi = mi;
@@ -271,6 +280,10 @@ root_child_delete (DbusmenuMenuitem * root, DbusmenuMenuitem * child, DbusmenuGt
#ifdef MASSIVEDEBUGGING
g_debug("Root child deleted");
#endif
+
+ /* Remove signal for realized */
+ remove_child_signals(child, menu);
+
DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(menu);
GtkWidget * item = GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(priv->client, child));
if (item != NULL) {
@@ -299,7 +312,7 @@ child_realized (DbusmenuMenuitem * child, gpointer userdata)
GtkWidget * child_widget = GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(priv->client, child));
if (child_widget != NULL) {
- gtk_menu_append(menu, child_widget);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), child_widget);
gtk_menu_reorder_child(GTK_MENU(menu), child_widget, dbusmenu_menuitem_get_position_realized(child, dbusmenu_client_get_root(DBUSMENU_CLIENT(priv->client))));
} else {
g_warning("Child is realized, but doesn't have a GTK Widget!");
@@ -308,15 +321,41 @@ child_realized (DbusmenuMenuitem * child, gpointer userdata)
return;
}
+/* Remove any signals we attached to children -- just realized right now */
+static void
+remove_child_signals (gpointer data, gpointer user_data)
+{
+ g_signal_handlers_disconnect_by_func(G_OBJECT(data), child_realized, user_data);
+ return;
+}
+
/* When the root menuitem changes we need to resetup things so that
we're back in the game. */
static void
root_changed (DbusmenuGtkClient * client, DbusmenuMenuitem * newroot, DbusmenuGtkMenu * menu) {
+ DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(menu);
+
+ /* Clear out our interest in the old root */
+ if (priv->root != NULL) {
+ GList * children = dbusmenu_menuitem_get_children(priv->root);
+ g_list_foreach(children, remove_child_signals, menu);
+
+ g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), root_child_added, menu);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), root_child_moved, menu);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), root_child_delete, menu);
+
+ g_object_unref(priv->root);
+ priv->root = NULL;
+ }
+
if (newroot == NULL) {
gtk_widget_hide(GTK_WIDGET(menu));
return;
}
+ priv->root = newroot;
+ g_object_ref(priv->root);
+
g_signal_connect(G_OBJECT(newroot), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(root_child_added), menu);
g_signal_connect(G_OBJECT(newroot), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(root_child_moved), menu);
g_signal_connect(G_OBJECT(newroot), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(root_child_delete), menu);
@@ -359,15 +398,15 @@ build_client (DbusmenuGtkMenu * self)
/* Public API */
/**
- dbusmenu_gtkmenu_new:
- @dbus_name: Name of the #DbusmenuServer on DBus
- @dbus_name: Name of the object on the #DbusmenuServer
-
- Creates a new #DbusmenuGtkMenu object and creates a #DbusmenuClient
- that connects across DBus to a #DbusmenuServer.
-
- Return value: A new #DbusmenuGtkMenu sync'd with a server
-*/
+ * dbusmenu_gtkmenu_new:
+ * @dbus_name: Name of the #DbusmenuServer on DBus
+ * @dbus_object: Name of the object on the #DbusmenuServer
+ *
+ * Creates a new #DbusmenuGtkMenu object and creates a #DbusmenuClient
+ * that connects across DBus to a #DbusmenuServer.
+ *
+ * Return value: A new #DbusmenuGtkMenu sync'd with a server
+ */
DbusmenuGtkMenu *
dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object)
{
@@ -378,14 +417,14 @@ dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object)
}
/**
- dbusmenu_gtkmenu_get_client:
- @menu: The #DbusmenuGtkMenu to get the client from
-
- An accessor for the client that this menu is using to
- communicate with the server.
-
- Return value: A valid #DbusmenuGtkClient or NULL on error.
-*/
+ * dbusmenu_gtkmenu_get_client:
+ * @menu: The #DbusmenuGtkMenu to get the client from
+ *
+ * An accessor for the client that this menu is using to
+ * communicate with the server.
+ *
+ * Return value: (transfer none): A valid #DbusmenuGtkClient or NULL on error.
+ */
DbusmenuGtkClient *
dbusmenu_gtkmenu_get_client (DbusmenuGtkMenu * menu)
{
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..508b43f 100644
--- a/libdbusmenu-gtk/menuitem.c
+++ b/libdbusmenu-gtk/menuitem.c
@@ -27,19 +27,21 @@ 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>
/**
- dbusmenu_menuitem_property_set_image:
- @menuitem: The #DbusmenuMenuitem to set the property on.
- @property: Name of the property to set.
- @data: The image to place on the property.
-
- This function takes the pixbuf that is stored in @data and
- turns it into a base64 encoded PNG so that it can be placed
- onto a standard #DbusmenuMenuitem property.
-
- Return value: Whether the function was able to set the property
- or not.
+ * dbusmenu_menuitem_property_set_image:
+ * @menuitem: The #DbusmenuMenuitem to set the property on.
+ * @property: Name of the property to set.
+ * @data: The image to place on the property.
+ *
+ * This function takes the pixbuf that is stored in @data and
+ * turns it into a base64 encoded PNG so that it can be placed
+ * onto a standard #DbusmenuMenuitem property.
+ *
+ * Return value: Whether the function was able to set the property
+ * or not.
*/
gboolean
dbusmenu_menuitem_property_set_image (DbusmenuMenuitem * menuitem, const gchar * property, const GdkPixbuf * data)
@@ -75,17 +77,17 @@ dbusmenu_menuitem_property_set_image (DbusmenuMenuitem * menuitem, const gchar *
}
/**
- dbusmenu_menuitem_property_get_image:
- @menuitem: The #DbusmenuMenuite to look for the property on
- @property: The name of the property to look for.
-
- This function looks on the menu item for a property by the
- name of @property. If one exists it tries to turn it into
- a #GdkPixbuf. It assumes that the property is a base64 encoded
- PNG file like the one created by #dbusmenu_menuite_property_set_image.
-
- Return value: A pixbuf or #NULL to signal error.
-*/
+ * dbusmenu_menuitem_property_get_image:
+ * @menuitem: The #DbusmenuMenuitem to look for the property on
+ * @property: The name of the property to look for.
+ *
+ * This function looks on the menu item for a property by the
+ * name of @property. If one exists it tries to turn it into
+ * a #GdkPixbuf. It assumes that the property is a base64 encoded
+ * PNG file like the one created by #dbusmenu_menuite_property_set_image.
+ *
+ * Return value: (transfer full): A pixbuf or #NULL to signal error.
+ */
GdkPixbuf *
dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar * property)
{
@@ -128,3 +130,184 @@ 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);
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ if (modifier & GDK_CONTROL_MASK) {
+ g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_CONTROL);
+ }
+ if (modifier & GDK_MOD1_MASK) {
+ g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_ALT);
+ }
+ if (modifier & GDK_SHIFT_MASK) {
+ g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_SHIFT);
+ }
+ if (modifier & GDK_SUPER_MASK) {
+ g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_SUPER);
+ }
+
+ const gchar * keyname = gdk_keyval_name(key);
+ g_variant_builder_add(&builder, "s", keyname);
+
+ GVariant * inside = g_variant_builder_end(&builder);
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add_value(&builder, inside);
+ GVariant * outsidevariant = g_variant_builder_end(&builder);
+
+ return dbusmenu_menuitem_property_set_variant(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT, outsidevariant);
+}
+
+/* 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);
+}
+
+/**
+ * dbusmenu_menuitem_property_get_shortcut:
+ * @menuitem: The #DbusmenuMenuitem to get the shortcut off
+ * @key: (out): Location to put the key value
+ * @modifier: (out): 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));
+
+ GVariant * wrapper = dbusmenu_menuitem_property_get_variant(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT);
+ if (wrapper == NULL) {
+ return;
+ }
+
+ if (g_variant_n_children(wrapper) != 1) {
+ g_warning("Unable to parse shortcut, too many keys");
+ g_variant_unref(wrapper);
+ return;
+ }
+
+ GVariantIter iter;
+ g_variant_iter_init(&iter, g_variant_get_child_value(wrapper, 0));
+ gchar * string;
+
+ while(g_variant_iter_next(&iter, "s", &string)) {
+ if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_CONTROL) == 0) {
+ *modifier |= GDK_CONTROL_MASK;
+ } else if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_ALT) == 0) {
+ *modifier |= GDK_MOD1_MASK;
+ } else if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_SHIFT) == 0) {
+ *modifier |= GDK_SHIFT_MASK;
+ } else if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_SUPER) == 0) {
+ *modifier |= GDK_SUPER_MASK;
+ } else {
+ GdkModifierType tempmod;
+ gtk_accelerator_parse(string, key, &tempmod);
+ }
+
+ g_free(string);
+ }
+
+ return;
+}
+
diff --git a/libdbusmenu-gtk/menuitem.h b/libdbusmenu-gtk/menuitem.h
index ff458de..6f009df 100644
--- a/libdbusmenu-gtk/menuitem.h
+++ b/libdbusmenu-gtk/menuitem.h
@@ -26,14 +26,25 @@ License version 3 and version 2.1 along with this program. If not, see
<http://www.gnu.org/licenses/>
*/
-#ifndef __DBUSMENU_GTKMENUITEM_H__
-#define __DBUSMENU_GTKMENUITEM_H__ 1
+#ifndef DBUSMENU_GTK_MENUITEM_H__
+#define DBUSMENU_GTK_MENUITEM_H__ 1
#include <glib.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libdbusmenu-glib/menuitem.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
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 * modifier);
+
+G_END_DECLS
+
#endif
diff --git a/libdbusmenu-gtk/parser.c b/libdbusmenu-gtk/parser.c
new file mode 100644
index 0000000..f516dde
--- /dev/null
+++ b/libdbusmenu-gtk/parser.c
@@ -0,0 +1,887 @@
+/*
+Parse to take a set of GTK Menus and turn them into something that can
+be sent over the wire.
+
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Numerous (check Bazaar)
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, 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 applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#include "parser.h"
+#include "menuitem.h"
+#include "serializablemenuitem.h"
+
+#define CACHED_MENUITEM "dbusmenu-gtk-parser-cached-item"
+#define PARSER_DATA "dbusmenu-gtk-parser-data"
+
+typedef struct _ParserData
+{
+ GtkWidget *label;
+ GtkAction *action;
+ GtkWidget *widget;
+ GtkWidget *shell;
+ GtkWidget *image;
+} ParserData;
+
+typedef struct _RecurseContext
+{
+ GtkWidget * toplevel;
+ DbusmenuMenuitem * parent;
+} RecurseContext;
+
+static void parse_menu_structure_helper (GtkWidget * widget, RecurseContext * recurse);
+static DbusmenuMenuitem * construct_dbusmenu_for_widget (GtkWidget * widget);
+static void accel_changed (GtkWidget * widget,
+ gpointer data);
+static void checkbox_toggled (GtkWidget * widget,
+ DbusmenuMenuitem * mi);
+static void update_icon (DbusmenuMenuitem * menuitem,
+ GtkImage * image);
+static GtkWidget * find_menu_label (GtkWidget * widget);
+static void label_notify_cb (GtkWidget * widget,
+ GParamSpec * pspec,
+ gpointer data);
+static void image_notify_cb (GtkWidget * widget,
+ GParamSpec * pspec,
+ gpointer data);
+static void action_notify_cb (GtkAction * action,
+ GParamSpec * pspec,
+ gpointer data);
+static void child_added_cb (GtkContainer * menu,
+ GtkWidget * widget,
+ gpointer data);
+static void theme_changed_cb (GtkIconTheme * theme,
+ gpointer data);
+static void item_activated (DbusmenuMenuitem * item,
+ guint timestamp,
+ gpointer user_data);
+static gboolean item_about_to_show (DbusmenuMenuitem * item,
+ gpointer user_data);
+static void widget_notify_cb (GtkWidget * widget,
+ GParamSpec * pspec,
+ gpointer data);
+static gboolean should_show_image (GtkImage * image);
+static void menuitem_notify_cb (GtkWidget * widget,
+ GParamSpec * pspec,
+ gpointer data);
+
+/**
+ * dbusmenu_gtk_parse_menu_structure:
+ * @widget: A #GtkMenuItem or #GtkMenuShell to turn into a #DbusmenuMenuitem
+ *
+ * Goes through the GTK structures and turns them into the appropraite
+ * Dbusmenu structures along with setting up all the relationships
+ * between the objects. It also stores the dbusmenu items as a cache
+ * on the GTK items so that they'll be reused if necissary.
+ *
+ * Return value: (transfer full): A dbusmenu item representing the menu structure
+ */
+DbusmenuMenuitem *
+dbusmenu_gtk_parse_menu_structure (GtkWidget * widget)
+{
+ g_return_val_if_fail(GTK_IS_MENU_ITEM(widget) || GTK_IS_MENU_SHELL(widget), NULL);
+
+ RecurseContext recurse = {0};
+
+ recurse.toplevel = gtk_widget_get_toplevel(widget);
+
+ parse_menu_structure_helper(widget, &recurse);
+
+ return recurse.parent;
+}
+
+/* Called when the dbusmenu item that we're keeping around
+ is finalized */
+static void
+dbusmenu_cache_freed (gpointer data, GObject * obj)
+{
+ /* If the dbusmenu item is killed we don't need to remove
+ the weak ref as well. */
+ g_object_steal_data(G_OBJECT(data), CACHED_MENUITEM);
+
+ ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(obj), PARSER_DATA);
+
+ if (pdata != NULL && pdata->label != NULL) {
+ g_signal_handlers_disconnect_by_func(pdata->label, G_CALLBACK(label_notify_cb), obj);
+ g_object_remove_weak_pointer(G_OBJECT(pdata->label), (gpointer*)&pdata->label);
+ }
+
+ if (pdata != NULL && pdata->action != NULL) {
+ g_signal_handlers_disconnect_by_func(pdata->action, G_CALLBACK(action_notify_cb), obj);
+ g_object_remove_weak_pointer(G_OBJECT(pdata->action), (gpointer*)&pdata->action);
+ }
+
+ if (pdata != NULL && pdata->widget != NULL) {
+ g_signal_handlers_disconnect_by_func(pdata->widget, G_CALLBACK(widget_notify_cb), obj);
+ g_object_remove_weak_pointer(G_OBJECT(pdata->widget), (gpointer*)&pdata->widget);
+ }
+
+ if (pdata != NULL && pdata->shell != NULL) {
+ g_signal_handlers_disconnect_by_func(pdata->shell, G_CALLBACK(child_added_cb), obj);
+ g_object_remove_weak_pointer(G_OBJECT(pdata->shell), (gpointer*)&pdata->shell);
+ }
+
+ if (pdata != NULL && pdata->image != NULL) {
+ g_signal_handlers_disconnect_by_func(pdata->image, G_CALLBACK(image_notify_cb), obj);
+ g_object_remove_weak_pointer(G_OBJECT(pdata->image), (gpointer*)&pdata->image);
+ }
+
+ return;
+}
+
+/* Called if we replace the cache on the object with a new
+ dbusmenu menuitem */
+static void
+object_cache_freed (gpointer data)
+{
+ // TODO: make this have access to both data and obj so we can call these
+ //if (!G_IS_OBJECT(obj)) return;
+ //g_object_weak_unref(G_OBJECT(obj), dbusmenu_cache_freed, data);
+ //dbusmenu_cache_freed(data, obj);
+
+ g_signal_handlers_disconnect_by_func(gtk_icon_theme_get_default(), G_CALLBACK(theme_changed_cb), data);
+
+ return;
+}
+
+/* Gets the positon of the child with its' parent if it has one.
+ Returns -1 if the position is unable to be calculated. */
+static gint
+get_child_position (GtkWidget * child)
+{
+ GtkWidget * parent = gtk_widget_get_parent (child);
+ if (parent == NULL || !GTK_IS_CONTAINER (parent))
+ return -1;
+
+ GList * children = gtk_container_get_children (GTK_CONTAINER (parent));
+ GList * iter;
+ gint position = 0;
+
+ for (iter = children; iter != NULL; iter = iter->next) {
+ if (iter->data == child)
+ break;
+ ++position;
+ }
+
+ g_list_free (children);
+
+ if (iter == NULL)
+ return -1;
+ else
+ return position;
+}
+
+/* Creates a new menu item that is attached to the widget and has
+ the data linkages hooked up. Also allocates the ParserData */
+static DbusmenuMenuitem *
+new_menuitem (GtkWidget * widget)
+{
+ DbusmenuMenuitem * item = dbusmenu_menuitem_new();
+
+ ParserData *pdata = g_new0 (ParserData, 1);
+ g_object_set_data_full(G_OBJECT(item), PARSER_DATA, pdata, g_free);
+
+ g_object_set_data_full(G_OBJECT(widget), CACHED_MENUITEM, item, object_cache_freed);
+ g_object_weak_ref(G_OBJECT(item), dbusmenu_cache_freed, widget);
+
+ return item;
+}
+
+static void
+parse_menu_structure_helper (GtkWidget * widget, RecurseContext * recurse)
+{
+
+ /* If this is a shell, then let's handle the items in it. */
+ if (GTK_IS_MENU_SHELL (widget)) {
+ /* Okay, this is a little janky and all.. but some applications update some
+ * menuitem properties such as sensitivity on the activate callback. This
+ * seems a little weird, but it's not our place to judge when all this code
+ * is so crazy. So we're going to get ever crazier and activate all the
+ * menus that are directly below the menubar and force the applications to
+ * update their sensitivity. The menus won't actually popup in the app
+ * window due to our gtk+ patches.
+ *
+ * Note that this will not force menuitems in submenus to be updated as well.
+ */
+ if (recurse->parent == NULL && GTK_IS_MENU_BAR(widget)) {
+ GList *children = gtk_container_get_children (GTK_CONTAINER (widget));
+ GList *iter;
+
+ for (iter = children; iter != NULL; iter = iter->next) {
+ gtk_menu_shell_activate_item (GTK_MENU_SHELL (widget),
+ iter->data,
+ TRUE);
+ }
+
+ g_list_free (children);
+ }
+
+ if (recurse->parent == NULL) {
+ recurse->parent = new_menuitem(widget);
+ }
+
+ ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(recurse->parent), PARSER_DATA);
+
+ pdata->shell = widget;
+ g_signal_connect (G_OBJECT (widget),
+ "child-added",
+ G_CALLBACK (child_added_cb),
+ recurse->parent);
+ g_object_add_weak_pointer(G_OBJECT (widget), (gpointer*)&pdata->shell);
+
+ gtk_container_foreach (GTK_CONTAINER (widget),
+ (GtkCallback)parse_menu_structure_helper,
+ recurse);
+ return;
+ }
+
+ if (GTK_IS_MENU_ITEM(widget)) {
+ DbusmenuMenuitem * thisitem = NULL;
+
+ /* Check to see if we're cached already */
+ gpointer pmi = g_object_get_data(G_OBJECT(widget), CACHED_MENUITEM);
+ if (pmi != NULL) {
+ thisitem = DBUSMENU_MENUITEM(pmi);
+ g_object_ref(G_OBJECT(thisitem));
+ }
+
+ /* We don't have one, so we'll need to build it */
+ if (thisitem == NULL) {
+ thisitem = construct_dbusmenu_for_widget (widget);
+
+ if (!gtk_widget_get_visible (widget)) {
+ g_signal_connect (G_OBJECT (widget),
+ "notify::visible",
+ G_CALLBACK (menuitem_notify_cb),
+ recurse->toplevel);
+ }
+
+ if (GTK_IS_TEAROFF_MENU_ITEM (widget)) {
+ dbusmenu_menuitem_property_set_bool (thisitem,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ FALSE);
+ }
+ }
+
+ /* Check to see if we're in our parents list of children, if we have
+ a parent. */
+ if (recurse->parent != NULL) {
+ GList * children = dbusmenu_menuitem_get_children (recurse->parent);
+ GList * peek = NULL;
+
+ if (children != NULL) {
+ peek = g_list_find (children, thisitem);
+ }
+
+ /* Oops, let's tell our parents about us */
+ if (peek == NULL) {
+ /* TODO: Should we set a weak ref on the parent? */
+ g_object_set_data (G_OBJECT (thisitem),
+ "dbusmenu-parent",
+ recurse->parent);
+ gint pos = get_child_position (widget);
+ if (pos >= 0)
+ dbusmenu_menuitem_child_add_position (recurse->parent,
+ thisitem,
+ pos);
+ else
+ dbusmenu_menuitem_child_append (recurse->parent,
+ thisitem);
+ }
+ }
+
+ GtkWidget *menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
+ if (menu != NULL) {
+ DbusmenuMenuitem * parent_save = recurse->parent;
+ recurse->parent = thisitem;
+ parse_menu_structure_helper (menu, recurse);
+ recurse->parent = parent_save;
+ }
+
+ if (recurse->parent == NULL) {
+ recurse->parent = thisitem;
+ } else {
+ g_object_unref(thisitem);
+ }
+ }
+
+ return;
+}
+
+/* Turn a widget into a dbusmenu item depending on the type of GTK
+ object that it is. */
+static DbusmenuMenuitem *
+construct_dbusmenu_for_widget (GtkWidget * widget)
+{
+ /* If it's a subclass of our serializable menu item then we can
+ use its own build function */
+ if (DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(widget)) {
+ DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(widget);
+ return dbusmenu_gtk_serializable_menu_item_build_menuitem(smi);
+ }
+
+ /* If it's a standard GTK Menu Item we need to do some of our own work */
+ if (GTK_IS_MENU_ITEM (widget))
+ {
+ DbusmenuMenuitem *mi = new_menuitem(widget);
+
+ ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(mi), PARSER_DATA);
+
+ gboolean visible = FALSE;
+ gboolean sensitive = FALSE;
+ if (GTK_IS_SEPARATOR_MENU_ITEM (widget))
+ {
+ dbusmenu_menuitem_property_set (mi,
+ "type",
+ "separator");
+
+ visible = gtk_widget_get_visible (widget);
+ sensitive = gtk_widget_get_sensitive (widget);
+ }
+ else
+ {
+ g_signal_connect (widget,
+ "accel-closures-changed",
+ G_CALLBACK (accel_changed),
+ mi);
+
+ if (GTK_IS_CHECK_MENU_ITEM (widget))
+ {
+ dbusmenu_menuitem_property_set (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
+ gtk_check_menu_item_get_draw_as_radio (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_RADIO : DBUSMENU_MENUITEM_TOGGLE_CHECK);
+
+ dbusmenu_menuitem_property_set_int (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
+ gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+
+ g_signal_connect (widget,
+ "activate",
+ G_CALLBACK (checkbox_toggled),
+ mi);
+ }
+
+ if (GTK_IS_IMAGE_MENU_ITEM (widget))
+ {
+ GtkWidget *image;
+
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (widget));
+
+ if (GTK_IS_IMAGE (image))
+ {
+ update_icon (mi, GTK_IMAGE (image));
+
+ /* Watch for theme changes because if gicon changes, we want to send a
+ different pixbuf. */
+ g_signal_connect(G_OBJECT(gtk_icon_theme_get_default()),
+ "changed", G_CALLBACK(theme_changed_cb), widget);
+
+ pdata->image = image;
+ g_signal_connect (G_OBJECT (image),
+ "notify",
+ G_CALLBACK (image_notify_cb),
+ mi);
+ g_object_add_weak_pointer(G_OBJECT (image), (gpointer*)&pdata->image);
+ }
+ }
+
+ GtkWidget *label = find_menu_label (widget);
+
+ if (label)
+ {
+ // Sometimes, an app will directly find and modify the label
+ // (like empathy), so watch the label especially for that.
+ dbusmenu_menuitem_property_set (mi,
+ "label",
+ gtk_label_get_text (GTK_LABEL (label)));
+
+ pdata->label = label;
+ g_signal_connect (G_OBJECT (label),
+ "notify",
+ G_CALLBACK (label_notify_cb),
+ mi);
+ g_object_add_weak_pointer(G_OBJECT (label), (gpointer*)&pdata->label);
+ }
+
+ if (GTK_IS_ACTIVATABLE (widget))
+ {
+ GtkActivatable *activatable = GTK_ACTIVATABLE (widget);
+
+ if (gtk_activatable_get_use_action_appearance (activatable))
+ {
+ GtkAction *action = gtk_activatable_get_related_action (activatable);
+
+ if (action)
+ {
+ visible = gtk_action_is_visible (action);
+ sensitive = gtk_action_is_sensitive (action);
+
+ pdata->action = action;
+ g_signal_connect_object (action, "notify",
+ G_CALLBACK (action_notify_cb),
+ mi,
+ G_CONNECT_AFTER);
+ g_object_add_weak_pointer(G_OBJECT (action), (gpointer*)&pdata->action);
+ }
+ }
+ }
+
+ if (!g_object_get_data (G_OBJECT (widget), "gtk-empty-menu-item") && !GTK_IS_TEAROFF_MENU_ITEM (widget))
+ {
+ visible = gtk_widget_get_visible (widget);
+ sensitive = gtk_widget_get_sensitive (widget);
+ }
+
+ dbusmenu_menuitem_property_set_shortcut_menuitem (mi, GTK_MENU_ITEM (widget));
+
+ g_signal_connect (G_OBJECT (mi),
+ DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (item_activated),
+ widget);
+
+ g_signal_connect (G_OBJECT (mi),
+ DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW,
+ G_CALLBACK (item_about_to_show),
+ widget);
+ }
+
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ visible);
+
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_ENABLED,
+ sensitive);
+
+ pdata->widget = widget;
+ g_signal_connect (widget,
+ "notify",
+ G_CALLBACK (widget_notify_cb),
+ mi);
+ g_object_add_weak_pointer(G_OBJECT (widget), (gpointer*)&pdata->widget);
+
+ return mi;
+ }
+
+ /* If it's none of those we're going to just create a
+ generic menuitem as a place holder for it. */
+ return new_menuitem(widget);
+}
+
+static void
+menuitem_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ if (pspec->name == g_intern_static_string ("visible"))
+ {
+ GtkWidget * new_toplevel = gtk_widget_get_toplevel (widget);
+ GtkWidget * old_toplevel = GTK_WIDGET(data);
+
+ if (new_toplevel == old_toplevel) {
+ /* TODO: Figure this out -> rebuild (context->bridge, window); */
+ }
+
+ /* We only care about this once, so let's disconnect now. */
+ g_signal_handlers_disconnect_by_func (widget,
+ G_CALLBACK (menuitem_notify_cb),
+ data);
+ }
+}
+
+static void
+accel_changed (GtkWidget *widget,
+ gpointer data)
+{
+ DbusmenuMenuitem *mi = (DbusmenuMenuitem *)data;
+ dbusmenu_menuitem_property_set_shortcut_menuitem (mi, GTK_MENU_ITEM (widget));
+}
+
+static void
+checkbox_toggled (GtkWidget *widget, DbusmenuMenuitem *mi)
+{
+ dbusmenu_menuitem_property_set_int (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
+ gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+}
+
+static void
+update_icon (DbusmenuMenuitem *menuitem, GtkImage *image)
+{
+ GdkPixbuf * pixbuf = NULL;
+ const gchar * icon_name = NULL;
+ GtkStockItem stock;
+ GIcon * gicon;
+ GtkIconInfo * info;
+ gint width;
+
+ if (image != NULL && should_show_image (image)) {
+ switch (gtk_image_get_storage_type (image)) {
+ case GTK_IMAGE_PIXBUF:
+ pixbuf = g_object_ref (gtk_image_get_pixbuf (image));
+ break;
+
+ case GTK_IMAGE_ICON_NAME:
+ gtk_image_get_icon_name (image, &icon_name, NULL);
+ break;
+
+ case GTK_IMAGE_STOCK:
+ gtk_image_get_stock (image, (gchar **) &icon_name, NULL);
+ if (gtk_stock_lookup (icon_name, &stock)) {
+ /* Now set label too */
+ const gchar * label = NULL;
+ label = dbusmenu_menuitem_property_get (menuitem,
+ DBUSMENU_MENUITEM_PROP_LABEL);
+ if (stock.label != NULL && label != NULL) {
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ stock.label);
+ }
+ }
+ break;
+
+ case GTK_IMAGE_GICON:
+ /* Load up a pixbuf and send that over. We don't bother differentiating
+ between icon-name gicons and pixbuf gicons because even when given a
+ icon-name gicon, there's no easy way to lookup which icon-name among
+ its set is present and should be used among the icon themes available.
+ So instead, we render to a pixbuf and watch icon theme changes. */
+ gtk_image_get_gicon (image, &gicon, NULL);
+ gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, NULL);
+ info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (),
+ gicon, width,
+ GTK_ICON_LOOKUP_FORCE_SIZE);
+ if (info != NULL) {
+ pixbuf = gtk_icon_info_load_icon (info, NULL);
+ gtk_icon_info_free (info);
+ }
+ break;
+
+ default:
+ g_debug ("Could not handle image type %i\n", gtk_image_get_storage_type (image));
+ break;
+ }
+ }
+
+ if (icon_name != NULL) {
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_NAME,
+ icon_name);
+ dbusmenu_menuitem_property_remove (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_DATA);
+ }
+ else if (pixbuf != NULL) {
+ dbusmenu_menuitem_property_remove (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_NAME);
+ dbusmenu_menuitem_property_set_image (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_DATA,
+ pixbuf);
+ }
+ else {
+ dbusmenu_menuitem_property_remove (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_NAME);
+ dbusmenu_menuitem_property_remove (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_DATA);
+ }
+
+ if (pixbuf != NULL) {
+ g_object_unref (pixbuf);
+ }
+}
+
+static GtkWidget *
+find_menu_label (GtkWidget *widget)
+{
+ GtkWidget *label = NULL;
+
+ if (GTK_IS_LABEL (widget))
+ return widget;
+
+ if (GTK_IS_CONTAINER (widget))
+ {
+ GList *children;
+ GList *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (widget));
+
+ for (l = children; l; l = l->next)
+ {
+ label = find_menu_label (l->data);
+
+ if (label)
+ break;
+ }
+
+ g_list_free (children);
+ }
+
+ return label;
+}
+
+static void
+label_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *child = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("label"))
+ {
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ gtk_label_get_text (GTK_LABEL (widget)));
+ }
+}
+
+static void
+image_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *mi = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("file") ||
+ pspec->name == g_intern_static_string ("gicon") ||
+ pspec->name == g_intern_static_string ("icon-name") ||
+ pspec->name == g_intern_static_string ("icon-set") ||
+ pspec->name == g_intern_static_string ("image") ||
+ pspec->name == g_intern_static_string ("mask") ||
+ pspec->name == g_intern_static_string ("pixbuf") ||
+ pspec->name == g_intern_static_string ("pixbuf-animation") ||
+ pspec->name == g_intern_static_string ("pixmap") ||
+ pspec->name == g_intern_static_string ("stock") ||
+ pspec->name == g_intern_static_string ("storage-type"))
+ {
+ update_icon (mi, GTK_IMAGE (widget));
+ }
+}
+
+static void
+action_notify_cb (GtkAction *action,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *mi = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("sensitive"))
+ {
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_ENABLED,
+ gtk_action_is_sensitive (action));
+ }
+ else if (pspec->name == g_intern_static_string ("visible"))
+ {
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ gtk_action_is_visible (action));
+ }
+ else if (pspec->name == g_intern_static_string ("active"))
+ {
+ dbusmenu_menuitem_property_set_int (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+ }
+ else if (pspec->name == g_intern_static_string ("label"))
+ {
+ dbusmenu_menuitem_property_set (mi,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ gtk_action_get_label (action));
+ }
+}
+
+static void
+item_activated (DbusmenuMenuitem *item, guint timestamp, gpointer user_data)
+{
+ GtkWidget *child;
+
+ if (user_data != NULL)
+ {
+ child = (GtkWidget *)user_data;
+
+ if (GTK_IS_MENU_ITEM (child))
+ {
+ gtk_menu_item_activate (GTK_MENU_ITEM (child));
+ }
+ }
+}
+
+static gboolean
+item_about_to_show (DbusmenuMenuitem *item, gpointer user_data)
+{
+ GtkWidget *child;
+
+ if (user_data != NULL)
+ {
+ child = (GtkWidget *)user_data;
+
+ if (GTK_IS_MENU_ITEM (child))
+ {
+ // Only called for items with submens. So we activate it here in
+ // case the program dynamically creates menus (like empathy does)
+ gtk_menu_item_activate (GTK_MENU_ITEM (child));
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+widget_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *child = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("sensitive"))
+ {
+ dbusmenu_menuitem_property_set_bool (child,
+ DBUSMENU_MENUITEM_PROP_ENABLED,
+ gtk_widget_get_sensitive (widget));
+ }
+ else if (pspec->name == g_intern_static_string ("label"))
+ {
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ gtk_menu_item_get_label (GTK_MENU_ITEM (widget)));
+ }
+ else if (pspec->name == g_intern_static_string ("visible"))
+ {
+ dbusmenu_menuitem_property_set_bool (child,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ gtk_widget_get_visible (widget));
+ }
+ else if (pspec->name == g_intern_static_string ("image") ||
+ pspec->name == g_intern_static_string ("always-show-image"))
+ {
+ GtkWidget *image;
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (widget));
+ update_icon (child, GTK_IMAGE (image));
+ }
+ else if (pspec->name == g_intern_static_string ("parent"))
+ {
+ /*
+ * We probably should have added a 'remove' method to the
+ * UbuntuMenuProxy early on, but it's late in the cycle now.
+ */
+ if (gtk_widget_get_parent (widget) == NULL)
+ {
+ g_signal_handlers_disconnect_by_func (widget,
+ G_CALLBACK (widget_notify_cb),
+ child);
+
+ DbusmenuMenuitem *parent = g_object_get_data (G_OBJECT (child), "dbusmenu-parent");
+
+ if (DBUSMENU_IS_MENUITEM (parent) && DBUSMENU_IS_MENUITEM (child))
+ {
+ dbusmenu_menuitem_child_delete (parent, child);
+ }
+ }
+ }
+ else if (pspec->name == g_intern_static_string ("submenu"))
+ {
+ /* The underlying submenu got swapped out. Let's see what it is now. */
+ /* First, delete any children that may exist currently. */
+ DbusmenuMenuitem * item = DBUSMENU_MENUITEM(g_object_get_data(G_OBJECT(widget), CACHED_MENUITEM));
+ if (item != NULL)
+ {
+ GList * children = dbusmenu_menuitem_take_children (item);
+ GList * child = children;
+ while (child != NULL) {
+ g_object_unref (G_OBJECT(child->data));
+ child = child->next;
+ }
+ g_list_free(children);
+ }
+
+ /* Now parse new submenu. */
+ RecurseContext recurse = {0};
+ recurse.toplevel = gtk_widget_get_toplevel(widget);
+ recurse.parent = item;
+
+ if (item != NULL) {
+ GtkWidget * menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
+ parse_menu_structure_helper(menu, &recurse);
+ } else {
+ /* Note: it would be really odd that we wouldn't have a cached
+ item, but we should handle that appropriately. */
+ parse_menu_structure_helper(widget, &recurse);
+ g_object_unref(G_OBJECT(recurse.parent));
+ }
+ }
+}
+
+/* A child item was added to a menu we're watching. Let's try to integrate it. */
+static void
+child_added_cb (GtkContainer *menu, GtkWidget *widget, gpointer data)
+{
+ DbusmenuMenuitem *menuitem = (DbusmenuMenuitem *)data;
+
+ RecurseContext recurse = {0};
+ recurse.toplevel = gtk_widget_get_toplevel(GTK_WIDGET(menu));
+ recurse.parent = menuitem;
+
+ parse_menu_structure_helper(widget, &recurse);
+}
+
+static void
+theme_changed_cb (GtkIconTheme *theme, gpointer data)
+{
+ GtkWidget *image;
+
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (data));
+
+ gpointer pmi = g_object_get_data(G_OBJECT(data), CACHED_MENUITEM);
+ if (pmi != NULL) {
+ update_icon(DBUSMENU_MENUITEM(pmi), GTK_IMAGE(image));
+ }
+
+ /* Switch signal to new theme */
+ g_signal_handlers_disconnect_by_func(theme, G_CALLBACK(theme_changed_cb), data);
+ g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(theme_changed_cb), data);
+}
+
+static gboolean
+should_show_image (GtkImage *image)
+{
+ GtkWidget *item;
+
+ item = gtk_widget_get_ancestor (GTK_WIDGET (image),
+ GTK_TYPE_IMAGE_MENU_ITEM);
+
+ if (item)
+ {
+ GtkSettings *settings;
+ gboolean gtk_menu_images;
+
+ settings = gtk_widget_get_settings (item);
+
+ g_object_get (settings, "gtk-menu-images", &gtk_menu_images, NULL);
+
+ if (gtk_menu_images)
+ return TRUE;
+
+ return gtk_image_menu_item_get_always_show_image (GTK_IMAGE_MENU_ITEM (item));
+ }
+
+ return FALSE;
+}
+
diff --git a/libdbusmenu-gtk/parser.h b/libdbusmenu-gtk/parser.h
new file mode 100644
index 0000000..a40d709
--- /dev/null
+++ b/libdbusmenu-gtk/parser.h
@@ -0,0 +1,37 @@
+/*
+Parse to take a set of GTK Menus and turn them into something that can
+be sent over the wire.
+
+Copyright 2011 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 either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, 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 applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifndef DBUSMENU_GTK_PARSER_H__
+#define DBUSMENU_GTK_PARSER_H__
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <gtk/gtk.h>
+
+DbusmenuMenuitem * dbusmenu_gtk_parse_menu_structure (GtkWidget * widget);
+
+#endif /* DBUSMENU_GTK_PARSER_H__ */
diff --git a/libdbusmenu-gtk/serializablemenuitem.c b/libdbusmenu-gtk/serializablemenuitem.c
new file mode 100644
index 0000000..29f83a8
--- /dev/null
+++ b/libdbusmenu-gtk/serializablemenuitem.c
@@ -0,0 +1,288 @@
+/*
+An object to act as a base class for easy GTK widgets that can be
+transfered over dbusmenu.
+
+Copyright 2011 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 either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, 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 applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "client.h"
+#include "serializablemenuitem.h"
+
+/*
+ DbusmenuGtkSerializableMenuItemPrivate:
+ @mi: Menuitem to watch the property changes from
+*/
+struct _DbusmenuGtkSerializableMenuItemPrivate {
+ DbusmenuMenuitem * mi;
+};
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_MENUITEM
+};
+
+/* Private macro, only used in object init */
+#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItemPrivate))
+
+/* Function prototypes */
+static void dbusmenu_gtk_serializable_menu_item_class_init (DbusmenuGtkSerializableMenuItemClass *klass);
+static void dbusmenu_gtk_serializable_menu_item_init (DbusmenuGtkSerializableMenuItem *self);
+static void dbusmenu_gtk_serializable_menu_item_dispose (GObject *object);
+static void dbusmenu_gtk_serializable_menu_item_finalize (GObject *object);
+static void set_property (GObject * obj,
+ guint id,
+ const GValue * value,
+ GParamSpec * pspec);
+static void get_property (GObject * obj,
+ guint id,
+ GValue * value,
+ GParamSpec * pspec);
+
+/* GObject boiler plate */
+G_DEFINE_TYPE (DbusmenuGtkSerializableMenuItem, dbusmenu_gtk_serializable_menu_item, GTK_TYPE_MENU_ITEM);
+
+/* Initialize the stuff in the class structure */
+static void
+dbusmenu_gtk_serializable_menu_item_class_init (DbusmenuGtkSerializableMenuItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (DbusmenuGtkSerializableMenuItemPrivate));
+
+ object_class->dispose = dbusmenu_gtk_serializable_menu_item_dispose;
+ object_class->finalize = dbusmenu_gtk_serializable_menu_item_finalize;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ g_object_class_install_property (object_class, PROP_MENUITEM,
+ g_param_spec_object(DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_PROP_MENUITEM, "DBusmenu Menuitem attached to item",
+ "A menuitem who's properties are being watched and where changes should be watched for updates. It is the responsibility of subclasses to set up the signal handlers for those property changes.",
+ DBUSMENU_TYPE_MENUITEM,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ return;
+}
+
+/* Initialize the object structures and private structure */
+static void
+dbusmenu_gtk_serializable_menu_item_init (DbusmenuGtkSerializableMenuItem *self)
+{
+ self->priv = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_PRIVATE(self);
+
+ self->priv->mi = NULL;
+
+ return;
+}
+
+/* Free all references to objects */
+static void
+dbusmenu_gtk_serializable_menu_item_dispose (GObject *object)
+{
+ DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(object);
+ g_return_if_fail(smi != NULL);
+
+ if (smi->priv->mi != NULL) {
+ g_object_unref(G_OBJECT(smi->priv->mi));
+ smi->priv->mi = NULL;
+ }
+
+
+ G_OBJECT_CLASS (dbusmenu_gtk_serializable_menu_item_parent_class)->dispose (object);
+ return;
+}
+
+/* Free memory */
+static void
+dbusmenu_gtk_serializable_menu_item_finalize (GObject *object)
+{
+
+
+
+ G_OBJECT_CLASS (dbusmenu_gtk_serializable_menu_item_parent_class)->finalize (object);
+ return;
+}
+
+/* Set an object property */
+static void
+set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
+{
+ DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(obj);
+
+ switch (id) {
+ case PROP_MENUITEM:
+ smi->priv->mi = g_value_get_object(value);
+ break;
+ default:
+ g_return_if_reached();
+ break;
+ }
+
+ return;
+}
+
+/* Get an object property */
+static void
+get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
+{
+ DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(obj);
+
+ switch (id) {
+ case PROP_MENUITEM:
+ g_value_set_object(value, smi->priv->mi);
+ break;
+ default:
+ g_return_if_reached();
+ break;
+ }
+
+ return;
+}
+
+/**
+ * dbusmenu_gtk_serializable_menu_item_build_menuitem:
+ * @smi: #DbusmenuGtkSerializableMenuItem to build a #DbusmenuMenuitem mirroring
+ *
+ * This function is for menu items that are instanciated from
+ * GTK and have their properites set using GTK functions. This
+ * builds a #DbusmenuMenuitem that then has the properties that
+ * should be sent over the bus to create a new item of this
+ * type on the other side.
+ *
+ * Return value: (transfer full): A #DbusmenuMenuitem who's values will be
+ * set by this object.
+ */
+DbusmenuMenuitem *
+dbusmenu_gtk_serializable_menu_item_build_menuitem (DbusmenuGtkSerializableMenuItem * smi)
+{
+ g_return_val_if_fail(DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(smi), NULL);
+
+ DbusmenuGtkSerializableMenuItemClass * klass = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_CLASS(smi);
+ if (klass->build_dbusmenu_menuitem != NULL) {
+ return klass->build_dbusmenu_menuitem(smi);
+ }
+
+ return NULL;
+}
+
+/* Callback to the generic type handler */
+typedef struct _type_handler_t type_handler_t;
+struct _type_handler_t {
+ DbusmenuGtkSerializableMenuItemClass * class;
+ GType type;
+};
+
+/* Handle the type with this item. */
+static gboolean
+type_handler (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data)
+{
+ type_handler_t * th = (type_handler_t *)user_data;
+
+ DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(g_object_new(th->type, NULL));
+ g_return_val_if_fail(smi != NULL, FALSE);
+
+ dbusmenu_gtk_serializable_menu_item_set_menuitem(smi, newitem);
+ dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, GTK_MENU_ITEM(smi), parent);
+
+ return TRUE;
+}
+
+/* Destruction is inevitable */
+static void
+type_destroy_handler (DbusmenuClient * client, const gchar * type, gpointer user_data)
+{
+ g_return_if_fail(user_data != NULL);
+ type_handler_t * th = (type_handler_t *)user_data;
+ g_type_class_unref(th->class);
+ g_free(user_data);
+ return;
+}
+
+/**
+ * dbusmenu_gtk_serializable_menu_item_register_to_client:
+ * @client: #DbusmenuClient that we should register a type at.
+ * @item_type: The #GType of a class that is a subclass of #DbusmenuGtkSerializableMenuItem
+ *
+ * Registers a generic handler for dealing with all subclasses of
+ * #DbusmenuGtkSerializableMenuItem. This handler responds to the callback,
+ * creates a new object and attaches it to the appropriate #DbusmenuMenuitem
+ * object.
+ */
+void
+dbusmenu_gtk_serializable_menu_item_register_to_client (DbusmenuClient * client, GType item_type)
+{
+ g_return_if_fail(g_type_is_a(item_type, DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM));
+
+ gpointer type_class = g_type_class_ref(item_type);
+ g_return_if_fail(type_class != NULL);
+
+ DbusmenuGtkSerializableMenuItemClass * class = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_CLASS(type_class);
+
+ if (class->get_type_string == NULL) {
+ g_type_class_unref(type_class);
+ g_error("No 'get_type_string' in subclass of DbusmenuGtkSerializableMenuItem");
+ return;
+ }
+
+ /* Register type */
+ type_handler_t * th = g_new0(type_handler_t, 1);
+ th->class = class;
+ th->type = item_type;
+ if (!dbusmenu_client_add_type_handler_full(client, class->get_type_string(), type_handler, th, type_destroy_handler)) {
+ type_destroy_handler(client, class->get_type_string(), th);
+ }
+
+ /* Register defaults */
+ /* TODO: Need API on another branch */
+
+ return;
+}
+
+/**
+ * dbusmenu_gtk_serializable_menu_item_set_menuitem:
+ * @smi: #DbusmenuGtkSerializableMenuItem to set the @DbusmenuGtkSerializableMenuItem::dbusmenu-menuitem of
+ * @mi: Menuitem to get the properties from
+ *
+ * This function is used on the server side to signal to the object
+ * that it should get its' property change events from @mi instead
+ * of expecting calls to its' API. A call to this function sets the
+ * property and subclasses should listen to the notify signal to
+ * pick up this property being set.
+ */
+void
+dbusmenu_gtk_serializable_menu_item_set_menuitem (DbusmenuGtkSerializableMenuItem * smi, DbusmenuMenuitem * mi)
+{
+ g_return_if_fail(DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(smi));
+ g_return_if_fail(mi != NULL);
+
+ smi->priv->mi = mi;
+ g_object_notify(G_OBJECT(smi), DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_PROP_MENUITEM);
+
+ return;
+}
diff --git a/libdbusmenu-gtk/serializablemenuitem.h b/libdbusmenu-gtk/serializablemenuitem.h
new file mode 100644
index 0000000..db28a24
--- /dev/null
+++ b/libdbusmenu-gtk/serializablemenuitem.h
@@ -0,0 +1,115 @@
+/*
+An object to act as a base class for easy GTK widgets that can be
+transfered over dbusmenu.
+
+Copyright 2011 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 either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, 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 applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifndef DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_H__
+#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_H__ 1
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <libdbusmenu-glib/menuitem.h>
+#include <libdbusmenu-glib/client.h>
+
+G_BEGIN_DECLS
+
+#define DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM (dbusmenu_gtk_serializable_menu_item_get_type ())
+#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItem))
+#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItemClass))
+#define DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM))
+#define DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM))
+#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItemClass))
+
+#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_PROP_MENUITEM "dbusmenu-menuitem"
+
+typedef struct _DbusmenuGtkSerializableMenuItem DbusmenuGtkSerializableMenuItem;
+typedef struct _DbusmenuGtkSerializableMenuItemClass DbusmenuGtkSerializableMenuItemClass;
+typedef struct _DbusmenuGtkSerializableMenuItemPrivate DbusmenuGtkSerializableMenuItemPrivate;
+
+/**
+ DbusmenuGtkSerializableMenuItemClass:
+ @parent_class: Inherit from GtkMenuItem
+ @get_type_string: Static function to get a string describing this type
+ @get_default_properties: Return a hashtable of defaults for the menu item type
+ @build_dbusmenu_menuitem: Build a menuitem that can be sent over dbus
+ @_dbusmenu_gtk_serializable_menu_item_reserved1: Reserved for future use.
+ @_dbusmenu_gtk_serializable_menu_item_reserved2: Reserved for future use.
+ @_dbusmenu_gtk_serializable_menu_item_reserved3: Reserved for future use.
+ @_dbusmenu_gtk_serializable_menu_item_reserved4: Reserved for future use.
+ @_dbusmenu_gtk_serializable_menu_item_reserved5: Reserved for future use.
+ @_dbusmenu_gtk_serializable_menu_item_reserved6: Reserved for future use.
+*/
+struct _DbusmenuGtkSerializableMenuItemClass {
+ GtkMenuItemClass parent_class;
+
+ /* Subclassable functions */
+ const gchar * (*get_type_string) (void);
+ GHashTable * (*get_default_properties) (void);
+
+ DbusmenuMenuitem * (*build_dbusmenu_menuitem) (DbusmenuGtkSerializableMenuItem * smi);
+
+ /* Signals */
+
+
+
+ /* Empty Space */
+ /*< Private >*/
+ void (*_dbusmenu_gtk_serializable_menu_item_reserved1) (void);
+ void (*_dbusmenu_gtk_serializable_menu_item_reserved2) (void);
+ void (*_dbusmenu_gtk_serializable_menu_item_reserved3) (void);
+ void (*_dbusmenu_gtk_serializable_menu_item_reserved4) (void);
+ void (*_dbusmenu_gtk_serializable_menu_item_reserved5) (void);
+ void (*_dbusmenu_gtk_serializable_menu_item_reserved6) (void);
+};
+
+/**
+ DbusmenuGtkSerializableMenuItem:
+ @parent: Inherit from GtkMenuItem
+ @priv: Blind structure of private variables
+
+ The Serializable Menuitem provides a way for menu items to be created
+ that can easily be picked up by the Dbusmenu GTK Parser. This way
+ you can create custom items, and transport them across dbusmenu to
+ your menus or the appmenu on the other side of the bus. By providing
+ these function the parser has enough information to both serialize, and
+ deserialize on the other side, the menuitem you've so carefully created.
+*/
+struct _DbusmenuGtkSerializableMenuItem {
+ GtkMenuItem parent;
+
+ DbusmenuGtkSerializableMenuItemPrivate * priv;
+};
+
+GType dbusmenu_gtk_serializable_menu_item_get_type (void);
+
+DbusmenuMenuitem * dbusmenu_gtk_serializable_menu_item_build_menuitem (DbusmenuGtkSerializableMenuItem * smi);
+void dbusmenu_gtk_serializable_menu_item_register_to_client (DbusmenuClient * client, GType item_type);
+void dbusmenu_gtk_serializable_menu_item_set_menuitem (DbusmenuGtkSerializableMenuItem * smi, DbusmenuMenuitem * mi);
+
+G_END_DECLS
+
+#endif
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f1b50bc..61b3e69 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,19 +1,33 @@
DBUS_RUNNER=dbus-test-runner
+CLEANFILES=
+DISTCLEANFILES=
+EXTRA_DIST =
+
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-shortcut-python \
+ test-gtk-reorder \
+ test-gtk-submenu \
+ test-gtk-parser-test \
+ test-glib-simple-items.py
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,15 +35,63 @@ 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 \
+ test-gtk-parser
XVFB_RUN=". $(srcdir)/run-xvfb.sh"
+# for the GI tests, prefer/use the typelibs from the local build tree
+TESTS_ENVIRONMENT = env GI_TYPELIB_PATH=$(top_builddir)/libdbusmenu-glib:$(top_builddir)/libdbusmenu-gtk:$(GI_TYPELIB_PATH)
+
+######################
+# JSON Loader lib
+######################
+
+lib_LTLIBRARIES = libdbusmenu-jsonloader.la
+
+libdbusmenu_jsonloaderincludedir=$(includedir)/libdbusmenu-0.4/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-0.4.pc
+pkgconfigdir = $(libdir)/pkgconfig
+
######################
# Test GLib server
######################
@@ -79,6 +141,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 +253,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 = \
@@ -133,6 +267,8 @@ test_glib_objects_LDADD = \
../libdbusmenu-glib/libdbusmenu-glib.la \
$(DBUSMENUGLIB_LIBS)
+DISTCLEANFILES += $(OBJECT_XML_REPORT)
+
######################
# Test Glib Properties
######################
@@ -231,6 +367,68 @@ test_glib_simple_items_LDADD = \
../libdbusmenu-glib/libdbusmenu-glib.la \
$(DBUSMENUGLIB_LIBS)
+EXTRA_DIST += test-glib-simple-items.py
+
+######################
+# 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)
+
+DISTCLEANFILES += $(GTK_OBJECT_XML_REPORT)
+
+######################
+# Test GTK Parser
+######################
+
+GTK_PARSER_XML_REPORT = test-gtk-parser.xml
+
+test-gtk-parser-test: test-gtk-parser Makefile.am
+ @echo "#!/bin/bash" > $@
+ @echo $(XVFB_RUN) >> $@
+ @echo gtester --verbose -k -o $(GTK_PARSER_XML_REPORT) ./test-gtk-parser >> $@
+ @chmod +x $@
+
+test_gtk_parser_SOURCES = \
+ test-gtk-parser.c
+
+test_gtk_parser_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) \
+ $(DBUSMENUGTK_CFLAGS) \
+ -DSRCDIR="\"$(srcdir)\"" \
+ -Wall -Werror
+
+test_gtk_parser_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ ../libdbusmenu-gtk/libdbusmenu-gtk.la \
+ $(DBUSMENUGLIB_LIBS) \
+ $(DBUSMENUGTK_LIBS)
+
+DISTCLEANFILES += $(GTK_PARSER_XML_REPORT)
+
#########################
# Test GTK Label
#########################
@@ -253,6 +451,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 +471,59 @@ 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 Shortcut Python
+#########################
+
+test-gtk-shortcut-python: test-gtk-shortcut-server test-gtk-shortcut-client.py Makefile.am
+ @echo "#!/bin/bash" > $@
+ @echo $(XVFB_RUN) >> $@
+ @echo $(DBUS_RUNNER) --task $(srcdir)/test-gtk-shortcut-client.py --task-name Client --task ./test-gtk-shortcut-server --task-name Server --ignore-return >> $@
+ @chmod +x $@
+
+EXTRA_DIST += test-gtk-shortcut-client.py
+CLEANFILES += test-gtk-shortcut-client.pyc
+
+#########################
# Test GTK Reorder
#########################
@@ -297,6 +549,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
#########################
@@ -325,10 +617,11 @@ jsondir = $(datadir)/${PACKAGE}/json/
json_DATA = \
test-gtk-label.json
-EXTRA_DIST = \
+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,15 +640,15 @@ 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:
-rm -rf $(builddir)/mago.results
-DISTCLEANFILES = \
- $(TESTS) \
- $(OBJECT_XML_REPORT)
+DISTCLEANFILES += \
+ $(filter-out %.py, $(TESTS))
diff --git a/tests/dbusmenu-jsonloader-0.4.pc.in b/tests/dbusmenu-jsonloader-0.4.pc.in
new file mode 100644
index 0000000..62bfeb2
--- /dev/null
+++ b/tests/dbusmenu-jsonloader-0.4.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: dbusmenu-glib-0.4 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..36157dc
--- /dev/null
+++ b/tests/json-loader.c
@@ -0,0 +1,179 @@
+/*
+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"
+
+static GVariant * node2variant (JsonNode * node);
+
+static void
+array_foreach (JsonArray * array, guint index, JsonNode * node, gpointer user_data)
+{
+ GVariantBuilder * builder = (GVariantBuilder *)user_data;
+ GVariant * variant = node2variant(node);
+ if (variant != NULL) {
+ g_variant_builder_add_value(builder, variant);
+ }
+ return;
+}
+
+static void
+object_foreach (JsonObject * array, const gchar * member, JsonNode * node, gpointer user_data)
+{
+ GVariantBuilder * builder = (GVariantBuilder *)user_data;
+ GVariant * variant = node2variant(node);
+ if (variant != NULL) {
+ g_variant_builder_add(builder, "{sv}", member, variant);
+ }
+ return;
+}
+
+static GVariant *
+node2variant (JsonNode * node)
+{
+ if (node == NULL) {
+ return NULL;
+ }
+
+ if (JSON_NODE_TYPE(node) == JSON_NODE_VALUE) {
+ switch (json_node_get_value_type(node)) {
+ case G_TYPE_INT:
+ case G_TYPE_INT64:
+ return g_variant_new_int32(json_node_get_int(node));
+ case G_TYPE_DOUBLE:
+ case G_TYPE_FLOAT:
+ return g_variant_new_double(json_node_get_double(node));
+ case G_TYPE_BOOLEAN:
+ return g_variant_new_boolean(json_node_get_boolean(node));
+ case G_TYPE_STRING:
+ return g_variant_new_string(json_node_get_string(node));
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ if (JSON_NODE_TYPE(node) == JSON_NODE_ARRAY) {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ JsonArray * array = json_node_get_array(node);
+ json_array_foreach_element(array, array_foreach, &builder);
+
+ return g_variant_builder_end(&builder);
+ }
+
+ if (JSON_NODE_TYPE(node) == JSON_NODE_OBJECT) {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_DICTIONARY);
+
+ JsonObject * array = json_node_get_object(node);
+ json_object_foreach_member(array, object_foreach, &builder);
+
+ return g_variant_builder_end(&builder);
+ }
+
+ return NULL;
+}
+
+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);
+ GVariant * variant = node2variant(lnode);
+
+ if (variant != NULL) {
+ dbusmenu_menuitem_property_set_variant(mi, member, variant);
+ }
+ }
+
+ 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..2cc5439
--- /dev/null
+++ b/tests/test-glib-events-client.c
@@ -0,0 +1,138 @@
+/*
+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, GVariant * 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_variant_get_int32(data) != DATA_VALUE) {
+ g_debug("Data value pass fail got: %d", g_variant_get_int32(g_variant_get_child_value(data, 0)));
+ 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;
+ }
+
+ GVariant * data = g_variant_new_int32(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_debug("Main loop complete");
+ 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..ab72c6b
--- /dev/null
+++ b/tests/test-glib-events-server.c
@@ -0,0 +1,103 @@
+/*
+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 <gio/gio.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;
+}
+
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ 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);
+
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "org.dbusmenu.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ 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-layout-client.c b/tests/test-glib-layout-client.c
index 5ea0cf8..3afe042 100644
--- a/tests/test-glib-layout-client.c
+++ b/tests/test-glib-layout-client.c
@@ -81,6 +81,11 @@ layout_updated (DbusmenuClient * client, gpointer data)
g_debug("Layout Updated");
DbusmenuMenuitem * menuroot = dbusmenu_client_get_root(client);
+ if (menuroot == NULL) {
+ g_debug("Root NULL, waiting");
+ return;
+ }
+
layout_t * layout = &layouts[layouton];
if (!verify_root_to_layout(menuroot, layout)) {
diff --git a/tests/test-glib-layout-server.c b/tests/test-glib-layout-server.c
index 111e164..e289349 100644
--- a/tests/test-glib-layout-server.c
+++ b/tests/test-glib-layout-server.c
@@ -20,11 +20,7 @@ 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 <gio/gio.h>
#include <libdbusmenu-glib/server.h>
#include <libdbusmenu-glib/menuitem.h>
@@ -72,33 +68,38 @@ timer_func (gpointer data)
return TRUE;
}
-int
-main (int argc, char ** argv)
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
{
- 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))));
+ server = dbusmenu_server_new("/org/test");
- DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
- guint nameret = 0;
+ timer_func(NULL);
+ g_timeout_add(2500, timer_func, NULL);
- if (!org_freedesktop_DBus_request_name(bus_proxy, "org.dbusmenu.test", 0, &nameret, &error)) {
- g_error("Unable to call to request name");
- return 1;
- }
+ return;
+}
- if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
- g_error("Unable to get name");
- return 1;
- }
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
- server = dbusmenu_server_new("/org/test");
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
- timer_func(NULL);
- g_timeout_add(2500, timer_func, NULL);
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "org.dbusmenu.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ NULL);
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
diff --git a/tests/test-glib-objects.c b/tests/test-glib-objects.c
index 1d4f673..9c99280 100644
--- a/tests/test-glib-objects.c
+++ b/tests/test-glib-objects.c
@@ -77,17 +77,17 @@ test_object_menuitem_props_string (void)
{
/* Build a menu item */
DbusmenuMenuitem * item = dbusmenu_menuitem_new();
- const GValue * out = NULL;
+ GVariant * out = NULL;
/* Test to make sure it's a happy object */
g_assert(item != NULL);
/* Setting a string */
dbusmenu_menuitem_property_set(item, "string", "value");
- out = dbusmenu_menuitem_property_get_value(item, "string");
+ out = dbusmenu_menuitem_property_get_variant(item, "string");
g_assert(out != NULL);
- g_assert(G_VALUE_TYPE(out) == G_TYPE_STRING);
- g_assert(!g_strcmp0(g_value_get_string(out), "value"));
+ g_assert(g_variant_type_equal(g_variant_get_type(out), G_VARIANT_TYPE_STRING));
+ g_assert(!g_strcmp0(g_variant_get_string(out, NULL), "value"));
g_assert(!g_strcmp0(dbusmenu_menuitem_property_get(item, "string"), "value"));
g_object_unref(item);
@@ -101,17 +101,17 @@ test_object_menuitem_props_int (void)
{
/* Build a menu item */
DbusmenuMenuitem * item = dbusmenu_menuitem_new();
- const GValue * out = NULL;
+ GVariant * out = NULL;
/* Test to make sure it's a happy object */
g_assert(item != NULL);
/* Setting a string */
dbusmenu_menuitem_property_set_int(item, "int", 12345);
- out = dbusmenu_menuitem_property_get_value(item, "int");
+ out = dbusmenu_menuitem_property_get_variant(item, "int");
g_assert(out != NULL);
- g_assert(G_VALUE_TYPE(out) == G_TYPE_INT);
- g_assert(g_value_get_int(out) == 12345);
+ g_assert(g_variant_type_equal(g_variant_get_type(out), G_VARIANT_TYPE_INT32));
+ g_assert(g_variant_get_int32(out) == 12345);
g_assert(dbusmenu_menuitem_property_get_int(item, "int") == 12345);
g_object_unref(item);
@@ -125,18 +125,18 @@ test_object_menuitem_props_bool (void)
{
/* Build a menu item */
DbusmenuMenuitem * item = dbusmenu_menuitem_new();
- const GValue * out = NULL;
+ GVariant * out = NULL;
/* Test to make sure it's a happy object */
g_assert(item != NULL);
/* Setting a string */
dbusmenu_menuitem_property_set_bool(item, "boolean", TRUE);
- out = dbusmenu_menuitem_property_get_value(item, "boolean");
+ out = dbusmenu_menuitem_property_get_variant(item, "boolean");
g_assert(out != NULL);
- g_assert(G_VALUE_TYPE(out) == G_TYPE_BOOLEAN);
- g_assert(g_value_get_boolean(out));
- g_assert(dbusmenu_menuitem_property_get_int(item, "boolean"));
+ g_assert(g_variant_type_equal(g_variant_get_type(out), G_VARIANT_TYPE_BOOLEAN));
+ g_assert(g_variant_get_boolean(out));
+ /* g_assert(dbusmenu_menuitem_property_get_int(item, "boolean") == 0); */
g_object_unref(item);
@@ -177,7 +177,7 @@ test_object_menuitem_props_swap (void)
/* A helper to put a value into a pointer for eval. */
static void
-test_object_menuitem_props_signals_helper (DbusmenuMenuitem * mi, gchar * property, GValue * value, GValue ** out)
+test_object_menuitem_props_signals_helper (DbusmenuMenuitem * mi, gchar * property, GVariant * value, GVariant ** out)
{
if (!g_strcmp0(property, "swapper")) {
*out = value;
@@ -194,7 +194,7 @@ test_object_menuitem_props_signals (void)
{
/* Build a menu item */
DbusmenuMenuitem * item = dbusmenu_menuitem_new();
- GValue * out = NULL;
+ GVariant * out = NULL;
/* Test to make sure it's a happy object */
g_assert(item != NULL);
@@ -205,25 +205,25 @@ test_object_menuitem_props_signals (void)
/* Setting a boolean */
dbusmenu_menuitem_property_set_bool(item, "swapper", TRUE);
g_assert(out != NULL);
- g_assert(g_value_get_boolean(out));
+ g_assert(g_variant_get_boolean(out));
out = NULL;
/* Setting a int */
dbusmenu_menuitem_property_set_int(item, "swapper", 5432);
g_assert(out != NULL);
- g_assert(g_value_get_int(out) == 5432);
+ g_assert(g_variant_get_int32(out) == 5432);
out = NULL;
/* Setting a string */
dbusmenu_menuitem_property_set(item, "swapper", "mystring");
g_assert(out != NULL);
- g_assert(!g_strcmp0(g_value_get_string(out), "mystring"));
+ g_assert(!g_strcmp0(g_variant_get_string(out, NULL), "mystring"));
out = NULL;
/* Setting a boolean */
dbusmenu_menuitem_property_set_bool(item, "swapper", FALSE);
g_assert(out != NULL);
- g_assert(!g_value_get_boolean(out));
+ g_assert(!g_variant_get_boolean(out));
out = NULL;
g_object_unref(item);
@@ -274,6 +274,37 @@ test_object_menuitem_props_boolstr (void)
return;
}
+/* Set and then remove a prop */
+static void
+test_object_menuitem_props_removal (void)
+{
+ /* Build a menu item */
+ DbusmenuMenuitem * item = dbusmenu_menuitem_new();
+
+ /* Test to make sure it's a happy object */
+ g_assert(item != NULL);
+
+ /* Set the property and ensure that it's set */
+ dbusmenu_menuitem_property_set_variant(item, "myprop", g_variant_new_int32(34));
+ g_assert(dbusmenu_menuitem_property_get_variant(item, "myprop") != NULL);
+
+ /* Remove the property and ensure it goes away */
+ dbusmenu_menuitem_property_set_variant(item, "myprop", NULL);
+ g_assert(dbusmenu_menuitem_property_get_variant(item, "myprop") == NULL);
+
+ /* Set the property again */
+ dbusmenu_menuitem_property_set_variant(item, "myprop", g_variant_new_int32(34));
+ g_assert(dbusmenu_menuitem_property_get_variant(item, "myprop") != NULL);
+
+ /* Remove the property with a NULL string */
+ dbusmenu_menuitem_property_set(item, "myprop", NULL);
+ g_assert(dbusmenu_menuitem_property_get_variant(item, "myprop") == NULL);
+
+ g_object_unref(item);
+
+ return;
+}
+
/* Build the test suite */
static void
test_glib_objects_suite (void)
@@ -286,6 +317,7 @@ test_glib_objects_suite (void)
g_test_add_func ("/dbusmenu/glib/objects/menuitem/props_swap", test_object_menuitem_props_swap);
g_test_add_func ("/dbusmenu/glib/objects/menuitem/props_signals", test_object_menuitem_props_signals);
g_test_add_func ("/dbusmenu/glib/objects/menuitem/props_boolstr", test_object_menuitem_props_boolstr);
+ g_test_add_func ("/dbusmenu/glib/objects/menuitem/props_removal", test_object_menuitem_props_removal);
return;
}
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-glib-properties-server.c b/tests/test-glib-properties-server.c
index 091e550..4248ea2 100644
--- a/tests/test-glib-properties-server.c
+++ b/tests/test-glib-properties-server.c
@@ -20,10 +20,7 @@ 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 <gio/gio.h>
#include <libdbusmenu-glib/menuitem.h>
#include <libdbusmenu-glib/server.h>
@@ -91,8 +88,6 @@ main (int argc, char ** argv)
{
g_type_init();
- g_debug("DBus ID: %s", dbus_connection_get_server_id(dbus_g_connection_get_connection(dbus_g_bus_get(DBUS_BUS_SESSION, NULL))));
-
server = dbusmenu_server_new("/org/test");
timer_func(NULL);
diff --git a/tests/test-glib-proxy-client.c b/tests/test-glib-proxy-client.c
index 0ae2e20..2e1e2d2 100644
--- a/tests/test-glib-proxy-client.c
+++ b/tests/test-glib-proxy-client.c
@@ -150,10 +150,9 @@ layout_verify_timer (gpointer data)
g_main_loop_quit(mainloop);
}
- GValue value = {0};
- g_value_init(&value, G_TYPE_INT);
- g_value_set_int(&value, 0);
- dbusmenu_menuitem_handle_event(menuroot, "clicked", &value, layouton);
+ GVariant * value = g_variant_new("i", 0);
+ dbusmenu_menuitem_handle_event(menuroot, "clicked", value, layouton);
+ g_variant_unref(value);
return FALSE;
}
diff --git a/tests/test-glib-proxy-proxy.c b/tests/test-glib-proxy-proxy.c
index 722cf1f..8a17ead 100644
--- a/tests/test-glib-proxy-proxy.c
+++ b/tests/test-glib-proxy-proxy.c
@@ -1,9 +1,5 @@
#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 <gio/gio.h>
#include <libdbusmenu-glib/menuitem.h>
#include <libdbusmenu-glib/menuitem-proxy.h>
@@ -32,6 +28,24 @@ root_changed (DbusmenuClient * client, DbusmenuMenuitem * newroot, gpointer user
return;
}
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ client = dbusmenu_client_new((gchar *)user_data, "/org/test");
+
+ g_signal_connect(client, DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED, G_CALLBACK(root_changed), server);
+
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
+
int
main (int argc, char ** argv)
{
@@ -47,28 +61,16 @@ main (int argc, char ** argv)
g_debug("I am '%s' and I'm proxying '%s'", whoami, myproxy);
- GError * error = NULL;
- 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(connection)));
-
- 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, whoami, 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");
- client = dbusmenu_client_new(myproxy, "/org/test");
- g_signal_connect(client, DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED, G_CALLBACK(root_changed), server);
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ whoami,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ myproxy,
+ NULL);
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
diff --git a/tests/test-glib-proxy-server.c b/tests/test-glib-proxy-server.c
index f32b426..a5dfd4e 100644
--- a/tests/test-glib-proxy-server.c
+++ b/tests/test-glib-proxy-server.c
@@ -20,11 +20,7 @@ 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 <gio/gio.h>
#include <libdbusmenu-glib/menuitem.h>
#include <libdbusmenu-glib/server.h>
@@ -104,31 +100,36 @@ layout_change (DbusmenuMenuitem * oldroot, guint timestamp, gpointer data)
return;
}
-int
-main (int argc, char ** argv)
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
{
- g_type_init();
-
- GError * error = NULL;
- 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(connection)));
+ server = dbusmenu_server_new("/org/test");
+ layout_change(NULL, 0, NULL);
- DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
- guint nameret = 0;
+ return;
+}
- if (!org_freedesktop_DBus_request_name(bus_proxy, "test.proxy.server", 0, &nameret, &error)) {
- g_error("Unable to call to request name");
- return 1;
- }
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
- if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
- g_error("Unable to get name");
- return 1;
- }
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
- server = dbusmenu_server_new("/org/test");
- layout_change(NULL, 0, NULL);
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "test.proxy.server",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ NULL);
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
diff --git a/tests/test-glib-simple-items.c b/tests/test-glib-simple-items.c
index 5b9f538..3ea5480 100644
--- a/tests/test-glib-simple-items.c
+++ b/tests/test-glib-simple-items.c
@@ -1,6 +1,3 @@
-#include <dbus/dbus-glib.h>
-#include <dbus/dbus-glib-bindings.h>
-
#include <libdbusmenu-glib/server.h>
#include <libdbusmenu-glib/menuitem.h>
diff --git a/tests/test-glib-simple-items.py b/tests/test-glib-simple-items.py
new file mode 100755
index 0000000..d7ad7d7
--- /dev/null
+++ b/tests/test-glib-simple-items.py
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+# This is the Python GI version of test-glib-simple-items.c
+
+import gobject
+from gi.repository import Dbusmenu
+
+dummies = ['Bob', 'Jim', 'Alvin', 'Mary']
+
+def dummy_users(root):
+ count = 0
+ for user in dummies:
+ mi = Dbusmenu.Menuitem()
+ print 'Creating item: %d %s' % (mi.get_id(), user)
+ print '\tRoot ID:', root.get_id()
+ mi.property_set('label', user)
+ root.child_add_position(mi, count)
+ assert mi.property_get('label') == user
+ count += 1
+
+def quititall(mainloop):
+ mainloop.quit()
+ return False
+
+# main
+
+server = Dbusmenu.Server.new('/test/object')
+root_menuitem = Dbusmenu.Menuitem()
+server.set_root(root_menuitem)
+print 'Root ID:', root_menuitem.get_id()
+
+dummy_users(root_menuitem)
+
+mainloop = gobject.MainLoop()
+gobject.timeout_add_seconds(1, quititall, mainloop)
+mainloop.run()
diff --git a/tests/test-glib-submenu-server.c b/tests/test-glib-submenu-server.c
index 68f7004..73362c1 100644
--- a/tests/test-glib-submenu-server.c
+++ b/tests/test-glib-submenu-server.c
@@ -20,11 +20,7 @@ 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 <gio/gio.h>
#include <libdbusmenu-glib/server.h>
#include <libdbusmenu-glib/menuitem.h>
@@ -72,33 +68,38 @@ timer_func (gpointer data)
return TRUE;
}
-int
-main (int argc, char ** argv)
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
{
- 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))));
+ server = dbusmenu_server_new("/org/test");
- DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
- guint nameret = 0;
+ timer_func(NULL);
+ g_timeout_add(2500, timer_func, NULL);
- if (!org_freedesktop_DBus_request_name(bus_proxy, "org.dbusmenu.test", 0, &nameret, &error)) {
- g_error("Unable to call to request name");
- return 1;
- }
+ return;
+}
- if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
- g_error("Unable to get name");
- return 1;
- }
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
- server = dbusmenu_server_new("/org/test");
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
- timer_func(NULL);
- g_timeout_add(2500, timer_func, NULL);
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "org.dbusmenu.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ NULL);
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
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..ddf8fcf 100644
--- a/tests/test-gtk-label-server.c
+++ b/tests/test-gtk-label-server.c
@@ -20,84 +20,13 @@ 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 <gio/gio.h>
#include <libdbusmenu-glib/menuitem.h>
#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,12 +43,31 @@ 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;
}
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ server = dbusmenu_server_new("/org/test");
+
+ timer_func(NULL);
+ g_timeout_add_seconds(5, timer_func, NULL);
+
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
+
int
main (int argc, char ** argv)
{
@@ -140,26 +88,15 @@ main (int argc, char ** argv)
root_array = json_node_get_array(root_node);
g_debug("%d layouts in test description '%s'", json_array_get_length(root_array), argv[1]);
- 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");
- timer_func(NULL);
- g_timeout_add_seconds(5, timer_func, NULL);
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "glib.label.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ NULL);
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
diff --git a/tests/test-gtk-objects.c b/tests/test-gtk-objects.c
new file mode 100644
index 0000000..30fc022
--- /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 */
+ GVariant * val = dbusmenu_menuitem_property_get_variant(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 */
+ GVariant * val = dbusmenu_menuitem_property_get_variant(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-parser.c b/tests/test-gtk-parser.c
new file mode 100644
index 0000000..b66b46a
--- /dev/null
+++ b/tests/test-gtk-parser.c
@@ -0,0 +1,115 @@
+/*
+Testing for the various objects just by themselves.
+
+Copyright 2011 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-private.h>
+#include <libdbusmenu-gtk/parser.h>
+
+/* Just makes sure we can connect here people */
+static void
+test_parser_runs (void)
+{
+ GtkWidget * gmi = gtk_menu_item_new_with_label("Test Item");
+ g_assert(gmi != NULL);
+ DbusmenuMenuitem * mi = dbusmenu_gtk_parse_menu_structure(gmi);
+ g_assert(mi != NULL);
+
+ g_object_unref(gmi);
+ g_object_unref(mi);
+
+ return;
+}
+
+const gchar * test_parser_children_builder =
+"<?xml version=\"1.0\"?>"
+"<interface>"
+"<requires lib=\"gtk+\" version=\"2.16\"/>"
+/* Start menu bar */
+"<object class=\"GtkMenuBar\" id=\"menubar\"><property name=\"visible\">True</property>"
+/* Child 1 */
+"<child><object class=\"GtkMenuItem\" id=\"child_one\"><property name=\"visible\">True</property><property name=\"label\">Child One</property></object></child>"
+/* Child 2 */
+"<child><object class=\"GtkMenuItem\" id=\"child_two\"><property name=\"visible\">True</property><property name=\"label\">Child Two</property></object></child>"
+/* Child 3 */
+"<child><object class=\"GtkMenuItem\" id=\"child_three\"><property name=\"visible\">True</property><property name=\"label\">Child Three</property></object></child>"
+/* Child 4 */
+"<child><object class=\"GtkMenuItem\" id=\"child_four\"><property name=\"visible\">True</property><property name=\"label\">Child Four</property></object></child>"
+/* Stop menubar */
+"</object>"
+"</interface>";
+
+/* Ensure the parser can find children */
+static void
+test_parser_children (void) {
+ GtkBuilder * builder = gtk_builder_new();
+ g_assert(builder != NULL);
+
+ GError * error = NULL;
+ gtk_builder_add_from_string(builder, test_parser_children_builder, -1, &error);
+ if (error != NULL) {
+ g_error("Unable to parse UI definition: %s", error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+
+ GtkWidget * menu = GTK_WIDGET(gtk_builder_get_object(builder, "menubar"));
+ g_assert(menu != NULL);
+
+ DbusmenuMenuitem * mi = dbusmenu_gtk_parse_menu_structure(menu);
+ g_assert(mi != NULL);
+
+/*
+ GPtrArray * xmlarray = g_ptr_array_new();
+ dbusmenu_menuitem_buildxml(mi, xmlarray);
+ g_debug("XML: %s", g_strjoinv("", (gchar **)xmlarray->pdata));
+*/
+
+ GList * children = dbusmenu_menuitem_get_children(mi);
+ g_assert(children != NULL);
+
+ g_assert(g_list_length(children) == 4);
+
+ g_object_unref(mi);
+ g_object_unref(menu);
+
+ return;
+}
+
+/* Build the test suite */
+static void
+test_gtk_parser_suite (void)
+{
+ g_test_add_func ("/dbusmenu/gtk/parser/base", test_parser_runs);
+ g_test_add_func ("/dbusmenu/gtk/parser/children", test_parser_children);
+ return;
+}
+
+gint
+main (gint argc, gchar * argv[])
+{
+ gtk_init(&argc, &argv);
+ g_test_init(&argc, &argv, NULL);
+
+ /* Test suites */
+ test_gtk_parser_suite();
+
+
+ return g_test_run ();
+}
diff --git a/tests/test-gtk-reorder-server.c b/tests/test-gtk-reorder-server.c
index eee9bb8..44209f1 100644
--- a/tests/test-gtk-reorder-server.c
+++ b/tests/test-gtk-reorder-server.c
@@ -20,11 +20,7 @@ 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 <gio/gio.h>
#include <libdbusmenu-glib/menuitem.h>
#include <libdbusmenu-glib/server.h>
@@ -73,29 +69,9 @@ timer_func (gpointer data)
return TRUE;
}
-int
-main (int argc, char ** argv)
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
{
- 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");
root = dbusmenu_menuitem_new();
dbusmenu_server_set_root(server, root);
@@ -109,6 +85,31 @@ main (int argc, char ** argv)
timer_func(NULL);
g_timeout_add_seconds(5, timer_func, NULL);
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "glib.label.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ NULL);
+
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
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-client.py b/tests/test-gtk-shortcut-client.py
new file mode 100755
index 0000000..885d227
--- /dev/null
+++ b/tests/test-gtk-shortcut-client.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+
+# A test for libdbusmenu to ensure its quality. This is the Python GI version
+# of test-gtk-shortcut-client.c
+#
+# Copyright 2011 Canonical Ltd.
+# Authors:
+# Martin Pitt <martin.pitt@ubuntu.com>
+
+import sys
+import gobject
+from gi.repository import Gtk, DbusmenuGtk
+Gtk.require_version('2.0')
+
+passed = True
+main_loop = gobject.MainLoop()
+
+def timer_func(data):
+ passed = True
+ main_loop.quit()
+ return False
+
+# main
+print 'Building Window'
+window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
+menubar = Gtk.MenuBar()
+menuitem = Gtk.MenuItem(label='Test')
+
+dmenu = DbusmenuGtk.Menu(dbus_name='glib.label.test', dbus_object='/org/test')
+dclient = dmenu.get_client()
+agroup = Gtk.AccelGroup()
+dclient.set_accel_group(agroup)
+
+menuitem.set_submenu(dmenu)
+menuitem.show()
+menubar.append(menuitem)
+menubar.show()
+window.add(menubar)
+window.set_title('libdbusmenu-gtk test')
+window.add_accel_group(agroup)
+window.show_all()
+
+gobject.timeout_add_seconds(10, timer_func, window)
+
+print 'Entering Mainloop'
+main_loop.run()
+
+if passed:
+ print 'Quiting'
+else:
+ print "Quiting as we're a failure"
+ sys.exit(1)
diff --git a/tests/test-gtk-shortcut-server.c b/tests/test-gtk-shortcut-server.c
new file mode 100644
index 0000000..b205d03
--- /dev/null
+++ b/tests/test-gtk-shortcut-server.c
@@ -0,0 +1,100 @@
+/*
+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 <gio/gio.h>
+#include <gdk/gdkkeysyms.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;
+}
+
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ server = dbusmenu_server_new("/org/test");
+ build_menu();
+
+ g_timeout_add_seconds(10, timer_func, NULL);
+
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "glib.label.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ 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..9c4d7d4
--- /dev/null
+++ b/tests/test-gtk-submenu-server.c
@@ -0,0 +1,114 @@
+/*
+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 <gio/gio.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;
+}
+
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ 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);
+
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "glib.label.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ NULL,
+ NULL);
+
+ 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..b626d20
--- /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?',
+ "visible": true
+ },
+ {
+ "id": 221,
+ "enabled": true,
+ "label": 'Rotate 90? counter-clockwise',
+ "visible": true
+ },
+ {
+ "id": 222,
+ "enabled": true,
+ "label": 'Rotate 90? 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?',
+ "visible": true
+ },
+ {
+ "id": 246,
+ "enabled": true,
+ "label": 'Rotate 90? counter-clockwise',
+ "visible": true
+ },
+ {
+ "id": 247,
+ "enabled": true,
+ "label": 'Rotate 90? 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..d4e782b
--- /dev/null
+++ b/tests/test-json-client.c
@@ -0,0 +1,79 @@
+/*
+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>
+
+GMainLoop * mainloop = NULL;
+
+gboolean
+timeout_func (gpointer user_data)
+{
+ g_warning("Timeout without getting name");
+ g_main_loop_quit(mainloop);
+ return FALSE;
+}
+
+void
+name_appeared (GDBusConnection * connection, const gchar * name, const gchar * owner, gpointer user_data)
+{
+ char ** argv = (char **)user_data;
+
+ 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_main_loop_quit(mainloop);
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+ g_debug("Wait for friends");
+
+ g_bus_watch_name(G_BUS_TYPE_SESSION,
+ "org.dbusmenu.test",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared,
+ NULL,
+ argv,
+ NULL);
+
+ g_timeout_add_seconds(2, timeout_func, NULL);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+
+ return 0;
+}
diff --git a/tests/test-json-server.c b/tests/test-json-server.c
new file mode 100644
index 0000000..083de60
--- /dev/null
+++ b/tests/test-json-server.c
@@ -0,0 +1,88 @@
+/*
+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 <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;
+}
+
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ gchar ** argv = (gchar **)user_data;
+
+ DbusmenuServer * server = dbusmenu_server_new("/org/test");
+
+ DbusmenuMenuitem * root = dbusmenu_json_build_from_file(argv[1]);
+ if (root == NULL) {
+ g_warning("Unable to build root");
+ g_main_loop_quit(mainloop);
+ return;
+ }
+
+ dbusmenu_server_set_root(server, root);
+
+ g_timeout_add(10000, timer_func, NULL);
+
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ g_main_loop_quit(mainloop);
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init();
+
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "org.dbusmenu.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ argv,
+ 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-bench b/tools/dbusmenu-bench
index 4b58da8..dcf5f6f 100755
--- a/tools/dbusmenu-bench
+++ b/tools/dbusmenu-bench
@@ -35,7 +35,7 @@ from xml.etree import ElementTree as ET
import dbus
-DBUS_INTERFACE = "org.ayatana.dbusmenu"
+DBUS_INTERFACE = "com.canonical.dbusmenu"
DBUS_SERVICE = "org.dbusmenu.test"
DBUS_PATH = "/MenuBar"
diff --git a/tools/dbusmenu-dumper.c b/tools/dbusmenu-dumper.c
index 55d631e..2db437d 100644
--- a/tools/dbusmenu-dumper.c
+++ b/tools/dbusmenu-dumper.c
@@ -22,25 +22,38 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include <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 <X11/Xlib.h>
+
static GMainLoop * mainloop = NULL;
+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);
+ GVariant * variant = dbusmenu_menuitem_property_get_variant(item, (gchar *)property->data);
+ gchar * str = g_variant_print(variant, FALSE);
+ g_print(",\n%s\"%s\": %s", space, (gchar *)property->data, str);
+ g_free(str);
+ g_variant_unref(variant);
}
g_list_free(properties);
@@ -90,11 +103,200 @@ 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)
+{
+ GError *error;
+ GDBusProxy *proxy;
+
+ error = NULL;
+
+ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "com.canonical.AppMenu.Registrar",
+ "/com/canonical/AppMenu/Registrar",
+ "com.canonical.AppMenu.Registrar",
+ NULL,
+ &error);
+ if (error != NULL) {
+ g_warning("Unable to get registrar proxy: %s", error->message);
+ g_error_free(error);
+ return FALSE;
+ }
+
+ error = NULL;
+ GVariant * retval;
+ GVariant * args[1];
+ args[0] = g_variant_new("u", window);
+
+ retval = g_dbus_proxy_call_sync(proxy,
+ "GetMenuForWindow",
+ g_variant_new_tuple(args, 1),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (error != NULL) {
+ g_warning("Unable to call 'GetMenuForWindow' on registrar: %s", error->message);
+ g_error_free(error);
+ return FALSE;
+ }
+
+ g_variant_get(retval, "(so)", &dbusname, &dbusobject);
+
+ g_variant_unref(retval);
+ 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 +329,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 +350,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);
diff --git a/tools/testapp/Makefile.am b/tools/testapp/Makefile.am
index a8b42dd..39de532 100644
--- a/tools/testapp/Makefile.am
+++ b/tools/testapp/Makefile.am
@@ -1,4 +1,10 @@
+if USE_GTK3
+VER=3
+else
+VER=
+endif
+
libexec_PROGRAMS = dbusmenu-testapp
dbusmenu_testapp_SOURCES = \
@@ -12,6 +18,6 @@ dbusmenu_testapp_CFLAGS = \
dbusmenu_testapp_LDADD = \
$(builddir)/../../libdbusmenu-glib/libdbusmenu-glib.la \
- $(builddir)/../../libdbusmenu-gtk/libdbusmenu-gtk.la \
+ $(builddir)/../../libdbusmenu-gtk/libdbusmenu-gtk$(VER).la \
$(DBUSMENUGTK_LIBS) \
$(DBUSMENUTESTS_LIBS)
diff --git a/tools/testapp/main.c b/tools/testapp/main.c
index f489407..67e962d 100644
--- a/tools/testapp/main.c
+++ b/tools/testapp/main.c
@@ -26,9 +26,7 @@ License version 3 and version 2.1 along with this program. If not, see
<http://www.gnu.org/licenses/>
*/
#include <glib.h>
-
-#include <dbus/dbus-glib.h>
-#include <dbus/dbus-glib-bindings.h>
+#include <gio/gio.h>
#include <json-glib/json-glib.h>
@@ -117,6 +115,24 @@ void init_menu(DbusmenuMenuitem *root, const char *filename)
}
}
+static void
+on_bus (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ DbusmenuServer *server = dbusmenu_server_new("/MenuBar");
+ DbusmenuMenuitem *root = dbusmenu_menuitem_new_with_id(0);
+ init_menu(root, (gchar *)user_data);
+ dbusmenu_server_set_root(server, root);
+
+ return;
+}
+
+static void
+name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_error("Unable to get name '%s' on DBus", name);
+ return;
+}
+
int main (int argc, char ** argv)
{
g_type_init();
@@ -127,25 +143,14 @@ int main (int argc, char ** argv)
}
const char *filename = argv[1];
- GError * error = NULL;
- DBusGConnection * 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("/MenuBar");
- DbusmenuMenuitem *root = dbusmenu_menuitem_new_with_id(0);
- init_menu(root, filename);
- dbusmenu_server_set_root(server, root);
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ "org.dbusmenu.test",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus,
+ NULL,
+ name_lost,
+ (gpointer)filename,
+ NULL);
g_main_loop_run(g_main_loop_new(NULL, FALSE));