aboutsummaryrefslogtreecommitdiff
path: root/libdbusmenu-glib/client.c
diff options
context:
space:
mode:
authorTed Gould <ted@canonical.com>2009-05-12 11:23:10 -0500
committerTed Gould <ted@canonical.com>2009-05-12 11:23:10 -0500
commit3d0e0276fd7856831dcc845a24a252ad304b3bad (patch)
tree5ec06325d34b0fdab498045aa911fb534eb2ca48 /libdbusmenu-glib/client.c
parentc5024e812fb046f6bccceafaf46ebbd966f465cf (diff)
parent53b4a13ca4541bcdf85ca3ee28cb6ffc81e6f5b0 (diff)
downloadlibdbusmenu-3d0e0276fd7856831dcc845a24a252ad304b3bad.tar.gz
libdbusmenu-3d0e0276fd7856831dcc845a24a252ad304b3bad.tar.bz2
libdbusmenu-3d0e0276fd7856831dcc845a24a252ad304b3bad.zip
Merging in my working branch that brings in basic object functionality passing across DBus.
Diffstat (limited to 'libdbusmenu-glib/client.c')
-rw-r--r--libdbusmenu-glib/client.c499
1 files changed, 499 insertions, 0 deletions
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
new file mode 100644
index 0000000..b0b1157
--- /dev/null
+++ b/libdbusmenu-glib/client.c
@@ -0,0 +1,499 @@
+/*
+A library to communicate a menu object set accross DBus and
+track updates and maintain consistency.
+
+Copyright 2009 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of 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 <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include "client.h"
+#include "dbusmenu-client.h"
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_DBUSOBJECT,
+ PROP_DBUSNAME
+};
+
+/* Signals */
+enum {
+ LAYOUT_UPDATED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct _DbusmenuClientPrivate DbusmenuClientPrivate;
+struct _DbusmenuClientPrivate
+{
+ DbusmenuMenuitem * root;
+
+ gchar * dbus_object;
+ gchar * dbus_name;
+
+ DBusGConnection * session_bus;
+ DBusGProxy * menuproxy;
+ DBusGProxy * propproxy;
+ DBusGProxyCall * layoutcall;
+};
+
+#define DBUSMENU_CLIENT_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_CLIENT, DbusmenuClientPrivate))
+
+/* GObject Stuff */
+static void dbusmenu_client_class_init (DbusmenuClientClass *klass);
+static void dbusmenu_client_init (DbusmenuClient *self);
+static void dbusmenu_client_dispose (GObject *object);
+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, DbusmenuClient * client);
+static void build_proxies (DbusmenuClient * client);
+static guint parse_node_get_id (xmlNodePtr node);
+static DbusmenuMenuitem * parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent);
+static void parse_layout (DbusmenuClient * client, const gchar * layout);
+static void update_layout_cb (DBusGProxy * proxy, DBusGProxyCall * call, void * data);
+static void update_layout (DbusmenuClient * client);
+
+/* Build a type */
+G_DEFINE_TYPE (DbusmenuClient, dbusmenu_client, G_TYPE_OBJECT);
+
+static void
+dbusmenu_client_class_init (DbusmenuClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (DbusmenuClientPrivate));
+
+ object_class->dispose = dbusmenu_client_dispose;
+ object_class->finalize = dbusmenu_client_finalize;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ /**
+ DbusmenuClient::layout-update:
+ @arg0: The #DbusmenuClient object
+
+ Tells that the layout has been updated and parsed by
+ this object and is ready for grabbing by the calling
+ application.
+ */
+ signals[LAYOUT_UPDATED] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED,
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (DbusmenuClientClass, layout_updated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ g_object_class_install_property (object_class, PROP_DBUSOBJECT,
+ g_param_spec_string(DBUSMENU_CLIENT_PROP_DBUS_OBJECT, "DBus Object we represent",
+ "The Object on the client that we're getting our data from.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_DBUSNAME,
+ g_param_spec_string(DBUSMENU_CLIENT_PROP_DBUS_NAME, "DBus Client we connect to",
+ "Name of the DBus client we're connecting to.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ return;
+}
+
+static void
+dbusmenu_client_init (DbusmenuClient *self)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(self);
+
+ priv->root = NULL;
+
+ priv->dbus_object = NULL;
+ priv->dbus_name = NULL;
+
+ priv->session_bus = NULL;
+ priv->menuproxy = NULL;
+ priv->propproxy = NULL;
+ priv->layoutcall = NULL;
+
+ return;
+}
+
+static void
+dbusmenu_client_dispose (GObject *object)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(object);
+
+ if (priv->layoutcall != NULL) {
+ dbus_g_proxy_cancel_call(priv->propproxy, priv->layoutcall);
+ priv->layoutcall = NULL;
+ }
+ if (priv->menuproxy != NULL) {
+ g_object_unref(G_OBJECT(priv->menuproxy));
+ priv->menuproxy = NULL;
+ }
+ if (priv->propproxy != NULL) {
+ g_object_unref(G_OBJECT(priv->propproxy));
+ priv->propproxy = NULL;
+ }
+ priv->session_bus = NULL;
+
+ G_OBJECT_CLASS (dbusmenu_client_parent_class)->dispose (object);
+ return;
+}
+
+static void
+dbusmenu_client_finalize (GObject *object)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(object);
+
+ g_free(priv->dbus_name);
+ g_free(priv->dbus_object);
+
+ G_OBJECT_CLASS (dbusmenu_client_parent_class)->finalize (object);
+ return;
+}
+
+static void
+set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(obj);
+
+ switch (id) {
+ case PROP_DBUSNAME:
+ priv->dbus_name = g_value_dup_string(value);
+ if (priv->dbus_name != NULL && priv->dbus_object != NULL) {
+ build_proxies(DBUSMENU_CLIENT(obj));
+ }
+ break;
+ case PROP_DBUSOBJECT:
+ priv->dbus_object = g_value_dup_string(value);
+ if (priv->dbus_name != NULL && priv->dbus_object != NULL) {
+ build_proxies(DBUSMENU_CLIENT(obj));
+ }
+ break;
+ default:
+ g_warning("Unknown property %d.", id);
+ return;
+ }
+
+ return;
+}
+
+static void
+get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(obj);
+
+ switch (id) {
+ case PROP_DBUSNAME:
+ g_value_set_string(value, priv->dbus_name);
+ break;
+ case PROP_DBUSOBJECT:
+ g_value_set_string(value, priv->dbus_object);
+ break;
+ default:
+ g_warning("Unknown property %d.", id);
+ return;
+ }
+
+ return;
+}
+
+/* Internal funcs */
+
+/* Annoying little wrapper to make the right function update */
+static void
+layout_update (DBusGProxy * proxy, DbusmenuClient * client)
+{
+ update_layout(client);
+ 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);
+ 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_error("Unable to get property proxy for %s on %s: %s", priv->dbus_name, priv->dbus_object, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ priv->menuproxy = dbus_g_proxy_new_for_name_owner(priv->session_bus,
+ priv->dbus_name,
+ priv->dbus_object,
+ "org.freedesktop.dbusmenu",
+ &error);
+ if (error != NULL) {
+ g_error("Unable to get dbusmenu proxy for %s on %s: %s", priv->dbus_name, priv->dbus_object, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ dbus_g_proxy_add_signal(priv->menuproxy, "LayoutUpdate", G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal(priv->menuproxy, "LayoutUpdate", G_CALLBACK(layout_update), client, NULL);
+
+ 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 guint
+parse_node_get_id (xmlNodePtr node)
+{
+ 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 0;
+ }
+
+ xmlAttrPtr attrib;
+ for (attrib = node->properties; attrib != NULL; attrib = attrib->next) {
+ if (g_strcmp0((gchar *)attrib->name, "id") == 0) {
+ if (attrib->children != NULL) {
+ guint id = (guint)g_ascii_strtoull((gchar *)attrib->children->content, NULL, 10);
+ g_debug ("Found ID: %d", id);
+ return id;
+ }
+ break;
+ }
+ }
+
+ g_warning("Unable to find an ID on the node");
+ return 0;
+}
+
+/* Parse recursively through the XML and make it into
+ objects as need be */
+static DbusmenuMenuitem *
+parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent)
+{
+ guint id = parse_node_get_id(node);
+ g_debug("Looking at node with id: %d", id);
+ if (item == NULL || dbusmenu_menuitem_get_id(item) != id || id == 0) {
+ if (item != NULL) {
+ if (parent != NULL) {
+ dbusmenu_menuitem_child_delete(parent, item);
+ }
+ g_object_unref(G_OBJECT(item));
+ }
+
+ if (id == 0) {
+ g_warning("ID from XML file is zero");
+ return NULL;
+ }
+
+ /* Build a new item */
+ item = dbusmenu_menuitem_new_with_id(id);
+ }
+
+ xmlNodePtr children;
+ guint position;
+ GList * oldchildren = dbusmenu_menuitem_take_children(item);
+
+ for (children = node->children, position = 0; children != NULL; children = children->next, position++) {
+ g_debug("Looking at child: %d", position);
+ guint childid = parse_node_get_id(children);
+ DbusmenuMenuitem * childmi = NULL;
+
+ GList * childsearch = NULL;
+ for (childsearch = oldchildren; childsearch != NULL; childsearch = g_list_next(childsearch)) {
+ DbusmenuMenuitem * cs_mi = DBUSMENU_MENUITEM(childsearch->data);
+ if (childid == dbusmenu_menuitem_get_id(cs_mi)) {
+ oldchildren = g_list_remove(oldchildren, cs_mi);
+ childmi = cs_mi;
+ break;
+ }
+ }
+
+ childmi = parse_layout_xml(children, childmi, item);
+ dbusmenu_menuitem_child_add_position(item, childmi, position);
+ }
+
+ GList * oldchildleft = NULL;
+ for (oldchildleft = oldchildren; oldchildleft != NULL; oldchildleft = g_list_next(oldchildleft)) {
+ DbusmenuMenuitem * oldmi = DBUSMENU_MENUITEM(oldchildleft->data);
+ g_object_unref(G_OBJECT(oldmi));
+ }
+ g_list_free(oldchildren);
+
+ return item;
+}
+
+/* Take the layout passed to us over DBus and turn it into
+ a set of beautiful objects */
+static void
+parse_layout (DbusmenuClient * client, const gchar * layout)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ xmlDocPtr xmldoc;
+
+ xmldoc = xmlReadMemory(layout, g_utf8_strlen(layout, 16*1024), "dbusmenu.xml", NULL, 0);
+
+ xmlNodePtr root = xmlDocGetRootElement(xmldoc);
+
+ priv->root = parse_layout_xml(root, priv->root, NULL);
+ if (priv->root == NULL) {
+ g_warning("Unable to parse layout on client %s object %s: %s", priv->dbus_name, priv->dbus_object, layout);
+ }
+
+ xmlFreeDoc(xmldoc);
+ return;
+}
+
+/* When the layout property returns, here's where we take care of that. */
+static void
+update_layout_cb (DBusGProxy * proxy, DBusGProxyCall * call, void * data)
+{
+ DbusmenuClient * client = DBUSMENU_CLIENT(data);
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ GError * error = NULL;
+ GValue value = {0};
+
+ if (!dbus_g_proxy_end_call(proxy, call, &error, G_TYPE_VALUE, &value, G_TYPE_INVALID)) {
+ g_warning("Getting layout failed on client %s object %s: %s", priv->dbus_name, priv->dbus_object, error->message);
+ priv->layoutcall = NULL;
+ g_error_free(error);
+ return;
+ }
+
+ const gchar * xml = g_value_get_string(&value);
+ g_debug("Got layout string: %s", xml);
+ parse_layout(client, xml);
+
+ priv->layoutcall = NULL;
+ g_debug("Root is now: 0x%X", (unsigned int)priv->root);
+ g_signal_emit(G_OBJECT(client), signals[LAYOUT_UPDATED], 0, TRUE);
+
+ return;
+}
+
+/* Call the property on the server we're connected to and set it up to
+ be async back to _update_layout_cb */
+static void
+update_layout (DbusmenuClient * client)
+{
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (priv->layoutcall != NULL) {
+ return;
+ }
+
+ priv->layoutcall = dbus_g_proxy_begin_call (priv->propproxy,
+ "Get",
+ update_layout_cb,
+ client,
+ NULL,
+ G_TYPE_STRING, "org.freedesktop.dbusmenu",
+ G_TYPE_STRING, "layout",
+ G_TYPE_INVALID, G_TYPE_VALUE, G_TYPE_INVALID);
+
+ 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
+*/
+DbusmenuClient *
+dbusmenu_client_new (const gchar * name, const gchar * object)
+{
+ DbusmenuClient * self = g_object_new(DBUSMENU_TYPE_CLIENT,
+ DBUSMENU_CLIENT_PROP_DBUS_NAME, name,
+ DBUSMENU_CLIENT_PROP_DBUS_OBJECT, object,
+ NULL);
+ update_layout(self);
+
+ return self;
+}
+
+/**
+ 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.
+*/
+DbusmenuMenuitem *
+dbusmenu_client_get_root (DbusmenuClient * client)
+{
+ g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), NULL);
+
+ DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+ if (priv->layoutcall != NULL) {
+ /* Will end the current call and block on it's completion */
+ update_layout_cb(priv->propproxy, priv->layoutcall, client);
+ }
+
+ return priv->root;
+}