diff options
Diffstat (limited to 'libdbusmenu-glib')
25 files changed, 4638 insertions, 1222 deletions
diff --git a/libdbusmenu-glib/Makefile.am b/libdbusmenu-glib/Makefile.am index 0a6513f..5b04415 100644 --- a/libdbusmenu-glib/Makefile.am +++ b/libdbusmenu-glib/Makefile.am @@ -1,27 +1,42 @@ -CLEANFILES = - +BUILT_SOURCES = +CLEANFILES = +DISTCLEANFILES = 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 +include $(top_srcdir)/Makefile.am.enum + lib_LTLIBRARIES = \ libdbusmenu-glib.la -libdbusmenu_glibincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-glib/ +libdbusmenu_glibincludedir=$(includedir)/libdbusmenu-0.4/libdbusmenu-glib/ -libdbusmenu_glibinclude_HEADERS = \ + +EXPORTED_OBJECTS = \ + enum-types.h \ menuitem.h \ menuitem-proxy.h \ server.h \ client.h +libdbusmenu_glibinclude_HEADERS = \ + $(EXPORTED_OBJECTS) \ + dbusmenu-glib.h \ + types.h + libdbusmenu_glib_la_SOURCES = \ - dbusmenu-server.h \ - dbusmenu-client.h \ + dbus-menu-clean.xml.h \ + dbus-menu-clean.xml.c \ + defaults.h \ + defaults.c \ + enum-types.h \ + enum-types.c \ menuitem.h \ menuitem.c \ menuitem-marshal.h \ @@ -37,6 +52,7 @@ libdbusmenu_glib_la_SOURCES = \ client-marshal.c \ client-menuitem.h \ client-menuitem.c \ + client-private.h \ client.h \ client.c @@ -51,12 +67,33 @@ 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 -BUILT_SOURCES = \ - dbusmenu-client.h \ - dbusmenu-server.h \ +glib_enum_h = enum-types.h +glib_enum_c = enum-types.c +glib_enum_headers = $(addprefix $(srcdir)/, $(libdbusmenu_glibinclude_HEADERS)) + +DISTCLEANFILES += \ + enum-types.c \ + enum-types.h + +%.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 += \ + dbus-menu-clean.xml.c \ + dbus-menu-clean.xml.h \ client-marshal.h \ client-marshal.c \ menuitem-marshal.h \ @@ -64,19 +101,7 @@ BUILT_SOURCES = \ 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 - -dbusmenu-client.h: dbus-menu.xml - dbus-binding-tool \ - --prefix=_dbusmenu_client \ - --mode=glib-client \ - --output=dbusmenu-client.h \ - $(srcdir)/dbus-menu.xml +CLEANFILES += $(BUILT_SOURCES) client-marshal.h: $(srcdir)/client-marshal.list glib-genmarshal --header \ @@ -114,25 +139,41 @@ 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) \ + $(EXPORTED_OBJECTS:.h=.c) -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) @@ -151,10 +192,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 index 2e14491..980c5c3 100644 --- a/libdbusmenu-glib/client-marshal.list +++ b/libdbusmenu-glib/client-marshal.list @@ -1,2 +1,4 @@ VOID: OBJECT, UINT -VOID: OBJECT, STRING, POINTER, UINT, POINTER +VOID: OBJECT, STRING, VARIANT, UINT, POINTER +VOID: ENUM +VOID: POINTER diff --git a/libdbusmenu-glib/client-menuitem.c b/libdbusmenu-glib/client-menuitem.c index 979cf79..483470a 100644 --- a/libdbusmenu-glib/client-menuitem.c +++ b/libdbusmenu-glib/client-menuitem.c @@ -30,6 +30,7 @@ License version 3 and version 2.1 along with this program. If not, see #endif #include "client-menuitem.h" +#include "client-private.h" typedef struct _DbusmenuClientMenuitemPrivate DbusmenuClientMenuitemPrivate; @@ -39,14 +40,14 @@ struct _DbusmenuClientMenuitemPrivate }; #define DBUSMENU_CLIENT_MENUITEM_GET_PRIVATE(o) \ -(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_CLIENT_MENUITEM_TYPE, DbusmenuClientMenuitemPrivate)) +(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_CLIENT_MENUITEM, DbusmenuClientMenuitemPrivate)) static void dbusmenu_client_menuitem_class_init (DbusmenuClientMenuitemClass *klass); 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); @@ -94,7 +95,7 @@ dbusmenu_client_menuitem_finalize (GObject *object) DbusmenuClientMenuitem * dbusmenu_client_menuitem_new (gint id, DbusmenuClient * client) { - DbusmenuClientMenuitem * mi = g_object_new(DBUSMENU_CLIENT_MENUITEM_TYPE, "id", id, NULL); + DbusmenuClientMenuitem * mi = g_object_new(DBUSMENU_TYPE_CLIENT_MENUITEM, "id", id, NULL); DbusmenuClientMenuitemPrivate * priv = DBUSMENU_CLIENT_MENUITEM_GET_PRIVATE(mi); priv->client = client; return mi; @@ -102,17 +103,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, mi); 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 +132,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-menuitem.h b/libdbusmenu-glib/client-menuitem.h index ef61213..a89cb11 100644 --- a/libdbusmenu-glib/client-menuitem.h +++ b/libdbusmenu-glib/client-menuitem.h @@ -35,12 +35,12 @@ License version 3 and version 2.1 along with this program. If not, see G_BEGIN_DECLS -#define DBUSMENU_CLIENT_MENUITEM_TYPE (dbusmenu_client_menuitem_get_type ()) -#define DBUSMENU_CLIENT_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_CLIENT_MENUITEM_TYPE, DbusmenuClientMenuitem)) -#define DBUSMENU_CLIENT_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_CLIENT_MENUITEM_TYPE, DbusmenuClientMenuitemClass)) -#define DBUSMENU_IS_CLIENT_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_CLIENT_MENUITEM_TYPE)) -#define DBUSMENU_IS_CLIENT_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_CLIENT_MENUITEM_TYPE)) -#define DBUSMENU_CLIENT_MENUITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_CLIENT_MENUITEM_TYPE, DbusmenuClientMenuitemClass)) +#define DBUSMENU_TYPE_CLIENT_MENUITEM (dbusmenu_client_menuitem_get_type ()) +#define DBUSMENU_CLIENT_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_TYPE_CLIENT_MENUITEM, DbusmenuClientMenuitem)) +#define DBUSMENU_CLIENT_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_TYPE_CLIENT_MENUITEM, DbusmenuClientMenuitemClass)) +#define DBUSMENU_IS_CLIENT_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_TYPE_CLIENT_MENUITEM)) +#define DBUSMENU_IS_CLIENT_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_CLIENT_MENUITEM)) +#define DBUSMENU_CLIENT_MENUITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_CLIENT_MENUITEM, DbusmenuClientMenuitemClass)) typedef struct _DbusmenuClientMenuitem DbusmenuClientMenuitem; typedef struct _DbusmenuClientMenuitemClass DbusmenuClientMenuitemClass; diff --git a/libdbusmenu-glib/client-private.h b/libdbusmenu-glib/client-private.h new file mode 100644 index 0000000..44c0066 --- /dev/null +++ b/libdbusmenu-glib/client-private.h @@ -0,0 +1,49 @@ +/* +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_CLIENT_PRIVATE_H__ +#define __DBUSMENU_CLIENT_PRIVATE_H__ + +#include "client.h" + +G_BEGIN_DECLS + +void dbusmenu_client_send_event (DbusmenuClient * client, + gint id, + const gchar * name, + GVariant * variant, + guint timestamp, + DbusmenuMenuitem * mi); +void dbusmenu_client_send_about_to_show(DbusmenuClient * client, + gint id, + void (*cb) (gpointer user_data), + gpointer cb_data); + +G_END_DECLS + +#endif diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c index ca16c9a..588c940 100644 --- a/libdbusmenu-glib/client.c +++ b/libdbusmenu-glib/client.c @@ -30,24 +30,29 @@ License version 3 and version 2.1 along with this program. If not, see #include "config.h" #endif -#include <dbus/dbus-glib-bindings.h> - -#include <libxml/parser.h> -#include <libxml/tree.h> +#include <gio/gio.h> #include "client.h" +#include "client-private.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" +#include "enum-types.h" + +/* How many property requests should we queue before + sending the message on dbus */ +#define MAX_PROPERTIES_TO_QUEUE 100 /* Properties */ enum { PROP_0, PROP_DBUSOBJECT, - PROP_DBUSNAME + PROP_DBUSNAME, + PROP_STATUS, + PROP_TEXT_DIRECTION }; /* Signals */ @@ -57,12 +62,14 @@ enum { NEW_MENUITEM, ITEM_ACTIVATE, EVENT_RESULT, + ICON_THEME_DIRS, 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; @@ -70,21 +77,29 @@ 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; + GVariant * layout_props; gint current_revision; gint my_revision; - DBusGProxy * dbusproxy; + guint dbusproxy; GHashTable * type_handlers; GArray * delayed_property_list; GArray * delayed_property_listeners; gint delayed_idle; + + DbusmenuTextDirection text_direction; + DbusmenuStatus status; + GStrv icon_dirs; }; typedef struct _newItemPropData newItemPropData; @@ -98,7 +113,7 @@ struct _newItemPropData typedef struct _properties_listener_t properties_listener_t; struct _properties_listener_t { gint id; - org_ayatana_dbusmenu_get_properties_reply callback; + properties_func callback; gpointer user_data; gboolean replied; }; @@ -108,13 +123,28 @@ struct _event_data_t { DbusmenuClient * client; DbusmenuMenuitem * menuitem; gchar * event; - GValue data; + GVariant * variant; guint timestamp; }; +typedef struct _type_handler_t type_handler_t; +struct _type_handler_t { + DbusmenuClient * client; + DbusmenuClientTypeHandler cb; + GDestroyNotify destroy_cb; + gpointer user_data; + gchar * type; +}; -#define DBUSMENU_CLIENT_GET_PRIVATE(o) \ -(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate)) +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); @@ -124,19 +154,27 @@ 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 get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, org_ayatana_dbusmenu_get_properties_reply callback, gpointer user_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 (DBusGProxy * proxy, gint id, guint timestamp, DbusmenuClient * client); +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_prop_changed_cb (GDBusProxy * proxy, GVariant * properties, GStrv invalidated, 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); @@ -234,8 +272,22 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (DbusmenuClientClass, event_result), NULL, NULL, - _dbusmenu_client_marshal_VOID__OBJECT_STRING_POINTER_UINT_POINTER, - G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_POINTER); + _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); + /** + DbusmenuClient::icon-theme-dirs-changed: + @arg0: The #DbusmenuClient object + @arg1: A #GStrv of theme directories + + Signaled when the theme directories are changed by the server. + */ + signals[ICON_THEME_DIRS] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_ICON_THEME_DIRS_CHANGED, + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DbusmenuClientClass, icon_theme_dirs), + NULL, NULL, + _dbusmenu_client_marshal_VOID__POINTER, + G_TYPE_NONE, 1, 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", @@ -247,13 +299,45 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass) "Name of the DBus client we're connecting to.", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_STATUS, + g_param_spec_enum(DBUSMENU_CLIENT_PROP_STATUS, "Status of viewing the menus", + "Whether the menus should be given special visuals", + DBUSMENU_TYPE_STATUS, DBUSMENU_STATUS_NORMAL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_TEXT_DIRECTION, + g_param_spec_enum(DBUSMENU_CLIENT_PROP_TEXT_DIRECTION, "Direction text values have", + "Signals which direction the default text direction is for the menus", + DBUSMENU_TYPE_TEXT_DIRECTION, DBUSMENU_TEXT_DIRECTION_NONE, + G_PARAM_READABLE | 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; } +#define LAYOUT_PROPS_COUNT 5 + 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; @@ -262,22 +346,39 @@ 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; + gchar * layout_props[LAYOUT_PROPS_COUNT + 1]; + layout_props[0] = DBUSMENU_MENUITEM_PROP_TYPE; + layout_props[1] = DBUSMENU_MENUITEM_PROP_LABEL; + layout_props[2] = DBUSMENU_MENUITEM_PROP_VISIBLE; + layout_props[3] = DBUSMENU_MENUITEM_PROP_ENABLED; + layout_props[4] = DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY; + layout_props[LAYOUT_PROPS_COUNT] = NULL; + priv->layout_props = g_variant_new_strv((const gchar * const *)layout_props, LAYOUT_PROPS_COUNT); + g_variant_ref_sink(priv->layout_props); + 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)); + priv->text_direction = DBUSMENU_TEXT_DIRECTION_NONE; + priv->status = DBUSMENU_STATUS_NORMAL; + priv->icon_dirs = NULL; + return; } @@ -313,7 +414,7 @@ dbusmenu_client_dispose (GObject *object) if (localerror == NULL) { g_set_error_literal(&localerror, error_domain(), 0, "DbusmenuClient Shutdown"); } - listener->callback(priv->menuproxy, NULL, localerror, listener->user_data); + listener->callback(NULL, localerror, listener->user_data); } } if (localerror != NULL) { @@ -325,22 +426,47 @@ dbusmenu_client_dispose (GObject *object) } 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; } + + if (priv->layout_props != NULL) { + g_variant_unref(priv->layout_props); + priv->layout_props = 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_signal_handlers_disconnect_matched(priv->menuproxy, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, object); 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; } - if (priv->dbusproxy != NULL) { - g_object_unref(G_OBJECT(priv->dbusproxy)); - priv->dbusproxy = NULL; + + /* 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->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)); @@ -363,6 +489,11 @@ dbusmenu_client_finalize (GObject *object) g_hash_table_destroy(priv->type_handlers); } + if (priv->icon_dirs != NULL) { + g_strfreev(priv->icon_dirs); + priv->icon_dirs = NULL; + } + G_OBJECT_CLASS (dbusmenu_client_parent_class)->finalize (object); return; } @@ -405,6 +536,12 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) case PROP_DBUSOBJECT: g_value_set_string(value, priv->dbus_object); break; + case PROP_STATUS: + g_value_set_enum(value, priv->status); + break; + case PROP_TEXT_DIRECTION: + g_value_set_enum(value, priv->text_direction); + break; default: g_warning("Unknown property %d.", id); return; @@ -445,79 +582,86 @@ find_listener (GArray * listeners, guint index, gint id) /* Call back from getting the group properties, now we need to unwind and call the various functions. */ static void -get_properties_callback (DBusGProxy *proxy, GPtrArray *OUT_properties, GError *error, gpointer userdata) +get_properties_callback (GObject *obj, GAsyncResult * res, gpointer user_data) { - GArray * listeners = (GArray *)userdata; + properties_callback_t * cbdata = (properties_callback_t *)user_data; + GArray * listeners = cbdata->listeners; int i; + GError * error = NULL; + GVariant * params = NULL; - #ifdef MASSIVEDEBUGGING - g_debug("Get properties callback: %d", OUT_properties->len); - #endif + 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(proxy, NULL, error, listener->user_data); + listener->callback(NULL, error, listener->user_data); } - g_array_free(listeners, TRUE); - return; + g_error_free(error); + goto out; } /* Callback all the folks we can find */ - for (i = 0; i < OUT_properties->len; i++) { - GValueArray * varray = (GValueArray *)g_ptr_array_index(OUT_properties, i); - - if (varray->n_values != 2) { - g_warning("Value Array is %d entries long but we expected 2.", varray->n_values); + GVariant * parent = g_variant_get_child_value(params, 0); + GVariantIter iter; + g_variant_iter_init(&iter, parent); + 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)); + g_variant_unref(child); continue; } - GValue * vid = g_value_array_get_nth(varray, 0); - GValue * vproperties = g_value_array_get_nth(varray, 1); + GVariant * idv = g_variant_get_child_value(child, 0); + gint id = g_variant_get_int32(idv); + g_variant_unref(idv); - if (G_VALUE_TYPE(vid) != G_TYPE_INT) { - g_warning("ID Entry not holding an int: %s", G_VALUE_TYPE_NAME(vid)); - } - if (G_VALUE_TYPE(vproperties) != dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) { - g_warning("Properties Entry not holding an a{sv}: %s", G_VALUE_TYPE_NAME(vproperties)); - } - - gint id = g_value_get_int(vid); - GHashTable * properties = g_value_get_boxed(vproperties); + 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); + g_variant_unref(properties); + g_variant_unref(child); continue; } if (!listener->replied) { - listener->callback(proxy, properties, NULL, listener->user_data); + 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_unref(properties); + g_variant_unref(child); } + g_variant_unref(parent); + 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(proxy, NULL, localerror, listener->user_data); + 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; } @@ -527,8 +671,9 @@ get_properties_callback (DBusGProxy *proxy, GPtrArray *OUT_properties, GError *e static gboolean get_properties_idle (gpointer user_data) { + properties_callback_t * cbdata = NULL; DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(user_data); - //org_ayatana_dbusmenu_get_properties_async(priv->menuproxy, id, properties, callback, 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."); @@ -536,16 +681,42 @@ get_properties_idle (gpointer user_data) } /* Build up an ID list to pass */ - GArray * idlist = g_array_new(FALSE, FALSE, sizeof(gint)); + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + gint i; for (i = 0; i < priv->delayed_property_listeners->len; i++) { - g_array_append_val(idlist, g_array_index(priv->delayed_property_listeners, properties_listener_t, i).id); - } - - org_ayatana_dbusmenu_get_group_properties_async(priv->menuproxy, idlist, (const gchar **)priv->delayed_property_list->data, get_properties_callback, priv->delayed_property_listeners); - - /* Free ID List */ - g_array_free(idlist, TRUE); + 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 */ + GVariantType * type = g_variant_type_new("as"); + g_variant_builder_init(&builder, type); + g_variant_type_free(type); + /* 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); @@ -579,22 +750,20 @@ get_properties_flush (DbusmenuClient * client) get_properties_idle(client); - dbus_g_connection_flush(priv->session_bus); - return; } /* A function to group all the get_properties commands to make them more efficient over dbus. */ static void -get_properties_globber (DbusmenuClient * client, gint id, const gchar ** properties, org_ayatana_dbusmenu_get_properties_reply callback, gpointer user_data) +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(priv->menuproxy, NULL, localerror, user_data); + callback(NULL, localerror, user_data); g_error_free(localerror); return; } @@ -628,12 +797,19 @@ get_properties_globber (DbusmenuClient * client, gint id, const gchar ** propert priv->delayed_idle = g_idle_add(get_properties_idle, client); } + /* Look at how many proprites we have queued up and + make it so that we don't leave too many in one + request. */ + if (priv->delayed_property_listeners->len == MAX_PROPERTIES_TO_QUEUE) { + get_properties_flush(client); + } + return; } /* Called when a server item wants to activate the menu */ static void -item_activated (DBusGProxy * proxy, gint id, guint timestamp, DbusmenuClient * client) +item_activated (GDBusProxy * proxy, gint id, guint timestamp, DbusmenuClient * client) { g_return_if_fail(DBUSMENU_IS_CLIENT(client)); @@ -657,7 +833,7 @@ item_activated (DBusGProxy * proxy, gint id, guint timestamp, DbusmenuClient * c /* 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; @@ -670,16 +846,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); @@ -692,7 +860,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; } @@ -700,7 +868,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); @@ -720,83 +888,39 @@ id_update (DBusGProxy * proxy, gint id, DbusmenuClient * client) /* 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! */ return build_proxies(client); } -/* This is the response to see if the name has an owner. If - it does, then we should build the proxies here. Race condition - check. */ -static void -name_owner_check (DBusGProxy *proxy, gboolean has_owner, GError *error, gpointer userdata) -{ - if (error != NULL) { - return; - } - - if (!has_owner) { - return; - } - - DbusmenuClient * client = DBUSMENU_CLIENT(userdata); - build_proxies(client); - return; -} - /* This function builds the DBus proxy which will look out for the service coming up. */ static void build_dbus_proxy (DbusmenuClient * client) { DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); - GError * error = NULL; - - if (priv->dbusproxy != NULL) { - 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); + if (priv->dbusproxy != 0) { return; } - 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); + 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); /* Now let's check to make sure we're not in some race condition case. */ - org_freedesktop_DBus_name_has_owner_async(priv->dbusproxy, - priv->dbus_name, - name_owner_check, - client); + /* TODO: Not sure how to check for names in GDBus */ return; } @@ -820,7 +944,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; @@ -830,119 +958,372 @@ 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; + + if (priv->menuproxy_cancel != NULL) { + g_object_unref(priv->menuproxy_cancel); + priv->menuproxy_cancel = NULL; + } + + /* Check the text direction if available */ + GVariant * textdir = g_dbus_proxy_get_cached_property(priv->menuproxy, "TextDirection"); + if (textdir != NULL) { + if (g_variant_is_of_type(textdir, G_VARIANT_TYPE_VARIANT)) { + GVariant * tmp = g_variant_get_variant(textdir); + g_variant_unref(textdir); + textdir = tmp; + } + + priv->text_direction = dbusmenu_text_direction_get_value_from_nick(g_variant_get_string(textdir, NULL)); + g_object_notify(G_OBJECT(user_data), DBUSMENU_CLIENT_PROP_TEXT_DIRECTION); + + g_variant_unref(textdir); + } + + /* Check the status if available */ + GVariant * status = g_dbus_proxy_get_cached_property(priv->menuproxy, "Status"); + if (status != NULL) { + if (g_variant_is_of_type(status, G_VARIANT_TYPE_VARIANT)) { + GVariant * tmp = g_variant_get_variant(status); + g_variant_unref(status); + status = tmp; + } + + priv->status = dbusmenu_status_get_value_from_nick(g_variant_get_string(status, NULL)); + g_object_notify(G_OBJECT(user_data), DBUSMENU_CLIENT_PROP_STATUS); + + g_variant_unref(status); } - 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); + /* Get the icon theme directories if available */ + GVariant * icon_dirs = g_dbus_proxy_get_cached_property(priv->menuproxy, "IconThemePath"); + if (icon_dirs != NULL) { + if (priv->icon_dirs != NULL) { + g_strfreev(priv->icon_dirs); + priv->icon_dirs = NULL; + } + + priv->icon_dirs = g_variant_dup_strv(icon_dirs, NULL); + g_signal_emit(G_OBJECT(client), signals[ICON_THEME_DIRS], 0, priv->icon_dirs, TRUE); - 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); + g_variant_unref(icon_dirs); + } - 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); + /* 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_object_register_marshaller(_dbusmenu_server_marshal_VOID__INT_UINT, G_TYPE_NONE, G_TYPE_INT, G_TYPE_UINT, G_TYPE_INVALID); - dbus_g_proxy_add_signal(priv->menuproxy, "ItemActivationRequested", G_TYPE_INT, G_TYPE_UINT, G_TYPE_INVALID); - dbus_g_proxy_connect_signal(priv->menuproxy, "ItemActivationRequested", G_CALLBACK(item_activated), client, NULL); + 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); + g_signal_connect(priv->menuproxy, "g-properties-changed", G_CALLBACK(menuproxy_prop_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 properites changing */ +static void +menuproxy_prop_changed_cb (GDBusProxy * proxy, GVariant * properties, GStrv invalidated, gpointer user_data) { - if (node == NULL) { - return -1; - } - 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; + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(user_data); + DbusmenuTextDirection olddir = priv->text_direction; + DbusmenuStatus oldstatus = priv->status; + gboolean dirs_changed = FALSE; + + /* Invalidate first */ + gchar * invalid; + gint i = 0; + for (invalid = invalidated[i]; invalid != NULL; invalid = invalidated[++i]) { + if (g_strcmp0(invalid, "TextDirection") == 0) { + priv->text_direction = DBUSMENU_TEXT_DIRECTION_NONE; + } + if (g_strcmp0(invalid, "Status") == 0) { + priv->status = DBUSMENU_STATUS_NORMAL; + } + if (g_strcmp0(invalid, "IconThemePath") == 0) { + if (priv->icon_dirs != NULL) { + dirs_changed = TRUE; + g_strfreev(priv->icon_dirs); + priv->icon_dirs = NULL; + } + } } - 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; + /* Check updates */ + GVariantIter iters; + gchar * key; GVariant * value; + g_variant_iter_init(&iters, properties); + while (g_variant_iter_loop(&iters, "{sv}", &key, &value)) { + if (g_strcmp0(key, "TextDirection") == 0) { + if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT)) { + GVariant * tmp = g_variant_get_variant(value); + g_variant_unref(value); + value = tmp; + } + + priv->text_direction = dbusmenu_text_direction_get_value_from_nick(g_variant_get_string(value, NULL)); + } + if (g_strcmp0(key, "Status") == 0) { + if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT)) { + GVariant * tmp = g_variant_get_variant(value); + g_variant_unref(value); + value = tmp; + } + + priv->status = dbusmenu_status_get_value_from_nick(g_variant_get_string(value, NULL)); + } + if (g_strcmp0(key, "IconThemePath") == 0) { + if (priv->icon_dirs != NULL) { + g_strfreev(priv->icon_dirs); + priv->icon_dirs = NULL; } - break; + + priv->icon_dirs = g_variant_dup_strv(value, NULL); + dirs_changed = TRUE; } } - g_warning("Unable to find an ID on the node"); - return -1; + if (olddir != priv->text_direction) { + g_object_notify(G_OBJECT(user_data), DBUSMENU_CLIENT_PROP_TEXT_DIRECTION); + } + + if (oldstatus != priv->status) { + g_object_notify(G_OBJECT(user_data), DBUSMENU_CLIENT_PROP_STATUS); + } + + if (dirs_changed) { + g_signal_emit(G_OBJECT(user_data), signals[ICON_THEME_DIRS], 0, priv->icon_dirs, TRUE); + } + + return; } -/* A small helper that calls _property_set on each hash table - entry in the properties hash. */ +/* Handle the case where we change owners */ static void -get_properties_helper (gpointer key, gpointer value, gpointer data) +menuproxy_name_changed_cb (GObject * object, GParamSpec * pspec, gpointer user_data) { - dbusmenu_menuitem_property_set_value((DbusmenuMenuitem *)data, (gchar *)key, (GValue *)value); + 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)); + } + + return; +} + +/* Handle the signals out of the proxy */ +static void +menuproxy_signal_cb (GDBusProxy * proxy, gchar * sender, gchar * signal, GVariant * params, gpointer user_data) +{ + 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, "ItemsPropertiesUpdated") == 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; + GVariant * ritemsv = g_variant_get_child_value(params, 1); + g_variant_iter_init(&ritems, ritemsv); + + GVariant * ritem; + while ((ritem = g_variant_iter_next_value(&ritems)) != NULL) { + GVariant * idv = g_variant_get_child_value(ritem, 0); + gint id = g_variant_get_int32(idv); + g_variant_unref(idv); + DbusmenuMenuitem * menuitem = dbusmenu_menuitem_find_id(priv->root, id); + + if (menuitem == NULL) { + continue; + } + + GVariantIter properties; + GVariant * propv = g_variant_get_child_value(ritem, 1); + g_variant_iter_init(&properties, propv); + gchar * property; + + while (g_variant_iter_loop(&properties, "s", &property)) { + /* g_debug("Removing property '%s' on %d", property, id); */ + dbusmenu_menuitem_property_remove(menuitem, property); + } + g_variant_unref(ritem); + g_variant_unref(propv); + } + g_variant_unref(ritemsv); + + GVariantIter items; + GVariant * itemsv = g_variant_get_child_value(params, 0); + g_variant_iter_init(&items, itemsv); + + GVariant * item; + while ((item = g_variant_iter_next_value(&items)) != NULL) { + GVariant * idv = g_variant_get_child_value(item, 0); + gint id = g_variant_get_int32(idv); + g_variant_unref(idv); + + GVariantIter properties; + GVariant * propv = g_variant_get_child_value(item, 1); + g_variant_iter_init(&properties, propv); + gchar * property; + GVariant * value; + + while (g_variant_iter_loop(&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); + + if (internalvalue != value) { + /* If we unboxed, we need to drop it, otherwise the + iter_loop function will unref for us */ + g_variant_unref(internalvalue); + } + } + g_variant_unref(propv); + g_variant_unref(item); + } + g_variant_unref(itemsv); + } 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); + g_free(property); + g_variant_unref(value); + } 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; } @@ -952,17 +1333,38 @@ 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; + goto out; } - g_hash_table_foreach(properties, get_properties_helper, data); - g_hash_table_destroy(properties); + + if (properties == NULL) { + goto out; + } + + if (!g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))) { + g_warning("Properties are of type '%s' instead of type '%s'", g_variant_get_type_string(properties), "a{sv}"); + goto out; + } + + GVariantIter iter; + gchar * key; + GVariant * value; + + g_variant_iter_init(&iter, properties); + + while (g_variant_iter_loop(&iter, "{sv}", &key, &value)) { + dbusmenu_menuitem_property_set_variant(item, key, value); + } + +out: g_object_unref(data); + return; } @@ -970,7 +1372,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; @@ -979,18 +1381,42 @@ menuitem_get_properties_replace_cb (DBusGProxy * proxy, GHashTable * properties, g_warning("Unable to replace properties on %d: %s", dbusmenu_menuitem_get_id(DBUSMENU_MENUITEM(data)), error->message); have_error = TRUE; } + + if (properties == NULL) { + have_error = TRUE; + } - GList * current_props = NULL; + /* Get the list of the current properties */ + GList * current_props = dbusmenu_menuitem_properties_list(DBUSMENU_MENUITEM(data)); + GList * tmp = 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); + if (!have_error && g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))) { + GVariantIter iter; + g_variant_iter_init(&iter, properties); + gchar * name; GVariant * value; + + /* Remove the entries from the current list that we have new + values for. This way we don't create signals of them being + removed with the duplication of the value being changed. */ + while (g_variant_iter_loop(&iter, "{sv}", &name, &value)) { + for (tmp = current_props; tmp != NULL; tmp = g_list_next(tmp)) { + if (g_strcmp0((gchar *)tmp->data, name) == 0) { + current_props = g_list_delete_link(current_props, tmp); + break; + } + } } } + /* Remove all entries that we're not getting values for, we can + assume that they no longer exist */ + for (tmp = current_props; tmp != NULL && have_error == FALSE; tmp = g_list_next(tmp)) { + dbusmenu_menuitem_property_remove(DBUSMENU_MENUITEM(data), (const gchar *)tmp->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); } @@ -1001,38 +1427,41 @@ 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(propdata->item); - g_free(data); - return; + goto out; + } + + if (properties == NULL) { + g_warning("Not realizing new item as properties for it were unavailable"); + goto out; } 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 @@ -1044,6 +1473,7 @@ menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GEr g_signal_emit(G_OBJECT(propdata->client), signals[NEW_MENUITEM], 0, propdata->item, TRUE); } +out: g_object_unref(propdata->item); g_free(propdata); @@ -1053,61 +1483,73 @@ 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 event '%s' on menu item %d: %s", edata->event, dbusmenu_menuitem_get_id(edata->menuitem), error->message); } - g_signal_emit(edata->client, signals[EVENT_RESULT], 0, edata->menuitem, edata->event, &edata->data, edata->timestamp, error, TRUE); + g_signal_emit(edata->client, signals[EVENT_RESULT], 0, edata->menuitem, edata->event, edata->variant, edata->timestamp, error, TRUE); - g_value_unset(&edata->data); + g_variant_unref(edata->variant); g_free(edata->event); g_object_unref(edata->menuitem); + g_object_unref(edata->client); g_free(edata); + if (G_UNLIKELY(error != NULL)) { + g_error_free(error); + } + if (G_LIKELY(params != NULL)) { + g_variant_unref(params); + } + return; } /* 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, DbusmenuMenuitem * mi) { 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); - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); if (mi == NULL) { g_warning("Asked to activate a menuitem %d that we don't know about", id); return; } - if (value == NULL) { - GValue internalval = {0}; - g_value_init(&internalval, G_TYPE_INT); - g_value_set_int(&internalval, 0); - value = &internalval; + if (variant == NULL) { + variant = g_variant_new_int32(0); } event_data_t * edata = g_new0(event_data_t, 1); edata->client = client; + g_object_ref(client); edata->menuitem = mi; g_object_ref(edata->menuitem); edata->event = g_strdup(name); - g_value_init(&edata->data, G_VALUE_TYPE(value)); - g_value_copy(value, &edata->data); edata->timestamp = timestamp; - - DBusGAsyncData *stuff; - stuff = g_slice_new (DBusGAsyncData); - stuff->cb = G_CALLBACK (menuitem_call_cb); - stuff->userdata = edata; - dbus_g_proxy_begin_call_with_timeout (priv->menuproxy, "Event", org_ayatana_dbusmenu_event_async_callback, stuff, _dbus_glib_async_data_free, 1000, G_TYPE_INT, id, G_TYPE_STRING, name, G_TYPE_VALUE, value, G_TYPE_UINT, timestamp, G_TYPE_INVALID); + 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; } @@ -1122,14 +1564,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. */ @@ -1160,7 +1612,14 @@ 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; } @@ -1206,10 +1665,16 @@ parse_layout_update (DbusmenuMenuitem * item, DbusmenuClient * client) /* 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) { + if (layout == NULL) { + return NULL; + } + /* First verify and figure out what we've got */ - gint id = parse_node_get_id(node); + GVariant * idv = g_variant_get_child_value(layout, 0); + gint id = g_variant_get_int32(idv); + g_variant_unref(idv); if (id < 0) { return NULL; } @@ -1221,20 +1686,34 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it g_return_val_if_fail(id == dbusmenu_menuitem_get_id(item), NULL); /* Some variables */ - xmlNodePtr children; - guint position; + GVariantIter children; + GVariant * childrenv; + + childrenv = g_variant_get_child_value(layout, 2); + g_variant_iter_init(&children, childrenv); + + guint position = 0; GList * oldchildren = g_list_copy(dbusmenu_menuitem_get_children(item)); /* g_debug("Starting old children: %d", g_list_length(oldchildren)); */ /* Go through all the XML Nodes and make sure that we have menuitems to cover those XML nodes. */ - for (children = node->children, position = 0; children != NULL; children = children->next, position++) { + GVariant * child; + 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)) { + GVariant * tmp = g_variant_get_variant(child); + g_variant_unref(child); + child = tmp; + } + + GVariant * childidv = g_variant_get_child_value(child, 0); + gint childid = g_variant_get_int32(childidv); + g_variant_unref(childidv); if (childid < 0) { /* Don't increment the position when there isn't a valid node in the XML tree. It's probably a comment. */ - position--; + g_variant_unref(child); continue; } DbusmenuMenuitem * childmi = NULL; @@ -1267,6 +1746,35 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it dbusmenu_menuitem_child_reorder(item, childmi, position); parse_layout_update(childmi, client); } + + /* Apply known properties sent in the structure to the + menu item. Sometimes they may just be copies */ + if (childmi != NULL) { + GVariantIter iter; + gchar * prop; + GVariant * value; + GVariant * child_props; + + /* Set the type first as it can manage the behavior of + all other properties. */ + child_props = g_variant_get_child_value(child, 1); + g_variant_iter_init(&iter, child_props); + while (g_variant_iter_loop(&iter, "{sv}", &prop, &value)) { + if (g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_TYPE) == 0) { + dbusmenu_menuitem_property_set_variant(childmi, prop, value); + } + } + + /* Now go through and do all the properties. */ + g_variant_iter_init(&iter, child_props); + while (g_variant_iter_loop(&iter, "{sv}", &prop, &value)) { + dbusmenu_menuitem_property_set_variant(childmi, prop, value); + } + g_variant_unref(child_props); + } + + position++; + g_variant_unref(child); } /* Remove any children that are no longer used by this version of @@ -1283,18 +1791,31 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it /* We've got everything built up at this node and reconcilled */ - /* Flush the properties requests */ - get_properties_flush(client); + /* 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. */ - children = node->children; + g_variant_iter_init(&children, childrenv); + + child = g_variant_iter_next_value(&children); GList * childmis = dbusmenu_menuitem_get_children(item); - while (children != NULL && childmis != NULL) { - gint xmlid = parse_node_get_id(children); + while (child != NULL && childmis != NULL) { + if (g_variant_is_of_type(child, G_VARIANT_TYPE_VARIANT)) { + GVariant * tmp = g_variant_get_variant(child); + g_variant_unref(child); + child = tmp; + } + + GVariant * xmlidv = g_variant_get_child_value(child, 0); + gint xmlid = g_variant_get_int32(xmlidv); + g_variant_unref(xmlidv); /* If this isn't a valid menu item we need to move on until we have one. This avoids things like comments. */ if (xmlid < 0) { - children = children->next; + g_variant_unref(child); + child = g_variant_iter_next_value(&children); continue; } @@ -1303,13 +1824,17 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it g_debug("Recursing parse_layout_xml. XML ID: %d MI ID: %d", xmlid, miid); #endif - parse_layout_xml(client, children, DBUSMENU_MENUITEM(childmis->data), item, proxy); + parse_layout_xml(client, child, DBUSMENU_MENUITEM(childmis->data), item, proxy); - children = children->next; + g_variant_unref(child); + child = g_variant_iter_next_value(&children); childmis = g_list_next(childmis); } - if (children != NULL) { - g_warning("Sync failed, now we've got extra XML nodes."); + + g_variant_unref(childrenv); + + 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."); @@ -1321,7 +1846,7 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it /* 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"); @@ -1329,17 +1854,6 @@ parse_layout (DbusmenuClient * client, const gchar * layout) DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); - xmlDocPtr xmldoc; - - /* No one should need more characters than this! */ - xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 1024*1024), "dbusmenu.xml", NULL, 0); - - xmlNodePtr root = xmlDocGetRootElement(xmldoc); - - if (root == NULL) { - g_warning("Unable to get root node of menu XML"); - } - DbusmenuMenuitem * oldroot = priv->root; if (priv->root == NULL) { @@ -1348,11 +1862,10 @@ parse_layout (DbusmenuClient * client, const gchar * layout) parse_layout_update(priv->root, client); } - priv->root = parse_layout_xml(client, root, priv->root, NULL, priv->menuproxy); - xmlFreeDoc(xmldoc); + 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) { @@ -1377,24 +1890,38 @@ 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; + GVariant * layout = 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)) { + GVariant * revv = g_variant_get_child_value(params, 0); + guint rev = g_variant_get_uint32(revv); + g_variant_unref(revv); + + 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 @@ -1406,6 +1933,21 @@ 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 (layout != NULL) { + g_variant_unref(layout); + } + + if (params != NULL) { + g_variant_unref(params); + } + + g_object_unref(G_OBJECT(client)); return; } @@ -1415,36 +1957,60 @@ static void update_layout (DbusmenuClient * client) { DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + g_return_if_fail(priv->layout_props != NULL); if (priv->menuproxy == NULL) { 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, priv->layout_props); // 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) @@ -1458,21 +2024,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) { @@ -1480,10 +2046,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 @@ -1491,30 +2053,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->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: (scope notified): 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: (scope notified): 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, GDestroyNotify destroy_func) +{ g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), FALSE); g_return_val_if_fail(type != NULL, FALSE); @@ -1535,6 +2141,70 @@ 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; } + +/** + dbusmenu_client_get_text_direction: + @client: #DbusmenuClient to check the text direction on + + Gets the text direction that the server is exporting. If + the server is not exporting a direction then the value + #DBUSMENU_TEXT_DIRECTION_NONE will be returned. + + Return value: Text direction being exported. +*/ +DbusmenuTextDirection +dbusmenu_client_get_text_direction (DbusmenuClient * client) +{ + g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), DBUSMENU_TEXT_DIRECTION_NONE); + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + return priv->text_direction; +} + +/** + dbusmenu_client_get_status: + @client: #DbusmenuClient to check the status on + + Gets the recommended current status that the server + is exporting for the menus. In situtations where the + value is #DBUSMENU_STATUS_NOTICE it is recommended that + the client show the menus to the user an a more noticible + way. + + Return value: Status being exported. +*/ +DbusmenuStatus +dbusmenu_client_get_status (DbusmenuClient * client) +{ + g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), DBUSMENU_STATUS_NORMAL); + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + return priv->status; +} + +/** + * dbusmenu_client_get_icon_paths: + * @client: The #DbusmenuClient to get the icon paths from + * + * Gets the stored and exported icon paths from the client. + * + * Return value: (transfer none): A NULL-terminated list of icon paths with + * memory managed by the client. Duplicate if you want + * to keep them. + */ +const GStrv +dbusmenu_client_get_icon_paths (DbusmenuClient * client) +{ + g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), NULL); + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + return priv->icon_dirs; +} + diff --git a/libdbusmenu-glib/client.h b/libdbusmenu-glib/client.h index 5d4b5c3..1cb9ee5 100644 --- a/libdbusmenu-glib/client.h +++ b/libdbusmenu-glib/client.h @@ -33,6 +33,7 @@ License version 3 and version 2.1 along with this program. If not, see #include <glib-object.h> #include "menuitem.h" +#include "types.h" G_BEGIN_DECLS @@ -43,28 +44,107 @@ G_BEGIN_DECLS #define DBUSMENU_IS_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_CLIENT)) #define DBUSMENU_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_CLIENT, DbusmenuClientClass)) +/** + * DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED: + * + * String to attach to signal #DbusmenuClient::layout-updated + */ #define DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED "layout-updated" +/** + * DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED: + * + * String to attach to signal #DbusmenuClient::root-changed + */ #define DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED "root-changed" +/** + * DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM: + * + * String to attach to signal #DbusmenuClient::new-menuitem + */ #define DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM "new-menuitem" +/** + * DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE: + * + * String to attach to signal #DbusmenuClient::item-activate + */ #define DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE "item-activate" +/** + * DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT: + * + * String to attach to signal #DbusmenuClient::event-result + */ #define DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT "event-result" +/** + * DBUSMENU_CLIENT_SIGNAL_ICON_THEME_DIRS_CHANGED: + * + * String to attach to signal #DbusmenuClient::icon-theme-dirs-changed + */ +#define DBUSMENU_CLIENT_SIGNAL_ICON_THEME_DIRS_CHANGED "icon-theme-dirs-changed" +/** + * DBUSMENU_CLIENT_PROP_DBUS_NAME: + * + * String to access property #DbusmenuClient:dbus-name + */ #define DBUSMENU_CLIENT_PROP_DBUS_NAME "dbus-name" +/** + * DBUSMENU_CLIENT_PROP_DBUS_OBJECT: + * + * String to access property #DbusmenuClient:dbus-object + */ #define DBUSMENU_CLIENT_PROP_DBUS_OBJECT "dbus-object" +/** + * DBUSMENU_CLIENT_PROP_STATUS: + * + * String to access property #DbusmenuClient:status + */ +#define DBUSMENU_CLIENT_PROP_STATUS "status" +/** + * DBUSMENU_CLIENT_PROP_TEXT_DIRECTION: + * + * String to access property #DbusmenuClient:text-direction + */ +#define DBUSMENU_CLIENT_PROP_TEXT_DIRECTION "text-direction" +/** + * DBUSMENU_CLIENT_TYPES_DEFAULT: + * + * Used to set the 'type' property on a menu item to create + * a standard menu item. + */ #define DBUSMENU_CLIENT_TYPES_DEFAULT "standard" +/** + * DBUSMENU_CLIENT_TYPES_SEPARATOR: + * + * Used to set the 'type' property on a menu item to create + * a separator menu item. + */ #define DBUSMENU_CLIENT_TYPES_SEPARATOR "separator" +/** + * DBUSMENU_CLIENT_TYPES_IMAGE: + * + * Used to set the 'type' property on a menu item to create + * an image menu item. Deprecated as standard menu items now + * support images as well. + */ #define DBUSMENU_CLIENT_TYPES_IMAGE "standard" +typedef struct _DbusmenuClientPrivate DbusmenuClientPrivate; + /** DbusmenuClientClass: @parent_class: #GObjectClass @layout_updated: Slot for #DbusmenuClient::layout-updated. + @root_changed: Slot for #DbusmenuClient::root-changed. @new_menuitem: Slot for #DbusmenuClient::new-menuitem. @item_activate: Slot for #DbusmenuClient::item-activate. @event_result: Slot for #DbusmenuClient::event-error. + @icon_theme_dirs: Slot for #DbusmenuClient::icon-theme-dirs-changed. @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. A simple class that takes all of the information from a #DbusmenuServer over DBus and makes the same set of @@ -78,18 +158,19 @@ struct _DbusmenuClientClass { void (*root_changed) (DbusmenuMenuitem * newroot); void (*new_menuitem) (DbusmenuMenuitem * newitem); void (*item_activate) (DbusmenuMenuitem * item, guint timestamp); - void (*event_result) (DbusmenuMenuitem * item, gchar * event, GValue * data, guint timestamp, GError * error); + void (*event_result) (DbusmenuMenuitem * item, gchar * event, GVariant * data, guint timestamp, GError * error); + void (*icon_theme_dirs) (DbusmenuMenuitem * item, gpointer theme_dirs, GError * error); - /* Reserved for future use */ + /*< Private >*/ void (*reserved1) (void); void (*reserved2) (void); - /* void (*reserved3) (void); */ - /* void (*reserved4) (void); */ + void (*reserved3) (void); + void (*reserved4) (void); + void (*reserved5) (void); }; /** DbusmenuClient: - @parent: #GObject. The client for a #DbusmenuServer creating a shared object set of #DbusmenuMenuitem objects. @@ -97,9 +178,25 @@ 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 + + Return value: #TRUE if the type has been handled. #FALSE if this + function was somehow unable to handle it. +*/ +typedef gboolean (*DbusmenuClientTypeHandler) (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data); GType dbusmenu_client_get_type (void); DbusmenuClient * dbusmenu_client_new (const gchar * name, @@ -108,15 +205,14 @@ DbusmenuMenuitem * dbusmenu_client_get_root (DbusmenuClient * client) gboolean dbusmenu_client_add_type_handler (DbusmenuClient * client, const gchar * type, DbusmenuClientTypeHandler newfunc); -void dbusmenu_client_send_event (DbusmenuClient * client, - gint id, - const gchar * name, - const GValue * value, - guint timestamp); -void dbusmenu_client_send_about_to_show(DbusmenuClient * client, - gint id, - void (*cb) (gpointer user_data), - gpointer cb_data); +gboolean dbusmenu_client_add_type_handler_full (DbusmenuClient * client, + const gchar * type, + DbusmenuClientTypeHandler newfunc, + gpointer user_data, + GDestroyNotify destroy_func); +DbusmenuTextDirection dbusmenu_client_get_text_direction (DbusmenuClient * client); +DbusmenuStatus dbusmenu_client_get_status (DbusmenuClient * client); +const GStrv dbusmenu_client_get_icon_paths (DbusmenuClient * client); /** SECTION:client diff --git a/libdbusmenu-glib/dbus-menu.xml b/libdbusmenu-glib/dbus-menu.xml index 9e8013c..4b5a5d8 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. @@ -157,6 +157,19 @@ License version 3 and version 2.1 along with this program. If not, see </td> <td>""</td> </tr> + <tr> + <td>disposition</td> + <td>string</td> + <td> + How the menuitem feels the information it's displaying to the + user should be presented. + - "normal" a standard menu item + - "informative" providing additional information to the user + - "warning" looking at potentially harmful results + - "alert" something bad could potentially happen + </td> + <td>"normal"</td> + </tr> </table> Vendor specific properties can be added by prefixing them with @@ -164,51 +177,83 @@ License version 3 and version 2.1 along with this program. If not, see ]]></dox:d> <!-- Properties --> - <property name="version" type="u" access="read"> + <property name="Version" type="u" access="read"> <dox:d> Provides the version of the DBusmenu API that this API is implementing. </dox:d> </property> + <property name="TextDirection" type="s" access="read"> + <dox:d> + Represents the way the text direction of the application. This + allows the server to handle mismatches intelligently. For left- + to-right the string is "ltr" for right-to-left it is "rtl". + </dox:d> + </property> + + <property name="Status" type="s" access="read"> + <dox:d> + Tells if the menus are in a normal state or they believe that they + could use some attention. Cases for showing them would be if help + were referring to them or they accessors were being highlighted. + This property can have two values: "normal" in almost all cases and + "notice" when they should have a higher priority to be shown. + </dox:d> + </property> + + <property name="IconThemePath" type="as" access="read"> + <dox:d> + A list of directories that should be used for finding icons using + the icon naming spec. Idealy there should only be one for the icon + theme, but additional ones are often added by applications for + app specific icons. + </dox:d> + </property> + <!-- 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 @a parentId. It will return all of the + properties or specific ones depending of the value in @a propertyNames. + + The format is recursive, where the second 'v' is in the same format + as the original 'a(ia{sv}av)'. Its content depends on the value + of @a recursionDepth. + </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="recursionDepth" direction="in"> + <dox:d> + The amount of levels of recursion to use. This affects the + content of the second variant array. + - -1: deliver all the items under the @a parentId. + - 0: no recursion, the array will be empty. + - n: array will contains items up to 'n' level depth. + </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"> - <dox:d>The layout as an XML string of IDs.</dox:d> + <arg type="(ia{sv}av)" name="layout" direction="out"> + <dox:d>The layout, as a recursive structure.</dox:d> </arg> </method> <method name="GetGroupProperties"> - <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QVariantList"/> - <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/> <dox:d> Returns the list of items which are children of @a parentId. </dox:d> @@ -236,33 +281,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"> @@ -274,6 +307,8 @@ License version 3 and version 2.1 along with this program. If not, see @li "clicked" @li "hovered" + @li "opened" + @li "closed" Vendor specific events can be added by prefixing them with "x-<vendor>-" ]]></dox:d> @@ -309,25 +344,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="updatedProps" direction="out" /> + <arg type="a(ias)" name="removedProps" direction="out" /> </signal> - <signal name="LayoutUpdated"> <dox:d> Triggered by the application to notify display of a layout update, up to 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/defaults.c b/libdbusmenu-glib/defaults.c new file mode 100644 index 0000000..a5caf0b --- /dev/null +++ b/libdbusmenu-glib/defaults.c @@ -0,0 +1,292 @@ +/* +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/> +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <glib/gi18n-lib.h> + +#include "defaults.h" +#include "menuitem.h" +#include "client.h" + +struct _DbusmenuDefaultsPrivate { + GHashTable * types; +}; + +typedef struct _DefaultEntry DefaultEntry; +struct _DefaultEntry { + GVariantType * type; + GVariant * value; +}; + +#define DBUSMENU_DEFAULTS_GET_PRIVATE(o) \ +(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_DEFAULTS, DbusmenuDefaultsPrivate)) + +static void dbusmenu_defaults_class_init (DbusmenuDefaultsClass *klass); +static void dbusmenu_defaults_init (DbusmenuDefaults *self); +static void dbusmenu_defaults_dispose (GObject *object); +static void dbusmenu_defaults_finalize (GObject *object); + +static DefaultEntry * entry_create (const GVariantType * type, GVariant * variant); +static void entry_destroy (gpointer entry); + +G_DEFINE_TYPE (DbusmenuDefaults, dbusmenu_defaults, G_TYPE_OBJECT); + +static void +dbusmenu_defaults_class_init (DbusmenuDefaultsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (DbusmenuDefaultsPrivate)); + + object_class->dispose = dbusmenu_defaults_dispose; + object_class->finalize = dbusmenu_defaults_finalize; + return; +} + +static void +dbusmenu_defaults_init (DbusmenuDefaults *self) +{ + self->priv = DBUSMENU_DEFAULTS_GET_PRIVATE(self); + + self->priv->types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); + + /* Standard defaults */ + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_VISIBLE, G_VARIANT_TYPE_BOOLEAN, g_variant_new_boolean(TRUE)); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_ENABLED, G_VARIANT_TYPE_BOOLEAN, g_variant_new_boolean(TRUE)); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_LABEL, G_VARIANT_TYPE_STRING, g_variant_new_string(_("Label Empty"))); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_ICON_NAME, G_VARIANT_TYPE_STRING, NULL); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, G_VARIANT_TYPE_STRING, NULL); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, G_VARIANT_TYPE_INT32, NULL); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_SHORTCUT, G_VARIANT_TYPE("aas"), NULL); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, G_VARIANT_TYPE_STRING, NULL); + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_DISPOSITION, G_VARIANT_TYPE_STRING, g_variant_new_string(DBUSMENU_MENUITEM_DISPOSITION_NORMAL)); + + /* Separator defaults */ + dbusmenu_defaults_default_set(self, DBUSMENU_CLIENT_TYPES_SEPARATOR, DBUSMENU_MENUITEM_PROP_VISIBLE, G_VARIANT_TYPE_BOOLEAN, g_variant_new_boolean(TRUE)); + + return; +} + +static void +dbusmenu_defaults_dispose (GObject *object) +{ + DbusmenuDefaults * self = DBUSMENU_DEFAULTS(object); + + if (self->priv->types != NULL) { + g_hash_table_destroy(self->priv->types); + self->priv->types = NULL; + } + + G_OBJECT_CLASS (dbusmenu_defaults_parent_class)->dispose (object); + return; +} + +static void +dbusmenu_defaults_finalize (GObject *object) +{ + + G_OBJECT_CLASS (dbusmenu_defaults_parent_class)->finalize (object); + return; +} + +/* Create a new entry based on the info provided */ +static DefaultEntry * +entry_create (const GVariantType * type, GVariant * variant) +{ + DefaultEntry * defentry = g_new0(DefaultEntry, 1); + + if (type != NULL) { + defentry->type = g_variant_type_copy(type); + } + + if (variant != NULL) { + defentry->value = variant; + g_variant_ref_sink(variant); + } + + return defentry; +} + +/* Destroy an entry */ +static void +entry_destroy (gpointer entry) +{ + DefaultEntry * defentry = (DefaultEntry *)entry; + + if (defentry->type != NULL) { + g_variant_type_free(defentry->type); + defentry->type = NULL; + } + + if (defentry->value != NULL) { + g_variant_unref(defentry->value); + defentry->value = NULL; + } + + g_free(defentry); + return; +} + +static DbusmenuDefaults * default_defaults = NULL; + +/* + * dbusmenu_defaults_ref_default: + * + * Get a reference to the default instance. If it doesn't exist this + * function will create it. + * + * Return value: (transfer full): A reference to the defaults + */ +DbusmenuDefaults * +dbusmenu_defaults_ref_default (void) +{ + if (default_defaults == NULL) { + default_defaults = DBUSMENU_DEFAULTS(g_object_new(DBUSMENU_TYPE_DEFAULTS, NULL)); + g_object_add_weak_pointer(G_OBJECT(default_defaults), (gpointer *)&default_defaults); + } else { + g_object_ref(default_defaults); + } + + return default_defaults; +} + +/* + * dbusmenu_defaults_default_set: + * @defaults: The #DbusmenuDefaults object to add to + * @type: (allow-none): The #DbusmenuMenuitem type for this default if #NULL will default to #DBUSMENU_CLIENT_TYPE_DEFAULT + * @property: Property name for the default + * @prop_type: (allow-none): Type of the property for runtime checking. To disable checking set to #NULL. + * @value: (allow-none): Default value for @property. #NULL if by default it is unset, but you want type checking from @prop_type. + * + * Sets up an entry in the defaults database for a given @property + * and menuitem type @type. @prop_type and @value can both be #NULL + * but both of them can not. + */ +void +dbusmenu_defaults_default_set (DbusmenuDefaults * defaults, const gchar * type, const gchar * property, const GVariantType * prop_type, GVariant * value) +{ + g_return_if_fail(DBUSMENU_IS_DEFAULTS(defaults)); + g_return_if_fail(property != NULL); + g_return_if_fail(prop_type != NULL || value != NULL); + + if (type == NULL) { + type = DBUSMENU_CLIENT_TYPES_DEFAULT; + } + + GHashTable * prop_table = (GHashTable *)g_hash_table_lookup(defaults->priv->types, type); + + /* We've never had a default for this type, so we need + to create a table for it. */ + if (prop_table == NULL) { + prop_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, entry_destroy); + + g_hash_table_insert(prop_table, g_strdup(property), entry_create(prop_type, value)); + g_hash_table_insert(defaults->priv->types, g_strdup(type), prop_table); + } else { + g_hash_table_replace(prop_table, g_strdup(property), entry_create(prop_type, value)); + } + + return; +} + +/* + * dbusmenu_defaults_default_get: + * @defaults: The default database to use + * @type: (allow-none): The #DbusmenuMenuitem type for this default if #NULL will default to #DBUSMENU_CLIENT_TYPE_DEFAULT + * @property: Property name to lookup + * + * Gets an entry in the database for a give @property and @type. + * + * Return value: (transfer none): Returns a variant that does not + * have it's ref count increased. If you want to keep it, you should + * do that. + */ +GVariant * +dbusmenu_defaults_default_get (DbusmenuDefaults * defaults, const gchar * type, const gchar * property) +{ + g_return_val_if_fail(DBUSMENU_IS_DEFAULTS(defaults), NULL); + g_return_val_if_fail(property != NULL, NULL); + + if (type == NULL) { + type = DBUSMENU_CLIENT_TYPES_DEFAULT; + } + + GHashTable * prop_table = (GHashTable *)g_hash_table_lookup(defaults->priv->types, type); + + if (prop_table == NULL) { + return NULL; + } + + DefaultEntry * entry = (DefaultEntry *)g_hash_table_lookup(prop_table, property); + + if (entry == NULL) { + return NULL; + } + + return entry->value; +} + +/* + * dbusmenu_defaults_default_get_type: + * @defaults: The default database to use + * @type: (allow-none): The #DbusmenuMenuitem type for this default if #NULL will default to #DBUSMENU_CLIENT_TYPE_DEFAULT + * @property: Property name to lookup + * + * Gets the type for an entry in the database for a give @property and @type. + * + * Return value: (transfer none): Returns a type for the given + * @property value. + */ +GVariantType * +dbusmenu_defaults_default_get_type (DbusmenuDefaults * defaults, const gchar * type, const gchar * property) +{ + g_return_val_if_fail(DBUSMENU_IS_DEFAULTS(defaults), NULL); + g_return_val_if_fail(property != NULL, NULL); + + if (type == NULL) { + type = DBUSMENU_CLIENT_TYPES_DEFAULT; + } + + GHashTable * prop_table = (GHashTable *)g_hash_table_lookup(defaults->priv->types, type); + + if (prop_table == NULL) { + return NULL; + } + + DefaultEntry * entry = (DefaultEntry *)g_hash_table_lookup(prop_table, property); + + if (entry == NULL) { + return NULL; + } + + return entry->type; +} + diff --git a/libdbusmenu-glib/defaults.h b/libdbusmenu-glib/defaults.h new file mode 100644 index 0000000..a1a8158 --- /dev/null +++ b/libdbusmenu-glib/defaults.h @@ -0,0 +1,86 @@ +/* +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_DEFAULTS_H__ +#define __DBUSMENU_DEFAULTS_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define DBUSMENU_TYPE_DEFAULTS (dbusmenu_defaults_get_type ()) +#define DBUSMENU_DEFAULTS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_TYPE_DEFAULTS, DbusmenuDefaults)) +#define DBUSMENU_DEFAULTS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_TYPE_DEFAULTS, DbusmenuDefaultsClass)) +#define DBUSMENU_IS_DEFAULTS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_TYPE_DEFAULTS)) +#define DBUSMENU_IS_DEFAULTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_DEFAULTS)) +#define DBUSMENU_DEFAULTS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_DEFAULTS, DbusmenuDefaultsClass)) + +typedef struct _DbusmenuDefaults DbusmenuDefaults; +typedef struct _DbusmenuDefaultsClass DbusmenuDefaultsClass; +typedef struct _DbusmenuDefaultsPrivate DbusmenuDefaultsPrivate; + +/* + * DbusmenuDefaultsClass: + * + * All of the signals and functions for #DbusmenuDefaults + */ +struct _DbusmenuDefaultsClass { + GObjectClass parent_class; +}; + +/* + * DbusmenuDefaults: + * + * A singleton to hold all of the defaults for the menuitems + * so they can use those easily. + */ +struct _DbusmenuDefaults { + GObject parent; + + /*< Private >*/ + DbusmenuDefaultsPrivate * priv; +}; + +GType dbusmenu_defaults_get_type (void); +DbusmenuDefaults * dbusmenu_defaults_ref_default (void); +void dbusmenu_defaults_default_set (DbusmenuDefaults * defaults, + const gchar * type, + const gchar * property, + const GVariantType * prop_type, + GVariant * value); +GVariant * dbusmenu_defaults_default_get (DbusmenuDefaults * defaults, + const gchar * type, + const gchar * property); +GVariantType * dbusmenu_defaults_default_get_type (DbusmenuDefaults * defaults, + const gchar * type, + const gchar * property); + +G_END_DECLS + +#endif diff --git a/libdbusmenu-glib/enum-types.c.in b/libdbusmenu-glib/enum-types.c.in new file mode 100644 index 0000000..40f1759 --- /dev/null +++ b/libdbusmenu-glib/enum-types.c.in @@ -0,0 +1,116 @@ +/*** BEGIN file-header ***/ +/* +Enums from the dbusmenu headers + +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/> +*/ + +#include "enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +#include "@basename@" +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +/** + @enum_name@_get_type: + + Builds a GLib type for the #@EnumName@ enumeration. + + Return value: A unique #GType for the #@EnumName@ enum. +*/ +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL} + }; + + etype = g_@type@_register_static (g_intern_static_string("@EnumName@"), values); + } + + return etype; +} + +/** + @enum_name@_get_nick: + @value: The value of @EnumName@ to get the nick of + + Looks up in the enum table for the nick of @value. + + Return value: The nick for the given value or #NULL on error +*/ +const gchar * +@enum_name@_get_nick (@EnumName@ value) +{ + GEnumClass * class = G_ENUM_CLASS(g_type_class_ref(@enum_name@_get_type())); + g_return_val_if_fail(class != NULL, NULL); + + const gchar * ret = NULL; + GEnumValue * val = g_enum_get_value(class, value); + if (val != NULL) { + ret = val->value_nick; + } + + g_type_class_unref(class); + return ret; +} + +/** + @enum_name@_get_value_from_nick: + @nick: The enum nick to lookup + + Looks up in the enum table for the value of @nick. + + Return value: The value for the given @nick +*/ +@EnumName@ +@enum_name@_get_value_from_nick (const gchar * nick) +{ + GEnumClass * class = G_ENUM_CLASS(g_type_class_ref(@enum_name@_get_type())); + g_return_val_if_fail(class != NULL, 0); + + @EnumName@ ret = 0; + GEnumValue * val = g_enum_get_value_by_nick(class, nick); + if (val != NULL) { + ret = val->value; + } + + g_type_class_unref(class); + return ret; +} + + +/*** END value-tail ***/ diff --git a/libdbusmenu-glib/enum-types.h.in b/libdbusmenu-glib/enum-types.h.in new file mode 100644 index 0000000..5758438 --- /dev/null +++ b/libdbusmenu-glib/enum-types.h.in @@ -0,0 +1,65 @@ +/*** BEGIN file-header ***/ +/* +Enums from the dbusmenu headers + +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_ENUM_TYPES_H__ +#define __DBUSMENU_ENUM_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-tail ***/ + +G_END_DECLS + +#endif /* __DBUSMENU_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from file: "@filename@" */ +#include "@basename@" + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ + +GType @enum_name@_get_type (void) G_GNUC_CONST; +const gchar * @enum_name@_get_nick (@EnumName@ value) G_GNUC_CONST; +@EnumName@ @enum_name@_get_value_from_nick (const gchar * nick) G_GNUC_CONST; + +/** + DBUSMENU_TYPE_@ENUMSHORT@: + + Gets the #GType value for the type associated with the + #@EnumName@ enumerated type. +*/ +#define DBUSMENU_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) + +/*** END value-header ***/ diff --git a/libdbusmenu-glib/menuitem-marshal.list b/libdbusmenu-glib/menuitem-marshal.list index 654c91b..4382ed7 100644 --- a/libdbusmenu-glib/menuitem-marshal.list +++ b/libdbusmenu-glib/menuitem-marshal.list @@ -1,6 +1,7 @@ -VOID: STRING, POINTER +VOID: STRING, VARIANT VOID: OBJECT, UINT, UINT VOID: OBJECT, UINT VOID: OBJECT VOID: VOID VOID: UINT +BOOLEAN: STRING, VARIANT, UINT diff --git a/libdbusmenu-glib/menuitem-private.h b/libdbusmenu-glib/menuitem-private.h index 3a0c026..336769d 100644 --- a/libdbusmenu-glib/menuitem-private.h +++ b/libdbusmenu-glib/menuitem-private.h @@ -33,9 +33,12 @@ 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); +gboolean dbusmenu_menuitem_exposed (DbusmenuMenuitem * mi); 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..ee988d8 100644 --- a/libdbusmenu-glib/menuitem-proxy.h +++ b/libdbusmenu-glib/menuitem-proxy.h @@ -42,33 +42,57 @@ 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); }; /** - DbusmeneMenuitemProxy: - @parent: The instance of #DbusmenuMenuitem + DbusmenuMenuitemProxy: Public instance data for a #DbusmenuMenuitemProxy. */ struct _DbusmenuMenuitemProxy { DbusmenuMenuitem parent; + + /*< Private >*/ + DbusmenuMenuitemProxyPrivate * priv; }; GType dbusmenu_menuitem_proxy_get_type (void); DbusmenuMenuitemProxy * dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi); DbusmenuMenuitem * dbusmenu_menuitem_proxy_get_wrapped (DbusmenuMenuitemProxy * pmi); +/** + * SECTION:menuitem-proxy + * @short_description: A menuitem that proxies from another menuitem + * @stability: Unstable + * @include: libdbusmenu-glib/menuitem-proxy.h + * + * This small object allows for proxying all the properties from a remote + * menuitem to a new object that can be moved around appropriately within + * the new menu structure. + */ + G_END_DECLS #endif diff --git a/libdbusmenu-glib/menuitem.c b/libdbusmenu-glib/menuitem.c index ea69776..4e037ee 100644 --- a/libdbusmenu-glib/menuitem.c +++ b/libdbusmenu-glib/menuitem.c @@ -33,6 +33,7 @@ License version 3 and version 2.1 along with this program. If not, see #include "menuitem.h" #include "menuitem-marshal.h" #include "menuitem-private.h" +#include "defaults.h" #ifdef MASSIVEDEBUGGING #define LABEL(x) dbusmenu_menuitem_property_get(DBUSMENU_MENUITEM(x), DBUSMENU_MENUITEM_PROP_LABEL) @@ -52,7 +53,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; @@ -60,6 +60,9 @@ struct _DbusmenuMenuitemPrivate GHashTable * properties; gboolean root; gboolean realized; + DbusmenuDefaults * defaults; + gboolean exposed; + DbusmenuMenuitem * parent; }; /* Signals */ @@ -71,6 +74,8 @@ enum { CHILD_MOVED, REALIZED, SHOW_TO_USER, + ABOUT_TO_SHOW, + EVENT, LAST_SIGNAL }; @@ -84,8 +89,7 @@ enum { #define PROP_ID_S "id" -#define DBUSMENU_MENUITEM_GET_PRIVATE(o) \ -(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_MENUITEM, DbusmenuMenuitemPrivate)) +#define DBUSMENU_MENUITEM_GET_PRIVATE(o) (DBUSMENU_MENUITEM(o)->priv) /* Prototypes */ static void dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass); @@ -96,7 +100,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); @@ -114,6 +119,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: @@ -129,8 +135,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. @@ -229,6 +235,38 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass) 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); + /** + DbusmenuMenuitem::event: + @arg0: The #DbusmenuMenuitem object. + @arg1: Name of the event + @arg2: Information passed along with the event + @arg3: X11 timestamp of when the event happened + + Emitted when an event is passed through. The event is signalled + after handle_event is called. + */ + signals[EVENT] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_EVENT, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + G_STRUCT_OFFSET(DbusmenuMenuitemClass, event), + g_signal_accumulator_true_handled, NULL, + _dbusmenu_menuitem_marshal_BOOLEAN__STRING_VARIANT_UINT, + G_TYPE_BOOLEAN, 3, G_TYPE_STRING, G_TYPE_VARIANT, G_TYPE_UINT); + g_object_class_install_property (object_class, PROP_ID, g_param_spec_int(PROP_ID_S, "ID for the menu item", "This is a unique indentifier for the menu item.", @@ -271,15 +309,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; } @@ -288,15 +323,20 @@ _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; + + priv->defaults = dbusmenu_defaults_ref_default(); + priv->exposed = FALSE; return; } @@ -313,6 +353,16 @@ dbusmenu_menuitem_dispose (GObject *object) g_list_free(priv->children); priv->children = NULL; + if (priv->defaults != NULL) { + g_object_unref(priv->defaults); + priv->defaults = NULL; + } + + if (priv->parent) { + g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); + priv->parent = NULL; + } + G_OBJECT_CLASS (dbusmenu_menuitem_parent_class)->dispose (object); return; } @@ -378,24 +428,60 @@ 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) { + if (g_strcmp0(name, DBUSMENU_MENUITEM_EVENT_ACTIVATED) == 0) { g_signal_emit(G_OBJECT(mi), signals[ITEM_ACTIVATED], 0, timestamp, TRUE); } 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_strcmp0(DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU, dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) != 0) { + 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; +} + +/* A helper function to get the type of the menuitem, this might + be a candidate for optimization in the future. */ +static const gchar * +menuitem_get_type (DbusmenuMenuitem * mi) +{ + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + GVariant * currentval = (GVariant *)g_hash_table_lookup(priv->properties, DBUSMENU_MENUITEM_PROP_TYPE); + if (currentval != NULL) { + return g_variant_get_string(currentval, NULL); + } + return NULL; +} + +/* Public interface */ + +/** + * dbusmenu_menuitem_new: + * + * Create a new #DbusmenuMenuitem with all default values. + * + * Return value: A newly allocated #DbusmenuMenuitem. + */ DbusmenuMenuitem * dbusmenu_menuitem_new (void) { @@ -403,13 +489,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) { @@ -419,13 +505,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) { @@ -442,17 +528,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) { @@ -462,12 +548,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) { @@ -482,15 +568,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) (element-type Dbusmenu.Menuitem): A #GList of pointers to #DbusmenuMenuitem objects. + */ GList * dbusmenu_menuitem_get_children (DbusmenuMenuitem * mi) { @@ -503,28 +589,29 @@ dbusmenu_menuitem_get_children (DbusmenuMenuitem * mi) /* For all the taken children we need to signal that they were removed */ static void -take_children_signal (gpointer data, gpointer user_data) +take_children_helper (gpointer data, gpointer user_data) { #ifdef MASSIVEDEBUGGING g_debug("Menuitem %d (%s) signalling child removed %d (%s)", ID(user_data), LABEL(user_data), ID(data), LABEL(data)); #endif + dbusmenu_menuitem_unparent(DBUSMENU_MENUITEM(data)); 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) { @@ -533,7 +620,7 @@ dbusmenu_menuitem_take_children (DbusmenuMenuitem * mi) DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); GList * children = priv->children; priv->children = NULL; - g_list_foreach(children, take_children_signal, mi); + g_list_foreach(children, take_children_helper, mi); dbusmenu_menuitem_property_remove(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY); @@ -541,16 +628,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) { @@ -580,15 +667,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) { @@ -624,15 +711,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) { @@ -642,6 +729,10 @@ dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child) DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); g_return_val_if_fail(g_list_find(priv->children, child) == NULL, FALSE); + if (!dbusmenu_menuitem_set_parent(child, mi)) { + return FALSE; + } + if (priv->children == NULL && !dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) { dbusmenu_menuitem_property_set(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU); } @@ -656,15 +747,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) { @@ -674,6 +765,10 @@ dbusmenu_menuitem_child_prepend (DbusmenuMenuitem * mi, DbusmenuMenuitem * child DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); g_return_val_if_fail(g_list_find(priv->children, child) == NULL, FALSE); + if (!dbusmenu_menuitem_set_parent(child, mi)) { + return FALSE; + } + if (priv->children == NULL && !dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) { dbusmenu_menuitem_property_set(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU); } @@ -688,24 +783,30 @@ 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) { g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE); g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE); + if (dbusmenu_menuitem_get_parent(child) != mi) { + g_warning("Trying to remove a child that doesn't believe we're it's parent."); + return FALSE; + } + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); priv->children = g_list_remove(priv->children, child); + dbusmenu_menuitem_unparent(child); #ifdef MASSIVEDEBUGGING g_debug("Menuitem %d (%s) signalling child removed %d (%s)", ID(mi), LABEL(mi), ID(child), LABEL(child)); #endif @@ -720,17 +821,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) { @@ -740,6 +841,10 @@ dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); g_return_val_if_fail(g_list_find(priv->children, child) == NULL, FALSE); + if (!dbusmenu_menuitem_set_parent(child, mi)) { + return FALSE; + } + if (priv->children == NULL && !dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) { dbusmenu_menuitem_property_set(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU); } @@ -754,17 +859,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) { @@ -794,16 +899,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) { @@ -847,18 +952,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) { @@ -875,239 +980,397 @@ 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_set_parent: + * @mi: The #DbusmenuMenuitem for which to set the parent + * @parent: The new parent #DbusmenuMenuitem + * + * Sets the parent of @mi to @parent. If @mi already + * has a parent, then this call will fail. The parent will + * be set automatically when using the usual methods to add a + * child menuitem, so this function should not normally be + * called directly + * + * Return value: Whether the parent was set successfully + */ +gboolean +dbusmenu_menuitem_set_parent (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE); + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE); + + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + + if (priv->parent != NULL) { + g_warning ("Menu item already has a parent"); + return FALSE; + } + + priv->parent = parent; + g_object_add_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); + + return TRUE; +} + +/** + * dbusmenu_menuitem_unparent: + * @mi: The #DbusmenuMenuitem to unparent + * + * Unparents the menu item @mi. If @mi doesn't have a + * parent, then this call will fail. The menuitem will + * be unparented automatically when using the usual methods + * to delete a child menuitem, so this function should not + * normally be called directly + * + * Return value: Whether the menu item was unparented successfully + */ +gboolean +dbusmenu_menuitem_unparent (DbusmenuMenuitem * mi) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE); + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + + if (priv->parent == NULL) { + g_warning("Menu item doesn't have a parent"); + return FALSE; + } + + g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); + priv->parent = NULL; + + return TRUE; +} + +/** + * dbusmenu_menuitem_get_parent: + * @mi: The #DbusmenuMenuitem for which to inspect the parent + * + * This function looks up the parent of @mi + * + * Return value: (transfer none): The parent of this menu item + */ +DbusmenuMenuitem * +dbusmenu_menuitem_get_parent (DbusmenuMenuitem * mi) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), NULL); + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + + return priv->parent; +} + +/** + * 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); + g_return_val_if_fail(g_utf8_validate(property, -1, NULL), FALSE); DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); - /* g_debug("Setting a property. ID: %d Prop: %s Value: %s", priv->id, property, value); */ + GVariant * default_value = NULL; + + const gchar * type = menuitem_get_type(mi); + + if (value != NULL) { + /* Check the expected type to see if we want to have a warning */ + GVariantType * default_type = dbusmenu_defaults_default_get_type(priv->defaults, type, property); + if (default_type != NULL) { + /* If we have an expected type we should check to see if + the value we've been given is of the same type and generate + a warning if it isn't */ + if (!g_variant_is_of_type(value, default_type)) { + g_warning("Setting menuitem property '%s' with value of type '%s' when expecting '%s'", property, g_variant_get_type_string(value), g_variant_type_peek_string(default_type)); + } + } + } - #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; + /* Check the defaults database to see if we have a default + for this property. */ + default_value = dbusmenu_defaults_default_get(priv->defaults, type, property); + if (default_value != NULL && value != NULL) { + /* Now see if we're setting this to the same value as the + default. If we are then we just want to swallow this variant + and make the function behave like we're clearing it. */ + if (g_variant_equal(default_value, value)) { + g_variant_ref_sink(value); + g_variant_unref(value); + value = NULL; + } } - #endif - gchar * lprop = g_strdup(property); - GValue * lval = g_new0(GValue, 1); - g_value_init(lval, G_VALUE_TYPE(value)); - g_value_copy(value, lval); + gboolean replaced = FALSE; + gboolean remove = FALSE; + gchar * hash_key = NULL; + GVariant * hash_variant = NULL; + gboolean inhash = g_hash_table_lookup_extended(priv->properties, property, (gpointer *)&hash_key, (gpointer *)&hash_variant); - 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 (inhash && hash_variant == NULL) { + g_warning("The property '%s' is in the hash with a NULL variant", property); + inhash = FALSE; + } + + if (value != NULL) { + /* NOTE: We're only marking this as replaced if this is true + but we're actually replacing it no matter. This is so that + the variant passed in sticks around which the caller may + expect. They shouldn't, but it's low cost to remove bugs. */ + if (!inhash || !g_variant_equal(hash_variant, value)) { + replaced = TRUE; + } + + gchar * lprop = g_strdup(property); + g_variant_ref_sink(value); - g_signal_emit(G_OBJECT(mi), signals[PROPERTY_CHANGED], 0, lprop, lval, TRUE); + /* Really important that this is _insert as that means the value + that we just created in the _strdup is free'd and not the one + currently in the hashtable. That could be the same as the one + being passed in and then the signal emit would be done with a + bad value */ + g_hash_table_insert(priv->properties, lprop, value); + } else { + if (inhash) { + /* So the question you should be asking if you're paying attention + is "Why not just do the remove here?" It's a good question with + an interesting answer. Bascially it's the same reason as above, + in a couple cases the passed in properties is the value in the hash + table so we can avoid strdup'ing it by removing it (and thus free'ing + it) after the signal emition */ + remove = TRUE; + replaced = TRUE; + g_hash_table_steal(priv->properties, property); + } + } + + /* 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 (!inhash || replaced) { + GVariant * signalval = value; + + if (signalval == NULL) { + /* Might also be NULL, but if it is we're definitely + clearing this thing. */ + signalval = default_value; + } + + g_signal_emit(G_OBJECT(mi), signals[PROPERTY_CHANGED], 0, property, signalval, TRUE); + } + + if (remove) { + g_free(hash_key); + g_variant_unref(hash_variant); + } 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); -} + GVariant * currentval = (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. + if (currentval == NULL) { + currentval = dbusmenu_defaults_default_get(priv->defaults, menuitem_get_type(mi), property); + } - Look up a property on @mi and return the value of it if - it exits. Returns #FALSE if the property doesn't exist. + return currentval; +} - 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_exist: + * @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) { @@ -1122,35 +1385,34 @@ 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) { g_return_if_fail(DBUSMENU_IS_MENUITEM(mi)); g_return_if_fail(property != NULL); - DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); - - g_hash_table_remove(priv->properties, property); + dbusmenu_menuitem_property_set_variant(mi, property, NULL); return; } /** - 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) @@ -1161,32 +1423,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); @@ -1196,16 +1464,84 @@ 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 ((properties == NULL || properties[0] == NULL) && 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); + } + + if (properties != NULL) { + GVariantBuilder builder; + gboolean builder_init = FALSE; + int i = 0; const gchar * prop; + + for (prop = properties[i]; prop != NULL; prop = properties[++i]) { + GVariant * propvalue = dbusmenu_menuitem_property_get_variant(mi, prop); + + if (propvalue == NULL) { + continue; + } + + if (!builder_init) { + builder_init = TRUE; + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + } + + GVariant * dict = g_variant_new_dict_entry(g_variant_new_string((gchar *)prop), + g_variant_new_variant((GVariant *)propvalue)); + g_variant_builder_add_value(&builder, dict); + } + + if (builder_init) { + 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) { @@ -1216,14 +1552,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) { @@ -1234,38 +1570,64 @@ 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); + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + priv->exposed = TRUE; 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 { @@ -1281,15 +1643,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) @@ -1305,26 +1667,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 @@ -1332,25 +1694,35 @@ dbusmenu_menuitem_handle_event (DbusmenuMenuitem * mi, const gchar * name, const #endif DbusmenuMenuitemClass * class = DBUSMENU_MENUITEM_GET_CLASS(mi); - if (class->handle_event != NULL) { - return class->handle_event(mi, name, value, timestamp); + gboolean handled = FALSE; + if (variant == NULL) { + variant = g_variant_new_int32(0); } - return; + + g_variant_ref_sink(variant); + + g_signal_emit(G_OBJECT(mi), signals[EVENT], g_quark_from_string(name), name, variant, timestamp, &handled); + + if (!handled && class->handle_event != NULL) { + class->handle_event(mi, name, variant, timestamp); + } + + g_variant_unref(variant); } /** - 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 @@ -1368,14 +1740,14 @@ dbusmenu_menuitem_send_about_to_show (DbusmenuMenuitem * mi, dbusmenu_menuitem_a } /** - 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. -*/ + * 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) { @@ -1385,3 +1757,31 @@ dbusmenu_menuitem_show_to_user (DbusmenuMenuitem * mi, guint timestamp) return; } + +/* Checks to see if the value of this property is unique or just the + default value. */ +gboolean +dbusmenu_menuitem_property_is_default (DbusmenuMenuitem * mi, const gchar * property) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE); + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + + GVariant * currentval = (GVariant *)g_hash_table_lookup(priv->properties, property); + if (currentval != NULL) { + /* If we're storing it locally, then it shouldn't be a default */ + return FALSE; + } + + /* If we haven't stored it locally, then it's the default */ + return TRUE; +} + +/* Check to see if this menu item has been sent into the bus yet or + not. If no one cares we can give less info */ +gboolean +dbusmenu_menuitem_exposed (DbusmenuMenuitem * mi) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), FALSE); + DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + return priv->exposed; +} diff --git a/libdbusmenu-glib/menuitem.h b/libdbusmenu-glib/menuitem.h index 9be938b..985e1a3 100644 --- a/libdbusmenu-glib/menuitem.h +++ b/libdbusmenu-glib/menuitem.h @@ -41,45 +41,329 @@ G_BEGIN_DECLS #define DBUSMENU_IS_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_MENUITEM)) #define DBUSMENU_MENUITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_MENUITEM, DbusmenuMenuitemClass)) - +/* ***************************************** */ +/* ********* GLib Object Signals ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED: + * + * String to attach to signal #DbusmenuServer::property-changed + */ #define DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED "property-changed" +/** + * DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED: + * + * String to attach to signal #DbusmenuServer::item-activated + */ #define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated" +/** + * DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED: + * + * String to attach to signal #DbusmenuServer::child-added + */ #define DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED "child-added" +/** + * DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED: + * + * String to attach to signal #DbusmenuServer::child-removed + */ #define DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED "child-removed" +/** + * DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED: + * + * String to attach to signal #DbusmenuServer::child-moved + */ #define DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED "child-moved" +/** + * DBUSMENU_MENUITEM_SIGNAL_REALIZED: + * + * String to attach to signal #DbusmenuServer::realized + */ #define DBUSMENU_MENUITEM_SIGNAL_REALIZED "realized" +/** + * DBUSMENU_MENUITEM_SIGNAL_REALIZED_ID: + * + * ID to attach to signal #DbusmenuServer::realized + */ #define DBUSMENU_MENUITEM_SIGNAL_REALIZED_ID (g_signal_lookup(DBUSMENU_MENUITEM_SIGNAL_REALIZED, DBUSMENU_TYPE_MENUITEM)) +/** + * DBUSMENU_MENUITEM_SIGNAL_SHOW_TO_USER: + * + * String to attach to signal #DbusmenuServer::show-to-user + */ #define DBUSMENU_MENUITEM_SIGNAL_SHOW_TO_USER "show-to-user" +/** + * DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW: + * + * String to attach to signal #DbusmenuServer::about-to-show + */ +#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show" +/** + * DBUSMENU_MENUITEM_SIGNAL_EVENT: + * + * String to attach to signal #DbusmenuServer::event + */ +#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event" +/* ***************************************** */ +/* ********* Menuitem Properties ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_PROP_TYPE: + * + * #DbusmenuMenuitem property used to represent what type of menuitem + * this object represents. Type: #G_VARIANT_TYPE_STRING. + */ #define DBUSMENU_MENUITEM_PROP_TYPE "type" +/** + * DBUSMENU_MENUITEM_PROP_VISIBLE: + * + * #DbusmenuMenuitem property used to represent whether the menuitem + * should be shown or not. Type: #G_VARIANT_TYPE_BOOLEAN. + */ #define DBUSMENU_MENUITEM_PROP_VISIBLE "visible" +/** + * DBUSMENU_MENUITEM_PROP_ENABLED: + * + * #DbusmenuMenuitem property used to represent whether the menuitem + * is clickable or not. Type: #G_VARIANT_TYPE_BOOLEAN. + */ #define DBUSMENU_MENUITEM_PROP_ENABLED "enabled" +/** + * DBUSMENU_MENUITEM_PROP_LABEL: + * + * #DbusmenuMenuitem property used for the text on the menu item. + * Type: #G_VARIANT_TYPE_STRING + */ #define DBUSMENU_MENUITEM_PROP_LABEL "label" +/** + * DBUSMENU_MENUITEM_PROP_ICON_NAME: + * + * #DbusmenuMenuitem property that is the name of the icon under the + * Freedesktop.org icon naming spec. Type: #G_VARIANT_TYPE_STRING + */ #define DBUSMENU_MENUITEM_PROP_ICON_NAME "icon-name" +/** + * DBUSMENU_MENUITEM_PROP_ICON_DATA: + * + * #DbusmenuMenuitem property that is the raw data of a custom icon + * used in the application. Type: #G_VARIANT_TYPE_VARIANT + * + * It is recommended that this is not set directly but instead the + * libdbusmenu-gtk library is used with the function dbusmenu_menuitem_property_set_image() + */ #define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data" +/** + * DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE: + * + * #DbusmenuMenuitem property that says what type of toggle entry should + * be shown in the menu. Should be either #DBUSMENU_MENUITEM_TOGGLE_CHECK + * or #DBUSMENU_MENUITEM_TOGGLE_RADIO. Type: #G_VARIANT_TYPE_STRING + */ #define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type" +/** + * DBUSMENU_MENUITEM_PROP_TOGGLE_STATE: + * + * #DbusmenuMenuitem property that says what state a toggle entry should + * be shown as the menu. Should be either #DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED + * #DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED or #DBUSMENU_MENUITEM_TOGGLE_STATUE_UNKNOWN. + * Type: #G_VARIANT_TYPE_INT32 + */ #define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state" +/** + * DBUSMENU_MENUITEM_PROP_SHORTCUT: + * + * #DbusmenuMenuitem property that is the entries that represent a shortcut + * to activate the menuitem. It is an array of arrays of strings. + * Type: #G_VARIANT_TYPE_ARRAY + * + * It is recommended that this is not set directly but instead the + * libdbusmenu-gtk library is used with the function dbusmenu_menuitem_property_set_shortcut() + */ #define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut" +/** + * DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY: + * + * #DbusmenuMenuitem property that tells how the children of this menuitem + * should be displayed. Most likely this will be unset or of the value + * #DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU. Type: #G_VARIANT_TYPE_STRING + */ #define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display" +/** + * DBUSMENU_MENUITEM_PROP_DISPOSITION: + * + * #DbusmenuMenuitem property to tell what type of information that the + * menu item is displaying to the user. Type: #G_VARIANT_TYPE_STRING + */ +#define DBUSMENU_MENUITEM_PROP_DISPOSITION "disposition" +/* ***************************************** */ +/* ********* Toggle Values ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_TOGGLE_CHECK: + * + * Used to set #DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE to be a standard + * check mark item. + */ #define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark" +/** + * DBUSMENU_MENUITEM_TOGGLE_RADIO: + * + * Used to set #DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE to be a standard + * radio item. + */ #define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio" +/* ***************************************** */ +/* ********* Toggle States ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED: + * + * Used to set #DBUSMENU_MENUITEM_PROP_TOGGLE_STATE so that the menu's + * toggle item is empty. + */ #define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0 +/** + * DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED: + * + * Used to set #DBUSMENU_MENUITEM_PROP_TOGGLE_STATE so that the menu's + * toggle item is filled. + */ #define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1 +/** + * DBUSMENU_MENUITEM_TOGGLE_STATE_UNKNOWN: + * + * Used to set #DBUSMENU_MENUITEM_PROP_TOGGLE_STATE so that the menu's + * toggle item is undecided. + */ #define DBUSMENU_MENUITEM_TOGGLE_STATE_UNKNOWN -1 +/* ***************************************** */ +/* ********* Icon specials ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_ICON_NAME_BLANK: + * + * Used to set #DBUSMENU_MENUITEM_PROP_TOGGLE_STATE so that the menu's + * toggle item is undecided. + */ #define DBUSMENU_MENUITEM_ICON_NAME_BLANK "blank-icon" +/* ***************************************** */ +/* ********* Shortcut Modifiers ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_SHORTCUT_CONTROL: + * + * Used in #DBUSMENU_MENUITEM_PROP_SHORTCUT to represent the + * control key. + */ #define DBUSMENU_MENUITEM_SHORTCUT_CONTROL "Control" +/** + * DBUSMENU_MENUITEM_SHORTCUT_ALT: + * + * Used in #DBUSMENU_MENUITEM_PROP_SHORTCUT to represent the + * alternate key. + */ #define DBUSMENU_MENUITEM_SHORTCUT_ALT "Alt" +/** + * DBUSMENU_MENUITEM_SHORTCUT_SHIFT: + * + * Used in #DBUSMENU_MENUITEM_PROP_SHORTCUT to represent the + * shift key. + */ #define DBUSMENU_MENUITEM_SHORTCUT_SHIFT "Shift" +/** + * DBUSMENU_MENUITEM_SHORTCUT_SUPER: + * + * Used in #DBUSMENU_MENUITEM_PROP_SHORTCUT to represent the + * super key. + */ #define DBUSMENU_MENUITEM_SHORTCUT_SUPER "Super" +/* ***************************************** */ +/* ********* Child Display Types ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU: + * + * Used in #DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY to have the + * subitems displayed as a submenu. + */ #define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu" +/* ***************************************** */ +/* ********* Menuitem Dispositions ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_DISPOSITION_NORMAL: + * + * Used in #DBUSMENU_MENUITEM_PROP_DISPOSITION to have a menu + * item displayed in the normal manner. Default value. + */ +#define DBUSMENU_MENUITEM_DISPOSITION_NORMAL "normal" +/** + * DBUSMENU_MENUITEM_DISPOSITION_INFORMATIVE: + * + * Used in #DBUSMENU_MENUITEM_PROP_DISPOSITION to have a menu + * item displayed in a way that conveys it's giving additional + * information to the user. + */ +#define DBUSMENU_MENUITEM_DISPOSITION_INFORMATIVE "informative" +/** + * DBUSMENU_MENUITEM_DISPOSITION_WARNING: + * + * Used in #DBUSMENU_MENUITEM_PROP_DISPOSITION to have a menu + * item displayed in a way that conveys it's giving a warning + * to the user. + */ +#define DBUSMENU_MENUITEM_DISPOSITION_WARNING "warning" +/** + * DBUSMENU_MENUITEM_DISPOSITION_ALERT: + * + * Used in #DBUSMENU_MENUITEM_PROP_DISPOSITION to have a menu + * item displayed in a way that conveys it's giving an alert + * to the user. + */ +#define DBUSMENU_MENUITEM_DISPOSITION_ALERT "alert" + +/* ***************************************** */ +/* ********* Dbusmenu Events ********* */ +/* ***************************************** */ +/** + * DBUSMENU_MENUITEM_EVENT_ACTIVATED: + * + * String for the event identifier when a menu item is clicked + * on by the user. + */ +#define DBUSMENU_MENUITEM_EVENT_ACTIVATED "clicked" + +/** + * DBUSMENU_MENUITEM_EVENT_OPENED: + * + * String for the event identifier when a menu is opened and + * displayed to the user. Only valid for items that contain + * submenus. + */ +#define DBUSMENU_MENUITEM_EVENT_OPENED "opened" + +/** + * DBUSMENU_MENUITEM_EVENT_CLOSED: + * + * String for the event identifier when a menu is closed and + * displayed to the user. Only valid for items that contain + * submenus. + */ +#define DBUSMENU_MENUITEM_EVENT_CLOSED "closed" + +typedef struct _DbusmenuMenuitemPrivate DbusmenuMenuitemPrivate; + /** * DbusmenuMenuitem: + * @parent: Parent object + * @priv: Private data * * This is the #GObject based object that represents a menu * item. It gets created the same on both the client and @@ -92,6 +376,9 @@ typedef struct _DbusmenuMenuitem DbusmenuMenuitem; struct _DbusmenuMenuitem { GObject parent; + + /*< Private >*/ + DbusmenuMenuitemPrivate * priv; }; /** @@ -105,27 +392,40 @@ 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. + * @properties: (allow-none): A list of properties that should be the only ones in the resulting variant structure * * 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: + * @parent_class: Functions and signals from our parent * @property_changed: Slot for #DbusmenuMenuitem::property-changed. * @item_activated: Slot for #DbusmenuMenuitem::item-activated. * @child_added: Slot for #DbusmenuMenuitem::child-added. * @child_removed: Slot for #DbusmenuMenuitem::child-removed. * @child_moved: Slot for #DbusmenuMenuitem::child-moved. * @realized: Slot for #DbusmenuMenuitem::realized. - * @buildxml: Virtual function that appends the strings required to represent this menu item in the menu XML file. + * @about_to_show: Slot for #DbusmenuMenuitem::about-to-show. + * @buildvariant: Virtual function that appends the strings required to represent this menu item in the menu variant. * @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. + * @event: Slot for #DbsumenuMenuitem::event. + * @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. + * + * Functions and signals that every menuitem should know something + * about. */ typedef struct _DbusmenuMenuitemClass DbusmenuMenuitemClass; struct _DbusmenuMenuitemClass @@ -133,7 +433,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); @@ -141,15 +441,21 @@ 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); - /* void (*reserved1) (void); */ - /* void (*reserved2) (void); */ - /* void (*reserved3) (void); */ - /* void (*reserved4) (void); -- realized, realloc when bumping lib version */ + gboolean (*about_to_show) (void); + + void (*event) (const gchar * name, GVariant * value, guint timestamp); + + /*< Private >*/ + void (*reserved1) (void); + void (*reserved2) (void); + void (*reserved3) (void); + void (*reserved4) (void); + void (*reserved5) (void); }; GType dbusmenu_menuitem_get_type (void); @@ -171,12 +477,16 @@ gboolean dbusmenu_menuitem_child_reorder (DbusmenuMenuitem * mi, DbusmenuMenuite DbusmenuMenuitem * dbusmenu_menuitem_child_find (DbusmenuMenuitem * mi, gint id); DbusmenuMenuitem * dbusmenu_menuitem_find_id (DbusmenuMenuitem * mi, gint id); +gboolean dbusmenu_menuitem_set_parent (DbusmenuMenuitem * mi, DbusmenuMenuitem * parent); +gboolean dbusmenu_menuitem_unparent (DbusmenuMenuitem *mi); +DbusmenuMenuitem * dbusmenu_menuitem_get_parent (DbusmenuMenuitem * mi); + 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); @@ -188,8 +498,8 @@ 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); diff --git a/libdbusmenu-glib/server-marshal.list b/libdbusmenu-glib/server-marshal.list index 0d68c4e..08ebf93 100644 --- a/libdbusmenu-glib/server-marshal.list +++ b/libdbusmenu-glib/server-marshal.list @@ -1,3 +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 68afb92..c7057df 100644 --- a/libdbusmenu-glib/server.c +++ b/libdbusmenu-glib/server.c @@ -30,41 +30,42 @@ License version 3 and version 2.1 along with this program. If not, see #include "config.h" #endif +#include <glib/gi18n-lib.h> +#include <gio/gio.h> + #include "menuitem-private.h" #include "server.h" #include "server-marshal.h" +#include "enum-types.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, gchar ** properties, GHashTable ** dict, GError ** error); -static gboolean _dbusmenu_server_get_group_properties (DbusmenuServer * server, GArray * ids, gchar ** properties, GPtrArray ** values, GError ** error); -static gboolean _dbusmenu_server_event (DbusmenuServer * server, gint id, gchar * eventid, GValue * data, guint timestamp, GError ** error); -static gboolean _dbusmenu_server_get_children (DbusmenuServer * server, gint id, GPtrArray * properties, GPtrArray ** output, GError ** error); -static gboolean _dbusmenu_server_about_to_show (DbusmenuServer * server, gint id, gboolean * need_update, GError ** error); -/* DBus Helpers */ -static void _gvalue_array_append_int(GValueArray *array, gint i); -static void _gvalue_array_append_hashtable(GValueArray *array, GHashTable * dict); - -#include "dbusmenu-server.h" +#include "dbus-menu-clean.xml.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; + + DbusmenuTextDirection text_direction; + DbusmenuStatus status; + GStrv icon_dirs; + + 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 { @@ -82,7 +83,10 @@ enum { PROP_0, PROP_DBUS_OBJECT, PROP_ROOT_NODE, - PROP_VERSION + PROP_VERSION, + PROP_TEXT_DIRECTION, + PROP_STATUS, + PROP_ICON_THEME_DIRS }; /* Errors */ @@ -91,22 +95,112 @@ 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 default_text_direction (DbusmenuServer * server); +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); @@ -137,8 +231,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. @@ -193,7 +287,7 @@ dbusmenu_server_class_init (DbusmenuServerClass *class) 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", @@ -205,8 +299,56 @@ dbusmenu_server_class_init (DbusmenuServerClass *class) "The version of the DBusmenu API that we're implementing.", DBUSMENU_VERSION_NUMBER, DBUSMENU_VERSION_NUMBER, DBUSMENU_VERSION_NUMBER, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_TEXT_DIRECTION, + g_param_spec_enum(DBUSMENU_SERVER_PROP_TEXT_DIRECTION, "The default direction of text", + "The object that represents this set of menus on DBus", + DBUSMENU_TYPE_TEXT_DIRECTION, DBUSMENU_TEXT_DIRECTION_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_STATUS, + g_param_spec_enum(DBUSMENU_SERVER_PROP_STATUS, "Status of viewing the menus", + "Exports over DBus whether the menus should be given special visuals", + DBUSMENU_TYPE_STATUS, DBUSMENU_STATUS_NORMAL, + G_PARAM_READWRITE | 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 "'"); + } + } + + /* 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; - dbus_g_object_type_install_info(DBUSMENU_TYPE_SERVER, &dbus_glib__dbusmenu_server_object_info); + 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; } @@ -214,12 +356,21 @@ 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; + + default_text_direction(self); + priv->status = DBUSMENU_STATUS_NORMAL; + priv->icon_dirs = NULL; return; } @@ -231,6 +382,17 @@ dbusmenu_server_dispose (GObject *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) { @@ -238,6 +400,27 @@ dbusmenu_server_dispose (GObject *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; } @@ -245,6 +428,13 @@ dbusmenu_server_dispose (GObject *object) static void dbusmenu_server_finalize (GObject *object) { + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(object); + + if (priv->icon_dirs != NULL) { + g_strfreev(priv->icon_dirs); + priv->icon_dirs = NULL; + } + G_OBJECT_CLASS (dbusmenu_server_parent_class)->finalize (object); return; } @@ -253,27 +443,37 @@ static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) { DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(obj); - GError * error = NULL; switch (id) { case PROP_DBUS_OBJECT: g_return_if_fail(priv->dbusobject == NULL); priv->dbusobject = g_value_dup_string(value); - DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error); - if (connection == NULL || error != NULL) { - g_warning("Unable to get session bus: %s", error == NULL ? "No message" : error->message); - if (error != NULL) { g_error_free(error); } + if (priv->bus == NULL) { + if (priv->bus_lookup == NULL) { + priv->bus_lookup = g_cancellable_new(); + g_return_if_fail(priv->bus_lookup != NULL); + } + + g_object_ref(obj); + g_bus_get(G_BUS_TYPE_SESSION, priv->bus_lookup, bus_got_cb, obj); } else { - dbus_g_connection_register_g_object(connection, - priv->dbusobject, - obj); + 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; } @@ -282,25 +482,80 @@ 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"); } layout_update_signal(DBUSMENU_SERVER(obj)); break; - default: - g_return_if_reached(); + case PROP_TEXT_DIRECTION: { + DbusmenuTextDirection indir = g_value_get_enum(value); + DbusmenuTextDirection olddir = priv->text_direction; + + /* If being set to none we need to go back to default, otherwise + we'll set things the way that we've been told */ + if (indir == DBUSMENU_TEXT_DIRECTION_NONE) { + default_text_direction(DBUSMENU_SERVER(obj)); + } else { + priv->text_direction = indir; + } + + /* If the value has changed we need to signal that on DBus */ + if (priv->text_direction != olddir && priv->bus != NULL && priv->dbusobject != NULL) { + GVariantBuilder params; + g_variant_builder_init(¶ms, G_VARIANT_TYPE_TUPLE); + g_variant_builder_add_value(¶ms, g_variant_new_string(DBUSMENU_INTERFACE)); + GVariant * dict = g_variant_new_dict_entry(g_variant_new_string("TextDirection"), g_variant_new_variant(g_variant_new_string(dbusmenu_text_direction_get_nick(priv->text_direction)))); + g_variant_builder_add_value(¶ms, g_variant_new_array(NULL, &dict, 1)); + g_variant_builder_add_value(¶ms, g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0)); + GVariant * vparams = g_variant_builder_end(¶ms); + + g_dbus_connection_emit_signal(priv->bus, + NULL, + priv->dbusobject, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + vparams, + NULL); + } + break; } + case PROP_STATUS: { + DbusmenuStatus instatus = g_value_get_enum(value); + + /* If the value has changed we need to signal that on DBus */ + if (priv->status != instatus && priv->bus != NULL && priv->dbusobject != NULL) { + GVariantBuilder params; + g_variant_builder_init(¶ms, G_VARIANT_TYPE_TUPLE); + g_variant_builder_add_value(¶ms, g_variant_new_string(DBUSMENU_INTERFACE)); + GVariant * dict = g_variant_new_dict_entry(g_variant_new_string("Status"), g_variant_new_variant(g_variant_new_string(dbusmenu_status_get_nick(instatus)))); + g_variant_builder_add_value(¶ms, g_variant_new_array(NULL, &dict, 1)); + g_variant_builder_add_value(¶ms, g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0)); + GVariant * vparams = g_variant_builder_end(¶ms); + + g_dbus_connection_emit_signal(priv->bus, + NULL, + priv->dbusobject, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + vparams, + NULL); + } - return; -} - -static void -xmlarray_foreach_free (gpointer arrayentry, gpointer userdata) -{ - if (arrayentry != NULL) { - /* g_debug("Freeing pointer: %s", (gchar *)arrayentry); */ - g_free(arrayentry); + priv->status = instatus; + break; + } + default: + g_return_if_reached(); + break; } return; @@ -321,6 +576,12 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) case PROP_VERSION: g_value_set_uint(value, DBUSMENU_VERSION_NUMBER); break; + case PROP_TEXT_DIRECTION: + g_value_set_enum(value, priv->text_direction); + break; + case PROP_STATUS: + g_value_set_enum(value, priv->status); + break; default: g_return_if_reached(); break; @@ -329,6 +590,192 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) return; } +/* Determines the default text direction */ +static void +default_text_direction (DbusmenuServer * server) +{ + DbusmenuTextDirection dir = DBUSMENU_TEXT_DIRECTION_NONE; + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + + const gchar * env = g_getenv("DBUSMENU_TEXT_DIRECTION"); + if (env != NULL) { + if (g_strcmp0(env, "ltr") == 0) { + dir = DBUSMENU_TEXT_DIRECTION_LTR; + } else if (g_strcmp0(env, "rtl") == 0) { + dir = DBUSMENU_TEXT_DIRECTION_RTL; + } else { + g_warning("Value of 'DBUSMENU_TEXT_DIRECTION' is '%s' which is not one of 'rtl' or 'ltr'", env); + } + } + + if (dir == DBUSMENU_TEXT_DIRECTION_NONE) { + /* TRANSLATORS: This is the direction of the text and can + either be the value 'ltr' for left-to-right text (English) + or 'rtl' for right-to-left (Arabic). */ + const gchar * default_dir = C_("default text direction", "ltr"); + + if (g_strcmp0(default_dir, "ltr") == 0) { + dir = DBUSMENU_TEXT_DIRECTION_LTR; + } else if (g_strcmp0(default_dir, "rtl") == 0) { + dir = DBUSMENU_TEXT_DIRECTION_RTL; + } else { + g_warning("Translation has an invalid value '%s' for default text direction. Defaulting to left-to-right.", default_dir); + dir = DBUSMENU_TEXT_DIRECTION_LTR; + } + } + + /* Shouldn't happen, but incase future patches make a mistake + this'll catch them */ + g_return_if_fail(dir != DBUSMENU_TEXT_DIRECTION_NONE); + + priv->text_direction = dir; + + 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); + g_object_unref(G_OBJECT(user_data)); + 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)); + + g_object_unref(G_OBJECT(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); + + if (g_strcmp0(property, "Version") == 0) { + return g_variant_new_uint32(DBUSMENU_VERSION_NUMBER); + } else if (g_strcmp0(property, "TextDirection") == 0) { + return g_variant_new_string(dbusmenu_text_direction_get_nick(priv->text_direction)); + } else if (g_strcmp0(property, "IconThemePath") == 0) { + GVariant * dirs = NULL; + + if (priv->icon_dirs != NULL) { + dirs = g_variant_new_strv((const gchar * const *)priv->icon_dirs, -1); + } else { + dirs = g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0); + } + + return dirs; + } else if (g_strcmp0(property, "Status") == 0) { + return g_variant_new_string(dbusmenu_status_get_nick(priv->status)); + } else { + g_warning("Unknown property '%s'", property); + } + + return NULL; +} + /* Handle actually signalling in the idle loop. This way we collect all the updates. */ static gboolean @@ -338,6 +785,15 @@ layout_update_idle (gpointer 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; @@ -358,10 +814,280 @@ layout_update_signal (DbusmenuServer * server) return; } +typedef struct _prop_idle_item_t prop_idle_item_t; +struct _prop_idle_item_t { + DbusmenuMenuitem * mi; + 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_object_unref(G_OBJECT(iitem->mi)); + 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); + + /* if it's not exposed we're going to block it's properties + from getting into the dbus message */ + if (dbusmenu_menuitem_exposed(iitem->mi) == FALSE) { + continue; + } + + 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(dbusmenu_menuitem_get_id(iitem->mi))); + 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(dbusmenu_menuitem_get_id(iitem->mi))); + 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)); + } + } + + /* these are going to be standard references in all code paths and must be unrefed */ + GVariant * megadata[2]; + gboolean gotsomething = FALSE; + + if (item_init) { + megadata[0] = g_variant_builder_end(&itembuilder); + g_variant_ref_sink(megadata[0]); + gotsomething = TRUE; + } 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); + g_variant_ref_sink(megadata[1]); + gotsomething = TRUE; + } else { + GError * error = NULL; + megadata[1] = g_variant_parse(G_VARIANT_TYPE("a(ias)"), "[ ]", NULL, NULL, &error); + + if (error != NULL) { + g_warning("Unable to parse '[ ]' as a 'a(ias)': %s", error->message); + g_error_free(error); + } + } + + if (gotsomething && priv->dbusobject != NULL && priv->bus != NULL) { + g_dbus_connection_emit_signal(priv->bus, + NULL, + priv->dbusobject, + DBUSMENU_INTERFACE, + "ItemsPropertiesUpdated", + g_variant_new_tuple(megadata, 2), + NULL); + } + + 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->mi == mi) { + item = iitem; + break; + } + } + + GArray * properties = NULL; + /* If not, we'll need to build ourselves one */ + if (item == NULL) { + prop_idle_item_t myitem; + myitem.mi = mi; + g_object_ref(G_OBJECT(mi)); + 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) { + if (prop->variant != 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; } @@ -412,7 +1138,20 @@ menuitem_child_moved (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint static void menuitem_shown (DbusmenuMenuitem * mi, guint timestamp, DbusmenuServer * server) { + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + 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; } @@ -452,215 +1191,321 @@ 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 */ + gint32 parent; + gint32 recurse; + const gchar ** props; - if (parent == 0) { - if (priv->root == NULL) { - /* g_debug("Getting layout without root node!"); */ - g_ptr_array_add(xmlarray, g_strdup("<menu id=\"0\"/>")); - } else { - dbusmenu_menuitem_buildxml(priv->root, xmlarray); + g_variant_get(params, "(ii^a&s)", &parent, &recurse, &props); + + /* Output */ + guint revision = priv->layout_revision; + GVariant * items = NULL; + + if (priv->root != NULL) { + DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, parent); + + if (mi != NULL) { + items = dbusmenu_menuitem_build_variant(mi, props, recurse); } - } 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; + } + g_free(props); + + /* 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 { + /* 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; + } + + gint32 id; + const gchar * property; + + g_variant_get(params, "(i&s)", &id, &property); + 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, gchar ** 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; + } + + gint32 id; + g_variant_get(params, "(i)", &id); + 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); + + g_dbus_method_invocation_return_value(invocation, g_variant_new("(a{sv})", dict)); - return TRUE; + return; } /* Handles getting a bunch of properties from a variety of menu items to make one mega dbus message */ -static gboolean -_dbusmenu_server_get_group_properties (DbusmenuServer * server, GArray * ids, gchar ** properties, GPtrArray ** values, GError ** error) +static void +bus_get_group_properties (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation) { - /* Build an initial pointer array */ - *values = g_ptr_array_new(); + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); - /* Go through each ID to get that ID's properties */ - int idcnt; - for (idcnt = 0; idcnt < ids->len; idcnt++) { - GHashTable * idprops = NULL; - GError * error = NULL; - gint id = g_array_index(ids, int, idcnt); + if (priv->root == NULL) { + /* Allow a request for just id 0 when root is null. Return no properties. + So that a request always returns a valid structure no matter the + state of the structure in the server. + */ + GVariant * idlist = g_variant_get_child_value(params, 0); + if (g_variant_n_children(idlist) == 1) { - /* Get the properties for this ID the old fashioned way. */ - if (!_dbusmenu_server_get_properties(server, id, properties, &idprops, &error)) { - g_warning("Error getting the properties from ID %d: %s", id, error->message); - g_error_free(error); - error = NULL; - continue; - } + GVariant *id_v = g_variant_get_child_value(idlist, 0); + gint32 id = g_variant_get_int32(id_v); + g_variant_unref(id_v); - GValueArray * valarray = g_value_array_new(2); + if (id == 0) { - _gvalue_array_append_int(valarray, id); - _gvalue_array_append_hashtable(valarray, idprops); + GVariant * final = g_variant_parse(G_VARIANT_TYPE("(a(ia{sv}))"), "([(0, {})],)", NULL, NULL, NULL); + g_dbus_method_invocation_return_value(invocation, final); + g_variant_unref(final); + } + } else { - g_ptr_array_add(*values, valarray); + g_dbus_method_invocation_return_error(invocation, + error_quark(), + NO_VALID_LAYOUT, + "There currently isn't a layout in this server"); + } + g_variant_unref(idlist); + return; } - return TRUE; -} + GVariantIter *ids; + g_variant_get(params, "(aias)", &ids, NULL); + /* TODO: implementation ignores propertyNames declared in XML */ -/* Allocate a value on the stack for the int and append - it to the array. */ -static void -_gvalue_array_append_int(GValueArray *array, gint i) -{ - GValue value = {0}; + GVariantBuilder builder; + gboolean builder_init = FALSE; - g_value_init(&value, G_TYPE_INT); - g_value_set_int(&value, i); - g_value_array_append(array, &value); - g_value_unset(&value); -} + gint32 id; + while (g_variant_iter_loop(ids, "i", &id)) { + DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); + if (mi == NULL) continue; -/* Allocate a value on the stack for the hashtable and append - it to the array. */ -static void -_gvalue_array_append_hashtable(GValueArray *array, GHashTable * dict) -{ - GValue value = {0}; + 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("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); + } + g_variant_iter_free(ids); + + /* a standard reference that must be unrefed */ + GVariant * ret = NULL; + + if (builder_init) { + ret = g_variant_builder_end(&builder); + g_variant_ref_sink(ret); + } else { + GError * error = NULL; + ret = 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); + } + } + + GVariant * final = NULL; + if (ret != NULL) { + g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE); + g_variant_builder_add_value(&builder, ret); + g_variant_unref(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); - - GValueArray * item = g_value_array_new(2); - _gvalue_array_append_int(item, id); - _gvalue_array_append_hashtable(item, dict); + g_variant_builder_add_value(&tuple, g_variant_new_int32(id)); - g_ptr_array_add(output, item); + GVariant * props = dbusmenu_menuitem_properties_variant(mi, NULL); + g_variant_builder_add_value(&tuple, props); - g_hash_table_unref(dict); + g_variant_builder_add_value(builder, g_variant_builder_end(&tuple)); 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); + gint32 id; + g_variant_get(params, "(i)", &id); + + 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); + + 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("(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; + } + } - return TRUE; + g_dbus_method_invocation_return_value(invocation, ret); + return; } /* Structure for holding the event data for the idle function @@ -669,7 +1514,7 @@ typedef struct _idle_event_t idle_event_t; struct _idle_event_t { DbusmenuMenuitem * mi; gchar * eventid; - GValue data; + GVariant * variant; guint timestamp; }; @@ -680,66 +1525,96 @@ event_local_handler (gpointer user_data) { idle_event_t * data = (idle_event_t *)user_data; - dbusmenu_menuitem_handle_event(data->mi, data->eventid, &data->data, data->timestamp); + dbusmenu_menuitem_handle_event(data->mi, data->eventid, data->variant, data->timestamp); g_object_unref(data->mi); g_free(data->eventid); - g_value_unset(&data->data); + g_variant_unref(data->variant); g_free(data); return FALSE; } -/* Handles the even coming off of DBus */ -static gboolean -_dbusmenu_server_event (DbusmenuServer * server, gint id, gchar * eventid, GValue * data, guint timestamp, GError ** error) +/* Handles the events coming off of DBus */ +static void +bus_event (DbusmenuServer * server, GVariant * params, GDBusMethodInvocation * invocation) { DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); - DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); - if (mi == NULL) { - if (error != NULL) { - g_set_error(error, + if (priv->root == NULL) { + 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; + NO_VALID_LAYOUT, + "There currently isn't a layout in this server"); + 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(eventid); - event_data->timestamp = timestamp; - g_value_init(&(event_data->data), G_VALUE_TYPE(data)); - g_value_copy(data, &(event_data->data)); + gint32 id; + gchar *etype; + GVariant *data; + guint32 ts; - g_timeout_add(0, event_local_handler, event_data); - return TRUE; + g_variant_get(params, "(isvu)", &id, &etype, &data, &ts); + + DbusmenuMenuitem * mi = dbusmenu_menuitem_find_id(priv->root, id); + + if (mi == NULL) { + + 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); + g_free(etype); + g_variant_unref(data); + + } else { + + idle_event_t * event_data = g_new0(idle_event_t, 1); + event_data->mi = g_object_ref(mi); + event_data->eventid = etype; + event_data->timestamp = ts; + event_data->variant = data; /* give away our reference */ + + 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; + } + + gint32 id; + g_variant_get(params, "(i)", &id); 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 */ @@ -750,7 +1625,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 */ @@ -758,7 +1633,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, @@ -792,5 +1667,164 @@ dbusmenu_server_set_root (DbusmenuServer * self, DbusmenuMenuitem * root) return; } +/** + dbusmenu_server_get_text_direction: + @server: The #DbusmenuServer object to get the text direction from + + Returns the value of the text direction that is being exported + over DBus for this server. It should relate to the direction + of the labels and other text fields that are being exported by + this server. + + Return value: Text direction exported for this server. +*/ +DbusmenuTextDirection +dbusmenu_server_get_text_direction (DbusmenuServer * server) +{ + g_return_val_if_fail(DBUSMENU_IS_SERVER(server), DBUSMENU_TEXT_DIRECTION_NONE); + + GValue val = {0}; + g_value_init(&val, DBUSMENU_TYPE_TEXT_DIRECTION); + g_object_get_property(G_OBJECT(server), DBUSMENU_SERVER_PROP_TEXT_DIRECTION, &val); + + DbusmenuTextDirection retval = g_value_get_enum(&val); + g_value_unset(&val); + + return retval; +} + +/** + dbusmenu_server_set_text_direction: + @server: The #DbusmenuServer object to set the text direction on + @dir: Direction of the text + + Sets the text direction that should be exported over DBus for + this server. If the value is set to #DBUSMENU_TEXT_DIRECTION_NONE + the default detection will be used for setting the value and + exported over DBus. +*/ +void +dbusmenu_server_set_text_direction (DbusmenuServer * server, DbusmenuTextDirection dir) +{ + g_return_if_fail(DBUSMENU_IS_SERVER(server)); + g_return_if_fail(dir == DBUSMENU_TEXT_DIRECTION_NONE || dir == DBUSMENU_TEXT_DIRECTION_LTR || dir == DBUSMENU_TEXT_DIRECTION_RTL); + + GValue newval = {0}; + g_value_init(&newval, DBUSMENU_TYPE_TEXT_DIRECTION); + g_value_set_enum(&newval, dir); + g_object_set_property(G_OBJECT(server), DBUSMENU_SERVER_PROP_TEXT_DIRECTION, &newval); + g_value_unset(&newval); + return; +} + +/** + dbusmenu_server_get_status: + @server: The #DbusmenuServer to get the status from + + Gets the current statust hat the server is sending out over + DBus. + + Return value: The current status the server is sending +*/ +DbusmenuStatus +dbusmenu_server_get_status (DbusmenuServer * server) +{ + g_return_val_if_fail(DBUSMENU_IS_SERVER(server), DBUSMENU_STATUS_NORMAL); + + GValue val = {0}; + g_value_init(&val, DBUSMENU_TYPE_STATUS); + g_object_get_property(G_OBJECT(server), DBUSMENU_SERVER_PROP_STATUS, &val); + + DbusmenuStatus retval = g_value_get_enum(&val); + g_value_unset(&val); + + return retval; +} + +/** + dbusmenu_server_set_status: + @server: The #DbusmenuServer to set the status on + @status: Status value to set on the server + + Changes the status of the server. +*/ +void +dbusmenu_server_set_status (DbusmenuServer * server, DbusmenuStatus status) +{ + g_return_if_fail(DBUSMENU_IS_SERVER(server)); + + GValue val = {0}; + g_value_init(&val, DBUSMENU_TYPE_STATUS); + g_value_set_enum(&val, status); + g_object_set_property(G_OBJECT(server), DBUSMENU_SERVER_PROP_STATUS, &val); + g_value_unset(&val); + + return; +} + +/** + * dbusmenu_server_get_icon_paths: + * @server: The #DbusmenuServer to get the icon paths from + * + * Gets the stored and exported icon paths from the server. + * + * Return value: (transfer none): A NULL-terminated list of icon paths with + * memory managed by the server. Duplicate if you want + * to keep them. + */ +const GStrv +dbusmenu_server_get_icon_paths (DbusmenuServer * server) +{ + g_return_val_if_fail(DBUSMENU_IS_SERVER(server), NULL); + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + return priv->icon_dirs; +} + +/** + dbusmenu_server_set_icon_paths: + @server: The #DbusmenuServer to set the icon paths on + + Sets the icon paths for the server. This will replace previously + set icon theme paths. +*/ +void +dbusmenu_server_set_icon_paths (DbusmenuServer * server, GStrv icon_paths) +{ + g_return_if_fail(DBUSMENU_IS_SERVER(server)); + DbusmenuServerPrivate * priv = DBUSMENU_SERVER_GET_PRIVATE(server); + + if (priv->icon_dirs != NULL) { + g_strfreev(priv->icon_dirs); + priv->icon_dirs = NULL; + } + + if (icon_paths != NULL) { + priv->icon_dirs = g_strdupv(icon_paths); + } + if (priv->bus != NULL && priv->dbusobject != NULL) { + GVariantBuilder params; + g_variant_builder_init(¶ms, G_VARIANT_TYPE_TUPLE); + g_variant_builder_add_value(¶ms, g_variant_new_string(DBUSMENU_INTERFACE)); + GVariant * items = NULL; + if (priv->icon_dirs != NULL) { + GVariant * dict = g_variant_new_dict_entry(g_variant_new_string("IconThemePath"), g_variant_new_variant(g_variant_new_strv((const gchar * const *)priv->icon_dirs, -1))); + items = g_variant_new_array(NULL, &dict, 1); + } else { + items = g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0); + } + g_variant_builder_add_value(¶ms, items); + g_variant_builder_add_value(¶ms, g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0)); + GVariant * vparams = g_variant_builder_end(¶ms); + + g_dbus_connection_emit_signal(priv->bus, + NULL, + priv->dbusobject, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + vparams, + NULL); + } + return; +} diff --git a/libdbusmenu-glib/server.h b/libdbusmenu-glib/server.h index a9bf213..5feb09b 100644 --- a/libdbusmenu-glib/server.h +++ b/libdbusmenu-glib/server.h @@ -33,6 +33,7 @@ License version 3 and version 2.1 along with this program. If not, see #include <glib-object.h> #include "menuitem.h" +#include "types.h" G_BEGIN_DECLS @@ -43,15 +44,69 @@ G_BEGIN_DECLS #define DBUSMENU_IS_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_SERVER)) #define DBUSMENU_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_SERVER, DbusmenuServerClass)) +/** + * DBUSMENU_SERVER_SIGNAL_ID_PROP_UPDATE: + * + * String to attach to signal #DbusmenuServer::item-property-updated + */ #define DBUSMENU_SERVER_SIGNAL_ID_PROP_UPDATE "item-property-updated" +/** + * DBUSMENU_SERVER_SIGNAL_ID_UPDATE: + * + * String to attach to signal #DbusmenuServer::item-updated + */ #define DBUSMENU_SERVER_SIGNAL_ID_UPDATE "item-updated" +/** + * DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATED: + * + * String to attach to signal #DbusmenuServer::layout-updated + */ #define DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATED "layout-updated" +/** + * DBUSMENU_SERVER_SIGNAL_ITEM_ACTIVATION: + * + * String to attach to signal #DbusmenuServer::item-activation-requested + */ #define DBUSMENU_SERVER_SIGNAL_ITEM_ACTIVATION "item-activation-requested" +/** + * DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATE: + * + * String to attach to signal #DbusmenuServer::layout-updated + */ #define DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATE DBUSMENU_SERVER_SIGNAL_LAYOUT_UPDATED +/** + * DBUSMENU_SERVER_PROP_DBUS_OBJECT: + * + * String to access property #DbusmenuServer:dbus-object + */ #define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object" +/** + * DBUSMENU_SERVER_PROP_ROOT_NODE: + * + * String to access property #DbusmenuServer:root-node + */ #define DBUSMENU_SERVER_PROP_ROOT_NODE "root-node" +/** + * DBUSMENU_SERVER_PROP_VERSION: + * + * String to access property #DbusmenuServer:version + */ #define DBUSMENU_SERVER_PROP_VERSION "version" +/** + * DBUSMENU_SERVER_PROP_TEXT_DIRECTION: + * + * String to access property #DbusmenuServer:text-direction + */ +#define DBUSMENU_SERVER_PROP_TEXT_DIRECTION "text-direction" +/** + * DBUSMENU_SERVER_PROP_STATUS: + * + * String to access property #DbusmenuServer:status + */ +#define DBUSMENU_SERVER_PROP_STATUS "status" + +typedef struct _DbusmenuServerPrivate DbusmenuServerPrivate; /** DbusmenuServerClass: @@ -59,10 +114,13 @@ G_BEGIN_DECLS @id_prop_update: Slot for #DbusmenuServer::id-prop-update. @id_update: Slot for #DbusmenuServer::id-update. @layout_updated: Slot for #DbusmenuServer::layout-update. - @item_activation_requested: Slot for #DbusmenuServer::item-activation-requested. - @dbusmenu_server_reserved1: Reserved for future use. - @dbusmenu_server_reserved2: Reserved for future use. - @dbusmenu_server_reserved3: Reserved for future use. + @item_activation: 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. */ @@ -76,15 +134,17 @@ struct _DbusmenuServerClass { void (*layout_updated)(gint revision); void (*item_activation)(gint id, guint timestamp); - /* Reserved */ - void (*dbusmenu_server_reserved1)(void); - void (*dbusmenu_server_reserved2)(void); - void (*dbusmenu_server_reserved3)(void); + /*< Private >*/ + void (*reserved1) (void); + void (*reserved2) (void); + void (*reserved3) (void); + void (*reserved4) (void); + void (*reserved5) (void); + void (*reserved6) (void); }; /** DbusmenuServer: - @parent: #GObject A server which represents a sharing of a set of #DbusmenuMenuitems across DBus to a #DbusmenuClient. @@ -92,14 +152,27 @@ struct _DbusmenuServerClass { typedef struct _DbusmenuServer DbusmenuServer; struct _DbusmenuServer { GObject parent; + + /*< Private >*/ + DbusmenuServerPrivate * priv; }; -GType dbusmenu_server_get_type (void); -DbusmenuServer * dbusmenu_server_new (const gchar * object); -void dbusmenu_server_set_root (DbusmenuServer * server, DbusmenuMenuitem * root); +GType dbusmenu_server_get_type (void); +DbusmenuServer * dbusmenu_server_new (const gchar * object); +void dbusmenu_server_set_root (DbusmenuServer * self, + DbusmenuMenuitem * root); +DbusmenuTextDirection dbusmenu_server_get_text_direction (DbusmenuServer * server); +void dbusmenu_server_set_text_direction (DbusmenuServer * server, + DbusmenuTextDirection dir); +DbusmenuStatus dbusmenu_server_get_status (DbusmenuServer * server); +void dbusmenu_server_set_status (DbusmenuServer * server, + DbusmenuStatus status); +const GStrv dbusmenu_server_get_icon_paths (DbusmenuServer * server); +void dbusmenu_server_set_icon_paths (DbusmenuServer * server, + GStrv icon_paths); /** - SECIONT:server + SECTION:server @short_description: The server signals changed and updates on a tree of #DbusmenuMenuitem objecs. @stability: Unstable diff --git a/libdbusmenu-glib/types.h b/libdbusmenu-glib/types.h new file mode 100644 index 0000000..03ae801 --- /dev/null +++ b/libdbusmenu-glib/types.h @@ -0,0 +1,76 @@ +/* +Types that are used in several objects. + +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_TYPES_H__ +#define __DBUSMENU_TYPES_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +/** + DbusmenuTextDirection: + @DBUSMENU_TEXT_DIRECTION_NONE: Unspecified text direction + @DBUSMENU_TEXT_DIRECTION_LTR: Left-to-right text direction + @DBUSMENU_TEXT_DIRECTION_RTL: Right-to-left text direction + + The direction of text that the strings that this server + will be sending strings as. +*/ +typedef enum { /*< prefix=DBUSMENU_TEXT_DIRECTION >*/ + DBUSMENU_TEXT_DIRECTION_NONE, /*< nick=none >*/ + DBUSMENU_TEXT_DIRECTION_LTR, /*< nick=ltr >*/ + DBUSMENU_TEXT_DIRECTION_RTL /*< nick=rtl >*/ +} DbusmenuTextDirection; + +/** + DbusmenuStatus: + @DBUSMENU_STATUS_NORMAL: Everything is normal + @DBUSMENU_STATUS_NOTICE: The menus should be shown at a higher priority + + Tracks how the menus should be presented to the user. +*/ +typedef enum { /*< prefix=DBUSMENU_STATUS >*/ + DBUSMENU_STATUS_NORMAL, /*< nick=normal >*/ + DBUSMENU_STATUS_NOTICE /*< nick=notice >*/ +} DbusmenuStatus; + +/** + SECTION:types + @short_description: Types that are used by both client and + server. + @stability: Unstable + @include: libdbusmenu-glib/types.h + + Enums that are used to describe states of the server across the + bus. They are sent over dbus using their nicks but then turned + back into enums by the client. +*/ +G_END_DECLS + +#endif /* __DBUSMENU_TYPES_H__ */ + |