propagate from branch 'im.pidgin.pidgin' (head ed811e1aea8072d306d472ee970bd5755e60c0ea) soc.2008.vv

Wed, 16 Jul 2008 21:55:08 +0000

author
Michael Ruprecht <maiku@pidgin.im>
date
Wed, 16 Jul 2008 21:55:08 +0000
branch
soc.2008.vv
changeset 26095
0eea4b8fd48c
parent 23754
e4022bc47232 (diff)
parent 23868
ed811e1aea80 (current diff)
child 26096
7249fdfc2eab

propagate from branch 'im.pidgin.pidgin' (head ed811e1aea8072d306d472ee970bd5755e60c0ea)
to branch 'im.pidgin.soc.2008.vv' (head e4022bc472320aed5b828b40425e436d457d9990)

COPYRIGHT file | annotate | diff | comparison | revisions
ChangeLog.API file | annotate | diff | comparison | revisions
configure.ac file | annotate | diff | comparison | revisions
libpurple/protocols/bonjour/bonjour.c file | annotate | diff | comparison | revisions
libpurple/protocols/gg/gg.c file | annotate | diff | comparison | revisions
libpurple/protocols/irc/irc.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/google.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/jabber.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/presence.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/Makefile.am file | annotate | diff | comparison | revisions
libpurple/protocols/msn/msn.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/soap.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/soap.h file | annotate | diff | comparison | revisions
libpurple/protocols/msn/soap2.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/soap2.h file | annotate | diff | comparison | revisions
libpurple/protocols/silc/silc.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/silc.c file | annotate | diff | comparison | revisions
libpurple/protocols/yahoo/yahoo.c file | annotate | diff | comparison | revisions
libpurple/xmlnode.c file | annotate | diff | comparison | revisions
po/POTFILES.in file | annotate | diff | comparison | revisions
--- a/ChangeLog.API	Wed Jul 16 09:16:52 2008 +0000
+++ b/ChangeLog.API	Wed Jul 16 21:55:08 2008 +0000
@@ -1,5 +1,11 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version ?.?.? (??/??/????):
+	libpurple:
+		Added:
+		* PurpleMedia API
+		* xmlnode_get_parent
+
 version 2.5.0 (??/??/2008):
 	libpurple:
 		Added:
--- a/configure.ac	Wed Jul 16 09:16:52 2008 +0000
+++ b/configure.ac	Wed Jul 16 21:55:08 2008 +0000
@@ -47,7 +47,7 @@
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [5])
 m4_define([purple_micro_version], [0])
-m4_define([purple_version_suffix], [])
+m4_define([purple_version_suffix], [vv-devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
@@ -56,7 +56,7 @@
 m4_define([gnt_major_version], [2])
 m4_define([gnt_minor_version], [5])
 m4_define([gnt_micro_version], [0])
-m4_define([gnt_version_suffix], [])
+m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
 m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix]))
@@ -325,6 +325,9 @@
 AC_SUBST(GLIB_CFLAGS)
 AC_SUBST(GLIB_LIBS)
 
+GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0`
+AC_SUBST(GLIB_GENMARSHAL)
+
 AC_ARG_WITH([extraversion],
 			AC_HELP_STRING([--with-extraversion=STRING],
 						   [extra version number to be displayed in Help->About and --help (for packagers)]),
@@ -746,6 +749,56 @@
 fi
 
 dnl #######################################################################
+dnl # Check for Farsight
+dnl #######################################################################
+AC_ARG_ENABLE(farsight,
+	[AC_HELP_STRING([--disable-farsight], [compile without farsight support])],
+	enable_farsight="$enableval", enable_farsight="yes")
+if test "x$enable_farsight" != "xno"; then
+	PKG_CHECK_MODULES(FARSIGHT, [farsight2-0.10 gstreamer-0.10 gstreamer-plugins-base-0.10 libxml-2.0], [
+		AC_DEFINE(USE_FARSIGHT, 1, [Use Farsight for voice and video])
+		AC_SUBST(FARSIGHT_CFLAGS)
+		AC_SUBST(FARSIGHT_LIBS)
+	], [
+		enable_farsight="no"
+	])
+fi
+
+dnl #######################################################################
+dnl # Check for GStreamer-properties
+dnl #######################################################################
+AC_ARG_ENABLE(gstprops,
+	[AC_HELP_STRING([--disable-gstprops], [compile without gstreamer props])],
+	enable_gstprops="$enableval", enable_gstprops="yes")
+if test "x$enable_gstprops" != "xno";
+then
+  dnl gstreamer-libs-$GST_MAJORMINOR
+  dnl gstreamer-gconf-$GST_MAJORMINOR
+  PKG_CHECK_MODULES(GSTPROPS, [gstreamer-0.10 gstreamer-plugins-base-0.10 libxml-2.0], [
+  		GSTPROPS_LIBS="$GSTPROPS_LIBS -lgstinterfaces-0.10"	   
+  		AC_DEFINE(USE_GSTPROPS, 1, [Use GStreamer property probe for finding devices])
+  		AC_SUBST(GSTPROPS_LIBS)
+  		AC_SUBST(GSTPROPS_CFLAGS)
+  ], [
+		enable_gstprops="no"
+  ])
+fi
+
+dnl #######################################################################
+dnl # Check for Voice and Video support
+dnl #######################################################################
+AC_ARG_ENABLE(vv,
+	[AC_HELP_STRING([--disable-vv], [compile without voice and video support])],
+	enable_vv="$enableval", enable_vv="yes")
+if test "x$enable_vv" != "xno"; then
+	if test "x$enable_farsight" != "xno" -a "x$enable_gstprops" != "xno"; then
+		AC_DEFINE(USE_VV, 1, [Use voice and video])
+	else
+		enable_vv="no"
+	fi
+fi
+
+dnl #######################################################################
 dnl # Check for Meanwhile headers (for Sametime)
 dnl #######################################################################
 AC_ARG_ENABLE(meanwhile,
@@ -2447,6 +2500,7 @@
 echo
 echo Build with GStreamer support.. : $enable_gst
 echo Build with D-Bus support...... : $enable_dbus
+echo Build with voice and video.... : $enable_vv
 if test "x$enable_dbus" = "xyes" ; then
 	eval eval echo D-Bus services directory...... : $DBUS_SERVICES_DIR
 fi
--- a/finch/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -26,6 +26,7 @@
 	finch.c \
 	gntidle.c \
 	gntlog.c \
+	gntmedia.c \
 	gntnotify.c \
 	gntplugin.c \
 	gntpounce.c \
@@ -47,6 +48,7 @@
 	finch.h \
 	gntidle.h \
 	gntlog.h \
+	gntmedia.h \
 	gntnotify.h \
 	gntplugin.h \
 	gntpounce.h \
@@ -69,6 +71,7 @@
 	$(INTLLIBS) \
 	$(GLIB_LIBS) \
 	$(LIBXML_LIBS) \
+	$(FARSIGHT_LIBS) \
 	$(GNT_LIBS) \
 	$(GSTREAMER_LIBS) \
 	./libgnt/libgnt.la \
@@ -88,5 +91,6 @@
 	$(GLIB_CFLAGS) \
 	$(DBUS_CFLAGS) \
 	$(LIBXML_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GSTREAMER_CFLAGS) \
 	$(GNT_CFLAGS)
--- a/finch/gntaccount.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntaccount.c	Wed Jul 16 21:55:08 2008 +0000
@@ -1057,3 +1057,4 @@
 	return &ui_ops;
 }
 
+
--- a/finch/gntdebug.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntdebug.c	Wed Jul 16 21:55:08 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -36,7 +38,6 @@
 #include "gntdebug.h"
 #include "finch.h"
 #include "notify.h"
-#include "util.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -347,6 +348,13 @@
 #ifdef USE_GSTREAMER
 	REGISTER_G_LOG_HANDLER("GStreamer");
 #endif
+#ifdef USE_VV
+#ifdef USE_FARSIGHT
+	REGISTER_G_LOG_HANDLER("farsight");
+	REGISTER_G_LOG_HANDLER("farsight-transmitter");
+	REGISTER_G_LOG_HANDLER("farsight-rtp");
+#endif
+#endif
 
 	g_set_print_handler(print_stderr);   /* Redirect the debug messages to stderr */
 	if (!purple_debug_is_enabled())
--- a/finch/gntft.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntft.c	Wed Jul 16 21:55:08 2008 +0000
@@ -25,6 +25,12 @@
  */
 #include "finch.h"
 
+#include "debug.h"
+#include "notify.h"
+#include "ft.h"
+#include "prpl.h"
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -32,12 +38,6 @@
 #include <gntlabel.h>
 #include <gnttree.h>
 
-#include "debug.h"
-#include "notify.h"
-#include "ft.h"
-#include "prpl.h"
-#include "util.h"
-
 #include "gntft.h"
 #include "prefs.h"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/gntmedia.c	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,467 @@
+/**
+ * @file gntmedia.c GNT Media API
+ * @ingroup finch
+ */
+
+/* finch
+ *
+ * Finch is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "finch.h"
+#include "mediamanager.h"
+
+#include "gntconv.h"
+#include "gntmedia.h"
+
+#include "gnt.h"
+#include "gntbutton.h"
+#include "gntbox.h"
+#include "gntlabel.h"
+
+#include "cmds.h"
+#include "conversation.h"
+#include "debug.h"
+
+/* An incredibly large part of the following is from gtkmedia.c */
+#ifdef USE_VV
+
+#undef hangup
+
+struct _FinchMediaPrivate
+{
+	PurpleMedia *media;
+	GstElement *send_level;
+	GstElement *recv_level;
+
+	GntWidget *accept;
+	GntWidget *reject;
+	GntWidget *hangup;
+	GntWidget *calling;
+
+	PurpleConversation *conv;
+};
+
+#define FINCH_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), FINCH_TYPE_MEDIA, FinchMediaPrivate))
+
+static void finch_media_class_init (FinchMediaClass *klass);
+static void finch_media_init (FinchMedia *media);
+static void finch_media_finalize (GObject *object);
+static void finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static GntBoxClass *parent_class = NULL;
+
+enum {
+	MESSAGE,
+	LAST_SIGNAL
+};
+static guint finch_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_MEDIA,
+	PROP_SEND_LEVEL,
+	PROP_RECV_LEVEL
+};
+
+GType
+finch_media_get_type(void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(FinchMediaClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) finch_media_class_init,
+			NULL,
+			NULL,
+			sizeof(FinchMedia),
+			0,
+			(GInstanceInitFunc) finch_media_init,
+			NULL
+		};
+		type = g_type_register_static(GNT_TYPE_BOX, "FinchMedia", &info, 0);
+	}
+	return type;
+}
+
+
+static void
+finch_media_class_init (FinchMediaClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = finch_media_finalize;
+	gobject_class->set_property = finch_media_set_property;
+	gobject_class->get_property = finch_media_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_MEDIA,
+			g_param_spec_object("media",
+			"PurpleMedia",
+			"The PurpleMedia associated with this media.",
+			PURPLE_TYPE_MEDIA,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_SEND_LEVEL,
+			g_param_spec_object("send-level",
+			"Send level",
+			"The GstElement of this media's send 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_RECV_LEVEL,
+			g_param_spec_object("recv-level",
+			"Receive level",
+			"The GstElement of this media's recv 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	finch_media_signals[MESSAGE] = g_signal_new("message", G_TYPE_FROM_CLASS(klass),
+					G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					g_cclosure_marshal_VOID__STRING,
+					G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	g_type_class_add_private(klass, sizeof(FinchMediaPrivate));
+}
+
+
+static void
+finch_media_init (FinchMedia *media)
+{
+	media->priv = FINCH_MEDIA_GET_PRIVATE(media);
+
+	media->priv->calling = gnt_label_new(_("Calling ... "));
+	media->priv->hangup = gnt_button_new(_("Hangup"));
+	media->priv->accept = gnt_button_new(_("Accept"));
+	media->priv->reject = gnt_button_new(_("Reject"));
+
+	gnt_box_set_alignment(GNT_BOX(media), GNT_ALIGN_MID);
+
+	gnt_box_add_widget(GNT_BOX(media), media->priv->accept);
+	gnt_box_add_widget(GNT_BOX(media), media->priv->reject);
+}
+
+static void
+finch_media_finalize (GObject *media)
+{
+}
+
+static void
+finch_media_emit_message(FinchMedia *gntmedia, const char *msg)
+{
+	g_signal_emit(gntmedia, finch_media_signals[MESSAGE], 0, msg);
+}
+
+static gboolean
+level_message_cb(GstBus *bus, GstMessage *message, FinchMedia *gntmedia)
+{
+	/* XXX: I am hesitant to just remove this function altogether, because I don't
+	 * know how necessary it is to have a callback to 'message'. If it isn't essential,
+	 * I suppose this should be removed.
+	 */
+	return TRUE;
+#if 0
+	const GstStructure *s;
+	const gchar *name;
+
+	int channels;
+	gdouble rms_db, peak_db, decay_db;
+	gdouble rms;
+	const GValue *list;
+	const GValue *value;
+
+	GstElement *src = GST_ELEMENT(message);
+
+	if (message->type != GST_MESSAGE_ELEMENT)
+		return TRUE;
+
+	s = gst_message_get_structure(message);
+	name = gst_structure_get_name(s);
+
+	if (strcmp(name, "level"))
+		return TRUE;
+
+	list = gst_structure_get_value(s, "rms");
+
+	/* Only bother with the first channel. */
+	value = gst_value_list_get_value(list, 0);
+	rms_db = g_value_get_double(value);
+
+	if (!strcmp(gst_element_get_name(src), "sendlevel"))
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gntmedia->priv->send_progress), pow(10, rms_db / 20) * 5);
+	else
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gntmedia->priv->recv_progress), pow(10, rms_db / 20) * 5);
+
+	return TRUE;
+#endif
+}
+
+static void
+finch_media_ready_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	GstElement *element = purple_media_get_pipeline(media);
+
+	GstElement *sendbin, *sendlevel;
+	GstElement *recvbin, *recvlevel;
+
+	GList *sessions = purple_media_get_session_names(media);
+
+	purple_media_audio_init_src(&sendbin, &sendlevel);
+	purple_media_audio_init_recv(&recvbin, &recvlevel);
+
+	for (; sessions; sessions = sessions->next) {
+		purple_media_set_src(media, sessions->data, sendbin);
+		purple_media_set_sink(media, sessions->data, recvbin);
+	}
+	g_list_free(sessions);
+
+	g_object_set(gntmedia, "send-level", &sendlevel,
+		     "recv-level", &recvlevel,
+		     NULL);
+
+	gst_bus_add_signal_watch(GST_BUS(gst_pipeline_get_bus(GST_PIPELINE(element))));
+	g_signal_connect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))), "message", G_CALLBACK(level_message_cb), gntmedia);
+}
+
+static void
+finch_media_accept_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	GntWidget *parent;
+
+	finch_media_emit_message(gntmedia, _("Call in progress."));
+
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->accept);
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->reject);
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->hangup);
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->calling);
+
+	gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->hangup);
+
+	gnt_widget_destroy(gntmedia->priv->accept);
+	gnt_widget_destroy(gntmedia->priv->reject);
+	gnt_widget_destroy(gntmedia->priv->calling);
+	gntmedia->priv->accept = NULL;
+	gntmedia->priv->reject = NULL;
+	gntmedia->priv->calling = NULL;
+
+	parent = GNT_WIDGET(gntmedia);
+	while (parent->parent)
+		parent = parent->parent;
+	gnt_box_readjust(GNT_BOX(parent));
+	gnt_widget_draw(parent);
+}
+
+static void
+finch_media_wait_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	GntWidget *parent;
+
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->accept);
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->reject);
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->hangup);
+	gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->calling);
+
+	gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->calling);
+	gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->hangup);
+
+	gnt_widget_destroy(gntmedia->priv->accept);
+	gnt_widget_destroy(gntmedia->priv->reject);
+	gntmedia->priv->accept = NULL;
+	gntmedia->priv->reject = NULL;
+
+	parent = GNT_WIDGET(gntmedia);
+	while (parent->parent)
+		parent = parent->parent;
+	gnt_box_readjust(GNT_BOX(parent));
+	gnt_widget_draw(parent);
+}
+
+static void
+finch_media_hangup_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	finch_media_emit_message(gntmedia, _("You have ended the call."));
+	finch_conversation_set_info_widget(gntmedia->priv->conv, NULL);
+	gnt_widget_destroy(GNT_WIDGET(gntmedia));
+}
+
+static void
+finch_media_got_hangup_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	finch_media_emit_message(gntmedia, _("The call has been terminated."));
+	finch_conversation_set_info_widget(gntmedia->priv->conv, NULL);
+	gnt_widget_destroy(GNT_WIDGET(gntmedia));
+}
+
+static void
+finch_media_reject_cb(PurpleMedia *media, FinchMedia *gntmedia)
+{
+	finch_media_emit_message(gntmedia, _("You have rejected the call."));
+	finch_conversation_set_info_widget(gntmedia->priv->conv, NULL);
+	gnt_widget_destroy(GNT_WIDGET(gntmedia));
+}
+
+static void
+finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	FinchMedia *media;
+	g_return_if_fail(FINCH_IS_MEDIA(object));
+
+	media = FINCH_MEDIA(object);
+	switch (prop_id) {
+		case PROP_MEDIA:
+			if (media->priv->media)
+				g_object_unref(media->priv->media);
+			media->priv->media = g_value_get_object(value);
+			g_object_ref(media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->accept), "activate",
+				 G_CALLBACK(purple_media_accept), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->reject), "activate",
+				 G_CALLBACK(purple_media_reject), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "activate",
+				 G_CALLBACK(purple_media_hangup), media->priv->media);
+
+			g_signal_connect(G_OBJECT(media->priv->media), "accepted",
+				G_CALLBACK(finch_media_accept_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media) ,"ready",
+				G_CALLBACK(finch_media_ready_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "wait",
+				G_CALLBACK(finch_media_wait_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "hangup",
+				G_CALLBACK(finch_media_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "reject",
+				G_CALLBACK(finch_media_reject_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-hangup",
+				G_CALLBACK(finch_media_got_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-accept",
+				G_CALLBACK(finch_media_accept_cb), media);
+			break;
+		case PROP_SEND_LEVEL:
+			if (media->priv->send_level)
+				gst_object_unref(media->priv->send_level);
+			media->priv->send_level = g_value_get_object(value);
+			g_object_ref(media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			if (media->priv->recv_level)
+				gst_object_unref(media->priv->recv_level);
+			media->priv->recv_level = g_value_get_object(value);
+			g_object_ref(media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	FinchMedia *media;
+	g_return_if_fail(FINCH_IS_MEDIA(object));
+
+	media = FINCH_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_MEDIA:
+			g_value_set_object(value, media->priv->media);
+			break;
+		case PROP_SEND_LEVEL:
+			g_value_set_object(value, media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			g_value_set_object(value, media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+GntWidget *
+finch_media_new(PurpleMedia *media)
+{
+	return GNT_WIDGET(g_object_new(finch_media_get_type(),
+				"media", media,
+				"vertical", FALSE,
+				"homogeneous", FALSE,
+				NULL));
+}
+
+static void
+gntmedia_message_cb(FinchMedia *gntmedia, const char *msg, PurpleConversation *conv)
+{
+	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
+		purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
+	}
+}
+
+static void
+finch_new_media(PurpleMediaManager *manager, PurpleMedia *media, gpointer null)
+{
+	GntWidget *gntmedia;
+	PurpleConversation *conv;
+
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
+			purple_connection_get_account(purple_media_get_connection(media)),
+			purple_media_get_screenname(media));
+
+	gntmedia = finch_media_new(media);
+	g_signal_connect(G_OBJECT(gntmedia), "message", G_CALLBACK(gntmedia_message_cb), conv);
+	FINCH_MEDIA(gntmedia)->priv->conv = conv;
+	finch_conversation_set_info_widget(conv, gntmedia);
+}
+
+static PurpleCmdRet
+call_cmd_cb(PurpleConversation *conv, const char *cmd, char **args,
+		char **eror, gpointer data)
+{
+	PurpleConnection *gc = purple_conversation_get_gc(conv);
+
+	PurpleMedia *media =
+		serv_initiate_media(gc,
+							purple_conversation_get_name(conv),
+							PURPLE_MEDIA_RECV_AUDIO & PURPLE_MEDIA_SEND_AUDIO);
+
+	if (!media)
+		return PURPLE_CMD_STATUS_FAILED;
+
+	purple_media_wait(media);
+	return PURPLE_CMD_STATUS_OK;
+}
+
+void finch_media_manager_init(void)
+{
+	PurpleMediaManager *manager = purple_media_manager_get();
+	g_signal_connect(G_OBJECT(manager), "init-media", G_CALLBACK(finch_new_media), NULL);
+	purple_cmd_register("call", "", PURPLE_CMD_P_DEFAULT,
+			PURPLE_CMD_FLAG_IM, NULL,
+			call_cmd_cb, _("call: Make an audio call."), NULL);
+}
+
+void finch_media_manager_uninit(void)
+{
+	PurpleMediaManager *manager = purple_media_manager_get();
+	g_signal_handlers_disconnect_by_func(G_OBJECT(manager),
+			G_CALLBACK(finch_new_media), NULL);
+}
+
+#endif  /* USE_VV */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/gntmedia.h	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,77 @@
+/**
+ * @file gntmedia.h GNT Media API
+ * @ingroup finch
+ */
+
+/* finch
+ *
+ * Finch is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef GNT_MEDIA_H
+#define GNT_MEDIA_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef USE_VV
+
+#include <glib-object.h>
+#include "gntbox.h"
+
+G_BEGIN_DECLS
+
+#define FINCH_TYPE_MEDIA            (finch_media_get_type())
+#define FINCH_MEDIA(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), FINCH_TYPE_MEDIA, FinchMedia))
+#define FINCH_MEDIA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), FINCH_TYPE_MEDIA, FinchMediaClass))
+#define FINCH_IS_MEDIA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), FINCH_TYPE_MEDIA))
+#define FINCH_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FINCH_TYPE_MEDIA))
+#define FINCH_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), FINCH_TYPE_MEDIA, FinchMediaClass))
+
+typedef struct _FinchMedia FinchMedia;
+typedef struct _FinchMediaClass FinchMediaClass;
+typedef struct _FinchMediaPrivate FinchMediaPrivate;
+typedef enum _FinchMediaState FinchMediaState;
+
+struct _FinchMediaClass
+{
+	GntBoxClass parent_class;
+};
+
+struct _FinchMedia
+{
+	GntBox parent;
+	FinchMediaPrivate *priv;
+};
+
+GType finch_media_get_type(void);
+
+GntWidget *finch_media_new(PurpleMedia *media);
+
+void finch_media_manager_init(void);
+
+void finch_media_manager_uninit(void);
+
+G_END_DECLS
+
+#endif /* USE_VV */
+
+#endif /* GNT_MEDIA_H */
+
--- a/finch/gntnotify.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntnotify.c	Wed Jul 16 21:55:08 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include <util.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -33,7 +35,6 @@
 
 #include "finch.h"
 
-#include <util.h>
 
 #include "gntnotify.h"
 
--- a/finch/gntplugin.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntplugin.c	Wed Jul 16 21:55:08 2008 +0000
@@ -23,6 +23,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include "notify.h"
+#include "request.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -34,8 +37,6 @@
 #include "finch.h"
 
 #include "debug.h"
-#include "notify.h"
-#include "request.h"
 
 #include "gntplugin.h"
 #include "gntrequest.h"
--- a/finch/gntpounce.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntpounce.c	Wed Jul 16 21:55:08 2008 +0000
@@ -24,6 +24,16 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
+#include "internal.h"
+#include "account.h"
+#include "conversation.h"
+#include "debug.h"
+#include "notify.h"
+#include "prpl.h"
+#include "request.h"
+#include "server.h"
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -37,15 +47,6 @@
 
 #include "finch.h"
 
-#include "account.h"
-#include "conversation.h"
-#include "debug.h"
-#include "notify.h"
-#include "prpl.h"
-#include "request.h"
-#include "server.h"
-#include "util.h"
-
 #include "gntpounce.h"
 
 
--- a/finch/gntrequest.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntrequest.c	Wed Jul 16 21:55:08 2008 +0000
@@ -23,6 +23,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "util.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -37,7 +40,6 @@
 #include "finch.h"
 #include "gntrequest.h"
 #include "debug.h"
-#include "util.h"
 
 typedef struct
 {
--- a/finch/gntstatus.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntstatus.c	Wed Jul 16 21:55:08 2008 +0000
@@ -23,6 +23,10 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include <notify.h>
+#include <request.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -35,9 +39,6 @@
 
 #include "finch.h"
 
-#include <notify.h>
-#include <request.h>
-
 #include "gntstatus.h"
 
 static struct
--- a/finch/gntui.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/gntui.c	Wed Jul 16 21:55:08 2008 +0000
@@ -19,9 +19,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include <prefs.h>
 #include "finch.h"
 
-#include "gntui.h"
 
 #include "gntaccount.h"
 #include "gntblist.h"
@@ -31,6 +31,7 @@
 #include "gntdebug.h"
 #include "gntft.h"
 #include "gntlog.h"
+#include "gntmedia.h"
 #include "gntnotify.h"
 #include "gntplugin.h"
 #include "gntpounce.h"
@@ -40,7 +41,7 @@
 #include "gntstatus.h"
 #include "gntsound.h"
 
-#include <prefs.h>
+#include "gntui.h"
 
 void gnt_ui_init()
 {
@@ -91,6 +92,11 @@
 	finch_roomlist_init();
 	purple_roomlist_set_ui_ops(finch_roomlist_get_ui_ops());
 
+#ifdef USE_VV
+	/* Media */
+	finch_media_manager_init();
+#endif
+
 	gnt_register_action(_("Accounts"), finch_accounts_show_all);
 	gnt_register_action(_("Buddy List"), finch_blist_show);
 	gnt_register_action(_("Buddy Pounces"), finch_pounces_manager_show);
@@ -136,6 +142,10 @@
 	finch_roomlist_uninit();
 	purple_roomlist_set_ui_ops(NULL);
 
+#ifdef USE_VV
+	finch_media_manager_uninit();
+#endif
+
 	gnt_quit();
 #endif
 }
--- a/finch/libgnt/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/libgnt/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -89,6 +89,7 @@
 
 AM_CPPFLAGS = \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GNT_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	$(LIBXML_CFLAGS) \
--- a/finch/libgnt/gntkeys.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/libgnt/gntkeys.h	Wed Jul 16 21:55:08 2008 +0000
@@ -165,5 +165,6 @@
 #undef lines
 #undef buttons
 #undef newline
+#undef set_clock
 
 #endif
--- a/finch/libgnt/wms/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/libgnt/wms/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -34,5 +34,6 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(GNT_CFLAGS) \
-	$(PLUGIN_CFLAGS)
+	$(PLUGIN_CFLAGS) \
+	$(FARSIGHT_CFLAGS) 
 
--- a/finch/libgnt/wms/s.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/libgnt/wms/s.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2,6 +2,7 @@
 #include <sys/types.h>
 
 #include "internal.h"
+#include "blist.h"
 
 #include "gnt.h"
 #include "gntbox.h"
@@ -11,7 +12,6 @@
 #include "gntwindow.h"
 #include "gntlabel.h"
 
-#include "blist.h"
 
 #define TYPE_S				(s_get_gtype())
 
--- a/finch/plugins/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/finch/plugins/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -42,6 +42,7 @@
 	-I$(top_srcdir)/finch \
 	-I$(top_srcdir)/finch/libgnt \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(GNT_CFLAGS) \
 	$(PLUGIN_CFLAGS)
--- a/libpurple/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -1,6 +1,7 @@
 EXTRA_DIST = \
 		dbus-analyze-functions.py \
 		dbus-analyze-types.py \
+		marshallers.list \
 		purple-notifications-example \
 		purple-remote \
 		purple-send \
@@ -51,6 +52,9 @@
 	idle.c \
 	imgstore.c \
 	log.c \
+	marshallers.c \
+	media.c \
+	mediamanager.c \
 	mime.c \
 	nat-pmp.c \
 	network.c \
@@ -104,6 +108,9 @@
 	idle.h \
 	imgstore.h \
 	log.h \
+	marshallers.h \
+	media.h \
+	mediamanager.h \
 	mime.h \
 	nat-pmp.h \
 	network.h \
@@ -137,6 +144,15 @@
 
 purple_builtheaders = purple.h version.h
 
+marshallers.h: marshallers.list
+	@echo "Generating marshallers.h"
+	$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --header > marshallers.h
+
+marshallers.c: marshallers.list
+	@echo "Generating marshallers.c"
+	echo "#include \"marshallers.h\"" > marshallers.c
+	$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --body >> marshallers.c
+
 if ENABLE_DBUS
 
 CLEANFILES = \
@@ -145,6 +161,8 @@
 	dbus-client-binding.h \
 	dbus-types.c \
 	dbus-types.h \
+	marshallers.c \
+	marshallers.h \
 	purple-client-bindings.c \
 	purple-client-bindings.h \
 	purple.service
@@ -215,6 +233,8 @@
 	dbus-types.c \
 	dbus-types.h \
 	dbus-bindings.c \
+	marshallers.c \
+	marshallers.h \
 	purple-client-bindings.c \
 	purple-client-bindings.h
 
@@ -248,6 +268,9 @@
 	$(LIBXML_LIBS) \
 	$(NETWORKMANAGER_LIBS) \
 	$(INTLLIBS) \
+	$(FARSIGHT_LIBS) \
+	$(GSTREAMER_LIBS) \
+	$(GSTPROPS_LIBS) \
 	-lm
 
 AM_CPPFLAGS = \
@@ -260,6 +283,9 @@
 	$(DEBUG_CFLAGS) \
 	$(DBUS_CFLAGS) \
 	$(LIBXML_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(GSTPROPS_CFLAGS) \
 	$(NETWORKMANAGER_CFLAGS)
 
 # INSTALL_SSL_CERTIFICATES is true when SSL_CERTIFICATES_DIR is empty.
--- a/libpurple/example/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/example/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -8,6 +8,7 @@
 	$(INTLLIBS) \
 	$(GLIB_LIBS) \
 	$(LIBXML_LIBS) \
+	$(FARSIGHT_LIBS) \
 	$(top_builddir)/libpurple/libpurple.la
 
 AM_CPPFLAGS = \
@@ -23,4 +24,5 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(DBUS_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(LIBXML_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/marshallers.list	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,2 @@
+VOID:BOXED,BOXED
+VOID:POINTER,POINTER,OBJECT
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media.c	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,1188 @@
+/**
+ * @file media.c Media API
+ * @ingroup core
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <string.h>
+
+#include "internal.h"
+
+#include "connection.h"
+#include "media.h"
+#include "marshallers.h"
+
+#include "debug.h"
+
+#ifdef USE_VV
+
+#include <gst/interfaces/propertyprobe.h>
+#include <gst/farsight/fs-conference-iface.h>
+
+struct _PurpleMediaSession
+{
+	gchar *id;
+	PurpleMedia *media;
+	GstElement *src;
+	GstElement *sink;
+	FsSession *session;
+	GHashTable *streams;		/* FsStream list map to participant's name */
+	PurpleMediaStreamType type;
+	GHashTable *local_candidates;	/* map to participant's name? */
+
+	/*
+	 * These will need to be per stream when sessions with multiple
+	 * streams are supported.
+	 */
+	FsCandidate *local_candidate;
+	FsCandidate *remote_candidate;
+};
+
+struct _PurpleMediaPrivate
+{
+	FsConference *conference;
+
+	char *name;
+	PurpleConnection *connection;
+
+	GHashTable *sessions;	/* PurpleMediaSession table */
+	GHashTable *participants; /* FsParticipant table */
+
+	GstElement *pipeline;
+};
+
+#define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate))
+
+static void purple_media_class_init (PurpleMediaClass *klass);
+static void purple_media_init (PurpleMedia *media);
+static void purple_media_finalize (GObject *object);
+static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static GObjectClass *parent_class = NULL;
+
+
+
+enum {
+	READY,
+	WAIT,
+	ACCEPTED,
+	HANGUP,
+	REJECT,
+	GOT_REQUEST,
+	GOT_HANGUP,
+	GOT_ACCEPT,
+	NEW_CANDIDATE,
+	CANDIDATES_PREPARED,
+	CANDIDATE_PAIR,
+	LAST_SIGNAL
+};
+static guint purple_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_FS_CONFERENCE,
+	PROP_NAME,
+	PROP_CONNECTION,
+};
+
+GType
+purple_media_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleMediaClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) purple_media_class_init,
+			NULL,
+			NULL,
+			sizeof(PurpleMedia),
+			0,
+			(GInstanceInitFunc) purple_media_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "PurpleMedia", &info, 0);
+	}
+	return type;
+}
+
+static void
+purple_media_class_init (PurpleMediaClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->finalize = purple_media_finalize;
+	gobject_class->set_property = purple_media_set_property;
+	gobject_class->get_property = purple_media_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_FS_CONFERENCE,
+			g_param_spec_object("farsight-conference",
+			"Farsight conference",
+			"The FsConference associated with this media.",
+			FS_TYPE_CONFERENCE,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_NAME,
+			g_param_spec_string("screenname",
+			"Screenname",
+			"The screenname of the remote user",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_CONNECTION,
+			g_param_spec_pointer("connection",
+			"Connection",
+			"The PurpleConnection associated with this session",
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	purple_media_signals[READY] = g_signal_new("ready", G_TYPE_FROM_CLASS(klass),
+				 	 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[WAIT] = g_signal_new("wait", G_TYPE_FROM_CLASS(klass),
+				 	 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[HANGUP] = g_signal_new("hangup", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[REJECT] = g_signal_new("reject", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[GOT_REQUEST] = g_signal_new("got-request", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[GOT_HANGUP] = g_signal_new("got-hangup", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[GOT_ACCEPT] = g_signal_new("got-accept", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__POINTER_POINTER_OBJECT,
+					 G_TYPE_NONE, 3, G_TYPE_POINTER,
+					 G_TYPE_POINTER, FS_TYPE_CANDIDATE);
+	purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[CANDIDATE_PAIR] = g_signal_new("candidate-pair", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__BOXED_BOXED,
+					 G_TYPE_NONE, 2, FS_TYPE_CANDIDATE, FS_TYPE_CANDIDATE);
+
+	g_type_class_add_private(klass, sizeof(PurpleMediaPrivate));
+}
+
+
+static void
+purple_media_init (PurpleMedia *media)
+{
+	media->priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	memset(media->priv, 0, sizeof(media->priv));
+}
+
+static void
+purple_media_finalize (GObject *media)
+{
+	PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media);
+	purple_debug_info("media","purple_media_finalize\n");
+
+	g_free(priv->name);
+
+	if (priv->pipeline) {
+		gst_element_set_state(priv->pipeline, GST_STATE_NULL);
+		gst_object_unref(priv->pipeline);
+	}
+
+	gst_object_unref(priv->conference);
+
+	parent_class->finalize(media);
+}
+
+static void
+purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	PurpleMedia *media;
+	g_return_if_fail(PURPLE_IS_MEDIA(object));
+
+	media = PURPLE_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_FS_CONFERENCE:
+			if (media->priv->conference)
+				g_object_unref(media->priv->conference);
+			media->priv->conference = g_value_get_object(value);
+			g_object_ref(media->priv->conference);
+			break;
+		case PROP_NAME:
+			g_free(media->priv->name);
+			media->priv->name = g_value_dup_string(value);
+			break;
+		case PROP_CONNECTION:
+			media->priv->connection = g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	PurpleMedia *media;
+	g_return_if_fail(PURPLE_IS_MEDIA(object));
+	
+	media = PURPLE_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_FS_CONFERENCE:
+			g_value_set_object(value, media->priv->conference);
+			break;
+		case PROP_NAME:
+			g_value_set_string(value, media->priv->name);
+			break;
+		case PROP_CONNECTION:
+			g_value_set_pointer(value, media->priv->connection);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+
+}
+
+FsMediaType
+purple_media_to_fs_media_type(PurpleMediaStreamType type)
+{
+	if (type & PURPLE_MEDIA_AUDIO)
+		return FS_MEDIA_TYPE_AUDIO;
+	else if (type & PURPLE_MEDIA_VIDEO)
+		return FS_MEDIA_TYPE_VIDEO;
+	else
+		return FS_MEDIA_TYPE_APPLICATION;
+}
+
+FsStreamDirection
+purple_media_to_fs_stream_direction(PurpleMediaStreamType type)
+{
+	if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
+			(type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
+		return FS_DIRECTION_BOTH;
+	else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
+			(type & PURPLE_MEDIA_SEND_VIDEO))
+		return FS_DIRECTION_SEND;
+	else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
+			(type & PURPLE_MEDIA_RECV_VIDEO))
+		return FS_DIRECTION_RECV;
+	else
+		return FS_DIRECTION_NONE;
+}
+
+PurpleMediaStreamType
+purple_media_from_fs(FsMediaType type, FsStreamDirection direction)
+{
+	PurpleMediaStreamType result = PURPLE_MEDIA_NONE;
+	if (type == FS_MEDIA_TYPE_AUDIO) {
+		if (direction & FS_DIRECTION_SEND)
+			result |= PURPLE_MEDIA_SEND_AUDIO;
+		if (direction & FS_DIRECTION_RECV)
+			result |= PURPLE_MEDIA_RECV_AUDIO;
+	} else if (type == FS_MEDIA_TYPE_VIDEO) {
+		if (direction & FS_DIRECTION_SEND)
+			result |= PURPLE_MEDIA_SEND_VIDEO;
+		if (direction & FS_DIRECTION_RECV)
+			result |= PURPLE_MEDIA_RECV_VIDEO;
+	}
+	return result;
+}
+
+PurpleMediaStreamType
+purple_media_get_overall_type(PurpleMedia *media)
+{
+	GList *values = g_hash_table_get_values(media->priv->sessions);
+	PurpleMediaStreamType type = PURPLE_MEDIA_NONE;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		PurpleMediaSession *session = values->data;
+		type |= session->type;
+	}
+
+	return type;
+}
+
+static PurpleMediaSession*
+purple_media_get_session(PurpleMedia *media, const gchar *sess_id)
+{
+	return (PurpleMediaSession*) (media->priv->sessions) ?
+			g_hash_table_lookup(media->priv->sessions, sess_id) : NULL;
+}
+
+static FsParticipant*
+purple_media_get_participant(PurpleMedia *media, const gchar *name)
+{
+	return (FsParticipant*) (media->priv->participants) ?
+			g_hash_table_lookup(media->priv->participants, name) : NULL;
+}
+
+static FsStream*
+purple_media_session_get_stream(PurpleMediaSession *session, const gchar *name)
+{
+	return (FsStream*) (session->streams) ?
+			g_hash_table_lookup(session->streams, name) : NULL;
+}
+
+static GList*
+purple_media_session_get_local_candidates(PurpleMediaSession *session, const gchar *name)
+{
+	return (GList*) (session->local_candidates) ?
+			g_hash_table_lookup(session->local_candidates, name) : NULL;
+}
+
+static void
+purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session)
+{
+	if (!media->priv->sessions) {
+		purple_debug_info("media", "Creating hash table for sessions\n");
+		media->priv->sessions = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	g_hash_table_insert(media->priv->sessions, g_strdup(session->id), session);
+}
+
+static gboolean
+purple_media_remove_session(PurpleMedia *media, PurpleMediaSession *session)
+{
+	return g_hash_table_remove(media->priv->sessions, session->id);
+}
+
+static FsParticipant *
+purple_media_add_participant(PurpleMedia *media, const gchar *name)
+{
+	FsParticipant *participant = purple_media_get_participant(media, name);
+	GError *err = NULL;
+
+	if (participant)
+		return participant;
+
+	participant = fs_conference_new_participant(media->priv->conference,
+						    g_strdup(name), &err);
+
+	if (err) {
+		purple_debug_error("media", "Error creating participant: %s\n",
+				   err->message);
+		g_error_free(err);
+		return NULL;
+	}
+
+	if (!media->priv->participants) {
+		purple_debug_info("media", "Creating hash table for participants\n");
+		media->priv->participants = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+
+	g_hash_table_insert(media->priv->participants, g_strdup(name), participant);
+
+	return participant;
+}
+
+static void
+purple_media_insert_stream(PurpleMediaSession *session, const gchar *name, FsStream *stream)
+{
+	if (!session->streams) {
+		purple_debug_info("media", "Creating hash table for streams\n");
+		session->streams = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+
+	g_hash_table_insert(session->streams, g_strdup(name), stream);
+}
+
+static void
+purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name,
+				     FsCandidate *candidate)
+{
+	GList *candidates = purple_media_session_get_local_candidates(session, name);
+
+	candidates = g_list_append(candidates, candidate);
+
+	if (!session->local_candidates) {
+		purple_debug_info("media", "Creating hash table for local candidates\n");
+		session->local_candidates = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+
+	g_hash_table_insert(session->local_candidates, g_strdup(name), candidates);
+}
+
+GList *
+purple_media_get_session_names(PurpleMedia *media)
+{
+	return g_hash_table_get_keys(media->priv->sessions);
+}
+
+void 
+purple_media_get_elements(PurpleMedia *media, GstElement **audio_src, GstElement **audio_sink,
+                                                  GstElement **video_src, GstElement **video_sink)
+{
+	GList *values = g_hash_table_get_values(media->priv->sessions);
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		PurpleMediaSession *session = (PurpleMediaSession*)values->data;
+
+		if (session->type & PURPLE_MEDIA_SEND_AUDIO && audio_src)
+			*audio_src = session->src;
+		if (session->type & PURPLE_MEDIA_RECV_AUDIO && audio_sink)
+			*audio_sink = session->sink;
+		if (session->type & PURPLE_MEDIA_SEND_VIDEO && video_src)
+			*video_src = session->src;
+		if (session->type & PURPLE_MEDIA_RECV_VIDEO && video_sink)
+			*video_sink = session->sink;
+	}
+}
+
+void 
+purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	GstPad *sinkpad;
+	GstPad *srcpad;
+	
+	if (session->src)
+		gst_object_unref(session->src);
+	session->src = src;
+	gst_bin_add(GST_BIN(purple_media_get_pipeline(media)),
+		    session->src);
+
+	g_object_get(session->session, "sink-pad", &sinkpad, NULL);
+	srcpad = gst_element_get_static_pad(src, "ghostsrc");
+	purple_debug_info("media", "connecting pad: %s\n", 
+			  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
+			  ? "success" : "failure");
+}
+
+void 
+purple_media_set_sink(PurpleMedia *media, const gchar *sess_id, GstElement *sink)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	if (session->sink)
+		gst_object_unref(session->sink);
+	session->sink = sink;
+	gst_bin_add(GST_BIN(purple_media_get_pipeline(media)),
+		    session->sink);
+}
+
+GstElement *
+purple_media_get_src(PurpleMedia *media, const gchar *sess_id)
+{
+	return purple_media_get_session(media, sess_id)->src;
+}
+
+GstElement *
+purple_media_get_sink(PurpleMedia *media, const gchar *sess_id)
+{
+	return purple_media_get_session(media, sess_id)->src;
+}
+
+static gboolean
+media_bus_call(GstBus *bus, GstMessage *msg, gpointer media)
+{
+	switch(GST_MESSAGE_TYPE(msg)) {
+		case GST_MESSAGE_EOS:
+			purple_debug_info("media", "End of Stream\n");
+			break;
+		case GST_MESSAGE_ERROR: {
+			gchar *debug = NULL;
+			GError *err = NULL;
+
+			gst_message_parse_error(msg, &err, &debug);
+
+			purple_debug_error("media", "gst pipeline error: %s\n", err->message);
+			g_error_free(err);
+
+			if (debug) {
+				purple_debug_error("media", "Debug details: %s\n", debug);
+				g_free (debug);
+			}
+			break;
+		}
+		case GST_MESSAGE_ELEMENT: {
+			if (gst_structure_has_name(msg->structure, "farsight-error")) {
+				gint error_no;
+				gst_structure_get_int(msg->structure, "error-no", &error_no);
+				purple_debug_error("media", "farsight-error: %i: %s\n", error_no,
+						  gst_structure_get_string(msg->structure, "error-msg"));
+			} else {
+				gchar *name, *str;
+				name = gst_object_get_name(GST_MESSAGE_SRC (msg));
+				purple_debug_info("media", "element name: %s\n", name);
+				g_free(name);
+
+				str = gst_structure_to_string(msg->structure);
+				purple_debug_info("media", "structure: %s\n", str);
+				g_free(str);
+			}
+			break;
+		}
+		default:
+			purple_debug_info("media", "gst message type: %s\n",
+					  GST_MESSAGE_TYPE_NAME(msg));
+			return TRUE;
+	}
+
+	return TRUE;
+}
+
+GstElement *
+purple_media_get_pipeline(PurpleMedia *media)
+{
+	if (!media->priv->pipeline) {
+		GstBus *bus;
+		media->priv->pipeline = gst_pipeline_new(media->priv->name);
+		bus = gst_pipeline_get_bus(GST_PIPELINE(media->priv->pipeline));
+		gst_bus_add_signal_watch(GST_BUS(bus));
+		gst_bus_add_watch(bus, media_bus_call, media);
+		gst_object_unref(bus);
+
+		gst_bin_add(GST_BIN(media->priv->pipeline), GST_ELEMENT(media->priv->conference));
+	}
+
+	return media->priv->pipeline;
+}
+
+PurpleConnection *
+purple_media_get_connection(PurpleMedia *media)
+{
+	PurpleConnection *gc;
+	g_object_get(G_OBJECT(media), "connection", &gc, NULL);
+	return gc;
+}
+
+const char *
+purple_media_get_screenname(PurpleMedia *media)
+{
+	const char *ret;
+	g_object_get(G_OBJECT(media), "screenname", &ret, NULL);
+	return ret;
+}
+
+void
+purple_media_ready(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[READY], 0);
+}
+
+void
+purple_media_wait(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[WAIT], 0);
+}
+
+void
+purple_media_accept(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[ACCEPTED], 0);
+}
+
+void
+purple_media_hangup(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[HANGUP], 0);
+}
+
+void
+purple_media_reject(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[REJECT], 0);
+}
+
+void
+purple_media_got_request(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[GOT_REQUEST], 0);
+}
+
+void
+purple_media_got_hangup(PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[GOT_HANGUP], 0);
+}
+
+void
+purple_media_got_accept(PurpleMedia *media)
+{
+    g_signal_emit(media, purple_media_signals[GOT_ACCEPT], 0);
+}
+
+gchar*
+purple_media_get_device_name(GstElement *element, GValue *device)
+{
+	gchar *name;
+
+	GstElementFactory *factory = gst_element_get_factory(element);
+	GstElement *temp = gst_element_factory_create(factory, "tmp_src");
+
+	g_object_set_property (G_OBJECT (temp), "device", device);
+	g_object_get (G_OBJECT (temp), "device-name", &name, NULL);
+	gst_object_unref(temp);
+
+	return name;
+}
+
+GList*
+purple_media_get_devices(GstElement *element)
+{
+	GObjectClass *klass;
+	GstPropertyProbe *probe;
+	const GParamSpec *pspec;
+
+	const gchar *longname = NULL;
+
+	GstElementFactory *factory =
+		gst_element_get_factory(element);
+
+	GList *ret = NULL;
+
+	longname = gst_element_factory_get_longname(factory);
+	klass = G_OBJECT_GET_CLASS(element);
+
+	if (!g_object_class_find_property (klass, "device") ||
+			!GST_IS_PROPERTY_PROBE (element) ||
+			!(probe = GST_PROPERTY_PROBE (element)) ||
+			!(pspec = gst_property_probe_get_property (probe, "device"))) {
+		purple_debug_info("media", "Found source '%s' (%s) - no device\n",
+				longname, GST_PLUGIN_FEATURE (factory)->name);
+	} else {
+		gint n;
+		gchar *name;
+		GValueArray *array;
+
+		purple_debug_info("media", "Found devices\n");
+
+		/* Set autoprobe[-fps] to FALSE to avoid delays when probing. */
+		if (g_object_class_find_property (klass, "autoprobe")) {
+			g_object_set (G_OBJECT (element), "autoprobe", FALSE, NULL);
+			if (g_object_class_find_property (klass, "autoprobe-fps")) {
+				g_object_set (G_OBJECT (element), "autoprobe-fps", FALSE, NULL);
+			}
+		}
+
+		array = gst_property_probe_probe_and_get_values (probe, pspec);
+		if (array != NULL) {
+
+			for (n = 0 ; n < array->n_values ; n++) {
+				GValue *device = g_value_array_get_nth (array, n);
+				gst_element_set_state (element, GST_STATE_NULL);
+
+				ret = g_list_append(ret, device);
+
+				name = purple_media_get_device_name(GST_ELEMENT(element), device);
+				purple_debug_info("media", "Found source '%s' (%s) - device '%s' (%s)\n",
+						  longname, GST_PLUGIN_FEATURE (factory)->name,
+						  name, g_value_get_string(device));
+				g_free(name);
+			}
+		}
+	}
+
+	return ret;
+}
+
+void
+purple_media_element_set_device(GstElement *element, GValue *device)
+{
+	g_object_set_property(G_OBJECT(element), "device", device); 
+}
+
+GValue *
+purple_media_element_get_device(GstElement *element)
+{
+	GValue *device;
+	g_object_get(G_OBJECT(element), "device", &device, NULL);
+	return device;
+}
+
+GstElement *
+purple_media_get_element(const gchar *factory_name)
+{
+	GstElementFactory *factory = gst_element_factory_find(factory_name);
+	GstElement *element = gst_element_factory_create(factory, "video_src");
+	gst_object_unref(factory);
+	return element;
+}
+
+void
+purple_media_audio_init_src(GstElement **sendbin, GstElement **sendlevel)
+{
+	GstElement *src;
+	GstPad *pad;
+	GstPad *ghost;
+	const gchar *audio_device = purple_prefs_get_string("/purple/media/audio/device");
+
+	purple_debug_info("media", "purple_media_audio_init_src\n");
+
+	*sendbin = gst_bin_new("purplesendaudiobin");
+	src = gst_element_factory_make("alsasrc", "asrc");
+	*sendlevel = gst_element_factory_make("level", "sendlevel");
+	gst_bin_add_many(GST_BIN(*sendbin), src, *sendlevel, NULL);
+	gst_element_link(src, *sendlevel);
+	pad = gst_element_get_pad(*sendlevel, "src");
+	ghost = gst_ghost_pad_new("ghostsrc", pad);
+	gst_element_add_pad(*sendbin, ghost);
+	g_object_set(G_OBJECT(*sendlevel), "message", TRUE, NULL);
+
+	/* set current audio device on "src"... */
+	if (audio_device) {
+		GList *devices = purple_media_get_devices(src);
+		GList *dev = devices;
+		purple_debug_info("media", "Setting device of GstElement src to %s\n",
+				audio_device);
+		for (; dev ; dev = dev->next) {
+			GValue *device = (GValue *) dev->data;
+			char *name = purple_media_get_device_name(src, device);
+			if (strcmp(name, audio_device) == 0) {
+				purple_media_element_set_device(src, device);
+			}
+			g_free(name);
+		}
+	}
+}
+
+void
+purple_media_video_init_src(GstElement **sendbin)
+{
+	GstElement *src, *tee, *queue, *local_sink;
+	GstPad *pad;
+	GstPad *ghost;
+	const gchar *video_plugin = purple_prefs_get_string("/purple/media/video/plugin");
+	const gchar *video_device = purple_prefs_get_string("/purple/media/video/device");
+
+	purple_debug_info("media", "purple_media_video_init_src\n");
+
+	*sendbin = gst_bin_new("purplesendvideobin");
+	src = gst_element_factory_make(video_plugin, "purplevideosource");
+	gst_bin_add(GST_BIN(*sendbin), src);
+
+	tee = gst_element_factory_make("tee", NULL);
+	gst_bin_add(GST_BIN(*sendbin), tee);
+	gst_element_link(src, tee);
+
+	queue = gst_element_factory_make("queue", NULL);
+	gst_bin_add(GST_BIN(*sendbin), queue);
+	gst_element_link(tee, queue);
+
+	if (!strcmp(video_plugin, "videotestsrc")) {
+		/* unless is-live is set to true it doesn't throttle videotestsrc */
+		g_object_set (G_OBJECT(src), "is-live", TRUE, NULL);
+	}
+
+	pad = gst_element_get_pad(queue, "src");
+	ghost = gst_ghost_pad_new("ghostsrc", pad);
+	gst_element_add_pad(*sendbin, ghost);
+
+	queue = gst_element_factory_make("queue", NULL);
+	gst_bin_add(GST_BIN(*sendbin), queue);
+	gst_element_link(tee, queue);
+
+	local_sink = gst_element_factory_make("autovideosink", "purplelocalvideosink");
+	gst_bin_add(GST_BIN(*sendbin), local_sink);
+	gst_element_link(queue, local_sink);
+
+	/* set current video device on "src"... */
+	if (video_device) {
+		GList *devices = purple_media_get_devices(src);
+		GList *dev = devices;
+		purple_debug_info("media", "Setting device of GstElement src to %s\n",
+				video_device);
+		for (; dev ; dev = dev->next) {
+			GValue *device = (GValue *) dev->data;
+			char *name = purple_media_get_device_name(src, device);
+			if (strcmp(name, video_device) == 0) {
+				purple_media_element_set_device(src, device);
+			}
+			g_free(name);
+		}
+	}
+}
+
+void
+purple_media_audio_init_recv(GstElement **recvbin, GstElement **recvlevel)
+{
+	GstElement *sink;
+	GstPad *pad, *ghost;
+
+	purple_debug_info("media", "purple_media_audio_init_recv\n");
+
+	*recvbin = gst_bin_new("pidginrecvaudiobin");
+	sink = gst_element_factory_make("alsasink", "asink");
+	g_object_set(G_OBJECT(sink), "sync", FALSE, NULL);
+	*recvlevel = gst_element_factory_make("level", "recvlevel");
+	gst_bin_add_many(GST_BIN(*recvbin), sink, *recvlevel, NULL);
+	gst_element_link(*recvlevel, sink);
+	pad = gst_element_get_pad(*recvlevel, "sink");
+	ghost = gst_ghost_pad_new("ghostsink", pad);
+	gst_element_add_pad(*recvbin, ghost);
+	g_object_set(G_OBJECT(*recvlevel), "message", TRUE, NULL);
+
+	purple_debug_info("media", "purple_media_audio_init_recv end\n");
+}
+
+void
+purple_media_video_init_recv(GstElement **recvbin)
+{
+	GstElement *sink;
+	GstPad *pad, *ghost;
+
+	purple_debug_info("media", "purple_media_video_init_recv\n");
+
+	*recvbin = gst_bin_new("pidginrecvvideobin");
+	sink = gst_element_factory_make("autovideosink", "purplevideosink");
+	gst_bin_add(GST_BIN(*recvbin), sink);
+	pad = gst_element_get_pad(sink, "sink");
+	ghost = gst_ghost_pad_new("ghostsink", pad);
+	gst_element_add_pad(*recvbin, ghost);
+
+	purple_debug_info("media", "purple_media_video_init_recv end\n");
+}
+
+static void
+purple_media_new_local_candidate_cb(FsStream *stream,
+				    FsCandidate *local_candidate,
+				    PurpleMediaSession *session)
+{
+	gchar *name;
+	FsParticipant *participant;
+	purple_debug_info("media", "got new local candidate: %s\n", local_candidate->candidate_id);
+	g_object_get(stream, "participant", &participant, NULL);
+	g_object_get(participant, "cname", &name, NULL);
+	g_object_unref(participant);
+
+	purple_media_insert_local_candidate(session, name, fs_candidate_copy(local_candidate));
+
+	g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE],
+		      0, session->id, name, fs_candidate_copy(local_candidate));
+
+	g_free(name);
+}
+
+static void
+purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session)
+{
+	gchar *name;
+	FsParticipant *participant;
+	g_object_get(stream, "participant", &participant, NULL);
+	g_object_get(participant, "cname", &name, NULL);
+	g_object_unref(participant);
+	g_signal_emit(session->media, purple_media_signals[CANDIDATES_PREPARED], 0);
+	g_free(name);
+}
+
+/* callback called when a pair of transport candidates (local and remote)
+ * has been established */
+static void
+purple_media_candidate_pair_established_cb(FsStream *stream,
+					   FsCandidate *native_candidate,
+					   FsCandidate *remote_candidate,
+					   PurpleMediaSession *session)
+{
+	session->local_candidate = fs_candidate_copy(native_candidate);
+	session->remote_candidate = fs_candidate_copy(remote_candidate);
+
+	purple_debug_info("media", "candidate pair established\n");
+	g_signal_emit(session->media, purple_media_signals[CANDIDATE_PAIR], 0,
+		      session->local_candidate,
+		      session->remote_candidate);
+}
+
+static void
+purple_media_src_pad_added_cb(FsStream *stream, GstPad *srcpad,
+			      FsCodec *codec, PurpleMediaSession *session)
+{
+	GstElement *pipeline = purple_media_get_pipeline(session->media);
+	GstPad *sinkpad = gst_element_get_static_pad(session->sink, "ghostsink");
+	purple_debug_info("media", "connecting new src pad: %s\n", 
+			  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure");
+	gst_element_set_state(pipeline, GST_STATE_PLAYING);
+}
+
+static gboolean
+purple_media_add_stream_internal(PurpleMedia *media, const gchar *sess_id,
+				 const gchar *who, FsMediaType type,
+				 FsStreamDirection type_direction,
+				 const gchar *transmitter)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	FsParticipant *participant = NULL;
+	FsStream *stream = NULL;
+	FsStreamDirection *direction = NULL;
+
+	if (!session) {
+		GError *err = NULL;
+		GList *codec_conf;
+
+		session = g_new0(PurpleMediaSession, 1);
+
+		session->session = fs_conference_new_session(media->priv->conference, type, &err);
+
+		if (err != NULL) {
+			purple_debug_error("media", "Error creating session: %s\n", err->message);
+			g_error_free(err);
+			purple_conv_present_error(who,
+						  purple_connection_get_account(purple_media_get_connection(media)),
+						  _("Error creating session."));
+			g_free(session);
+			return FALSE;
+		}
+
+	/*
+	 * None of these three worked for me. THEORA is known to
+	 * not work as of at least Farsight2 0.0.2
+	 */
+		codec_conf = g_list_prepend(NULL, fs_codec_new(FS_CODEC_ID_DISABLE,
+				"THEORA", FS_MEDIA_TYPE_VIDEO, 90000));
+		codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_DISABLE,
+				"MPV", FS_MEDIA_TYPE_VIDEO, 90000));
+		codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_DISABLE,
+				"H264", FS_MEDIA_TYPE_VIDEO, 90000));
+
+	/* XXX: SPEEX has a latency of 5 or 6 seconds for me */
+#if 0
+	/* SPEEX is added through the configuration */
+		codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY,
+				"SPEEX", FS_MEDIA_TYPE_AUDIO, 8000));
+		codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY,
+				"SPEEX", FS_MEDIA_TYPE_AUDIO, 16000));
+#endif
+
+		g_object_set(G_OBJECT(session->session), "local-codecs-config",
+			     codec_conf, NULL);
+
+	/*
+	 * Temporary fix to remove a 5-7 second delay before
+	 * receiving the src-pad-added signal.
+	 * Only works for one-to-one sessions.
+	 * Specific to FsRtpSession.
+	 */
+		g_object_set(G_OBJECT(session->session), "no-rtcp-timeout", 0, NULL);
+
+
+		fs_codec_list_destroy(codec_conf);
+
+		session->id = g_strdup(sess_id);
+		session->media = media;
+		session->type = purple_media_from_fs(type, type_direction);
+
+		purple_media_add_session(media, session);
+	}
+
+	if (!(participant = purple_media_add_participant(media, who))) {
+		purple_media_remove_session(media, session);
+		g_free(session);
+		return FALSE;
+	}
+
+	stream = purple_media_session_get_stream(session, who);
+
+	if (!stream) {
+		GError *err = NULL;
+
+		stream = fs_session_new_stream(session->session, participant, 
+					       type_direction, transmitter, 0,
+					       NULL, &err);
+
+		if (err) {
+			purple_debug_error("media", "Error creating stream: %s\n",
+					   err->message);
+			g_error_free(err);
+			g_object_unref(participant);
+			purple_media_remove_session(media, session);
+			g_free(session);
+			return FALSE;
+		}
+
+		purple_media_insert_stream(session, who, stream);
+		/* callback for new local candidate (new local candidate retreived) */
+		g_signal_connect(G_OBJECT(stream),
+				 "new-local-candidate", G_CALLBACK(purple_media_new_local_candidate_cb), session);
+		/* callback for source pad added (new stream source ready) */
+		g_signal_connect(G_OBJECT(stream),
+				 "src-pad-added", G_CALLBACK(purple_media_src_pad_added_cb), session);
+		/* callback for local candidates prepared (local candidates ready to send) */
+		g_signal_connect(G_OBJECT(stream), 
+				 "local-candidates-prepared", 
+				 G_CALLBACK(purple_media_candidates_prepared_cb), session);
+		/* callback for new active candidate pair (established connection) */
+		g_signal_connect(G_OBJECT(stream),
+				 "new-active-candidate-pair", 
+				 G_CALLBACK(purple_media_candidate_pair_established_cb), session);
+	} else if (*direction != type_direction) {	
+		/* change direction */
+		g_object_set(stream, "direction", type_direction, NULL);
+	}
+
+	return TRUE;
+}
+
+gboolean
+purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who,
+			PurpleMediaStreamType type,
+			const gchar *transmitter)
+{
+	FsStreamDirection type_direction;
+
+	if (type & PURPLE_MEDIA_AUDIO) {
+		type_direction = purple_media_to_fs_stream_direction(type & PURPLE_MEDIA_AUDIO);
+
+		if (!purple_media_add_stream_internal(media, sess_id, who,
+						      FS_MEDIA_TYPE_AUDIO, type_direction,
+						      transmitter)) {
+			return FALSE;
+		}
+	}
+	if (type & PURPLE_MEDIA_VIDEO) {
+		type_direction = purple_media_to_fs_stream_direction(type & PURPLE_MEDIA_VIDEO);
+
+		if (!purple_media_add_stream_internal(media, sess_id, who,
+						      FS_MEDIA_TYPE_VIDEO, type_direction,
+						      transmitter)) {
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+void
+purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who)
+{
+	
+}
+
+PurpleMediaStreamType
+purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	return session->type;
+}
+
+GList *
+purple_media_get_local_codecs(PurpleMedia *media, const gchar *sess_id)
+{
+	GList *codecs;
+	g_object_get(G_OBJECT(purple_media_get_session(media, sess_id)->session),
+		     "local-codecs", &codecs, NULL);
+	return codecs;
+}
+
+GList *
+purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	return purple_media_session_get_local_candidates(session, name);
+}
+
+GList *
+purple_media_get_negotiated_codecs(PurpleMedia *media, const gchar *sess_id)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	GList *codec_intersection;
+	g_object_get(session->session, "negotiated-codecs", &codec_intersection, NULL);
+	return codec_intersection;
+}
+
+void
+purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id,
+				   const gchar *name, GList *remote_candidates)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	FsStream *stream = purple_media_session_get_stream(session, name);
+	GList *candidates = remote_candidates;
+	for (; candidates; candidates = candidates->next) {
+		GError *err = NULL;
+		fs_stream_add_remote_candidate(stream, candidates->data, &err);
+
+		if (err) {
+			purple_debug_error("media", "Error adding remote candidate: %s\n",
+					   err->message);
+			g_error_free(err);
+		}
+	}
+}
+
+FsCandidate *
+purple_media_get_local_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	return session->local_candidate;
+}
+
+FsCandidate *
+purple_media_get_remote_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	return session->remote_candidate;
+}
+
+gboolean
+purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, const gchar *name, GList *codecs)
+{
+	PurpleMediaSession *session = purple_media_get_session(media, sess_id);
+	FsStream *stream = purple_media_session_get_stream(session, name);
+	GError *err = NULL;
+
+	fs_stream_set_remote_codecs(stream, codecs, &err);
+
+	if (err) {
+		purple_debug_error("media", "Error setting remote codecs: %s\n",
+				   err->message);
+		g_error_free(err);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+gboolean
+purple_media_candidates_prepared(PurpleMedia *media, const gchar *name)
+{
+	GList *sessions = purple_media_get_session_names(media);
+
+	for (; sessions; sessions = sessions->next) {
+		const gchar *session = sessions->data;
+		if (!purple_media_get_local_candidate(media, session, name) ||
+				!purple_media_get_remote_candidate(media, session, name))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/media.h	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,147 @@
+/**
+ * @file media.h Media API
+ * @ingroup core
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __MEDIA_H_
+#define __MEDIA_H_
+
+#ifdef USE_VV
+
+#include <gst/gst.h>
+#include <gst/farsight/fs-stream.h>
+#include <gst/farsight/fs-session.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "connection.h"
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA            (purple_media_get_type())
+#define PURPLE_MEDIA(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA, PurpleMedia))
+#define PURPLE_MEDIA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA, PurpleMediaClass))
+#define PURPLE_IS_MEDIA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA))
+#define PURPLE_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA))
+#define PURPLE_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA, PurpleMediaClass))
+
+typedef struct _PurpleMedia PurpleMedia;
+typedef struct _PurpleMediaClass PurpleMediaClass;
+typedef struct _PurpleMediaPrivate PurpleMediaPrivate;
+typedef struct _PurpleMediaSession PurpleMediaSession;
+
+typedef enum {
+	PURPLE_MEDIA_NONE	= 0,
+	PURPLE_MEDIA_RECV_AUDIO = 1 << 0,
+	PURPLE_MEDIA_SEND_AUDIO = 1 << 1,
+	PURPLE_MEDIA_RECV_VIDEO = 1 << 2,
+	PURPLE_MEDIA_SEND_VIDEO = 1 << 3,
+	PURPLE_MEDIA_AUDIO = PURPLE_MEDIA_RECV_AUDIO | PURPLE_MEDIA_SEND_AUDIO,
+	PURPLE_MEDIA_VIDEO = PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_SEND_VIDEO
+} PurpleMediaStreamType;
+
+struct _PurpleMediaClass
+{
+	GObjectClass parent_class;
+};
+
+struct _PurpleMedia
+{
+	GObject parent;
+	PurpleMediaPrivate *priv;
+};
+
+GType purple_media_get_type(void);
+
+FsMediaType purple_media_to_fs_media_type(PurpleMediaStreamType type);
+FsStreamDirection purple_media_to_fs_stream_direction(PurpleMediaStreamType type);
+PurpleMediaStreamType purple_media_from_fs(FsMediaType type, FsStreamDirection direction);
+
+PurpleMediaStreamType purple_media_get_overall_type(PurpleMedia *media);
+
+GList *purple_media_get_session_names(PurpleMedia *media);
+
+void purple_media_get_elements(PurpleMedia *media, GstElement **audio_src, GstElement **audio_sink,
+						  GstElement **video_src, GstElement **video_sink);
+
+void purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src);
+void purple_media_set_sink(PurpleMedia *media, const gchar *sess_id, GstElement *src);
+
+GstElement *purple_media_get_src(PurpleMedia *media, const gchar *sess_id);
+GstElement *purple_media_get_sink(PurpleMedia *media, const gchar *sess_id);
+
+GstElement *purple_media_get_pipeline(PurpleMedia *media);
+
+PurpleConnection *purple_media_get_connection(PurpleMedia *media);
+const char *purple_media_get_screenname(PurpleMedia *media);
+void purple_media_ready(PurpleMedia *media);
+void purple_media_wait(PurpleMedia *media);
+void purple_media_accept(PurpleMedia *media);
+void purple_media_reject(PurpleMedia *media);
+void purple_media_hangup(PurpleMedia *media);
+void purple_media_got_request(PurpleMedia *media);
+void purple_media_got_hangup(PurpleMedia *media);
+void purple_media_got_accept(PurpleMedia *media);
+
+gchar *purple_media_get_device_name(GstElement *element, 
+										  GValue *device);
+
+GList *purple_media_get_devices(GstElement *element);
+void purple_media_element_set_device(GstElement *element, GValue *device);
+void purple_media_element_set_device_from_name(GstElement *element,
+											   const gchar *name);
+GValue *purple_media_element_get_device(GstElement *element);
+GstElement *purple_media_get_element(const gchar *factory_name);
+
+void purple_media_audio_init_src(GstElement **sendbin,
+                                 GstElement **sendlevel);
+void purple_media_video_init_src(GstElement **sendbin);
+
+void purple_media_audio_init_recv(GstElement **recvbin, GstElement **recvlevel);
+void purple_media_video_init_recv(GstElement **sendbin);
+
+gboolean purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who,
+			     PurpleMediaStreamType type, const gchar *transmitter);
+void purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who);
+
+PurpleMediaStreamType purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id);
+
+GList *purple_media_get_negotiated_codecs(PurpleMedia *media, const gchar *sess_id);
+
+GList *purple_media_get_local_codecs(PurpleMedia *media, const gchar *sess_id);
+void purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id,
+					const gchar *name, GList *remote_candidates);
+GList *purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *name);
+FsCandidate *purple_media_get_local_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name);
+FsCandidate *purple_media_get_remote_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name);
+gboolean purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id,
+					const gchar *name, GList *codecs);
+
+gboolean purple_media_candidates_prepared(PurpleMedia *media, const gchar *name);
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __MEDIA_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/mediamanager.c	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,157 @@
+/**
+ * @file mediamanager.c Media Manager API
+ * @ingroup core
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "internal.h"
+
+#include "connection.h"
+#include "mediamanager.h"
+#include "media.h"
+
+#ifdef USE_VV
+
+#include <gst/farsight/fs-conference-iface.h>
+
+struct _PurpleMediaManagerPrivate
+{
+	GList *medias;
+};
+
+#define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate))
+
+static void purple_media_manager_class_init (PurpleMediaManagerClass *klass);
+static void purple_media_manager_init (PurpleMediaManager *media);
+static void purple_media_manager_finalize (GObject *object);
+
+static GObjectClass *parent_class = NULL;
+
+
+
+enum {
+	INIT_MEDIA,
+	LAST_SIGNAL
+};
+static guint purple_media_manager_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_FARSIGHT_SESSION,
+	PROP_NAME,
+	PROP_CONNECTION,
+	PROP_MIC_ELEMENT,
+	PROP_SPEAKER_ELEMENT,
+};
+
+GType
+purple_media_manager_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleMediaManagerClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) purple_media_manager_class_init,
+			NULL,
+			NULL,
+			sizeof(PurpleMediaManager),
+			0,
+			(GInstanceInitFunc) purple_media_manager_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "PurpleMediaManager", &info, 0);
+	}
+	return type;
+}
+
+
+static void
+purple_media_manager_class_init (PurpleMediaManagerClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->finalize = purple_media_manager_finalize;
+
+	purple_media_manager_signals[INIT_MEDIA] = g_signal_new ("init-media",
+		G_TYPE_FROM_CLASS (klass),
+		G_SIGNAL_RUN_LAST,
+		0, NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1, PURPLE_TYPE_MEDIA);
+	g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate));
+}
+
+static void
+purple_media_manager_init (PurpleMediaManager *media)
+{
+	media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
+	media->priv->medias = NULL;
+}
+
+static void
+purple_media_manager_finalize (GObject *media)
+{
+	parent_class->finalize(media);
+}
+
+PurpleMediaManager *
+purple_media_manager_get()
+{
+	static PurpleMediaManager *manager = NULL;
+
+	if (manager == NULL)
+		manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL));
+	return manager;
+}
+
+PurpleMedia *
+purple_media_manager_create_media(PurpleMediaManager *manager,
+				  PurpleConnection *gc,
+				  const char *conference_type,
+				  const char *remote_user)
+{
+	PurpleMedia *media;
+	FsConference *conference = FS_CONFERENCE(gst_element_factory_make(conference_type, NULL));
+	GstStateChangeReturn ret = gst_element_set_state(GST_ELEMENT(conference), GST_STATE_READY);
+
+	if (ret == GST_STATE_CHANGE_FAILURE) {
+		purple_conv_present_error(remote_user,
+					  purple_connection_get_account(gc),
+					  _("Error creating conference."));
+		return NULL;
+	}
+
+	media = PURPLE_MEDIA(g_object_new(purple_media_get_type(),
+			     "screenname", remote_user,
+			     "connection", gc, 
+			     "farsight-conference", conference,
+			     NULL));
+	manager->priv->medias = g_list_append(manager->priv->medias, media);
+	g_signal_emit(manager, purple_media_manager_signals[INIT_MEDIA], 0, media);
+	return media;
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/mediamanager.h	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,74 @@
+/**
+ * @file mediamanager.h Media Manager API
+ * @ingroup core
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __MEDIA_MANAGER_H_
+#define __MEDIA_MANAGER_H_
+
+#ifdef USE_VV
+
+#include <gst/farsight/fs-session.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "connection.h"
+#include "media.h"
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_MEDIA_MANAGER            (purple_media_manager_get_type())
+#define PURPLE_MEDIA_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManager))
+#define PURPLE_MEDIA_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerClass))
+#define PURPLE_IS_MEDIA_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_MANAGER))
+#define PURPLE_IS_MEDIA_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_MANAGER))
+#define PURPLE_MEDIA_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerClass))
+
+typedef struct _PurpleMediaManager PurpleMediaManager;
+typedef struct _PurpleMediaManagerClass PurpleMediaManagerClass;
+typedef struct _PurpleMediaManagerPrivate PurpleMediaManagerPrivate;
+
+struct _PurpleMediaManagerClass
+{
+	GObjectClass parent_class;
+};
+
+struct _PurpleMediaManager
+{
+	GObject parent;
+	PurpleMediaManagerPrivate *priv;
+};
+
+GType purple_media_manager_get_type(void);
+PurpleMediaManager *purple_media_manager_get(void);
+PurpleMedia *purple_media_manager_create_media(PurpleMediaManager *manager,
+						PurpleConnection *gc,
+						const char *conference_type,
+						const char *remote_user);
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __MEDIA_MANAGER_H_ */
--- a/libpurple/plugins/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/plugins/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -140,6 +140,9 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
 	$(DBUS_CFLAGS)
--- a/libpurple/plugins/perl/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/plugins/perl/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -5,7 +5,7 @@
 plugin_LTLIBRARIES = perl.la
 
 perl_la_LDFLAGS = -module -avoid-version
-perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS)
+perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS) $(FARSIGHT_LIBS)
 perl_la_SOURCES = \
 	perl.c \
 	perl-common.c \
@@ -164,4 +164,5 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
-	$(PERL_CFLAGS)
+	$(PERL_CFLAGS) \
+	$(FARSIGHT_CFLAGS)
--- a/libpurple/plugins/ssl/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/plugins/ssl/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -31,6 +31,9 @@
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
 	$(PLUGIN_CFLAGS)
 
 ssl_gnutls_la_CFLAGS = $(AM_CPPFLAGS) $(GNUTLS_CFLAGS)
--- a/libpurple/plugins/tcl/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/plugins/tcl/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -7,7 +7,7 @@
 tcl_la_SOURCES = tcl.c tcl_glib.c tcl_glib.h tcl_cmds.c tcl_signals.c tcl_purple.h \
                  tcl_ref.c tcl_cmd.c
 
-tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS)
+tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS) $(FARSIGHT_LIBS)
 
 EXTRA_DIST = signal-test.tcl Makefile.mingw
 
@@ -18,5 +18,6 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(TK_CFLAGS) \
 	$(TCL_CFLAGS)
--- a/libpurple/protocols/bonjour/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -51,5 +51,12 @@
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	$(LIBXML_CFLAGS) \
-	$(AVAHI_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
 
+#if MDNS_AVAHI
+#  AM_CPPFLAGS += $(AVAHI_CFLAGS)
+#else
+#endif
+
--- a/libpurple/protocols/bonjour/bonjour.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Wed Jul 16 21:55:08 2008 +0000
@@ -499,13 +499,13 @@
 	NULL,                                                    /* whiteboard_prpl_ops */
 	NULL,                                                    /* send_raw */
 	NULL,                                                    /* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,                                                    /* unregister_user */
+	NULL,                                                    /* send_attention */
+	NULL,                                                    /* get_attention_types */
+	sizeof(PurplePluginProtocolInfo),                        /* struct_size */
+	NULL,                                                    /* get_account_text_table */
+	NULL,                                                    /* initiate_media */
+	NULL                                                     /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -725,3 +725,4 @@
 }
 
 PURPLE_INIT_PLUGIN(bonjour, init_plugin, info);
+
--- a/libpurple/protocols/gg/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/gg/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -75,5 +75,8 @@
 	-I$(top_builddir)/libpurple \
 	$(INTGG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
 	$(DEBUG_CFLAGS)
 
--- a/libpurple/protocols/gg/gg.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2165,13 +2165,13 @@
 	NULL,				/* whiteboard_prpl_ops */
 	NULL,				/* send_raw */
 	NULL,				/* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,				/* unregister_user */
+	NULL,				/* send_attention */
+	NULL,				/* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,                           /* get_account_text_table */
+	NULL,                           /* initiate_media */
+	NULL                            /* can_do_media */
 };
 /* }}} */
 
@@ -2258,3 +2258,4 @@
 PURPLE_INIT_PLUGIN(gg, init_plugin, info);
 
 /* vim: set ts=8 sts=0 sw=8 noet: */
+
--- a/libpurple/protocols/irc/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/irc/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -32,4 +32,6 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
 	$(DEBUG_CFLAGS)
--- a/libpurple/protocols/irc/irc.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Wed Jul 16 21:55:08 2008 +0000
@@ -897,13 +897,13 @@
 	NULL,					/* whiteboard_prpl_ops */
 	irc_send_raw,			/* send_raw */
 	NULL,					/* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,                   /* unregister_user */
+	NULL,                   /* send_attention */
+	NULL,                   /* get_attention_types */
+	sizeof(PurplePluginProtocolInfo),    /* struct_size */
+	NULL,                    /* get_account_text_table */
+	NULL,                    /* initiate_media */
+	NULL					 /* can_do_media */
 };
 
 static gboolean load_plugin (PurplePlugin *plugin) {
--- a/libpurple/protocols/jabber/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -19,6 +19,8 @@
 			  iq.h \
 			  jabber.c \
 			  jabber.h \
+			  jingle.c \
+			  jingle.h \
 			  jutil.c \
 			  jutil.h \
 			  message.c \
@@ -69,7 +71,7 @@
 pkg_LTLIBRARIES = libjabber.la libxmpp.la
 noinst_LIBRARIES =
 
-libjabber_la_SOURCES = $(JABBERSOURCES)
+libjabber_la_SOURCES = $(JABBERSOURCES)		
 libjabber_la_LIBADD = $(GLIB_LIBS) $(SASL_LIBS) $(LIBXML_LIBS)
 
 libxmpp_la_SOURCES = libxmpp.c
@@ -82,4 +84,6 @@
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
 	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/jabber/buddy.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2485,5 +2485,30 @@
 			js);
 }
 
+gboolean
+jabber_buddy_has_capability(JabberBuddy *jb, const gchar *cap)
+{
+	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, NULL);
+	const GList *iter = NULL;
+	
+	if (!jbr) {
+		purple_debug_error("jabber", 
+				   "Unable to find caps: buddy might be offline\n");
+		return FALSE;
+	}
+	
+	if (!jbr->caps) {
+		purple_debug_error("jabber",
+				   "Unable to find caps: nothing known about buddy\n");
+		return FALSE;
+	}
+	
+	for (iter = jbr->caps->features ; iter ; iter = g_list_next(iter)) {
+		if (strcmp(iter->data, cap) == 0)
+			return TRUE;
+	}
+	
+	return FALSE;
+}
 
 
--- a/libpurple/protocols/jabber/buddy.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Wed Jul 16 21:55:08 2008 +0000
@@ -97,6 +97,8 @@
 const char *jabber_buddy_get_status_msg(JabberBuddy *jb);
 void jabber_buddy_get_info(PurpleConnection *gc, const char *who);
 
+gboolean jabber_buddy_has_capability(JabberBuddy *jb, const gchar *cap);
+
 GList *jabber_blist_node_menu(PurpleBlistNode *node);
 
 void jabber_set_info(PurpleConnection *gc, const char *info);
--- a/libpurple/protocols/jabber/caps.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/caps.c	Wed Jul 16 21:55:08 2008 +0000
@@ -27,6 +27,7 @@
 #include "util.h"
 #include "iq.h"
 
+
 #define JABBER_CAPS_FILENAME "xmpp-caps.xml"
 
 static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */
--- a/libpurple/protocols/jabber/disco.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/disco.c	Wed Jul 16 21:55:08 2008 +0000
@@ -79,7 +79,7 @@
 void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
 	const char *from = xmlnode_get_attrib(packet, "from");
 	const char *type = xmlnode_get_attrib(packet, "type");
-
+	
 	if(!from || !type)
 		return;
 
@@ -105,7 +105,7 @@
 
 		if(node)
 			xmlnode_set_attrib(query, "node", node);
-
+		
 		if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) {
 			identity = xmlnode_new_child(query, "identity");
 			xmlnode_set_attrib(identity, "category", "client");
@@ -142,6 +142,17 @@
 						SUPPORT_FEATURE(feat->namespace);
 				}
 			}
+#ifdef USE_VV
+		} else if (node && !strcmp(node, CAPS0115_NODE "#voice-v1")) {
+			SUPPORT_FEATURE("http://www.google.com/session");
+			SUPPORT_FEATURE("http://www.google.com/transport/p2p");
+			SUPPORT_FEATURE("http://www.google.com/transport/raw-udp");
+			SUPPORT_FEATURE("http://www.google.com/session/phone");
+			SUPPORT_FEATURE("urn:xmpp:tmp:jingle");
+			SUPPORT_FEATURE("urn:xmpp:tmp:jingle:apps:rtp#audio");
+			SUPPORT_FEATURE("urn:xmpp:tmp:jingle:apps:rtp#video");
+			SUPPORT_FEATURE("urn:xmpp:tmp:jingle:transports:ice-udp");
+#endif
 		} else {
 			const char *ext = NULL;
 			unsigned pos;
--- a/libpurple/protocols/jabber/google.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/google.c	Wed Jul 16 21:55:08 2008 +0000
@@ -20,6 +20,7 @@
 
 #include "internal.h"
 #include "debug.h"
+#include "mediamanager.h"
 #include "util.h"
 #include "privacy.h"
 
@@ -29,6 +30,357 @@
 #include "presence.h"
 #include "iq.h"
 
+#ifdef USE_VV
+#include <gst/farsight/fs-conference-iface.h>
+
+typedef struct {
+	char *id;
+	char *initiator;
+} GoogleSessionId;
+
+typedef enum {
+	UNINIT,
+	SENT_INITIATE,
+	RECEIVED_INITIATE,
+	IN_PRORESS,
+	TERMINATED
+} GoogleSessionState;
+
+typedef struct {
+	GoogleSessionId id;
+	GoogleSessionState state;
+	PurpleMedia *media;
+	JabberStream *js; 
+	char *remote_jid;
+} GoogleSession;
+
+GHashTable *sessions = NULL;
+
+static guint 
+google_session_id_hash(gconstpointer key) 
+{
+	GoogleSessionId *id = (GoogleSessionId*)key;
+	
+	guint id_hash = g_str_hash(id->id);
+	guint init_hash = g_str_hash(id->initiator);
+
+	return 23 * id_hash + init_hash;
+}
+
+static gboolean 
+google_session_id_equal(gconstpointer a, gconstpointer b)
+{
+	GoogleSessionId *c = (GoogleSessionId*)a;
+	GoogleSessionId *d = (GoogleSessionId*)b;
+	
+	return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator);
+}
+
+static void
+google_session_destroy(GoogleSession *session)
+{
+	g_hash_table_remove(sessions, &(session->id));
+	g_free(session->id.id);
+	g_free(session->id.initiator);
+	g_free(session->remote_jid);
+	g_object_unref(session->media);
+	g_free(session);
+}
+
+static xmlnode *
+google_session_create_xmlnode(GoogleSession *session, const char *type)
+{
+	xmlnode *node = xmlnode_new("session");
+	xmlnode_set_namespace(node, "http://www.google.com/session");
+	xmlnode_set_attrib(node, "id", session->id.id);
+	xmlnode_set_attrib(node, "initiator", session->id.initiator);
+	xmlnode_set_attrib(node, "type", type);
+	return node;
+}
+
+static void
+google_session_send_accept(GoogleSession *session)
+{
+	xmlnode *sess, *desc, *payload;
+	GList *codecs = purple_media_get_negotiated_codecs(session->media, "google-voice");
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "accept");
+	xmlnode_insert_child(iq->node, sess);
+	desc = xmlnode_new_child(sess, "description");
+	xmlnode_set_namespace(desc, "http://www.google.com/session/phone");
+
+	for (;codecs; codecs = codecs->next) {
+		FsCodec *codec = (FsCodec*)codecs->data;
+		char id[8], clockrate[10];
+		payload = xmlnode_new_child(desc, "payload-type");
+		g_snprintf(id, sizeof(id), "%d", codec->id);
+		g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate);
+		xmlnode_set_attrib(payload, "name", codec->encoding_name);
+		xmlnode_set_attrib(payload, "id", id);
+		xmlnode_set_attrib(payload, "clockrate", clockrate);
+	}
+
+	fs_codec_list_destroy(codecs);
+	jabber_iq_send(iq);
+	gst_element_set_state(purple_media_get_pipeline(session->media), GST_STATE_PLAYING);
+}
+
+static void
+google_session_send_terminate(GoogleSession *session)
+{
+	xmlnode *sess;
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "terminate");
+	xmlnode_insert_child(iq->node, sess);
+	
+	jabber_iq_send(iq);
+	google_session_destroy(session);
+}
+
+static void
+google_session_send_reject(GoogleSession *session)
+{
+	xmlnode *sess;
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	sess = google_session_create_xmlnode(session, "reject");
+	xmlnode_insert_child(iq->node, sess);
+	
+	jabber_iq_send(iq);
+	google_session_destroy(session);
+}
+
+
+static void 
+google_session_candidates_prepared (PurpleMedia *media, GoogleSession *session)
+{
+	JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET);
+	GList *candidates = purple_media_get_local_candidates(session->media, "google-voice",
+							      session->remote_jid);
+	FsCandidate *transport;
+	xmlnode *sess;
+	xmlnode *candidate;
+	sess = google_session_create_xmlnode(session, "candidates");
+	xmlnode_insert_child(iq->node, sess);
+	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
+	
+	for (;candidates;candidates = candidates->next) {
+		char port[8];
+		char pref[8];
+		transport = (FsCandidate*)(candidates->data);
+
+		if (!strcmp(transport->ip, "127.0.0.1"))
+			continue;
+	
+		candidate = xmlnode_new("candidate");
+
+		g_snprintf(port, sizeof(port), "%d", transport->port);
+		g_snprintf(pref, sizeof(pref), "%d", transport->priority);
+
+		xmlnode_set_attrib(candidate, "address", transport->ip);
+		xmlnode_set_attrib(candidate, "port", port);
+		xmlnode_set_attrib(candidate, "name", "rtp");
+		xmlnode_set_attrib(candidate, "username", transport->username);
+		xmlnode_set_attrib(candidate, "password", transport->password);
+		xmlnode_set_attrib(candidate, "preference", pref);
+		xmlnode_set_attrib(candidate, "protocol", transport->proto == FS_NETWORK_PROTOCOL_UDP ? "udp" : "tcp");
+		xmlnode_set_attrib(candidate, "type", transport->type == FS_CANDIDATE_TYPE_HOST ? "local" :
+						      transport->type == FS_CANDIDATE_TYPE_PRFLX ? "stun" :
+					       	      transport->type == FS_CANDIDATE_TYPE_RELAY ? "relay" : NULL);
+		xmlnode_set_attrib(candidate, "generation", "0");
+		xmlnode_set_attrib(candidate, "network", "0");
+		xmlnode_insert_child(sess, candidate);
+		
+	}
+	jabber_iq_send(iq);
+}
+
+static void
+google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	JabberIq *result;
+	GList *codecs = NULL;
+	xmlnode *desc_element, *codec_element;
+	FsCodec *codec;
+	const char *id, *encoding_name,  *clock_rate;
+		
+	if (session->state != UNINIT) {
+		purple_debug_error("jabber", "Received initiate for active session.\n");
+		return;
+	}
+
+	session->media = purple_media_manager_create_media(purple_media_manager_get(), js->gc,
+							   "fsrtpconference", session->remote_jid);
+
+	/* "rawudp" will need to be changed to "nice" when libnice is finished */
+	purple_media_add_stream(session->media, "google-voice", session->remote_jid, 
+				PURPLE_MEDIA_AUDIO, "rawudp");
+
+	desc_element = xmlnode_get_child(sess, "description");
+	
+	for (codec_element = xmlnode_get_child(desc_element, "payload-type"); 
+	     codec_element; 
+	     codec_element = xmlnode_get_next_twin(codec_element)) {
+		encoding_name = xmlnode_get_attrib(codec_element, "name");
+		id = xmlnode_get_attrib(codec_element, "id");
+		clock_rate = xmlnode_get_attrib(codec_element, "clockrate");
+
+		codec = fs_codec_new(atoi(id), encoding_name, FS_MEDIA_TYPE_AUDIO,
+				     clock_rate ? atoi(clock_rate) : 0);
+		codecs = g_list_append(codecs, codec);
+	}
+
+	purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs);
+
+	g_signal_connect_swapped(G_OBJECT(session->media), "accepted",
+				 G_CALLBACK(google_session_send_accept), session);
+	g_signal_connect_swapped(G_OBJECT(session->media), "reject",
+				 G_CALLBACK(google_session_send_reject), session);
+	g_signal_connect_swapped(G_OBJECT(session->media), "hangup",
+				 G_CALLBACK(google_session_send_terminate), session);
+	g_signal_connect(G_OBJECT(session->media), "candidates-prepared", 
+			 G_CALLBACK(google_session_candidates_prepared), session);
+	purple_media_ready(session->media);
+
+	fs_codec_list_destroy(codecs);
+	
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", session->remote_jid);
+	jabber_iq_send(result);
+}
+
+static void 
+google_session_handle_candidates(JabberStream  *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	JabberIq *result;
+	GList *list = NULL;
+	xmlnode *cand;
+	static int name = 0;
+	char n[4];	
+		
+	for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) {
+		FsCandidate *info;
+		g_snprintf(n, sizeof(n), "S%d", name++);
+		info = fs_candidate_new(n, FS_COMPONENT_RTP, !strcmp(xmlnode_get_attrib(cand, "type"), "local") ?
+					FS_CANDIDATE_TYPE_HOST :
+			     		!strcmp(xmlnode_get_attrib(cand, "type"), "stun") ?
+						FS_CANDIDATE_TYPE_PRFLX :
+			     			!strcmp(xmlnode_get_attrib(cand, "type"), "relay") ?
+							FS_CANDIDATE_TYPE_RELAY : FS_CANDIDATE_TYPE_HOST,
+						!strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ?
+							FS_NETWORK_PROTOCOL_UDP : FS_NETWORK_PROTOCOL_TCP,
+					xmlnode_get_attrib(cand, "address"), atoi(xmlnode_get_attrib(cand, "port")));
+
+		info->username = g_strdup(xmlnode_get_attrib(cand, "username"));
+		info->password = g_strdup(xmlnode_get_attrib(cand, "password"));
+
+		list = g_list_append(list, info);
+	}
+
+	purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list);
+	fs_candidate_list_destroy(list);
+
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", session->remote_jid);
+	jabber_iq_send(result);
+}
+
+static void
+google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	purple_media_got_hangup(session->media);
+	
+	google_session_destroy(session);
+}
+
+static void
+google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess)
+{
+	purple_media_got_hangup(session->media);
+
+	google_session_destroy(session);
+}
+
+static void
+google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet)
+{
+	xmlnode *sess = xmlnode_get_child(packet, "session");	
+	const char *type = xmlnode_get_attrib(sess, "type");
+
+	if (!strcmp(type, "initiate")) {
+		google_session_handle_initiate(js, session, packet, sess);
+	} else if (!strcmp(type, "accept")) {
+	} else if (!strcmp(type, "reject")) {
+		google_session_handle_reject(js, session, packet, sess);
+	} else if (!strcmp(type, "terminate")) {
+		google_session_handle_terminate(js, session, packet, sess);
+	} else if (!strcmp(type, "candidates")) {
+		google_session_handle_candidates(js, session, packet, sess);
+	}
+}
+#endif /* USE_VV */
+
+void
+jabber_google_session_parse(JabberStream *js, xmlnode *packet)
+{
+#ifdef USE_VV
+	GoogleSession *session;
+	GoogleSessionId id;
+
+	xmlnode *session_node;
+	xmlnode *desc_node;
+
+	if (strcmp(xmlnode_get_attrib(packet, "type"), "set"))
+		return;
+
+	session_node = xmlnode_get_child(packet, "session");
+	if (!session_node)
+		return;
+
+	id.id = (gchar*)xmlnode_get_attrib(session_node, "id");
+	if (!id.id)
+		return;
+
+	id.initiator = (gchar*)xmlnode_get_attrib(session_node, "initiator");
+	if (!id.initiator)
+		return;
+
+	if (sessions == NULL)
+		sessions = g_hash_table_new(google_session_id_hash, google_session_id_equal);
+	session = (GoogleSession*)g_hash_table_lookup(sessions, &id);
+
+	if (session) {
+		google_session_parse_iq(js, session, packet);
+		return;
+	}
+
+	/* If the session doesn't exist, this has to be an initiate message */
+	if (strcmp(xmlnode_get_attrib(session_node, "type"), "initiate"))
+		return;
+	desc_node = xmlnode_get_child(session_node, "description");
+	if (!desc_node)
+		return;
+	session = g_new0(GoogleSession, 1);
+	session->id.id = g_strdup(id.id);
+	session->id.initiator = g_strdup(id.initiator);
+	session->state = UNINIT;
+	session->js = js;
+	session->remote_jid = g_strdup(session->id.initiator);
+	g_hash_table_insert(sessions, &(session->id), session);
+
+	google_session_parse_iq(js, session, packet);
+#else
+	/* TODO: send proper error response */
+#endif /* USE_VV */
+}
+
 static void
 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul)
 {
--- a/libpurple/protocols/jabber/google.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/google.h	Wed Jul 16 21:55:08 2008 +0000
@@ -45,6 +45,7 @@
 
 char *jabber_google_format_to_html(const char *text);
 
+void jabber_google_session_parse(JabberStream *js, xmlnode *node);
 
 
 #endif   /* _PURPLE_GOOGLE_H_ */
--- a/libpurple/protocols/jabber/iq.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/iq.c	Wed Jul 16 21:55:08 2008 +0000
@@ -28,6 +28,7 @@
 #include "disco.h"
 #include "google.h"
 #include "iq.h"
+#include "jingle.h"
 #include "oob.h"
 #include "roster.h"
 #include "si.h"
@@ -313,7 +314,7 @@
 	const char *xmlns;
 	const char *type, *id, *from;
 	JabberIqHandler *jih;
-
+	
 	query = xmlnode_get_child(packet, "query");
 	type = xmlnode_get_attrib(packet, "type");
 	from = xmlnode_get_attrib(packet, "from");
@@ -337,6 +338,11 @@
 			return;
 		}
 	}
+	
+	if (xmlnode_get_child_with_namespace(packet, "session", "http://www.google.com/session")) {
+		jabber_google_session_parse(js, packet);
+		return;
+	}
 
 	if(xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si")) {
 		jabber_si_parse(js, packet);
@@ -348,12 +354,17 @@
 		return;
 	}
 	
-	purple_debug_info("jabber", "jabber_iq_parse\n");
-
 	if(xmlnode_get_child_with_namespace(packet, "ping", "urn:xmpp:ping")) {
 		jabber_ping_parse(js, packet);
 		return;
 	}
+	
+#ifdef USE_VV
+	if (xmlnode_get_child_with_namespace(packet, "jingle", "urn:xmpp:tmp:jingle")) {
+		jabber_jingle_session_parse(js, packet);
+		return;
+	}
+#endif
 
 	/* If we get here, send the default error reply mandated by XMPP-CORE */
 	if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) {
@@ -394,6 +405,9 @@
 	jabber_iq_register_handler("http://jabber.org/protocol/disco#items", jabber_disco_items_parse);
 	jabber_iq_register_handler("jabber:iq:register", jabber_register_parse);
 	jabber_iq_register_handler("urn:xmpp:ping", urn_xmpp_ping_parse);
+#ifdef USE_VV
+	jabber_iq_register_handler("urn:xmpp:tmp:jingle", jabber_jingle_session_parse);
+#endif
 }
 
 void jabber_iq_uninit(void)
--- a/libpurple/protocols/jabber/jabber.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed Jul 16 21:55:08 2008 +0000
@@ -56,6 +56,16 @@
 #include "xdata.h"
 #include "pep.h"
 #include "adhoccommands.h"
+#include "jingle.h"
+
+#ifdef USE_VV
+#include <gst/farsight/fs-conference-iface.h>
+
+#define XEP_0167_AUDIO_CAP "urn:xmpp:tmp:jingle:apps:rtp#audio"
+#define XEP_0167_VIDEO_CAP "urn:xmpp:tmp:jingle:apps:rtp#video"
+#define GTALK_CAP "http://www.google.com/session/phone"
+
+#endif
 
 #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5)
 
@@ -625,6 +635,10 @@
 	js->keepalive_timeout = -1;
 	js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user ? js->user->domain : NULL);
 
+#ifdef USE_VV
+	js->sessions = NULL;
+#endif
+
 	if(!js->user) {
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
@@ -1235,6 +1249,11 @@
 {
 	JabberStream *js = gc->proto_data;
 
+#ifdef USE_VV
+	/* Close all of the open Jingle sessions on this stream */
+	jabber_jingle_session_terminate_sessions(js);
+#endif
+
 	/* Don't perform any actions on the ssl connection
 	 * if we were forcibly disconnected because it will crash
 	 * on some SSL backends.
@@ -1861,6 +1880,9 @@
 	if(!(jid = jabber_id_new(who)))
 		return;
 
+#ifdef USE_VV
+	jabber_jingle_session_terminate_session_media(js, who);
+#endif
 	if((jb = jabber_buddy_find(js, who, TRUE)) &&
 			(jbr = jabber_buddy_find_resource(jb, jid->resource))) {
 		if(jbr->thread_id) {
@@ -2338,6 +2360,55 @@
 	return TRUE;
 }
 
+#ifdef USE_VV
+
+PurpleMedia *
+jabber_initiate_media(PurpleConnection *gc, const char *who, 
+		      PurpleMediaStreamType type)
+{
+	return jabber_jingle_session_initiate_media(gc->proto_data, who, type);
+}
+
+gboolean jabber_can_do_media(PurpleConnection *gc, const char *who, 
+                             PurpleMediaStreamType type)
+{
+	JabberStream *js = (JabberStream *) gc->proto_data;
+	JabberBuddy *jb;
+
+	if (!js) {
+		purple_debug_error("jabber", "jabber_can_do_media: NULL stream\n");
+		return FALSE;
+	}
+
+	jb = jabber_buddy_find(js, who, FALSE);
+
+	if (!jb) {
+		purple_debug_error("jabber", "Could not find buddy\n");
+		return FALSE;
+	}
+	/* XMPP will only support two-way media, AFAIK... */
+	if (type == (PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO)) {
+		purple_debug_info("jabber", 
+				  "Checking audio/video XEP support for %s\n", who);
+		return (jabber_buddy_has_capability(jb, XEP_0167_AUDIO_CAP) ||
+				jabber_buddy_has_capability(jb, GTALK_CAP)) && 
+				jabber_buddy_has_capability(jb, XEP_0167_VIDEO_CAP);
+	} else if (type == (PURPLE_MEDIA_AUDIO)) {
+		purple_debug_info("jabber", 
+				  "Checking audio XEP support for %s\n", who);
+		return jabber_buddy_has_capability(jb, XEP_0167_AUDIO_CAP) ||
+				jabber_buddy_has_capability(jb, GTALK_CAP);
+	} else if (type == (PURPLE_MEDIA_VIDEO)) {
+		purple_debug_info("jabber", 
+				  "Checking video XEP support for %s\n", who);
+		return jabber_buddy_has_capability(jb, XEP_0167_VIDEO_CAP);
+	}
+
+	return FALSE;
+}
+
+#endif
+
 void jabber_register_commands(void)
 {
 	purple_cmd_register("config", "", PURPLE_CMD_P_PRPL,
--- a/libpurple/protocols/jabber/jabber.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Wed Jul 16 21:55:08 2008 +0000
@@ -55,6 +55,8 @@
 #include "dnssrv.h"
 #include "roomlist.h"
 #include "sslconn.h"
+#include "media.h"
+#include "mediamanager.h"
 
 #include "jutil.h"
 #include "xmlnode.h"
@@ -201,6 +203,11 @@
 	
 	/* A purple timeout tag for the keepalive */
 	int keepalive_timeout;
+
+#ifdef USE_VV
+	/* keep a hash table of JingleSessions */
+	GHashTable *sessions;
+#endif
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
@@ -268,4 +275,9 @@
 void jabber_register_commands(void);
 void jabber_init_plugin(PurplePlugin *plugin);
 
+#ifdef USE_VV
+PurpleMedia *jabber_initiate_media(PurpleConnection *gc, const char *who, PurpleMediaStreamType type);
+gboolean jabber_can_do_media(PurpleConnection *gc, const char *who, PurpleMediaStreamType type);
+#endif
+
 #endif /* _PURPLE_JABBER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle.c	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,1430 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+
+#include "config.h"
+#include "purple.h"
+#include "jingle.h"
+#include "xmlnode.h"
+#include "iq.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#ifdef USE_VV
+
+#include <gst/farsight/fs-candidate.h>
+
+#define JINGLE "urn:xmpp:tmp:jingle"
+#define JINGLE_RTP "urn:xmpp:tmp:jingle:apps:rtp"
+#define JINGLE_RTP_INFO "urn:xmpp:tmp:jingle:apps:rtp:info"
+#define TRANSPORT_ICEUDP "urn:xmpp:tmp:jingle:transports:ice-udp"
+
+typedef enum {
+	PENDING,
+	GOT_ACK,
+	ACCEPTED,
+	ACTIVE
+} JingleSessionState;
+
+typedef struct {
+	char *id;
+	JabberStream *js;
+	PurpleMedia *media;
+	char *remote_jid;
+	char *initiator;
+	gboolean is_initiator;
+	JingleSessionState state;
+	GHashTable *contents;	/* JingleSessionContent table */
+} JingleSession;
+
+typedef struct {
+	gchar *name;
+	JingleSession *session;
+	gchar *creator;
+	gchar *sender;
+	gchar *transport_type;
+	gchar *type;
+	gchar *subtype;
+} JingleSessionContent;
+
+static void
+jabber_jingle_session_content_create_internal(JingleSession *session,
+					      const gchar *name,
+					      const gchar *creator,
+					      const gchar *sender,
+					      const gchar *transport_type,
+					      const gchar *type,
+					      const gchar *subtype)
+{
+	JingleSessionContent *content = g_new0(JingleSessionContent, 1);
+	content->session = session;
+	content->name = g_strdup(name);
+	content->creator = g_strdup(creator);
+	content->sender = g_strdup(sender);
+	content->transport_type = g_strdup(transport_type);
+	content->type = g_strdup(type);
+	content->subtype = g_strdup(subtype);
+
+	if (!session->contents) {
+		purple_debug_info("jingle", "Creating hash table for contents\n");
+		session->contents = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	purple_debug_info("jingle", "inserting content with name == \"%s\" into table\n",
+			  content->name);
+	g_hash_table_insert(session->contents, content->name, content);
+}
+
+static void
+jabber_jingle_session_destroy_content(JingleSessionContent *content)
+{
+	purple_debug_info("jingle", "destroying content with name == \"%s\"\n",
+			  content->name);
+	g_hash_table_remove(content->session->contents, content->name);
+	g_free(content->name);
+	g_free(content->creator);
+	g_free(content->sender);
+	g_free(content->transport_type);
+	g_free(content->type);
+	g_free(content->subtype);
+	g_free(content);
+}
+
+static const gchar *
+jabber_jingle_session_content_get_name(const JingleSessionContent *jsc)
+{
+	return jsc->name;
+}
+
+static JingleSession *
+jabber_jingle_session_content_get_session(const JingleSessionContent *jsc)
+{
+	return jsc->session;
+}
+
+static const gchar *
+jabber_jingle_session_content_get_creator(const JingleSessionContent *jsc)
+{
+	return jsc->creator;
+}
+
+static const gchar *
+jabber_jingle_session_content_get_sender(const JingleSessionContent *jsc)
+{
+	return jsc->sender;
+}
+
+static const gchar *
+jabber_jingle_session_content_get_transport_type(const JingleSessionContent *jsc)
+{
+	return jsc->transport_type;
+}
+
+static gboolean
+jabber_jingle_session_content_is_transport_type(const JingleSessionContent *jsc,
+						const gchar *transport_type)
+{
+	return !strcmp(jabber_jingle_session_content_get_transport_type(jsc),
+			transport_type);
+}
+
+static const gchar *
+jabber_jingle_session_content_get_type(const JingleSessionContent *jsc)
+{
+	return jsc->type;
+}
+
+static gboolean
+jabber_jingle_session_content_is_type(const JingleSessionContent *jsc,
+				      const gchar *type)
+{
+	return !strcmp(jabber_jingle_session_content_get_type(jsc), type);
+}
+
+static gchar *
+jabber_jingle_session_content_get_subtype(const JingleSessionContent *jsc)
+{
+	return jsc->subtype;
+}
+
+static gboolean
+jabber_jingle_session_content_is_vv_type(const JingleSessionContent *jsc,
+					 const gchar *media_type)
+{
+	return jabber_jingle_session_content_is_type(jsc, JINGLE_RTP) &&
+			!strcmp(jabber_jingle_session_content_get_subtype(jsc),
+				media_type);
+}
+
+static void
+jabber_jingle_session_content_set_sender(JingleSessionContent *jsc,
+					    const char *sender)
+{
+	if (jsc->sender)
+		g_free(jsc->sender);
+	jsc->sender = g_strdup(sender);
+}
+
+static gboolean
+jabber_jingle_session_equal(gconstpointer a, gconstpointer b)
+{
+	purple_debug_info("jingle", 
+					  "jabber_jingle_session_equal, comparing %s and %s\n",
+					  ((JingleSession *)a)->id,
+					  ((JingleSession *)b)->id);
+	return !strcmp(((JingleSession *) a)->id, ((JingleSession *) b)->id);
+}
+
+static JingleSession *
+jabber_jingle_session_create_internal(JabberStream *js,
+									  const char *id)
+{
+    JingleSession *sess = g_new0(JingleSession, 1);
+	sess->js = js;
+		
+	if (id) {
+		sess->id = g_strdup(id);
+	} else if (js) {
+		/* init the session ID... */
+		sess->id = jabber_get_next_id(js);
+	}
+	
+	/* insert it into the hash table */
+	if (!js->sessions) {
+		purple_debug_info("jingle", "Creating hash table for sessions\n");
+		js->sessions = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	purple_debug_info("jingle", "inserting session with key: %s into table\n",
+					  sess->id);
+	g_hash_table_insert(js->sessions, sess->id, sess);
+
+	sess->state = PENDING;
+
+	return sess;
+}
+
+static JabberStream *
+jabber_jingle_session_get_js(const JingleSession *sess)
+{
+	return sess->js;
+}
+
+static JingleSession *
+jabber_jingle_session_create(JabberStream *js)
+{
+	JingleSession *sess = jabber_jingle_session_create_internal(js, NULL);
+	sess->is_initiator = TRUE;	
+	return sess;
+}
+
+static JingleSession *
+jabber_jingle_session_create_by_id(JabberStream *js, const char *id)
+{
+	JingleSession *sess = jabber_jingle_session_create_internal(js, id);
+	sess->is_initiator = FALSE;
+	return sess;
+}
+
+static const char *
+jabber_jingle_session_get_id(const JingleSession *sess)
+{
+	return sess->id;
+}
+
+static void
+jabber_jingle_session_destroy(JingleSession *sess)
+{
+	GList *contents;
+	g_hash_table_remove(sess->js->sessions, sess->id);
+	g_free(sess->id);
+
+	if (sess->media)
+		g_object_unref(sess->media);
+
+	for (contents = g_hash_table_get_values(sess->contents); contents;
+			contents = g_list_delete_link(contents, contents))
+		jabber_jingle_session_destroy_content(contents->data);
+
+	g_free(sess);
+}
+
+static JingleSession *
+jabber_jingle_session_find_by_id(JabberStream *js, const char *id)
+{
+	purple_debug_info("jingle", "find_by_id %s\n", id);
+	purple_debug_info("jingle", "lookup: %p\n", (js->sessions) ?
+			  g_hash_table_lookup(js->sessions, id) : NULL);  
+	return (JingleSession *) (js->sessions) ?
+			  g_hash_table_lookup(js->sessions, id) : NULL;
+}
+
+static JingleSession *
+jabber_jingle_session_find_by_jid(JabberStream *js, const char *jid)
+{
+	GList *values = g_hash_table_get_values(js->sessions);
+	gboolean use_bare = strchr(jid, '/') == NULL;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		JingleSession *session = (JingleSession *)values->data;
+		gchar *cmp_jid = use_bare ? jabber_get_bare_jid(session->remote_jid)
+					  : g_strdup(session->remote_jid);
+		if (!strcmp(jid, cmp_jid)) {
+			g_free(cmp_jid);
+			g_list_free(values);
+			return session;
+		}
+		g_free(cmp_jid);
+	}
+
+	return NULL;	
+}
+
+static GList *
+jabber_jingle_get_codecs(xmlnode *description)
+{
+	GList *codecs = NULL;
+	xmlnode *codec_element = NULL;
+	const char *encoding_name,*id, *clock_rate;
+	FsCodec *codec;
+	const gchar *media = xmlnode_get_attrib(description, "media");
+	FsMediaType type = !strcmp(media, "video") ? FS_MEDIA_TYPE_VIDEO :
+			!strcmp(media, "audio") ? FS_MEDIA_TYPE_AUDIO : 
+			FS_MEDIA_TYPE_APPLICATION;
+
+	for (codec_element = xmlnode_get_child(description, "payload-type") ;
+		 codec_element ;
+		 codec_element = xmlnode_get_next_twin(codec_element)) {
+		encoding_name = xmlnode_get_attrib(codec_element, "name");
+
+		id = xmlnode_get_attrib(codec_element, "id");
+		clock_rate = xmlnode_get_attrib(codec_element, "clockrate");
+
+		codec = fs_codec_new(atoi(id), encoding_name, 
+				     type, 
+				     clock_rate ? atoi(clock_rate) : 0);
+		purple_debug_info("jingle", "codec: %i, %s, %s, %i\n", codec->id, 
+				codec->encoding_name, codec->media_type == FS_MEDIA_TYPE_AUDIO ?
+				"FS_MEDIA_TYPE_AUDIO" : codec->media_type == FS_MEDIA_TYPE_VIDEO ?
+				"FS_MEDIA_TYPE_VIDEO" : "FS_MEDIA_TYPE_NONE", codec->clock_rate);
+		codecs = g_list_append(codecs, codec);
+	}
+	return codecs;
+}
+
+static GList *
+jabber_jingle_get_candidates(const xmlnode *transport)
+{
+	GList *candidates = NULL;
+	xmlnode *candidate = NULL;
+	FsCandidate *c;
+	
+	for (candidate = xmlnode_get_child(transport, "candidate") ;
+		 candidate ;
+		 candidate = xmlnode_get_next_twin(candidate)) {
+		const char *type = xmlnode_get_attrib(candidate, "type");
+		c = fs_candidate_new(xmlnode_get_attrib(candidate, "component"), 
+							atoi(xmlnode_get_attrib(candidate, "component")),
+							strcmp(type, "host") == 0 ?
+							FS_CANDIDATE_TYPE_HOST :
+							strcmp(type, "prflx") == 0 ?
+							FS_CANDIDATE_TYPE_PRFLX :
+							strcmp(type, "relay") == 0 ?
+							FS_CANDIDATE_TYPE_RELAY :
+							strcmp(type, "srflx") == 0 ?
+							FS_CANDIDATE_TYPE_SRFLX : 0,
+							strcmp(xmlnode_get_attrib(candidate, "protocol"),
+							  "udp") == 0 ? 
+				 				FS_NETWORK_PROTOCOL_UDP :
+				 				FS_NETWORK_PROTOCOL_TCP,
+							xmlnode_get_attrib(candidate, "ip"),
+							atoi(xmlnode_get_attrib(candidate, "port")));
+		candidates = g_list_append(candidates, c);
+	}
+	
+	return candidates;
+}
+
+static JingleSessionContent *
+jabber_jingle_session_get_content(const JingleSession *session,
+				  const char *name)
+{
+	return (JingleSession *) name ?
+			g_hash_table_lookup(session->contents, name) : NULL;
+}
+
+static GList *
+jabber_jingle_session_get_contents(const JingleSession *session)
+{
+	return g_hash_table_get_values(session->contents);
+}
+
+static PurpleMedia *
+jabber_jingle_session_get_media(const JingleSession *sess)
+{
+	return sess->media;
+}
+
+static void
+jabber_jingle_session_set_media(JingleSession *sess, PurpleMedia *media)
+{
+	sess->media = media;
+}
+
+static const char *
+jabber_jingle_session_get_remote_jid(const JingleSession *sess)
+{
+	return sess->remote_jid;
+}
+
+static void
+jabber_jingle_session_set_remote_jid(JingleSession *sess, 
+									 const char *remote_jid)
+{
+	sess->remote_jid = strdup(remote_jid);
+}
+
+static JingleSessionState
+jabber_jingle_session_get_state(JingleSession *sess)
+{
+	return sess->state;
+}
+
+static void
+jabber_jingle_session_set_state(JingleSession *sess,
+				JingleSessionState state)
+{
+	sess->state = state;
+}
+				
+
+static const char *
+jabber_jingle_session_get_initiator(const JingleSession *sess)
+{
+	return sess->initiator;
+}
+
+static void
+jabber_jingle_session_set_initiator(JingleSession *sess,
+									const char *initiator)
+{
+	sess->initiator = g_strdup(initiator);
+}
+
+static gboolean
+jabber_jingle_session_is_initiator(const JingleSession *sess)
+{
+	return sess->is_initiator;
+}
+
+static void
+jabber_jingle_session_add_payload_types(const JingleSessionContent *jsc,
+					xmlnode *description,
+					GList *codecs)
+{
+	for (; codecs ; codecs = codecs->next) {
+		FsCodec *codec = (FsCodec*)codecs->data;
+		char id[8], clockrate[10], channels[10];
+		xmlnode *payload = xmlnode_new_child(description, "payload-type");
+		
+		g_snprintf(id, sizeof(id), "%d", codec->id);
+		g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate);
+		g_snprintf(channels, sizeof(channels), "%d", codec->channels);
+		
+		xmlnode_set_attrib(payload, "name", codec->encoding_name);
+		xmlnode_set_attrib(payload, "id", id);
+		xmlnode_set_attrib(payload, "clockrate", clockrate);
+		xmlnode_set_attrib(payload, "channels", channels);
+	}
+}
+
+static xmlnode *
+jabber_jingle_session_add_description_vv(const JingleSessionContent *jsc,
+					 xmlnode *description)
+{
+	xmlnode_set_attrib(description, "media", 
+			jabber_jingle_session_content_get_subtype(jsc));
+	xmlnode_set_attrib(description, "profile", "RTP/AVP");
+	return description;
+}
+
+static xmlnode *
+jabber_jingle_session_add_description(const JingleSessionContent *jsc,
+				      xmlnode *content)
+{
+	xmlnode *description = xmlnode_new_child(content, "description");
+	xmlnode_set_namespace(description,
+			jabber_jingle_session_content_get_type(jsc));
+
+	if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP))
+		return jabber_jingle_session_add_description_vv(jsc, description);
+	else
+		return description;
+}
+
+static xmlnode *
+jabber_jingle_session_add_candidate_iceudp(xmlnode *transport,
+					   FsCandidate *c,
+					   FsCandidate *remote)
+{
+	char port[8];
+	char prio[8];
+	char component[8];
+	xmlnode *candidate = xmlnode_new_child(transport, "candidate");
+	
+	g_snprintf(port, sizeof(port), "%d", c->port);
+	g_snprintf(prio, sizeof(prio), "%d", c->priority);
+	g_snprintf(component, sizeof(component), "%d", c->component_id);
+	
+	xmlnode_set_attrib(candidate, "component", component);
+	xmlnode_set_attrib(candidate, "foundation", "1"); /* what about this? */
+	xmlnode_set_attrib(candidate, "generation", "0"); /* ? */
+	xmlnode_set_attrib(candidate, "ip", c->ip);
+	xmlnode_set_attrib(candidate, "network", "0"); /* ? */
+	xmlnode_set_attrib(candidate, "port", port);
+	xmlnode_set_attrib(candidate, "priority", prio); /* Is this correct? */
+	xmlnode_set_attrib(candidate, "protocol",
+			   c->proto == FS_NETWORK_PROTOCOL_UDP ?
+			   "udp" : "tcp");
+	if (c->username)
+		xmlnode_set_attrib(transport, "ufrag", c->username);
+	if (c->password)
+		xmlnode_set_attrib(transport, "pwd", c->password);
+	
+	xmlnode_set_attrib(candidate, "type", 
+			   c->type == FS_CANDIDATE_TYPE_HOST ? 
+			   "host" :
+			   c->type == FS_CANDIDATE_TYPE_PRFLX ? 
+			   "prflx" :
+		       	   c->type == FS_CANDIDATE_TYPE_RELAY ? 
+			   "relay" :
+			   c->type == FS_CANDIDATE_TYPE_SRFLX ?
+			   "srflx" : NULL);
+
+	/* relay */
+	if (c->type == FS_CANDIDATE_TYPE_RELAY) {
+		/* set rel-addr and rel-port? How? */
+	}
+
+	if (remote) {
+		char remote_port[8];
+		g_snprintf(remote_port, sizeof(remote_port), "%d", remote->port);
+		xmlnode_set_attrib(candidate, "rem-addr", remote->ip);
+		xmlnode_set_attrib(candidate, "rem-port", remote_port);
+	}
+
+	return candidate;
+}
+
+static xmlnode *
+jabber_jingle_session_add_transport(const JingleSessionContent *jsc,
+				    xmlnode *content)
+{
+	xmlnode *transport = xmlnode_new_child(content, "transport");
+	const gchar *transport_type = jabber_jingle_session_content_get_transport_type(jsc);
+	xmlnode_set_namespace(transport, transport_type);
+	return transport;
+}
+
+static xmlnode *
+jabber_jingle_session_add_content(const JingleSessionContent *jsc,
+				  xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_new_child(jingle, "content");
+	xmlnode_set_attrib(content, "creator",
+			   jabber_jingle_session_content_get_creator(jsc));
+	xmlnode_set_attrib(content, "name",
+			   jabber_jingle_session_content_get_name(jsc));
+	xmlnode_set_attrib(content, "sender",
+			   jabber_jingle_session_content_get_sender(jsc));
+	return content;
+}
+
+
+static xmlnode *
+jabber_jingle_session_add_jingle(const JingleSession *sess,
+				 JabberIq *iq, const char *action)
+{
+	xmlnode *jingle = iq ? xmlnode_new_child(iq->node, "jingle") : 
+				xmlnode_new("jingle");
+	xmlnode_set_namespace(jingle, JINGLE);
+	xmlnode_set_attrib(jingle, "action", action);
+	xmlnode_set_attrib(jingle, "initiator", 
+			   jabber_jingle_session_get_initiator(sess));
+	if (jabber_jingle_session_is_initiator(sess))
+		xmlnode_set_attrib(jingle, "responder",
+				jabber_jingle_session_get_remote_jid(sess));
+	else {
+		gchar *responder = g_strdup_printf("%s@%s/%s",
+				sess->js->user->node,
+				sess->js->user->domain,
+				sess->js->user->resource);
+		xmlnode_set_attrib(jingle, "responder", responder);
+		g_free(responder);
+	}
+	xmlnode_set_attrib(jingle, "sid", jabber_jingle_session_get_id(sess));
+	
+	return jingle;
+}
+
+static JabberIq *
+jabber_jingle_session_create_ack(JingleSession *session, xmlnode *jingle)
+{
+	JabberIq *result = jabber_iq_new(
+			jabber_jingle_session_get_js(session),
+			JABBER_IQ_RESULT);
+	xmlnode *packet = xmlnode_get_parent(jingle);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "from", xmlnode_get_attrib(packet, "to"));
+	xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
+	return result;
+}
+
+static JabberIq *
+jabber_jingle_session_create_iq(const JingleSession *session)
+{
+	JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
+					 JABBER_IQ_SET);
+	gchar *from = g_strdup_printf("%s@%s/%s", session->js->user->node,
+				      session->js->user->domain,
+				      session->js->user->resource);
+	xmlnode_set_attrib(result->node, "from", from);
+	g_free(from);
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+	return result;
+}
+#if 0
+static xmlnode *
+jabber_jingle_session_create_content_accept(const JingleSession *sess)
+{
+	xmlnode *jingle = 
+		jabber_jingle_session_add_jingle(sess, NULL, "content-accept");
+
+	xmlnode *content = xmlnode_new_child(jingle, "content");
+	xmlnode *description = jabber_jingle_session_create_description(sess);
+
+	xmlnode_set_attrib(content, "creator", "initiator");
+	xmlnode_set_attrib(content, "name", "audio-content");
+	xmlnode_set_attrib(content, "profile", "RTP/AVP");
+
+	xmlnode_insert_child(content, description);
+
+	return jingle;
+}
+
+static xmlnode *
+jabber_jingle_session_create_content_replace(const JingleSession *sess,
+					     FsCandidate *native_candidate,
+					     FsCandidate *remote_candidate)
+{
+	xmlnode *jingle = 
+		jabber_jingle_session_add_jingle(sess, NULL, "content-replace");
+	xmlnode *content = NULL;
+	xmlnode *transport = NULL;
+
+	purple_debug_info("jingle", "creating content-modify for native candidate %s " \
+			  ", remote candidate %s\n", native_candidate->candidate_id,
+			  remote_candidate->candidate_id);
+
+	content = xmlnode_new_child(jingle, "content");
+	xmlnode_set_attrib(content, "creator", "initiator");
+	xmlnode_set_attrib(content, "name", "audio-content");
+	xmlnode_set_attrib(content, "profile", "RTP/AVP");
+	
+	/* get top codec from codec_intersection to put here... */
+	/* later on this should probably handle changing codec */
+
+	xmlnode_insert_child(content, jabber_jingle_session_create_description(sess));
+
+	transport = xmlnode_new_child(content, "transport");
+	xmlnode_set_namespace(transport, TRANSPORT_ICEUDP);
+	jabber_jingle_session_add_candidate_iceudp(transport, native_candidate,
+						   remote_candidate);
+
+	purple_debug_info("jingle", "End create content modify\n");
+	
+	return jingle;
+}
+#endif
+
+static JabberIq *
+jabber_jingle_session_create_session_accept(const JingleSession *session)
+{
+	PurpleMedia *media = jabber_jingle_session_get_media(session);
+	const gchar *remote_jid = jabber_jingle_session_get_remote_jid(session);
+	JabberIq *request = jabber_jingle_session_create_iq(session);
+	xmlnode *jingle =
+		jabber_jingle_session_add_jingle(session, request,
+						 "session-accept");
+	GList *contents = jabber_jingle_session_get_contents(session);
+
+	for (; contents; contents = contents->next) {
+		JingleSessionContent *jsc = contents->data;
+		const gchar *content_name = jabber_jingle_session_content_get_name(jsc);
+		xmlnode *content = jabber_jingle_session_add_content(jsc, jingle);
+		xmlnode *description = jabber_jingle_session_add_description(jsc, content);
+		xmlnode *transport = jabber_jingle_session_add_transport(jsc, content);
+		if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP)) {
+			GList *codecs = purple_media_get_negotiated_codecs(media,
+									   content_name);
+			jabber_jingle_session_add_payload_types(jsc, description, codecs);
+			fs_codec_list_destroy(codecs);
+		}
+		if (jabber_jingle_session_content_is_transport_type(jsc, TRANSPORT_ICEUDP)) {
+			jabber_jingle_session_add_candidate_iceudp(transport, 
+					purple_media_get_local_candidate(media, content_name,
+									 remote_jid),
+					purple_media_get_remote_candidate(media, content_name,
+									  remote_jid));
+		}
+	}
+
+	return request;
+}
+
+static JabberIq *
+jabber_jingle_session_create_session_info(const JingleSession *session,
+					  const gchar *type)
+{
+	JabberIq *request = jabber_jingle_session_create_iq(session);
+	xmlnode *jingle =
+		jabber_jingle_session_add_jingle(session, request,
+						 "session-info");
+	xmlnode *info = xmlnode_new_child(jingle, type);
+	xmlnode_set_namespace(info, JINGLE_RTP_INFO);
+	return request;
+}
+
+static JabberIq *
+jabber_jingle_session_create_session_initiate(const JingleSession *session)
+{
+	JabberIq *request = jabber_jingle_session_create_iq(session);
+	xmlnode *jingle =
+		jabber_jingle_session_add_jingle(session, request,
+						 "session-initiate");
+	GList *contents = jabber_jingle_session_get_contents(session);
+
+	for (; contents; contents = contents->next) {
+		JingleSessionContent *jsc = contents->data;
+		xmlnode *content = jabber_jingle_session_add_content(jsc, jingle);
+		xmlnode *description = jabber_jingle_session_add_description(jsc, content);
+		if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP)) {
+			PurpleMedia *media = jabber_jingle_session_get_media(session);
+			const gchar *content_name =
+					jabber_jingle_session_content_get_name(jsc);
+			GList *codecs = purple_media_get_local_codecs(media, content_name);
+			jabber_jingle_session_add_payload_types(jsc, description, codecs);
+			fs_codec_list_destroy(codecs);
+		}
+		jabber_jingle_session_add_transport(jsc, content);
+	}
+
+	return request;
+}
+
+static JabberIq *
+jabber_jingle_session_create_session_terminate(const JingleSession *sess,
+					       const char *reasoncode,
+					       const char *reasontext)
+{
+	JabberIq *request = jabber_jingle_session_create_iq(sess);
+	xmlnode *jingle = 
+		jabber_jingle_session_add_jingle(sess, request,
+						 "session-terminate");
+	xmlnode *reason = xmlnode_new_child(jingle, "reason");
+	xmlnode *condition = xmlnode_new_child(reason, "condition");
+	xmlnode_new_child(condition, reasoncode);
+	if (reasontext) {
+		xmlnode *text = xmlnode_new_child(reason, "text");
+		xmlnode_insert_data(text, reasontext, strlen(reasontext));
+	}
+	
+	return request;
+}
+
+static JabberIq *
+jabber_jingle_session_create_transport_info(const JingleSessionContent *jsc,
+					    FsCandidate *candidate)
+{
+	JingleSession *session = 
+			jabber_jingle_session_content_get_session(jsc);
+	JabberIq *request = jabber_jingle_session_create_iq(session);
+	xmlnode *jingle =
+		jabber_jingle_session_add_jingle(session, request,
+						 "transport-info");
+	xmlnode *content = jabber_jingle_session_add_content(jsc, jingle);
+	xmlnode *transport = jabber_jingle_session_add_transport(jsc, content);
+	jabber_jingle_session_add_candidate_iceudp(transport, candidate, NULL);
+	return request;
+}
+#if 0
+static void
+jabber_jingle_session_send_content_accept(JingleSession *session)
+{
+	JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
+					 JABBER_IQ_SET);
+	xmlnode *jingle = jabber_jingle_session_create_content_accept(session);
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+
+	xmlnode_insert_child(result->node, jingle);
+	jabber_iq_send(result);
+}
+#endif
+static void
+jabber_jingle_session_send_session_accept(JingleSession *session)
+{
+	/* create transport-info packages */
+	PurpleMedia *media = jabber_jingle_session_get_media(session);
+	GList *contents = jabber_jingle_session_get_contents(session);
+	const gchar *remote_jid = jabber_jingle_session_get_remote_jid(session);
+	for (; contents; contents = contents->next) {
+		JingleSessionContent *jsc = contents->data;
+		GList *candidates = purple_media_get_local_candidates(
+				media,
+				jabber_jingle_session_content_get_name(jsc),
+				remote_jid);
+		purple_debug_info("jingle",
+				  "jabber_session_candidates_prepared: %d candidates\n",
+				  g_list_length(candidates));
+		for (; candidates; candidates = candidates->next) {
+			FsCandidate *candidate = candidates->data;
+			JabberIq *result = jabber_jingle_session_create_transport_info(jsc,
+					candidate);
+			jabber_iq_send(result);
+		}
+		fs_candidate_list_destroy(candidates);
+
+		purple_debug_info("jingle", "codec intersection: %i\n",
+				g_list_length(purple_media_get_negotiated_codecs(media,
+				jabber_jingle_session_content_get_name(jsc))));
+	}
+
+	if (purple_media_candidates_prepared(media, remote_jid)) {
+		jabber_iq_send(jabber_jingle_session_create_session_accept(session));
+
+		purple_debug_info("jingle", "Sent session accept, starting stream\n");
+		gst_element_set_state(purple_media_get_pipeline(session->media), GST_STATE_PLAYING);
+		jabber_jingle_session_set_state(session, ACTIVE);
+	} else
+		jabber_jingle_session_set_state(session, ACCEPTED);
+}
+
+static void
+jabber_jingle_session_send_session_reject(JingleSession *session)
+{
+	jabber_iq_send(jabber_jingle_session_create_session_terminate(session,
+			"decline", NULL));
+	jabber_jingle_session_destroy(session);
+}
+
+static void
+jabber_jingle_session_send_session_terminate(JingleSession *session)
+{
+	jabber_iq_send(jabber_jingle_session_create_session_terminate(session,
+			"no-error", NULL));
+	jabber_jingle_session_destroy(session);
+}
+
+static void
+jabber_jingle_session_content_create_media(JingleSession *session,
+					     PurpleMediaStreamType type)
+{
+	gchar sender[10] = "";
+
+	if (type & PURPLE_MEDIA_AUDIO) {
+		if (type == PURPLE_MEDIA_SEND_AUDIO)
+			strcpy(sender, "initiator");
+		else if (type == PURPLE_MEDIA_RECV_AUDIO)
+			strcpy(sender, "responder");
+		else
+			strcpy(sender, "both");
+		jabber_jingle_session_content_create_internal(session,
+				"audio-content", "initiator", sender,
+				TRANSPORT_ICEUDP, JINGLE_RTP, "audio");
+	}
+	if (type & PURPLE_MEDIA_VIDEO) {
+		if (type == PURPLE_MEDIA_SEND_VIDEO)
+			strcpy(sender, "initiator");
+		else if (type == PURPLE_MEDIA_RECV_VIDEO)
+			strcpy(sender, "responder");
+		else
+			strcpy(sender, "both");
+		jabber_jingle_session_content_create_internal(session,
+				"video-content", "initiator", sender,
+				TRANSPORT_ICEUDP, JINGLE_RTP, "video");
+	}
+}
+
+static void
+jabber_jingle_session_content_create_parse(JingleSession *session,
+					   xmlnode *content)
+{
+	xmlnode *description = xmlnode_get_child(content, "description");
+	xmlnode *transport = xmlnode_get_child(content, "transport");
+
+	const gchar *creator = xmlnode_get_attrib(content, "creator");
+	const gchar *sender = xmlnode_get_attrib(content, "sender");
+	const gchar *subtype = xmlnode_get_attrib(description, "media");
+
+	jabber_jingle_session_content_create_internal(session,
+						      xmlnode_get_attrib(content, "name"),
+						      creator ? creator : "initiator",
+						      sender ? sender : "both",
+						      xmlnode_get_namespace(transport),
+						      xmlnode_get_namespace(description),
+						      subtype);
+}
+
+static void
+jabber_jingle_session_new_candidate_cb(PurpleMedia *media,
+				       const gchar *session_id,
+				       const gchar *name,
+				       FsCandidate *candidate,
+				       JingleSession *session)
+{
+	if (jabber_jingle_session_get_state(session) == GOT_ACK ||
+			jabber_jingle_session_get_state(session) == ACTIVE) {
+		JingleSessionContent *jsc = jabber_jingle_session_get_content(session,
+									      session_id);
+		jabber_iq_send(jabber_jingle_session_create_transport_info(jsc,
+				candidate));
+	}
+}
+
+/* callback called when a pair of transport candidates (local and remote)
+	has been established */
+static void
+jabber_jingle_session_candidate_pair_established_cb(PurpleMedia *media,
+						    FsCandidate *native_candidate,
+						    FsCandidate *remote_candidate,
+						    JingleSession *session)
+{
+	if (!jabber_jingle_session_is_initiator(session) &&
+			jabber_jingle_session_get_state(session) == ACCEPTED &&
+			purple_media_candidates_prepared(media,
+				jabber_jingle_session_get_remote_jid(session))) {
+		jabber_iq_send(jabber_jingle_session_create_session_accept(session));
+		
+		purple_debug_info("jingle", "Sent session accept, starting stream\n");
+		gst_element_set_state(purple_media_get_pipeline(session->media),
+				      GST_STATE_PLAYING);
+		jabber_jingle_session_set_state(session, ACTIVE);
+	}
+}
+
+static gboolean
+jabber_jingle_session_initiate_media_internal(JingleSession *session,
+					      const char *initiator,
+					      const char *remote_jid)
+{
+	PurpleMedia *media = NULL;
+	GList *contents = jabber_jingle_session_get_contents(session);
+
+	media = purple_media_manager_create_media(purple_media_manager_get(), 
+						  session->js->gc, "fsrtpconference", remote_jid);
+
+	if (!media) {
+		purple_debug_error("jingle", "Couldn't create fsrtpconference\n");
+		return FALSE;
+	}
+
+	for (; contents; contents = g_list_delete_link(contents, contents)) {
+		JingleSessionContent *jsc = contents->data;
+		gboolean result = FALSE;
+
+		/* these will need to be changed to "nice" once the libnice transmitter is finished */
+		if (jabber_jingle_session_content_is_vv_type(jsc, "audio")) {
+			result = purple_media_add_stream(media, "audio-content", remote_jid,
+							 PURPLE_MEDIA_AUDIO, "rawudp");
+			purple_debug_info("jingle", "Created Jingle audio session\n");
+		}
+		else if (jabber_jingle_session_content_is_vv_type(jsc, "video")) {
+			result = purple_media_add_stream(media, "video-content", remote_jid,
+							 PURPLE_MEDIA_VIDEO, "rawudp");
+			purple_debug_info("jingle", "Created Jingle video session\n");
+		}
+
+		if (!result) {
+			purple_debug_error("jingle", "Couldn't create stream\n");
+			purple_media_reject(media);
+			return FALSE;
+		}
+	}
+
+	jabber_jingle_session_set_remote_jid(session, remote_jid);
+	jabber_jingle_session_set_initiator(session, initiator);
+	jabber_jingle_session_set_media(session, media);
+
+	/* connect callbacks */
+	g_signal_connect_swapped(G_OBJECT(media), "accepted", 
+				 G_CALLBACK(jabber_jingle_session_send_session_accept), session);
+	g_signal_connect_swapped(G_OBJECT(media), "reject", 
+				 G_CALLBACK(jabber_jingle_session_send_session_reject), session);
+	g_signal_connect_swapped(G_OBJECT(media), "hangup", 
+				 G_CALLBACK(jabber_jingle_session_send_session_terminate), session);
+	g_signal_connect(G_OBJECT(media), "new-candidate", 
+				 G_CALLBACK(jabber_jingle_session_new_candidate_cb), session);
+	g_signal_connect(G_OBJECT(media), "candidate-pair", 
+				 G_CALLBACK(jabber_jingle_session_candidate_pair_established_cb), session);
+
+	purple_media_ready(media);
+
+	return TRUE;
+}
+
+static void
+jabber_jingle_session_initiate_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	const char *from = xmlnode_get_attrib(packet, "from");
+	JingleSession *session = jabber_jingle_session_find_by_jid(js, from);
+	PurpleMedia *media;
+	GList *contents;
+
+	if (!session) {
+		/* respond with an error here */
+		purple_debug_error("jingle", "Received session-initiate ack"
+				   " to nonexistent session\n");
+		return;
+	}
+
+	media = session->media;
+
+	if (!strcmp(xmlnode_get_attrib(packet, "type"), "error")) {
+		purple_media_got_hangup(media);
+		return;
+	}
+
+	/* catch errors */
+	if (xmlnode_get_child(packet, "error")) {
+		purple_media_got_hangup(media);
+		return;
+	}
+
+	/* create transport-info packages */
+	contents = jabber_jingle_session_get_contents(session);
+	for (; contents; contents = contents->next) {
+		JingleSessionContent *jsc = contents->data;
+		GList *candidates = purple_media_get_local_candidates(
+				jabber_jingle_session_get_media(session),
+				jabber_jingle_session_content_get_name(jsc),
+				jabber_jingle_session_get_remote_jid(session));
+		purple_debug_info("jingle",
+				  "jabber_session_candidates_prepared: %d candidates\n",
+				  g_list_length(candidates));
+		for (; candidates; candidates = candidates->next) {
+			FsCandidate *candidate = candidates->data;
+			JabberIq *result = jabber_jingle_session_create_transport_info(jsc,
+					candidate);
+			jabber_iq_send(result);
+		}
+		fs_candidate_list_destroy(candidates);
+	}
+
+	jabber_jingle_session_set_state(session, GOT_ACK);
+}
+
+PurpleMedia *
+jabber_jingle_session_initiate_media(JabberStream *js, const char *who, 
+				     PurpleMediaStreamType type)
+{
+	/* create content negotiation */
+	JabberIq *request;
+	JingleSession *session;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	
+	char *jid = NULL, *me = NULL;
+
+	/* construct JID to send to */
+	jb = jabber_buddy_find(js, who, FALSE);
+	if (!jb) {
+		purple_debug_error("jingle", "Could not find Jabber buddy\n");
+		return NULL;
+	}
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if (!jbr) {
+		purple_debug_error("jingle", "Could not find buddy's resource\n");
+	}
+
+	if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) {
+		jid = g_strdup_printf("%s/%s", who, jbr->name);
+	} else {
+		jid = g_strdup(who);
+	}
+	
+	session = jabber_jingle_session_create(js);
+	jabber_jingle_session_content_create_media(session, type);
+
+	/* set ourselves as initiator */
+	me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource);
+
+	if (!jabber_jingle_session_initiate_media_internal(session, me, jid)) {
+		g_free(jid);
+		g_free(me);
+		jabber_jingle_session_destroy(session);
+		return NULL;
+	}
+
+	g_free(jid);
+	g_free(me);
+
+	/* create request */
+	request = jabber_jingle_session_create_session_initiate(session);
+	jabber_iq_set_callback(request, jabber_jingle_session_initiate_result_cb, NULL);
+
+	/* send request to other part */	
+	jabber_iq_send(request);
+
+	return session->media;
+}
+
+void
+jabber_jingle_session_terminate_session_media(JabberStream *js, const gchar *who)
+{
+	JingleSession *session;
+
+	session = jabber_jingle_session_find_by_jid(js, who);
+
+	if (session)
+		purple_media_hangup(session->media);
+}
+
+void
+jabber_jingle_session_terminate_sessions(JabberStream *js)
+{
+	GList *values = js->sessions ?
+			g_hash_table_get_values(js->sessions) : NULL;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		JingleSession *session = (JingleSession *)values->data;
+		purple_media_hangup(session->media);
+	}
+}
+
+static void
+jabber_jingle_session_handle_content_replace(JingleSession *session, xmlnode *jingle)
+{
+#if 0
+	xmlnode *jingle = xmlnode_get_child(packet, "jingle");
+	const char *sid = xmlnode_get_attrib(jingle, "sid");
+	JingleSession *session = jabber_jingle_session_find_by_id(js, sid);
+
+	if (!jabber_jingle_session_is_initiator(session) && session->session_started) {
+		JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
+		JabberIq *accept = jabber_iq_new(js, JABBER_IQ_SET);
+		xmlnode *content_accept = NULL;
+
+		/* send acknowledement */
+		xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id"));
+		xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
+		jabber_iq_send(result);
+
+		/* send content-accept */
+		content_accept = jabber_jingle_session_create_content_accept(session);
+		xmlnode_set_attrib(accept->node, "id", xmlnode_get_attrib(packet, "id"));
+		xmlnode_set_attrib(accept->node, "to", xmlnode_get_attrib(packet, "from"));
+		xmlnode_insert_child(accept->node, content_accept);
+
+		jabber_iq_send(accept);
+	}
+#endif
+}
+
+static void
+jabber_jingle_session_handle_session_accept(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	const char *action = xmlnode_get_attrib(jingle, "action");
+	GList *remote_codecs = NULL;
+	GList *remote_transports = NULL;
+	GList *codec_intersection;
+	FsCodec *top = NULL;
+	xmlnode *description = NULL;
+	xmlnode *transport = NULL;
+
+	/* We should probably check validity of the incoming XML... */
+
+	for (content = xmlnode_get_child(jingle, "content"); content;
+			content = xmlnode_get_next_twin(content)) {
+		description = xmlnode_get_child(content, "description");
+		transport = xmlnode_get_child(content, "transport");
+
+		/* fetch codecs from remote party */
+		purple_debug_info("jingle", "get codecs from session-accept\n");
+		remote_codecs = jabber_jingle_get_codecs(description);
+		purple_debug_info("jingle", "get transport candidates from session accept\n");
+		remote_transports = jabber_jingle_get_candidates(transport);
+
+		purple_debug_info("jingle", "Got %d codecs from responder\n",
+				  g_list_length(remote_codecs));
+		purple_debug_info("jingle", "Got %d transport candidates from responder\n",
+				  g_list_length(remote_transports));
+
+		purple_debug_info("jingle", "Setting remote codecs on stream\n");
+
+		if (!purple_media_set_remote_codecs(session->media,
+						    xmlnode_get_attrib(content, "name"),
+						    jabber_jingle_session_get_remote_jid(session),
+						    remote_codecs)) {
+			purple_media_reject(jabber_jingle_session_get_media(session));
+			return;
+		}
+
+		codec_intersection = purple_media_get_negotiated_codecs(session->media,
+									xmlnode_get_attrib(content, "name"));
+		purple_debug_info("jingle", "codec_intersection contains %d elems\n",
+				  g_list_length(codec_intersection));
+		/* get the top codec */
+		if (g_list_length(codec_intersection) > 0) {
+			top = (FsCodec *) codec_intersection->data;
+			purple_debug_info("jingle", "Found a suitable codec on stream = %d\n",
+					  top->id);
+
+			/* we have found a suitable codec, but we will not start the stream
+			   just yet, wait for transport negotiation to complete... */
+		}
+		/* if we also got transport candidates, add them to our streams
+		   list of known remote candidates */
+		if (g_list_length(remote_transports) > 0) {
+			purple_media_add_remote_candidates(session->media,
+							   xmlnode_get_attrib(content, "name"),
+							   jabber_jingle_session_get_remote_jid(session),
+							   remote_transports);
+			fs_candidate_list_destroy(remote_transports);
+		}
+		if (g_list_length(codec_intersection) == 0 &&
+				g_list_length(remote_transports)) {
+			/* we didn't get any candidates and the codec intersection is empty,
+			   this means this was not a content-accept message and we couldn't
+			   find any suitable codecs, should return error and hang up */
+
+		}
+
+		fs_codec_list_destroy(codec_intersection);
+
+	}
+
+	if (!strcmp(action, "session-accept")) {
+		purple_media_got_accept(jabber_jingle_session_get_media(session));
+		purple_debug_info("jingle", "Got session-accept, starting stream\n");
+		gst_element_set_state(purple_media_get_pipeline(session->media),
+				      GST_STATE_PLAYING);
+	}
+
+	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
+
+	jabber_jingle_session_set_state(session, ACTIVE);
+}
+
+static void
+jabber_jingle_session_handle_session_info(JingleSession *session, xmlnode *jingle)
+{
+	purple_debug_info("jingle", "got session-info\n");
+	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
+}
+
+static void 
+jabber_jingle_session_handle_session_initiate(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = NULL;
+	xmlnode *description = NULL;
+	xmlnode *transport = NULL;
+	const char *initiator = NULL;
+	GList *codecs = NULL;
+
+	if (!jingle) {
+		purple_debug_error("jingle", "Malformed request");
+		return;
+	}
+
+	initiator = xmlnode_get_attrib(jingle, "initiator");
+
+	for (content = xmlnode_get_child(jingle, "content"); content;
+			content = xmlnode_get_next_twin(content)) {
+		/* init media */
+		if (!content) {
+			purple_debug_error("jingle", "jingle tag must contain content tag\n");
+			/* should send error here */
+			return;
+		}
+
+		description = xmlnode_get_child(content, "description");
+
+		if (!description) {
+			purple_debug_error("jingle", "content tag must contain description tag\n");
+			/* we should create an error iq here */
+			return;
+		}
+
+		transport = xmlnode_get_child(content, "transport");
+
+		if (!transport) {
+			purple_debug_error("jingle", "content tag must contain transport tag\n");
+			/* we should create an error iq here */
+			return;
+		}
+
+		jabber_jingle_session_content_create_parse(session, content);
+	}
+
+	if (!jabber_jingle_session_initiate_media_internal(session, initiator, initiator)) {
+		purple_debug_error("jingle", "Couldn't start media session with %s\n", initiator);
+		jabber_jingle_session_destroy(session);
+		/* we should create an error iq here */
+		return;
+	}
+
+	for (content = xmlnode_get_child(jingle, "content"); content;
+			content = xmlnode_get_next_twin(content)) {
+		/* init media */
+		if (!content) {
+			purple_debug_error("jingle", "jingle tag must contain content tag\n");
+			/* should send error here */
+			return;
+		}
+
+		description = xmlnode_get_child(content, "description");
+
+		if (!description) {
+			purple_debug_error("jingle", "content tag must contain description tag\n");
+			/* we should create an error iq here */
+			return;
+		}
+		codecs = jabber_jingle_get_codecs(description);
+
+		purple_media_set_remote_codecs(session->media,
+					       xmlnode_get_attrib(content, "name"),
+					       initiator, codecs);
+		purple_debug_info("jingle", "codec intersection: %i\n",
+				g_list_length(purple_media_get_negotiated_codecs(session->media,
+				xmlnode_get_attrib(content, "name"))));
+	}
+	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
+	jabber_iq_send(jabber_jingle_session_create_session_info(session, "ringing"));
+
+	purple_media_got_request(jabber_jingle_session_get_media(session));
+}
+
+static void
+jabber_jingle_session_handle_session_terminate(JingleSession *session, xmlnode *jingle)
+{
+	if (!session) {
+		purple_debug_error("jingle", "jabber_handle_session_terminate couldn't find session\n");
+		return;
+	}
+
+	/* maybe we should look at the reasoncode to determine if it was
+	   a hangup or a reject, and call different callbacks to purple_media */
+	gst_element_set_state(purple_media_get_pipeline(session->media), GST_STATE_NULL);
+
+	purple_media_got_hangup(jabber_jingle_session_get_media(session));
+	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
+	jabber_jingle_session_destroy(session);
+}
+
+static void
+jabber_jingle_session_handle_transport_info(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	xmlnode *transport = xmlnode_get_child(content, "transport");
+	GList *remote_candidates = jabber_jingle_get_candidates(transport);
+
+	if (!session)
+		purple_debug_error("jingle", "jabber_handle_session_candidates couldn't find session\n");
+
+	/* send acknowledement */
+	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
+
+	/* add candidates to our list of remote candidates */
+	if (g_list_length(remote_candidates) > 0) {
+		purple_media_add_remote_candidates(session->media,
+						   xmlnode_get_attrib(content, "name"),
+						   xmlnode_get_attrib(xmlnode_get_parent(jingle), "from"),
+						   remote_candidates);
+		fs_candidate_list_destroy(remote_candidates);
+	}
+}
+
+void
+jabber_jingle_session_parse(JabberStream *js, xmlnode *packet)
+{
+	const gchar *type = xmlnode_get_attrib(packet, "type");
+	xmlnode *jingle;
+	const gchar *action;
+	const char *sid;
+	JingleSession *session;
+
+	if (!type || strcmp(type, "set")) {
+		/* send iq error here */
+		return;
+	}
+
+	/* is this a Jingle package? */
+	if (!(jingle = xmlnode_get_child(packet, "jingle"))) {
+		/* send iq error here */
+		return;
+	}
+
+	if (!(action = xmlnode_get_attrib(jingle, "action"))) {
+		/* send iq error here */
+		return;
+	}
+
+	purple_debug_info("jabber", "got Jingle package action = %s\n",
+			  action);
+
+	if (!(sid = xmlnode_get_attrib(jingle, "sid"))) {
+		/* send iq error here */
+		return;
+	}
+
+	if (!(session = jabber_jingle_session_find_by_id(js, sid))
+			&& strcmp(action, "session-initiate")) {
+		purple_debug_error("jingle", "jabber_jingle_session_parse couldn't find session\n");
+		/* send iq error here */
+		return;
+	}
+
+	if (!strcmp(action, "session-initiate")) {
+		if (session) {
+			/* This should only happen if you start a session with yourself */
+			purple_debug_error("jingle", "Jingle session with "
+					"id={%s} already exists\n", sid);
+			/* send iq error */
+		} else {
+			session = jabber_jingle_session_create_by_id(js, sid);
+			jabber_jingle_session_handle_session_initiate(session, jingle);
+		}
+	} else if (!strcmp(action, "session-accept")
+			   || !strcmp(action, "content-accept")) {
+		jabber_jingle_session_handle_session_accept(session, jingle);
+	} else if (!strcmp(action, "session-info")) {
+		jabber_jingle_session_handle_session_info(session, jingle);
+	} else if (!strcmp(action, "session-terminate")) {
+		jabber_jingle_session_handle_session_terminate(session, jingle);
+	} else if (!strcmp(action, "transport-info")) {
+		jabber_jingle_session_handle_transport_info(session, jingle);
+	} else if (!strcmp(action, "content-replace")) {
+		jabber_jingle_session_handle_content_replace(session, jingle);
+	}
+}
+
+#endif /* USE_VV */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle.h	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,49 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
+ */
+ 
+#ifndef JINGLE_H
+#define JINGLE_H
+
+#include "config.h"
+#include "jabber.h"
+#include "media.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+/*
+ * When Jingle content types other than voice and video are implemented,
+ * this #ifdef others surrounding Jingle code should be changed to just
+ * be around the voice and video specific parts.
+ */
+#ifdef USE_VV
+
+G_BEGIN_DECLS
+
+void jabber_jingle_session_parse(JabberStream *js, xmlnode *packet);
+
+PurpleMedia *jabber_jingle_session_initiate_media(JabberStream *js,
+						  const char *who,
+						  PurpleMediaStreamType type);
+
+void jabber_jingle_session_terminate_session_media(JabberStream *js, const gchar *who);
+void jabber_jingle_session_terminate_sessions(JabberStream *js);
+
+G_END_DECLS
+
+#endif /* USE_VV */
+
+#endif /* JINGLE_H */
--- a/libpurple/protocols/jabber/libxmpp.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Wed Jul 16 21:55:08 2008 +0000
@@ -115,9 +115,15 @@
 	jabber_unregister_account,		/* unregister_user */
 	jabber_send_attention,			/* send_attention */
 	jabber_attention_types,			/* attention_types */
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL, /* get_account_text_table */
+#ifdef USE_VV
+	jabber_initiate_media,          /* initiate_media */
+	jabber_can_do_media             /* can_do_media */
+#else
+	NULL,					/* initiate_media */
+	NULL					/* can_do_media */
+#endif
 };
 
 static gboolean load_plugin(PurplePlugin *plugin)
@@ -268,6 +274,9 @@
 	jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns", jabber_buzz_isenabled);
 	
 	jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata);
+#ifdef USE_VV
+	jabber_add_feature("voice-v1", "http://www.xmpp.org/extensions/xep-0167.html#ns", NULL);
+#endif
 }
 
 
--- a/libpurple/protocols/jabber/presence.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/jabber/presence.c	Wed Jul 16 21:55:08 2008 +0000
@@ -265,6 +265,10 @@
 	xmlnode_set_namespace(c, "http://jabber.org/protocol/caps");
 	xmlnode_set_attrib(c, "node", CAPS0115_NODE);
 	xmlnode_set_attrib(c, "ver", VERSION);
+#ifdef USE_VV
+	/* Make sure this is 'voice-v1', or you won't be able to talk to Google Talk */
+	xmlnode_set_attrib(c, "ext", "voice-v1");
+#endif
 	
 	if(js != NULL) {
 		/* add the extensions */
--- a/libpurple/protocols/msn/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -93,4 +93,7 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS)
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/msn/msn.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2508,9 +2508,10 @@
 	NULL,					/* unregister_user */
 	msn_send_attention,                     /* send_attention */
 	msn_attention_types,                    /* attention_types */
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	msn_get_account_text_table,             /* get_account_text_table */
+	NULL,                                   /* initiate_media */
+	NULL                                    /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -2592,3 +2593,4 @@
 }
 
 PURPLE_INIT_PLUGIN(msn, init_plugin, info);
+
--- a/libpurple/protocols/msnp9/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/msnp9/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -87,4 +87,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/msnp9/msn.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2276,9 +2276,10 @@
 	NULL,					/* unregister_user */
 	msn_send_attention,                     /* send_attention */
 	msn_attention_types,                    /* attention_types */
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	msn_get_account_text_table,             /* get_account_text_table */
+	NULL,                                   /* initiate_media */
+	NULL                                    /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -2359,3 +2360,4 @@
 }
 
 PURPLE_INIT_PLUGIN(msnp9, init_plugin, info);
+
--- a/libpurple/protocols/myspace/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/myspace/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -40,4 +40,7 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/myspace/myspace.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Wed Jul 16 21:55:08 2008 +0000
@@ -3146,9 +3146,10 @@
 	NULL,                  /* unregister_user */
 	msim_send_attention,   /* send_attention */
 	msim_attention_types,  /* attention_types */
-
-	sizeof(PurplePluginProtocolInfo),  /* struct_size */
+	sizeof(PurplePluginProtocolInfo), /* struct_size */
 	msim_get_account_text_table,              /* get_account_text_table */
+	NULL,                   /* initiate_media */
+	NULL                    /* can_do_media */
 };
 
 
--- a/libpurple/protocols/novell/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/novell/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -54,4 +54,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
-	$(GLIB_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/novell/novell.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/novell/novell.c	Wed Jul 16 21:55:08 2008 +0000
@@ -3512,13 +3512,13 @@
 	NULL,						/* whiteboard_prpl_ops */
 	NULL,						/* send_raw */
 	NULL,						/* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,						/* unregister_user */
+	NULL,						/* send_attention */
+	NULL,						/* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,						/* get_account_text_table */
+	NULL,						/* initiate_media */
+	NULL						/* can_do_media */
 };
 
 static PurplePluginInfo info = {
@@ -3573,3 +3573,4 @@
 }
 
 PURPLE_INIT_PLUGIN(novell, init_plugin, info);
+
--- a/libpurple/protocols/null/nullprpl.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/null/nullprpl.c	Wed Jul 16 21:55:08 2008 +0000
@@ -1122,10 +1122,12 @@
   NULL,                                /* whiteboard_prpl_ops */
   NULL,                                /* send_raw */
   NULL,                                /* roomlist_room_serialize */
-  NULL,                                /* padding... */
-  NULL,
-  NULL,
-  NULL,
+  NULL,                                /* unregister_user */
+  NULL,                                /* send_attention */
+  NULL,                                /* get_attention_types */
+  sizeof(PurplePluginProtocolInfo),    /* struct_size */
+  NULL,                                 /* initiate_media */
+  NULL                                  /* can_do_media */	
 };
 
 static void nullprpl_init(PurplePlugin *plugin)
--- a/libpurple/protocols/oscar/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/oscar/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -76,4 +76,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/oscar/libaim.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Wed Jul 16 21:55:08 2008 +0000
@@ -95,9 +95,10 @@
 	NULL,					/* unregister_user */
 	NULL,					/* send_attention */
 	NULL,					/* get_attention_types */
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,					/* get_account_text_table */
+	NULL,					/* initiate_media */
+	NULL					/* can_do_media */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/oscar/libicq.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Wed Jul 16 21:55:08 2008 +0000
@@ -95,9 +95,10 @@
 	NULL,					/* unregister_user */
 	NULL,					/* send_attention */
 	NULL,					/* get_attention_types */
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,					/* get_account_text_table */
+	NULL,					/* initiate_media */
+	NULL					/* can_do_media */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/qq/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -88,4 +88,8 @@
 	-I$(top_builddir)/libpurple \
 	-DQQ_BUDDY_ICON_DIR=\"$(datadir)/pixmaps/purple/buddy_icons/qq\" \
 	$(DEBUG_CFLAGS) \
-	$(GLIB_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/qq/qq.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Wed Jul 16 21:55:08 2008 +0000
@@ -744,13 +744,13 @@
 	NULL,							/* PurpleWhiteboardPrplOps */
 	NULL,							/* send_raw */
 	NULL,							/* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,							/* unregister_user */
+	NULL,							/* send_attention */
+	NULL,							/* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,							/* get_account_text_table */
+	NULL,							/* initiate_media */
+	NULL                            /* can_do_media */
 };
 
 static PurplePluginInfo info = {
--- a/libpurple/protocols/sametime/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/sametime/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -32,7 +32,7 @@
 
 
 AM_CFLAGS = \
-	$(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) \
+	$(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) $(FARSIGHT_CFLAGS) \
 	$(DEBUG_CFLAGS) \
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple
--- a/libpurple/protocols/sametime/sametime.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Wed Jul 16 21:55:08 2008 +0000
@@ -5188,7 +5188,8 @@
   .new_xfer                  = mw_prpl_new_xfer,
   .offline_message           = NULL,
   .whiteboard_prpl_ops       = NULL,
-  .send_raw                  = NULL
+  .send_raw                  = NULL,
+  .struct_size               = sizeof(PurplePluginProtocolInfo)		
 };
 
 
--- a/libpurple/protocols/silc/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/silc/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -25,7 +25,7 @@
 noinst_LIBRARIES =
 
 libsilcpurple_la_SOURCES = $(SILCSOURCES)
-libsilcpurple_la_LIBADD  = $(GLIB_LIBS) $(SILC_LIBS)
+libsilcpurple_la_LIBADD  = $(GLIB_LIBS) $(SILC_LIBS) $(FARSIGHT_LIBS)
 
 endif
 
@@ -33,4 +33,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/silc/silc.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/silc/silc.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2103,13 +2103,13 @@
 	&silcpurple_wb_ops,			/* whiteboard_prpl_ops */
 	NULL,					/* send_raw */
 	NULL,				        /* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,				        /* unregister_user */
+	NULL,				        /* send_attention */
+	NULL,				        /* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,				        /* get_account_text_table */
+	NULL,				        /* initiate_media */
+	NULL                        /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -2243,3 +2243,4 @@
 }
 
 PURPLE_INIT_PLUGIN(silc, init_plugin, info);
+
--- a/libpurple/protocols/silc10/silc.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/silc10/silc.c	Wed Jul 16 21:55:08 2008 +0000
@@ -1836,12 +1836,13 @@
 	&silcpurple_wb_ops,			/* whiteboard_prpl_ops */
 	NULL,                       /* send_raw */
 	NULL,                       /* roomlist_room_serialize */
-
-	NULL,
-	NULL,
-	NULL,
+	NULL,                       /* unregister_user */
+	NULL,                       /* send_attention */
+	NULL,                       /* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,                       /* get_account_text_table */
+	NULL,                       /* initiate_media */
+	NULL                       /* can_do_media */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/simple/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/simple/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -33,4 +33,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/simple/simple.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/simple/simple.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2062,13 +2062,13 @@
 	NULL,					/* whiteboard_prpl_ops */
 	simple_send_raw,		/* send_raw */
 	NULL,					/* roomlist_room_serialize */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
+	NULL,					/* unregister_user */
+	NULL,					/* send_attention */
+	NULL,					/* get_attention_types */
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,					/* get_account_text_table */
+	NULL,					/* initiate_media */
+	NULL					/* can_do_media */
 };
 
 
@@ -2134,3 +2134,4 @@
 }
 
 PURPLE_INIT_PLUGIN(simple, _init_plugin, info);
+
--- a/libpurple/protocols/yahoo/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/yahoo/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -53,4 +53,8 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(FARSIGHT_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/yahoo/yahoo.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Wed Jul 16 21:55:08 2008 +0000
@@ -4386,12 +4386,12 @@
 	NULL, /* send_raw */
 	NULL, /* roomlist_room_serialize */
 	NULL, /* unregister_user */
-
 	yahoo_send_attention,
 	yahoo_attention_types,
-
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL, /* get_account_text_table */
+	NULL, /* initiate_media */
+	NULL  /* can_do_media */
 };
 
 static PurplePluginInfo info =
@@ -4484,3 +4484,4 @@
 }
 
 PURPLE_INIT_PLUGIN(yahoo, init_plugin, info);
+
--- a/libpurple/protocols/zephyr/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/zephyr/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -106,5 +106,9 @@
 	-I$(top_srcdir)/libpurple/protocols \
 	-DCONFDIR=\"$(sysconfdir)\" \
 	$(GLIB_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(KRB4_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
+	$(LIBXML_CFLAGS)
+
--- a/libpurple/protocols/zephyr/zephyr.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Wed Jul 16 21:55:08 2008 +0000
@@ -2915,7 +2915,9 @@
 	NULL,
 	NULL,
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,					/* get_account_text_table */
+	NULL,					/* initate_media */
+	NULL					/* can_do_media */
 };
 
 static PurplePluginInfo info = {
--- a/libpurple/prpl.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/prpl.h	Wed Jul 16 21:55:08 2008 +0000
@@ -65,6 +65,7 @@
 #include "conversation.h"
 #include "ft.h"
 #include "imgstore.h"
+#include "media.h"
 #include "notify.h"
 #include "proxy.h"
 #include "plugin.h"
@@ -72,8 +73,8 @@
 #include "status.h"
 #include "whiteboard.h"
 
+/** @copydoc PurpleBuddyIconSpec */
 
-/** @copydoc PurpleBuddyIconSpec */
 struct _PurpleBuddyIconSpec {
 	/** This is a comma-delimited list of image formats or @c NULL if icons
 	 *  are not supported.  Neither the core nor the prpl will actually
@@ -404,7 +405,7 @@
 	 * reasons.
 	 */
 	void (*unregister_user)(PurpleAccount *, PurpleAccountUnregistrationCb cb, void *user_data);
-	
+
 	/* Attention API for sending & receiving zaps/nudges/buzzes etc. */
 	gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type);
 	GList *(*get_attention_types)(PurpleAccount *acct);
@@ -440,6 +441,24 @@
 	 *         destroyed by the caller when it's no longer needed.
 	 */
 	GHashTable *(*get_account_text_table)(PurpleAccount *account);
+
+#ifdef USE_VV
+	/** Initiate media with the given buddy */
+	PurpleMedia  *(*initiate_media)(PurpleConnection *conn, const char *who, PurpleMediaStreamType type);
+
+	gboolean (*can_do_media)(PurpleConnection *conn, const char *who, PurpleMediaStreamType type);
+    
+    /*
+	gboolean (*can_receive_video)(PurpleConnection *conn, const char *who);
+	gboolean (*can_send_video)(PurpleConnection *conn, const char *who);
+	gboolean (*can_receive_audio)(PurpleConnection *conn, const char *who);
+	gboolean (*can_send_audio)(PurpleConnection *conn, const char *who);
+	*/
+    
+#else
+	void (*initiate_media)(void);
+	void (*can_do_media)(void);
+#endif
 };
 
 #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \
@@ -714,3 +733,4 @@
 #endif
 
 #endif /* _PRPL_H_ */
+
--- a/libpurple/server.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/server.c	Wed Jul 16 21:55:08 2008 +0000
@@ -37,6 +37,9 @@
 #include "server.h"
 #include "status.h"
 #include "util.h"
+#ifdef USE_VV
+#include "media.h"
+#endif
 
 #define SECS_BEFORE_RESENDING_AUTORESPONSE 600
 #define SEX_BEFORE_RESENDING_AUTORESPONSE "Only after you're married"
@@ -1053,3 +1056,58 @@
 		}
 	}
 }
+
+#ifdef USE_VV
+PurpleMedia *serv_initiate_media(PurpleConnection *gc, const char *who,
+								 PurpleMediaStreamType type)
+{
+	PurplePlugin *prpl = NULL;
+	PurplePluginProtocolInfo *prpl_info = NULL;
+
+	if (gc)
+		prpl = purple_connection_get_prpl(gc);
+	if (prpl)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+
+	if (prpl_info && prpl_info->initiate_media) {
+		/* should check that the protol supports this media type here.... */
+		return prpl_info->initiate_media(gc, who, type);
+	} else {
+		return NULL;
+	}
+}
+
+gboolean
+serv_can_do_media(PurpleConnection *gc, const char *who, 
+                  PurpleMediaStreamType type)
+{
+    PurplePlugin *prpl = NULL;
+	PurplePluginProtocolInfo *prpl_info = NULL;
+	
+	if (gc)
+		prpl = purple_connection_get_prpl(gc);
+	if (prpl)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+	
+	if (prpl_info && prpl_info->can_do_media) {
+		/* should check that the protol supports this media type here.... */
+		return prpl_info->can_do_media(gc, who, type);
+	} else {
+		return FALSE;
+	}
+}
+#else
+void *
+serv_initiate_media(void *gc, void *who, void *type)
+{
+	purple_debug_info("serv", "Blank serv_initiate_media called\n");
+	return NULL;
+}
+
+void *
+serv_can_do_media(void *gc, void *who, void *type)
+{
+	purple_debug_info("serv", "Blank serv_can_do_media called\n");
+	return NULL;
+}
+#endif /* USE_VV */
--- a/libpurple/server.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/server.h	Wed Jul 16 21:55:08 2008 +0000
@@ -29,6 +29,7 @@
 #include "account.h"
 #include "conversation.h"
 #include "prpl.h"
+#include "media.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -182,6 +183,17 @@
 					  PurpleMessageFlags flags, const char *message, time_t mtime);
 void serv_send_file(PurpleConnection *gc, const char *who, const char *file);
 
+#ifdef USE_VV
+PurpleMedia *serv_initiate_media(PurpleConnection *gc, const char *who,
+						  PurpleMediaStreamType type);
+gboolean serv_can_do_media(PurpleConnection *gc, const char *who, 
+                           PurpleMediaStreamType type);
+#else
+/* hmm, is this really nice? */
+void *serv_initiate_media(void*, void*, void*);
+void *serv_can_do_media(void *, void *, void *);
+#endif
+	
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/xmlnode.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/xmlnode.c	Wed Jul 16 21:55:08 2008 +0000
@@ -307,6 +307,12 @@
 	return node->prefix;
 }
 
+xmlnode *xmlnode_get_parent(const xmlnode *child)
+{
+	g_return_val_if_fail(child != NULL, NULL);
+	return child->parent;
+}
+
 void
 xmlnode_free(xmlnode *node)
 {
--- a/libpurple/xmlnode.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/libpurple/xmlnode.h	Wed Jul 16 21:55:08 2008 +0000
@@ -246,6 +246,15 @@
 const char *xmlnode_get_prefix(xmlnode *node);
 
 /**
+ * Gets the parent node.
+ *
+ * @param child The child node.
+ *
+ * @return The parent or NULL.
+ */
+xmlnode *xmlnode_get_parent(const xmlnode *child);
+
+/**
  * Returns the node in a string of xml.
  *
  * @param node The starting node to output.
--- a/pidgin/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -99,6 +99,7 @@
 	gtkimhtmltoolbar.c \
 	gtklog.c \
 	gtkmain.c \
+	gtkmedia.c \
 	gtkmenutray.c \
 	gtknotify.c \
 	gtkplugin.c \
@@ -151,6 +152,7 @@
 	gtkimhtml.h \
 	gtkimhtmltoolbar.h \
 	gtklog.h \
+	gtkmedia.c \
 	gtkmenutray.h \
 	gtknickcolors.h \
 	gtknotify.h \
@@ -196,6 +198,8 @@
 	$(STARTUP_NOTIFICATION_LIBS) \
 	$(LIBXML_LIBS) \
 	$(GTK_LIBS) \
+	$(FARSIGHT_LIBS) \
+	$(GSTPROPS_LIBS) \
 	$(top_builddir)/libpurple/libpurple.la
 
 if USE_INTERNAL_LIBGADU
@@ -220,5 +224,7 @@
 	$(GTKSPELL_CFLAGS) \
 	$(STARTUP_NOTIFICATION_CFLAGS) \
 	$(LIBXML_CFLAGS) \
-	$(INTGG_CFLAGS)
+	$(INTGG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTPROPS_CFLAGS)
 endif  # ENABLE_GTK
--- a/pidgin/gtkconv.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkconv.c	Wed Jul 16 21:55:08 2008 +0000
@@ -46,6 +46,7 @@
 #include "idle.h"
 #include "imgstore.h"
 #include "log.h"
+#include "mediamanager.h"
 #include "notify.h"
 #include "prpl.h"
 #include "request.h"
@@ -60,6 +61,7 @@
 #include "gtkimhtml.h"
 #include "gtkimhtmltoolbar.h"
 #include "gtklog.h"
+#include "gtkmedia.h"
 #include "gtkmenutray.h"
 #include "gtkpounce.h"
 #include "gtkprefs.h"
@@ -1194,6 +1196,18 @@
 	gtk_widget_grab_focus(s->entry);
 }
 
+#ifdef USE_VV
+/* Forward declare this here, because I want to keep VV-related stuff together
+for now */
+static void 
+menu_initiate_audio_call_cb(gpointer data, guint action, GtkWidget *widget);
+static void 
+menu_initiate_video_call_cb(gpointer data, guint action, GtkWidget *widget);
+static void 
+menu_initiate_audio_video_call_cb(gpointer data, guint action, GtkWidget *widget);
+
+#endif
+
 static void
 menu_send_file_cb(gpointer data, guint action, GtkWidget *widget)
 {
@@ -3073,6 +3087,17 @@
 
 	{ "/Conversation/sep1", NULL, NULL, 0, "<Separator>", NULL },
 
+#ifdef USE_VV
+	{ N_("/Conversation/M_edia"), NULL, NULL, 0, "<Branch>", NULL },
+
+	{ N_("/Conversation/Media/_Audio Call"), NULL, menu_initiate_audio_call_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL },
+	{ N_("/Conversation/Media/_Video Call"), NULL, menu_initiate_video_call_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL },
+	{ N_("/Conversation/Media/Audio\\/Video _Call"), NULL, menu_initiate_audio_video_call_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL },
+#endif
+
 	{ N_("/Conversation/Se_nd File..."), NULL, menu_send_file_cb, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_FILE },
 	{ N_("/Conversation/Add Buddy _Pounce..."), NULL, menu_add_pounce_cb,
 			0, "<Item>", NULL },
@@ -3383,6 +3408,18 @@
 		gtk_item_factory_get_widget(win->menu.item_factory,
 		                            N_("/Conversation/View Log"));
 
+#ifdef USE_VV
+	win->menu.audio_call =
+		gtk_item_factory_get_widget(win->menu.item_factory,
+					    N_("/Conversation/Media/Audio Call"));
+	win->menu.video_call =
+		gtk_item_factory_get_widget(win->menu.item_factory,
+					    N_("/Conversation/Media/Video Call"));
+	win->menu.audio_video_call =
+		gtk_item_factory_get_widget(win->menu.item_factory,
+					    N_("/Conversation/Media/Audio\\/Video Call"));
+#endif
+	
 	/* --- */
 
 	win->menu.send_file =
@@ -4727,7 +4764,7 @@
 static GtkWidget *
 setup_common_pane(PidginConversation *gtkconv)
 {
-	GtkWidget *vbox, *frame, *imhtml_sw, *event_box;
+	GtkWidget *vbox, *hpaned, *frame, *imhtml_sw, *event_box;
 	GtkCellRenderer *rend;
 	GtkTreePath *path;
 	PurpleConversation *conv = gtkconv->active_conv;
@@ -4737,7 +4774,7 @@
 	int buddyicon_size = 0;
 
 	/* Setup the top part of the pane */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtkconv->topvbox = vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_widget_show(vbox);
 
 	/* Setup the info pane */
@@ -4807,23 +4844,21 @@
 	/* Setup the gtkimhtml widget */
 	frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
 	gtk_widget_set_size_request(gtkconv->imhtml, -1, 0);
+
+	/* Add the gtkimhtml frame */
+	gtkconv->middle_hpaned = hpaned = gtk_hpaned_new();
+	gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
+	gtk_widget_show(hpaned);
+	gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE);
+
 	if (chat) {
-		GtkWidget *hpaned;
-
 		/* Add the topic */
 		setup_chat_topic(gtkconv, vbox);
 
-		/* Add the gtkimhtml frame */
-		hpaned = gtk_hpaned_new();
-		gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
-		gtk_widget_show(hpaned);
-		gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE);
-
 		/* Now add the userlist */
 		setup_chat_userlist(gtkconv, hpaned);
-	} else {
-		gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
-	}
+	}
+
 	gtk_widget_show(frame);
 
 	gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml");
@@ -6342,6 +6377,32 @@
 		else
 			buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
 
+#ifdef USE_VV
+		/* check if account support voice calls, and if the current buddy
+			supports it */
+		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
+			gboolean audio = serv_can_do_media(gc, purple_conversation_get_name(conv), 
+							   PURPLE_MEDIA_AUDIO);
+			gboolean video = serv_can_do_media(gc, purple_conversation_get_name(conv), 
+							   PURPLE_MEDIA_VIDEO);
+			gboolean av = serv_can_do_media(gc, purple_conversation_get_name(conv),
+							PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
+
+			gtk_widget_set_sensitive(win->menu.audio_call, audio ? TRUE : FALSE);
+			gtk_widget_set_sensitive(win->menu.video_call, video ? TRUE : FALSE);
+			gtk_widget_set_sensitive(win->menu.audio_video_call, av ? TRUE : FALSE);
+		} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
+			/* for now, don't care about chats... */
+			gtk_widget_set_sensitive(win->menu.audio_call, FALSE);
+			gtk_widget_set_sensitive(win->menu.video_call, FALSE);
+			gtk_widget_set_sensitive(win->menu.audio_video_call, FALSE);
+		} else {
+			gtk_widget_set_sensitive(win->menu.audio_call, FALSE);
+			gtk_widget_set_sensitive(win->menu.video_call, FALSE);
+			gtk_widget_set_sensitive(win->menu.audio_video_call, FALSE);
+		}							
+#endif
+		
 		gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons);
 		if (account != NULL)
 			gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), purple_account_get_protocol_id(account));
@@ -6876,7 +6937,7 @@
 	gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE);
 #endif
 	gtk_widget_add_events(event,
-                              GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
+			GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
 	g_signal_connect(G_OBJECT(event), "button-press-event",
 					 G_CALLBACK(icon_menu), gtkconv);
 
@@ -7609,6 +7670,91 @@
 	return TRUE;
 }
 
+
+#ifdef USE_VV
+
+static void
+pidgin_gtkmedia_message_cb(PidginMedia *media, const char *msg, PurpleConversation *conv)
+{
+	purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL));
+}
+
+static void
+menu_initiate_audio_call_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = (PidginWindow *)data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	PurpleConnection *gc = purple_conversation_get_gc(conv);
+
+	PurpleMedia *media =
+		serv_initiate_media(gc,
+				    purple_conversation_get_name(conv),
+				    PURPLE_MEDIA_AUDIO);
+
+	if (media)
+		purple_media_wait(media);
+}
+
+static void
+menu_initiate_video_call_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = (PidginWindow *)data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	PurpleConnection *gc = purple_conversation_get_gc(conv);
+
+	PurpleMedia *media =
+		serv_initiate_media(gc,
+				    purple_conversation_get_name(conv),
+				    PURPLE_MEDIA_VIDEO);
+
+	if (media)
+		purple_media_wait(media);
+}
+
+static void
+menu_initiate_audio_video_call_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = (PidginWindow *)data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	PurpleConnection *gc = purple_conversation_get_gc(conv);
+
+	PurpleMedia *media =
+		serv_initiate_media(gc,
+				    purple_conversation_get_name(conv),
+				    PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
+
+	if (media)
+		purple_media_wait(media);
+}
+
+static void
+pidgin_conv_new_media_cb(PurpleMediaManager *manager, PurpleMedia *media, gpointer nul)
+{
+	GtkWidget *gtkmedia;
+	PurpleConversation *conv;
+	PidginConversation *gtkconv;
+
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
+				       purple_connection_get_account(purple_media_get_connection(media)),
+				       purple_media_get_screenname(media));
+	gtkconv = PIDGIN_CONVERSATION(conv);
+	if (gtkconv->gtkmedia)
+		gtk_widget_destroy(gtkconv->gtkmedia);
+
+	gtkmedia = pidgin_media_new(media);
+	gtk_box_pack_start(GTK_BOX(gtkconv->topvbox), gtkmedia, FALSE, FALSE, 0);
+	gtk_widget_show(gtkmedia);
+	g_signal_connect(G_OBJECT(gtkmedia), "message", G_CALLBACK(pidgin_gtkmedia_message_cb), conv);
+
+	gtkconv->gtkmedia = gtkmedia;
+	g_signal_connect(G_OBJECT(gtkmedia), "destroy", G_CALLBACK(gtk_widget_destroyed), &(gtkconv->gtkmedia));
+
+	gtk_paned_pack2(GTK_PANED(gtkconv->middle_hpaned),
+			pidgin_media_get_display_widget(gtkmedia), FALSE, TRUE);
+}
+
+#endif
+
 void *
 pidgin_conversations_get_handle(void)
 {
@@ -7709,7 +7855,10 @@
 								show_protocol_icons_pref_cb, NULL);
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/hide_new",
                                 hide_new_pref_cb, NULL);
-
+#ifdef USE_VV
+	g_signal_connect(G_OBJECT(purple_media_manager_get()), "init-media",
+			 G_CALLBACK(pidgin_conv_new_media_cb), NULL);
+#endif
 
 
 	/**********************************************************************
--- a/pidgin/gtkconv.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkconv.h	Wed Jul 16 21:55:08 2008 +0000
@@ -150,6 +150,7 @@
 	gpointer depr1;
 #endif
 
+	GtkWidget *middle_hpaned;
 	GtkWidget *lower_hbox;
 
 	GtkWidget *toolbar;
@@ -169,6 +170,8 @@
 	GtkWidget *infopane;
 	GtkListStore *infopane_model;
 	GtkTreeIter infopane_iter;
+	GtkWidget *topvbox;
+	GtkWidget *gtkmedia;
 
 	/* Used when attaching a PidginConversation to a PurpleConversation
 	 * with message history */
--- a/pidgin/gtkconvwin.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkconvwin.h	Wed Jul 16 21:55:08 2008 +0000
@@ -49,7 +49,11 @@
 		GtkWidget *menubar;
 
 		GtkWidget *view_log;
-
+#ifdef USE_VV
+		GtkWidget *audio_call;
+		GtkWidget *video_call;
+		GtkWidget *audio_video_call;
+#endif
 		GtkWidget *send_file;
 		GtkWidget *add_pounce;
 		GtkWidget *get_info;
--- a/pidgin/gtkdebug.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkdebug.c	Wed Jul 16 21:55:08 2008 +0000
@@ -985,6 +985,13 @@
 #ifdef USE_GSTREAMER
 	REGISTER_G_LOG_HANDLER("GStreamer");
 #endif
+#ifdef USE_VV
+#ifdef USE_FARSIGHT
+	REGISTER_G_LOG_HANDLER("farsight");
+	REGISTER_G_LOG_HANDLER("farsight-transmitter");
+	REGISTER_G_LOG_HANDLER("farsight-rtp");
+#endif
+#endif
 
 #ifdef _WIN32
 	if (!purple_debug_is_enabled())
--- a/pidgin/gtkdialogs.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkdialogs.c	Wed Jul 16 21:55:08 2008 +0000
@@ -670,6 +670,12 @@
 	g_string_append(str, "    <b>Tk:</b> Disabled<br/>");
 }
 
+#ifdef USE_VV
+	g_string_append(str, "    <b>Voice and Video:</b> Enabled<br/>");
+#else
+	g_string_append(str, "    <b>Voice and Video:</b> Disabled<br/>");
+#endif
+
 #ifndef _WIN32
 #ifdef USE_SM
 	g_string_append(str, "    <b>X Session Management:</b> Enabled<br/>");
--- a/pidgin/gtkimhtmltoolbar.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Wed Jul 16 21:55:08 2008 +0000
@@ -40,6 +40,8 @@
 #include "gtkthemes.h"
 #include "gtkutils.h"
 
+#include "debug.h"
+
 #include <gdk/gdkkeysyms.h>
 
 static GtkHBoxClass *parent_class = NULL;
@@ -449,12 +451,12 @@
 
 static void insert_hr_cb(GtkWidget *widget, GtkIMHtmlToolbar *toolbar)
 {
-        GtkTextIter iter;
-        GtkTextMark *ins;
+	GtkTextIter iter;
+	GtkTextMark *ins;
 	GtkIMHtmlScalable *hr;
 
-        ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)));
-        gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins);
+	ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)));
+	gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins);
 	hr = gtk_imhtml_hr_new();
 	gtk_imhtml_hr_add_to(hr, GTK_IMHTML(toolbar->imhtml), &iter);
 }
@@ -1297,6 +1299,7 @@
 	GtkWidget *insert_button;
 	GtkWidget *font_button;
 	GtkWidget *smiley_button;
+
 	GtkWidget *font_menu;
 	GtkWidget *insert_menu;
 	GtkWidget *menuitem;
--- a/pidgin/gtkimhtmltoolbar.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.h	Wed Jul 16 21:55:08 2008 +0000
@@ -76,6 +76,7 @@
 	char *sml;
 	GtkWidget *strikethrough;
 	GtkWidget *insert_hr;
+	GtkWidget *call;
 };
 
 struct _GtkIMHtmlToolbarClass {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkmedia.c	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,580 @@
+/**
+ * @file media.c Account API
+ * @ingroup core
+ *
+ * Pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <string.h>
+#include "debug.h"
+#include "internal.h"
+#include "connection.h"
+#include "media.h"
+#include "pidgin.h"
+
+#include "gtkmedia.h"
+
+#ifdef USE_VV
+
+#include <gst/interfaces/xoverlay.h>
+
+typedef enum
+{
+	/* Waiting for response */
+	PIDGIN_MEDIA_WAITING = 1,
+	/* Got request */
+	PIDGIN_MEDIA_REQUESTED,
+	/* Accepted call */
+	PIDGIN_MEDIA_ACCEPTED,
+	/* Rejected call */
+	PIDGIN_MEDIA_REJECTED,
+} PidginMediaState;
+
+struct _PidginMediaPrivate
+{
+	PurpleMedia *media;
+	GstElement *send_level;
+	GstElement *recv_level;
+
+	GtkWidget *calling;
+	GtkWidget *accept;
+	GtkWidget *reject;
+	GtkWidget *hangup;
+
+	GtkWidget *send_progress;
+	GtkWidget *recv_progress;
+
+	PidginMediaState state;
+
+	GtkWidget *display;
+	GtkWidget *local_video;
+	GtkWidget *remote_video;
+};
+
+#define PIDGIN_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PIDGIN_TYPE_MEDIA, PidginMediaPrivate))
+
+static void pidgin_media_class_init (PidginMediaClass *klass);
+static void pidgin_media_init (PidginMedia *media);
+static void pidgin_media_finalize (GObject *object);
+static void pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state);
+
+static GtkHBoxClass *parent_class = NULL;
+
+
+
+enum {
+	MESSAGE,
+	LAST_SIGNAL
+};
+static guint pidgin_media_signals[LAST_SIGNAL] = {0};
+
+enum {
+	PROP_0,
+	PROP_MEDIA,
+	PROP_SEND_LEVEL,
+	PROP_RECV_LEVEL
+};
+
+GType
+pidgin_media_get_type(void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PidginMediaClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) pidgin_media_class_init,
+			NULL,
+			NULL,
+			sizeof(PidginMedia),
+			0,
+			(GInstanceInitFunc) pidgin_media_init,
+			NULL
+		};
+		type = g_type_register_static(GTK_TYPE_HBOX, "PidginMedia", &info, 0);
+	}
+	return type;
+}
+
+
+static void
+pidgin_media_class_init (PidginMediaClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+/*	GtkContainerClass *container_class = (GtkContainerClass*)klass; */
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = pidgin_media_finalize;
+	gobject_class->set_property = pidgin_media_set_property;
+	gobject_class->get_property = pidgin_media_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_MEDIA,
+			g_param_spec_object("media",
+			"PurpleMedia",
+			"The PurpleMedia associated with this media.",
+			PURPLE_TYPE_MEDIA,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_SEND_LEVEL,
+			g_param_spec_object("send-level",
+			"Send level",
+			"The GstElement of this media's send 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+	g_object_class_install_property(gobject_class, PROP_RECV_LEVEL,
+			g_param_spec_object("recv-level",
+			"Receive level",
+			"The GstElement of this media's recv 'level'",
+			GST_TYPE_ELEMENT,
+			G_PARAM_READWRITE));
+
+	pidgin_media_signals[MESSAGE] = g_signal_new("message", G_TYPE_FROM_CLASS(klass),
+					G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					g_cclosure_marshal_VOID__STRING,
+					G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	g_type_class_add_private(klass, sizeof(PidginMediaPrivate));
+}
+
+
+static void
+pidgin_media_init (PidginMedia *media)
+{
+	media->priv = PIDGIN_MEDIA_GET_PRIVATE(media);
+	media->priv->calling = gtk_label_new_with_mnemonic("Calling...");
+	media->priv->hangup = gtk_button_new_with_label("Hangup");
+	media->priv->accept = gtk_button_new_with_label("Accept");
+	media->priv->reject = gtk_button_new_with_label("Reject");
+
+	gtk_box_pack_start(GTK_BOX(media), media->priv->calling, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(media), media->priv->hangup, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(media), media->priv->accept, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(media), media->priv->reject, FALSE, FALSE, 0);
+
+	gtk_widget_show_all(media->priv->accept);
+	gtk_widget_show_all(media->priv->reject);
+
+	media->priv->display = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+}
+
+static gboolean
+level_message_cb(GstBus *bus, GstMessage *message, PidginMedia *gtkmedia)
+{
+	const GstStructure *s;
+	const gchar *name;
+
+	gdouble rms_db;
+	gdouble percent;
+	const GValue *list;
+	const GValue *value;
+
+	GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(message));
+
+	if (message->type != GST_MESSAGE_ELEMENT)
+		return TRUE;
+
+	s = gst_message_get_structure(message);
+	name = gst_structure_get_name(s);
+
+	if (strcmp(name, "level"))
+		return TRUE;
+
+	list = gst_structure_get_value(s, "rms");
+
+	/* Only bother with the first channel. */
+	value = gst_value_list_get_value(list, 0);
+	rms_db = g_value_get_double(value);
+
+	percent = pow(10, rms_db / 20) * 5;
+
+	if(percent > 1.0)
+		percent = 1.0;
+
+	if (!strcmp(gst_element_get_name(src), "sendlevel"))	
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkmedia->priv->send_progress), percent);
+	else
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkmedia->priv->recv_progress), percent);
+
+	return TRUE;
+}
+
+
+static void
+pidgin_media_disconnect_levels(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	GstElement *element = purple_media_get_pipeline(media);
+	gulong handler_id = g_signal_handler_find(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))),
+						  G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, 
+						  NULL, G_CALLBACK(level_message_cb), gtkmedia);
+	if (handler_id)
+		g_signal_handler_disconnect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))),
+					    handler_id);
+}
+
+static void
+pidgin_media_finalize (GObject *media)
+{
+	PidginMedia *gtkmedia = PIDGIN_MEDIA(media);
+	if (gtkmedia->priv->media) {
+		pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia);
+		g_object_unref(gtkmedia->priv->media);
+	}
+	if (gtkmedia->priv->send_level)
+		gst_object_unref(gtkmedia->priv->send_level);
+	if (gtkmedia->priv->recv_level)
+		gst_object_unref(gtkmedia->priv->recv_level);
+	if (gtkmedia->priv->display)
+		gtk_widget_destroy(gtkmedia->priv->display);
+}
+
+static void
+pidgin_media_emit_message(PidginMedia *gtkmedia, const char *msg)
+{
+	g_signal_emit(gtkmedia, pidgin_media_signals[MESSAGE], 0, msg);
+}
+
+GtkWidget *
+pidgin_media_get_display_widget(GtkWidget *gtkmedia)
+{
+	return PIDGIN_MEDIA_GET_PRIVATE(gtkmedia)->display;
+}
+
+static gboolean
+create_window (GstBus *bus, GstMessage *message, PidginMedia *gtkmedia)
+{
+	char *name;
+
+	if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT)
+		return TRUE;
+
+	if (!gst_structure_has_name(message->structure, "prepare-xwindow-id"))
+		return TRUE;
+
+	name = gst_object_get_name(GST_MESSAGE_SRC (message));
+	purple_debug_info("gtkmedia", "prepare-xwindow-id object name: %s\n", name);
+
+	/* The XOverlay's name is the sink's name with a suffix */
+	if (!strncmp(name, "purplevideosink", strlen("purplevideosink")))
+		gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)),
+					     GDK_WINDOW_XWINDOW(gtkmedia->priv->remote_video->window));
+	else if (!strncmp(name, "purplelocalvideosink", strlen("purplelocalvideosink")))
+		gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)),
+					     GDK_WINDOW_XWINDOW(gtkmedia->priv->local_video->window));
+	g_free(name);
+	return TRUE;
+}
+
+static void
+pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	GstElement *element = purple_media_get_pipeline(media);
+
+	GstElement *audiosendbin = NULL, *audiosendlevel = NULL;
+	GstElement *audiorecvbin = NULL, *audiorecvlevel = NULL;
+	GstElement *videosendbin = NULL;
+	GstElement *videorecvbin = NULL;
+
+	GList *sessions = purple_media_get_session_names(media);
+	GstBus *bus;
+
+	for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
+		if (purple_media_get_session_type(media, sessions->data) & PURPLE_MEDIA_AUDIO) {
+			if (!audiosendbin)
+				purple_media_audio_init_src(&audiosendbin, &audiosendlevel);
+			if (!audiorecvbin)
+				purple_media_audio_init_recv(&audiorecvbin, &audiorecvlevel);
+			purple_media_set_src(media, sessions->data, audiosendbin);
+			purple_media_set_sink(media, sessions->data, audiorecvbin);
+		} else if (purple_media_get_session_type(media, sessions->data) & PURPLE_MEDIA_VIDEO) {
+			if (!videosendbin)
+				purple_media_video_init_src(&videosendbin);
+			if (!videorecvbin)
+				purple_media_video_init_recv(&videorecvbin);
+			purple_media_set_src(media, sessions->data, videosendbin);
+			purple_media_set_sink(media, sessions->data, videorecvbin);
+		}
+	}
+
+	if (audiosendlevel && audiorecvlevel) {
+		g_object_set(gtkmedia, "send-level", audiosendlevel,
+				       "recv-level", audiorecvlevel,
+				       NULL);
+	}
+
+	bus = gst_pipeline_get_bus(GST_PIPELINE(element));
+	gst_bus_add_signal_watch(GST_BUS(bus));
+	g_signal_connect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))),
+			 "message", G_CALLBACK(level_message_cb), gtkmedia);
+	if (videorecvbin || videosendbin)
+		gst_bus_set_sync_handler(bus, (GstBusSyncHandler)create_window, gtkmedia);
+	gst_object_unref(bus);
+}
+
+static void
+pidgin_media_wait_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_WAITING);
+}
+
+/* maybe we should have different callbacks for when we received the accept
+    and we accepted ourselves */
+static void
+pidgin_media_accept_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	GtkWidget *send_widget = NULL, *recv_widget = NULL;
+
+	GstElement *audiosendbin = NULL;
+	GstElement *audiorecvbin = NULL;
+	GstElement *videosendbin = NULL;
+	GstElement *videorecvbin = NULL;
+
+	pidgin_media_emit_message(gtkmedia, _("Call in progress."));
+	pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED);
+
+	purple_media_get_elements(media, &audiosendbin, &audiorecvbin,
+				  &videosendbin, &videorecvbin);
+
+	recv_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	send_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+
+	gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), recv_widget, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), send_widget, TRUE, TRUE, 0);
+
+	gtk_widget_show(recv_widget);
+	gtk_widget_show(send_widget);
+
+	if (videorecvbin || videosendbin) {
+		GtkWidget *aspect;
+		GtkWidget *remote_video;
+		GtkWidget *local_video;
+
+		gtk_widget_show(gtkmedia->priv->display);
+
+		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
+		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
+		gtk_box_pack_start(GTK_BOX(recv_widget), aspect, TRUE, TRUE, 0);
+
+		remote_video = gtk_drawing_area_new();
+		gtk_container_add(GTK_CONTAINER(aspect), remote_video);
+		gtk_widget_set_size_request (GTK_WIDGET(remote_video), 100, -1);
+		gtk_widget_show(remote_video);
+		gtk_widget_show(aspect);
+
+		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
+		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
+		gtk_box_pack_start(GTK_BOX(send_widget), aspect, TRUE, TRUE, 0);
+
+		local_video = gtk_drawing_area_new();
+		gtk_container_add(GTK_CONTAINER(aspect), local_video);
+		gtk_widget_show(local_video);
+		gtk_widget_show(aspect);
+
+		gtkmedia->priv->local_video = local_video;
+		gtkmedia->priv->remote_video = remote_video;
+	}
+
+	if (audiorecvbin || audiosendbin) {
+		gtk_widget_show(gtkmedia->priv->display);
+
+		gtkmedia->priv->send_progress = gtk_progress_bar_new();
+		gtkmedia->priv->recv_progress = gtk_progress_bar_new();
+
+		gtk_widget_set_size_request(gtkmedia->priv->send_progress, 10, 70);
+		gtk_widget_set_size_request(gtkmedia->priv->recv_progress, 10, 70);
+
+		gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(gtkmedia->priv->send_progress),
+						 GTK_PROGRESS_BOTTOM_TO_TOP);
+		gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(gtkmedia->priv->recv_progress),
+						 GTK_PROGRESS_BOTTOM_TO_TOP);
+
+		gtk_box_pack_start(GTK_BOX(send_widget),
+				   gtkmedia->priv->send_progress, FALSE, FALSE, 0);
+		gtk_box_pack_start(GTK_BOX(recv_widget),
+				   gtkmedia->priv->recv_progress, FALSE, FALSE, 0);
+
+		gtk_widget_show(gtkmedia->priv->send_progress);
+		gtk_widget_show(gtkmedia->priv->recv_progress);
+	}
+}
+
+static void
+pidgin_media_hangup_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_emit_message(gtkmedia, _("You have ended the call."));
+	gtk_widget_destroy(GTK_WIDGET(gtkmedia));
+}
+
+static void
+pidgin_media_got_request_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	PurpleMediaStreamType type = purple_media_get_overall_type(media);
+	gchar *message;
+
+	if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) {
+		message = g_strdup_printf(_("%s wishes to start an audio/video session with you."),
+					  purple_media_get_screenname(media));
+	} else if (type & PURPLE_MEDIA_AUDIO) {
+		message = g_strdup_printf(_("%s wishes to start an audio session with you."),
+					  purple_media_get_screenname(media));
+	} else if (type & PURPLE_MEDIA_VIDEO) {
+		message = g_strdup_printf(_("%s wishes to start a video session with you."),
+					  purple_media_get_screenname(media));
+	} else {
+		return;
+	}
+
+	pidgin_media_emit_message(gtkmedia, message);
+	g_free(message);
+}
+
+static void
+pidgin_media_got_hangup_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_emit_message(gtkmedia, _("The call has been terminated."));
+	gtk_widget_destroy(GTK_WIDGET(gtkmedia));
+}
+
+static void
+pidgin_media_reject_cb(PurpleMedia *media, PidginMedia *gtkmedia)
+{
+	pidgin_media_emit_message(gtkmedia, _("You have rejected the call."));
+	gtk_widget_destroy(GTK_WIDGET(gtkmedia));
+}
+
+static void
+pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	PidginMedia *media;
+	g_return_if_fail(PIDGIN_IS_MEDIA(object));
+
+	media = PIDGIN_MEDIA(object);
+	switch (prop_id) {
+		case PROP_MEDIA:
+			if (media->priv->media)
+				g_object_unref(media->priv->media);
+			media->priv->media = g_value_get_object(value);
+			g_object_ref(media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->accept), "clicked", 
+				 G_CALLBACK(purple_media_accept), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->reject), "clicked",
+				 G_CALLBACK(purple_media_reject), media->priv->media);
+			g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "clicked",
+				 G_CALLBACK(purple_media_hangup), media->priv->media);
+
+			g_signal_connect(G_OBJECT(media->priv->media), "accepted",
+				G_CALLBACK(pidgin_media_accept_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media) ,"ready",
+				G_CALLBACK(pidgin_media_ready_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media) ,"wait",
+				G_CALLBACK(pidgin_media_wait_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "hangup",
+				G_CALLBACK(pidgin_media_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "reject",
+				G_CALLBACK(pidgin_media_reject_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-request",
+				G_CALLBACK(pidgin_media_got_request_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-hangup",
+				G_CALLBACK(pidgin_media_got_hangup_cb), media);
+			g_signal_connect(G_OBJECT(media->priv->media), "got-accept",
+				G_CALLBACK(pidgin_media_accept_cb), media);
+			break;
+		case PROP_SEND_LEVEL:
+			if (media->priv->send_level)
+				gst_object_unref(media->priv->send_level);
+			media->priv->send_level = g_value_get_object(value);
+			g_object_ref(media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			if (media->priv->recv_level)
+				gst_object_unref(media->priv->recv_level);
+			media->priv->recv_level = g_value_get_object(value);
+			g_object_ref(media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	PidginMedia *media;
+	g_return_if_fail(PIDGIN_IS_MEDIA(object));
+
+	media = PIDGIN_MEDIA(object);
+
+	switch (prop_id) {
+		case PROP_MEDIA:
+			g_value_set_object(value, media->priv->media);
+			break;
+		case PROP_SEND_LEVEL:
+			g_value_set_object(value, media->priv->send_level);
+			break;
+		case PROP_RECV_LEVEL:
+			g_value_set_object(value, media->priv->recv_level);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+GtkWidget *
+pidgin_media_new(PurpleMedia *media)
+{
+	PidginMedia *gtkmedia = g_object_new(pidgin_media_get_type(),
+					     "media", media, NULL);
+	return GTK_WIDGET(gtkmedia);
+}
+
+static void
+pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state)
+{
+	gtkmedia->priv->state = state;
+	switch (state) {
+		case PIDGIN_MEDIA_WAITING:
+			gtk_widget_show(gtkmedia->priv->calling);
+			gtk_widget_hide(gtkmedia->priv->accept);
+			gtk_widget_hide(gtkmedia->priv->reject);
+			gtk_widget_show(gtkmedia->priv->hangup);
+			break;
+		case PIDGIN_MEDIA_REQUESTED:
+			gtk_widget_hide(gtkmedia->priv->calling);
+			gtk_widget_show(gtkmedia->priv->accept);
+			gtk_widget_show(gtkmedia->priv->reject);
+			gtk_widget_hide(gtkmedia->priv->hangup);
+			break;
+		case PIDGIN_MEDIA_ACCEPTED:
+			gtk_widget_show(gtkmedia->priv->hangup);
+			gtk_widget_hide(gtkmedia->priv->calling);
+			gtk_widget_hide(gtkmedia->priv->accept);
+			gtk_widget_hide(gtkmedia->priv->reject);
+			break;
+		default:
+			break;
+	}
+}
+
+#endif  /* USE_VV */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkmedia.h	Wed Jul 16 21:55:08 2008 +0000
@@ -0,0 +1,70 @@
+/**
+ * @file media.h Account API
+ * @ingroup core
+ *
+ * Pidgin 
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __GTKMEDIA_H_
+#define __GTKMEDIA_H_
+
+#ifdef USE_VV
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+#include "connection.h"
+
+G_BEGIN_DECLS
+
+#define PIDGIN_TYPE_MEDIA            (pidgin_media_get_type())
+#define PIDGIN_MEDIA(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MEDIA, PidginMedia))
+#define PIDGIN_MEDIA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MEDIA, PidginMediaClass))
+#define PIDGIN_IS_MEDIA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MEDIA))
+#define PIDGIN_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MEDIA))
+#define PIDGIN_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MEDIA, PidginMediaClass))
+
+typedef struct _PidginMedia PidginMedia;
+typedef struct _PidginMediaClass PidginMediaClass;
+typedef struct _PidginMediaPrivate PidginMediaPrivate;
+
+struct _PidginMediaClass
+{
+	GtkHBoxClass parent_class;
+};
+
+struct _PidginMedia
+{
+	GtkHBox parent;
+	PidginMediaPrivate *priv;
+};
+
+GType pidgin_media_get_type(void);
+
+GtkWidget *pidgin_media_new(PurpleMedia *media);
+GtkWidget *pidgin_media_get_display_widget(GtkWidget *gtkmedia);
+
+G_END_DECLS
+
+#endif  /* USE_VV */
+
+
+#endif  /* __GTKMEDIA_H_ */
--- a/pidgin/gtkprefs.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkprefs.c	Wed Jul 16 21:55:08 2008 +0000
@@ -145,12 +145,10 @@
 
 	if (type == PURPLE_PREF_INT) {
 		int_value = GPOINTER_TO_INT(g_object_get_data(w, "value"));
-
 		purple_prefs_set_int(key, int_value);
 	}
 	else if (type == PURPLE_PREF_STRING) {
 		str_value = (const char *)g_object_get_data(w, "value");
-
 		purple_prefs_set_string(key, str_value);
 	}
 	else if (type == PURPLE_PREF_BOOLEAN) {
@@ -945,7 +943,7 @@
 					_("Never"), "never",
 					NULL);
 	gtk_size_group_add_widget(sg, label);
-        gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
 
 	vbox = pidgin_make_frame(ret, _("Conversation Window Hiding"));
 	label = pidgin_prefs_dropdown(vbox, _("_Hide new IM conversations:"),
@@ -955,7 +953,7 @@
 					_("Always"), "always",
 					NULL);
 	gtk_size_group_add_widget(sg, label);
-        gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
 
 
 	/* All the tab options! */
@@ -990,7 +988,7 @@
 #endif
 					NULL);
 	gtk_size_group_add_widget(sg, label);
-        gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
 
 	names = pidgin_conv_placement_get_options();
 	label = pidgin_prefs_dropdown_from_list(vbox2, _("N_ew conversations:"),
@@ -2019,6 +2017,231 @@
 	return ret;
 }
 
+#ifdef USE_VV
+
+/* get a GList of pairs name / device */
+static GList *
+get_device_items(const GstElement *element, 
+		 const GList *devices)
+{
+	GList *ret = NULL;
+
+	for(; devices ; devices = devices->next) {
+		gchar *name = purple_media_get_device_name(GST_ELEMENT(element), devices->data);
+		ret = g_list_append(ret, name);
+		ret = g_list_append(ret, g_value_dup_string(devices->data));
+	}
+
+	return ret;
+}
+
+/*
+ * Test functions to run video preview
+ */
+static gboolean
+preview_video_bus_call(GstBus *bus, GstMessage *msg, gpointer pipeline)
+{
+	switch(GST_MESSAGE_TYPE(msg)) {
+		case GST_MESSAGE_EOS:
+			purple_debug_info("preview-video", "End of Stream\n");
+			break;
+		case GST_MESSAGE_ERROR: {
+			gchar *debug = NULL;
+			GError *err = NULL;
+
+			gst_message_parse_error(msg, &err, &debug);
+
+			purple_debug_error("preview-video", "Error: %s\n", err->message);
+			g_error_free(err);
+
+			if (debug) {
+				purple_debug_error("preview-video", "details: %s\n", debug);
+				g_free (debug);
+			}
+			break;
+		}
+		default:
+			return TRUE;
+	}
+
+	gst_element_set_state(pipeline, GST_STATE_NULL);
+	gst_object_unref(GST_PIPELINE(pipeline));
+	return TRUE;
+}
+
+static void
+preview_button_clicked(GtkWidget *widget, gpointer *data)
+{
+	const char *plugin = purple_prefs_get_string("/purple/media/video/plugin");
+	const char *device = purple_prefs_get_string("/purple/media/video/device");
+	GstBus *bus;
+
+	/* create a preview window... */
+	GstElement *pipeline = NULL;
+	GError *p_err;
+
+	gchar *test_pipeline_str = NULL;
+
+	if (strlen(device) > 0)
+		test_pipeline_str = g_strdup_printf("%s device=\"%s\" !" \
+						    " ffmpegcolorspace !" \
+						    " autovideosink",
+						    plugin, device);
+	else
+		test_pipeline_str = g_strdup_printf("%s ! ffmpegcolorspace !" \
+						    " autovideosink", plugin);
+
+	pipeline = gst_parse_launch (test_pipeline_str, &p_err);
+
+	g_free(test_pipeline_str);
+
+	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
+	gst_bus_add_watch(bus, preview_video_bus_call, pipeline);
+	gst_object_unref(bus);
+
+	gst_element_set_state(pipeline, GST_STATE_PLAYING);
+}
+
+static void
+media_plugin_changed_cb(const gchar *name, PurplePrefType type,
+			gconstpointer value, gpointer data)
+{
+	GtkWidget *hbox = data;
+	GtkWidget *dd = NULL;
+	GtkWidget *preview_button = NULL;
+	const char *plugin = value;
+	const char *device = purple_prefs_get_string("/purple/media/video/device");
+	GstElement *video = purple_media_get_element(plugin);
+	GList *video_devices = purple_media_get_devices(video);
+	GList *video_items = get_device_items(video, video_devices);
+	GList *list;
+	g_list_free(video_devices);
+
+	if (video_items == NULL) {
+		video_items = g_list_prepend(video_items, g_strdup(""));
+		video_items = g_list_prepend(video_items, g_strdup("Default"));
+	}
+
+	if (g_list_find(video_items, device) == NULL)
+	{
+		purple_prefs_set_string("/purple/media/video/device", 
+					g_list_next(video_items)->data);
+	}
+
+	list = gtk_container_get_children(GTK_CONTAINER(hbox));
+
+	while (list) {
+		gtk_widget_destroy(list->data);
+		list = g_list_delete_link(list, list);
+	}
+
+	dd = pidgin_prefs_dropdown_from_list(hbox, _("_Device:"), PURPLE_PREF_STRING,
+					     "/purple/media/video/device",
+					     video_items);
+
+	gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
+
+	preview_button = gtk_button_new_with_mnemonic(_("_Preview"));
+	g_signal_connect(G_OBJECT(preview_button), "clicked",
+			 G_CALLBACK(preview_button_clicked), video);
+
+	gtk_container_add(GTK_CONTAINER(hbox), preview_button);
+
+	gtk_widget_show_all(hbox);
+}
+
+static GtkWidget *
+media_page()
+{
+	GtkWidget *ret;
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *dd;
+	GtkWidget *preview_button;
+	GtkSizeGroup *sg;
+	const char *plugin = purple_prefs_get_string("/purple/media/video/plugin");
+	const char *device = purple_prefs_get_string("/purple/media/video/device");
+
+	GstElement *video = purple_media_get_element(plugin);
+	GstElement *audio = purple_media_get_element("alsasrc");
+
+	GList *video_devices = purple_media_get_devices(video);
+	GList *audio_devices = purple_media_get_devices(audio);
+
+	GList *video_items = get_device_items(video, video_devices);
+	GList *audio_items = get_device_items(audio, audio_devices);
+
+	g_list_free(video_devices);
+	g_list_free(audio_devices);
+
+	if (video_items == NULL) {
+		video_items = g_list_prepend(video_items, "");
+		video_items = g_list_prepend(video_items, "Default");
+	}
+
+	if (g_list_find(video_items, device) == NULL)
+	{
+		purple_prefs_set_string("/purple/media/video/device", 
+					g_list_next(video_items)->data);
+	}
+
+	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
+
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	vbox = pidgin_make_frame (ret, _("Video Input"));
+
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	dd = pidgin_prefs_dropdown(vbox, _("_Plugin:"), PURPLE_PREF_STRING,
+				   "/purple/media/video/plugin",
+				   _("Default"), "gconfvideosrc",
+				   _("Video4Linux"), "v4lsrc",
+				   _("Video4Linux2"), "v4l2src",
+				   _("Video Test Source"), "videotestsrc",
+				   NULL);
+
+	gtk_size_group_add_widget(sg, dd);
+	gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
+
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	dd = pidgin_prefs_dropdown_from_list(hbox, _("_Device:"), PURPLE_PREF_STRING,
+			"/purple/media/video/device",
+			video_items);
+
+	purple_prefs_connect_callback(prefs, "/purple/media/video/plugin",
+				      media_plugin_changed_cb, hbox);
+
+	g_signal_connect_swapped(hbox, "destroy",
+				 G_CALLBACK(purple_prefs_disconnect_by_handle), hbox);
+
+	gtk_size_group_add_widget(sg, dd);
+	gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
+
+	preview_button = gtk_button_new_with_mnemonic(_("_Preview"));
+	g_signal_connect(G_OBJECT(preview_button), "clicked",
+			G_CALLBACK(preview_button_clicked), video);
+
+	gtk_container_add(GTK_CONTAINER(hbox), preview_button);
+	gtk_container_add(GTK_CONTAINER(vbox), hbox);
+
+	vbox = pidgin_make_frame (ret, _("Audio Input"));
+	dd = pidgin_prefs_dropdown_from_list(vbox, _("_Device:"), PURPLE_PREF_STRING,
+			"/purple/media/audio/device",
+			audio_items);
+
+	gtk_size_group_add_widget(sg, dd);
+	gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
+
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+	gtk_widget_show_all(ret);
+
+	return ret;
+}
+
+#endif	/* USE_VV */
 
 static void
 set_idle_away(PurpleSavedStatus *status)
@@ -2144,6 +2367,10 @@
 	prefs_notebook_add_page(_("Conversations"), conv_page(), notebook_page++);
 	prefs_notebook_add_page(_("Smiley Themes"), theme_page(), notebook_page++);
 	prefs_notebook_add_page(_("Sounds"), sound_page(), notebook_page++);
+
+#ifdef USE_VV
+	prefs_notebook_add_page(_("Media"), media_page(), notebook_page++);
+#endif	
 	prefs_notebook_add_page(_("Network"), network_page(), notebook_page++);
 #ifndef _WIN32
 	/* We use the registered default browser in windows */
@@ -2269,6 +2496,15 @@
 	purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/smileys/theme",
 								smiley_theme_pref_cb, NULL);
 
+#ifdef USE_VV
+	purple_prefs_add_none("/purple/media");
+	purple_prefs_add_none("/purple/media/video");
+	purple_prefs_add_string("/purple/media/video/plugin", "gconfvideosrc");
+	purple_prefs_add_string("/purple/media/video/device", "");
+	purple_prefs_add_none("/purple/media/audio");
+	purple_prefs_add_string("/purple/media/audio/device", "");
+#endif /* USE_VV */
+
 	pidgin_prefs_update_old();
 }
 
--- a/pidgin/gtkprefs.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/gtkprefs.h	Wed Jul 16 21:55:08 2008 +0000
@@ -29,6 +29,7 @@
 
 #include "prefs.h"
 
+
 /**
  * Initializes all UI-specific preferences.
  */
--- a/pidgin/pidginstock.c	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/pidginstock.c	Wed Jul 16 21:55:08 2008 +0000
@@ -170,6 +170,12 @@
 	{ PIDGIN_STOCK_TOOLBAR_SEND_FILE, "toolbar", "send-file.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_TRANSFER, "toolbar", "transfer.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 
+#ifdef USE_VV
+	{ PIDGIN_STOCK_TOOLBAR_AUDIO_CALL, "toolbar", "audio-call.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
+	{ PIDGIN_STOCK_TOOLBAR_VIDEO_CALL, "toolbar", "video-call.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
+	{ PIDGIN_STOCK_TOOLBAR_AUDIO_VIDEO_CALL, "toolbar", "audio-video-call.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
+#endif
+
 	{ PIDGIN_STOCK_TRAY_AVAILABLE, "tray", "tray-online.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TRAY_INVISIBLE, "tray", "tray-invisible.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TRAY_AWAY, "tray", "tray-away.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL  },
--- a/pidgin/pidginstock.h	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/pidginstock.h	Wed Jul 16 21:55:08 2008 +0000
@@ -130,6 +130,11 @@
 #define PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR "pidgin-select-avatar"
 #define PIDGIN_STOCK_TOOLBAR_SEND_FILE    "pidgin-send-file"
 #define PIDGIN_STOCK_TOOLBAR_TRANSFER     "pidgin-transfer"
+#ifdef USE_VV
+#define PIDGIN_STOCK_TOOLBAR_AUDIO_CALL   "pidgin-audio-call"
+#define PIDGIN_STOCK_TOOLBAR_VIDEO_CALL   "pidgin-video-call"
+#define PIDGIN_STOCK_TOOLBAR_AUDIO_VIDEO_CALL "pidgin-audio-video-call"
+#endif
 
 /* Tray icons */
 #define PIDGIN_STOCK_TRAY_AVAILABLE       "pidgin-tray-available"
--- a/pidgin/pixmaps/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/pixmaps/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -553,6 +553,7 @@
 		toolbar/16/scalable/font-size-up.svg
 
 TOOLBAR_16 = \
+		toolbar/16/audio-call.png \
 		toolbar/16/change-bgcolor.png \
 		toolbar/16/change-fgcolor.png \
 		toolbar/16/emote-select.png \
@@ -566,7 +567,8 @@
 		toolbar/16/plugins.png \
 		toolbar/16/send-file.png \
 		toolbar/16/transfer.png \
-		toolbar/16/unblock.png
+		toolbar/16/unblock.png \
+		toolbar/16/video-call.png
 
 TOOLBAR_22_SCALABLE = \
 		toolbar/22/scalable/select-avatar.svg
Binary file pidgin/pixmaps/toolbar/16/audio-call.png has changed
Binary file pidgin/pixmaps/toolbar/16/video-call.png has changed
--- a/pidgin/plugins/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/plugins/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -123,6 +123,7 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS) \
 	$(PLUGIN_CFLAGS)
 
--- a/pidgin/plugins/cap/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/plugins/cap/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -15,7 +15,7 @@
 
 endif
 
-cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS)
+cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS) $(FARSIGHT_LIBS) $(GSTPROPS_LIBS)
 
 AM_CPPFLAGS = \
 	-DDATADIR=\"$(datadir)\" \
@@ -24,6 +24,8 @@
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
 	$(GTK_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
+	$(GSTPROPS_CFLAGS) \
 	$(SQLITE3_CFLAGS)
 
 EXTRA_DIST = Makefile.mingw
--- a/pidgin/plugins/gestures/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/plugins/gestures/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -23,4 +23,5 @@
 	-I$(top_builddir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS)
--- a/pidgin/plugins/gevolution/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/plugins/gevolution/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -26,4 +26,5 @@
 	-I$(top_srcdir)/pidgin \
 	$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
 	$(DEBUG_CFLAGS) \
-	$(GTK_CFLAGS)
+	$(GTK_CFLAGS) \
+	$(FARSIGHT_CFLAGS)
--- a/pidgin/plugins/musicmessaging/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/plugins/musicmessaging/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -40,5 +40,6 @@
 	-I$(top_srcdir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS) \
 	$(DBUS_CFLAGS)
--- a/pidgin/plugins/ticker/Makefile.am	Wed Jul 16 09:16:52 2008 +0000
+++ b/pidgin/plugins/ticker/Makefile.am	Wed Jul 16 21:55:08 2008 +0000
@@ -24,4 +24,5 @@
 	-I$(top_builddir)/libpurple \
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
+	$(FARSIGHT_CFLAGS) \
 	$(GTK_CFLAGS)
--- a/po/POTFILES.in	Wed Jul 16 09:16:52 2008 +0000
+++ b/po/POTFILES.in	Wed Jul 16 21:55:08 2008 +0000
@@ -8,6 +8,7 @@
 finch/gntdebug.c
 finch/gntft.c
 finch/gntlog.c
+finch/gntmedia.c
 finch/gntnotify.c
 finch/gntplugin.c
 finch/gntpounce.c
@@ -86,6 +87,7 @@
 libpurple/protocols/jabber/buddy.c
 libpurple/protocols/jabber/chat.c
 libpurple/protocols/jabber/jabber.c
+libpurple/protocols/jabber/jingle.c
 libpurple/protocols/jabber/libxmpp.c
 libpurple/protocols/jabber/message.c
 libpurple/protocols/jabber/parser.c

mercurial