summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/Makefile.am11
-rw-r--r--plugins/console.c668
-rw-r--r--plugins/console.h88
-rwxr-xr-xplugins/telepathy-gabble-xmpp-console423
-rw-r--r--plugins/test.c2
5 files changed, 1192 insertions, 0 deletions
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 97f87a4c5..0330cec53 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -1,6 +1,7 @@
plugindir = $(libdir)/telepathy/gabble-0
installable_plugins = \
+ console.la \
gateways.la
test_only_plugins = \
@@ -29,10 +30,16 @@ endif
if ENABLE_PLUGINS
plugin_LTLIBRARIES = $(installable_plugins)
+
+dist_bin_SCRIPTS = \
+ telepathy-gabble-xmpp-console
else
# we still compile the plugin (just to make sure it compiles!) but we don't
# install it
noinst_LTLIBRARIES += $(installable_plugins)
+
+EXTRA_DIST = \
+ telepathy-gabble-xmpp-console
endif
AM_LDFLAGS = -module -avoid-version -shared
@@ -45,6 +52,10 @@ gateways_la_SOURCES = \
gateways.c \
gateways.h
+console_la_SOURCES = \
+ console.c \
+ console.h
+
AM_CFLAGS = $(ERROR_CFLAGS) \
-I $(top_srcdir) -I $(top_builddir) \
@DBUS_CFLAGS@ @GLIB_CFLAGS@ @WOCKY_CFLAGS@ @TP_GLIB_CFLAGS@ \
diff --git a/plugins/console.c b/plugins/console.c
new file mode 100644
index 000000000..13ca0eccf
--- /dev/null
+++ b/plugins/console.c
@@ -0,0 +1,668 @@
+/* XML console plugin
+ *
+ * Copyright © 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "console.h"
+
+#include "config.h"
+
+#include <string.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <wocky/wocky-xmpp-error.h>
+#include <wocky/wocky-utils.h>
+#include <wocky/wocky-xmpp-reader.h>
+#include <wocky/wocky-xmpp-writer.h>
+#include <wocky/wocky-namespaces.h>
+
+#include "extensions/extensions.h"
+
+#include <gabble/gabble.h>
+
+/*************************
+ * Plugin implementation *
+ *************************/
+
+static guint debug = 0;
+
+#define DEBUG(format, ...) \
+G_STMT_START { \
+ if (debug != 0) \
+ g_debug ("%s: " format, G_STRFUNC, ## __VA_ARGS__); \
+} G_STMT_END
+
+static const GDebugKey debug_keys[] = {
+ { "console", 1 },
+ { NULL, 0 }
+};
+
+static void plugin_iface_init (
+ gpointer g_iface,
+ gpointer data);
+
+static const gchar * const sidecar_interfaces[] = {
+ GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE,
+ NULL
+};
+
+G_DEFINE_TYPE_WITH_CODE (GabbleConsolePlugin, gabble_console_plugin,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_PLUGIN, plugin_iface_init);
+ )
+
+static void
+gabble_console_plugin_init (GabbleConsolePlugin *self)
+{
+}
+
+static void
+gabble_console_plugin_class_init (GabbleConsolePluginClass *klass)
+{
+}
+
+static void
+gabble_console_plugin_create_sidecar (
+ GabblePlugin *plugin,
+ const gchar *sidecar_interface,
+ GabbleConnection *connection,
+ WockySession *session,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (plugin),
+ callback, user_data,
+ /* sic: all plugins share gabble_plugin_create_sidecar_finish() so we
+ * need to use the same source tag.
+ */
+ gabble_plugin_create_sidecar);
+ GabbleSidecar *sidecar = NULL;
+
+ if (!tp_strdiff (sidecar_interface, GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE))
+ {
+ sidecar = g_object_new (GABBLE_TYPE_CONSOLE_SIDECAR,
+ "connection", connection,
+ "session", session,
+ NULL);
+ }
+ else
+ {
+ g_simple_async_result_set_error (result, TP_ERRORS,
+ TP_ERROR_NOT_IMPLEMENTED, "'%s' not implemented", sidecar_interface);
+ }
+
+ if (sidecar != NULL)
+ g_simple_async_result_set_op_res_gpointer (result, sidecar,
+ g_object_unref);
+
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+static void
+plugin_iface_init (
+ gpointer g_iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ GabblePluginInterface *iface = g_iface;
+
+ iface->name = "XMPP console";
+ iface->version = PACKAGE_VERSION;
+ iface->sidecar_interfaces = sidecar_interfaces;
+ iface->create_sidecar = gabble_console_plugin_create_sidecar;
+}
+
+GabblePlugin *
+gabble_plugin_create (void)
+{
+ debug = g_parse_debug_string (g_getenv ("GABBLE_DEBUG"), debug_keys,
+ G_N_ELEMENTS (debug_keys) - 1);
+ DEBUG ("loaded");
+
+ return g_object_new (GABBLE_TYPE_CONSOLE_PLUGIN,
+ NULL);
+}
+
+/**************************
+ * Sidecar implementation *
+ **************************/
+
+enum {
+ PROP_0,
+ PROP_CONNECTION,
+ PROP_SESSION,
+ PROP_SPEW
+};
+
+struct _GabbleConsoleSidecarPrivate
+{
+ WockySession *session;
+ TpBaseConnection *connection;
+ WockyXmppReader *reader;
+ WockyXmppWriter *writer;
+
+ /* %TRUE if we should emit signals when sending or receiving stanzas */
+ gboolean spew;
+ /* 0 if spew is FALSE; or a WockyPorter handler id for all incoming stanzas
+ * if spew is TRUE. */
+ guint incoming_handler;
+ /* 0 if spew is FALSE; a GLib signal handler id for WockyPorter::sending if
+ * spew is TRUE.
+ */
+ gulong sending_id;
+};
+
+static void sidecar_iface_init (
+ gpointer g_iface,
+ gpointer data);
+static void console_iface_init (
+ gpointer g_iface,
+ gpointer data);
+static void gabble_console_sidecar_set_spew (
+ GabbleConsoleSidecar *self,
+ gboolean spew);
+
+G_DEFINE_TYPE_WITH_CODE (GabbleConsoleSidecar, gabble_console_sidecar,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SIDECAR, sidecar_iface_init);
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_GABBLE_PLUGIN_CONSOLE,
+ console_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ )
+
+static void
+gabble_console_sidecar_init (GabbleConsoleSidecar *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_CONSOLE_SIDECAR,
+ GabbleConsoleSidecarPrivate);
+ self->priv->reader = wocky_xmpp_reader_new_no_stream ();
+ self->priv->writer = wocky_xmpp_writer_new_no_stream ();
+}
+
+static void
+gabble_console_sidecar_get_property (
+ GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (object);
+
+ switch (property_id)
+ {
+ case PROP_SPEW:
+ g_value_set_boolean (value, self->priv->spew);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+gabble_console_sidecar_set_property (
+ GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_assert (self->priv->connection == NULL); /* construct-only */
+ self->priv->connection = g_value_dup_object (value);
+ break;
+
+ case PROP_SESSION:
+ g_assert (self->priv->session == NULL); /* construct-only */
+ self->priv->session = g_value_dup_object (value);
+ break;
+
+ case PROP_SPEW:
+ gabble_console_sidecar_set_spew (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+gabble_console_sidecar_dispose (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (gabble_console_sidecar_parent_class)->dispose;
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (object);
+
+ gabble_console_sidecar_set_spew (self, FALSE);
+
+ tp_clear_object (&self->priv->connection);
+ tp_clear_object (&self->priv->reader);
+ tp_clear_object (&self->priv->writer);
+ tp_clear_object (&self->priv->session);
+
+ if (chain_up != NULL)
+ chain_up (object);
+}
+
+static void
+gabble_console_sidecar_class_init (GabbleConsoleSidecarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ static TpDBusPropertiesMixinPropImpl console_props[] = {
+ { "SpewStanzas", "spew-stanzas", "spew-stanzas" },
+ { NULL },
+ };
+ static TpDBusPropertiesMixinIfaceImpl interfaces[] = {
+ { GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ /* FIXME: if we were feeling clever, we'd override the setter so that
+ * we can monitor the bus name of any application which sets
+ * SpewStanzas to TRUE and flip it back to false when that application
+ * dies.
+ *
+ * Alternatively, we could just replace this sidecar with a channel.
+ */
+ tp_dbus_properties_mixin_setter_gobject_properties,
+ console_props
+ },
+ { NULL },
+ };
+
+ object_class->get_property = gabble_console_sidecar_get_property;
+ object_class->set_property = gabble_console_sidecar_set_property;
+ object_class->dispose = gabble_console_sidecar_dispose;
+
+ g_type_class_add_private (klass, sizeof (GabbleConsoleSidecarPrivate));
+
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ g_param_spec_object ("connection", "Connection",
+ "Gabble connection",
+ GABBLE_TYPE_CONNECTION,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SESSION,
+ g_param_spec_object ("session", "Session",
+ "Wocky session",
+ WOCKY_TYPE_SESSION,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_SPEW,
+ g_param_spec_boolean ("spew-stanzas", "SpewStanzas",
+ "If %TRUE, someone wants us to spit out a tonne of stanzas",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ klass->props_class.interfaces = interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (GabbleConsoleSidecarClass, props_class));
+}
+
+static void sidecar_iface_init (
+ gpointer g_iface,
+ gpointer data)
+{
+ GabbleSidecarInterface *iface = g_iface;
+
+ iface->interface = GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE;
+ iface->get_immutable_properties = NULL;
+}
+
+static gboolean
+incoming_cb (
+ WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (user_data);
+ const guint8 *body;
+ gsize length;
+
+ wocky_xmpp_writer_write_stanza (self->priv->writer, stanza, &body, &length);
+ gabble_svc_gabble_plugin_console_emit_stanza_received (self,
+ (const gchar *) body);
+ return FALSE;
+}
+
+static void
+sending_cb (
+ WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (user_data);
+
+ if (stanza != NULL)
+ {
+ const guint8 *body;
+ gsize length;
+
+ wocky_xmpp_writer_write_stanza (self->priv->writer, stanza, &body,
+ &length);
+ gabble_svc_gabble_plugin_console_emit_stanza_sent (self,
+ (const gchar *) body);
+ }
+}
+
+static void
+gabble_console_sidecar_set_spew (
+ GabbleConsoleSidecar *self,
+ gboolean spew)
+{
+ GabbleConsoleSidecarPrivate *priv = self->priv;
+
+ if (!spew != !priv->spew)
+ {
+ WockyPorter *porter = wocky_session_get_porter (self->priv->session);
+ const gchar *props[] = { "SpewStanzas", NULL };
+
+ priv->spew = spew;
+ tp_dbus_properties_mixin_emit_properties_changed (G_OBJECT (self),
+ GABBLE_IFACE_GABBLE_PLUGIN_CONSOLE, props);
+
+ if (spew)
+ {
+ g_return_if_fail (priv->incoming_handler == 0);
+ priv->incoming_handler = wocky_porter_register_handler_from_anyone (
+ porter, WOCKY_STANZA_TYPE_NONE, WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_MAX, incoming_cb, self, NULL);
+
+ g_return_if_fail (priv->sending_id == 0);
+ priv->sending_id = g_signal_connect (porter, "sending",
+ (GCallback) sending_cb, self);
+ }
+ else
+ {
+ g_return_if_fail (priv->incoming_handler != 0);
+ wocky_porter_unregister_handler (porter, priv->incoming_handler);
+ priv->incoming_handler = 0;
+
+ g_return_if_fail (priv->sending_id != 0);
+ g_signal_handler_disconnect (porter, priv->sending_id);
+ priv->sending_id = 0;
+ }
+ }
+}
+
+static void
+return_from_send_iq (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (source);
+ DBusGMethodInvocation *context = user_data;
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ GError *error = NULL;
+
+ if (g_simple_async_result_propagate_error (simple, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+ else
+ {
+ WockyStanza *reply = g_simple_async_result_get_op_res_gpointer (simple);
+ WockyStanzaSubType sub_type;
+ const guint8 *body;
+ gsize length;
+
+ wocky_stanza_get_type_info (reply, NULL, &sub_type);
+ wocky_xmpp_writer_write_stanza (self->priv->writer, reply, &body, &length);
+
+ /* woop woop */
+ gabble_svc_gabble_plugin_console_return_from_send_iq (context,
+ sub_type == WOCKY_STANZA_SUB_TYPE_RESULT ? "result" : "error",
+ (const gchar *) body);
+ }
+}
+
+static void
+console_iq_reply_cb (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source);
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+ GError *error = NULL;
+ WockyStanza *reply = wocky_porter_send_iq_finish (porter, result, &error);
+
+ if (reply != NULL)
+ {
+ g_simple_async_result_set_op_res_gpointer (simple, reply, g_object_unref);
+ }
+ else
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ }
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static gboolean
+get_iq_type (const gchar *type_str,
+ WockyStanzaSubType *sub_type_out,
+ GError **error)
+{
+ if (!wocky_strdiff (type_str, "get"))
+ {
+ *sub_type_out = WOCKY_STANZA_SUB_TYPE_GET;
+ return TRUE;
+ }
+
+ if (!wocky_strdiff (type_str, "set"))
+ {
+ *sub_type_out = WOCKY_STANZA_SUB_TYPE_SET;
+ return TRUE;
+ }
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Type must be 'get' or 'set', not '%s'", type_str);
+ return FALSE;
+}
+
+static gboolean
+validate_jid (const gchar **to,
+ GError **error)
+{
+ if (tp_str_empty (*to))
+ {
+ *to = NULL;
+ return TRUE;
+ }
+
+ if (wocky_decode_jid (*to, NULL, NULL, NULL))
+ return TRUE;
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "'%s' is not a valid (or empty) JID", *to);
+ return FALSE;
+}
+
+/*
+ * @xml: doesn't actually have to be a top-level stanza. It can be the body of
+ * an IQ or whatever.
+ */
+static gboolean
+parse_me_a_stanza (
+ GabbleConsoleSidecar *self,
+ const gchar *xml,
+ WockyStanza **stanza_out,
+ GError **error)
+{
+ GabbleConsoleSidecarPrivate *priv = self->priv;
+ WockyStanza *stanza;
+
+ wocky_xmpp_reader_reset (priv->reader);
+ wocky_xmpp_reader_push (priv->reader, (const guint8 *) xml, strlen (xml));
+
+ *error = wocky_xmpp_reader_get_error (priv->reader);
+
+ if (*error != NULL)
+ return FALSE;
+
+ stanza = wocky_xmpp_reader_pop_stanza (priv->reader);
+
+ if (stanza == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Incomplete stanza! Bad person.");
+ return FALSE;
+ }
+
+ *stanza_out = stanza;
+ return TRUE;
+}
+
+static void
+console_send_iq (
+ GabbleSvcGabblePluginConsole *sidecar,
+ const gchar *type_str,
+ const gchar *to,
+ const gchar *body,
+ DBusGMethodInvocation *context)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (sidecar);
+ WockyPorter *porter = wocky_session_get_porter (self->priv->session);
+ WockyStanzaSubType sub_type;
+ WockyStanza *fragment;
+ GError *error = NULL;
+
+ if (get_iq_type (type_str, &sub_type, &error) &&
+ validate_jid (&to, &error) &&
+ parse_me_a_stanza (self, body, &fragment, &error))
+ {
+ GSimpleAsyncResult *simple = g_simple_async_result_new (G_OBJECT (self),
+ return_from_send_iq, context, console_send_iq);
+ WockyStanza *stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, sub_type,
+ NULL, to, NULL);
+
+ wocky_node_add_node_tree (wocky_stanza_get_top_node (stanza),
+ WOCKY_NODE_TREE (fragment));
+ wocky_porter_send_iq_async (porter, stanza, NULL, console_iq_reply_cb, simple);
+ g_object_unref (fragment);
+ }
+ else
+ {
+ DEBUG ("%s", error->message);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+static void
+console_stanza_sent_cb (
+ GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source);
+ DBusGMethodInvocation *context = user_data;
+ GError *error = NULL;
+
+ if (wocky_porter_send_finish (porter, result, &error))
+ {
+ gabble_svc_gabble_plugin_console_return_from_send_stanza (context);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ }
+}
+
+static gboolean
+stanza_looks_coherent (
+ WockyStanza *stanza,
+ GError **error)
+{
+ WockyNode *top_node = wocky_stanza_get_top_node (stanza);
+ WockyStanzaType t;
+ WockyStanzaSubType st;
+
+ wocky_stanza_get_type_info (stanza, &t, &st);
+
+ if (t == WOCKY_STANZA_TYPE_UNKNOWN)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "I don't know what a <%s/> is", top_node->name);
+ return FALSE;
+ }
+ else if (st == WOCKY_STANZA_SUB_TYPE_UNKNOWN)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "I don't know what type='%s' means",
+ wocky_node_get_attribute (top_node, "type"));
+ return FALSE;
+ }
+ else
+ {
+ if (top_node->ns == g_quark_from_static_string (""))
+ {
+ /* So... Wocky puts an empty string in as the namespace. Greaaat. */
+ top_node->ns = g_quark_from_static_string (WOCKY_XMPP_NS_JABBER_CLIENT);
+ }
+
+ return TRUE;
+ }
+}
+
+static void
+console_send_stanza (
+ GabbleSvcGabblePluginConsole *sidecar,
+ const gchar *xml,
+ DBusGMethodInvocation *context)
+{
+ GabbleConsoleSidecar *self = GABBLE_CONSOLE_SIDECAR (sidecar);
+ WockyPorter *porter = wocky_session_get_porter (self->priv->session);
+ WockyStanza *stanza = NULL;
+ GError *error = NULL;
+
+ if (parse_me_a_stanza (self, xml, &stanza, &error) &&
+ stanza_looks_coherent (stanza, &error))
+ {
+ wocky_porter_send_async (porter, stanza, NULL, console_stanza_sent_cb,
+ context);
+ }
+ else
+ {
+ DEBUG ("%s", error->message);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+
+ tp_clear_object (&stanza);
+}
+
+static void
+console_iface_init (
+ gpointer klass,
+ gpointer data G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) gabble_svc_gabble_plugin_console_implement_##x (\
+ klass, console_##x)
+ IMPLEMENT (send_iq);
+ IMPLEMENT (send_stanza);
+#undef IMPLEMENT
+}
diff --git a/plugins/console.h b/plugins/console.h
new file mode 100644
index 000000000..a91b95337
--- /dev/null
+++ b/plugins/console.h
@@ -0,0 +1,88 @@
+/* XML console plugin
+ *
+ * Copyright © 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <glib-object.h>
+
+#include <gio/gio.h>
+#include <wocky/wocky-session.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+
+typedef struct _GabbleConsolePlugin GabbleConsolePlugin;
+typedef struct _GabbleConsolePluginClass GabbleConsolePluginClass;
+typedef struct _GabbleConsolePluginPrivate GabbleConsolePluginPrivate;
+
+struct _GabbleConsolePlugin {
+ GObject parent;
+ GabbleConsolePluginPrivate *priv;
+};
+
+struct _GabbleConsolePluginClass {
+ GObjectClass parent;
+};
+
+GType gabble_console_plugin_get_type (void);
+
+#define GABBLE_TYPE_CONSOLE_PLUGIN \
+ (gabble_console_plugin_get_type ())
+#define GABBLE_CONSOLE_PLUGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GABBLE_TYPE_CONSOLE_PLUGIN, \
+ GabbleConsolePlugin))
+#define GABBLE_CONSOLE_PLUGIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GABBLE_TYPE_CONSOLE_PLUGIN, \
+ GabbleConsolePluginClass))
+#define GABBLE_IS_CONSOLE_PLUGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GABBLE_TYPE_CONSOLE_PLUGIN))
+#define GABBLE_IS_CONSOLE_PLUGIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GABBLE_TYPE_CONSOLE_PLUGIN))
+#define GABBLE_CONSOLE_PLUGIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GABBLE_TYPE_CONSOLE_PLUGIN, \
+ GabbleConsolePluginClass))
+
+typedef struct _GabbleConsoleSidecar GabbleConsoleSidecar;
+typedef struct _GabbleConsoleSidecarClass GabbleConsoleSidecarClass;
+typedef struct _GabbleConsoleSidecarPrivate GabbleConsoleSidecarPrivate;
+
+struct _GabbleConsoleSidecar {
+ GObject parent;
+ GabbleConsoleSidecarPrivate *priv;
+};
+
+struct _GabbleConsoleSidecarClass {
+ GObjectClass parent;
+
+ TpDBusPropertiesMixinClass props_class;
+};
+
+GType gabble_console_sidecar_get_type (void);
+
+#define GABBLE_TYPE_CONSOLE_SIDECAR \
+ (gabble_console_sidecar_get_type ())
+#define GABBLE_CONSOLE_SIDECAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GABBLE_TYPE_CONSOLE_SIDECAR, \
+ GabbleConsoleSidecar))
+#define GABBLE_CONSOLE_SIDECAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GABBLE_TYPE_CONSOLE_SIDECAR, \
+ GabbleConsoleSidecarClass))
+#define GABBLE_IS_CONSOLE_SIDECAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GABBLE_TYPE_CONSOLE_SIDECAR))
+#define GABBLE_IS_CONSOLE_SIDECAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GABBLE_TYPE_CONSOLE_SIDECAR))
+#define GABBLE_CONSOLE_SIDECAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GABBLE_TYPE_CONSOLE_SIDECAR, \
+ GabbleConsoleSidecarClass))
diff --git a/plugins/telepathy-gabble-xmpp-console b/plugins/telepathy-gabble-xmpp-console
new file mode 100755
index 000000000..4ab6ecbea
--- /dev/null
+++ b/plugins/telepathy-gabble-xmpp-console
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 sts=4 sw=4 et :
+"""
+The world's worst XMPP console user interface.
+
+Pass it the bus name of a Gabble connection; type some words; get minimalistic
+error reporting.
+
+Copyright © 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+
+import sys
+import re
+from xml.dom import minidom
+
+import pygtk
+pygtk.require('2.0')
+
+from gi.repository import Gtk
+from gi.repository import GLib
+from gi.repository import Gio
+from gi.repository import GtkSource
+
+PADDING = 6
+
+def pathify(name):
+ return '/' + name.replace('.', '/')
+
+def nameify(path):
+ return (path[1:]).replace('/', '.')
+
+CONN_FUTURE_IFACE = "org.freedesktop.Telepathy.Connection.FUTURE"
+CONSOLE_IFACE = "org.freedesktop.Telepathy.Gabble.Plugin.Console"
+
+class StanzaViewer(Gtk.ScrolledWindow):
+ def __init__(self):
+ Gtk.ScrolledWindow.__init__(self)
+
+ self.b = GtkSource.Buffer()
+ self.view = GtkSource.View.new_with_buffer(self.b)
+ self.b.set_language(
+ GtkSource.LanguageManager.get_default().get_language('xml'))
+ self.b.set_highlight_matching_brackets(False)
+ self.view.set_editable(False)
+ self.view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
+ self.view.set_property('expand', True)
+
+ self.add(self.view)
+
+ def clear(self):
+ self.b.set_text("")
+
+ def append_stanza(self, xml):
+ pretty = minidom.parseString(xml).toprettyxml()
+ pretty = pretty.replace('<?xml version="1.0" ?>\n', '')
+ i = self.b.get_end_iter()
+ self.b.insert(i, pretty + '\n')
+
+ def append_comment(self, text):
+ i = self.b.get_end_iter()
+ self.b.insert(i, '<!-- %s -->\n' % text)
+
+ def tell_me_everything(self):
+ return self.b.get_property('text')
+
+class SpinWrapper(Gtk.Notebook):
+ PRIMARY_PAGE = 0
+ SPINNER_PAGE = 1
+
+ def __init__(self, main_widget):
+ Gtk.Notebook.__init__(self)
+ self.set_show_tabs(False)
+ self.set_show_border(False)
+ self.insert_page(main_widget, None, self.PRIMARY_PAGE)
+
+ self.spinner = Gtk.Spinner()
+ self.spinner.set_property('halign', Gtk.Align.CENTER)
+ self.spinner.set_property('valign', Gtk.Align.CENTER)
+ self.spinner.set_property('width-request', 32)
+ self.spinner.set_property('height-request', 32)
+ self.insert_page(self.spinner, None, self.SPINNER_PAGE)
+
+ def start_spinning(self):
+ self.set_current_page(self.SPINNER_PAGE)
+ self.spinner.start()
+
+ def stop_spinning(self):
+ self.spinner.stop()
+ self.set_current_page(self.PRIMARY_PAGE)
+
+class Page(Gtk.Grid):
+ def __init__(self, console_proxy):
+ Gtk.Grid.__init__(self)
+
+ self.console_proxy = console_proxy
+
+ self.set_column_spacing(PADDING)
+ self.set_row_spacing(PADDING)
+
+ def add_title(self, title, below=None):
+ label = Gtk.Label()
+ label.set_markup("<b>%s</b>" % title)
+ label.set_property('xalign', 0)
+
+ if below is None:
+ self.attach(label, 0, 0, 2, 1)
+ else:
+ self.attach_next_to(label, below, Gtk.PositionType.BOTTOM, 2, 1)
+
+ return label
+
+ def add_label(self, title, below=None):
+ label = Gtk.Label(title)
+ label.set_property('margin-left', PADDING)
+ label.set_property('xalign', 0)
+
+ if below is None:
+ self.attach(label, 0, 0, 1, 1)
+ else:
+ self.attach_next_to(label, below, Gtk.PositionType.BOTTOM, 1, 1)
+
+ return label
+
+class IQPage(Page):
+ def __init__(self, console_proxy):
+ Page.__init__(self, console_proxy)
+
+ request_label = self.add_title("Request")
+
+ recipient_label, recipient_entry = self.add_label_entry_pair(
+ 'To:', below=request_label)
+ self.recipient_entry = recipient_entry
+
+ type_label = self.add_label('IQ Type:', below=recipient_label)
+ self.get_button = Gtk.RadioButton.new_with_label([], "get")
+ self.get_button.set_active(True)
+ self.set_button = Gtk.RadioButton.new_with_label_from_widget(
+ self.get_button, "set")
+
+ box = Gtk.ButtonBox.new(Gtk.Orientation.HORIZONTAL)
+ box.set_layout(Gtk.ButtonBoxStyle.START)
+ box.add(self.get_button)
+ box.add(self.set_button)
+ self.attach_next_to(box, type_label,
+ Gtk.PositionType.RIGHT, 1, 1)
+
+ body_label, body_entry = self.add_label_entry_pair(
+ 'Body:', below=type_label)
+ body_entry.set_text(
+ "<query xmlns='http://jabber.org/protocol/disco#info'/>")
+ body_entry.set_icon_from_stock(
+ Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_GO_FORWARD)
+ body_entry.set_icon_tooltip_text(
+ Gtk.EntryIconPosition.SECONDARY, "Send this IQ")
+ self.body_entry = body_entry
+
+ reply_label = self.add_title("Reply", below=body_label)
+
+ self.stanza_viewer = StanzaViewer()
+ self.stanza_viewer.append_comment("send a request to see the reply here")
+
+ self.result_nb = SpinWrapper(self.stanza_viewer)
+
+ self.attach_next_to(self.result_nb, reply_label, Gtk.PositionType.BOTTOM, 2, 1)
+
+ body_entry.connect('activate', self.send_iq)
+ body_entry.connect('icon-release', self.send_iq)
+
+ def add_label_entry_pair(self, title, below):
+ label = self.add_label(title, below)
+
+ entry = Gtk.Entry()
+ entry.set_property('margin-right', PADDING)
+ entry.set_property('hexpand', True)
+
+ self.attach_next_to(entry, label, Gtk.PositionType.RIGHT, 1, 1)
+
+ return label, entry
+
+ def send_iq(self, *misc):
+ type = 'get' if self.get_button.get_active() else 'set'
+ to = self.recipient_entry.get_text()
+ body = self.body_entry.get_text()
+
+ self.console_proxy.SendIQ('(sss)', type, to, body,
+ result_handler=self.send_iq_cb)
+ self.result_nb.start_spinning()
+
+ def send_iq_cb(self, proxy, result, user_data):
+ self.stanza_viewer.clear()
+
+ if isinstance(result, Exception):
+ self.stanza_viewer.append_comment("error:\n%s" % result)
+ else:
+ reply_type, reply = result
+ self.stanza_viewer.append_stanza(reply)
+
+ self.result_nb.stop_spinning()
+
+class StanzaPage(Page):
+ def __init__(self, console_proxy):
+ Page.__init__(self, console_proxy)
+
+ title = self.add_title("Enter a complete stanza:")
+
+ self.sv = StanzaViewer()
+ self.sv.view.set_editable(True)
+ self.sv.append_stanza("<message to='t-pain@test.collabora.co.uk' type='chat'><body>Been on any nice boats recently?</body></message>")
+
+ self.spin_wrapper = SpinWrapper(self.sv)
+ self.attach_next_to(self.spin_wrapper, title, Gtk.PositionType.BOTTOM,
+ 2, 1)
+
+ self.result_label = self.add_label('', self.spin_wrapper)
+ self.result_label.set_property('hexpand', True)
+ self.result_label.set_line_wrap(True)
+
+ b = Gtk.Button.new_with_mnemonic("_Send")
+ b.connect('clicked', self.__send_stanza)
+ b.set_property('hexpand', False)
+ self.attach_next_to(b, self.result_label, Gtk.PositionType.RIGHT, 1, 1)
+
+ def __send_stanza(self, button):
+ self.console_proxy.SendStanza('(s)', self.sv.tell_me_everything(),
+ result_handler=self.__send_stanza_cb)
+ self.spin_wrapper.start_spinning()
+
+ def __send_stanza_cb(self, proxy, result, user_data):
+ if isinstance(result, Exception):
+ # FIXME: this sucks. You can't just get the free text bit without
+ # the D-Bus error bit.
+ t = result.message
+ else:
+ t = "yes sir, captain tightpants"
+
+ self.result_label.set_text(t)
+ self.spin_wrapper.stop_spinning()
+
+class SnoopyPage(Page):
+ def __init__(self, console_proxy):
+ Page.__init__(self, console_proxy)
+
+ label = self.add_label("Stanza monitor:")
+ label.set_property('hexpand', True)
+
+ switch = Gtk.Switch()
+ self.attach_next_to(switch, label, Gtk.PositionType.RIGHT, 1, 1)
+
+ self.stanza_viewer = StanzaViewer()
+ self.attach_next_to(self.stanza_viewer, label, Gtk.PositionType.BOTTOM, 2, 1)
+
+ switch.set_active(self.get_remote_active())
+ switch.connect('notify::active', self.__switch_switched_cb)
+
+ self.console_proxy.connect('g-signal', self.__g_signal_cb)
+
+ def teardown(self):
+ """Turn off the monitor when we quit."""
+ self.__set_spew(False)
+
+ def __set_spew(self, spew):
+ args = GLib.Variant("(ssv)", (CONSOLE_IFACE, "SpewStanzas",
+ GLib.Variant.new_boolean(spew)))
+ self.console_proxy.call_sync(
+ "org.freedesktop.DBus.Properties.Set",
+ args,
+ 0, -1, None)
+
+ def get_remote_active(self):
+ return self.console_proxy.get_cached_property('SpewStanzas').get_boolean()
+
+ def __switch_switched_cb(self, switch, pspec):
+ remote = self.get_remote_active()
+ new_local = switch.get_active()
+
+ if new_local != remote:
+ self.__set_spew(new_local)
+ self.stanza_viewer.append_comment(
+ 'started monitoring' if new_local else 'stopped monitoring')
+
+ def __g_signal_cb(self, console_proxy, sender_name, signal_name, parameters):
+ if signal_name in ['StanzaSent', 'StanzaReceived']:
+ outgoing = (signal_name == 'StanzaSent')
+ xml, = parameters
+
+ self.stanza_viewer.append_comment('sent' if outgoing else 'received')
+ self.stanza_viewer.append_stanza(xml)
+
+class Window(Gtk.Window):
+ IQ_PAGE = 0
+ STANZA_PAGE = 1
+ SNOOPY_PAGE = 2
+
+ def __init__(self, bus, connection_bus_name):
+ Gtk.Window.__init__(self)
+
+ self.set_title('XMPP Console')
+ self.set_default_size(600, 371)
+
+ conn_future_proxy = Gio.DBusProxy.new_sync(bus, 0, None,
+ connection_bus_name, pathify(connection_bus_name),
+ CONN_FUTURE_IFACE, None)
+ try:
+ sidecar_path, _ = conn_future_proxy.EnsureSidecar('(s)', CONSOLE_IFACE)
+ except Exception, e:
+ print """
+Couldn't connect to the XMPP console interface on '%(connection_bus_name)s':
+ %(e)s
+Check that it's a running Jabber connection, and that you have the console
+plugin installed.""" % locals()
+
+ raise SystemExit(2)
+
+ self.console_proxy = Gio.DBusProxy.new_sync(bus, 0, None,
+ connection_bus_name, sidecar_path, CONSOLE_IFACE, None)
+
+ # Build up the UI
+ self.nb = Gtk.Notebook()
+ self.add(self.nb)
+
+ self.iq = IQPage(self.console_proxy)
+ self.nb.insert_page(self.iq,
+ Gtk.Label.new_with_mnemonic("_IQ console"),
+ self.IQ_PAGE)
+
+ self.stanza = StanzaPage(self.console_proxy)
+ self.nb.insert_page(self.stanza,
+ Gtk.Label.new_with_mnemonic("Send a s_tanza"),
+ self.STANZA_PAGE)
+
+ self.snoopy = SnoopyPage(self.console_proxy)
+ self.nb.insert_page(self.snoopy,
+ Gtk.Label.new_with_mnemonic("_Monitor network traffic"),
+ self.SNOOPY_PAGE)
+
+ self.connect('destroy', Window.__destroy_cb)
+
+ def __destroy_cb(self):
+ self.snoopy.teardown()
+ Gtk.main_quit()
+
+GABBLE_PREFIX = 'org.freedesktop.Telepathy.Connection.gabble.jabber.'
+
+AM_BUS_NAME = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_PREFIX = '/org/freedesktop/Telepathy/Account'
+ACCOUNT_IFACE = 'org.freedesktop.Telepathy.Account'
+
+def usage():
+ print """
+Usage:
+
+ %(arg0)s gabble/jabber/blahblah
+ %(arg0)s %(prefix)sblahblah
+
+List account identifiers using `mc-tool list | grep gabble`.
+List connection bus names using `qdbus | grep gabble`.
+""" % { 'arg0': sys.argv[0],
+ 'prefix': GABBLE_PREFIX,
+ }
+ raise SystemExit(1)
+
+if __name__ == '__main__':
+ bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
+
+ if len(sys.argv) != 2:
+ usage()
+
+ thing = sys.argv[1]
+
+ if re.match('^gabble/jabber/[a-zA-Z0-9_]+$', thing):
+ # Looks like an account path to me.
+ account_proxy = Gio.DBusProxy.new_sync(bus, 0, None,
+ AM_BUS_NAME, '%s/%s' % (ACCOUNT_PREFIX, thing),
+ ACCOUNT_IFACE, None)
+ path = account_proxy.get_cached_property('Connection').get_string()
+ if path == '/':
+ print "%s is not online" % thing
+ raise SystemExit(1)
+ else:
+ thing = nameify(path)
+
+ if not re.match('^%s[a-zA-Z0-9_]+$' % GABBLE_PREFIX, thing):
+ usage()
+
+ win = Window(bus, thing)
+ win.show_all()
+
+ Gtk.main()
+
+"""
+ .,,.
+ ,;;*;;;;,
+ .-'``;-');;.
+ /' .-. /*;;
+ .' \d \;; .;;;,
+ / o ` \; ,__. ,;*;;;*;,
+ \__, _.__,' \_.-') __)--.;;;;;*;;;;,
+ `""`;;;\ /-')_) __) `\' ';;;;;;
+ ;*;;; -') `)_) |\ | ;;;;*;
+ ;;;;| `---` O | | ;;*;;;
+ *;*;\| O / ;;;;;*
+ ;;;;;/| .-------\ / ;*;;;;;
+ ;;;*;/ \ | '. (`. ;;;*;;;
+ ;;;;;'. ; | ) \ | ;;;;;;
+ ,;*;;;;\/ |. / /` | ';;;*;
+ ;;;;;;/ |/ / /__/ ';;;
+ '*jgs/ | / | ;*;
+ `""""` `""""` ;'
+"""
diff --git a/plugins/test.c b/plugins/test.c
index 9130cd1b3..05d784052 100644
--- a/plugins/test.c
+++ b/plugins/test.c
@@ -1,4 +1,5 @@
#include "test.h"
+#include "config.h"
#include <stdio.h>
@@ -161,6 +162,7 @@ plugin_iface_init (
GabblePluginInterface *iface = g_iface;
iface->name = "Sidecar test plugin";
+ iface->version = PACKAGE_VERSION;
iface->sidecar_interfaces = sidecar_interfaces;
iface->create_sidecar = test_plugin_create_sidecar;
iface->create_channel_managers = test_plugin_create_channel_managers;