From fcee1ff2856eda4c8f114ad7855e7fe19653cfdb Mon Sep 17 00:00:00 2001
From: David Woodhouse <dwmw@amazon.co.uk>
Date: Thu, 2 Nov 2017 14:55:26 +0000
Subject: [PATCH] Dummy conference hack to nullprpl

---
 libpurple/media/backend-fs2.c                      |  12 +
 libpurple/protocols/null/Makefile.am               |   7 +-
 .../protocols/null/fs-app-stream-transmitter.c     | 600 +++++++++++++
 .../protocols/null/fs-app-stream-transmitter.h     |  98 +++
 libpurple/protocols/null/fs-app-transmitter.c      | 941 +++++++++++++++++++++
 libpurple/protocols/null/fs-app-transmitter.h      | 125 +++
 libpurple/protocols/null/nullprpl.c                |  54 +-
 7 files changed, 1832 insertions(+), 5 deletions(-)
 create mode 100644 libpurple/protocols/null/fs-app-stream-transmitter.c
 create mode 100644 libpurple/protocols/null/fs-app-stream-transmitter.h
 create mode 100644 libpurple/protocols/null/fs-app-transmitter.c
 create mode 100644 libpurple/protocols/null/fs-app-transmitter.h

diff --git a/libpurple/media/backend-fs2.c b/libpurple/media/backend-fs2.c
index 47ba4bc..2edfc84 100644
--- a/libpurple/media/backend-fs2.c
+++ b/libpurple/media/backend-fs2.c
@@ -994,6 +994,8 @@ gst_handle_message_element(GstBus *bus, GstMessage *msg,
 	static guint level_id = 0;
 	const GstStructure *structure = gst_message_get_structure(msg);
 
+	printf("%s %s\n", __func__, gst_structure_get_name(structure));
+	
 	if (level_id == 0)
 		level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);
 
@@ -1588,6 +1590,7 @@ stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
 		gchar *sid, gchar *name, gboolean local,
 		PurpleMediaBackendFs2 *self)
 {
+	printf("stream_info_cb %d\n", type);
 	if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
 		PurpleMediaBackendFs2Stream *stream =
 				get_stream(self, sid, name);
@@ -2868,6 +2871,15 @@ purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
 	PurpleMediaBackendFs2Session *session;
 	FsDTMFEvent event;
 
+		printf("DUMP DOT FILE\n");
+		GstElement *pipeline;
+		PurpleMediaBackendFs2Private *priv =
+				PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
+
+		pipeline = purple_media_manager_get_pipeline(
+				purple_media_get_manager(priv->media));
+		GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "conference graph");
+
 	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
 
 	session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
diff --git a/libpurple/protocols/null/Makefile.am b/libpurple/protocols/null/Makefile.am
index 3bc76ee..b91475c 100644
--- a/libpurple/protocols/null/Makefile.am
+++ b/libpurple/protocols/null/Makefile.am
@@ -4,7 +4,9 @@ EXTRA_DIST = \
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
-NULLSOURCES = nullprpl.c
+NULLSOURCES = nullprpl.c \
+	fs-app-stream-transmitter.c fs-app-stream-transmitter.h \
+	fs-app-transmitter.c fs-app-transmitter.h
 
 AM_CFLAGS = $(st)
 
@@ -14,10 +16,11 @@ libnull_la_LDFLAGS = -module -avoid-version
 st =
 pkg_LTLIBRARIES    = libnull.la
 libnull_la_SOURCES = $(NULLSOURCES)
-libnull_la_LIBADD  = $(GLIB_LIBS)
+libnull_la_LIBADD  = $(GLIB_LIBS) $(FARSTREAM_LIBS)
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
+	$(FARSTREAM_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS)
diff --git a/libpurple/protocols/null/fs-app-stream-transmitter.c b/libpurple/protocols/null/fs-app-stream-transmitter.c
new file mode 100644
index 0000000..d174f80
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-stream-transmitter.c
@@ -0,0 +1,600 @@
+/*
+ * Farstream - Farstream Shared Memory Stream Transmitter
+ *
+ * Copyright 2009 Collabora Ltd.
+ *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ * Copyright 2009 Nokia Corp.
+ *
+ * fs-app-stream-transmitter.c - A Farstream GstAppShared memory stream transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+
+/**
+ * SECTION:fs-app-stream-transmitter
+ * @short_description: A stream transmitter object for Shared Memory
+ *
+ * The name of this transmitter is "app".
+ *
+ * This transmitter is meant to send and received the data from another process
+ * on the same system while minimizing the memory pressure associated with the
+ * use of sockets.
+ *
+ * Two sockets are used to control the shared memory areas. One is used to
+ * send data and one to receive data. The receiver always connects to the
+ * sender. The sender socket must exist before the receiver connects to it.
+ *
+ * Negotiating the paths of the sockets can happen in two ways. If the
+ * create-local-candidates is True then the transmitter will generate the
+ * path of the local candidate and us it as the ip filed in #FsCandidate. The
+ * transmitter will expect the path of the applications sender socket to be in
+ * the "ip" field of the remote candidates #FsCandidate as well.
+ *
+ * Or alternatively, if create-local-candidates is false then
+ * the sender socket can be created by giving the transmitter a candidate
+ * with the path of the socket in the "ip" field of the #FsCandidate. This
+ * #FsCandidate can be given to the #FsStreamTransmitter in two ways, either
+ * by setting the #FsStreamTransmitter:preferred-local-candidates property
+ * or by calling the fs_stream_transmitter_force_remote_candidates() function.
+ * There can be only one single send socket per stream. When the send socket
+ * is ready to be connected to, #FsStreamTransmitter::new-local-candidate signal
+ * will be emitted.
+ *
+ * To connect the receive side to the other application, one must create a
+ * #FsCandidate with the path of the sender's socket in the "username" field.
+ * If the receiver can not connect to the sender,
+ * the fs_stream_transmitter_force_remote_candidates() call will fail.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fs-app-stream-transmitter.h"
+#include "fs-app-transmitter.h"
+
+#include <farstream/fs-candidate.h>
+#include <farstream/fs-conference.h>
+
+#include <gst/gst.h>
+
+#include <glib/gstdio.h>
+
+#include <string.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdlib.h>
+
+GST_DEBUG_CATEGORY_EXTERN (fs_app_transmitter_debug);
+#define GST_CAT_DEFAULT fs_app_transmitter_debug
+
+/* Signals */
+enum
+{
+  LAST_SIGNAL
+};
+
+/* props */
+enum
+{
+  PROP_0,
+  PROP_SENDING,
+  PROP_PREFERRED_LOCAL_CANDIDATES,
+  PROP_CREATE_LOCAL_CANDIDATES,
+};
+
+struct _FsAppStreamTransmitterPrivate
+{
+  /* We don't actually hold a ref to this,
+   * But since our parent FsStream can not exist without its parent
+   * FsSession, we should be safe
+   */
+  FsAppTransmitter *transmitter;
+
+  GList *preferred_local_candidates;
+
+  GMutex mutex;
+
+  /* Protected by the mutex */
+  gboolean sending;
+
+  /* Protected by the mutex */
+  FsCandidate **candidates;
+
+  /* Whether we create the local candidate ourselves or rely on the remote end
+   * to pass them to us as part of the candidate */
+  gboolean create_local_candidates;
+
+  /* temporary socket directy in case we made one */
+  gchar *socket_dir;
+
+  AppSrc **app_src;
+  AppSink **app_sink;
+};
+
+#define FS_APP_STREAM_TRANSMITTER_GET_PRIVATE(o)  \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                                FsAppStreamTransmitterPrivate))
+
+#define FS_APP_STREAM_TRANSMITTER_LOCK(s) \
+  g_mutex_lock (&(s)->priv->mutex)
+#define FS_APP_STREAM_TRANSMITTER_UNLOCK(s) \
+  g_mutex_unlock (&(s)->priv->mutex)
+
+static void fs_app_stream_transmitter_class_init (FsAppStreamTransmitterClass *klass);
+static void fs_app_stream_transmitter_init (FsAppStreamTransmitter *self);
+static void fs_app_stream_transmitter_dispose (GObject *object);
+static void fs_app_stream_transmitter_finalize (GObject *object);
+
+static void fs_app_stream_transmitter_get_property (GObject *object,
+                                                guint prop_id,
+                                                GValue *value,
+                                                GParamSpec *pspec);
+static void fs_app_stream_transmitter_set_property (GObject *object,
+                                                guint prop_id,
+                                                const GValue *value,
+                                                GParamSpec *pspec);
+
+static gboolean fs_app_stream_transmitter_force_remote_candidates (
+    FsStreamTransmitter *streamtransmitter, GList *candidates,
+    GError **error);
+static gboolean fs_app_stream_transmitter_gather_local_candidates (
+    FsStreamTransmitter *streamtransmitter,
+    GError **error);
+
+static gboolean
+fs_app_stream_transmitter_add_sink (FsAppStreamTransmitter *self,
+    FsCandidate *candidate, GError **error);
+
+
+static GObjectClass *parent_class = NULL;
+// static guint signals[LAST_SIGNAL] = { 0 };
+
+static GType type = 0;
+
+GType
+fs_app_stream_transmitter_get_type (void)
+{
+  return type;
+}
+
+GType
+fs_app_stream_transmitter_register_type (FsPlugin *module G_GNUC_UNUSED)
+{
+  static const GTypeInfo info = {
+    sizeof (FsAppStreamTransmitterClass),
+    NULL,
+    NULL,
+    (GClassInitFunc) fs_app_stream_transmitter_class_init,
+    NULL,
+    NULL,
+    sizeof (FsAppStreamTransmitter),
+    0,
+    (GInstanceInitFunc) fs_app_stream_transmitter_init
+  };
+
+  type = g_type_register_static (FS_TYPE_STREAM_TRANSMITTER,
+      "FsAppStreamTransmitter", &info, 0);
+
+  return type;
+}
+
+static void
+fs_app_stream_transmitter_class_init (FsAppStreamTransmitterClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  FsStreamTransmitterClass *streamtransmitterclass =
+    FS_STREAM_TRANSMITTER_CLASS (klass);
+  GParamSpec *pspec;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->set_property = fs_app_stream_transmitter_set_property;
+  gobject_class->get_property = fs_app_stream_transmitter_get_property;
+
+  streamtransmitterclass->force_remote_candidates =
+    fs_app_stream_transmitter_force_remote_candidates;
+  streamtransmitterclass->gather_local_candidates =
+    fs_app_stream_transmitter_gather_local_candidates;
+
+  g_object_class_override_property (gobject_class, PROP_SENDING, "sending");
+  g_object_class_override_property (gobject_class,
+      PROP_PREFERRED_LOCAL_CANDIDATES, "preferred-local-candidates");
+
+  pspec = g_param_spec_boolean ("create-local-candidates",
+    "CreateLocalCandidates",
+    "Whether the transmitter should automatically create local candidates",
+    TRUE,
+    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (gobject_class,
+    PROP_CREATE_LOCAL_CANDIDATES,
+    pspec);
+
+
+  gobject_class->dispose = fs_app_stream_transmitter_dispose;
+  gobject_class->finalize = fs_app_stream_transmitter_finalize;
+
+  g_type_class_add_private (klass, sizeof (FsAppStreamTransmitterPrivate));
+}
+
+static void
+fs_app_stream_transmitter_init (FsAppStreamTransmitter *self)
+{
+  /* member init */
+  self->priv = FS_APP_STREAM_TRANSMITTER_GET_PRIVATE (self);
+
+  self->priv->sending = TRUE;
+
+  g_mutex_init (&self->priv->mutex);
+}
+
+static void
+fs_app_stream_transmitter_dispose (GObject *object)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+  gint c; /* component_id */
+
+  for (c = 1; c <= self->priv->transmitter->components; c++)
+  {
+    if (self->priv->app_src[c])
+    {
+      fs_app_transmitter_check_app_src (self->priv->transmitter,
+          self->priv->app_src[c], NULL);
+    }
+    self->priv->app_src[c] = NULL;
+
+    if (self->priv->app_sink[c])
+    {
+      fs_app_transmitter_check_app_sink (self->priv->transmitter,
+          self->priv->app_sink[c], NULL);
+    }
+    self->priv->app_sink[c] = NULL;
+  }
+
+  if (self->priv->socket_dir != NULL)
+    g_rmdir (self->priv->socket_dir);
+  g_free (self->priv->socket_dir);
+  self->priv->socket_dir = NULL;
+
+  parent_class->dispose (object);
+}
+
+static void
+fs_app_stream_transmitter_finalize (GObject *object)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+
+  fs_candidate_list_destroy (self->priv->preferred_local_candidates);
+
+  g_free (self->priv->app_src);
+  g_free (self->priv->app_sink);
+  g_mutex_clear (&self->priv->mutex);
+
+  parent_class->finalize (object);
+}
+
+static void
+fs_app_stream_transmitter_get_property (GObject *object,
+                                           guint prop_id,
+                                           GValue *value,
+                                           GParamSpec *pspec)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+
+  switch (prop_id)
+  {
+    case PROP_SENDING:
+      FS_APP_STREAM_TRANSMITTER_LOCK (self);
+      g_value_set_boolean (value, self->priv->sending);
+      FS_APP_STREAM_TRANSMITTER_UNLOCK (self);
+      break;
+    case PROP_PREFERRED_LOCAL_CANDIDATES:
+      g_value_set_boxed (value, self->priv->preferred_local_candidates);
+      break;
+    case PROP_CREATE_LOCAL_CANDIDATES:
+      g_value_set_boolean (value, self->priv->create_local_candidates);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+fs_app_stream_transmitter_set_property (GObject *object,
+                                           guint prop_id,
+                                           const GValue *value,
+                                           GParamSpec *pspec)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+
+  switch (prop_id) {
+    case PROP_SENDING:
+      FS_APP_STREAM_TRANSMITTER_LOCK (self);
+      self->priv->sending = g_value_get_boolean (value);
+      if (self->priv->app_sink[1])
+        fs_app_transmitter_sink_set_sending (self->priv->transmitter,
+            self->priv->app_sink[1], self->priv->sending);
+      FS_APP_STREAM_TRANSMITTER_UNLOCK (self);
+      break;
+    case PROP_PREFERRED_LOCAL_CANDIDATES:
+      self->priv->preferred_local_candidates = g_value_dup_boxed (value);
+      break;
+    case PROP_CREATE_LOCAL_CANDIDATES:
+      self->priv->create_local_candidates = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static gboolean
+fs_app_stream_transmitter_build (FsAppStreamTransmitter *self,
+  GError **error)
+{
+  self->priv->app_src = g_new0 (AppSrc *,
+      self->priv->transmitter->components + 1);
+  self->priv->app_sink = g_new0 (AppSink *,
+      self->priv->transmitter->components + 1);
+
+  return TRUE;
+}
+
+static void
+got_buffer_func (GstBuffer *buffer, guint component, gpointer data)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER_CAST (data);
+
+  g_signal_emit_by_name (self, "known-source-packet-received", component,
+      buffer);
+}
+
+static void ready_cb (guint component, gchar *path, gpointer data)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER_CAST (data);
+  FsCandidate *candidate = fs_candidate_new (NULL, component,
+      FS_CANDIDATE_TYPE_HOST, FS_NETWORK_PROTOCOL_UDP, path, 0);
+
+  GST_DEBUG ("Emitting new local candidate with path %s", path);
+
+  g_signal_emit_by_name (self, "new-local-candidate", candidate);
+  g_signal_emit_by_name (self, "local-candidates-prepared");
+
+  fs_candidate_destroy (candidate);
+}
+
+static void
+connected_cb (guint component, gint id, gpointer data)
+{
+  FsAppStreamTransmitter *self = data;
+  printf("emit state-changed for %p:%u\n", self, component);
+  g_signal_emit_by_name (self, "state-changed", component,
+      FS_STREAM_STATE_READY);
+}
+
+static gboolean
+fs_app_stream_transmitter_add_sink (FsAppStreamTransmitter *self,
+    FsCandidate *candidate, GError **error)
+{
+  if (self->priv->create_local_candidates)
+    return TRUE;
+
+  if (!candidate->ip || !candidate->ip[0])
+    return TRUE;
+
+  if (self->priv->app_sink[candidate->component_id])
+  {
+    if (fs_app_transmitter_check_app_sink (self->priv->transmitter,
+            self->priv->app_sink[candidate->component_id], candidate->ip))
+      return TRUE;
+    self->priv->app_sink[candidate->component_id] = NULL;
+  }
+
+  self->priv->app_sink[candidate->component_id] =
+    fs_app_transmitter_get_app_sink (self->priv->transmitter,
+        candidate->component_id, candidate->ip, ready_cb, connected_cb,
+        self, error);
+
+  if (self->priv->app_sink[candidate->component_id] == NULL)
+    return FALSE;
+
+  if (candidate->component_id == 1) {
+    fs_app_transmitter_sink_set_sending (self->priv->transmitter,
+        self->priv->app_sink[candidate->component_id], self->priv->sending);
+    connected_cb(1, 0, self);
+  }
+  return TRUE;
+}
+
+
+static void
+disconnected_cb (guint component, gint id, gpointer data)
+{
+  FsAppStreamTransmitter *self = data;
+
+  g_signal_emit_by_name (self, "state-changed", component,
+      FS_STREAM_STATE_FAILED);
+}
+
+static gboolean
+fs_app_stream_transmitter_force_remote_candidate (
+    FsAppStreamTransmitter *self, FsCandidate *candidate,
+    GError **error)
+{
+  const gchar *path;
+  if (!fs_app_stream_transmitter_add_sink (self, candidate, error))
+    return FALSE;
+
+  if (self->priv->create_local_candidates)
+    path = candidate->ip;
+  else
+    path = candidate->username;
+
+  if (path && path[0])
+  {
+    if (self->priv->app_src[candidate->component_id])
+    {
+      if (fs_app_transmitter_check_app_src (self->priv->transmitter,
+              self->priv->app_src[candidate->component_id], path))
+        return TRUE;
+      self->priv->app_src[candidate->component_id] = NULL;
+    }
+
+    self->priv->app_src[candidate->component_id] =
+      fs_app_transmitter_get_app_src (self->priv->transmitter,
+          candidate->component_id, path, got_buffer_func, disconnected_cb,
+          self, error);
+
+    if (self->priv->app_src[candidate->component_id] == NULL)
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+ * fs_app_stream_transmitter_force_remote_candidates
+ */
+
+static gboolean
+fs_app_stream_transmitter_force_remote_candidates (
+    FsStreamTransmitter *streamtransmitter, GList *candidates,
+    GError **error)
+{
+  GList *item = NULL;
+  FsAppStreamTransmitter *self =
+    FS_APP_STREAM_TRANSMITTER (streamtransmitter);
+
+  for (item = candidates; item; item = g_list_next (item))
+  {
+    FsCandidate *candidate = item->data;
+
+    if (candidate->component_id == 0 ||
+        candidate->component_id > self->priv->transmitter->components) {
+      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
+          "The candidate passed has an invalid component id %u (not in [1,%u])",
+          candidate->component_id, self->priv->transmitter->components);
+      return FALSE;
+    }
+
+    if ((!candidate->ip || !candidate->ip[0]) &&
+        (!candidate->username || !candidate->username[0]))
+    {
+      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
+          "The candidate does not have a SINK app segment in its ip"
+          " or a SRC app segment in its username");
+      return FALSE;
+    }
+  }
+
+  for (item = candidates; item; item = g_list_next (item))
+    if (!fs_app_stream_transmitter_force_remote_candidate (self,
+            item->data, error))
+      return FALSE;
+
+
+  return TRUE;
+}
+
+
+FsAppStreamTransmitter *
+fs_app_stream_transmitter_newv (FsAppTransmitter *transmitter,
+  guint n_parameters, GParameter *parameters, GError **error)
+{
+  FsAppStreamTransmitter *streamtransmitter = NULL;
+
+  streamtransmitter = g_object_newv (FS_TYPE_APP_STREAM_TRANSMITTER,
+    n_parameters, parameters);
+
+  if (!streamtransmitter) {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+      "Could not build the stream transmitter");
+    return NULL;
+  }
+
+  streamtransmitter->priv->transmitter = transmitter;
+
+  if (!fs_app_stream_transmitter_build (streamtransmitter, error)) {
+    g_object_unref (streamtransmitter);
+    return NULL;
+  }
+
+  return streamtransmitter;
+}
+
+
+static gboolean
+fs_app_stream_transmitter_gather_local_candidates (
+    FsStreamTransmitter *streamtransmitter,
+    GError **error)
+{
+  FsAppStreamTransmitter *self =
+    FS_APP_STREAM_TRANSMITTER (streamtransmitter);
+  GList *item;
+
+  if (self->priv->create_local_candidates)
+  {
+    guint c;
+    gchar *socket_dir;
+
+    socket_dir = g_build_filename (g_get_tmp_dir (),
+      "farstream-app-XXXXXX", NULL);
+
+    if (g_mkdtemp (socket_dir) == NULL)
+      return FALSE;
+
+    self->priv->socket_dir = socket_dir;
+
+    for (c = 1; c <= self->priv->transmitter->components; c++)
+    {
+      gchar *path = g_strdup_printf ("%s/app-sink-socket-%d", socket_dir, c);
+
+      self->priv->app_sink[c] =
+        fs_app_transmitter_get_app_sink (self->priv->transmitter,
+          c, path, ready_cb, connected_cb, self, error);
+      g_free (path);
+
+      if (self->priv->app_sink[c] == NULL)
+        return FALSE;
+
+      if (c == 1) {
+        fs_app_transmitter_sink_set_sending (self->priv->transmitter,
+            self->priv->app_sink[c], self->priv->sending);
+	connected_cb(1, 0, self);
+      }
+    }
+
+    return TRUE;
+  }
+
+  for (item = self->priv->preferred_local_candidates;
+       item;
+       item = g_list_next (item))
+  {
+    FsCandidate *candidate = item->data;
+
+    if (candidate->ip && candidate->ip[0])
+      if (!fs_app_stream_transmitter_add_sink (self, candidate, error))
+        return FALSE;
+  }
+
+  return TRUE;
+}
diff --git a/libpurple/protocols/null/fs-app-stream-transmitter.h b/libpurple/protocols/null/fs-app-stream-transmitter.h
new file mode 100644
index 0000000..049f453
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-stream-transmitter.h
@@ -0,0 +1,98 @@
+/*
+ * Farstream - Farstream Shared Memory Stream Transmitter
+ *
+ * Copyright 2007-2008 Collabora Ltd.
+ *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ * Copyright 2007-2008 Nokia Corp.
+ *
+ * fs-app-stream-transmitter.h - A Farstream Shared Memory stream transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef __FS_APP_STREAM_TRANSMITTER_H__
+#define __FS_APP_STREAM_TRANSMITTER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <farstream/fs-stream-transmitter.h>
+#include <farstream/fs-plugin.h>
+#include "fs-app-transmitter.h"
+
+G_BEGIN_DECLS
+
+/* TYPE MACROS */
+#define FS_TYPE_APP_STREAM_TRANSMITTER \
+  (fs_app_stream_transmitter_get_type ())
+#define FS_APP_STREAM_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                              FsAppStreamTransmitter))
+#define FS_APP_STREAM_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                           FsAppStreamTransmitterClass))
+#define FS_IS_APP_STREAM_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), FS_TYPE_APP_STREAM_TRANSMITTER))
+#define FS_IS_APP_STREAM_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), FS_TYPE_APP_STREAM_TRANSMITTER))
+#define FS_APP_STREAM_TRANSMITTER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                              FsAppStreamTransmitterClass))
+#define FS_APP_STREAM_TRANSMITTER_CAST(obj) ((FsAppStreamTransmitter *) (obj))
+
+typedef struct _FsAppStreamTransmitter FsAppStreamTransmitter;
+typedef struct _FsAppStreamTransmitterClass FsAppStreamTransmitterClass;
+typedef struct _FsAppStreamTransmitterPrivate FsAppStreamTransmitterPrivate;
+
+/**
+ * FsAppStreamTransmitterClass:
+ * @parent_class: Our parent
+ *
+ * The Shared Memory stream transmitter class
+ */
+
+struct _FsAppStreamTransmitterClass
+{
+  FsStreamTransmitterClass parent_class;
+
+  /*virtual functions */
+  /*< private >*/
+};
+
+/**
+ * FsAppStreamTransmitter:
+ * @parent: Parent object
+ *
+ * All members are private, access them using methods and properties
+ */
+struct _FsAppStreamTransmitter
+{
+  FsStreamTransmitter parent;
+
+  /*< private >*/
+  FsAppStreamTransmitterPrivate *priv;
+};
+
+GType fs_app_stream_transmitter_register_type (FsPlugin *module);
+
+GType fs_app_stream_transmitter_get_type (void);
+
+FsAppStreamTransmitter *
+fs_app_stream_transmitter_newv (FsAppTransmitter *transmitter,
+  guint n_parameters, GParameter *parameters, GError **error);
+
+G_END_DECLS
+
+#endif /* __FS_APP_STREAM_TRANSMITTER_H__ */
diff --git a/libpurple/protocols/null/fs-app-transmitter.c b/libpurple/protocols/null/fs-app-transmitter.c
new file mode 100644
index 0000000..a51e445
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-transmitter.c
@@ -0,0 +1,941 @@
+/*
+ * Farstream - Farstream App UDP Transmitter
+ *
+ * Copyright 2007-2008 Collabora Ltd.
+ *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ * Copyright 2007-2008 Nokia Corp.
+ *
+ * fs-app-transmitter.c - A Farstream appsink/appsrc transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/**
+ * SECTION:fs-app-transmitter
+ * @short_description: A transmitter for appsink/appsrc I/O
+ *
+ * This transmitter provides appsink/appsrc I/O
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fs-app-transmitter.h"
+#include "fs-app-stream-transmitter.h"
+
+#include <farstream/fs-conference.h>
+#include <farstream/fs-plugin.h>
+
+#include <string.h>
+#include <stdio.h>
+
+GST_DEBUG_CATEGORY (fs_app_transmitter_debug);
+#define GST_CAT_DEFAULT fs_app_transmitter_debug
+
+/* Signals */
+enum
+{
+  LAST_SIGNAL
+};
+
+/* props */
+enum
+{
+  PROP_0,
+  PROP_GST_SINK,
+  PROP_GST_SRC,
+  PROP_COMPONENTS,
+  PROP_DO_TIMESTAMP,
+};
+
+struct _FsAppTransmitterPrivate
+{
+  /* We hold references to this element */
+  GstElement *gst_sink;
+  GstElement *gst_src;
+
+  /* We don't hold a reference to these elements, they are owned
+     by the bins */
+  /* They are tables of pointers, one per component */
+  GstElement **funnels;
+  GstElement **tees;
+
+  gboolean do_timestamp;
+};
+
+#define FS_APP_TRANSMITTER_GET_PRIVATE(o)  \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_APP_TRANSMITTER,   \
+      FsAppTransmitterPrivate))
+
+static void fs_app_transmitter_class_init (
+    FsAppTransmitterClass *klass);
+static void fs_app_transmitter_init (FsAppTransmitter *self);
+static void fs_app_transmitter_constructed (GObject *object);
+static void fs_app_transmitter_dispose (GObject *object);
+static void fs_app_transmitter_finalize (GObject *object);
+
+static void fs_app_transmitter_get_property (GObject *object,
+                                                guint prop_id,
+                                                GValue *value,
+                                                GParamSpec *pspec);
+static void fs_app_transmitter_set_property (GObject *object,
+                                                guint prop_id,
+                                                const GValue *value,
+                                                GParamSpec *pspec);
+
+static FsStreamTransmitter *fs_app_transmitter_new_stream_transmitter (
+    FsTransmitter *transmitter, FsParticipant *participant,
+    guint n_parameters, GParameter *parameters, GError **error);
+static GType fs_app_transmitter_get_stream_transmitter_type (
+    FsTransmitter *transmitter);
+
+
+static GObjectClass *parent_class = NULL;
+//static guint signals[LAST_SIGNAL] = { 0 };
+
+/*
+ * Private bin subclass
+ */
+
+enum {
+  BIN_SIGNAL_READY,
+  BIN_SIGNAL_DISCONNECTED,
+  BIN_LAST_SIGNAL
+};
+
+static guint bin_signals[BIN_LAST_SIGNAL] = { 0 };
+static GType app_bin_type = 0;
+gpointer app_bin_parent_class = NULL;
+
+typedef struct _FsAppBin
+{
+  GstBin parent;
+} FsAppBin;
+
+typedef struct _FsAppBinClass
+{
+  GstBinClass parent_class;
+} FsAppBinClass;
+
+static void fs_app_bin_init (FsAppBin *self)
+{
+}
+
+static GstElement *
+fs_app_bin_new (void)
+{
+  return g_object_new (app_bin_type, NULL);
+}
+
+static void
+fs_app_bin_handle_message (GstBin *bin, GstMessage *message)
+{
+  GstState old, new, pending;
+  GError *gerror;
+  gchar *msg;
+
+  switch (GST_MESSAGE_TYPE (message))
+  {
+    case GST_MESSAGE_STATE_CHANGED:
+      gst_message_parse_state_changed (message, &old, &new, &pending);
+
+      if (old == GST_STATE_PAUSED && new == GST_STATE_PLAYING)
+        g_signal_emit (bin, bin_signals[BIN_SIGNAL_READY], 0,
+            GST_MESSAGE_SRC (message));
+      break;
+    case GST_MESSAGE_ERROR:
+      gst_message_parse_error (message, &gerror, &msg);
+
+      if (g_error_matches (gerror, GST_RESOURCE_ERROR,
+              GST_RESOURCE_ERROR_READ))
+      {
+        g_signal_emit (bin, bin_signals[BIN_SIGNAL_DISCONNECTED], 0,
+            GST_MESSAGE_SRC (message));
+        gst_message_unref (message);
+        return;
+      }
+      break;
+    default:
+      break;
+  }
+
+  GST_BIN_CLASS (app_bin_parent_class)->handle_message (bin, message);
+}
+
+static void fs_app_bin_class_init (FsAppBinClass *klass)
+{
+  GstBinClass *bin_class = GST_BIN_CLASS (klass);
+
+  app_bin_parent_class = g_type_class_peek_parent (klass);
+
+  bin_signals[BIN_SIGNAL_READY] =
+    g_signal_new ("ready", G_TYPE_FROM_CLASS (klass),
+        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+        g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+  bin_signals[BIN_SIGNAL_DISCONNECTED] =
+    g_signal_new ("disconnected", G_TYPE_FROM_CLASS (klass),
+        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+        g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+  bin_class->handle_message = GST_DEBUG_FUNCPTR (fs_app_bin_handle_message);
+}
+
+/*
+ * Lets register the plugin
+ */
+
+static GType type = 0;
+
+GType
+fs_app_transmitter_get_type (void)
+{
+  g_assert (type);
+  return type;
+}
+
+GType
+fs_app_transmitter_register_type (FsPlugin *module);
+GType
+fs_app_transmitter_register_type (FsPlugin *module)
+{
+  static const GTypeInfo info = {
+    sizeof (FsAppTransmitterClass),
+    NULL,
+    NULL,
+    (GClassInitFunc) fs_app_transmitter_class_init,
+    NULL,
+    NULL,
+    sizeof (FsAppTransmitter),
+    0,
+    (GInstanceInitFunc) fs_app_transmitter_init
+  };
+
+  static const GTypeInfo bin_info = {
+    sizeof (FsAppBinClass),
+    NULL,
+    NULL,
+    (GClassInitFunc) fs_app_bin_class_init,
+    NULL,
+    NULL,
+    sizeof (FsAppBin),
+    0,
+    (GInstanceInitFunc) fs_app_bin_init
+  };
+
+
+  GST_DEBUG_CATEGORY_INIT (fs_app_transmitter_debug,
+      "fsapptransmitter", 0,
+      "Farstream app UDP transmitter");
+
+  fs_app_stream_transmitter_register_type (module);
+
+  type = g_type_register_static (FS_TYPE_TRANSMITTER, "FsAppTransmitter",
+      &info, 0);
+
+  app_bin_type = g_type_register_static (
+    GST_TYPE_BIN, "FsAppBin", &bin_info, 0);
+
+  return type;
+}
+
+static void
+fs_app_transmitter_class_init (FsAppTransmitterClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  FsTransmitterClass *transmitter_class = FS_TRANSMITTER_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->set_property = fs_app_transmitter_set_property;
+  gobject_class->get_property = fs_app_transmitter_get_property;
+
+  gobject_class->constructed = fs_app_transmitter_constructed;
+
+  g_object_class_override_property (gobject_class, PROP_GST_SRC, "gst-src");
+  g_object_class_override_property (gobject_class, PROP_GST_SINK, "gst-sink");
+  g_object_class_override_property (gobject_class, PROP_COMPONENTS,
+    "components");
+  g_object_class_override_property (gobject_class, PROP_DO_TIMESTAMP,
+    "do-timestamp");
+
+  transmitter_class->new_stream_transmitter =
+    fs_app_transmitter_new_stream_transmitter;
+  transmitter_class->get_stream_transmitter_type =
+    fs_app_transmitter_get_stream_transmitter_type;
+
+  gobject_class->dispose = fs_app_transmitter_dispose;
+  gobject_class->finalize = fs_app_transmitter_finalize;
+
+  g_type_class_add_private (klass, sizeof (FsAppTransmitterPrivate));
+}
+
+static void
+fs_app_transmitter_init (FsAppTransmitter *self)
+{
+
+  /* member init */
+  self->priv = FS_APP_TRANSMITTER_GET_PRIVATE (self);
+
+  self->components = 2;
+  self->priv->do_timestamp = TRUE;
+}
+
+static void
+fs_app_transmitter_constructed (GObject *object)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER_CAST (object);
+  FsTransmitter *trans = FS_TRANSMITTER_CAST (self);
+  GstPad *pad = NULL, *pad2 = NULL;
+  GstPad *ghostpad = NULL;
+  gchar *padname;
+  GstPadLinkReturn ret;
+  int c; /* component_id */
+
+
+  /* We waste one space in order to have the index be the component_id */
+  self->priv->funnels = g_new0 (GstElement *, self->components+1);
+  self->priv->tees = g_new0 (GstElement *, self->components+1);
+
+  /* First we need the src elemnet */
+
+  self->priv->gst_src = fs_app_bin_new ();
+
+  if (!self->priv->gst_src) {
+    trans->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_CONSTRUCTION,
+      "Could not build the transmitter src bin");
+    return;
+  }
+
+  gst_object_ref (self->priv->gst_src);
+
+
+  /* Second, we do the sink element */
+
+  self->priv->gst_sink = fs_app_bin_new ();
+
+  if (!self->priv->gst_sink) {
+    trans->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_CONSTRUCTION,
+      "Could not build the transmitter sink bin");
+    return;
+  }
+
+  g_object_set (G_OBJECT (self->priv->gst_sink),
+      "async-handling", TRUE,
+      NULL);
+
+  gst_object_ref (self->priv->gst_sink);
+
+  for (c = 1; c <= self->components; c++) {
+    GstElement *fakesink = NULL;
+
+    /* Lets create the RTP source funnel */
+
+    self->priv->funnels[c] = gst_element_factory_make ("funnel", NULL);
+
+    if (!self->priv->funnels[c]) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not make the funnel element");
+      return;
+    }
+
+    if (!gst_bin_add (GST_BIN (self->priv->gst_src),
+        self->priv->funnels[c])) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not add the funnel element to the transmitter src bin");
+    }
+
+    pad = gst_element_get_static_pad (self->priv->funnels[c], "src");
+    padname = g_strdup_printf ("src_%u", c);
+    ghostpad = gst_ghost_pad_new (padname, pad);
+    g_free (padname);
+    gst_object_unref (pad);
+
+    gst_pad_set_active (ghostpad, TRUE);
+    gst_element_add_pad (self->priv->gst_src, ghostpad);
+
+
+    /* Lets create the RTP sink tee */
+
+    self->priv->tees[c] = gst_element_factory_make ("tee", NULL);
+
+    if (!self->priv->tees[c]) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not make the tee element");
+      return;
+    }
+
+    if (!gst_bin_add (GST_BIN (self->priv->gst_sink),
+        self->priv->tees[c])) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not add the tee element to the transmitter sink bin");
+    }
+
+    pad = gst_element_get_static_pad (self->priv->tees[c], "sink");
+    padname = g_strdup_printf ("sink_%u", c);
+    ghostpad = gst_ghost_pad_new (padname, pad);
+    g_free (padname);
+    gst_object_unref (pad);
+
+    gst_pad_set_active (ghostpad, TRUE);
+    gst_element_add_pad (self->priv->gst_sink, ghostpad);
+
+    fakesink = gst_element_factory_make ("fakesink", NULL);
+
+    if (!fakesink) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not make the fakesink element");
+      return;
+    }
+
+    g_object_set (fakesink,
+        "async", FALSE,
+        "sync" , FALSE,
+        NULL);
+
+    if (!gst_bin_add (GST_BIN (self->priv->gst_sink), fakesink))
+    {
+      gst_object_unref (fakesink);
+      trans->construction_error = g_error_new (FS_ERROR,
+          FS_ERROR_CONSTRUCTION,
+          "Could not add the fakesink element to the transmitter sink bin");
+      return;
+    }
+
+    pad = gst_element_get_request_pad (self->priv->tees[c], "src_%u");
+    pad2 = gst_element_get_static_pad (fakesink, "sink");
+
+    ret = gst_pad_link (pad, pad2);
+
+    gst_object_unref (pad2);
+    gst_object_unref (pad);
+
+    if (GST_PAD_LINK_FAILED(ret)) {
+      trans->construction_error = g_error_new (FS_ERROR,
+          FS_ERROR_CONSTRUCTION,
+          "Could not link the tee to the fakesink");
+      return;
+    }
+  }
+
+  GST_CALL_PARENT (G_OBJECT_CLASS, constructed, (object));
+}
+
+static void
+fs_app_transmitter_dispose (GObject *object)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  if (self->priv->gst_src) {
+    gst_object_unref (self->priv->gst_src);
+    self->priv->gst_src = NULL;
+  }
+
+  if (self->priv->gst_sink) {
+    gst_object_unref (self->priv->gst_sink);
+    self->priv->gst_sink = NULL;
+  }
+
+  parent_class->dispose (object);
+}
+
+static void
+fs_app_transmitter_finalize (GObject *object)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  if (self->priv->funnels) {
+    g_free (self->priv->funnels);
+    self->priv->funnels = NULL;
+  }
+
+  if (self->priv->tees) {
+    g_free (self->priv->tees);
+    self->priv->tees = NULL;
+  }
+
+  parent_class->finalize (object);
+}
+
+static void
+fs_app_transmitter_get_property (GObject *object,
+                             guint prop_id,
+                             GValue *value,
+                             GParamSpec *pspec)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  switch (prop_id) {
+    case PROP_GST_SINK:
+      g_value_set_object (value, self->priv->gst_sink);
+      break;
+    case PROP_GST_SRC:
+      g_value_set_object (value, self->priv->gst_src);
+      break;
+    case PROP_COMPONENTS:
+      g_value_set_uint (value, self->components);
+      break;
+    case PROP_DO_TIMESTAMP:
+      g_value_set_boolean (value, self->priv->do_timestamp);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+fs_app_transmitter_set_property (GObject *object,
+                                    guint prop_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  switch (prop_id) {
+    case PROP_COMPONENTS:
+      self->components = g_value_get_uint (value);
+      break;
+    case PROP_DO_TIMESTAMP:
+      self->priv->do_timestamp = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+/**
+ * fs_app_transmitter_new_stream_app_transmitter:
+ * @transmitter: a #FsTranmitter
+ * @participant: the #FsParticipant for which the #FsStream using this
+ * new #FsStreamTransmitter is created
+ *
+ * This function will create a new #FsStreamTransmitter element for a
+ * specific participant for this #FsAppTransmitter
+ *
+ * Returns: a new #FsStreamTransmitter
+ */
+
+static FsStreamTransmitter *
+fs_app_transmitter_new_stream_transmitter (FsTransmitter *transmitter,
+  FsParticipant *participant, guint n_parameters, GParameter *parameters,
+  GError **error)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (transmitter);
+
+  printf("New App transmitter\n");
+  return FS_STREAM_TRANSMITTER (fs_app_stream_transmitter_newv (
+        self, n_parameters, parameters, error));
+}
+
+static GType
+fs_app_transmitter_get_stream_transmitter_type (
+    FsTransmitter *transmitter)
+{
+  return FS_TYPE_APP_STREAM_TRANSMITTER;
+}
+
+
+struct _AppSrc {
+  guint component;
+  gchar *path;
+  GstElement *src;
+  GstPad *funnelpad;
+
+  got_buffer got_buffer_func;
+  connection disconnected_func;
+  gpointer cb_data;
+  gulong buffer_probe;
+};
+
+
+static GstPadProbeReturn
+src_buffer_probe_cb (GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+{
+  AppSrc *app = user_data;
+  GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);
+
+  app->got_buffer_func (buffer, app->component, app->cb_data);
+
+  return TRUE;
+}
+
+
+static void
+disconnected_cb (GstBin *bin, GstElement *elem, AppSrc *app)
+{
+  if (elem != app->src)
+    return;
+
+  app->disconnected_func (app->component, 0, app->cb_data);
+}
+
+
+AppSrc *
+fs_app_transmitter_get_app_src (FsAppTransmitter *self,
+    guint component,
+    const gchar *path,
+    got_buffer got_buffer_func,
+    connection disconnected_func,
+    gpointer cb_data,
+    GError **error)
+{
+  AppSrc *app = g_slice_new0 (AppSrc);
+  GstElement *elem;
+  GstPad *pad;
+
+  app->component = component;
+  app->got_buffer_func = got_buffer_func;
+  app->disconnected_func = disconnected_func;
+  app->cb_data = cb_data;
+
+  app->path = g_strdup (path);
+
+  elem = gst_element_factory_make ("audiotestsrc", NULL);
+  if (!elem)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not make appsrc");
+    goto error;
+  }
+
+  g_object_set (elem,
+      "socket-path", path,
+      "do-timestamp", self->priv->do_timestamp,
+      "is-live", TRUE,
+      NULL);
+
+  if (app->disconnected_func)
+    g_signal_connect (self->priv->gst_src, "disconnected",
+        G_CALLBACK (disconnected_cb), app);
+
+  if (!gst_bin_add (GST_BIN (self->priv->gst_src), elem))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not add recvonly filter to bin");
+    gst_object_unref (elem);
+    goto error;
+  }
+
+  app->src = elem;
+
+  app->funnelpad = gst_element_get_request_pad (self->priv->funnels[component],
+      "sink_%u");
+
+  if (!app->funnelpad)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not get funnelpad");
+    goto error;
+  }
+
+  pad = gst_element_get_static_pad (app->src, "src");
+  if (GST_PAD_LINK_FAILED (gst_pad_link (pad, app->funnelpad)))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION, "Could not link tee"
+        " and valve");
+    gst_object_unref (pad);
+    goto error;
+  }
+
+  gst_object_unref (pad);
+
+  if (got_buffer_func)
+    app->buffer_probe = gst_pad_add_probe (app->funnelpad,
+        GST_PAD_PROBE_TYPE_BUFFER,
+        src_buffer_probe_cb, app, NULL);
+
+  if (!gst_element_sync_state_with_parent (app->src))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not sync the state of the new appsrc with its parent");
+    goto error;
+  }
+
+  return app;
+
+ error:
+  fs_app_transmitter_check_app_src (self, app, NULL);
+  return NULL;
+}
+
+/*
+ * Returns: %TRUE if the path is the same, other %FALSE and freeds the AppSrc
+ */
+
+gboolean
+fs_app_transmitter_check_app_src (FsAppTransmitter *self, AppSrc *app,
+    const gchar *path)
+{
+  if (path && !strcmp (path, app->path))
+    return TRUE;
+
+  if (app->buffer_probe)
+    gst_pad_remove_probe (app->funnelpad, app->buffer_probe);
+  app->buffer_probe = 0;
+
+  if (app->funnelpad) {
+    gst_element_release_request_pad (self->priv->funnels[app->component],
+        app->funnelpad);
+    gst_object_unref (app->funnelpad);
+  }
+  app->funnelpad = NULL;
+
+  if (app->src)
+  {
+    gst_element_set_locked_state (app->src, TRUE);
+    gst_element_set_state (app->src, GST_STATE_NULL);
+    gst_bin_remove (GST_BIN (self->priv->gst_src), app->src);
+  }
+  app->src = NULL;
+
+  g_free (app->path);
+  g_slice_free (AppSrc, app);
+
+  return FALSE;
+}
+
+
+
+struct _AppSink {
+  guint component;
+  gchar *path;
+  GstElement *sink;
+  GstElement *recvonly_filter;
+  GstPad *teepad;
+
+  ready ready_func;
+  connection connected_func;
+  gpointer cb_data;
+};
+
+
+static void
+ready_cb (GstBin *bin, GstElement *elem, AppSink *app)
+{
+  gchar *path = NULL;
+
+  if (elem != app->sink)
+    return;
+
+  g_object_get (elem, "socket-path", &path, NULL);
+  app->ready_func (app->component, path, app->cb_data);
+  g_free (path);
+}
+
+
+static void
+connected_cb (GstBin *bin, gint id, AppSink *app)
+{
+  app->connected_func (app->component, id, app->cb_data);
+}
+
+AppSink *
+fs_app_transmitter_get_app_sink (FsAppTransmitter *self,
+    guint component,
+    const gchar *path,
+    ready ready_func,
+    connection connected_func,
+    gpointer cb_data,
+    GError **error)
+{
+  AppSink *app = g_slice_new0 (AppSink);
+  GstElement *elem;
+  GstPad *pad;
+
+  GST_DEBUG ("Trying to add app sink for c:%u path %s", component, path);
+
+  app->component = component;
+
+  app->path = g_strdup (path);
+
+  app->ready_func = ready_func;
+  app->connected_func = connected_func;
+  app->cb_data = cb_data;
+
+  /* First add the sink */
+
+  elem = gst_element_factory_make ("filesink", NULL);
+  if (!elem)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not make appsink");
+    goto error;
+  }
+  g_object_set (elem,
+      "location", "/tmp/pidgin.s16",
+      "wait-for-connection", FALSE,
+      "async", FALSE,
+      "sync" , FALSE,
+      NULL);
+
+  if (ready_func)
+    g_signal_connect (self->priv->gst_sink, "ready", G_CALLBACK (ready_cb),
+        app);
+
+  if (connected_func)
+    g_signal_connect (elem, "client-connected", G_CALLBACK (connected_cb), app);
+
+  if (!gst_bin_add (GST_BIN (self->priv->gst_sink), elem))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not add appsink to bin");
+    gst_object_unref (elem);
+    goto error;
+  }
+
+  app->sink = elem;
+
+  /* Second add the recvonly filter */
+
+  elem = gst_element_factory_make ("valve", NULL);
+  if (!elem)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not make valve");
+    goto error;
+  }
+
+  if (!gst_bin_add (GST_BIN (self->priv->gst_sink), elem))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not add recvonly filter to bin");
+    gst_object_unref (elem);
+    goto error;
+  }
+
+  app->recvonly_filter = elem;
+
+  /* Third connect these */
+
+  if (!gst_element_link (app->recvonly_filter, app->sink))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not link recvonly filter and appsink");
+    goto error;
+  }
+
+  if (!gst_element_sync_state_with_parent (app->sink))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not sync the state of the new appsink with its parent");
+    goto error;
+  }
+
+  if (!gst_element_sync_state_with_parent (app->recvonly_filter))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not sync the state of the new recvonly filter  with its parent");
+    goto error;
+  }
+
+  app->teepad = gst_element_get_request_pad (self->priv->tees[component],
+      "src_%u");
+
+  if (!app->teepad)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not get teepad");
+    goto error;
+  }
+
+  pad = gst_element_get_static_pad (app->recvonly_filter, "sink");
+  if (GST_PAD_LINK_FAILED (gst_pad_link (app->teepad, pad)))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION, "Could not link tee"
+        " and valve");
+    gst_object_unref (pad);
+    goto error;
+  }
+  gst_object_unref (pad);
+
+  return app;
+
+ error:
+  fs_app_transmitter_check_app_sink (self, app, NULL);
+
+  return NULL;
+}
+
+gboolean
+fs_app_transmitter_check_app_sink (FsAppTransmitter *self, AppSink *app,
+    const gchar *path)
+{
+  if (path && !strcmp (path, app->path))
+    return TRUE;
+
+  if (path)
+    GST_DEBUG ("Replacing app socket %s with %s", app->path, path);
+  else
+    GST_DEBUG ("Freeing app socket %s", app->path);
+
+  if (app->teepad)
+  {
+    gst_element_release_request_pad (self->priv->tees[app->component],
+        app->teepad);
+    gst_object_unref (app->teepad);
+  }
+  app->teepad = NULL;
+
+  if (app->sink)
+  {
+    gst_element_set_locked_state (app->sink, TRUE);
+    gst_element_set_state (app->sink, GST_STATE_NULL);
+    gst_bin_remove (GST_BIN (self->priv->gst_sink), app->sink);
+  }
+  app->sink = NULL;
+
+  if (app->recvonly_filter)
+  {
+    gst_element_set_locked_state (app->recvonly_filter, TRUE);
+    gst_element_set_state (app->recvonly_filter, GST_STATE_NULL);
+    gst_bin_remove (GST_BIN (self->priv->gst_sink), app->recvonly_filter);
+  }
+  app->recvonly_filter = NULL;
+
+  g_free (app->path);
+  g_slice_free (AppSink, app);
+
+  return FALSE;
+}
+
+
+void
+fs_app_transmitter_sink_set_sending (FsAppTransmitter *self, AppSink *app,
+    gboolean sending)
+{
+  GObjectClass *klass = G_OBJECT_GET_CLASS (app->recvonly_filter);
+
+  if (g_object_class_find_property (klass, "drop"))
+    g_object_set (app->recvonly_filter, "drop", !sending, NULL);
+
+  if (sending)
+    gst_element_send_event (app->sink,
+        gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM,
+            gst_structure_new ("GstForceKeyUnit",
+              "all-headers", G_TYPE_BOOLEAN, TRUE,
+              NULL)));
+}
diff --git a/libpurple/protocols/null/fs-app-transmitter.h b/libpurple/protocols/null/fs-app-transmitter.h
new file mode 100644
index 0000000..1f56ba1
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-transmitter.h
@@ -0,0 +1,125 @@
+/*
+ * Farstream - Farstream Shared Memory Transmitter
+ *
+ * Copyright 2007-2008 Collabora Ltd.
+ *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ * Copyright 2007-2008 Nokia Corp.
+ *
+ * fs-app-transmitter.h - A Farstream Shared Memory transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef __FS_APP_TRANSMITTER_H__
+#define __FS_APP_TRANSMITTER_H__
+
+#include <farstream/fs-transmitter.h>
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+/* TYPE MACROS */
+#define FS_TYPE_APP_TRANSMITTER \
+  (fs_app_transmitter_get_type ())
+#define FS_APP_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), FS_TYPE_APP_TRANSMITTER, \
+    FsAppTransmitter))
+#define FS_APP_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), FS_TYPE_APP_TRANSMITTER, \
+    FsAppTransmitterClass))
+#define FS_IS_APP_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), FS_TYPE_APP_TRANSMITTER))
+#define FS_IS_APP_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), FS_TYPE_APP_TRANSMITTER))
+#define FS_APP_TRANSMITTER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), FS_TYPE_APP_TRANSMITTER, \
+    FsAppTransmitterClass))
+#define FS_APP_TRANSMITTER_CAST(obj) ((FsAppTransmitter *) (obj))
+
+typedef struct _FsAppTransmitter FsAppTransmitter;
+typedef struct _FsAppTransmitterClass FsAppTransmitterClass;
+typedef struct _FsAppTransmitterPrivate FsAppTransmitterPrivate;
+
+/**
+ * FsAppTransmitterClass:
+ * @parent_class: Our parent
+ *
+ * The Shared Memory transmitter class
+ */
+
+struct _FsAppTransmitterClass
+{
+  FsTransmitterClass parent_class;
+};
+
+/**
+ * FsAppTransmitter:
+ * @parent: Parent object
+ *
+ * All members are private, access them using methods and properties
+ */
+struct _FsAppTransmitter
+{
+  FsTransmitter parent;
+
+  /* The number of components (READONLY) */
+  gint components;
+
+  /*< private >*/
+  FsAppTransmitterPrivate *priv;
+};
+
+GType fs_app_transmitter_get_type (void);
+
+typedef struct _AppSrc AppSrc;
+typedef struct _AppSink AppSink;
+
+typedef void (*got_buffer) (GstBuffer *buffer, guint component, gpointer data);
+typedef void (*ready) (guint component, gchar *path, gpointer data);
+typedef void (*connection) (guint component, gint id, gpointer data);
+
+AppSrc *fs_app_transmitter_get_app_src (FsAppTransmitter *self,
+    guint component,
+    const gchar *path,
+    got_buffer got_buffer_func,
+    connection disconnected_func,
+    gpointer cb_data,
+    GError **error);
+
+gboolean fs_app_transmitter_check_app_src (FsAppTransmitter *self,
+    AppSrc *app,
+    const gchar *path);
+
+AppSink *fs_app_transmitter_get_app_sink (FsAppTransmitter *self,
+    guint component,
+    const gchar *path,
+    ready ready_func,
+    connection connected_fubnc,
+    gpointer cb_data,
+    GError **error);
+
+gboolean fs_app_transmitter_check_app_sink (FsAppTransmitter *self,
+    AppSink *app,
+    const gchar *path);
+
+void fs_app_transmitter_sink_set_sending (FsAppTransmitter *self,
+    AppSink *app, gboolean sending);
+
+
+
+G_END_DECLS
+
+#endif /* __FS_APP_TRANSMITTER_H__ */
diff --git a/libpurple/protocols/null/nullprpl.c b/libpurple/protocols/null/nullprpl.c
index e2d8662..8320d4d 100644
--- a/libpurple/protocols/null/nullprpl.c
+++ b/libpurple/protocols/null/nullprpl.c
@@ -204,14 +204,56 @@ static void nullprpl_input_user_info(PurplePluginAction *action)
   purple_account_request_change_user_info(acct);
 }
 
+#include <media.h>
+#include <media-gst.h>
+#include <mediamanager.h>
+
+static void nullprpl_dummy_call(PurplePluginAction *action)
+{
+  PurpleConnection *gc = (PurpleConnection *)action->context;
+  PurpleAccount *acct = purple_connection_get_account(gc);
+
+  const gchar *sess = "dummy session";
+  const gchar *name = "Dummy Call";
+  PurpleMediaManager *mgr = purple_media_manager_get();
+  PurpleMedia *media = purple_media_manager_create_media(mgr,
+							 acct,
+							 "fsrawconference",
+							 name,
+							 TRUE);
+  GList *cands = g_list_append (NULL,
+				purple_media_candidate_new(NULL, 1,
+				   PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
+				   PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+				   "/tmp/src1", 0));
+
+  GList *codecs = g_list_append(NULL,
+				purple_media_codec_new(1, "audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int)44100, channels=(int)1", PURPLE_MEDIA_AUDIO, 0));
+
+  purple_media_add_stream(media, sess, name,
+			  PURPLE_MEDIA_AUDIO, TRUE,
+			  "app", 0, NULL);
+
+  purple_media_add_remote_candidates(media, sess, name, cands);
+  purple_media_set_send_codec(media, sess, codecs->data);
+  purple_media_set_remote_codecs(media, sess, name, codecs);
+  purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT, sess, name, FALSE);
+
+  GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(purple_media_manager_get_pipeline(mgr)), GST_DEBUG_GRAPH_SHOW_ALL, "dummy conference graph");
+}
+
+
 /* this is set to the actions member of the PurplePluginInfo struct at the
  * bottom.
  */
 static GList *nullprpl_actions(PurplePlugin *plugin, gpointer context)
 {
-  PurplePluginAction *action = purple_plugin_action_new(
-    _("Set User Info..."), nullprpl_input_user_info);
-  return g_list_append(NULL, action);
+  GList *l = NULL;
+  l = g_list_append(l, purple_plugin_action_new(
+			_("Set User Info..."), nullprpl_input_user_info));
+  l = g_list_append(l, purple_plugin_action_new(
+			_("Dummy audio call..."), nullprpl_dummy_call));
+  return l;
 }
 
 
@@ -1126,6 +1168,10 @@ static PurplePluginProtocolInfo prpl_info =
   NULL                                 /* add_buddies_with_invite */
 };
 
+#include "fs-app-transmitter.h"
+#include <farstream/fs-plugin.h>
+extern GType fs_app_transmitter_register_type(void *);
+
 static void nullprpl_init(PurplePlugin *plugin)
 {
   /* see accountopt.h for information about user splits and protocol options */
@@ -1159,6 +1205,8 @@ static void nullprpl_init(PurplePlugin *plugin)
                                             g_free,      /* key free fn */
                                             NULL);       /* value free fn */
 
+  fs_plugin_register_static("app", "transmitter", fs_app_transmitter_register_type(NULL));
+
   _null_protocol = plugin;
 }
 
-- 
2.7.4

