diff options
Diffstat (limited to 'libdbusmenu-glib')
-rw-r--r-- | libdbusmenu-glib/Makefile.am | 100 | ||||
-rw-r--r-- | libdbusmenu-glib/clean-namespaces.xslt | 14 | ||||
-rw-r--r-- | libdbusmenu-glib/client-marshal.list | 2 | ||||
-rw-r--r-- | libdbusmenu-glib/client-menuitem.c | 12 | ||||
-rw-r--r-- | libdbusmenu-glib/client.c | 1368 | ||||
-rw-r--r-- | libdbusmenu-glib/client.h | 48 | ||||
-rw-r--r-- | libdbusmenu-glib/dbus-menu.xml | 119 | ||||
-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.h | 37 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-marshal.list | 2 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-private.h | 4 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-proxy.c | 52 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-proxy.h | 18 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem.c | 1025 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem.h | 59 | ||||
-rw-r--r-- | libdbusmenu-glib/server-marshal.list | 3 | ||||
-rw-r--r-- | libdbusmenu-glib/server.c | 1176 | ||||
-rw-r--r-- | libdbusmenu-glib/server.h | 33 |
18 files changed, 2999 insertions, 1077 deletions
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, ×tamp); + 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); |