summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Thompson <will.thompson@collabora.co.uk>2011-11-21 13:14:04 +0000
committerWill Thompson <will.thompson@collabora.co.uk>2011-11-21 13:14:09 +0000
commit357aef5d882f461a05037255f3f0dee844d18d6f (patch)
tree00cbf9ed66b37b7d174c6fea880074d7d39efc2d
parentcbe05e97476160fab38ba491717f4d5a42c17887 (diff)
parentac3ed729bed7d8c4fb62a11ade54f54b6139c0d0 (diff)
Merge branch 'xmpp-console'
Fixes: <https://bugs.freedesktop.org/show_bug.cgi?id=xmpp-console> Reviewed-by: Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
-rw-r--r--extensions/Gabble_Plugin_Console.xml117
-rw-r--r--extensions/Makefile.am3
-rw-r--r--extensions/all.xml1
m---------lib/ext/wocky0
-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
-rw-r--r--tests/twisted/Makefile.am1
-rw-r--r--tests/twisted/console.py86
-rw-r--r--tools/c-constants-gen.py57
-rw-r--r--tools/glib-ginterface-gen.py191
-rw-r--r--tools/glib-gtypes-generator.py97
-rw-r--r--tools/glib-interfaces-gen.py84
15 files changed, 1684 insertions, 145 deletions
diff --git a/extensions/Gabble_Plugin_Console.xml b/extensions/Gabble_Plugin_Console.xml
new file mode 100644
index 000000000..1e3b52385
--- /dev/null
+++ b/extensions/Gabble_Plugin_Console.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" ?>
+<node name="/Gabble_Plugin_Console"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2011 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>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.</p>
+
+ <p>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.</p>
+
+ <p>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 Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Gabble.Plugin.Console">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal"
+ value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A sidecar interface providing an XMPP console.</p>
+ </tp:docstring>
+
+ <method name="SendIQ" tp:name-for-bindings="Send_IQ">
+ <arg direction="in" name="Type" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The type of IQ request to send; either <code>"get"</code> or
+ <code>"set"</code>.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="To" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The recipient for the IQ; or the empty string to send an IQ to the
+ server (with no recipient specified).
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Body" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An XML fragment for the body of the IQ, which MUST have exactly one
+ top-level element.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Reply_Type" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The type of IQ reply; either <code>"result"</code> or
+ <code>"error"</code>.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Body" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An XML fragment for the body of the response, which MUST have between
+ zero and two top-level elements (the query element, an error element,
+ both, or neither).
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Hai.
+ </tp:docstring>
+ </method>
+
+ <method name="SendStanza" tp:name-for-bindings="Send_Stanza">
+ <arg direction="in" name="Stanza" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A complete stanza.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Sends a stanza, yo.
+ </tp:docstring>
+ </method>
+
+ <signal name="StanzaSent" tp:name-for-bindings="Stanza_Sent">
+ <arg name="Stanza" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The stanza, in glorious XML.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted whenever a stanza is sent and
+ <tp:member-ref>SpewStanzas</tp:member-ref> is
+ <code>True</code>.
+ </tp:docstring>
+ </signal>
+
+ <signal name="StanzaReceived" tp:name-for-bindings="Stanza_Received">
+ <arg name="Stanza" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The stanza, in glorious XML.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted whenever a stanza is received and
+ <tp:member-ref>SpewStanzas</tp:member-ref> is
+ <code>True</code>.
+ </tp:docstring>
+ </signal>
+
+ <property name="SpewStanzas" type="b" access="readwrite"
+ tp:name-for-bindings="Spew_Stanzas">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ If <code>True</code>, <tp:member-ref>StanzaReceived</tp:member-ref> and
+ <tp:member-ref>StanzaSent</tp:member-ref> will be emitted, drowning
+ your session bus in a sea of XML.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index feee4e790..628eee5c4 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -5,6 +5,7 @@ EXTRA_DIST = \
Channel_Type_FileTransfer_Future.xml \
Connection_Future.xml \
Connection_Interface_Gabble_Decloak.xml \
+ Gabble_Plugin_Console.xml \
Gabble_Plugin_Gateways.xml \
Gabble_Plugin_Test.xml \
OLPC_Activity_Properties.xml \
@@ -79,7 +80,7 @@ _gen/signals-marshal.c: _gen/signals-marshal.list Makefile.am
_gen/enums.h: _gen/all.xml $(tools_dir)/c-constants-gen.py \
Makefile.am
- $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py Gabble $< > $@
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py Gabble $< _gen/enums
_gen/interfaces.h _gen/interfaces-body.h: _gen/all.xml \
$(tools_dir)/glib-interfaces-gen.py \
diff --git a/extensions/all.xml b/extensions/all.xml
index cb368ef9e..90f52a89b 100644
--- a/extensions/all.xml
+++ b/extensions/all.xml
@@ -40,6 +40,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA</p>
<xi:include href="Connection_Interface_Gabble_Decloak.xml"/>
<xi:include href="Connection_Future.xml"/>
+<xi:include href="Gabble_Plugin_Console.xml"/>
<xi:include href="Gabble_Plugin_Gateways.xml"/>
<xi:include href="Gabble_Plugin_Test.xml"/>
diff --git a/lib/ext/wocky b/lib/ext/wocky
-Subproject a0e74a1cd04ab5e35922536f047664a5ce69c11
+Subproject 79008a4927792ddda8720d70d09948951e1b842
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;
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
index 5d7c5abc9..5f52cc7a6 100644
--- a/tests/twisted/Makefile.am
+++ b/tests/twisted/Makefile.am
@@ -27,6 +27,7 @@ TWISTED_TESTS = \
connect/test-nonblocking-tls.py \
connect/test-success.py \
connect/test-twice.py \
+ console.py \
dataforms.py \
gateways.py \
last-activity.py \
diff --git a/tests/twisted/console.py b/tests/twisted/console.py
new file mode 100644
index 000000000..f6a6336c9
--- /dev/null
+++ b/tests/twisted/console.py
@@ -0,0 +1,86 @@
+"""
+A smoketest for the XMPP console API.
+"""
+
+from servicetest import (
+ ProxyWrapper, EventPattern,
+ call_async, assertEquals, assertContains, assertNotEquals, sync_dbus,
+)
+from gabbletest import exec_test, acknowledge_iq, elem, elem_iq
+from config import PLUGINS_ENABLED
+from twisted.words.xish import domish
+
+CONSOLE_PLUGIN_IFACE = "org.freedesktop.Telepathy.Gabble.Plugin.Console"
+STACY = 'stacy@pilgrim.lit'
+
+if not PLUGINS_ENABLED:
+ print "NOTE: built without --enable-plugins, not testing XMPP console"
+ raise SystemExit(77)
+
+def send_unrecognised_get(q, stream):
+ stream.send(
+ elem_iq(stream, 'get')(
+ elem('urn:unimaginative', 'dont-handle-me-bro')
+ ))
+
+ return q.expect('stream-iq', iq_type='error')
+
+def test(q, bus, conn, stream):
+ path, _ = conn.Future.EnsureSidecar(CONSOLE_PLUGIN_IFACE)
+ console = ProxyWrapper(bus.get_object(conn.bus_name, path),
+ CONSOLE_PLUGIN_IFACE)
+
+ assert not console.Properties.Get(CONSOLE_PLUGIN_IFACE, 'SpewStanzas')
+ es = [
+ EventPattern('dbus-signal', signal='StanzaReceived'),
+ EventPattern('dbus-signal', signal='StanzaSent'),
+ ]
+ q.forbid_events(es)
+
+ call_async(q, console, 'SendIQ', 'get', STACY,
+ '<coffee xmlns="urn:unimaginative"/>')
+ e = q.expect('stream-iq', iq_type='get', query_ns='urn:unimaginative',
+ query_name='coffee')
+ acknowledge_iq(stream, e.stanza)
+ e = q.expect('dbus-return', method='SendIQ')
+ type_, body = e.value
+ assertEquals('result', type_)
+ # We just assume the body works.
+
+ # Turn on signalling incoming and outgoing stanzas
+ console.Properties.Set(CONSOLE_PLUGIN_IFACE, 'SpewStanzas', True)
+ sync_dbus(bus, q, conn)
+ q.unforbid_events(es)
+
+ send_unrecognised_get(q, stream)
+
+ e = q.expect('dbus-signal', signal='StanzaReceived')
+ xml, = e.args
+ assertContains('<iq', xml)
+ assertContains('<dont-handle-me-bro', xml)
+
+ signal = q.expect('dbus-signal', signal='StanzaSent')
+ assertContains('service-unavailable', signal.args[0])
+
+ # Turn off spewing out stanzas; check it works.
+ console.Properties.Set(CONSOLE_PLUGIN_IFACE, 'SpewStanzas', False)
+ q.forbid_events(es)
+ send_unrecognised_get(q, stream)
+ sync_dbus(bus, q, conn)
+
+ # Try sending just any old stanza
+ console.SendStanza('''
+ <message to='%(stacy)s' type='headline'>
+ <body>
+ Hi sis.
+ </body>
+ </message>''' % { 'stacy': STACY })
+
+ e = q.expect('stream-message', to=STACY, message_type='headline')
+ # Wocky fills in xmlns='' for us if we don't specify a namespace... great.
+ # So this means <message/> gets sent as <message xmlns=''/> and the server
+ # kicks us off.
+ assertNotEquals('', e.stanza.uri)
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/tools/c-constants-gen.py b/tools/c-constants-gen.py
index 8969ffdca..ff2a24d47 100644
--- a/tools/c-constants-gen.py
+++ b/tools/c-constants-gen.py
@@ -7,17 +7,23 @@ from libglibcodegen import NS_TP, get_docstring, \
get_descendant_text, get_by_path
class Generator(object):
- def __init__(self, prefix, dom):
+ def __init__(self, prefix, dom, output_base):
self.prefix = prefix + '_'
self.spec = get_by_path(dom, "spec")[0]
+ self.__header = open(output_base + '.h', 'w')
+ self.__docs = open(output_base + '-gtk-doc.h', 'w')
+
def __call__(self):
self.do_header()
self.do_body()
self.do_footer()
def write(self, code):
- stdout.write(code.encode('utf-8'))
+ self.__header.write(code.encode('utf-8'))
+
+ def d(self, code):
+ self.__docs.write(code.encode('utf-8'))
# Header
def do_header(self):
@@ -54,25 +60,27 @@ extern "C" {
value_prefix = flags.getAttribute('singular') or \
flags.getAttribute('value-prefix') or \
flags.getAttribute('name')
- self.write("""\
+ self.d("""\
/**
*
%s:
""" % (self.prefix + name).replace('_', ''))
for flag in get_by_path(flags, 'flag'):
self.do_gtkdoc(flag, value_prefix)
- self.write(' *\n')
+ self.d(' *\n')
docstrings = get_by_path(flags, 'docstring')
if docstrings:
- self.write("""\
+ self.d("""\
* <![CDATA[%s]]>
*
""" % get_descendant_text(docstrings).replace('\n', ' '))
- self.write("""\
+ self.d("""\
* Bitfield/set of flags generated from the Telepathy specification.
*/
-typedef enum {
""")
+
+ self.write("typedef enum /*< flags >*/ {\n")
+
for flag in get_by_path(flags, 'flag'):
self.do_val(flag, value_prefix)
self.write("""\
@@ -87,7 +95,7 @@ typedef enum {
enum.getAttribute('name')
name_plural = enum.getAttribute('plural') or \
enum.getAttribute('name') + 's'
- self.write("""\
+ self.d("""\
/**
*
%s:
@@ -95,28 +103,35 @@ typedef enum {
vals = get_by_path(enum, 'enumvalue')
for val in vals:
self.do_gtkdoc(val, value_prefix)
- self.write(' *\n')
+ self.d(' *\n')
docstrings = get_by_path(enum, 'docstring')
if docstrings:
- self.write("""\
+ self.d("""\
* <![CDATA[%s]]>
*
""" % get_descendant_text(docstrings).replace('\n', ' '))
- self.write("""\
+ self.d("""\
* Bitfield/set of flags generated from the Telepathy specification.
*/
-typedef enum {
""")
+
+ self.write("typedef enum {\n")
+
for val in vals:
self.do_val(val, value_prefix)
- self.write("""\
-} %(mixed-name)s;
+ self.write("} %s;\n" % (self.prefix + name).replace('_', ''))
+ self.d("""\
/**
- * NUM_%(upper-plural)s:
+ * NUM_%(upper-plural)s: (skip)
*
* 1 higher than the highest valid value of #%(mixed-name)s.
*/
+""" % {'mixed-name' : (self.prefix + name).replace('_', ''),
+ 'upper-plural' : (self.prefix + name_plural).upper(),
+ 'last-val' : vals[-1].getAttribute('value')})
+
+ self.write("""\
#define NUM_%(upper-plural)s (%(last-val)s+1)
""" % {'mixed-name' : (self.prefix + name).replace('_', ''),
@@ -133,13 +148,13 @@ typedef enum {
self.write(' %s = %s,\n' % (use_name, val.getAttribute('value')))
def do_gtkdoc(self, node, value_prefix):
- self.write(' * @')
- self.write((self.prefix + value_prefix + '_' +
+ self.d(' * @')
+ self.d((self.prefix + value_prefix + '_' +
node.getAttribute('suffix')).upper())
- self.write(': <![CDATA[')
+ self.d(': <![CDATA[')
docstring = get_by_path(node, 'docstring')
- self.write(get_descendant_text(docstring).replace('\n', ' '))
- self.write(']]>\n')
+ self.d(get_descendant_text(docstring).replace('\n', ' '))
+ self.d(']]>\n')
# Footer
def do_footer(self):
@@ -151,4 +166,4 @@ typedef enum {
if __name__ == '__main__':
argv = argv[1:]
- Generator(argv[0], xml.dom.minidom.parse(argv[1]))()
+ Generator(argv[0], xml.dom.minidom.parse(argv[1]), argv[2])()
diff --git a/tools/glib-ginterface-gen.py b/tools/glib-ginterface-gen.py
index 95c827c7a..e277b91f4 100644
--- a/tools/glib-ginterface-gen.py
+++ b/tools/glib-ginterface-gen.py
@@ -33,6 +33,16 @@ from libglibcodegen import Signature, type_to_gtype, cmp_by_name, \
NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+def get_emits_changed(node):
+ try:
+ return [
+ annotation.getAttribute('value')
+ for annotation in node.getElementsByTagName('annotation')
+ if annotation.getAttribute('name') == 'org.freedesktop.DBus.Property.EmitsChangedSignal'
+ ][0]
+ except IndexError:
+ return None
+
class Generator(object):
def __init__(self, dom, prefix, basename, signal_marshal_prefix,
@@ -41,6 +51,7 @@ class Generator(object):
self.dom = dom
self.__header = []
self.__body = []
+ self.__docs = []
assert prefix.endswith('_')
assert not signal_marshal_prefix.endswith('_')
@@ -74,11 +85,20 @@ class Generator(object):
self.allow_havoc = allow_havoc
def h(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
self.__header.append(s)
def b(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
self.__body.append(s)
+ def d(self, s):
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.__docs.append(s)
+
def do_node(self, node):
node_name = node.getAttribute('name').replace('/', '')
node_name_mixed = self.node_name_mixed = node_name.replace('_', '')
@@ -98,6 +118,8 @@ class Generator(object):
if tmp and not self.allow_havoc:
raise AssertionError('%s is %s' % (self.iface_name, tmp))
+ iface_emits_changed = get_emits_changed(interface)
+
self.b('static const DBusGObjectInfo _%s%s_object_info;'
% (self.prefix_, node_name_lc))
self.b('')
@@ -158,54 +180,54 @@ class Generator(object):
self.b('}')
self.b('')
- self.h('/**')
- self.h(' * %s%s:' % (self.Prefix, node_name_mixed))
- self.h(' *')
- self.h(' * Dummy typedef representing any implementation of this '
+ self.d('/**')
+ self.d(' * %s%s:' % (self.Prefix, node_name_mixed))
+ self.d(' *')
+ self.d(' * Dummy typedef representing any implementation of this '
'interface.')
- self.h(' */')
+ self.d(' */')
+
self.h('typedef struct _%s%s %s%s;'
% (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
self.h('')
- self.h('/**')
- self.h(' * %s%sClass:' % (self.Prefix, node_name_mixed))
- self.h(' *')
- self.h(' * The class of %s%s.' % (self.Prefix, node_name_mixed))
+
+ self.d('/**')
+ self.d(' * %s%sClass:' % (self.Prefix, node_name_mixed))
+ self.d(' *')
+ self.d(' * The class of %s%s.' % (self.Prefix, node_name_mixed))
if methods:
- self.h(' *')
- self.h(' * In a full implementation of this interface (i.e. all')
- self.h(' * methods implemented), the interface initialization')
- self.h(' * function used in G_IMPLEMENT_INTERFACE() would')
- self.h(' * typically look like this:')
- self.h(' *')
- self.h(' * <programlisting>')
- self.h(' * static void')
- self.h(' * implement_%s (gpointer klass,' % self.node_name_lc)
- self.h(' * gpointer unused G_GNUC_UNUSED)')
- self.h(' * {')
- # "#" is special to gtkdoc under some circumstances; it appears
- # that escaping "##" as "#<!---->#" or "&#35;&#35;" doesn't work,
- # but adding an extra hash symbol does. Thanks, gtkdoc :-(
- self.h(' * #define IMPLEMENT(x) %s%s_implement_###x (\\'
+ self.d(' *')
+ self.d(' * In a full implementation of this interface (i.e. all')
+ self.d(' * methods implemented), the interface initialization')
+ self.d(' * function used in G_IMPLEMENT_INTERFACE() would')
+ self.d(' * typically look like this:')
+ self.d(' *')
+ self.d(' * <programlisting>')
+ self.d(' * static void')
+ self.d(' * implement_%s (gpointer klass,' % self.node_name_lc)
+ self.d(' * gpointer unused G_GNUC_UNUSED)')
+ self.d(' * {')
+ self.d(' * #define IMPLEMENT(x) %s%s_implement_&num;&num;x (\\'
% (self.prefix_, self.node_name_lc))
- self.h(' * klass, my_object_###x)')
+ self.d(' * klass, my_object_&num;&num;x)')
for method in methods:
class_member_name = method.getAttribute('tp:name-for-bindings')
class_member_name = class_member_name.lower()
- self.h(' * IMPLEMENT (%s);' % class_member_name)
+ self.d(' * IMPLEMENT (%s);' % class_member_name)
- self.h(' * #undef IMPLEMENT')
- self.h(' * }')
- self.h(' * </programlisting>')
+ self.d(' * #undef IMPLEMENT')
+ self.d(' * }')
+ self.d(' * </programlisting>')
else:
- self.h(' * This interface has no D-Bus methods, so an')
- self.h(' * implementation can typically pass %NULL to')
- self.h(' * G_IMPLEMENT_INTERFACE() as the interface')
- self.h(' * initialization function.')
+ self.d(' * This interface has no D-Bus methods, so an')
+ self.d(' * implementation can typically pass %NULL to')
+ self.d(' * G_IMPLEMENT_INTERFACE() as the interface')
+ self.d(' * initialization function.')
- self.h(' */')
+ self.d(' */')
+ self.d('')
self.h('typedef struct _%s%sClass %s%sClass;'
% (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
@@ -260,6 +282,16 @@ class Generator(object):
flags = ('TP_DBUS_PROPERTIES_MIXIN_FLAG_READ | '
'TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE')
+ prop_emits_changed = get_emits_changed(m)
+
+ if prop_emits_changed is None:
+ prop_emits_changed = iface_emits_changed
+
+ if prop_emits_changed == 'true':
+ flags += ' | TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_CHANGED'
+ elif prop_emits_changed == 'invalidates':
+ flags += ' | TP_DBUS_PROPERTIES_MIXIN_FLAG_EMITS_INVALIDATED'
+
self.b(' { 0, %s, "%s", 0, NULL, NULL }, /* %s */'
% (flags, m.getAttribute('type'), m.getAttribute('name')))
@@ -477,18 +509,19 @@ class Generator(object):
else:
out_args.append(struct)
- # Implementation type declaration (in header, docs in body)
- self.b('/**')
- self.b(' * %s:' % impl_name)
- self.b(' * @self: The object implementing this interface')
+ # Implementation type declaration (in header, docs separated)
+ self.d('/**')
+ self.d(' * %s:' % impl_name)
+ self.d(' * @self: The object implementing this interface')
for (ctype, name) in in_args:
- self.b(' * @%s: %s (FIXME, generate documentation)'
+ self.d(' * @%s: %s (FIXME, generate documentation)'
% (name, ctype))
- self.b(' * @context: Used to return values or throw an error')
- self.b(' *')
- self.b(' * The signature of an implementation of the D-Bus method')
- self.b(' * %s on interface %s.' % (dbus_method_name, self.iface_name))
- self.b(' */')
+ self.d(' * @context: Used to return values or throw an error')
+ self.d(' *')
+ self.d(' * The signature of an implementation of the D-Bus method')
+ self.d(' * %s on interface %s.' % (dbus_method_name, self.iface_name))
+ self.d(' */')
+
self.h('typedef void (*%s) (%s%s *self,'
% (impl_name, self.Prefix, self.node_name_mixed))
for (ctype, name) in in_args:
@@ -533,18 +566,19 @@ class Generator(object):
% (self.prefix_, self.node_name_lc, class_member_name,
self.Prefix, self.node_name_mixed, impl_name))
- self.b('/**')
- self.b(' * %s%s_implement_%s:'
+ self.d('/**')
+ self.d(' * %s%s_implement_%s:'
% (self.prefix_, self.node_name_lc, class_member_name))
- self.b(' * @klass: A class whose instances implement this interface')
- self.b(' * @impl: A callback used to implement the %s D-Bus method'
+ self.d(' * @klass: A class whose instances implement this interface')
+ self.d(' * @impl: A callback used to implement the %s D-Bus method'
% dbus_method_name)
- self.b(' *')
- self.b(' * Register an implementation for the %s method in the vtable'
+ self.d(' *')
+ self.d(' * Register an implementation for the %s method in the vtable'
% dbus_method_name)
- self.b(' * of an implementation of this interface. To be called from')
- self.b(' * the interface init function.')
- self.b(' */')
+ self.d(' * of an implementation of this interface. To be called from')
+ self.d(' * the interface init function.')
+ self.d(' */')
+
self.b('void')
self.b('%s%s_implement_%s (%s%sClass *klass, %s impl)'
% (self.prefix_, self.node_name_lc, class_member_name,
@@ -555,16 +589,18 @@ class Generator(object):
self.b('')
# Return convenience function (static inline, in header)
- self.h('/**')
- self.h(' * %s:' % ret_name)
- self.h(' * @context: The D-Bus method invocation context')
+ self.d('/**')
+ self.d(' * %s:' % ret_name)
+ self.d(' * @context: The D-Bus method invocation context')
for (ctype, name) in out_args:
- self.h(' * @%s: %s (FIXME, generate documentation)'
+ self.d(' * @%s: %s (FIXME, generate documentation)'
% (name, ctype))
- self.h(' *')
- self.h(' * Return successfully by calling dbus_g_method_return().')
- self.h(' * This inline function exists only to provide type-safety.')
- self.h(' */')
+ self.d(' *')
+ self.d(' * Return successfully by calling dbus_g_method_return().')
+ self.d(' * This inline function exists only to provide type-safety.')
+ self.d(' */')
+ self.d('')
+
tmp = (['DBusGMethodInvocation *context'] +
[ctype + name for (ctype, name) in out_args])
self.h('static inline')
@@ -634,17 +670,17 @@ class Generator(object):
# FIXME: emit docs
- self.b('/**')
- self.b(' * %s:' % stub_name)
- self.b(' * @instance: The object implementing this interface')
+ self.d('/**')
+ self.d(' * %s:' % stub_name)
+ self.d(' * @instance: The object implementing this interface')
for (ctype, name, gtype) in args:
- self.b(' * @%s: %s (FIXME, generate documentation)'
+ self.d(' * @%s: %s (FIXME, generate documentation)'
% (name, ctype))
- self.b(' *')
- self.b(' * Type-safe wrapper around g_signal_emit to emit the')
- self.b(' * %s signal on interface %s.'
+ self.d(' *')
+ self.d(' * Type-safe wrapper around g_signal_emit to emit the')
+ self.d(' * %s signal on interface %s.'
% (dbus_name, self.iface_name))
- self.b(' */')
+ self.d(' */')
self.b('void')
self.b(('%s (' % stub_name) + (',\n '.join(tmp)) + ')')
@@ -660,16 +696,20 @@ class Generator(object):
signal_name = dbus_gutils_wincaps_to_uscore(dbus_name).replace('_',
'-')
- in_base_init.append(' /**')
- in_base_init.append(' * %s%s::%s:'
+
+ self.d('/**')
+ self.d(' * %s%s::%s:'
% (self.Prefix, self.node_name_mixed, signal_name))
+ self.d(' * @self: an object')
for (ctype, name, gtype) in args:
- in_base_init.append(' * @%s: %s (FIXME, generate documentation)'
+ self.d(' * @%s: %s (FIXME, generate documentation)'
% (name, ctype))
- in_base_init.append(' *')
- in_base_init.append(' * The %s D-Bus signal is emitted whenever '
+ self.d(' *')
+ self.d(' * The %s D-Bus signal is emitted whenever '
'this GObject signal is.' % dbus_name)
- in_base_init.append(' */')
+ self.d(' */')
+ self.d('')
+
in_base_init.append(' %s_signals[%s] ='
% (self.node_name_lc, const_name))
in_base_init.append(' g_signal_new ("%s",' % signal_name)
@@ -727,6 +767,7 @@ class Generator(object):
self.b('')
open(self.basename + '.h', 'w').write('\n'.join(self.__header))
open(self.basename + '.c', 'w').write('\n'.join(self.__body))
+ open(self.basename + '-gtk-doc.h', 'w').write('\n'.join(self.__docs))
def cmdline_error():
diff --git a/tools/glib-gtypes-generator.py b/tools/glib-gtypes-generator.py
index ebc2ad4c9..a49c36e7f 100644
--- a/tools/glib-gtypes-generator.py
+++ b/tools/glib-gtypes-generator.py
@@ -44,8 +44,9 @@ class GTypesGenerator(object):
self.header = open(output + '.h', 'w')
self.body = open(output + '-body.h', 'w')
+ self.docs = open(output + '-gtk-doc.h', 'w')
- for f in (self.header, self.body):
+ for f in (self.header, self.body, self.docs):
f.write('/* Auto-generated, do not edit.\n *\n'
' * This file may be distributed under the same terms\n'
' * as the specification from which it was generated.\n'
@@ -70,6 +71,9 @@ class GTypesGenerator(object):
def c(self, code):
self.body.write(code.encode("utf-8"))
+ def d(self, code):
+ self.docs.write(code.encode('utf-8'))
+
def do_mapping_header(self, mapping):
members = mapping.getElementsByTagNameNS(NS_TP, 'member')
assert len(members) == 2
@@ -85,41 +89,41 @@ class GTypesGenerator(object):
docstring = get_docstring(mapping) or '(Undocumented)'
- self.h('/**\n * %s:\n *\n' % name)
- self.h(' * %s\n' % xml_escape(docstring))
- self.h(' *\n')
- self.h(' * This macro expands to a call to a function\n')
- self.h(' * that returns the #GType of a #GHashTable\n')
- self.h(' * appropriate for representing a D-Bus\n')
- self.h(' * dictionary of signature\n')
- self.h(' * <literal>a{%s}</literal>.\n' % impl_sig)
- self.h(' *\n')
+ self.d('/**\n * %s:\n *\n' % name)
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+ self.d(' * This macro expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GHashTable\n')
+ self.d(' * appropriate for representing a D-Bus\n')
+ self.d(' * dictionary of signature\n')
+ self.d(' * <literal>a{%s}</literal>.\n' % impl_sig)
+ self.d(' *\n')
key, value = members
- self.h(' * Keys (D-Bus type <literal>%s</literal>,\n'
+ self.d(' * Keys (D-Bus type <literal>%s</literal>,\n'
% key.getAttribute('type'))
tp_type = key.getAttributeNS(NS_TP, 'type')
if tp_type:
- self.h(' * type <literal>%s</literal>,\n' % tp_type)
- self.h(' * named <literal>%s</literal>):\n'
+ self.d(' * type <literal>%s</literal>,\n' % tp_type)
+ self.d(' * named <literal>%s</literal>):\n'
% key.getAttribute('name'))
docstring = get_docstring(key) or '(Undocumented)'
- self.h(' * %s\n' % xml_escape(docstring))
- self.h(' *\n')
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
- self.h(' * Values (D-Bus type <literal>%s</literal>,\n'
+ self.d(' * Values (D-Bus type <literal>%s</literal>,\n'
% value.getAttribute('type'))
tp_type = value.getAttributeNS(NS_TP, 'type')
if tp_type:
- self.h(' * type <literal>%s</literal>,\n' % tp_type)
- self.h(' * named <literal>%s</literal>):\n'
+ self.d(' * type <literal>%s</literal>,\n' % tp_type)
+ self.d(' * named <literal>%s</literal>):\n'
% value.getAttribute('name'))
docstring = get_docstring(value) or '(Undocumented)'
- self.h(' * %s\n' % xml_escape(docstring))
- self.h(' *\n')
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
- self.h(' */\n')
+ self.d(' */\n')
self.h('#define %s (%s ())\n\n' % (name, impl))
self.need_mappings[impl_sig] = esc_impl_sig
@@ -130,11 +134,12 @@ class GTypesGenerator(object):
contents_sig = 'a{' + impl_sig + '}'
esc_contents_sig = escape_as_identifier(contents_sig)
impl = self.prefix_ + 'type_dbus_array_of_' + esc_contents_sig
- self.h('/**\n * %s:\n\n' % gtype_name)
- self.h(' * Expands to a call to a function\n')
- self.h(' * that returns the #GType of a #GPtrArray\n')
- self.h(' * of #%s.\n' % name)
- self.h(' */\n')
+ self.d('/**\n * %s:\n\n' % gtype_name)
+ self.d(' * Expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GPtrArray\n')
+ self.d(' * of #%s.\n' % name)
+ self.d(' */\n\n')
+
self.h('#define %s (%s ())\n\n' % (gtype_name, impl))
self.need_other_arrays[contents_sig] = esc_contents_sig
@@ -157,41 +162,43 @@ class GTypesGenerator(object):
docstring = '(Undocumented)'
else:
docstring = '(Undocumented)'
- self.h('/**\n * %s:\n\n' % name)
- self.h(' * %s\n' % xml_escape(docstring))
- self.h(' *\n')
- self.h(' * This macro expands to a call to a function\n')
- self.h(' * that returns the #GType of a #GValueArray\n')
- self.h(' * appropriate for representing a D-Bus struct\n')
- self.h(' * with signature <literal>(%s)</literal>.\n'
+ self.d('/**\n * %s:\n\n' % name)
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+ self.d(' * This macro expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GValueArray\n')
+ self.d(' * appropriate for representing a D-Bus struct\n')
+ self.d(' * with signature <literal>(%s)</literal>.\n'
% impl_sig)
- self.h(' *\n')
+ self.d(' *\n')
for i, member in enumerate(members):
- self.h(' * Member %d (D-Bus type '
+ self.d(' * Member %d (D-Bus type '
'<literal>%s</literal>,\n'
% (i, member.getAttribute('type')))
tp_type = member.getAttributeNS(NS_TP, 'type')
if tp_type:
- self.h(' * type <literal>%s</literal>,\n' % tp_type)
- self.h(' * named <literal>%s</literal>):\n'
+ self.d(' * type <literal>%s</literal>,\n' % tp_type)
+ self.d(' * named <literal>%s</literal>):\n'
% member.getAttribute('name'))
docstring = get_docstring(member) or '(Undocumented)'
- self.h(' * %s\n' % xml_escape(docstring))
- self.h(' *\n')
+ self.d(' * %s\n' % xml_escape(docstring))
+ self.d(' *\n')
+
+ self.d(' */\n\n')
- self.h(' */\n')
self.h('#define %s (%s ())\n\n' % (name, impl))
array_name = struct.getAttribute('array-name')
if array_name != '':
array_name = (self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper())
impl = self.prefix_ + 'type_dbus_array_' + esc_impl_sig
- self.h('/**\n * %s:\n\n' % array_name)
- self.h(' * Expands to a call to a function\n')
- self.h(' * that returns the #GType of a #GPtrArray\n')
- self.h(' * of #%s.\n' % name)
- self.h(' */\n')
+ self.d('/**\n * %s:\n\n' % array_name)
+ self.d(' * Expands to a call to a function\n')
+ self.d(' * that returns the #GType of a #GPtrArray\n')
+ self.d(' * of #%s.\n' % name)
+ self.d(' */\n\n')
+
self.h('#define %s (%s ())\n\n' % (array_name, impl))
self.need_struct_arrays[impl_sig] = esc_impl_sig
diff --git a/tools/glib-interfaces-gen.py b/tools/glib-interfaces-gen.py
index 95439687e..69c721be3 100644
--- a/tools/glib-interfaces-gen.py
+++ b/tools/glib-interfaces-gen.py
@@ -9,8 +9,13 @@ from libglibcodegen import NS_TP, get_docstring, \
class Generator(object):
def __init__(self, prefix, implfile, declfile, dom):
self.prefix = prefix + '_'
+
+ assert declfile.endswith('.h')
+ docfile = declfile[:-2] + '-gtk-doc.h'
+
self.impls = open(implfile, 'w')
self.decls = open(declfile, 'w')
+ self.docs = open(docfile, 'w')
self.spec = get_by_path(dom, "spec")[0]
def h(self, code):
@@ -19,6 +24,9 @@ class Generator(object):
def c(self, code):
self.impls.write(code.encode('utf-8'))
+ def d(self, code):
+ self.docs.write(code.encode('utf-8'))
+
def __call__(self):
for f in self.h, self.c:
self.do_header(f)
@@ -50,25 +58,37 @@ class Generator(object):
def do_iface(self, iface):
parent_name = get_by_path(iface, '../@name')
- self.h("""\
+ self.d("""\
/**
* %(IFACE_DEFINE)s:
*
* The interface name "%(name)s"
*/
+""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \
+ parent_name).upper().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.h("""
#define %(IFACE_DEFINE)s \\
"%(name)s"
""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \
parent_name).upper().replace('/', ''),
'name' : iface.getAttribute('name')})
- self.h("""
+ self.d("""
/**
* %(IFACE_QUARK_DEFINE)s:
*
* Expands to a call to a function that returns a quark for the interface \
name "%(name)s"
*/
+""" % {'IFACE_QUARK_DEFINE' : (self.prefix + 'IFACE_QUARK_' + \
+ parent_name).upper().replace('/', ''),
+ 'iface_quark_func' : (self.prefix + 'iface_quark_' + \
+ parent_name).lower().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.h("""
#define %(IFACE_QUARK_DEFINE)s \\
(%(iface_quark_func)s ())
@@ -99,12 +119,20 @@ GQuark
'name' : iface.getAttribute('name')})
for prop in iface.getElementsByTagNameNS(None, 'property'):
- self.decls.write("""
+ self.d("""
/**
* %(IFACE_PREFIX)s_%(PROP_UC)s:
*
* The fully-qualified property name "%(name)s.%(prop)s"
*/
+""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \
+ parent_name).upper().replace('/', ''),
+ 'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ self.h("""
#define %(IFACE_PREFIX)s_%(PROP_UC)s \\
"%(name)s.%(prop)s"
""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \
@@ -114,6 +142,56 @@ GQuark
'prop' : prop.getAttribute('name'),
})
+
+ for prop in iface.getElementsByTagNameNS(NS_TP, 'contact-attribute'):
+ self.d("""
+/**
+ * %(TOKEN_PREFIX)s_%(TOKEN_UC)s:
+ *
+ * The fully-qualified contact attribute token name "%(name)s/%(prop)s"
+ */
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ self.h("""
+#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\
+"%(name)s/%(prop)s"
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ for prop in iface.getElementsByTagNameNS(NS_TP, 'hct'):
+ if (prop.getAttribute('is-family') != "yes"):
+ self.d("""
+/**
+ * %(TOKEN_PREFIX)s_%(TOKEN_UC)s:
+ *
+ * The fully-qualified capability token name "%(name)s/%(prop)s"
+ */
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+ self.h("""
+#define %(TOKEN_PREFIX)s_%(TOKEN_UC)s \\
+"%(name)s/%(prop)s"
+""" % {'TOKEN_PREFIX' : (self.prefix + 'TOKEN_' + \
+ parent_name).upper().replace('/', ''),
+ 'TOKEN_UC': prop.getAttributeNS(None, "name").upper().replace("-", "_").replace(".", "_"),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
if __name__ == '__main__':
argv = argv[1:]
Generator(argv[0], argv[1], argv[2], xml.dom.minidom.parse(argv[3]))()